From fd5d07b2f265c66b039f5d0f8a57336c7b149440 Mon Sep 17 00:00:00 2001 From: seven332 Date: Sat, 7 Mar 2015 15:56:40 +0800 Subject: [PATCH] Init --- .gitignore | 6 + .idea/.name | 1 + .idea/compiler.xml | 23 + .idea/copyright/profiles_settings.xml | 3 + .idea/encodings.xml | 5 + .idea/gradle.xml | 18 + .idea/inspectionProfiles/Project_Default.xml | 15 + .../inspectionProfiles/profiles_settings.xml | 7 + .idea/misc.xml | 10 + .idea/modules.xml | 10 + .idea/scopes/scope_settings.xml | 5 + .idea/vcs.xml | 7 + EhViewer.iml | 19 + app/.gitignore | 1 + app/app.iml | 95 + app/build.gradle | 31 + app/libs/libGoogleAnalyticsServices.jar | Bin 0 -> 511310 bytes app/proguard-rules.pro | 49 + .../com/hippo/ehviewer/ApplicationTest.java | 13 + app/src/main/AndroidManifest.xml | 146 ++ .../main/assets/fonts/GloriaHallelujah.ttf | Bin 0 -> 60052 bytes app/src/main/assets/fonts/face.ttf | Bin 0 -> 4064 bytes .../java/com/hippo/ehviewer/Analytics.java | 62 + .../java/com/hippo/ehviewer/AppContext.java | 174 ++ .../java/com/hippo/ehviewer/AppHandler.java | 60 + .../java/com/hippo/ehviewer/ImageLoader.java | 181 ++ .../com/hippo/ehviewer/NotificationId.java | 22 + .../ehviewer/SimpleSuggestionProvider.java | 28 + .../com/hippo/ehviewer/StartActivity.java | 256 +++ .../java/com/hippo/ehviewer/UpdateHelper.java | 258 +++ .../hippo/ehviewer/app/DirSelectDialog.java | 82 + .../ehviewer/app/MaterialAlertDialog.java | 504 +++++ .../ehviewer/app/MaterialProgressDialog.java | 289 +++ .../hippo/ehviewer/cache/DiskLruCache.java | 969 ++++++++ .../com/hippo/ehviewer/cache/ImageCache.java | 721 ++++++ .../com/hippo/ehviewer/cache/LruCache.java | 333 +++ .../ehviewer/cardview/CardViewSalon.java | 54 + .../ehviewer/cardview/RoundRectDrawable.java | 164 ++ .../cardview/RoundRectDrawableWithShadow.java | 192 ++ .../hippo/ehviewer/data/ApiGalleryDetail.java | 106 + .../hippo/ehviewer/data/ApiGalleryInfo.java | 82 + .../java/com/hippo/ehviewer/data/Comment.java | 29 + .../java/com/hippo/ehviewer/data/Data.java | 934 ++++++++ .../com/hippo/ehviewer/data/DownloadInfo.java | 63 + .../hippo/ehviewer/data/GalleryDetail.java | 106 + .../com/hippo/ehviewer/data/GalleryInfo.java | 174 ++ .../hippo/ehviewer/data/GalleryPopular.java | 54 + .../com/hippo/ehviewer/data/HistoryItem.java | 73 + .../hippo/ehviewer/data/LargePreviewList.java | 126 ++ .../com/hippo/ehviewer/data/ListUrls.java | 334 +++ .../hippo/ehviewer/data/LofiDetailImpl.java | 30 + .../ehviewer/data/LofiGalleryDetail.java | 104 + .../hippo/ehviewer/data/LofiGalleryInfo.java | 86 + .../ehviewer/data/NormalPreviewList.java | 182 ++ .../com/hippo/ehviewer/data/PreviewImpl.java | 52 + .../com/hippo/ehviewer/data/PreviewList.java | 77 + .../java/com/hippo/ehviewer/data/Tag.java | 42 + .../ehviewer/drawable/BatteryDrawable.java | 172 ++ .../drawable/FreeMaterialDrawable.java | 187 ++ .../drawable/MaterialIndicatorDrawable.java | 695 ++++++ .../drawable/MaterialProgressDrawable.java | 446 ++++ .../hippo/ehviewer/drawable/OvalDrawable.java | 68 + .../ehviewer/effect/DrawableTransition.java | 87 + .../hippo/ehviewer/effect/ripple/Ripple.java | 423 ++++ .../effect/ripple/RippleBackground.java | 285 +++ .../effect/ripple/RippleDrawable.java | 579 +++++ .../ehviewer/effect/ripple/RippleOwner.java | 29 + .../ehviewer/effect/ripple/RippleSalon.java | 55 + .../ehclient/AddToFavoriteParser.java | 31 + .../hippo/ehviewer/ehclient/DetailParser.java | 324 +++ .../ehviewer/ehclient/DetailUrlParser.java | 41 + .../ehviewer/ehclient/EdDetailParser.java | 131 ++ .../com/hippo/ehviewer/ehclient/EhClient.java | 1424 ++++++++++++ .../com/hippo/ehviewer/ehclient/EhInfo.java | 267 +++ .../hippo/ehviewer/ehclient/ExDownloader.java | 922 ++++++++ .../ehclient/ExDownloaderManager.java | 61 + .../ehviewer/ehclient/ImagePageParser.java | 85 + .../hippo/ehviewer/ehclient/ListParser.java | 230 ++ .../ehviewer/ehclient/LofiDetailParser.java | 56 + .../hippo/ehviewer/ehclient/LoginParser.java | 51 + .../hippo/ehviewer/ehclient/RateParser.java | 40 + .../hippo/ehviewer/ehclient/VoteParser.java | 38 + .../ehviewer/exception/FileException.java | 64 + .../ehviewer/exception/NoSdCardException.java | 21 + .../ehviewer/exception/ParserException.java | 23 + .../exception/StopRequestException.java | 21 + .../hippo/ehviewer/gallery/GalleryView.java | 1969 +++++++++++++++++ .../ehviewer/gallery/anim/Animation.java | 92 + .../gallery/anim/CanvasAnimation.java | 25 + .../anim/StateTransitionAnimation.java | 176 ++ .../hippo/ehviewer/gallery/data/ImageSet.java | 297 +++ .../gallery/glrenderer/BasicTexture.java | 218 ++ .../gallery/glrenderer/BitmapTexture.java | 53 + .../gallery/glrenderer/CanvasTexture.java | 54 + .../gallery/glrenderer/ColorTexture.java | 63 + .../gallery/glrenderer/ExtTexture.java | 60 + .../gallery/glrenderer/FadeInTexture.java | 43 + .../gallery/glrenderer/FadeOutTexture.java | 42 + .../gallery/glrenderer/FadeTexture.java | 81 + .../ehviewer/gallery/glrenderer/GLCanvas.java | 215 ++ .../gallery/glrenderer/GLES11Canvas.java | 995 +++++++++ .../gallery/glrenderer/GLES11IdImpl.java | 68 + .../gallery/glrenderer/GLES20Canvas.java | 1011 +++++++++ .../gallery/glrenderer/GLES20IdImpl.java | 42 + .../ehviewer/gallery/glrenderer/GLId.java | 33 + .../ehviewer/gallery/glrenderer/GLPaint.java | 46 + .../gallery/glrenderer/ImageTexture.java | 142 ++ .../gallery/glrenderer/MovieTexture.java | 60 + .../gallery/glrenderer/MultiLineTexture.java | 52 + .../gallery/glrenderer/NinePatchChunk.java | 82 + .../gallery/glrenderer/NinePatchTexture.java | 425 ++++ .../gallery/glrenderer/RawTexture.java | 73 + .../gallery/glrenderer/ResourceTexture.java | 52 + .../gallery/glrenderer/StringTexture.java | 87 + .../ehviewer/gallery/glrenderer/Texture.java | 44 + .../gallery/glrenderer/TextureUploader.java | 105 + .../gallery/glrenderer/TiledTexture.java | 356 +++ .../ehviewer/gallery/glrenderer/Uploaded.java | 25 + .../gallery/glrenderer/UploadedTexture.java | 297 +++ .../ehviewer/gallery/image/GifImage.java | 113 + .../hippo/ehviewer/gallery/image/Image.java | 133 ++ .../ehviewer/gallery/ui/AnimationTime.java | 45 + .../ehviewer/gallery/ui/DownUpDetector.java | 61 + .../com/hippo/ehviewer/gallery/ui/GLRoot.java | 53 + .../hippo/ehviewer/gallery/ui/GLRootView.java | 585 +++++ .../com/hippo/ehviewer/gallery/ui/GLView.java | 469 ++++ .../gallery/ui/GestureRecognizer.java | 167 ++ .../gallery/ui/OrientationSource.java | 22 + .../gallery/ui/SynchronizedHandler.java | 41 + .../hippo/ehviewer/gallery/ui/TimeRunner.java | 159 ++ .../ehviewer/gallery/util/GalleryUtils.java | 53 + .../gallery/util/MotionEventHelper.java | 120 + .../hippo/ehviewer/network/HttpHelper.java | 1104 +++++++++ .../com/hippo/ehviewer/network/Network.java | 41 + .../hippo/ehviewer/network/UrlBuilder.java | 57 + .../ehviewer/preference/ColorPreference.java | 246 ++ .../ehviewer/preference/DialogPreference.java | 113 + .../preference/EditTextPreference.java | 139 ++ .../ehviewer/preference/ListPreference.java | 261 +++ .../preference/NumberOfColumnsPreference.java | 129 ++ .../preference/SeekBarDialogPreference.java | 106 + .../receiver/NetworkChangeReceiver.java | 42 + .../ehviewer/service/DownloadService.java | 479 ++++ .../com/hippo/ehviewer/tile/RectDrawable.java | 84 + .../com/hippo/ehviewer/tile/TileSalon.java | 32 + .../com/hippo/ehviewer/ui/AbsActivity.java | 52 + .../ehviewer/ui/AbsPreferenceActivity.java | 51 + .../ehviewer/ui/AbsTranslucentActivity.java | 38 + .../hippo/ehviewer/ui/DownloadActivity.java | 397 ++++ .../hippo/ehviewer/ui/FavouriteActivity.java | 594 +++++ .../hippo/ehviewer/ui/GalleryActivity.java | 551 +++++ .../ehviewer/ui/GalleryDetailActivity.java | 1310 +++++++++++ .../ehviewer/ui/GalleryListActivity.java | 1624 ++++++++++++++ .../hippo/ehviewer/ui/HistoryActivity.java | 238 ++ .../com/hippo/ehviewer/ui/SecretActivity.java | 81 + .../hippo/ehviewer/ui/SettingsActivity.java | 921 ++++++++ .../hippo/ehviewer/util/AnimatorUtils.java | 30 + .../hippo/ehviewer/util/AutoExpandArray.java | 84 + .../com/hippo/ehviewer/util/BgThread.java | 62 + .../java/com/hippo/ehviewer/util/Config.java | 643 ++++++ .../com/hippo/ehviewer/util/Constants.java | 33 + .../java/com/hippo/ehviewer/util/Crash.java | 209 ++ .../com/hippo/ehviewer/util/DateUtils.java | 81 + .../com/hippo/ehviewer/util/DialogUtils.java | 33 + .../java/com/hippo/ehviewer/util/EhUtils.java | 124 ++ .../util/FastByteArrayOutputStream.java | 227 ++ .../com/hippo/ehviewer/util/Favorite.java | 127 ++ .../hippo/ehviewer/util/FullScreenHelper.java | 156 ++ .../com/hippo/ehviewer/util/IntArray.java | 60 + .../java/com/hippo/ehviewer/util/Log.java | 149 ++ .../com/hippo/ehviewer/util/MathUtils.java | 249 +++ .../java/com/hippo/ehviewer/util/Secret.java | 89 + .../com/hippo/ehviewer/util/SqlUtils.java | 55 + .../java/com/hippo/ehviewer/util/Theme.java | 84 + .../main/java/com/hippo/ehviewer/util/Ui.java | 376 ++++ .../java/com/hippo/ehviewer/util/Utils.java | 743 +++++++ .../com/hippo/ehviewer/util/ViewUtils.java | 205 ++ .../hippo/ehviewer/util/ZInterpolator.java | 38 + .../ehviewer/widget/ActionableToastBar.java | 229 ++ .../ehviewer/widget/AlphaPatternDrawable.java | 132 ++ .../hippo/ehviewer/widget/AutoWrapLayout.java | 283 +++ .../hippo/ehviewer/widget/BatteryView.java | 158 ++ .../hippo/ehviewer/widget/CategoryTable.java | 106 + .../hippo/ehviewer/widget/CheckTextView.java | 80 + .../ehviewer/widget/ColorPickerView.java | 947 ++++++++ .../com/hippo/ehviewer/widget/ColorView.java | 59 + .../hippo/ehviewer/widget/DrawerListView.java | 115 + .../ehviewer/widget/ExpandableListItem.java | 24 + .../ehviewer/widget/ExpandingListView.java | 563 +++++ .../ehviewer/widget/FileExplorerView.java | 274 +++ .../hippo/ehviewer/widget/FitWindowView.java | 82 + .../ehviewer/widget/FixedAspectImageView.java | 248 +++ .../ehviewer/widget/FloatingActionButton.java | 151 ++ .../ehviewer/widget/GalleryListView.java | 795 +++++++ .../ehviewer/widget/HotspotTouchHelper.java | 299 +++ .../hippo/ehviewer/widget/Hotspotable.java | 21 + .../ehviewer/widget/LinkifyTextView.java | 104 + .../hippo/ehviewer/widget/LoadImageView.java | 160 ++ .../ehviewer/widget/MaterialProgress.java | 82 + .../hippo/ehviewer/widget/MaterialToast.java | 64 + .../hippo/ehviewer/widget/PrefixEditText.java | 93 + .../ehviewer/widget/ProgressiveRatingBar.java | 77 + .../ehviewer/widget/ProgressiveTextView.java | 99 + .../com/hippo/ehviewer/widget/RatingView.java | 64 + .../hippo/ehviewer/widget/RefreshLayout.java | 238 ++ .../ehviewer/widget/RefreshTextView.java | 155 ++ .../ehviewer/widget/ResponedScrollView.java | 86 + .../com/hippo/ehviewer/widget/SearchView.java | 64 + .../ehviewer/widget/SimpleGridLayout.java | 249 +++ .../ehviewer/widget/SlidingDrawerLayout.java | 1184 ++++++++++ .../hippo/ehviewer/widget/SlidingLayout.java | 598 +++++ .../ehviewer/widget/SuggestionHelper.java | 90 + .../ehviewer/widget/SuggestionTextView.java | 124 ++ .../hippo/ehviewer/widget/SuperButton.java | 119 + .../widget/SuperSwipeRefreshLayout.java | 501 +++++ .../ehviewer/widget/SwipeProgressBar.java | 337 +++ .../hippo/ehviewer/widget/TagListView.java | 833 +++++++ .../hippo/ehviewer/widget/TagsAdapter.java | 83 + .../com/hippo/ehviewer/widget/TextClock.java | 272 +++ .../ehviewer/widget/TransitionLayout.java | 87 + .../widget/recyclerview/EasyRecyclerView.java | 889 ++++++++ .../LinearDividerItemDecoration.java | 162 ++ .../recyclerview/MarginItemDecoration.java | 46 + .../windowsanimate/AnimateCanvas.java | 48 + .../ehviewer/windowsanimate/BitmapSprite.java | 54 + .../windowsanimate/RippleHelpDrawable.java | 120 + .../ehviewer/windowsanimate/RippleSprite.java | 470 ++++ .../ehviewer/windowsanimate/RoundSprite.java | 46 + .../hippo/ehviewer/windowsanimate/Sprite.java | 42 + .../windowsanimate/WindowsAnimate.java | 283 +++ app/src/main/jniLibs/armeabi-v7a/libimage.so | Bin 0 -> 279816 bytes app/src/main/jniLibs/armeabi/libimage.so | Bin 0 -> 287996 bytes app/src/main/jniLibs/x86/libimage.so | Bin 0 -> 597104 bytes app/src/main/res/anim/anim_fade_in.xml | 20 + app/src/main/res/anim/anim_fade_out.xml | 20 + app/src/main/res/anim/fade_in.xml | 22 + app/src/main/res/anim/fade_out.xml | 22 + .../main/res/drawable-hdpi/btn_check_off.png | Bin 0 -> 292 bytes .../res/drawable-hdpi/btn_check_off_dark.png | Bin 0 -> 291 bytes .../btn_check_off_dark_disabled.png | Bin 0 -> 295 bytes .../drawable-hdpi/btn_check_off_disabled.png | Bin 0 -> 285 bytes .../main/res/drawable-hdpi/btn_check_on.png | Bin 0 -> 427 bytes .../res/drawable-hdpi/btn_check_on_dark.png | Bin 0 -> 420 bytes .../btn_check_on_dark_disabled.png | Bin 0 -> 411 bytes .../drawable-hdpi/btn_check_on_disabled.png | Bin 0 -> 407 bytes .../main/res/drawable-hdpi/btn_radio_off.png | Bin 0 -> 627 bytes .../res/drawable-hdpi/btn_radio_off_dark.png | Bin 0 -> 606 bytes .../btn_radio_off_dark_disabled.png | Bin 0 -> 579 bytes .../drawable-hdpi/btn_radio_off_disabled.png | Bin 0 -> 621 bytes .../main/res/drawable-hdpi/btn_radio_on.png | Bin 0 -> 692 bytes .../res/drawable-hdpi/btn_radio_on_dark.png | Bin 0 -> 689 bytes .../btn_radio_on_dark_disabled.png | Bin 0 -> 675 bytes .../drawable-hdpi/btn_radio_on_disabled.png | Bin 0 -> 723 bytes .../main/res/drawable-hdpi/ic_action_add.png | Bin 0 -> 188 bytes .../res/drawable-hdpi/ic_action_clear.png | Bin 0 -> 175 bytes .../res/drawable-hdpi/ic_action_close.png | Bin 0 -> 262 bytes .../drawable-hdpi/ic_action_core_overflow.png | Bin 0 -> 280 bytes .../res/drawable-hdpi/ic_action_delete.png | Bin 0 -> 247 bytes .../res/drawable-hdpi/ic_action_filter.png | Bin 0 -> 176 bytes .../main/res/drawable-hdpi/ic_action_list.png | Bin 0 -> 339 bytes .../main/res/drawable-hdpi/ic_action_move.png | Bin 0 -> 412 bytes .../main/res/drawable-hdpi/ic_action_redo.png | Bin 0 -> 914 bytes .../res/drawable-hdpi/ic_action_search.png | Bin 0 -> 436 bytes .../res/drawable-hdpi/ic_action_start.png | Bin 0 -> 344 bytes .../main/res/drawable-hdpi/ic_action_stop.png | Bin 0 -> 211 bytes .../drawable-hdpi/ic_action_voice_search.png | Bin 0 -> 374 bytes .../main/res/drawable-hdpi/ic_checkmark.png | Bin 0 -> 1384 bytes .../res/drawable-hdpi/ic_dir_dark_dialog.png | Bin 0 -> 375 bytes .../res/drawable-hdpi/ic_drawer_download.png | Bin 0 -> 320 bytes .../res/drawable-hdpi/ic_drawer_favorite.png | Bin 0 -> 485 bytes .../res/drawable-hdpi/ic_drawer_history.png | Bin 0 -> 652 bytes .../main/res/drawable-hdpi/ic_drawer_home.png | Bin 0 -> 383 bytes .../res/drawable-hdpi/ic_drawer_panda.png | Bin 0 -> 730 bytes .../res/drawable-hdpi/ic_drawer_search.png | Bin 0 -> 649 bytes .../res/drawable-hdpi/ic_drawer_settings.png | Bin 0 -> 663 bytes .../res/drawable-hdpi/ic_drawer_whatshot.png | Bin 0 -> 554 bytes .../main/res/drawable-hdpi/ic_favorite.png | Bin 0 -> 1926 bytes .../res/drawable-hdpi/ic_more_of_uploader.png | Bin 0 -> 2106 bytes app/src/main/res/drawable-hdpi/ic_rate.png | Bin 0 -> 3971 bytes .../ic_reading_action_refresh.png | Bin 0 -> 683 bytes .../drawable-hdpi/ic_reading_action_send.png | Bin 0 -> 365 bytes .../ic_reading_action_settings.png | Bin 0 -> 657 bytes app/src/main/res/drawable-hdpi/ic_reply.png | Bin 0 -> 368 bytes .../drawable-hdpi/ic_setting_about_dark.png | Bin 0 -> 662 bytes .../drawable-hdpi/ic_setting_about_light.png | Bin 0 -> 571 bytes .../ic_setting_advanced_dark.png | Bin 0 -> 542 bytes .../ic_setting_advanced_light.png | Bin 0 -> 536 bytes .../drawable-hdpi/ic_setting_display_dark.png | Bin 0 -> 394 bytes .../ic_setting_display_light.png | Bin 0 -> 353 bytes .../ic_setting_download_dark.png | Bin 0 -> 355 bytes .../ic_setting_download_light.png | Bin 0 -> 343 bytes .../drawable-hdpi/ic_setting_panda_dark.png | Bin 0 -> 983 bytes .../drawable-hdpi/ic_setting_panda_light.png | Bin 0 -> 896 bytes .../drawable-hdpi/ic_setting_read_dark.png | Bin 0 -> 482 bytes .../drawable-hdpi/ic_setting_read_light.png | Bin 0 -> 474 bytes app/src/main/res/drawable-hdpi/ic_similar.png | Bin 0 -> 1974 bytes app/src/main/res/drawable-hdpi/ic_stat_eh.png | Bin 0 -> 787 bytes app/src/main/res/drawable-hdpi/ic_touch.png | Bin 0 -> 2203 bytes app/src/main/res/drawable-hdpi/ic_warning.png | Bin 0 -> 847 bytes .../main/res/drawable-hdpi/toast_frame.9.png | Bin 0 -> 1573 bytes .../main/res/drawable-mdpi/btn_check_off.png | Bin 0 -> 228 bytes .../res/drawable-mdpi/btn_check_off_dark.png | Bin 0 -> 244 bytes .../btn_check_off_dark_disabled.png | Bin 0 -> 241 bytes .../drawable-mdpi/btn_check_off_disabled.png | Bin 0 -> 226 bytes .../main/res/drawable-mdpi/btn_check_on.png | Bin 0 -> 319 bytes .../res/drawable-mdpi/btn_check_on_dark.png | Bin 0 -> 320 bytes .../btn_check_on_dark_disabled.png | Bin 0 -> 322 bytes .../drawable-mdpi/btn_check_on_disabled.png | Bin 0 -> 301 bytes .../main/res/drawable-mdpi/btn_radio_off.png | Bin 0 -> 467 bytes .../res/drawable-mdpi/btn_radio_off_dark.png | Bin 0 -> 428 bytes .../btn_radio_off_dark_disabled.png | Bin 0 -> 429 bytes .../drawable-mdpi/btn_radio_off_disabled.png | Bin 0 -> 467 bytes .../main/res/drawable-mdpi/btn_radio_on.png | Bin 0 -> 513 bytes .../res/drawable-mdpi/btn_radio_on_dark.png | Bin 0 -> 509 bytes .../btn_radio_on_dark_disabled.png | Bin 0 -> 460 bytes .../drawable-mdpi/btn_radio_on_disabled.png | Bin 0 -> 463 bytes .../main/res/drawable-mdpi/ic_action_add.png | Bin 0 -> 178 bytes .../res/drawable-mdpi/ic_action_clear.png | Bin 0 -> 148 bytes .../res/drawable-mdpi/ic_action_close.png | Bin 0 -> 207 bytes .../drawable-mdpi/ic_action_core_overflow.png | Bin 0 -> 233 bytes .../res/drawable-mdpi/ic_action_delete.png | Bin 0 -> 171 bytes .../res/drawable-mdpi/ic_action_filter.png | Bin 0 -> 154 bytes .../main/res/drawable-mdpi/ic_action_list.png | Bin 0 -> 252 bytes .../main/res/drawable-mdpi/ic_action_move.png | Bin 0 -> 265 bytes .../main/res/drawable-mdpi/ic_action_redo.png | Bin 0 -> 600 bytes .../res/drawable-mdpi/ic_action_search.png | Bin 0 -> 315 bytes .../res/drawable-mdpi/ic_action_start.png | Bin 0 -> 237 bytes .../main/res/drawable-mdpi/ic_action_stop.png | Bin 0 -> 149 bytes .../drawable-mdpi/ic_action_voice_search.png | Bin 0 -> 265 bytes .../main/res/drawable-mdpi/ic_checkmark.png | Bin 0 -> 790 bytes .../res/drawable-mdpi/ic_dir_dark_dialog.png | Bin 0 -> 228 bytes .../res/drawable-mdpi/ic_drawer_download.png | Bin 0 -> 274 bytes .../res/drawable-mdpi/ic_drawer_favorite.png | Bin 0 -> 382 bytes .../res/drawable-mdpi/ic_drawer_history.png | Bin 0 -> 488 bytes .../main/res/drawable-mdpi/ic_drawer_home.png | Bin 0 -> 315 bytes .../res/drawable-mdpi/ic_drawer_panda.png | Bin 0 -> 583 bytes .../res/drawable-mdpi/ic_drawer_search.png | Bin 0 -> 414 bytes .../res/drawable-mdpi/ic_drawer_settings.png | Bin 0 -> 448 bytes .../res/drawable-mdpi/ic_drawer_whatshot.png | Bin 0 -> 425 bytes .../main/res/drawable-mdpi/ic_favorite.png | Bin 0 -> 1219 bytes .../res/drawable-mdpi/ic_more_of_uploader.png | Bin 0 -> 1345 bytes app/src/main/res/drawable-mdpi/ic_rate.png | Bin 0 -> 2401 bytes .../ic_reading_action_refresh.png | Bin 0 -> 469 bytes .../drawable-mdpi/ic_reading_action_send.png | Bin 0 -> 293 bytes .../ic_reading_action_settings.png | Bin 0 -> 469 bytes app/src/main/res/drawable-mdpi/ic_reply.png | Bin 0 -> 258 bytes .../drawable-mdpi/ic_setting_about_dark.png | Bin 0 -> 468 bytes .../drawable-mdpi/ic_setting_about_light.png | Bin 0 -> 423 bytes .../ic_setting_advanced_dark.png | Bin 0 -> 461 bytes .../ic_setting_advanced_light.png | Bin 0 -> 419 bytes .../drawable-mdpi/ic_setting_display_dark.png | Bin 0 -> 305 bytes .../ic_setting_display_light.png | Bin 0 -> 284 bytes .../ic_setting_download_dark.png | Bin 0 -> 302 bytes .../ic_setting_download_light.png | Bin 0 -> 298 bytes .../drawable-mdpi/ic_setting_panda_dark.png | Bin 0 -> 727 bytes .../drawable-mdpi/ic_setting_panda_light.png | Bin 0 -> 647 bytes .../drawable-mdpi/ic_setting_read_dark.png | Bin 0 -> 357 bytes .../drawable-mdpi/ic_setting_read_light.png | Bin 0 -> 363 bytes app/src/main/res/drawable-mdpi/ic_similar.png | Bin 0 -> 1249 bytes app/src/main/res/drawable-mdpi/ic_stat_eh.png | Bin 0 -> 523 bytes app/src/main/res/drawable-mdpi/ic_touch.png | Bin 0 -> 1251 bytes app/src/main/res/drawable-mdpi/ic_warning.png | Bin 0 -> 560 bytes .../main/res/drawable-mdpi/toast_frame.9.png | Bin 0 -> 965 bytes .../main/res/drawable-xhdpi/btn_check_off.png | Bin 0 -> 370 bytes .../res/drawable-xhdpi/btn_check_off_dark.png | Bin 0 -> 381 bytes .../btn_check_off_dark_disabled.png | Bin 0 -> 383 bytes .../drawable-xhdpi/btn_check_off_disabled.png | Bin 0 -> 362 bytes .../main/res/drawable-xhdpi/btn_check_on.png | Bin 0 -> 548 bytes .../res/drawable-xhdpi/btn_check_on_dark.png | Bin 0 -> 532 bytes .../btn_check_on_dark_disabled.png | Bin 0 -> 538 bytes .../drawable-xhdpi/btn_check_on_disabled.png | Bin 0 -> 509 bytes .../main/res/drawable-xhdpi/btn_radio_off.png | Bin 0 -> 794 bytes .../res/drawable-xhdpi/btn_radio_off_dark.png | Bin 0 -> 747 bytes .../btn_radio_off_dark_disabled.png | Bin 0 -> 753 bytes .../drawable-xhdpi/btn_radio_off_disabled.png | Bin 0 -> 769 bytes .../main/res/drawable-xhdpi/btn_radio_on.png | Bin 0 -> 903 bytes .../res/drawable-xhdpi/btn_radio_on_dark.png | Bin 0 -> 902 bytes .../btn_radio_on_dark_disabled.png | Bin 0 -> 908 bytes .../drawable-xhdpi/btn_radio_on_disabled.png | Bin 0 -> 916 bytes .../main/res/drawable-xhdpi/ic_action_add.png | Bin 0 -> 215 bytes .../res/drawable-xhdpi/ic_action_clear.png | Bin 0 -> 164 bytes .../res/drawable-xhdpi/ic_action_close.png | Bin 0 -> 304 bytes .../ic_action_core_overflow.png | Bin 0 -> 291 bytes .../res/drawable-xhdpi/ic_action_delete.png | Bin 0 -> 217 bytes .../res/drawable-xhdpi/ic_action_filter.png | Bin 0 -> 170 bytes .../res/drawable-xhdpi/ic_action_list.png | Bin 0 -> 377 bytes .../res/drawable-xhdpi/ic_action_move.png | Bin 0 -> 492 bytes .../res/drawable-xhdpi/ic_action_redo.png | Bin 0 -> 1185 bytes .../res/drawable-xhdpi/ic_action_search.png | Bin 0 -> 543 bytes .../res/drawable-xhdpi/ic_action_start.png | Bin 0 -> 393 bytes .../res/drawable-xhdpi/ic_action_stop.png | Bin 0 -> 169 bytes .../drawable-xhdpi/ic_action_voice_search.png | Bin 0 -> 438 bytes .../main/res/drawable-xhdpi/ic_checkmark.png | Bin 0 -> 1589 bytes .../res/drawable-xhdpi/ic_dir_dark_dialog.png | Bin 0 -> 499 bytes .../res/drawable-xhdpi/ic_drawer_download.png | Bin 0 -> 355 bytes .../res/drawable-xhdpi/ic_drawer_favorite.png | Bin 0 -> 615 bytes .../res/drawable-xhdpi/ic_drawer_history.png | Bin 0 -> 827 bytes .../res/drawable-xhdpi/ic_drawer_home.png | Bin 0 -> 464 bytes .../res/drawable-xhdpi/ic_drawer_panda.png | Bin 0 -> 983 bytes .../res/drawable-xhdpi/ic_drawer_search.png | Bin 0 -> 775 bytes .../res/drawable-xhdpi/ic_drawer_settings.png | Bin 0 -> 706 bytes .../res/drawable-xhdpi/ic_drawer_whatshot.png | Bin 0 -> 737 bytes .../main/res/drawable-xhdpi/ic_favorite.png | Bin 0 -> 2607 bytes .../drawable-xhdpi/ic_more_of_uploader.png | Bin 0 -> 2938 bytes app/src/main/res/drawable-xhdpi/ic_rate.png | Bin 0 -> 5455 bytes .../ic_reading_action_refresh.png | Bin 0 -> 844 bytes .../drawable-xhdpi/ic_reading_action_send.png | Bin 0 -> 424 bytes .../ic_reading_action_settings.png | Bin 0 -> 837 bytes app/src/main/res/drawable-xhdpi/ic_reply.png | Bin 0 -> 411 bytes .../drawable-xhdpi/ic_setting_about_dark.png | Bin 0 -> 786 bytes .../drawable-xhdpi/ic_setting_about_light.png | Bin 0 -> 675 bytes .../ic_setting_advanced_dark.png | Bin 0 -> 739 bytes .../ic_setting_advanced_light.png | Bin 0 -> 694 bytes .../ic_setting_display_dark.png | Bin 0 -> 463 bytes .../ic_setting_display_light.png | Bin 0 -> 459 bytes .../ic_setting_download_dark.png | Bin 0 -> 429 bytes .../ic_setting_download_light.png | Bin 0 -> 416 bytes .../drawable-xhdpi/ic_setting_panda_dark.png | Bin 0 -> 1309 bytes .../drawable-xhdpi/ic_setting_panda_light.png | Bin 0 -> 1202 bytes .../drawable-xhdpi/ic_setting_read_dark.png | Bin 0 -> 623 bytes .../drawable-xhdpi/ic_setting_read_light.png | Bin 0 -> 630 bytes .../main/res/drawable-xhdpi/ic_similar.png | Bin 0 -> 2748 bytes .../main/res/drawable-xhdpi/ic_stat_eh.png | Bin 0 -> 1019 bytes app/src/main/res/drawable-xhdpi/ic_touch.png | Bin 0 -> 3011 bytes .../main/res/drawable-xhdpi/ic_warning.png | Bin 0 -> 1098 bytes .../main/res/drawable-xhdpi/toast_frame.9.png | Bin 0 -> 2090 bytes .../res/drawable-xxhdpi/btn_check_off.png | Bin 0 -> 439 bytes .../drawable-xxhdpi/btn_check_off_dark.png | Bin 0 -> 421 bytes .../btn_check_off_dark_disabled.png | Bin 0 -> 421 bytes .../btn_check_off_disabled.png | Bin 0 -> 429 bytes .../main/res/drawable-xxhdpi/btn_check_on.png | Bin 0 -> 677 bytes .../res/drawable-xxhdpi/btn_check_on_dark.png | Bin 0 -> 681 bytes .../btn_check_on_dark_disabled.png | Bin 0 -> 681 bytes .../drawable-xxhdpi/btn_check_on_disabled.png | Bin 0 -> 668 bytes .../res/drawable-xxhdpi/btn_radio_off.png | Bin 0 -> 1129 bytes .../drawable-xxhdpi/btn_radio_off_dark.png | Bin 0 -> 1088 bytes .../btn_radio_off_dark_disabled.png | Bin 0 -> 1076 bytes .../btn_radio_off_disabled.png | Bin 0 -> 1138 bytes .../main/res/drawable-xxhdpi/btn_radio_on.png | Bin 0 -> 1328 bytes .../res/drawable-xxhdpi/btn_radio_on_dark.png | Bin 0 -> 1323 bytes .../btn_radio_on_dark_disabled.png | Bin 0 -> 1283 bytes .../drawable-xxhdpi/btn_radio_on_disabled.png | Bin 0 -> 1394 bytes .../res/drawable-xxhdpi/ic_action_add.png | Bin 0 -> 229 bytes .../res/drawable-xxhdpi/ic_action_clear.png | Bin 0 -> 190 bytes .../res/drawable-xxhdpi/ic_action_close.png | Bin 0 -> 393 bytes .../ic_action_core_overflow.png | Bin 0 -> 435 bytes .../res/drawable-xxhdpi/ic_action_delete.png | Bin 0 -> 319 bytes .../res/drawable-xxhdpi/ic_action_filter.png | Bin 0 -> 193 bytes .../res/drawable-xxhdpi/ic_action_list.png | Bin 0 -> 510 bytes .../res/drawable-xxhdpi/ic_action_move.png | Bin 0 -> 704 bytes .../res/drawable-xxhdpi/ic_action_redo.png | Bin 0 -> 1858 bytes .../res/drawable-xxhdpi/ic_action_search.png | Bin 0 -> 737 bytes .../res/drawable-xxhdpi/ic_action_start.png | Bin 0 -> 525 bytes .../res/drawable-xxhdpi/ic_action_stop.png | Bin 0 -> 211 bytes .../ic_action_voice_search.png | Bin 0 -> 615 bytes .../main/res/drawable-xxhdpi/ic_checkmark.png | Bin 0 -> 2876 bytes .../drawable-xxhdpi/ic_dir_dark_dialog.png | Bin 0 -> 853 bytes .../drawable-xxhdpi/ic_drawer_download.png | Bin 0 -> 426 bytes .../drawable-xxhdpi/ic_drawer_favorite.png | Bin 0 -> 927 bytes .../res/drawable-xxhdpi/ic_drawer_history.png | Bin 0 -> 1187 bytes .../res/drawable-xxhdpi/ic_drawer_home.png | Bin 0 -> 581 bytes .../res/drawable-xxhdpi/ic_drawer_panda.png | Bin 0 -> 1325 bytes .../res/drawable-xxhdpi/ic_drawer_search.png | Bin 0 -> 1178 bytes .../drawable-xxhdpi/ic_drawer_settings.png | Bin 0 -> 1105 bytes .../drawable-xxhdpi/ic_drawer_whatshot.png | Bin 0 -> 1099 bytes .../main/res/drawable-xxhdpi/ic_favorite.png | Bin 0 -> 4173 bytes .../drawable-xxhdpi/ic_more_of_uploader.png | Bin 0 -> 5053 bytes app/src/main/res/drawable-xxhdpi/ic_rate.png | Bin 0 -> 9157 bytes .../ic_reading_action_refresh.png | Bin 0 -> 1203 bytes .../ic_reading_action_send.png | Bin 0 -> 562 bytes .../ic_reading_action_settings.png | Bin 0 -> 1196 bytes app/src/main/res/drawable-xxhdpi/ic_reply.png | Bin 0 -> 531 bytes .../drawable-xxhdpi/ic_setting_about_dark.png | Bin 0 -> 1173 bytes .../ic_setting_about_light.png | Bin 0 -> 1077 bytes .../ic_setting_advanced_dark.png | Bin 0 -> 1039 bytes .../ic_setting_advanced_light.png | Bin 0 -> 972 bytes .../ic_setting_display_dark.png | Bin 0 -> 568 bytes .../ic_setting_display_light.png | Bin 0 -> 549 bytes .../ic_setting_download_dark.png | Bin 0 -> 534 bytes .../ic_setting_download_light.png | Bin 0 -> 511 bytes .../drawable-xxhdpi/ic_setting_panda_dark.png | Bin 0 -> 1951 bytes .../ic_setting_panda_light.png | Bin 0 -> 1724 bytes .../drawable-xxhdpi/ic_setting_read_dark.png | Bin 0 -> 849 bytes .../drawable-xxhdpi/ic_setting_read_light.png | Bin 0 -> 827 bytes .../main/res/drawable-xxhdpi/ic_similar.png | Bin 0 -> 4556 bytes .../main/res/drawable-xxhdpi/ic_stat_eh.png | Bin 0 -> 1532 bytes app/src/main/res/drawable-xxhdpi/ic_touch.png | Bin 0 -> 5222 bytes .../main/res/drawable-xxhdpi/ic_warning.png | Bin 0 -> 1709 bytes .../textfield_search_default.9.png | Bin 0 -> 16706 bytes .../textfield_search_right_default.9.png | Bin 0 -> 17124 bytes .../textfield_search_right_selected.9.png | Bin 0 -> 16685 bytes .../textfield_search_selected.9.png | Bin 0 -> 16688 bytes .../res/drawable-xxhdpi/toast_frame.9.png | Bin 0 -> 2323 bytes .../res/drawable-xxxhdpi/btn_check_off.png | Bin 0 -> 564 bytes .../drawable-xxxhdpi/btn_check_off_dark.png | Bin 0 -> 575 bytes .../btn_check_off_dark_disabled.png | Bin 0 -> 574 bytes .../btn_check_off_disabled.png | Bin 0 -> 555 bytes .../res/drawable-xxxhdpi/btn_check_on.png | Bin 0 -> 857 bytes .../drawable-xxxhdpi/btn_check_on_dark.png | Bin 0 -> 842 bytes .../btn_check_on_dark_disabled.png | Bin 0 -> 837 bytes .../btn_check_on_disabled.png | Bin 0 -> 896 bytes .../res/drawable-xxxhdpi/btn_radio_off.png | Bin 0 -> 1606 bytes .../drawable-xxxhdpi/btn_radio_off_dark.png | Bin 0 -> 1585 bytes .../btn_radio_off_dark_disabled.png | Bin 0 -> 1546 bytes .../btn_radio_off_disabled.png | Bin 0 -> 1608 bytes .../res/drawable-xxxhdpi/btn_radio_on.png | Bin 0 -> 1882 bytes .../drawable-xxxhdpi/btn_radio_on_dark.png | Bin 0 -> 1866 bytes .../btn_radio_on_dark_disabled.png | Bin 0 -> 1657 bytes .../btn_radio_on_disabled.png | Bin 0 -> 1932 bytes .../res/drawable-xxxhdpi/ic_action_add.png | Bin 0 -> 302 bytes .../res/drawable-xxxhdpi/ic_action_clear.png | Bin 0 -> 295 bytes .../res/drawable-xxxhdpi/ic_action_close.png | Bin 0 -> 496 bytes .../ic_action_core_overflow.png | Bin 0 -> 439 bytes .../res/drawable-xxxhdpi/ic_action_delete.png | Bin 0 -> 352 bytes .../res/drawable-xxxhdpi/ic_action_filter.png | Bin 0 -> 227 bytes .../res/drawable-xxxhdpi/ic_action_list.png | Bin 0 -> 636 bytes .../res/drawable-xxxhdpi/ic_action_move.png | Bin 0 -> 937 bytes .../res/drawable-xxxhdpi/ic_action_search.png | Bin 0 -> 995 bytes .../res/drawable-xxxhdpi/ic_action_start.png | Bin 0 -> 606 bytes .../res/drawable-xxxhdpi/ic_action_stop.png | Bin 0 -> 263 bytes .../ic_action_voice_search.png | Bin 0 -> 800 bytes .../drawable-xxxhdpi/ic_drawer_download.png | Bin 0 -> 534 bytes .../drawable-xxxhdpi/ic_drawer_favorite.png | Bin 0 -> 1188 bytes .../drawable-xxxhdpi/ic_drawer_history.png | Bin 0 -> 1566 bytes .../res/drawable-xxxhdpi/ic_drawer_home.png | Bin 0 -> 685 bytes .../res/drawable-xxxhdpi/ic_drawer_panda.png | Bin 0 -> 1951 bytes .../res/drawable-xxxhdpi/ic_drawer_search.png | Bin 0 -> 1476 bytes .../drawable-xxxhdpi/ic_drawer_settings.png | Bin 0 -> 1458 bytes .../drawable-xxxhdpi/ic_drawer_whatshot.png | Bin 0 -> 1423 bytes .../ic_reading_action_refresh.png | Bin 0 -> 1735 bytes .../ic_reading_action_send.png | Bin 0 -> 622 bytes .../ic_reading_action_settings.png | Bin 0 -> 1640 bytes .../main/res/drawable-xxxhdpi/ic_reply.png | Bin 0 -> 639 bytes .../ic_setting_about_dark.png | Bin 0 -> 1583 bytes .../ic_setting_about_light.png | Bin 0 -> 1474 bytes .../ic_setting_advanced_dark.png | Bin 0 -> 1364 bytes .../ic_setting_advanced_light.png | Bin 0 -> 1285 bytes .../ic_setting_display_dark.png | Bin 0 -> 757 bytes .../ic_setting_display_light.png | Bin 0 -> 728 bytes .../ic_setting_download_dark.png | Bin 0 -> 684 bytes .../ic_setting_download_light.png | Bin 0 -> 642 bytes .../ic_setting_panda_dark.png | Bin 0 -> 2580 bytes .../ic_setting_panda_light.png | Bin 0 -> 2398 bytes .../drawable-xxxhdpi/ic_setting_read_dark.png | Bin 0 -> 1168 bytes .../ic_setting_read_light.png | Bin 0 -> 1104 bytes .../main/res/drawable/btn_check_material.xml | 31 + .../res/drawable/btn_check_material_dark.xml | 31 + .../main/res/drawable/btn_radio_material.xml | 30 + .../res/drawable/btn_radio_material_dark.xml | 30 + .../main/res/drawable/clickable_bg_purple.xml | 32 + .../drawable/dialog_background_material.xml | 25 + .../dialog_background_material_dark.xml | 25 + .../main/res/drawable/ic_setting_about.xml | 24 + .../main/res/drawable/ic_setting_advanced.xml | 24 + .../main/res/drawable/ic_setting_display.xml | 24 + .../main/res/drawable/ic_setting_download.xml | 24 + .../main/res/drawable/ic_setting_panda.xml | 24 + app/src/main/res/drawable/ic_setting_read.xml | 24 + .../res/drawable/textfield_searchview.xml | 20 + .../drawable/textfield_searchview_right.xml | 20 + .../main/res/layout-land/start_activity.xml | 47 + .../main/res/layout/actionable_toast_row.xml | 80 + app/src/main/res/layout/add_to_favorite.xml | 39 + .../main/res/layout/alert_dialog_material.xml | 136 ++ .../main/res/layout/alert_dialog_progress.xml | 53 + app/src/main/res/layout/category_table.xml | 114 + .../main/res/layout/color_scheme_dialog.xml | 157 ++ app/src/main/res/layout/comments_item.xml | 47 + .../res/layout/dialog_message_checkbox.xml | 34 + app/src/main/res/layout/dir_selection.xml | 48 + app/src/main/res/layout/download.xml | 36 + .../main/res/layout/download_list_item.xml | 137 ++ app/src/main/res/layout/drawer_list_item.xml | 12 + app/src/main/res/layout/exculde_language.xml | 517 +++++ app/src/main/res/layout/exculde_tag_group.xml | 107 + app/src/main/res/layout/favorite.xml | 44 + app/src/main/res/layout/favorite_menu.xml | 24 + .../main/res/layout/file_explorer_item.xml | 24 + app/src/main/res/layout/gallery_detail.xml | 387 ++++ app/src/main/res/layout/gallery_list.xml | 44 + .../res/layout/gallery_list_detail_item.xml | 90 + .../res/layout/gallery_list_thumb_item.xml | 49 + app/src/main/res/layout/gallery_list_view.xml | 35 + app/src/main/res/layout/gl_root_group.xml | 63 + app/src/main/res/layout/history.xml | 32 + app/src/main/res/layout/history_filter.xml | 40 + app/src/main/res/layout/jump.xml | 51 + app/src/main/res/layout/list_menu_left.xml | 37 + app/src/main/res/layout/list_menu_right.xml | 63 + app/src/main/res/layout/login.xml | 41 + app/src/main/res/layout/material_toast.xml | 36 + app/src/main/res/layout/menu_item.xml | 25 + app/src/main/res/layout/menu_tag.xml | 25 + app/src/main/res/layout/preview_item.xml | 35 + app/src/main/res/layout/preview_list.xml | 53 + .../main/res/layout/preview_list_panel.xml | 60 + app/src/main/res/layout/progress_dialog.xml | 43 + .../main/res/layout/pull_list_view_footer.xml | 48 + .../res/layout/reading_quick_settings.xml | 253 +++ app/src/main/res/layout/search.xml | 36 + app/src/main/res/layout/search_image.xml | 77 + app/src/main/res/layout/search_normal.xml | 172 ++ app/src/main/res/layout/search_tag.xml | 31 + .../main/res/layout/select_dialog_item.xml | 31 + .../res/layout/select_dialog_singlechoice.xml | 26 + app/src/main/res/layout/select_mode.xml | 90 + app/src/main/res/layout/send_crash.xml | 38 + app/src/main/res/layout/set_column_number.xml | 66 + app/src/main/res/layout/set_name.xml | 34 + app/src/main/res/layout/start_activity.xml | 47 + .../layout/test_gallery_list_detail_thumb.xml | 56 + app/src/main/res/layout/user_panel.xml | 82 + app/src/main/res/menu/download.xml | 30 + app/src/main/res/menu/favorite.xml | 22 + app/src/main/res/menu/favorite_choice.xml | 27 + app/src/main/res/menu/gallery_list.xml | 45 + app/src/main/res/menu/history.xml | 30 + app/src/main/res/menu/quick_search.xml | 30 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 1451 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1086 bytes app/src/main/res/mipmap-xdpi/ic_launcher.png | Bin 0 -> 1928 bytes app/src/main/res/mipmap-xxdpi/ic_launcher.png | Bin 0 -> 2714 bytes .../main/res/mipmap-xxxdpi/ic_launcher.png | Bin 0 -> 3444 bytes app/src/main/res/raw/change_log | 284 +++ app/src/main/res/raw/default_avatar.svg | 12 + app/src/main/res/raw/sad_pandroid.svg | 16 + app/src/main/res/raw/thanks.html | 63 + app/src/main/res/values-sw600dp/bools.xml | 19 + app/src/main/res/values-v19/themes.xml | 23 + app/src/main/res/values-v21/themes.xml | 75 + app/src/main/res/values-zh-rCN/strings.xml | 465 ++++ app/src/main/res/values-zh-rHK/strings.xml | 453 ++++ app/src/main/res/values-zh-rTW/strings.xml | 453 ++++ app/src/main/res/values/analytics.xml | 11 + app/src/main/res/values/arrays.xml | 176 ++ app/src/main/res/values/attrs.xml | 105 + app/src/main/res/values/bools.xml | 19 + app/src/main/res/values/colors.xml | 93 + app/src/main/res/values/dimens.xml | 40 + app/src/main/res/values/ids.xml | 26 + app/src/main/res/values/strings.xml | 490 ++++ app/src/main/res/values/styles.xml | 302 +++ app/src/main/res/values/themes.xml | 160 ++ app/src/main/res/xml/about_settings.xml | 99 + app/src/main/res/xml/advanced_settings.xml | 81 + app/src/main/res/xml/display_settings.xml | 102 + app/src/main/res/xml/download_settings.xml | 54 + app/src/main/res/xml/eh_settings.xml | 81 + app/src/main/res/xml/read_settings.xml | 85 + app/src/main/res/xml/searchable.xml | 21 + app/src/main/res/xml/settings_headers.xml | 49 + build.gradle | 19 + gradle.properties | 18 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 164 ++ gradlew.bat | 90 + settings.gradle | 1 + 658 files changed, 57526 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.name create mode 100644 .idea/compiler.xml create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/scopes/scope_settings.xml create mode 100644 .idea/vcs.xml create mode 100644 EhViewer.iml create mode 100644 app/.gitignore create mode 100644 app/app.iml create mode 100644 app/build.gradle create mode 100644 app/libs/libGoogleAnalyticsServices.jar create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/hippo/ehviewer/ApplicationTest.java create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/assets/fonts/GloriaHallelujah.ttf create mode 100644 app/src/main/assets/fonts/face.ttf create mode 100644 app/src/main/java/com/hippo/ehviewer/Analytics.java create mode 100644 app/src/main/java/com/hippo/ehviewer/AppContext.java create mode 100644 app/src/main/java/com/hippo/ehviewer/AppHandler.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ImageLoader.java create mode 100644 app/src/main/java/com/hippo/ehviewer/NotificationId.java create mode 100644 app/src/main/java/com/hippo/ehviewer/SimpleSuggestionProvider.java create mode 100644 app/src/main/java/com/hippo/ehviewer/StartActivity.java create mode 100644 app/src/main/java/com/hippo/ehviewer/UpdateHelper.java create mode 100644 app/src/main/java/com/hippo/ehviewer/app/DirSelectDialog.java create mode 100644 app/src/main/java/com/hippo/ehviewer/app/MaterialAlertDialog.java create mode 100644 app/src/main/java/com/hippo/ehviewer/app/MaterialProgressDialog.java create mode 100644 app/src/main/java/com/hippo/ehviewer/cache/DiskLruCache.java create mode 100644 app/src/main/java/com/hippo/ehviewer/cache/ImageCache.java create mode 100644 app/src/main/java/com/hippo/ehviewer/cache/LruCache.java create mode 100644 app/src/main/java/com/hippo/ehviewer/cardview/CardViewSalon.java create mode 100644 app/src/main/java/com/hippo/ehviewer/cardview/RoundRectDrawable.java create mode 100644 app/src/main/java/com/hippo/ehviewer/cardview/RoundRectDrawableWithShadow.java create mode 100644 app/src/main/java/com/hippo/ehviewer/data/ApiGalleryDetail.java create mode 100644 app/src/main/java/com/hippo/ehviewer/data/ApiGalleryInfo.java create mode 100644 app/src/main/java/com/hippo/ehviewer/data/Comment.java create mode 100644 app/src/main/java/com/hippo/ehviewer/data/Data.java create mode 100644 app/src/main/java/com/hippo/ehviewer/data/DownloadInfo.java create mode 100644 app/src/main/java/com/hippo/ehviewer/data/GalleryDetail.java create mode 100644 app/src/main/java/com/hippo/ehviewer/data/GalleryInfo.java create mode 100644 app/src/main/java/com/hippo/ehviewer/data/GalleryPopular.java create mode 100644 app/src/main/java/com/hippo/ehviewer/data/HistoryItem.java create mode 100644 app/src/main/java/com/hippo/ehviewer/data/LargePreviewList.java create mode 100644 app/src/main/java/com/hippo/ehviewer/data/ListUrls.java create mode 100644 app/src/main/java/com/hippo/ehviewer/data/LofiDetailImpl.java create mode 100644 app/src/main/java/com/hippo/ehviewer/data/LofiGalleryDetail.java create mode 100644 app/src/main/java/com/hippo/ehviewer/data/LofiGalleryInfo.java create mode 100644 app/src/main/java/com/hippo/ehviewer/data/NormalPreviewList.java create mode 100644 app/src/main/java/com/hippo/ehviewer/data/PreviewImpl.java create mode 100644 app/src/main/java/com/hippo/ehviewer/data/PreviewList.java create mode 100644 app/src/main/java/com/hippo/ehviewer/data/Tag.java create mode 100644 app/src/main/java/com/hippo/ehviewer/drawable/BatteryDrawable.java create mode 100644 app/src/main/java/com/hippo/ehviewer/drawable/FreeMaterialDrawable.java create mode 100644 app/src/main/java/com/hippo/ehviewer/drawable/MaterialIndicatorDrawable.java create mode 100644 app/src/main/java/com/hippo/ehviewer/drawable/MaterialProgressDrawable.java create mode 100644 app/src/main/java/com/hippo/ehviewer/drawable/OvalDrawable.java create mode 100644 app/src/main/java/com/hippo/ehviewer/effect/DrawableTransition.java create mode 100644 app/src/main/java/com/hippo/ehviewer/effect/ripple/Ripple.java create mode 100644 app/src/main/java/com/hippo/ehviewer/effect/ripple/RippleBackground.java create mode 100644 app/src/main/java/com/hippo/ehviewer/effect/ripple/RippleDrawable.java create mode 100644 app/src/main/java/com/hippo/ehviewer/effect/ripple/RippleOwner.java create mode 100644 app/src/main/java/com/hippo/ehviewer/effect/ripple/RippleSalon.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ehclient/AddToFavoriteParser.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ehclient/DetailParser.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ehclient/DetailUrlParser.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ehclient/EdDetailParser.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ehclient/EhClient.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ehclient/EhInfo.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ehclient/ExDownloader.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ehclient/ExDownloaderManager.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ehclient/ImagePageParser.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ehclient/ListParser.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ehclient/LofiDetailParser.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ehclient/LoginParser.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ehclient/RateParser.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ehclient/VoteParser.java create mode 100644 app/src/main/java/com/hippo/ehviewer/exception/FileException.java create mode 100644 app/src/main/java/com/hippo/ehviewer/exception/NoSdCardException.java create mode 100644 app/src/main/java/com/hippo/ehviewer/exception/ParserException.java create mode 100644 app/src/main/java/com/hippo/ehviewer/exception/StopRequestException.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/GalleryView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/anim/Animation.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/anim/CanvasAnimation.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/anim/StateTransitionAnimation.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/data/ImageSet.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/BasicTexture.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/BitmapTexture.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/CanvasTexture.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/ColorTexture.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/ExtTexture.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/FadeInTexture.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/FadeOutTexture.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/FadeTexture.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLCanvas.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLES11Canvas.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLES11IdImpl.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLES20Canvas.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLES20IdImpl.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLId.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLPaint.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/ImageTexture.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/MovieTexture.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/MultiLineTexture.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/NinePatchChunk.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/NinePatchTexture.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/RawTexture.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/ResourceTexture.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/StringTexture.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/Texture.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/TextureUploader.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/TiledTexture.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/Uploaded.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/UploadedTexture.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/image/GifImage.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/image/Image.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/ui/AnimationTime.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/ui/DownUpDetector.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/ui/GLRoot.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/ui/GLRootView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/ui/GLView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/ui/GestureRecognizer.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/ui/OrientationSource.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/ui/SynchronizedHandler.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/ui/TimeRunner.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/util/GalleryUtils.java create mode 100644 app/src/main/java/com/hippo/ehviewer/gallery/util/MotionEventHelper.java create mode 100644 app/src/main/java/com/hippo/ehviewer/network/HttpHelper.java create mode 100644 app/src/main/java/com/hippo/ehviewer/network/Network.java create mode 100644 app/src/main/java/com/hippo/ehviewer/network/UrlBuilder.java create mode 100644 app/src/main/java/com/hippo/ehviewer/preference/ColorPreference.java create mode 100644 app/src/main/java/com/hippo/ehviewer/preference/DialogPreference.java create mode 100644 app/src/main/java/com/hippo/ehviewer/preference/EditTextPreference.java create mode 100644 app/src/main/java/com/hippo/ehviewer/preference/ListPreference.java create mode 100644 app/src/main/java/com/hippo/ehviewer/preference/NumberOfColumnsPreference.java create mode 100644 app/src/main/java/com/hippo/ehviewer/preference/SeekBarDialogPreference.java create mode 100644 app/src/main/java/com/hippo/ehviewer/receiver/NetworkChangeReceiver.java create mode 100644 app/src/main/java/com/hippo/ehviewer/service/DownloadService.java create mode 100644 app/src/main/java/com/hippo/ehviewer/tile/RectDrawable.java create mode 100644 app/src/main/java/com/hippo/ehviewer/tile/TileSalon.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ui/AbsActivity.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ui/AbsPreferenceActivity.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ui/AbsTranslucentActivity.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ui/DownloadActivity.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ui/FavouriteActivity.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ui/GalleryDetailActivity.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ui/GalleryListActivity.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ui/HistoryActivity.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ui/SecretActivity.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ui/SettingsActivity.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/AnimatorUtils.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/AutoExpandArray.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/BgThread.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/Config.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/Constants.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/Crash.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/DateUtils.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/DialogUtils.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/EhUtils.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/FastByteArrayOutputStream.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/Favorite.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/FullScreenHelper.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/IntArray.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/Log.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/MathUtils.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/Secret.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/SqlUtils.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/Theme.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/Ui.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/Utils.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/ViewUtils.java create mode 100644 app/src/main/java/com/hippo/ehviewer/util/ZInterpolator.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/ActionableToastBar.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/AlphaPatternDrawable.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/AutoWrapLayout.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/BatteryView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/CategoryTable.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/CheckTextView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/ColorPickerView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/ColorView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/DrawerListView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/ExpandableListItem.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/ExpandingListView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/FileExplorerView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/FitWindowView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/FixedAspectImageView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/FloatingActionButton.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/GalleryListView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/HotspotTouchHelper.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/Hotspotable.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/LinkifyTextView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/LoadImageView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/MaterialProgress.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/MaterialToast.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/PrefixEditText.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/ProgressiveRatingBar.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/ProgressiveTextView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/RatingView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/RefreshLayout.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/RefreshTextView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/ResponedScrollView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/SearchView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/SimpleGridLayout.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/SlidingDrawerLayout.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/SlidingLayout.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/SuggestionHelper.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/SuggestionTextView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/SuperButton.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/SuperSwipeRefreshLayout.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/SwipeProgressBar.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/TagListView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/TagsAdapter.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/TextClock.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/TransitionLayout.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/recyclerview/EasyRecyclerView.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/recyclerview/LinearDividerItemDecoration.java create mode 100644 app/src/main/java/com/hippo/ehviewer/widget/recyclerview/MarginItemDecoration.java create mode 100644 app/src/main/java/com/hippo/ehviewer/windowsanimate/AnimateCanvas.java create mode 100644 app/src/main/java/com/hippo/ehviewer/windowsanimate/BitmapSprite.java create mode 100644 app/src/main/java/com/hippo/ehviewer/windowsanimate/RippleHelpDrawable.java create mode 100644 app/src/main/java/com/hippo/ehviewer/windowsanimate/RippleSprite.java create mode 100644 app/src/main/java/com/hippo/ehviewer/windowsanimate/RoundSprite.java create mode 100644 app/src/main/java/com/hippo/ehviewer/windowsanimate/Sprite.java create mode 100644 app/src/main/java/com/hippo/ehviewer/windowsanimate/WindowsAnimate.java create mode 100644 app/src/main/jniLibs/armeabi-v7a/libimage.so create mode 100644 app/src/main/jniLibs/armeabi/libimage.so create mode 100644 app/src/main/jniLibs/x86/libimage.so create mode 100644 app/src/main/res/anim/anim_fade_in.xml create mode 100644 app/src/main/res/anim/anim_fade_out.xml create mode 100644 app/src/main/res/anim/fade_in.xml create mode 100644 app/src/main/res/anim/fade_out.xml create mode 100644 app/src/main/res/drawable-hdpi/btn_check_off.png create mode 100644 app/src/main/res/drawable-hdpi/btn_check_off_dark.png create mode 100644 app/src/main/res/drawable-hdpi/btn_check_off_dark_disabled.png create mode 100644 app/src/main/res/drawable-hdpi/btn_check_off_disabled.png create mode 100644 app/src/main/res/drawable-hdpi/btn_check_on.png create mode 100644 app/src/main/res/drawable-hdpi/btn_check_on_dark.png create mode 100644 app/src/main/res/drawable-hdpi/btn_check_on_dark_disabled.png create mode 100644 app/src/main/res/drawable-hdpi/btn_check_on_disabled.png create mode 100644 app/src/main/res/drawable-hdpi/btn_radio_off.png create mode 100644 app/src/main/res/drawable-hdpi/btn_radio_off_dark.png create mode 100644 app/src/main/res/drawable-hdpi/btn_radio_off_dark_disabled.png create mode 100644 app/src/main/res/drawable-hdpi/btn_radio_off_disabled.png create mode 100644 app/src/main/res/drawable-hdpi/btn_radio_on.png create mode 100644 app/src/main/res/drawable-hdpi/btn_radio_on_dark.png create mode 100644 app/src/main/res/drawable-hdpi/btn_radio_on_dark_disabled.png create mode 100644 app/src/main/res/drawable-hdpi/btn_radio_on_disabled.png create mode 100644 app/src/main/res/drawable-hdpi/ic_action_add.png create mode 100644 app/src/main/res/drawable-hdpi/ic_action_clear.png create mode 100644 app/src/main/res/drawable-hdpi/ic_action_close.png create mode 100644 app/src/main/res/drawable-hdpi/ic_action_core_overflow.png create mode 100644 app/src/main/res/drawable-hdpi/ic_action_delete.png create mode 100644 app/src/main/res/drawable-hdpi/ic_action_filter.png create mode 100644 app/src/main/res/drawable-hdpi/ic_action_list.png create mode 100644 app/src/main/res/drawable-hdpi/ic_action_move.png create mode 100644 app/src/main/res/drawable-hdpi/ic_action_redo.png create mode 100644 app/src/main/res/drawable-hdpi/ic_action_search.png create mode 100644 app/src/main/res/drawable-hdpi/ic_action_start.png create mode 100644 app/src/main/res/drawable-hdpi/ic_action_stop.png create mode 100644 app/src/main/res/drawable-hdpi/ic_action_voice_search.png create mode 100644 app/src/main/res/drawable-hdpi/ic_checkmark.png create mode 100644 app/src/main/res/drawable-hdpi/ic_dir_dark_dialog.png create mode 100644 app/src/main/res/drawable-hdpi/ic_drawer_download.png create mode 100644 app/src/main/res/drawable-hdpi/ic_drawer_favorite.png create mode 100644 app/src/main/res/drawable-hdpi/ic_drawer_history.png create mode 100644 app/src/main/res/drawable-hdpi/ic_drawer_home.png create mode 100644 app/src/main/res/drawable-hdpi/ic_drawer_panda.png create mode 100644 app/src/main/res/drawable-hdpi/ic_drawer_search.png create mode 100644 app/src/main/res/drawable-hdpi/ic_drawer_settings.png create mode 100644 app/src/main/res/drawable-hdpi/ic_drawer_whatshot.png create mode 100644 app/src/main/res/drawable-hdpi/ic_favorite.png create mode 100644 app/src/main/res/drawable-hdpi/ic_more_of_uploader.png create mode 100644 app/src/main/res/drawable-hdpi/ic_rate.png create mode 100644 app/src/main/res/drawable-hdpi/ic_reading_action_refresh.png create mode 100644 app/src/main/res/drawable-hdpi/ic_reading_action_send.png create mode 100644 app/src/main/res/drawable-hdpi/ic_reading_action_settings.png create mode 100644 app/src/main/res/drawable-hdpi/ic_reply.png create mode 100644 app/src/main/res/drawable-hdpi/ic_setting_about_dark.png create mode 100644 app/src/main/res/drawable-hdpi/ic_setting_about_light.png create mode 100644 app/src/main/res/drawable-hdpi/ic_setting_advanced_dark.png create mode 100644 app/src/main/res/drawable-hdpi/ic_setting_advanced_light.png create mode 100644 app/src/main/res/drawable-hdpi/ic_setting_display_dark.png create mode 100644 app/src/main/res/drawable-hdpi/ic_setting_display_light.png create mode 100644 app/src/main/res/drawable-hdpi/ic_setting_download_dark.png create mode 100644 app/src/main/res/drawable-hdpi/ic_setting_download_light.png create mode 100644 app/src/main/res/drawable-hdpi/ic_setting_panda_dark.png create mode 100644 app/src/main/res/drawable-hdpi/ic_setting_panda_light.png create mode 100644 app/src/main/res/drawable-hdpi/ic_setting_read_dark.png create mode 100644 app/src/main/res/drawable-hdpi/ic_setting_read_light.png create mode 100644 app/src/main/res/drawable-hdpi/ic_similar.png create mode 100644 app/src/main/res/drawable-hdpi/ic_stat_eh.png create mode 100644 app/src/main/res/drawable-hdpi/ic_touch.png create mode 100644 app/src/main/res/drawable-hdpi/ic_warning.png create mode 100644 app/src/main/res/drawable-hdpi/toast_frame.9.png create mode 100644 app/src/main/res/drawable-mdpi/btn_check_off.png create mode 100644 app/src/main/res/drawable-mdpi/btn_check_off_dark.png create mode 100644 app/src/main/res/drawable-mdpi/btn_check_off_dark_disabled.png create mode 100644 app/src/main/res/drawable-mdpi/btn_check_off_disabled.png create mode 100644 app/src/main/res/drawable-mdpi/btn_check_on.png create mode 100644 app/src/main/res/drawable-mdpi/btn_check_on_dark.png create mode 100644 app/src/main/res/drawable-mdpi/btn_check_on_dark_disabled.png create mode 100644 app/src/main/res/drawable-mdpi/btn_check_on_disabled.png create mode 100644 app/src/main/res/drawable-mdpi/btn_radio_off.png create mode 100644 app/src/main/res/drawable-mdpi/btn_radio_off_dark.png create mode 100644 app/src/main/res/drawable-mdpi/btn_radio_off_dark_disabled.png create mode 100644 app/src/main/res/drawable-mdpi/btn_radio_off_disabled.png create mode 100644 app/src/main/res/drawable-mdpi/btn_radio_on.png create mode 100644 app/src/main/res/drawable-mdpi/btn_radio_on_dark.png create mode 100644 app/src/main/res/drawable-mdpi/btn_radio_on_dark_disabled.png create mode 100644 app/src/main/res/drawable-mdpi/btn_radio_on_disabled.png create mode 100644 app/src/main/res/drawable-mdpi/ic_action_add.png create mode 100644 app/src/main/res/drawable-mdpi/ic_action_clear.png create mode 100644 app/src/main/res/drawable-mdpi/ic_action_close.png create mode 100644 app/src/main/res/drawable-mdpi/ic_action_core_overflow.png create mode 100644 app/src/main/res/drawable-mdpi/ic_action_delete.png create mode 100644 app/src/main/res/drawable-mdpi/ic_action_filter.png create mode 100644 app/src/main/res/drawable-mdpi/ic_action_list.png create mode 100644 app/src/main/res/drawable-mdpi/ic_action_move.png create mode 100644 app/src/main/res/drawable-mdpi/ic_action_redo.png create mode 100644 app/src/main/res/drawable-mdpi/ic_action_search.png create mode 100644 app/src/main/res/drawable-mdpi/ic_action_start.png create mode 100644 app/src/main/res/drawable-mdpi/ic_action_stop.png create mode 100644 app/src/main/res/drawable-mdpi/ic_action_voice_search.png create mode 100644 app/src/main/res/drawable-mdpi/ic_checkmark.png create mode 100644 app/src/main/res/drawable-mdpi/ic_dir_dark_dialog.png create mode 100644 app/src/main/res/drawable-mdpi/ic_drawer_download.png create mode 100644 app/src/main/res/drawable-mdpi/ic_drawer_favorite.png create mode 100644 app/src/main/res/drawable-mdpi/ic_drawer_history.png create mode 100644 app/src/main/res/drawable-mdpi/ic_drawer_home.png create mode 100644 app/src/main/res/drawable-mdpi/ic_drawer_panda.png create mode 100644 app/src/main/res/drawable-mdpi/ic_drawer_search.png create mode 100644 app/src/main/res/drawable-mdpi/ic_drawer_settings.png create mode 100644 app/src/main/res/drawable-mdpi/ic_drawer_whatshot.png create mode 100644 app/src/main/res/drawable-mdpi/ic_favorite.png create mode 100644 app/src/main/res/drawable-mdpi/ic_more_of_uploader.png create mode 100644 app/src/main/res/drawable-mdpi/ic_rate.png create mode 100644 app/src/main/res/drawable-mdpi/ic_reading_action_refresh.png create mode 100644 app/src/main/res/drawable-mdpi/ic_reading_action_send.png create mode 100644 app/src/main/res/drawable-mdpi/ic_reading_action_settings.png create mode 100644 app/src/main/res/drawable-mdpi/ic_reply.png create mode 100644 app/src/main/res/drawable-mdpi/ic_setting_about_dark.png create mode 100644 app/src/main/res/drawable-mdpi/ic_setting_about_light.png create mode 100644 app/src/main/res/drawable-mdpi/ic_setting_advanced_dark.png create mode 100644 app/src/main/res/drawable-mdpi/ic_setting_advanced_light.png create mode 100644 app/src/main/res/drawable-mdpi/ic_setting_display_dark.png create mode 100644 app/src/main/res/drawable-mdpi/ic_setting_display_light.png create mode 100644 app/src/main/res/drawable-mdpi/ic_setting_download_dark.png create mode 100644 app/src/main/res/drawable-mdpi/ic_setting_download_light.png create mode 100644 app/src/main/res/drawable-mdpi/ic_setting_panda_dark.png create mode 100644 app/src/main/res/drawable-mdpi/ic_setting_panda_light.png create mode 100644 app/src/main/res/drawable-mdpi/ic_setting_read_dark.png create mode 100644 app/src/main/res/drawable-mdpi/ic_setting_read_light.png create mode 100644 app/src/main/res/drawable-mdpi/ic_similar.png create mode 100644 app/src/main/res/drawable-mdpi/ic_stat_eh.png create mode 100644 app/src/main/res/drawable-mdpi/ic_touch.png create mode 100644 app/src/main/res/drawable-mdpi/ic_warning.png create mode 100644 app/src/main/res/drawable-mdpi/toast_frame.9.png create mode 100644 app/src/main/res/drawable-xhdpi/btn_check_off.png create mode 100644 app/src/main/res/drawable-xhdpi/btn_check_off_dark.png create mode 100644 app/src/main/res/drawable-xhdpi/btn_check_off_dark_disabled.png create mode 100644 app/src/main/res/drawable-xhdpi/btn_check_off_disabled.png create mode 100644 app/src/main/res/drawable-xhdpi/btn_check_on.png create mode 100644 app/src/main/res/drawable-xhdpi/btn_check_on_dark.png create mode 100644 app/src/main/res/drawable-xhdpi/btn_check_on_dark_disabled.png create mode 100644 app/src/main/res/drawable-xhdpi/btn_check_on_disabled.png create mode 100644 app/src/main/res/drawable-xhdpi/btn_radio_off.png create mode 100644 app/src/main/res/drawable-xhdpi/btn_radio_off_dark.png create mode 100644 app/src/main/res/drawable-xhdpi/btn_radio_off_dark_disabled.png create mode 100644 app/src/main/res/drawable-xhdpi/btn_radio_off_disabled.png create mode 100644 app/src/main/res/drawable-xhdpi/btn_radio_on.png create mode 100644 app/src/main/res/drawable-xhdpi/btn_radio_on_dark.png create mode 100644 app/src/main/res/drawable-xhdpi/btn_radio_on_dark_disabled.png create mode 100644 app/src/main/res/drawable-xhdpi/btn_radio_on_disabled.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_action_add.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_action_clear.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_action_close.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_action_core_overflow.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_action_delete.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_action_filter.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_action_list.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_action_move.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_action_redo.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_action_search.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_action_start.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_action_stop.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_action_voice_search.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_checkmark.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_dir_dark_dialog.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_drawer_download.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_drawer_favorite.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_drawer_history.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_drawer_home.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_drawer_panda.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_drawer_search.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_drawer_settings.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_drawer_whatshot.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_favorite.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_more_of_uploader.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_rate.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_reading_action_refresh.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_reading_action_send.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_reading_action_settings.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_reply.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_setting_about_dark.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_setting_about_light.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_setting_advanced_dark.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_setting_advanced_light.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_setting_display_dark.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_setting_display_light.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_setting_download_dark.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_setting_download_light.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_setting_panda_dark.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_setting_panda_light.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_setting_read_dark.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_setting_read_light.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_similar.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_stat_eh.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_touch.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_warning.png create mode 100644 app/src/main/res/drawable-xhdpi/toast_frame.9.png create mode 100644 app/src/main/res/drawable-xxhdpi/btn_check_off.png create mode 100644 app/src/main/res/drawable-xxhdpi/btn_check_off_dark.png create mode 100644 app/src/main/res/drawable-xxhdpi/btn_check_off_dark_disabled.png create mode 100644 app/src/main/res/drawable-xxhdpi/btn_check_off_disabled.png create mode 100644 app/src/main/res/drawable-xxhdpi/btn_check_on.png create mode 100644 app/src/main/res/drawable-xxhdpi/btn_check_on_dark.png create mode 100644 app/src/main/res/drawable-xxhdpi/btn_check_on_dark_disabled.png create mode 100644 app/src/main/res/drawable-xxhdpi/btn_check_on_disabled.png create mode 100644 app/src/main/res/drawable-xxhdpi/btn_radio_off.png create mode 100644 app/src/main/res/drawable-xxhdpi/btn_radio_off_dark.png create mode 100644 app/src/main/res/drawable-xxhdpi/btn_radio_off_dark_disabled.png create mode 100644 app/src/main/res/drawable-xxhdpi/btn_radio_off_disabled.png create mode 100644 app/src/main/res/drawable-xxhdpi/btn_radio_on.png create mode 100644 app/src/main/res/drawable-xxhdpi/btn_radio_on_dark.png create mode 100644 app/src/main/res/drawable-xxhdpi/btn_radio_on_dark_disabled.png create mode 100644 app/src/main/res/drawable-xxhdpi/btn_radio_on_disabled.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_action_add.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_action_clear.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_action_close.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_action_core_overflow.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_action_delete.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_action_filter.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_action_list.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_action_move.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_action_redo.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_action_search.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_action_start.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_action_stop.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_action_voice_search.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_checkmark.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_dir_dark_dialog.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_drawer_download.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_drawer_favorite.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_drawer_history.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_drawer_home.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_drawer_panda.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_drawer_search.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_drawer_settings.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_drawer_whatshot.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_favorite.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_more_of_uploader.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_rate.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_reading_action_refresh.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_reading_action_send.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_reading_action_settings.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_reply.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_setting_about_dark.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_setting_about_light.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_setting_advanced_dark.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_setting_advanced_light.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_setting_display_dark.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_setting_display_light.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_setting_download_dark.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_setting_download_light.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_setting_panda_dark.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_setting_panda_light.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_setting_read_dark.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_setting_read_light.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_similar.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_stat_eh.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_touch.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_warning.png create mode 100644 app/src/main/res/drawable-xxhdpi/textfield_search_default.9.png create mode 100644 app/src/main/res/drawable-xxhdpi/textfield_search_right_default.9.png create mode 100644 app/src/main/res/drawable-xxhdpi/textfield_search_right_selected.9.png create mode 100644 app/src/main/res/drawable-xxhdpi/textfield_search_selected.9.png create mode 100644 app/src/main/res/drawable-xxhdpi/toast_frame.9.png create mode 100644 app/src/main/res/drawable-xxxhdpi/btn_check_off.png create mode 100644 app/src/main/res/drawable-xxxhdpi/btn_check_off_dark.png create mode 100644 app/src/main/res/drawable-xxxhdpi/btn_check_off_dark_disabled.png create mode 100644 app/src/main/res/drawable-xxxhdpi/btn_check_off_disabled.png create mode 100644 app/src/main/res/drawable-xxxhdpi/btn_check_on.png create mode 100644 app/src/main/res/drawable-xxxhdpi/btn_check_on_dark.png create mode 100644 app/src/main/res/drawable-xxxhdpi/btn_check_on_dark_disabled.png create mode 100644 app/src/main/res/drawable-xxxhdpi/btn_check_on_disabled.png create mode 100644 app/src/main/res/drawable-xxxhdpi/btn_radio_off.png create mode 100644 app/src/main/res/drawable-xxxhdpi/btn_radio_off_dark.png create mode 100644 app/src/main/res/drawable-xxxhdpi/btn_radio_off_dark_disabled.png create mode 100644 app/src/main/res/drawable-xxxhdpi/btn_radio_off_disabled.png create mode 100644 app/src/main/res/drawable-xxxhdpi/btn_radio_on.png create mode 100644 app/src/main/res/drawable-xxxhdpi/btn_radio_on_dark.png create mode 100644 app/src/main/res/drawable-xxxhdpi/btn_radio_on_dark_disabled.png create mode 100644 app/src/main/res/drawable-xxxhdpi/btn_radio_on_disabled.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_action_add.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_action_clear.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_action_close.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_action_core_overflow.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_action_delete.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_action_filter.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_action_list.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_action_move.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_action_search.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_action_start.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_action_stop.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_action_voice_search.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_drawer_download.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_drawer_favorite.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_drawer_history.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_drawer_home.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_drawer_panda.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_drawer_search.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_drawer_settings.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_drawer_whatshot.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_reading_action_refresh.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_reading_action_send.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_reading_action_settings.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_reply.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_setting_about_dark.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_setting_about_light.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_setting_advanced_dark.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_setting_advanced_light.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_setting_display_dark.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_setting_display_light.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_setting_download_dark.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_setting_download_light.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_setting_panda_dark.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_setting_panda_light.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_setting_read_dark.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_setting_read_light.png create mode 100644 app/src/main/res/drawable/btn_check_material.xml create mode 100644 app/src/main/res/drawable/btn_check_material_dark.xml create mode 100644 app/src/main/res/drawable/btn_radio_material.xml create mode 100644 app/src/main/res/drawable/btn_radio_material_dark.xml create mode 100644 app/src/main/res/drawable/clickable_bg_purple.xml create mode 100644 app/src/main/res/drawable/dialog_background_material.xml create mode 100644 app/src/main/res/drawable/dialog_background_material_dark.xml create mode 100644 app/src/main/res/drawable/ic_setting_about.xml create mode 100644 app/src/main/res/drawable/ic_setting_advanced.xml create mode 100644 app/src/main/res/drawable/ic_setting_display.xml create mode 100644 app/src/main/res/drawable/ic_setting_download.xml create mode 100644 app/src/main/res/drawable/ic_setting_panda.xml create mode 100644 app/src/main/res/drawable/ic_setting_read.xml create mode 100644 app/src/main/res/drawable/textfield_searchview.xml create mode 100644 app/src/main/res/drawable/textfield_searchview_right.xml create mode 100644 app/src/main/res/layout-land/start_activity.xml create mode 100644 app/src/main/res/layout/actionable_toast_row.xml create mode 100644 app/src/main/res/layout/add_to_favorite.xml create mode 100644 app/src/main/res/layout/alert_dialog_material.xml create mode 100644 app/src/main/res/layout/alert_dialog_progress.xml create mode 100644 app/src/main/res/layout/category_table.xml create mode 100644 app/src/main/res/layout/color_scheme_dialog.xml create mode 100644 app/src/main/res/layout/comments_item.xml create mode 100644 app/src/main/res/layout/dialog_message_checkbox.xml create mode 100644 app/src/main/res/layout/dir_selection.xml create mode 100644 app/src/main/res/layout/download.xml create mode 100644 app/src/main/res/layout/download_list_item.xml create mode 100644 app/src/main/res/layout/drawer_list_item.xml create mode 100644 app/src/main/res/layout/exculde_language.xml create mode 100644 app/src/main/res/layout/exculde_tag_group.xml create mode 100644 app/src/main/res/layout/favorite.xml create mode 100644 app/src/main/res/layout/favorite_menu.xml create mode 100644 app/src/main/res/layout/file_explorer_item.xml create mode 100644 app/src/main/res/layout/gallery_detail.xml create mode 100644 app/src/main/res/layout/gallery_list.xml create mode 100644 app/src/main/res/layout/gallery_list_detail_item.xml create mode 100644 app/src/main/res/layout/gallery_list_thumb_item.xml create mode 100644 app/src/main/res/layout/gallery_list_view.xml create mode 100644 app/src/main/res/layout/gl_root_group.xml create mode 100644 app/src/main/res/layout/history.xml create mode 100644 app/src/main/res/layout/history_filter.xml create mode 100644 app/src/main/res/layout/jump.xml create mode 100644 app/src/main/res/layout/list_menu_left.xml create mode 100644 app/src/main/res/layout/list_menu_right.xml create mode 100644 app/src/main/res/layout/login.xml create mode 100644 app/src/main/res/layout/material_toast.xml create mode 100644 app/src/main/res/layout/menu_item.xml create mode 100644 app/src/main/res/layout/menu_tag.xml create mode 100644 app/src/main/res/layout/preview_item.xml create mode 100644 app/src/main/res/layout/preview_list.xml create mode 100644 app/src/main/res/layout/preview_list_panel.xml create mode 100644 app/src/main/res/layout/progress_dialog.xml create mode 100644 app/src/main/res/layout/pull_list_view_footer.xml create mode 100644 app/src/main/res/layout/reading_quick_settings.xml create mode 100644 app/src/main/res/layout/search.xml create mode 100644 app/src/main/res/layout/search_image.xml create mode 100644 app/src/main/res/layout/search_normal.xml create mode 100644 app/src/main/res/layout/search_tag.xml create mode 100644 app/src/main/res/layout/select_dialog_item.xml create mode 100644 app/src/main/res/layout/select_dialog_singlechoice.xml create mode 100644 app/src/main/res/layout/select_mode.xml create mode 100644 app/src/main/res/layout/send_crash.xml create mode 100644 app/src/main/res/layout/set_column_number.xml create mode 100644 app/src/main/res/layout/set_name.xml create mode 100644 app/src/main/res/layout/start_activity.xml create mode 100644 app/src/main/res/layout/test_gallery_list_detail_thumb.xml create mode 100644 app/src/main/res/layout/user_panel.xml create mode 100644 app/src/main/res/menu/download.xml create mode 100644 app/src/main/res/menu/favorite.xml create mode 100644 app/src/main/res/menu/favorite_choice.xml create mode 100644 app/src/main/res/menu/gallery_list.xml create mode 100644 app/src/main/res/menu/history.xml create mode 100644 app/src/main/res/menu/quick_search.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxdpi/ic_launcher.png create mode 100644 app/src/main/res/raw/change_log create mode 100644 app/src/main/res/raw/default_avatar.svg create mode 100644 app/src/main/res/raw/sad_pandroid.svg create mode 100644 app/src/main/res/raw/thanks.html create mode 100644 app/src/main/res/values-sw600dp/bools.xml create mode 100644 app/src/main/res/values-v19/themes.xml create mode 100644 app/src/main/res/values-v21/themes.xml create mode 100644 app/src/main/res/values-zh-rCN/strings.xml create mode 100644 app/src/main/res/values-zh-rHK/strings.xml create mode 100644 app/src/main/res/values-zh-rTW/strings.xml create mode 100644 app/src/main/res/values/analytics.xml create mode 100644 app/src/main/res/values/arrays.xml create mode 100644 app/src/main/res/values/attrs.xml create mode 100644 app/src/main/res/values/bools.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/ids.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 app/src/main/res/xml/about_settings.xml create mode 100644 app/src/main/res/xml/advanced_settings.xml create mode 100644 app/src/main/res/xml/display_settings.xml create mode 100644 app/src/main/res/xml/download_settings.xml create mode 100644 app/src/main/res/xml/eh_settings.xml create mode 100644 app/src/main/res/xml/read_settings.xml create mode 100644 app/src/main/res/xml/searchable.xml create mode 100644 app/src/main/res/xml/settings_headers.xml create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..afbdab33e --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 000000000..0a09a1b9c --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +EhViewer \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 000000000..28bb10af0 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 000000000..c7d1c5a83 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 000000000..7c62b52a1 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 000000000..68bc9c08d --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 000000000..a1e308618 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 000000000..6933c1eaf --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..2f486ebd0 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..464db56d3 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml new file mode 100644 index 000000000..0d5175ca0 --- /dev/null +++ b/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..ab55cf163 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/EhViewer.iml b/EhViewer.iml new file mode 100644 index 000000000..8d4928417 --- /dev/null +++ b/EhViewer.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/app.iml b/app/app.iml new file mode 100644 index 000000000..4fc7e1434 --- /dev/null +++ b/app/app.iml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 000000000..c6e7ec635 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 21 + buildToolsVersion "21.1.2" + + defaultConfig { + applicationId "com.hippo.ehviewer" + minSdkVersion 14 + targetSdkVersion 21 + versionCode 35 + versionName "0.6.11.2" + } + buildTypes { + debug { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:support-v4:21.0.3' + compile 'com.android.support:recyclerview-v7:21.0.3' + compile 'com.github.japgolly.android:svg-android:2.0.6' +} diff --git a/app/libs/libGoogleAnalyticsServices.jar b/app/libs/libGoogleAnalyticsServices.jar new file mode 100644 index 0000000000000000000000000000000000000000..082a9180eb4f84fbc4fd85b2bf61b942c2765526 GIT binary patch literal 511310 zcmb@u1CVW7^CwugZQQbL+f}!0+qP}nwr$(Ib<4JG^nKs=P0w^kzt{hsnG>;3#EG4m zd#$~4W#;;2=J_cF41xjx0RaI306@t7=j*?JAOS!CWJHt%Xe4As>Aofa0Du90N`e1H z2LSXRqMv`ujPi%#zcb4S$V!NcC@Rs)h~CRgO-f7A(9Xk3(NNAz%{D5~FEQ;LI?zhW zP)o?nJ6C`}rk!J+lpG(I9v7XZq*I!;1*SvgxQ}~ebZK>I{c8m3;_O8Hf8-1Qx&D3x ze+|@H-^Sd;*wN{Jie^k=T4tI`f_i*vb`f@*dR(Ge!ESa|ZdO7WLPA>gMrKM%T8>7+ zZc2(`Vpdun!sYhpPWTr}N=9meF34hW;ZAyL)hOt{9$(tS)+q5uxcYw{4+Q}5ub1*a zzCDmXcMWZ=>HZqv|C$2&KNP06wx(9b|3HZRKN9NO7&+LQ8~pCZf@ZEgO6m*{`X>+EE1^)CVcYhB~~uL)2wcQiM!G8VOUQ2Mi~<~F9ZhF1EHj>b_u za0B!xfu9p{e&ONC(eY%2w0#T7fI~Sg@=28AiEBxN40Zr;!Q$u`bw-nK#P5$j+<-WB zN@-N&!Jzui@%fc9r(J|rHZ`uvY{gEhi)84RJ-=LK*q4!3BGhcvMoW-?*TM3b;t=1( zWgYj@==0R9o}0aDHJ8W+UBaJYkenXe^`C0Gf(XX*^pg)ToBFSU|Fu*0JX4_)|4gj= z&-M3eK=}7l*SFEPa(6N}{HJB3{I9T^+c+6J{1NsKDL zqF8=X5|{y{+k7-ev2t0g)9$js1_q+0BuoKCj4}ZDYVm=yXo{F*9OaV*-Nz4~WXrsq zrcGWe=pf6{@#=j4{t_2pCJ%c+2jZO&C&+G$K-`aGAEB6XaUc~9Q~K*_ z6<<>EW-5(ZjP_v#i{(gt_k`w7wYF$q(z7Y(r4I!(cnJMo@JD)K-4b-O^7XWol#Dsa zR#}$CDXFSdD9WX@1a~zfQF0JP3(QL!%Qz44K5^v$|4#>bJO4*UHv> z#v>8h$o}&KHVnqn$&&edv!4odW)P7eV7<&rg_~J?11#52RT9?4OfxIcf2L}1KJ|c1KUDU5AKXPx_*SJ0;i2yCM*<%fJz*aD91%OgGNF{0QFQgwh-i%NGrA` zl2mIgPXf_2wlwz;B6P8*%a|xMwx>t&i+QMbmJds1s7IONuG2>%19>K-sJk#PrY}lb zcJQ0hCZ^4hCbTb2J$R|JL8roq@fUT$$U!)<;T=nLnA~?!!}9=rF@>13AA@x5?s9ab zG(jz4NMe`pcDFR-t#erC7fFd#b*W@5S_#!yX7ZYj=N+RjxLcCwG*uZ$#9Es4w?qkc zwJDHRC$r)Z>BeA}6s~IxD~4BhSJ`Yc-@V02g9TbFPEd?Dl^vFIV9VN4G)&wsfeuoi zJPq9pVidsEHrG+l{gP&ZhaE&Y*>q8R7B}JfV_wcxo0QqyK&61HHQFg@=aBJiYFN$E zIW(CjHF8)mAxp8^O<1^UQ%N(dibHDaiH=x0-K13ptpiv_$%oBzC3#*GoE~*-+q9Ef znkr;e?V@Px+C}ker^!Ipu#<|T8-!eRw`e+Q^Ej|N%fq=)S|5KQbv7E;7on=(=Ujf) z)pz_tPUL#Xb@$64sZK_1kj_eU&(xYduZ-zeQPs1{ZeBi_Od^baxTvs^)=;ji+T(<) zARpI<5Cb7K<5gk7uS}1f_o}ktZ87F;kt&ZsO{1i)QhEH?UPeyySixpIC= z!HkJuVTZD*XbW}*<@Un0EptP*EqB9iRqhIOMeQox^Y$v*GxRFhQ}im?bHtIq4aSk( zOX_r$N}z+gr66Z(0lHo@Y5CfT=AjRQ zARnJH3a~8;s67Z!!HM+8U5U0qY7Mb~VO~y7ErK==4zjkyMj8f@4gMBtVA$%wd8Bwm ze>HLgkJH`4y^wWsnY>~hZTe*=5A^7&)&CnP@cV4-Y0~X_!!@jz^j!dbSA-9*pWsyN z&==5agU>JU@sY3w-%<^qG&Bxi^49(;s}QVTJ$fUU4e9hodG4-z(miQkXRJ5cJL!}k zn-ei5OW6m8Xn?~HQP{G>CUJd`1LqZ(#vpORCulqHVeA?r`UC7xvnl2S%J|;2sYl$= zU*^t3o9pwYvk)G*2n?~H^)qT8;4_ugv%-TR0^=Cr4Plf)1J{w~=H+|9Czvk?i0npq z8uK*CX$g6?Ds+4TxX3|7qF%g*^~#(62KS(Kw*xZOcPJS!>h*L3<#@1= z(NJJJyDV${2WlDW1?IEZ`v>X;u5Gvq0%x_I6+XPMZ(k1-=SX0DPYPZ!-5h#Vo?}x$ z4cO_`87CtG?&Z^_$Sm&KO?!`AJxpG!khEZsc^Ko?ew0f<4w*d`1$QuJP0bz*6ni8| ziVG%s-Gm}IdVP!pFH`Eq3OeG!mAoWRINHPz1=mcOqVzM!A*uydUW@?gGo_R-oSb3+ zX1TGH!trKFf>MAAH*GTUp>ug8_v5?)%d9f4;u6D=N>JrcN6u!N4iL= z^^UP8)yX~R%k!3$T)@%`3GV~=7vNv6!L~KPtrQdh;Qdd?`1h_s`u_*lK>X9e*3JFj z4x(B`(-m16<%_gcVliJ(1E^n9d8x@?SU{LUFTaVEg(g{vzi99DATEb7{WK#TNV{XH z8Q?pjwjpJCGLZK?R-xB1A!JL3UzN-AiDR|(di_1?ptjfd9k&mX`k3DDmiJ;Xn40#8 z-d{>X+Jd2YI8;O0QvQ4thIc_jJ5n9nk(G8mtyQnJcFYAq3R=i{mu--rrBXntT3g9S zF~ks!(KNYs?;Z^<`H2yu-GYz>kr{KRz2ok1L&H-d1lnMHU$zIYZJ?7qJ&DdmYHE5Z7d?*t0n?8RBTd3xlbMGR4+lL31P9zTlK7OU@hX-3cj12cc#x@CS^7 z(TFyxikutGU!pjay&|4m2|kV*!)UU}oVm5b6O}U&zv|OBpERk@C?jBf+dV0h?th?M ziZ{0LgaijpQtJG)R=+n*J}wvU@ZT;fW)Xr?73r!9-zu!5ezk-)?C>?!Sn#T(Fwobx zqk^U$NX{|2P$5_4amlai4nD8Uw9eW?CeT>zQg_)_4 z5kF8vQFSr}cgKFL#|Ni|VR$A<=3JcFF|d@Zb2edeHc_KjG}!O#b+!Y>>u84@3CBB1 z1Fm~g+?N3RTL5{X-#WW4*b%MdE0<{q(nIk4uo(4p1ebmmYklCww>qk@vHEe{;-YD5 zpzmo*lfBMz%^f7?g@!t`zk9|Hf7M{MoygO1>>iPNSWup$?Gx)1H?Q9_Kf?GLPaWb) z+_2k|`w_=Dx*0Hjn`7SOZtD1vUq~|9e3_>%-71_&7 z^cRd}(+4tAY`f$R!686GlB4)8;~RY}kZN!Y3*0VfpBWt^WT% zAv&!QD^_WYQo}h|!}>YZj+xQUnQ_TO1EgL4h>|OmC^2D-8jy~`2kv! zyK&f}$n-lf#^I?Our_6BYl2Bt4`A<(Arxn>S1Jc{Evhx_livdrCgeS0U(!$G4QZeI z3{n&NIE~WGQU(uP#u40d8T~}LN8z&q@hPZi?x7&=>=CRJHw+(+MZ2+pRdu$1C%4@1(%KBhYjmpLz&E zw-=rODcr=zV$DAN`0*N+u3^*%O?c&8$0c`*2>p-4c32=Gf!p&4Q{$QAJoz0p{YY0h zVIP5U7)*)$2d#`5ihUPwNAAZyq5ra*kK(GOJ%0}3=aBzoH`)G=?55D4v+$o2_`gkL zwz9SDx)O>{O(JW|d9&qj=Se*)!{A~Gu0>Rg!s0oPQ>?MKgd+dDVux(0qw#vOUYOh5 zmp%|}lDHSZ5Ar6*+HT^Bs&GV#=M6`%ocp^hOp=8+_jmq2GhG~}E z%{iJOmhL=-sGu>wHBH;}-gGe3b(ZQk(kfSs(`5WZ#hMG(Bs#y^N(yG|C|EKzk8CcD z-phW~uqb;?=CNyovA}HrQ`Nn}AkT`BhXkxvSvNmtf_jl9`Dw=E({Grm>9E~o*X)LZ z30Pfel+x--pW!JPLJaUeU!HhKB5ma1%_^ zfBNKVEwfP!AB604%##iV-ep#Opie4<6576LQ`(p8x_QxE<=*AdSRhNkw;Z}u^&xs# zOo_?iU`?)lm!+{^-j>NS&!(m}l(37$#)1L=lsG458cP<*hdyLePr@aS5euAl8?&pQ z6ymaz<)9HC>LyR>H-o4Bz+p$UeGexaOs?8-M&vmjMNIxmw0wk^hTxJ>Jih|*`DRhu ztlM-#(wK}LZ`+4xcxZa;EKtX7t*hFnG;vNEvX{Ymu205Kf--vT`Gsy7(U!|LFUB; zM}a>0*jm%m7sXd96SpXV7I-$kV#9zbGetNbzE0tB`7@dvp%a}!>Y2j>pQ26p?IX-5 z{UI3f6Uy71{7y&mkcC1jK>4nd(3&lO{u7P2=$=(wtKm>$Ms=p2@)r|%@*&d!C6(z| zZ(NuNCpx{r@eg;OpdY*=%vV1}j2HYQ1IW&C`L}8&I)-GF3dSMcBz9Tk zUx6oV1C`QUV=K$aBC^;-hdBeEKaCwki;qnn5V z{Pas^?t^oO(xuV=z(F1)UmS|R8QVv^?V=*0A>IlU$GR%P^$PgKeB_{!l<@9cl|3TP zGLe_<*#PU`G*0P|Qegl?$3S63o9v!2xOa4r2Y-rRD^a;xz|d-ZlPTdR;7{U3360V^ zd(&?9{FV2)-}I?a{DuTtZyP>U%*dB><0<(9Ou}6f#1}))m_e^EJd7U5u$jW!x1{?s zc2@oacMW>=Ee=b56*&DuYHG|0Di{GD^?Fh6=FR99^)FkoL}NZb3Jd@M{|C$a`xuMk z|BbC6X84b_NVK$Fq(=$SZ8cv=0Vp;+R2XTl)6rYyhl!4sRBR5HJp2JGBk1Lzxaclv zNFm{WTe&ky>c0biQ;0BXo>tN#SQTM~kKcQ2KU4pC{@j`ifM-Af5A8~=bSJ)RgGYLG zYBMkROK@5+uzw@2Cn^Kx)WSNo->f`Gk9|A7$BhHWN#rKPmFEtHYvOQ=7QQHlOqOUZ zkq{W;qUAa=@9>r$<-mQz{#V~A)vxEE!B3q$quEn4cj~Jy;#an5wco$Z*}Z zMIlqIP#8vU%t9uGTur-3(eUW0KN@M@@=9<)yMtcK{nbjI4d;Vr7l0>!7G{~dgiwcy zmUKT7)zumm=_%Td%qV9SZ{b1O!J+5r!Ww6S@Cju+1ii;Bt&bos=#BtbGUPEMkpNh0 z4|#RbDbop5&zC8u-@zA+Fiaz-O)-!LPPPH5tP~|zLLO0=H%(Nf!s|yls>s<*8ODS= z3{dQ?ft1>ZO?^8$X@H>_e8O%-jcT^Z6e3naFGU!o+Y6`kaMonD^ZlidGq3}&6n~(g zk3atF@4-W~|4|>E^iBUbCw)_6hkpQT@&A^`&cW8n*1*~1ADD^%_sll>Hn#r&G_wB} zC8(M^`~~z{>pRK*5%}*NG^_#XmGrBbk1TyE=)e$K7*apHEY8@#hyhBUfe;d;L5~q+ zHAvzP5sZ|{oe3Fjol@CSyS^D!Lfg{5a*x(bS!+#oi%R6=d*eED zoQQ{vBHr4aH5n_g4T%j-SpId{djyFAUDG^fwyET03;N&7Pxxj1XfF!!dzyY2X5i zS)?B&J&u$XlUk0Z$ut#oHPvM<4E7>FKnUu>sgqT;)KoOsWsmxKi5PX&Rd}XZyyeZ+ zjYX7$E31?skkY_JAeX; zD7BoI?Un5}r3oGM9F^6zwQrfT)Q&VjuQSw_%cPv{F7 z<~NzRNEoX>N0^ybB`qx}rJ$<1(oIKNRXFaZ5FNv$>8@To&lz*&?tM?ET{UE40Eb{LR({|vTpx< z4d?%oL>{SDR)|9V*vjJO9G8n_U2>h+IduJeW`obVLgwL>GiQ`W$W^LS=y=+N(lGBv zs%chEHpX0Y_acO9_0D`wnA)j+q+O}$TsK)W<}XGZ09s{GmE!4StLUsL6^N(j(A1WC zPOC06QS-B@T&n7PoQkOHt+fMKz`Mn^eYSU@G>tyl2tP4T@l(b_F}SvwMyY|bD7A@3 zy#PFE43*=i2yj9W&sRW1X0<}Jo8{p49S7t)$L&WKE{L%QXsV}%jQTLzJ#&UcG6i!W z!kh%&>M--`kdvQr0HH6z2xIf=$Z%^g_f~+U9KDhg5_NI}cGe-h$B^KJDQh42!Gx~-(x8sRp$VY- zy<9eb7ab*F>1j2k*Qr%+Rdfn@B<`u>K(wiERvf|_2a*TJ|yGmCQfd9|Jv(p^G9t`FjLXb-3f|VS87!@fgMe* zhC1!ZTT^wlq7-&P8O~rN`{En~tPFz*q`+?@4yHwgka`Mw@WVy~#F(C`r>~+vj%0qL z&?ddIe+Q5N(aO|K=t|_Y2htAEwOWO>z!qS%d4Y~PjBf};@ELT@m%+jP$m??t6wi$qO0V&E6C)&E; zkl!oAJ?mIsGkN+OT zaIzI~H0{KJ0u6K&Q=6Q1omvvmpIJcYz>dk1cppg`X0~C8OQ&}0;t7q7^{hXSM1&Vt z<`DP<&}gHl79y695Nt1k3;YvPl~|gd!6izx7W3tWbI&e#eZAy2HKJGeU6Y-5*?i`M3e;tO zZ;+yL5rWek*QOQpD1}pc{(R>{4!tk_N(v7VS&JKr4}Qs<&KtR;LSvTW9GQvTM3CtF zDdp~Cc^DgAY3QBY$|pz5LX2ni-n#wTd{^{~8GBq3+&onPv3>Ex~rf?^_9`K7J(z-wW1ww15tVj3G z;*Z#IBtS2#@R=Wih)9&B4Ww8PCRm90HvUH~Xq)I~a3oie3Vh44Qfu5>q%Ty@a=Xmq zzNN{aKwtbSLFYS;+&RFU608Q)q)!>5YfYNIlo{qu!h3 zfc3V*hu@%;A`HSj1r=RnRGw}vdc2UXUbn*#Uutr2WU!ur1M+&l^c5F^i5h04YtJVFf97pe-MVw*6@$t7u!ED>Kvr&C-qV>F$SA z1~EBbK+dia$ku z->pbxwQlv50rD&b&dc@Pkc~CHM8Mc=AhaZm%P}z(a7eR+8hEMYOFU12gY)P`m1YsF z5v0GMo``PgrCg*?(Qp2K0Dc8B&*C7s;!5*EonI^9O(`xv&%pg;=8UH^Mva~+ieKE^*Y{8{s$^P5~Q z=L_9M99x$7x+m#th#<1wQM%2(e>tXBn2@Yk_R%qK>xlf@T1MVQH31aCT zTSqsN|K1~Qs|Vae^}~zPeqb#}A`*pJfWnyYrGSK~!ed3^8qX3M|98g9NlUxBhQ?FUfF#sDAuDxtg6@zNnA7pAz#=9aWC*U&{mbnP~VKe-N-ROfUPZeKOx#avouU6WqP>U-xi2Ewv}#jdx_^W#YmMGXUeu$p=!ldUgoUy*iSGU z$ayaZCa!YM968u&##ia^-NpnyWd}wX@KNZp6eQHq(Y)3y9?sgtGqPh?z;(gQ2k6AR zkP$4(v7w{MzxEToD+`jSQJUxDY7e-+p%*RXU;BzYS+Kw8mgJ zC2w_;8F)k2U&d@R&N-Y}<@2o-YRXEhSiv{@aG7CbH1laTQ{2f`*UM|`T||Id@jvNZ z&B&_p7OHpI?J%Pp=|Z}Yrh*ZL(6K^BIQCD(Bvrw?q)#i1@PWg_uSPz2(mK%zz44p6 z@i*}J=bDI=@C4PkVJ_M9wngpSgL;3;=Dy%Qn(#`WdW9(z(q4}~APe#NfTE$UUrczW zB)BcYbvRwAXecJPM%U<7%xsJPrmrr=aav~N5r8ILVYJdbLDzONyh77(MK_;;$+XYC z6w`IIDSQ-!6IJ`1_NYMfNfV*zO1`Rbp{yAgIPffakRFkEU2*gkT|l$AE`Zg~u{F4B zea&t%PF&nEv|JO_hHNO*IJt+tlR2BEADV;C(}6=UfwT_nIHezVSpy`Y18y1DLed&3 zKeAd1>f3@$P3#?95s3|?DBns<6Z(|OJf(O*Dg#!%0l$z@x=O}geN{6$t{Iy-8k;dm zbL)-R|9WW$?ZgW%Z#8I!MzR6MvljEZ7VhE|Ro;;g+ z&>wlG&`7@eh5Kk9w=ST1gj*1lbZr*Kz$uJDmMQx|GWROL9@23TM4AO#YUsJO-#T%4 z;5-kl+-^?C!>>`(n5ct#e^hem4nP~Vi>>|8!qE6T>oqTum(}DcohjFO9(`mA(D7)U z_RZGtD38OjEV75mBnAWAJ^%*1j;1v+@wXyPMiSYI%H`Dc0dM1ivZx$Cv@L$)OK@M!EhcfCthM8yzoEi@Fw@A z-OK|*fO0Lf=KUBKfXIP}^?$ZUdK}vBJEkMN@)NHgb&-vC=ZqZk2wa)L4Z^@7Tge*w zqN4{h?0>pQDmJ>uk540d#dVH@&|z?vHbWI--{Gz)bmVAS$U19vdeA z%GwbU1c63@&q2xGM$M;-nG3DYL%5N7z_q9)irXVU4A`-f&8yE6KW>PgF$9;{HOd(6 zHp49aNoDCo@hrcLFFYPP?k_$oy0H$Y$cOq+O7_4&^Nm_O<|<#PQ-{jmpws3L>z=Kp za+svYhpXaM&eX}6qQx4Pz#4((knYs~(-_RK=*#R?qxOXrMHzQ_G=7hryFnhw#jY(nE z%FOlxR`jU`xGTutQK;?Y5cb0pQnm-NIkR5KBcw>8E*8LwEv*9cieBi>n4iQR%!CPW zRR{oBi13r1Gdfg})pXvBHyX+v1oi$@Y1lL_a<(X#I|0iLrP+?P`UnTalU5rxdbt4^ z^(XHpeB3q!1^)2(I^cRI;BoDpng?{>3t_I%dwX2Zq2nnRNLBQxqis&#sn(DXATg0U z(lKxZ=?%(<>Xfn8Kr(l-sUrnlUs?R*>g1vw+oC(DYM*Y!3u(^yIlnWRDpeVVPt919P(~Tw&xR&JLDsZT#=)OCL%n*xjz6%K{E@`Ji zCBaqu9W1lYkGmFc^=#Ru0kFP`^PN&FxL>^4No8xM7ZMfTwDzWw;C#7lOwED1(i`U2 zJE_R7oSZLm;at|ZAIC$ZC&h3+u+#ByK1BAW4&c4P{W)+x*wb5ZTaicn;Ju+odf>i( zCc5Ch#3}kx)8_-zzkvH_L7b+AE;|rvUn3E6B6sp42^+DMRe&jzL@R`UX+m?&1RJ9b zI!u=-dbu4j_)=*oEcE`X5@VIdirV{6ZL=orKV=F2r)t*!2P*sD5!Qc!q{=pyHny%d zqUOd{M#B0|`u|B2C`i~Y@?&@#ayersjIab1LpB-9EGRSviCA(D1G%!}99Y^n8 zjfMLNfjVjx3FE^k4eSX+`Y;MVV*5W-gA6yRtWEXp9u|t2di?OKVM6Gj&uP2E~(Z$bo6yoP`3khDv^n^0%dcYb+Ejt=PW`In$ zj`0N7SPLk2#-DEMO!Rgo#gERH%_77gK9{C4ws#5V*Up_PXOWVE%3Oa2AmUEIOlM!g zj9%d-)+pgIfl-e9tfBd&R$UtJa%#K>T1{iVM^B}SMQM5bRJ^iP#XO)F#6>MfdRLSm zx;0Z-_(d=cn?~#cWiGvV%_!7bDlf?5kQ#J{iaEloNHUmi_7#r*S5m=+u!lwUPf{WC zPonGZlU#pw0sK|E{-471zg1ruV@Jn7xrV>s`JWE|Dlz`I0#~cLdnu`+e%Tz!Y)#Rt zV_@hJEP;R~=!;Jn1R*RW#4U{xQ$u=cx}|qX<4K#8*qV?#<51o9{+*q+mE@nIi6>jj`2K!`n*0A z^EeYu$|FdT=F@bNmkW3_v?9+;lozCnka5#%D%#NGl$uY@HY?;^{6(S< z=SHs_auRWN8j_Y`kcK=F+R+pw)kWU#6h1-%B!W&|L5@nk^Tk?7hPj{b>Es`NSeUgQ ziRo4fZtRxI@A^9Sh-fp2&ZsvYCgVb2IAQ#QOfLo8J&oq4bNo(a8Nse-2CG3fQWpna z5V2b#mJE}AiU!A#N4arrh*KqTd6SVstSi(MF3C!lGprtXX0I3a$ znyHf+3+$_jO4!oF@y25i%WGB+M0l`>xl*8~@FAC9gJPNq5G@k`v(kx0_6C#(-->Y4B4~_cD67*? zmpq6zX?E~o|KMBsgMrmyaWcf^#TUUg(4AJLmz@btYc!L-G%$EkjQ;9}M3#*PMPred zVdK%#9g=u|^0Jc=(LKoGk{dPp01dCi9tq)Xc(bok628WHr2_(mkDUu05x7=Xm)NQ- zP*M2Ba+gE2Y&FBqg$35Hs`i1|+jV%cF#yM2)F8fWHg`Ls8`SikDA7j@1Diz8UAe*d zjZ>cXl~cP)8>9@~(RZ>uC8f?1l}AUd1c%veQa0}Zwl$m%cQ!xf%h}=p>%nM>v9&vC z#Rt-uORGKYb1(d!7gMaH-p#3sZ)=!p7H3=IfLkU)$15;tx$OjCqZ4)!S)6|2Jr{1w zF7l4yFihp_y4ue`DHF$r!QbbNCyiw_QfZ~ltm)w#5xwHNe9irt8$Z;82k!iD>DY7BWy8-b1#BkvA1cThBvJ+q8?f4(Gum)@lHF+*)Z zZPH0O;g@0(HK(vmATUuE*X~3y-xH@2VJ;p}6x;4?@Mj@oy3brWah?vaVTe*L>W5B7 zS}H*;Y%ji)L>*&__O>@P+)<+E7>6z>BGPd5r5#(1c!koM&4vtkuzKrmiOfm`#9}S% zfIb74zX%n$8=D*zL%Vj|5G_B%Sw_Z|mI+^zS>dS!sJf_|;#shi3VZSm&hora_y~Av zLpF*TE=7t_#7*0VE;Bz%bx{+(plTWpFc*kwMb0x4!AfmK#OxAdie@=II-Rjhv=lRJ11Ib|aE<+M2%10H`=^@VRc zA;`=Qh*Uw!#>XG)PE0`YhWgH1F(!9pDY}%N24`vySA8l;h@LESG4_sE+L9w0p?Bs{ z*|?q{NrWYo8Vx@;QDoZQgM5Hm>1MM;t(3g@k$}_?(&9AUrw%36o_bH}%7MD@D;Io* z^7om8X*M7_ANu@+fuOw9C+Avq>EWcgE#^1N4&723MCco7?JPH{Kr%lhY9i@=EgDoU z5^GPGJCNv^nz=q;{U-W>Jhta>(VRLt8PLQA_0}Z2YE|yJ<0N6;i^hR?^)Hqgzx(hr zFR|t`)z4QkJJOwp%6m&KKvgvC%1wv1hLL?&s?Oxlo4jr9i#6>F&Y(}g*y}TDPWU}p zPqa%BAAFm*w{?pVP^}>voqgD-$$^VuQ4SLeC?L?-owv|kXg8pJpsjYmVIVL0K2NlY zLiifLR{T~abk2&%50S3{qBi24NAyQSTNwsd6p)BZT+c}1?M%Aoe6zCjZpz@)Y2&ii zNrLN>q(*Aqwk^1A*I1Y~_(~e_fX|gy0|fN#&;x@L39CyH^%Izur+uO!k=sWFJUhu~ z9nVg(Jo>s=2@pP7l7uK$*aqU=d+B>Z?jQs1;Rig@!bm$XCi|d9!G|AFg)1UDwSA>G zkP{nfgUzAoKJZU{!uYDAyR=q=Wah(>hwQS`a*5>0ZfLprEy&PzXt~8IycM@BUNKC{ z%=6F3=ED?;v2=D|mM&ROK`fe*p6<&RAmN8~CJ9*)BC-Mcwbdq*O8K65wSZt7_UIQ!#Xbga# z!lu2_op)5~VOuqW8=!`RkLfiIkWCWk$^o zKi)?wR`c0_>I61Qt7p~j*{Zm#4mch@x&En^tkqKWVE8?)l6y@aQ%qvCh$~jdtd=l5 z-jNVUp25_OU)tyvutBl^P}!ZvUg`!78tCwxb?VT-77OCDaK%7v5~(D>nKp31H>|{U z(zW#?`GT?1(cC2;Q@6EaiYlciG}l$p1D?X3?s;nz|NWVX=Y2)^nduOGCGbig34iPn z?HK!p-r?3(8a+`rG|{)oUXNg`4HTe`;DpOE*|QzSo;!y(M9RUM@nq*Wb3hp2 zrD|PY4;Mzr<_Fr2=W_g^0CaVVSJ?D9{UDbG?g*f~#P%%gi%&8!SutWftz~hmdFhQ{ zWTDMl6A=V>_}aL|a4kwI-Utj_XRHBMC}t>q{NRNUImBx+wGQ!+f86!x#QslK&W{UJ zy5`6I-`po}-Ee|FjKrSred_27#*pi`wh-+Dk~Yl*sin~eGuK1E4l28S)m!lpm`)VUx6@#T@&nj(iGmVG&Pp_oQpN7Z3k4{2U6 zry@|G73PsmEm~dKC7B2O4zlbV<^sL!TYTpyCt-o9@_b|L^o4WsU_GZ%z5Q}uaHF@Y z(}b`CQ9(ZJrmowY-gL9?U7hcve7zfBb=bBLMT|BK$&Q;^0wrEls!cdlWZg>Rj^3^% z2{Ifh`z4~r?b5dx@1#L}5su4Q$&_lcv@E=ivL;^0gp%hemr7J11(;Ncsg1}_=L-B$ z5X{f3F_-WVK}eg548n6r!Tx42)AX8iL1})w$CnL#HBNy!xF}}R65n>&B3Sv;E|O$L zrkbEokJ2zy^LWMW%0Ne5rtc05s2GymN#lc8Rav;TFU1jjPUERU$yO>JYyMXh{>oTb z>oU<%@VZlD3AUXv;mtraBBzH3^0PEhT4sFa&PYxz%e>y!=Uv+k7SFGFK)ba5?z)lDsF!>7NH7 zt=+;YqV>7(Vhyl!!t`M|l>j!4I4M#%_>N?gs-mPPtMG+l4enUEDe8Rc*=1F5wrM9g ze_WujyQD_8Bu2j5Y(&h?nQSP8hrhea&5Y8J>4@dJ6=boBwJdM>%w$ug!tZ@62Q5WuTcTDzi9aLZh1<5uRKBcHRJg4g9Zj#M3L zPH3=drZg>U#mUahLL1=+i^dMT6~Z?f52YLOn#hsskI+zusHV30_d737oFS{IF6|Z*(HzJDRZ#zcDQZpbI^u0ue^}cXg7~EZH=Ep z{l>{rja;N2NnFTK_L{x>&;;5a9cu=s?~%Mhj@Y1s2(lJ)^wcx&hr;nC2mPMKtAMHp zc_I6D$45nXL+-!TRGaTY?YW}Kmwd{QH%0G+v zCcNfypjOOI)Xl%ZxvQCeU#LQXA!fNjkbwJq)~3`^S!z5m!;zwUyMlj_d11{S;twJR zPb!g17@CsjPZqk2m~BU;BZ}7yy|6NM#`Y3LjCS@ovc>rHTCo2OBnHk-w<4OzR!_b9 zz@$;@xC9dWt>z47tv1tA^)B<_s;HXaNHvFNCef&Dxt_?E|H$}b99xa2`c0Jr<`;!! zt>W?srXGCu+UP3q&7(#>T1}Pt7WRuNsn7$B(<9$Z`ZI?sggy+WU^j4Efna#Q3oZ#a z^f?KREx)RsNm2tTE&xw2ssx#*AWtj$joNH;Mt{s1&``5L;t{JMbSqs5qsS?5pTEC*%> z?UoZm9V%~I;k-6VJ+a_*O(1SK9V(Nis4*!S@HGDLj?Eio?fC?3(;AGbLcRd^SSyih z8+v+IaxG95K_O>q0KdkZN)bE!=MS273K!du0a$&I$*#@tIqOp1l-7sCU@kZ9N09SP zk<*Hb5i`Mr;{!gtfUf%W&Nizgx@pts5?JJ#J}f zy2a?WO0(gaD-I5t!u^SapL$m4>>nwz;LPq+`H>Z_xq_lA$G7#KK4R=nC@EeY;CpQY z`_Fu0R9v;?|A3wG3_ijZVr5dd$v?|o#7gV^rVJi0%g;uv5bUuMc>JrWkE&wa&?zzv zwFvL30=ZWOeU*y3jbLsFpM8g%@g0AYQ3uFj9-2noIa;($nLeVg_){(2lDPT?`*=m` zdm$4bu*x95V_Tnv)s1&pbd~RnL6D*U)TnBpT-xnus8Kiu|I+y9~t;+YjxM8qbp z#CfHO&VDIn{v18ETEuHfH$MzE9V|;9l1h1vKQb7PaHr5&Vky*!_>48U(M3kRZx1o{7%!~YFiu|Sz#+WxZPJryUqG_d% zH>-b0JzfmAj?2a-LckVk*Sh$1xyuI6Nnw#Ca)bbx;&^_kcijST$k1qj3ht=#C#=o6 z;5&Ul)EUH^z92=Jo8|5ZPF^eU%p|>9h*a#6UL9UJaFldeZcKBvq!kZ^Iv$;Cr_uK% zUjwUVe&E^=#6Ca90En(2={-X5Ja)iLXbRrFnylN#=n`0Ly932HpHz1J7YZBUbAEcJ z*#VEGWNp;8vU_mDEO@`2;tGZmPuUuP&j&>zj1A7qLzxHrE|Z>A}{z`ZQNdRc>gLmpi=8-xE1>5Eez z!rTABLzBS&X{)pT7o$#z|NPU!|6Q}3oM0`7EPycj4V|>Cf#<=kF3f>a-;@H3Qr&MB z5C8~`0WAYDZ{(>RGk9+8xJl_nq(jrCysC zsU)HxlkiUy^F0Ynx17~)6*=i@VA$n;N=rfg+U;&4#le8hHeWp@|zpL3SSWYSA)tzP3^KZ`TDJ&*ZY*0SY*Q|`i z7{(BE>1>YgUW-TDgE5#x(nyvj!jYDy%}TMJRFN=7#Bu%`Y3~#yTGwQYmTQ-7+qP}n zwr$(4UAAr8wr$(4+IRomefr+$xc!~eaUSNwdR!SZW8@r}W28hhqp+*nisL(a4MTMC zs~YE75JSx8!zFam>AlM+0;*eNB-Om?DGcTTSj5vpiEctcO{e0yff48GWn?GHjf(SQ zyw_F{$AwG&iD8=Ym@|3nbfeSRcwE<2cwI?3yX^rg@MEdYTPnpu6VdUCd6TSK0(2nS zErY?t#&`xR9$?h<7yQy9uFxzW(Wq@xB4UZ>wEhXg}P^99-NB6LN)BoMyO?+?SqtRy58sDW6YLfe}*BZ_x3GXzd@HIEs}2!12yDs=a;64{L3O4 zf#Cc@yDo_K8Tw85i2Se4$T5r@`?hj^19L9Jy!BXusq1FTc6@BGmWerww#x#-HnJAA zBTS7}bi*Is^NBg1;IG7Soz%A1oFnlgjWK)vQ8bkOIx;~S+~itxeRCtXbZXR{nih6uZ2!Es9*>Le=T7< zNl9`4p`pv+BLH7JCv}uGQFS(cW=zhcY6L^VpS(|$EYB^-fg#6!LKyT!l7NUTy1*#O zDb$5bBwj?`q?bf}t$@vDX)v~1oJ(qq+S)pb?T0EElOphXOiK`4MdNe|B?;WA)!Ja= z*7g35R=apCoi3u+r`&x0?#1he`sLemu=5G$J=0bqijm{7c@e7P5@|Rb|5{M3nPUG{ zgGLKhexZ4WhmwC&ni|hcx=ZC(q!xTcv>la#_e<4G4Y zNayfbhcnT5GJYqFd|VIhXX&Leyu{8^?xzv5oHrclY>Ybb%fGQ1q*axK4}Vjt;vWTNeE-vwB3AaW zH<7mZ>*r%&{U5xB^11DzG%`<8k;FxZjk!cVjTA880suzUKnL)njBZ?G!CY=!!Dtq%t%7w zTeL-{Zv;U`W><}%NN*r6G(9oQ{;bBeHmd045M?nfqe{9Knye%#hWPDJ}}HL5!p5qO|EbW*Z~H)b<54 zI=V>8PKa9Q3+=F>k++KK^%|S3poeR+>z7pG_=9zgn{LgZcDyLB839}n!H`+Y-(amp z7Aq{6EC!#-2ky2HGqqM*A=^G5)q{nemwq6%a--R*VI2eASP70 zVxdG}+YC#c-=B;CRlQEL1W=YiNd>Q(TYVDhtI4GEqkjw6AjBPbg zvy-ThprML4c+Qe63CDt15B|YjUvlx6Q?CRZy9in4kJ4z6l(5XxrDax5Gd!G;`3;3m zmz-`gJ;o6g-t+cVtcWUS=+v1lZChnD5Nu6PYTh+}{32Edee8yrt#8=0JsdjibSoDn zhb`JV*o`KzpSxt(XzrlsQV4u_o)m~|9LLV^d~}bNNhd8fc*K$k{e9lu(5HHb@3f** zN_z;up}+AMe67qcxum^koBg!5JTVfM+J9v zHPOy6gtB&`lC=ozPzKvVtJ?&AGTDW41nTgN!-|1en;e^?&$5_|;y z6b6KoDlj55^iC`k#Db@E28+uSwhB{+g;PYJBfH1%LoLv);+J;BE9s1J&VYH&&O4ch z^p~i3GC6mML}_Xal4tE*73dezz?2im9;mz9 zYO0&WE!`3RGJky9bMITEJ%S`bV`!9hJU_N0$<=1_cwtK{4>bi;$bws8r=6>aXA_*h zih2G4`PbtR;E>G2=5NJV@Q)Sa|0uJK4gT7i{eSxD|M4DF*7{pIBL9S@NLjU`h!m^z zL$!cvh{)%|L;%H%0FhRsZ3+MLsh7#F;6Z)=eKEBIwe^RsA9GpYpUGF7&?rS)f=-ZjWePw)a1eHgCAhFXhZX9fIb%{Hd@_(}GcmZ_ zn%j-iOb5$7(*WW*i^+!?gl@cs?^>`?Xu9Hb3*wBWhgQ@cbPC~yj2B}pl_E)})n|@< zY#W1DYwvI&51OAhiIza1m{_@wfmpaaJvd7lugkLF^TJ4&1p|V%wVV)j(JFgrnaD0^ zGMx*1kWETHHWSJlip)uCXe8^Pu8gWxp28{3+BQ*D+tqvJ=wcSbCK!tLCm{gL zL+QTZc)f!BA~alf4l8nj?l&djF3a?*`DzI72GhG#zC#K%UJN`tD+<2eAK}a~hB5VD6l!etQGutK6~)J<;)rE4VdH#x+r+kE z#CX3HB*mp-bJ53q#9p2O9Y4ioWx%`Kj717~Z{7Dck7UBS99e=ogE@AUQ(;j+%pli` zQi}WtsUN_TS%yFvmM3d?&aC9wAjYJx(!>~FUjl^!z z6ud;3M1~i2Oo=Gf&WW1|IeJ@cOw7d+kPRSNC2o!?ZKNk5?CmyrR?4?b8?dh!h2!?r7hq@s z1JrcC+FxI*xP+WpLI=MDpnq68#Gq_%9?6y`>E^L$- zlw+e)snpczcz*37$A%_d{%WgyZ3-4->4Yd-X6a-h_iyL5=T89Bz4H(V@W4C@6cu}9 z8QU)e02Fg1&%&?kIm`%bK&6R*fhLB2nnqzL2pT5<;!rr0C(jVSnCA`Z6J@SD$n#GZu0NyvWNjWYN#qsN*z}9#hzsG)WO4`mGV`4QxQPu*<>f=zkLV zn1_oYq`M&5H`=sG#K1G&w^>f$q?m+i$R(l;Y(-&+bkUNjdj%-1|x%VLYuLG3@dc=B`_n-w~zV&fuP?#%tx`_w+ z<_g;*gjjO-{vt-OHJ#wLZ`jaEQ?DM}zA3dm-FQOn07E?V+SuMj~No&~3r3-L0xk~7$({~>r9QZB1W zKhMHJTzuFdu2uqB324PRQG+M-87oNxrdN>h_Kj_PanQ`S9~*PVgBS$HlFr zO(Gfrn>VD{=Xx903PKcYOK=i|?(Y`!yI)^9aJZqnqc(l|ZWm#k1LMXZL<@K;g;Z+h zcjVxH(5p0{GZP6|vPJ;lXLVemFKJ`fECet=@VtjXa)Wp>fn8k+L1gV=xhy_{3EP~u z>BPl8k&C{xha}6~gDiY@B}6?%>G}OkU&FbElGhiuwOYpkbm9X0S_?3GL!7+k#3H7s zW}7Qsn#}IFKU3hd{kfTyRPKnFS%(p9vBKc&;)BqyiC8~CS{!zy3_vuCn^Q?_oE?6& zeW~7egkK~ZQ9CwEpP|t|HFpX@*~s<^x`9$p->DAk(dKx9&!OO6&jJ|%O^$^_YWpS^ zt9r!}b3=tOz)B}5?!P6wLcQ={M7e}J54o#+4kF0px(2_B=$PNYz<)?ZxrC-yDCUD# z_cxUvV6@o@#v7&w>`LE6&TEHM-IGePfp#S{Ml*AZyF)C|%(@y`#F+PK-e^TUxJ&|J zipYH=<$DKz_s~($*fpgIxpr2o3BL2-=J@q@7kHKS_}+yIj~rUV6=-d?ef|ZX!9a`@j>A ze&u})v||>g%d$351_C-lN+MWvgD6c1vuG=fzaTs?tdqPk!Y5F0C{EQw_AR?iHmaI; z*0IdQiEs~!$$2*u)3Xzare|4cq{ogYJRWiwn9A6VWXp~lK?p(1K?-b=sitKz5hz8p zi_tgR(?JwlQjF74DjwS{EjlXNg(MrMAeTX{qv9R4a!l`QskVD5XWWCTOdn9prXZR# z**6Q4!>eoSHN`91#braiUGNn@y;4TrG z+H? zLYov;;BAhcaK&wt;vmEjC5(n-?tl)L&u!16Vh%8v&FSR7n1(jwQz$&a2{*@F@y?0e zt1Yr*1=@MA**1*s5xS1&2LaCq2K&Q#t?RNjK>3~$Y#TCncok|s$I9|`J;3?DDFE1O z@BGMy_7c-P#JU(wcEEYx;NSKb!vVwlAsG7L_t$ycjG}eUk~-f(mj&W*p8}CC7m1iP zTs(`G){8x4#n>(Hu&UGUH6}yF3bnPzdiJY7%HA8OSW#~WbA<7lT z{MFaXyW&#y6aUzPee|dNas$r*k|A3)G2X6T=X?wNI3T& zYQkkT{wPu`nQ$=Ard7%uVh)!U8beM=+bA`JoQ|c4lx8r-{GHaq?_>IeuC&hXESoBk zlr|E7j?ajbnAT$QI8NZb!NHY#BT(X%aNahw^n02t?#|-%%9}Gn8c(R?lcZ#GKX$y( z?bJE_MN&GKzeH*5DR0ObjfVMLT6Utz82h7Sa*2+CCs;aHuq2Fx6c`L-F*~pvAKf0J zq%#PR4R-MzlDY>dsutiq6jTpT>sBn`mf^??d-B90g2~Xq^q!Vg=Qfn&Hiy53EaAEl zagp5H*s-MkobjEwglX)_!`M^Pkdqx{F=3s7CrVmRxa5?$q)_R4-^(oZ_HfJ@H=UHY zd*mvOmS8b8;hYL_QLJ=h@O3u!9L3lZC0#cu&DW`-<4LQrQfgPY#ExL`5^<5N zByz`YcBZsftb{G$oC|UBR(hEaHzQkaSEQsh;oJ*xk&)ZgzQlAtiLK2Y=;v=r$;~4- zK1Qz=+&8SqqPN9$cSe^cQ6b)8a_^`0L9W8>umj3P58}zebj`p@PEIV-1zTo~5Z6?*nbbRk}pT>583m3bgu*vt{gzY>xz(?Ms2X6fr==kNvr zLTu5ID`Sb|iztjOgcM2A_vaSDGb=)a76lWd|1RJlS-4);8CRbz*QO9sLlrOEPNBLS z^6V(X!ib_Mv_!N-DcHf!f7uI&FT4O^p+k3v-il?~>+|Nz#LeV};)!F?<@z8TBZ<JjasJP4^9UC=5eV zO*pkuj(Z&eYcipznP-e}hL4N1V5owL9ULwdP64(2q>x2-o+=YrJ}plc|Y zWMH1xNWizU@2nQt%p?wFCk7cb?au?X(nz)}CU@}m;wXTbi|qx}F2ZmWj|%oX>)3B4 z`XL?KrQXMz%;}VyC3fSYztJLD`m6!AWGlEyl$X;D^H~Wh>qfj7tre1KgHm{vC@ZDb zQexqy2h^KH1Zf|cJb5u#c$VtRZJLsCDUO7A^Gpe(2A)KPBdGf}JPGLy?L}xcVS?>h zfOtc7?dsk3V})5C&7i1LsMxKiVpPST`@&(hBdkUq93Xv#Q|s3xGr$q5@(cExFh%?L z>aNspmQW`{xXdLR_H+|fFNMDZzZ*Pk4SaANId|<;2WrZf^<`)$WXNNjL}d<)^at5sb0!~U}Zq! zT9X^8Gnsqp>u;d|R0(@p7JBho#Qz2(W+Jlw!lA4YSz$L|7EF0fq%7-DYDHNEvdN;B zf1>3sbdUiX@Mo;yjFKVwdOfOwul_2XGc=>fBJX5&@ANz));hzB zWEx?~8;=)0Lu^>4oO*2L5T_Z90k#?KHTSxWKjj%Y@%VInnvS7}V{bI5#S&+3JDjPX zckuKO!Jec&MB;b-0c1IFha>#f1i>AZ7``6Igkh+wb!nI@-S=QsbNm*tnSEB(cuV|X zBGQ`2Ii6XFW2H;1$T8`byh}P)_NqnX@d#6^GqIvEg2zwQ!X4<6Hht&TAH)h( zjiQ)9*;1S^wh-hi(IQ1TnO zv3rMNdRPOQWnNTMD8I4~;3PxRf-mucx}b_MUS~%hi=K;@x-*1OHuBsR-g#{k-SYW8 zW~nJb$={$YkWL?h+RT?aIV~}o?;yy#0B&#}@445X5PxVPUiO4*js9?u!#J1jo?KQn zdf`+NXI|@ZChG~tJ^+Fw!x=<8P%Vb~J4?Bbbof@G)w_ZN!^I@goV$Uv4j;<)dY6G{ zR}l8+G};zUKV3p@GB#80h)IktrNZTcTmi*h$O6+M_mwhuK2`2mTG^m+LmW4oP#eCr z=?7jJClva*Qu8EXY$y}I>k+DHg;t$})+M~BjxC@2MJTh3sXXH6%>-CB1E`ieP1{K< zI}s;cR>|eyn5w~nO^}XVFQCNv;oNJmzE#b}^E!q-_a}v!@VYN6p3A<#SWRk>R49u? zQhUuz!oikDt4-dYVo<>u7Pu*cev!rU$=Ve@VJXt@_1?PkX0Zpr0rr08lrX97$V?7V zPQFlTh2E*tfVFDEWM~F4REE4?ATp`ut$*kJfeXDOBK<0(FfnDvF_U_v1H|E&O+ih> z<#e1^pu=gO^<41sj;U_3C$jCYs}2-ILDSR$j#_Rmu;b`@gLBDgu$_`%4yPS0h-^2W z_1O`_9VMxB_aDwb2vEkS{JQaC%W8jc)x<~D2v9fX%5cES=r3*Y*W3Q}Zi$s#d98~7 zrHEfqjrkV3#vkFhh#CzV`}$Cy6#IH#9~JAu>$>QGhv#*+rf-y*3jq@ePKtsGCvC0H z?cRu;iP++ZRpcYP9ci@zL2|*WUS=Csd;x2&=q4zB9k^}D_Eg@fue?32Jq1{s67+}p zD@ISRZ6K%0 z55Qwt^95a`3SOTGD+Se8j!_GE?|1&tkGzFnSqmR^GhbB$?g9^b9XEQN$u!|sZ60PC z3oyML&JaCZKO+nDIYSIyEmpn1Aska z`ON>+YE=8b<$s9(l0tH(O7;duCjWKHCTUj|nE`pEm+5QW1oUqU6tJ0cj~enYS854L zK8b-!SdHzzp>tDbWU3`eR*d+br0Z_O><$c3#v6dn8}$$veHL09UcinysY!3sspm&_ z^Zny-5e=Z#A?&Wn=0jjMO}7e&%*&_TL~b3wek5IW1*U0wIjR4Eo(b-3rk(64S8UOJHza{B@;~ z{Dxd>fZd?4mPL#qlnF&TPtx5HB%<*5RTNHoBn}qG4D!q`ijI2cg5fSfO)9sLB^-ld zR8pq_XORqGC_aXAa-+xq<3#dpf~vRYU4<3P(GuA66;TINCXQu}fiAos9(z`}ETUv# z>}R4;EIoYp=4D1&gozn!WB8J2@}vzGlPn6;F&~KK4vR;r_%B?|de0oT;Cs)aWz!Wq3NLmg zi?peh7gOg^h>SA$mDWs`EP;pR;nHyz8DKRhQhTOd#cUyir0?62N`9=~QV z{O(U-^d)!gVYuNV)OdG6I2S8^_@V!;aQFHGv=5g>)V z6vsn%iss`lu9y!h>-LF9%Fb%`YV=)*(XT6Xw|!IR6Py!c6wbqZn1Yg+CM?jXqMN2+ zq92Z`STLf=FzNw_S~VeVSV4)R%jlcciq~SY@VEYi(kxcUx*n>6_$DgZV5o zQOer2g0Zrc<8Za$3oNbSgp(%rn}Y+eCEMAi?L0Urcg8CarGWt9CB?`VT2y5+V~&o2 zIhyy-V#AQ337I7R@yza0o{%8*CtiU-)EWvmCQ_-intc)Y}19Nu=pf>P%2YQklrbW1z>D;kocvAKWyGU(o(S%XQdRI(#PNrl0( z2LS?jUZ-E2y(yH=b%+oHp|MVR&~6}6c28Hq*Jz`Y*Y3;#b=q}Ny28L%?&jCvF{+<7 zFq-QXJ*R7n_~<#oQb6AYHxNlR${1ui! zBLOyBhSB|vzT(I&;*d}(!OW@BvAr@>^%mz155ROXDbsX zfdK@DT5-o_Oq*;qq9T@d4OiPDJgaLmILOv>Q^4(Z{vx->K#7&mk>D0Z$Qlq5xlC#C zszv6~vn#%&<94dV25dI!ydf`;D%U0m@tvp?YBH8Aq!9~Zlq*YcQbdb-#+Gi6 zp=Y5i7sWwIXPK5oD+eMJDJY$J3;m!}71z0K8iWB62Zg3>iD@=sva?c_BIGjXT7*_V zrk#_O%LWn6LS{W#=_FEV*eg20WtAx_5n3+GkRifwCkuqG{5yHD3Rr=*h<}?&9o~{e zYT+lfWrDM8hh8bRK&=U_jx9k zvu8Z-Y{(~)v3=b;J*nQFbSysTNQ^Kg5DqF_X|N&E!2*J22Mw;&Gku1VuTXtWmPT-^ zcvXlZ!l(;nVqq9YOv?@*_b|FR0;i9N=T2dC1>b>ztS7d6XN-I5Ja$}v=}iwq7hXoD zEwznlROKR)^Mnl9dF9=~4M>;qir5L@pUbJ>&IeH@fhAvOtUK+D%_PYy_>7hExKp^8 zoM5ZhA&5PfqWSA0UwE*?dwn~x zDsaFPIy($aWEyB~BZ;>$fbZhWnMJk+pf%{867veJckfD!6b&WZMn>jkHBf>=V9{;l zn+@cvgYp|K#Unej`TIC(XYYVglA`O|g{Z7AFUfbxIblP=_G$lpi@ zJEK8)(Z0#HDApE@?kdVEzAzyy))F}(5}vF+=zQ1g&^;1%e(?A0IQDP%XvWjFmd^do z0rb4_R(G3cP$KRsGIvUL2k8;Yad9ZErj`4V-rwLNM%}mLh$(wDQKdwZxb>fzA@r8h zb=p9j{8=&NM7RPah;U{&ohD?^G(m}n)Y%f_C-_$w^~`J)km|T~?*=hc)=nV28i5*N z;6cq9D!>q`FAK))iFKALWA!b(2vPeP%+Uw74(uJ{%}iydFoPs^vhNB6nMq}+R-0_L zR_f}j%T>$@-@8?2NBjX?7>mTV#=_l*jW2Z$UiUx3H0PbT@%L?Eoft1(}NyThlNuUwCSJ(xMugi2%}s;h`3RM${05S3zo>O zA;e(PrI#gnIjdr`se*VcZ&?2#>UMR#a@U|atQa(MC+7W#XsMyEmQm6DFC@EG&(reO znw|FxoxGip3!S1Jq9HGC_LBm;EW#C0LtaWNT|t$!m=Xr{^a(HJhWvIy$9J0_G%)io z4B6ol2xG%+ZfqQ?QFgJarO4aSid*0#{GHBY9ekU@-9sKZBqnNaN2vyQBD^p{(WG!M zwNM+J9CFb5k@XS4-?g&OuZvGcM=zT*3L(|Aap%y{U$Y9s4vw($*CM=Ez{NCBPs$lW zPAF#(2UnYS+OgXJ$*VwbROQIUcf?Emi}hAy)p=>}!{PJDU-?5S;wwNAp>Di^PqxX)pPr-*f_{y0-GHk3({ZtXDVdfkuoz?gG~G}dZ`p% zjgs5kAxa(vZ@P*&giO|b54FN>yLxiPgipw|GP zbg=!q1_!r!u1J4?#QDqesVIQ_u1hc@1!#EGJ5qOuKoteMw4YY zC!B%A#9@Pzrz5A6y66B^x@|8tUao22=)br_7FOo~ZS+_cGEHWeGED)MNy_xA^0#)@ zbac;U!+S=^G|#H=W5i{l$Hc(Nj#a_Brw{Y2=Gs-EMWjrSVbW{}r>N|wMZ;z&Y5bSL z6ZKA#-k z)`i}LxYpdl!QW;>tX+QydY*@wNbz%oN`Y4tJ`%%!i=tG(Y6|MH3b}C4kbaP=>hZUE zL+N>A?SJ6R^^p4ug;z3B4Q z)=rQ*5(URJqUPxvm?mO!%J|Vbs|o2bC3Q=W((MDiG2GPV&ar=RWr))ewSOZ!JV|nj zp=!qaX8#Bh@WjdB-buB^qQQh*UYC}l!U5Yf0=%TYEUrUa_8OZN=oZLx#UJPFm*sCz zJW-3X2JFU72|mrP`Juet%-Xm9U?bPg8-J)>eFN!!aBfT5;g|QOD}0ex9^@7}NoIvD zU$^V0O-ct$N=?Qv-@mybW3C9l$Z}0&QHv#f^hPi14Ji16 zezfL)y5)URwXN??>enhZVV;LrPYv8kP9+~#XPb1cI$k{$bsNJA?FuZU}7q<)- z*CZ)VsX4T?-qyur<9fUK|GX5iCAoT_zYwPR4gkjlv1Gpr5QnD{$RlinugaDtJd*>3 z|4s~&$A)_-g7D?F`FemlP2garf*$1$>6%&nnT76u)>%sn7vHU4^Hq?B_r5! z%pl=VcF`$L;95J@V?gRnNZq3zd4wqt?A7DFaIE>1zZ@O;nxhYN(EKU~7(=Y~x3%== zbcrAQi&NqKn^?5_rlRbh-~^ZqS|Q4iHNm?1FR2zx_*MS0&GPu_V#F0oz%K4;cUD;2 z+0E?lRS@bN*!w92xl;EI?$@{iwO6m<#<69uI_20%Pq>t|V|(gQSdJ%yG@`I$bk1XY z(okBCtX(g9q*OOa_L4m>nSP~`J)V+1H;Mjv%#brZddEDB^~la6lW-Q8-%nQP9Qh5+ zZECutsq%BmzCfGMpxY3jHUWVxf&!cR@7HxU@g?=H?7m_t1CH;rN)<|#3fa0h?w>q| z*Pn@Dp`@kS+IsCN$qnF-f6EWjAZ&;FBiXuLuKsR5m)*InA&f3VgF67;yMpCJ=-3Kr z}Gh1V4vyj*hEZ_OQwsuh^XjFDZv%oP)zbvuU9t6&(}ZWt9R4 zQ^O-9H5NP>##+nr8nJyHvAh~OBJtQo^B44`XZH6siLy%wkZ}t8H#jAUFnh3BK9n(S zB-*)6EV{YhF{kI-2d^LJy$-uSa(v&r9v}X0*>ThlUj95(5#x|$10d=GL4>|!z$cBS z7m2n%$of1oNb5}D>t+bGv&B@o5nq1q8SXbq?vw#$hNw9S(pX?;4FJv_=y{Sz^2I>R z$C&;-;QYH`y1wE{YjI<_t_dS%gS17t2kYPx#(^9_pZ3hh z@k1Paj8NwFKI} z0ekK^E~{8=2s=fJt`QS!CPgEy!OG{GXO<2bQf#u>h7!Yl%(R*U{ z)4VSP*=ynXw#C1TxatQyk+rOgCQ$5mO**S6z zjAq?)-Kr0=xHlb&^Wf>~)z-Vn2P+v(TDxv1qUb$Wl+pbSm={G)dl@Dw-Cw0Zv$^dHae-Iv0&3-6Ksk8U{dqqlhx%iIQrN z%0W*OnFu|EBdd|@SVy^K5V{tOH8AIB(i~ekndh_SrkJ-bnA#9vXzF~FHiw8?eEqR5 z6ZZdkCf(`6slm8_=%GG@5L?OEGn!T|qv>^BeZ%+B#d$BVY+NaJ ziaYCXom9si7U?10sPWYtPB4BoSUvA&ZzoZh7DAR&#aZK2GP-Qwtiw3-koj z_+JDfZAvdc`d8y4e5B!~Da7g8#Bn^}nk6=oC$y>|7lG*UHlv=}yQ2 ze)z1IICJH?x_Y_mfb3>`_3*rqXUtS4g7NX~^HGgAK;L9^qI2pjLpN7b*PPl<;67|% z!K^ev3nORRiALHsV;WVRs(aDvq4Y`_Vi1H8j9v9(ZOdjJ)5JG;nt`(MDPC5CdpW(W zc%h3~tb1?6S%Qw>ak?=0zk^Y~PXy6xCKaz3%}$3@_y9NF2>E++aD@E66=4 zk8h4D@3J|C5}jCn6xm*Q3{9fsXt63KjcL$vXc+lfo&jCdL1)wEDed$`KWNm;t(+>Q z-fmDLV$&I%IClay+9FSF*AHBw4+N^N>;tbkTXhNe&NM(070+`bk^~xE0k}IJD6le} zEU&U@@36ptW$>w^7;LxIeE(*dGYwcm-Gwa_$+JKa+-t7B;Z>pd4JkTbr%j}0)DUxH z!%Q^C$mnb9d9pBfbTk*u@9BEgUdTp1calJDN5A8xD}?4MGUuNb3hT^D0WLpJ>wRgxol0~C=c>DmAS4Ju*q%I)6&rEw z<%!h*+qNHpgTOlAdRB1GVpE6y%y}_TSC`%96qb#;Y_AmQnZ>Xza#j;?de{=`?;{+J@enwM^#5CYS+^2ycMbd`dm%qHX^ zvycLsD>{*~n!>dD&HUgKYYqp|?yQ$Jo6@_aM;@u>AlK&yl zcc#a8;5m9>i_(%#+^L4B7=nkB%mw{b(*1LUQtJk0PvH2k8H33Cre(|kC6@unrc;_)sHaW6BdQA;uNr#z7n?33gI>zHlR?R1o~c-~ zrer(qP}<=%BN&QGlWCE`aje2v9ptv|kfRxOtTG)n9EsINH*^nNrO`R3MNi!z3b{w1 znyMTYl7aZcvnbtca$$MlXanlbmPcjOmDFIY^2CIYNl}Rl-g28qse zo`4z$7dYwG{q*)W{yZYvjzu8b;vTUvWYd&ghW~BLrsf{y#uvUOZ1I|!Jr8(fqr|!G zTD9XNtQx+wp%AVglDqc z;qLZP!M~cyjZwv0dH^cY)bd$ttsSyN%;q{U2jF}0n;jrs$ujc|+bwUX+~?x94(AvQ}mCQdv+nNUB0#3jdr}-CWXFF?w4+_8e8#lf-Cy!-ezQKMHVZ@1X zBo!~YY1vy@)oX~^8j@tKQV`lwBbGQdC9qqqYhr|KB})e+`U)E z;x7#TGYBUle!sJDcf#{ZU;c2XS=dZVAzS)j16!Ky9-(&Ph#%viCsz!!A}hb36zeDN zta#dShp|VQSfp_jX+G^!Q9l;?Z+E0qAGQ~I_&$S^Cxp;L@}z6@ASQoZNWWiX$yCO3 z%b|J|xPN93Zv|PXdtcsRz!w>Y?Liw;7jZ{z7=|4{QXS{)=a4pN9Z&27@8Tvc~ zbx+6j1o2jd+fKlw_!puUuSu?onqyXK>q&{b&9`+u9WTp)m%{D#bPy9Ex2i?2XnSH` zy+LIg{%$6DnnlO?B6sV~|jzBaCyi57~XJB+u`I3X%y%j4P^4wkV8> zOF%5>W@$LweQHGAGVPLs)O~=d`>j%OtpqamsRg~Nvy%2KQU$X+{Q5bp$>I5P|JnO}yUq9W{mko+;5}H!Wj8|_S)#GzF`ydN zSdov~{yR3v-jKaxh<1JH z)S5<5cFGb|i|CPLvT0g)huK<%sZAsnX}ukBcIvkc?#$p~p}$>EG-Z|iQgUzBHt=IzOfbAxj|)byc`(<95y>H^jxdH^(Q zqH7NwL@9*mDw7wb0;gawG?+YHY*_`mb~7LOPnJ=94(PObtUc<3d0CHvlY=dh z#bhyE8hP(I=AI5hCe~5Nq=s>{3{R6R)6z%Lj>XE1G?{2c2p?HpzfVcDm|#F11W8kQ zIG9gHbwg##jqSM-G9|faX+Q^s)o(uVtoC}#!vojCz5$8p1wq@n&qf=Eezu7|ztDA7 zh_U|)N1y?tQk_};vZ9{whk~E@1PZ4St^i6zhc4WVvL|O zn;8wNP_S(j1VW9SpNr#kpbtQF;g6&lg{?aA6V+nEEzX=0FmMq*g$_f)ZA7zzCkB1H z#^yHD7%Y6qUI@G`%y9&+| zS<(R-3W{yC1pC5+$rJ~TxDQ5|QIhlaL<0=UC>%R+$)nZm1=!ogWedv5pmb3KYZVLY zrlX~DkFUxWpIEwRjnA1ctf0fnTr~u8JhQGS);osJoAYD~)7W=@<@p=JD-3}=D6hCG zrI1*4=ooT-kgpn5#*u|z&V@W`0Lf8tA;*KGIcy0_TotilbYQyoo@LpX5D0e8Q*gx{ zl_(x2Odu-Sx6-^j1QGlkfhBcYrn4<$WOoMllKkQmGjF45RE^|0H~rWhGK8_>F{-vw zzva9=-~l}RgdSD94VY;`YQPIyFx6cyRgprulRW>ziJFao2K_hay33H`1 z|M@7=bllx!)SQ>Ujud(9&9KI{a#UsY!OFBO(Y#~e3HrP9=B((x zMHz|ex-E$2S5mUPFMpxnA-}id-PGK-g4|^p+ZBvgkJzgG8W;D)Cf18ez0X5S>i6B< zZTBPbMZ$?(f+pOzt3DaV%N|8!EqL{v5!bZa!UR~y)J;LUTH5I18jTTNA8ukH6jC?b zTrFtsoF!UJta7h3zxQS7eajaqwRK4=yoGDxFuV`kp_1lyWJ=i4SZSV?K<#5u@7x`n z=Y4vUa~NR((uj;(G$As+^N^5`3OAA`t+PI0*om6|O2DauJBE4ZUHep)0X(3CgxmGE zbYVC*hF;(OZl1EkgBFNjX$3n{?(L_7>6Q>5sSR-UJ!Q$sErHiQtz&*Fnti$5U$a1Z zB9x0gPJ80ZJQ9*AX~E;*H0OLof*X5E@!v6s%nTJb_|u_het|XCpCt0SO}6JxZE4;D z9CJRS)B9YGl=HQx_RW!2=U!($^y(o3l`A^RCyV)+Pamiv6km2^wL!K`2tduinRl!K zBg8nehd2}}RG)@hz)}aUkX(`-;A0!;7P!Rc5Eyj*z9Iy0vVD4rea`RB5PS!SJ97HI zcU-VPO$FY*5BK{G4>q*u?SQg;gQ3{gS`}@WPcW;~Byte*IDR2mbDlq{8abclvibHP>|xuB6hY zK|dK`-u%3hS20kY@>fzW{gpL4-d(kT+I#W#iop|W@Ks1jYpPf4lFV>dmhClCufz<% z2(c$!hKFlfPOt2#geGew*nf^@u!8BE5=oC4k0mGv&dK>Qzjhr&&a7-FqO?3dJMuqM z=4@S<%28vJrUs(id9;KTT0u*+|JnnbPn1LRzRntaWem|R!_$iSJ^xAk{4ur1yHFpK zIWGdg$_p~>z!8MQ$m>|O>|GY96uP34VCPS|^ac7Cc1oM_%t%0?NvxSk9vVqyZhp$^5LO=O9-FN5jZfZ{y9uP(;s4BrawdJeEf4OnsFPJo!2j#)jF*=sv89~LmB={6+FV*HQiRNqc3yY z*X`Tew%4i(FYi5nW{pWPB)MB~-*<04dr!M}?Q%Sx6X<~2#CNF5B8ZWmyT^^~wyIiV zc7hEsx=ky5Gf_6pN)|-B<{1xyT&WXp7(IF5``h2)oQtE!cG8wBTTviQPmk-ICOURlm!Nva8~VX*R<(Oe0g5++bcZvx6))rK#6!hrp%bBtc+mbECj@ z1?o*ne*Hg;yl8 zcCEa!)gdpno+sZ6S=8FLC@BS?24HTUb*yzbJ+j+fJmxFP=fZhlWu&v&oc%>oW_4w0 zzBsqEIX&OnSXo`&Y%(}2wmD;mC#UZ*oa(dEEK-|nwxQG<#VN+%v5DjJohH3 zvMcQE%!_`qoayIV5t!JVo>(--FXE5u3N0}`08FtXIm|jDj}~SPc9rO zJL=I`7K22teMXG~Yz_?c46_WdZH98c9dIxKCe7CZ9yJy%FzT9GRSLcLgHmLDw?O8hqE7JMsYo@y zAr2|xpUTsRb^nx3Sg3Q_4d%1lClZX?L@%g1o3iJygUVatMFdCX{TuZgv}l%5i_U6% z6m=?YOE`TPDZKEIWARa)tC`!jnM6s)bj>xu?bO}MbA~{@(>*__CY|FDqGK+UNPBk} zD6=M0zX|`&Av+%nbI^#ct5^JKwu;vr35CuT!1h)PN9&sz7|ZoX`Um}1d_wAd92*{E z{(TST>L0d?EhAC$eH~wCeI?AbUWOFt&XqBqC8L4)V)O|vrSqGg%)E1E--rhpvi)KM zhxVM41Wvo&J%=)an1PP4AVxygE&?Jy1Qi(aiXfyc6gnIz{p zNLLzLuYx2J@Wk=d(sqX`_~kzstO}3y;E4WyEzI^-5VWGK8GmmGBXj{CDSvppioN@v zRpzKStmgOz-*9NGwmFp0IFuZit(hxo8~#MQ{Y;ARW@*Jf4k+fyt0UnjSU=2!6iSX@mX&-_I4qQ1kHKwKEQ|zY>@W#db)*Z@_`HHN7t@s!XRg=K=m?F1HGhcyC2*p=`V| z6g9tSiXVf@`)O5^v|$%aaji(dHF=Q6zqJ-G?dZm!bdH}QS6{#8b;E~$IiNRtLUd!C zF6%aHB~rEG{ll+n;$5)V*=xc7RF4>+i}ZtDDGVoe|5k1Y{;tvNIJ2nU!+v?S&BUZ3 z=Ea``xE0?oRD^FMQm)jvQai;qpGwsh{e;Pshf9#{l&m~dvci4h zaiLjV)2?awk}t!ZfAls?j?+>M}Dam|~+@R1r1kLQou zFTCPBXFW#(JlG31q0#KvNdhR!?)&QL@>3CX4+Jp^rwKNtkgP(73dif__Pd331U#yK z1Mh4i8SN+iRJ=K}LT>fd4~~#*rl=hbRMQhWf|j$fl|7K|+(+KAlwS>qC(vmQN~1+} zvXU60$R-|9FRX1|@Es2;KwC-h{BZ-698w1yR+X7yQtV~O(MJ-TvNY;BIm1@gEsLzW zk02S*iE;YC&W~$Z^mCM+Uq24pdE{7gjD5buyk!=j{0y^l@kvJ(6`ZfTSn zMZ2Dh@$m<@OAj7w9yMe_Oh_-}}@AXe(E$C-@DV>Etm|)=uxJ8`?Gpe*vCWN?zc=W_&wjSWGlRXDS?ie z@?Q=Uvc7jg0TcIrqiMa>?HXX3YYDkA6H_4-Q#_xt8UF={wbDzFZA zb^?&5BGd_0@!b)g+TYC3*FwD<=G!dYUC+bPB;KwNYzESg>H^sYaxNi<35g!)wW)00 zMJZtmqw(Km>C8Fn{iopeZQS+&BDEhfmu6yt^2K8;%?*l_shoT?3mrlq?S%6_OhO_d z><0H8I#nbmK#;-r)15EVsc@uKAzrCBj8=IM{i9NT`S26R;e&G~ASZnM^hJxZp9AMjmjV+nOY?&?38XojjqMy z+IAb4;X5&?DWFpzNXx5e6x*Jkgbx~p?02A_i8FLMQbw9D71QniR->DbY*tG?PTrd8 zR`=sKc^ZT2BECqXPJL~NA-5sEOj`8hJWLwW29p6UOSBLH>&nj#f+hymwI~K|>&Z7y zqO;4$otFR?*5A`ODqaORZtV^8h;g3Uv@wX1I>(A%k%morlI zF2DVyXP`pk=^NJ2*AhE>JgUlyw%JV6n*vEWgs*OJFn&&*Q=(Ehd5}W8^R zY=)B}_hXHjfng@mrSc-(P3U?C2?eP9?Npfp(i3IIjewn{3Edl~_$v=?E*8o)LNcG& zILD@P1?FO5R=Lb{?*Am8+h%7g< zv^cazt`hBKE+7{!DlvOOiCdpb+eNo}dkD=iQ+5<59iftUnI_Gl=#Cdb)02Ih9N?0VrnAu#&9zF$ z-iD=F&Dsg9)=t#9-t>@}zP8`m{v)P;Ucn71yHDtP5x4)+;GQ>EISbTU7HkD)K-*}d zR~M`OH$pKH0K~s5p~2UUHiWl24oBwlpj*eWftfG(H+VMM^0OoKw7Pivb@p!b*e~3L zg|)Fi409K|>4Y#*`d>P!Iu(C=6*h?7*BQ1vAeLO4*@yk5@m*<&YS=DWn2qHr1FeWjk(u z5sg8flcxYj%?Nt#JV=YNGemC;FtguXYRv>_c?@5`L~y&m%2s7jq2%-_Tbi9xQ{3&; zf)n5A<@@4rZw_%ko4S73&qUuxw3F}Floh>Cqj3>t;9{a;zA5jV*%~YFU~@5{ppqxy zVp^&-ohhJd5?_($z|R!pFF-&O9@1sH#SK=qPWtzEM0osg)M~xeB7O!$tjvvF;r8?6 z^ZMg69lw|6QD|piW5Evg?v44Cy_W^@XmbakV@U>(4&OlcI=sg(-GlH}`_Rjyz`k?5 z!*1Eva{T25Y?tmv)DroXNNjNjd~rFyTJ`4Z-#EOtNSYBp;}0H5;HcsJWrEdp5n%4p zQ%JWE5wfUlycL0!{rRN_>J6l;HzU?b&*?6aQ;r?z+nz_Go(#H)f8WufT}grN>A~?A zIyvSSd@|Lm+i6(0aj@tY%j_G@eW>O4KpUMU9w5)B`jh*-=V zTf=a6#_BQdIeRVc9Iz85XCO-eK9#<@U-9FW+ zX_I)K5cplWLP-aFk8&T~ZwgA0rEInF;(}9YQuN-wHlynJp`vJc5P1$dKk!KZsZPXk zp*ASb=9-IwB2{mAvGN@C&J^~I%wc}{R|-@%?cCIw90>r0v~#;x_ZPVff|}XC7jjl$ zLSke!SK#-$q;%hSQz3G;^@(UH|LvZ0`Q!2k5EFOB3v(8OyzaAbPo8FbWoGh*qS^o= zc8v-4Y5?{E%yXyV_=$7j8YB*2(t%+N!#Bi$N4|jY?Oom8N8IBTxGAkH@>su4 zweCY6cqgs9FgqT~Nwarf(Mr_tiIkjE+&k_3$zU%@3&=E0QRcBlu3ZWQ_pw!N&H9ovXm^Tl zr$SmFk4iT}GOt-IOQY;c?p}BJWqOViS00kzURg`2>R++xU6#L5OKBjU_*dKjQgOkK z(%wOt%%wsfjs7A5uq(v#^{-lnfnDpj>4r?<^imr#AAH zA<`5gl15Xr?Cj&XRIcNuE-nU?O`1}pyIf)l~RbIhvWNB`}j8i<5ILlyb#Z zm96>|KV2krX|H3Kpk=DKefKFe9)3dwfE4BSA{qr|l7|Aw zmaa#MM5YGGzaz2Q?noKhb=;>^Jt`iS>U3)g=&@2j^%JD@@Zy0|)i5JeDcZ=fO>}K^ z1hnbi;&dd#+R>RYF7>*aE`w6W&I$HjHICX+-@m3S7-<_@t4iCnG_I_HGME;XG*zsBx&&CCrEl-OJK%=Z-?g;!@XooSM?3sQk)* zuPDd}%PER6tQrc{-;PJi784lECRpVskjF}6o@oHEwB7`jl?nN-2*EX>ZZG30cM+RO ziZSYr)nhJR>z+3pEoKs*ek3mlsgMDzUZ^57!gv$LMbj3syVf)a&-UC(4(NG~66=hrm!;>5(y ze5+?v5hNzy80yG!c(9at^ zt!y&Oy2(A=b6m_3-EBYVR#Czbd*>{4Tx~f``Av11b0N zY4KE84TxxS3HV$b#pVp85-BaWJe|twn*+~k zYyBvkCw5*`X>w68ih`|q3`y)Nh%YvM+(=v{8IOX{>9|mFE!3= z?}BAiK!Kr#A6Nv$;J`TILKJN!qDsc_RkP>${Rgjcaoa|eU=ol8q;n*|>UboE1z;3; zjv-$M-7A^V4#*fp0J_5rnMJKglZ>0v4C&HcUIhSe7}Z~N;s}PG2KDX9cBdEtc@buG zKAb}Z+SImEhIpm>)+iL7?VdRpygp$^WUUfVZ~m5wq&fDq2MyTJS*nx83gE~e{GO76bf$tdcCQh} zQgyWJBJ7vjysv{Wx22jv<)Z5?qO%>bOdsTJ+2AI<0m8MdR2YcnA?P7&*}~Q}yNROI zwzu>>?$tzB_2=H&#%2O0EG~tsDVsS2Dscp&_Exl1uI>*-6Smw1yeYk23gc(&&aRQd?l$Cy7 z;Se3~PFp~=r?o^FbvhVItU~7?+8<5Bbo%aj&`8kSUJJ@z2hNx%7mU2v1^z_aszTXL zO9w&3y{^?LVpY{Lj8W@4Oc7H>l7dG{8IE!9-Bhr|uKRXvq5s zZ=*sWCuW>W)kb$oOYqIsvNbm?I%{@MDhU90?&Rpmzy2py#UH*DAS|RtQYO*?4@*zF zA$tT8csY60H;#gJt9!zjE*`-frN%THO`J(r$<#5Xro$1mtd7!UAUA^xHxqb=#Fb2C zm5qG|wxxV-SC{*sy?kw?n&)yUA8n?z^22!cL##6^+{iujx3BD(J38{hfEU-=P5#>9 z6XnEO)(q0LrrgZF-Fm-2E<7%`!mntIkn?xpU(+g)A=0?pW5UrIswn+t>PglA%kC5A$ohyEp4Rl3fUH* zJ$H?t_KYI70N=t9DAOyFJ<0W1+AKYsGch#yWBJC+>6X6G%Ap0z*^d?V8Gp`UEdNc< z>DF$8sfPdDLKRbTWyjl}e(@W=ifbi3+=9_Pv`%L|>>yc(rh3LIOh^fS{T=*0bi#7iyf|!Lob|7pzezv%;GRYYY!WMVOOi90<%me_*Sf%=m^0T|;Qk?wYC*#C zG}39$5xrmrIP=_amRpZogk5Uh>Q|TCHZM=RWaQVI5btbF-R6;fQO(SV$hb?7zf@t= z&BDNDmIIdkX-Xc9l@(x$NZwx?890$kzJYeHRT77p;)YRfwf-J$VE0FFF_CM#~ISec-|yGZuJo75Gl%k1I^8d0bw#&!6dmv-qd6fQ-PcsQ8C3yCdpk(PD`U`^yNNW zRPWDp5@za7{0Wm%QJZ{0hsq*Z8tpN)<5q1}NFkORG^(p8MC;vZkkvz5KckZ^B@eK~ zbCmivdGWeuj9v;9v6&<(@rKkfWW}odulmw^&?gg2o9mADZPuLyf)oK}Bi|v@T#Y5Z-9KD^WQmfBqeOJ1F~+xu9|B)uURmgjbLv z%Vp32<#NKjv>K*_ZZc{WPzJm4c5TCQ&$2Pu^9w^N+L=Qp2kN9Hh!D~53MXrRMRu08 zxe39_9-NxB<$WX7)snGIpUwPhI>FgOmu=G8W~OAzvRH2=O0?-F1;$zI1B+{Ax)XZp z1TxN?A*B`~OgWi}y9@mak2)kJie^0GebAb>!GmhWYo&h1Frkg|WJBKM1+)t58?fNC zw%cE0NT1;E+>{Lc)TH4Rsl(Xf`&*fu()yug$dKk(u7Ix6JoT~iH>=9i=>}|3yG{oa zTMc>h6R0K!a&%o;zmU|VIPF4(aiiR`w_QA|X|B_+tHh$4yHVChH(5*DvBGl2y2_>! zd|uJHaNGAdvQfw}V`GX>_)>qd3fi`L%;f-K15}jY?w~3YL1~`Q4RWZ+Fr1_b$WDpp zWz(9{EBU&~DoJbWEdGr_b{Ov5n4RJYT2i(SRQ_EQ{F*>yL`YIL>r_Xr(DpR&mf;P{ zLztnyWW24a0HQM5l{9AdYn9l~>gvX4(d+%Un4s-zFgJ#=NXr|KFW=ra{MB4r znK>u_Ut?3|s|o9SjqT8jVCX1S-E3EbNS*?+vB~gb?G=K~p+AHdr#`W_J6#d_!4kD{ zHlv^7eGJzHzOH{rb;@&`mOL{)Y?EccLVav=M+>@-z_B*FO)hiQe5T8tu-*Pxzm`3W z4yR+z?+cwsob%JaAfJ@^dDOsmU@s^12TVvUM44I8wps$)^oq(AsdfET@l^#b*uM*s z|DzR#`x`lMvBVyreG1)jgA*n=#GGSw$_XMAeaw()!ZS_QG9rajV!*L2rf+=jgR(7V z1T#$h%!U@MeR8yJTq=qsPS5o_A04%sP4 z{q4x`3-1WQEQ5(DfdY&{#L`rJ;Q60(r%p7SAE89*SGBH%N`-4*1qtX@*UBcmC3-=4A4ot(6 zin>vxXeLE2iK`-Zy%L*G0@SAiii5_H+zZw;YQCuCrd>F@H6bj^+OQ9atG`i`C$K$E zBaAI)6)M+$ip?TcTGF5PnQ+IBiYcKll`TJT#}10+Lfh!9RGL6RYK=dd)MQ z_23oW#zV{!nO&z$HyK0X9YwmxtsSPQ;iZhlb=MyB&8!_B`SN)uVEsbwy1OF!g1<4g z{N(-qNcI(Tt!(*e=6URw{yx3ew(w4WKlR1)vVN04YL^3!jhu;CpxOWPysCL}kIKX z#~0y?gr|Dd@*Y{%bqv1*^c5eZlT+CTnD3`Ej=Zcjnwnzh!c)<=6`RCX!|0 ziqoN$W80P+r2ESX3$U~Nb~B1smItQ6eeLTIQYN@0_})Q~Kjf!rNzz^H^bD>9Dn*@x zrBFdUlOzg$qX$%!B^3R&j>CP2yKp06`B!>|gM3>sI_YSNWD>Xd=;h>PDh$@`sdl$v z@BMxtZTd++0n;b$!W2qr9kvDY7C7R~6yq-H{p0x!PdROurM9)EpDX(!>)Ett1W)=e znpXeMx?^$Cl2&ES2}GS>?UMV8M4Ty?vEOuqx3LHql2*%AFB9A9-Tpk=}%a_HPJN0FJ(FCaKT;#LrBb z&tyyHhG`JNh#*atMD~fcAbl07BinRf!-8CX?612frddMP*h+V_>?~q(Ke55`CWAHC;N<_ZGS-wL)FPv@% z=#9^6jeACEp?c$k&(W0wKJf8lZiX0gsQry6xA^m3`n~oFqL%M2Vju!qw$4Fqezh;uLCU*y7i~M1cseiKIv4&i@ zASvlmNmg!#g&;vSSR8nnA~9G8l4LeFVqB2sB*5M|RC)WMLOt@~B#!kO2KzAxuwfD@ z)`Y+wwc2+|46dyeCjJG9Se(S*&PPRgnW!OV{Oa3QOA?~4e>;K$tPnkR;O&_TEChSb z0_??b5y%mCLDC$#l7>OSC3;QMGOA74;_M#ERUYCZxG>f&3AtXmm){?C{=qcO=A}Nn ztAnA_DR%@*jm1`$>>+f_%x7TTG@6av6E&Et0U1f*0byj%_lU_wA4}zJeIAdg)yZts zj*1!z<4NXlB9`@bN0%1Z&Gh3XK5{ocln66S182Gje=3DAO>AmxW$OWpS%Fj35&do~$_g5zqF)6dDX&f)MdArXBl>71XXTOMOx^O~3? z*bL$U6TAk%t5cuN@EHxEH(}xC>qE%LCW5v`KbIG18@`!gIc$n4+Qq(6Q9laOWmq=^ z4=uv##gF+9<;F*yYIR(G;dncyM$0#0(a>=6th-k8F7sKV4<#L}k(Zf9(-xir!W!2H zsHe={YKwd;vN$77!lQCyr-IY;|2)wl)kD@FQ%Gi9)bTI;nZt zLga|0qE%SL&5;N4xXHY59^Ty_xf>QjA4@C&J(?3fpV_3`VwZKIi8pSCTAPmeE~~QR z#=4f{2S4ZQZ{5dTg=x+bvl27vTH^Ojw{ZWlKh(o+z zQU_j^8&e*Gku<{iPA)_jrq6Jqfg$aM23AoT+&g7P#^`HYSh8OE_*IGuO7>}6od!`* zNwE_AOHoJSvO-;`x+v(ZY#i;;S!*{gV<#a4G1+E?q-CFLYLmom7p)C_{gCfbeZ5W% z?+^7Y0bo?87~`oP~9X`Co@jlTN4b zpVo9g^#mgT#8O73xMJb6^(!`d1Q~a-C9VOOQ?W!(q!MK@l6fA9hH78nw@S10Vs6+b z`08}HsVmX6uv5Ud*5Jj?MnXbMKRxn*=QDNGm;IqT6t-iD>G5I#aEWe4UV#_Con7^d zp~SF->VWS$iXtyvYk?P?Wpz2YYSn$Bm|}uu8ZDK=*t2C`K**{To4b%Xxk4vTND>=} z7Hd^x64X=@gqGbW&GnVn?6J1Tos9{(`z6d4XZZVH{Qf24O0Z@8d z*7J;IU2F{hxAHAoRZ9)o3}rLn2O*($)Ng{OeAVDwLxDQ9Ca5?lSY4=K!wVA#Aq;7z zR2USkb<2vXrY1|D`Wl}d5igAjpo;3naVtM%wWqYSAO?R>_?PPAbEn78sVAAwkESj$X&6i{GB^QW0>*~;_bu3l{& z`bEp)lckt0JY`v7Het&?Z7bxsZfeT0`cdJnstHAS9K>EAYxp zQu0>lPm?_}j0EY!Ls)_a1Y9GbCrESKq(z+`DbuYQ(Sr7^Ms8MRL@4e*-Wj9>L_k{c zz7d>j{u|PzPP4e&O`IAdZ$XCLWOq@YY8iC1frrL8gm; z+#{2?bVe7=iIXThT779{GddMr3+;z0SB)|h<=Pbr1>zqktr%4n+|=9!58wzW`01P< zS+23v#;h@)&D9;TpTBPf)!g>vW%_Uslz0*TLzZZONXg8!kp+5pCw*i2rX;pzzfmAl zVP>JyEMq=owUTYROu{F>O(5OzzP?(9xWwK@ijk~$Dqrr;mU?J{%HzjR;XhjWu`kv6 zUKev(<=h?Zo;VC!0=#bH>aHn)tzlA%NOls}{w{Oa$-wSfH2G)hQ4t4Ml~itC%iwL} zYfT!gX)ZS%4186Clb5Pk&-+W`d9c`u6B(s#OP>T=!{G5Rp-_^rPLku5Waqg`UzDue zi&oMr=v6tr@s%w}6~jmw!oOmRgO2esjH0+3+Y8*FGi3L>!JJC)c?s?0@;4S;qD{F{@o*8`s*62SqlP>=w9`a zdvq4;Y7fxI_Y9O>cTMz2n|HX(<2bWrR9|%tS~H<(K_ZG_11Cz(NCMoW-`N=yX#1ll zNuE%Ec^NJ*OBLuk0oZ;)(hzePg!o9hos?LX$cWzF(HgQO)kKbB%oOoB`T9sNxgt|G zXfGTLam-~KYVQyVp`Reob~*+F;+V1`OP+Tci$mZmu<^3+3vmr^7`VD#*oaT~AT%^H zQWf#@*^z65o~gYEq5;b@VC?#8r7kKC6ojK(Ybe!xmv$z9ma`a@R#qd=``Y^CZdZp+ zr`HSam0~tnM`)e@nn#dgwQr<79#`q`cR*8pw2N>2Z>&$P5fh?@r{(DUW$=x#k zNi$76Z1NF@5|DDeTqQ{Xy}o;*csZR=b)Cd#&Sq~I+*GS6Le~|^bK8b?GU&v7gvQA^ zQ&YrMQ03XN!tk~z5B5j^Pb6Hux%QwT!?0nFccMLRO6E1MJWZe^q+VsrWL|w(zdT$` zpW>k*Ud?7NwaM-JhGrt#ZdCmtKjm=Sc3Fg`RwNYS&K8ijB63fwV>xYKd;EA=`|@~j z%uP73*NQ(a@(teSxny}Igt*DLopg5Gut8(kbhgcidHh9mXG3J1@)H0w5jqcin$4Ic z%SahDN%M_I&x@LhelIdwg>7YPf*XA-Z`;KSeLT+OjnuXV@%1cPXu6>*!41HX=cD>} z*w#%i?hUrt?PfClGKS5yN5v^b?IL^Mcg_Rtz3oXK^iQ|o=R54}zSc6gylA9Yau+(k zy3fnkzv4w{wlu=}w^aH7_MhSf=H)Sb;k4A)#`Q-+ zp8ZG6v|`D0$Lh_KV^e7yd+Y7JGyzRBO~q%@?`rAh(w*1f&jnFeoV5LY$_z5w*-v*K zTgQd`zP@j8JDAx_*6uemH~TB_!3mCjBgB`H11Em&ky>dpHr%$6(aO8_#T^Y#vQ?7iW~Uj1;C0|I*pk#rQWkQZ(hOl~;(J80 zE2#X_KTu`7o}iQYzkv|(r| z#EHsok=cDaD!yzFSAR~gkhvHKO0B4DLp?6&-ao~6-5DPKdmif%Nn{)1+eB~?WAtEdtIp^bF-qok_A z1^pf3%%uqDg;N}72Ph1f|2KYcs*AL^1R_$E<@naBhd1-xnXgnkTo^GaMxtT378Nfm{zj{^%}wB&^|R&yy|M@_1)jUDA0;A zZXr>G48*_`ub37rX_ay=ZvHf_Ybs2x)03+_*kaC8q{Q}1&dC&DRJ zr}KuaN|=Y#BiR|2315|AAe?VdzF-!cEbL6h?SSJg=2R`}F-V6SS%z9EHqP@I((=Ax zUfC^cnUQA3Pkl%-EFrNn#zn4Z3`8R_sT-=lLQ{j_YqSo^I|Oebhw?l+a{;>6I1{yo zaFwN6qfLmqrcs}Q(IOyF$;UU-3ZKqL-NLKJ@Ed0YErNIrit)HM&KyZ6@45OBRW(5# zc^=_|Bw3-!Hz`CcDVmG0?QyB&w-@OOGn_d2#L$(kQoy$0j1I$mES{;@fp2Zyc_M@p zE9UAZdU-fISz9n*zR^Fz$+~|uw^!by#_=U4cKALjguc&=L?4ZIFAXTB1)SPwg=jtJ zMXt`zJL8PuUr-$q6*rT{+c5~^LNXEN`Ox8};h)R)Su|wzG8Mfv)pp)}ll1B@0vXmo zwG1p#l2nGMZj`NjKHy;cqEE=S`r`yM-h#5{~=zr zs3~n4eD7M)ZzS@6Z;Jo-DMICc?OGGZZve8Z#s4!xlJ#%86$Y;oUD3EILK&JMD9DM3 z`VHL2b9dE$6Cq}DF#l;?xP>vpCA(971} zUJ zn5n}iV9d=+P==Se1d2N~d+ELxE_>n3Qxjw~x>Gu+Ae)>~%pIYZMP= zZltBqS+RePzV&uGvMybD;359$)PrlyJ*0icX7}H7X4UlNjwQ1{#}&aMsM%u|Dpp-Y z#^9gNK)zcW>v`Yq7#{|zMJNV?`wApPRI@0oJm%6TL*Z%*Z!AwUc0U63TZJkXF@9nc z$#JMs`3Yb8MClaYxbFuYAq#YlAp`g8XyOZ^M-Ph|rLHleI{^@Mo%r&YeVDfoaHxI$ ztf(1h`}URTWZI~Ww4mIk64k&4=dJi<41>#@+csm!(C9nh@1q3!U?sGx zAP@Ngr0PQ|2!}Utb9?%7cB67) z;hXkDeO-pO4>&%EU1xN-LQU+De2luadD9& ztl{Hge`M*PgH(TQE5ZT;5tDOouVaCM2F#nzJL5y$4KMR=qr>R5I7+mUtLe)vD+pCZFOuoM~%$lnA8JQ447{;j=Il{Od z`)Vir=hFhgZy+_v!!81qp@mC(uq}56Ju3^@#ZJY11YhIf zeLzJIICTVhafO9y4s=ycdKkka@)M>HyXkfVf9(W>d~!~Xe*~b*2YaqSq2f%q#NqP9 z;*z{g5Iz!B@tEY)B%m$R52&C{UKv>2vvx~<9@!Mpy0=nIyH`Xv$TY6$tgUYfrCngI zvv*i}@66XMdQ4~ORjBb6A8&LFZHJ&A_>C^8mKGkh9pa~isqe7UEFK4w4RC_b4I-2Yqgrb9kcdh>m$xI_Nar9%7PUp)W+;7-E$pI1+`;)N}e z0s=2O1$1kZ`s|!0>P54_;+$$#diszrO^A33SZw-zOv<6@YWq3scxZQA&*A`v(d!Ss zM2F<6Se7N0)huQ+N7HYk>bJ9}7~~&k?nAMxCMEjc;Pd>Mebvy84J6&XONYCxYd9Xx zTCKkMB@~8`14jJofPS3{c?&$~h7y8_ST17S8x@#H^zupq&+0}RTA=7$8n8YE1f$fi z9A&`(!bi2~+VVxd72bxf>20gT5>Ssd4ms}m077Twe57faVb_Z-G$D=@JBEp9aCYM= zyXMGdd(HM^iBpeYLarTIJ!i~hrYJ0(FHr zSRhWx^?5hl1Y^#yje^Dy-OaKEXH@ur6(}2N??NleoSr-xsxE4+0E2cKnM;;dv%6yA z({6)Qs`YAy*R0}eS>l8yye`D=8#cRiwA(BqpEvQW`edZ~Lh~(uIhhu$>7c!D-Nn6y zx_uPpg7H^P2I>5#rVl)$G@(SRtTC9u?T;JWWjSB6Z|dvd*HI54a+&w9gT+`nge;7C z()?M*No19wd%oF~RJ1TM;0H5d3kk{zeJl$a{cr}UVRz|Pf17i}Qx1XXT0jc}WWT%k z@Ou6P%P!T@h^g4t(8#=)uFpY}=oay7LBHU(JUapKgM8lU+ZRKKU1^OTcMv2GVUz&j zTjHlhM6h>q)GSR(Zg=X(zsQq-#P}wFLb($~LNX$?@g$HBc3eIAyl7Qc z;#q=>4}wfm`u<<9bF*fv3s51V0iV?IH0JG>DK}G7zxVf7+&{dX#;Hu&TJ#;Zt4Qq! zCK=qFG(27AYle~7+1Q{)>O7kk1xFZj&RH}qm6iE0bZk=!O-1ibTzzcBRwPNf1UmofUvoWl)j zH#s8=LX^vq4{S;(bs~uU6<`rbzLC7sl}ysd(-g>cm#h^Uw&#%DMvyt9*dcfh0;7%+ zA-@p1$z-Iq_mUQ!KRXZAZDd$upQ(iUNq5QPRc@VW_lR)f58^%SSxxiYIK5t z947VwA?xb;GgKS<&9Qw2&LS0SX;Z&)_@A**^>eGrmG7bqK!+Vp77%E24-7hF_o*r{ z6nJt2)h29a?`pld#v2I4Q$p1N29PmfvCe0d!*v0^(NP0T@h)z``EnFBmXCnzh=)M# zL^oL5q*k+X4}IxoEXrTC2-K-2q2uXd`%h(G`7gm6*i}1AJVMr~RN?MZgwvIS6)Flv z)JXXq@C@1taxl^D2txR;qqjk($O{$pj#0fa-P>4Ao87E}Hb6rD$54IUtbAhCyLK2a z0a9Wt@VjO8OUw3{Mhnz1(W<`{BGT^*V!%1>7O1!7pM};h<>xQC0vRpI86>DJkF8Eo zi(LcG$i;I6)S~+Ne${s)5tPmneZ>EzpA}F`?x1~<*=xgp0QI!*i943g4Rehe&WU>o z>MdZ?d9{#R+FlKI!B4}(R#fR<`S!X4hZq(VLOA+9II#biI@165gJWPOV_<9Woh}O5 z**ZBJ*gF4@$~jtIMh=J(C3CEV8X^E6hc|+Zrng`yhyV#$9cD*YLL)=7zHwCXT`yaj z=Nb52VW=e%%!tSRZ)aM{sIl%gzwY*rMtP%BaMN8$BV!8f@xuTsI*|j}R-8(U;lD+P zYjjTfFo&Fed$uD@&Y=x$5Y}*%A#3=LqUXk?vTI!@xfe}do3r<}ES=p~(il`9GE*y} zlH<^0m!zz08XgCJpwFAL&#r$N3#YLsRq?oCR;7xPx(vm$7CwL?R$O95!)YEBXfdZ= zzq(_&PP?JKQtH#Lt3VUPT|27p+ z9^)&d{x%d?{zp>*vj2X7azhnT)SlVl0jAp(6SKMefC4A=>OmqLic?lNEz zks-o1pVo5IRd@}o%KX;U@>ax00wP{lIT2WdzRC)bm zPT-B#VH>+MKHB!{zWV@ty?NYDzTpGaDyu@j=NWgB#!HJDno6yO4MYbaGFBzy5!cUZ zSR(WmMn?TCAT=c+qBXPb8i6TdO@t&fEkmOO>g`mV!vf696LK_@=!h{HQrDm;b6EAx z#cSJ)3a^EcNn0f(;e>@G{k9amNVVw=-%oK7%}G;lAvS(2Di{q4!Rl@7+Xj&#Epkmm z*EF?UA2ME9kf$!4TE&3(m132~gx^Ghod=-}K3T%DDCl32sFx{DiYT5lCECoA)nqxL z=*>iu#xiAiEZ_i*4qj!FXKlo+TTvx;t_vxMq#BjCN+mAEF+h}VX0GMOjc&4LdBs{H zom-NHy#qnMOsDH|}-^1L@r$DrX`w zp4s>CXfJJa;k-Y{!LXCkGk48c`fbq^(!Zd&9CtyvX+fPH7F)>$EfVqD)!0Z1|JO(I zVf!u}rajXGAkcbI-9|)v1f$lG)kLjm4Y{l_4x((2=7a^4N>v1TmIeEaG+N}wk2{YR zvmiJFl>9(3+B{}JsT_770TMiaa#q>dYC=1w(Q`t1t+%VvVb>J5V)ir;-3p|k!QAC> z*f1krLDTk-rMJmCfN&VsZ$z9V-r`GjK8L`DbP2kc4KTPqYhMn3Hba-H3xWe%lE7J6w?jehh(@IFhFXdsKf)ujS2wZsnXAgc|Xn!v*MPYU0wfmo_>ekj3$82Hr9K1EMBLz+)Ufc()~>%Jmu4Ae=*fp ztHHq&KCQIR`0^2A{%+w;Wh@+_T8?}gt-vR}{RzN%Z3?&aAY$-T@*#w4t@z4=zn{*F zp)D6md#lR$hPG6*nK|VAM6XdLy7r9hp(OSPBYe7hQuH-4#Ry|JlZ$#HK&LPa-Pp=2 zlRZP4qr;Cp(tucgfo7vjaPz1i+H~2DAgSEi7W7oXn8Uk+PJtZTrBwcDKm9_Zwg8@o z--J5BwW7D(E*im>wmoE-!m%J@Nc^|MQX3@-u}#6B^yQZOfzwC z#!dSqWUW?PNE1a!6uREi?Lv3)LEdq!W3>}Iv!a*3J1(zyfIIwD z%5P~u(w9?fx0yeZm1|F()_^5NnonGzzsiTc2V5LwR@Pr9Y2qbp_UKO8;&bA5Y*_UF z6w%-`vg?eg&TnvSZj?&2)+_Z=Pw@y-F`K{Oj4o5RDT*h;(uPe>re{5#Yce^_UUtNS zJ2K0onWmPAelUa*2AyhF%bb(hl6tqJR+a_^*li;o26~C(KeSq1g+v_~NjEM(My|G%W*dkM;w;~E2gsIWF7Yz9~mxmhbGV%t0yUn^ zIj?)K;b#mdwT5jht_#ntANclM%QzJQGFmb%Sp&RL7mnI;yfUTYSM0v{4Zyg=0eThZ zmFsi?^G~3D{wocjTE0cxC*T|Y_0Ku1bP*2j!8cRfY_u~Spr}As{lNVxTM}0@cmz|^ zrnK8y0wH$C)?+mBh_6s=Zg9<&vgJURsP2gcA~^1gB06J&tKDFW9$pceDtQRF#k|=) zP#zEtY?CxS%nzUHZ|>ODe5`wx8$_hBZWIS};GGy;7#vI|-}8Mj$RCP)O2s!?gZ^U# zRAaBjU?%L_nNC?-Q6krTZ+00-u2}JBAWwVpzyokw_15QLPV0ZlmUT!)?<5^vGpCN2+es#L=YLO4SS>_x-vu%2D!oA1^fnY^OuU)YiKy0?h zxi|EH050^#mrEs!y8VvyrjkOXu7S-ds^Vx32ST5Ip&0NyBjbq0kB6P;*RAg@M|5Q)Z0FC+tJ=ryXH6Uw`2V9hu+2vX9OmCX8KtdS4etlHS6+b-!PsvZXfGY z{M(d&iM8@I--Wd`#(?K!y9lwN3<iLLlR2C~fC<8qKJ8O0ID7gk0N3QENsCvK z&;wTd^>51o$df%N+qg`vtmRsD|7^NluOKHX46VXh*HoFPVVe8Vk3v6HoQ?3{t0_7e z2NuSoWJY<|c%jhS)otxu|LV9@gBnWnSc_m6S{zKPAeRXA;Fpgih{(zIwaLP;Uonxo z4cN%&oD?@~=M2kHtY+(aV{QQ3G(I9WVq66g)1A|aWbt{V2ANYruO@-Ke<+{DvXb!?{&TLfDd zR;sVJlzl0_FvY1g2yEVRhf7{^H-hsndxqIN)C!>t^Lz=WOa3HOmDASgg0%sw5@A&# z;2(OWq&TB|3RSVhPgA!r|D%7Oqo@;2{-{2mvp|-bM(u#HE$7IK_gPWFp8va3*R)Nm?~Vry2Fq^OBn$`$Gkt?8b?v}7w7Z_2jcl?V zraQsAW-4E?T>-u*)!8zUx;KKFPc5N#(E8fZ3CG}fMFsya>88sv@__2RFisrFl9GbmvR&me z1(0}|(qpx%PbK{}h2JH|ay;RV^=cgsqpaEBbw1a^SF{ISF`)Z-3Q=#oe|=tC;f_>x zp?>_}!ucnOlHq^wY5F!VH?}u&FqN_~{5PGEsQ4wbCJXn)1%YG^8a5T`kj~L8XXtz} zE`tUL$V3tnDuhsnn*>_kSkxf)^Q)?KRI|zQ_V|z7_!V+puW{W?+D7O3rstKzv-bP` zaq{&K?LB0Hs(mM?@q4P_h4+m!`(%xxYStDfx(#crRMM_hPVO6?6b3YbTIZqZ)+^iP zx$>)=cCB=fycz{vQAvUkFh-)U0UL1E{XPe7Q0eaS`O8%2QYR38k|3 z!hQ99&)1qQvdiUi5vV)idPUAE45+tuv)jDm4fGW5AP?bm2DL$KqCGG@X<h!&;Qi|pss*#pR?8Ho^UgQMSOph1+SRt0X9s2o zxx`eIAv-(*D~DPHS#PY7NA*_i&fdX#wI#E|6%ZNBeW%%LOrtB#!|23f=#}XH zc{4FfiexG9`(pz+WwOCTB!!9o8nG3tRcg47>qXl;R-||bPA&5XUDOw|<1Km`G}o{m zYm`B(H=IXR)L3U-=V@MaKk8K`5S;_Ma7$BAoPN%g&D4+Yt&AWFV~I`p?Xs(#7YxeN z#J&Z%v!;BBqG-v+&Gs~l+)9W>@97|Rxt163uTHDB@`(-v=fo=pf7?z8%KJLFG6O(}2@|1t zfT=b|%#@GZB{f41l15b7hC88p|&uoxq(L_|R5nBc{ihc5h%wm10jx6pE3YaA>2 zEwrTmqeA%)a{B)jTK?Uh``14|Flt&HkRC4BTj9J)Yu#GLZ>QIPXVOPSMxPuEnQ!pj z5u0C}C06RJ|8WQKAy>Sfx-Qud@c!68HG0)@x%Yljg09i#QqQrdBzg<^<=WVi#s9+t@PT?pwFF zJrvj~pETLlpR!Uz;|ih^$$;H@hdC}a7(RgAJFCOI(!+V6`kK3wjA*P#7kt8p8IicW zNS>h5rx=5VW4kecOKIPGOCJWiq!*}_pMTYMXe-Rg;&M2 zg9d@od=lSF7pP;oS=p%0?S8w%M+@S1Q%6dU1VI@_y*0myUvME!G^*^K4(jd0Tb5F| z0zoiIY|}My(D|zF?E!$MM@C0sN!{4kMu47Bg-j_WJ(cLSGE!c9m;)8>ZtrX)ZciT2 zvnpSiP=RFe+M+5CFYgEx)ZV(@#J5B9Wt7w6xRfBjtSzjmkt9Wz=s~|lMUke;N{S10 zF1cf`Q-f66^P24l09e#ajpEul{{Ur3@d{V_6*~D*_+I21d=goku zT^U{L2YeE5hywfozeumOjz;1LD(*}{ujTQkq!dNS^TMRy6iiq=c0<;u-G=9;dx|JF#1H2?2uI7p6U#$9fi?OZ@P%nba@BTr1dnRa*ch{@FCWI69r|jlRZkRr|*AD(dP0EM+dqe5OdK zD+K!|(MPap^{?NtT(zk;XR_n54c?OM%S_m(L00_m%|c}ZdQ{JweXJXMWiEdXWQzz3 z8cg2I$hOH)T{+Xl(rQm04Pkj)GWLUqHLreL7`^NZ(AN#k{;b0SbYbbr_pps>9jjzq zV0q>3zjO#I8-Ac$XAcHR+yE?4dUgsr3t(1y#iu1+omiTo_WR z@WPq@Enq8nEP4lc5O5z2qNGq#?}UJkBrAB!`d6i9R|I^He(x8t?`GkDrVswJi|jYE z_HPN9i1FXzrGl2tUp+#=1mLztr1C*^4*Mqz|>3p#I^hoeVUp`<#aH<+GyYQ_TUDoiKOl;JAHKgzI{c`d~4kL zkdq!BlWNWg71}F(z*Yt5k=h6~C2SgC3(?6!5RZz#jWvJd-jMPDi}fvC=V!gzn~vw( z9nwE=$n%Be{EmnQ0W)!xMV%=<_{@E!>l=dwVS@J_kg0!~^He=dUCtIh2sOu6tPf){7ory$ zLx&+8So4S11GOf@xGNlJ@Sd}YGUJ3y2|3x8y}~gtX3Gk#GFU19PIGWk?t?<-Zs7gQ z?r=7ol%ipFiy0@%LqfGQlP7|~r4w_KUgchL_X2Vo#Y&`!zjuRqJ#i>!Iq5UhqzWDE zo>^xRe2S^l*Jg~H6Qa7MX#de_xokY=+zHHl(W$WeJj6@qsUA2>!7rP|=ze#FACi26 zaJ+5c1bff&r%U(F$8GZj=>c!h_LvD0Lgce3SL6;xe=51ANlL^HsI-g$yu=88G;>A$ z*3Oq{uoKpDYD*8tv>V`{XjCO9;yFmQ;R?{9ZTuxLkq$+MPBvIk%OEEGgl@bO0E9%Q zN|a|UoRPxD5Y+f*N`EkFw4Q9kU&pXp!6+ z2zw?+bLOQNy7q7|WOR+A0DkT#%~I{3UnSO7xGyb{z^Ker2~fg`uxYq2c}w>d`I$wT@`&?T_Z>XVPGv( zMILeeB(@qMpg^Zrm+7YUS=i0(tK{*RTU>CO&m#Z+6}amgJ1u;lN9h0f()u5rNB?FP z{)HqWeHKw~HZ?RT9vlUR0Vm6={eo*4fi%~jD-#p@o?kaq({#3TSsSV6?FD82gh0H8 z<$2q&VHjdu_tRxoVqHzGKlV80T)tSo&gKDB9U|#(IK9M7I#!FKBWiwX9jD&KT;C{j z8rX{i8H!oox3Xs2Xp8{mbni>R$b)*j?zC|jE&qtgD0jvSr1XFWgj&t6fDDVuouO?D z0uu1(ceWB6^_#{(WLXRN&PS+7oyiQ=x z;)g$FElD$2JpNr#Jub>7#=RVUZ&DB@db)FAtJHT0@A_ zYdUw;Z>4bPEMua{XA3c#kS#A%N$$E-ha!9vvlNk7p`20Tc7P(cP9w;Hp^=AO{>RJvjsyk#BNH?)B z?)6N+X4BWe4vS_v^0M$2dC{6YFk+IEs&)*VBG=7N56JEx`b%Ot$#}U% zi6MKNshOz`#zO}0>(jJ4KQ{)ng3vij)TQCrvTBH2$~KOpz$)XQw6=d^goRl+kVDZ0 zjXqTcXT(AI4-=s3+;C6XUdvJq{(hfpBrHEuE|7q0q>J%20_{4eqU8r@ZhE%JYrqwg z%cOCrmfj%`TAa^EYw5`umW3*1UUo5x)gx;!y{qTC;+#x_Y^@8mPBAk*(VIDNVAW6A z{3zx@Mw@GQ+D@|Gu~9?=k?v!R2;mm@?`ZZ5)=ysMMlTkks;*DZd@8jC0kfNOI(1t@ z)TTVhqK{~*)(#W~2|{yI6w~K4MH-dm@fWuzO*hd8N}fj(4$9qzuUqX#IoEBnnjjle zoM0QoYQ{)0Jh9r0L75UyuYFk`&E-jL39~RGvdugS3Z6pf_n%sy=}|8lExV0T2}Ht= z3}YMXS|aI;N(~>_bg2**Ck(s29)A2IN5*HxT>{4Z;5*sL0ISutA8OS|hLyKkrs5*F z!iCOrCVU_zAR}h~P8`epSQU5vqPZmSjavrGjIcjybGuYa(}x_VNVg#^2xxHGFIv&F zdfaJ0yCQ{(rsvfz=Xi38W9-m&W7Ft+e{h^G;g!%L{O#T~FmvX2SpQ;^Wf#6=O;rAvKL*&h@#Toz)Z<%is^ zxU||m0UCf5on(7VoG_M~cNLnQUHT+>rB`v*78np$rS|?py-d~ls|ZTRI)N*`iy-^| zBg>%RsAuo!@K=1@e=CGW#Z75MzHcO5+&c*v0stV7z^}=ND(4qLfGOSpiD`tDcwZ#U z`Y403ciJX7`1MY{D}IXZULfp_0DAXo)rd?ehykr}7rXmW#{K2Q(|dIF_75ukz+6Mn ztiQl?h;J}`pT|YeR0#=Isey3u^IUeJa58bkk6uzs2)6J#BiES@s(CiWeHxanzn+nz zUheDy9 zmtaQpY|+Du{2A1+mJHuN@uY(bICusRnvijpMAUDOnOz=J6j${a$EqyR4iy+)KqZVG zakB6fhT|+1vD@UU(IUWoGm~l~1=-NrD{uu)%zq}4@vLcqc&1a`cgE2fy9L=vf3Rf9 z${t?W^qu7rx(nR(cwW&cf&z=yMzMHoQ6VMK#Rte#}NpOF^Lj|zfque&3Xb(=R*ZDYJ*1Du!M0P z$CR47in#GdR7s?QZj&DoSSymW41FV?0$Bwyh3IpyG++1%@s9Hr3}?7BOE#@^{s2)d zdWhawn(i4rFds70c|NPq)r9o1^qz4ntN8Z;>0f(0I$~DN`+ILs|KkDA_#Z#!3TEFl zw~@4wql=Bbg`k;(t)8QS>3_{GH>$aNC@yAxea5A3j2eQ)py|58T*oZ`FrV>R`0>*( z-bc)*sKhn3m54CDbw87n%Okv`5*Eo)XvJK^Lc&GDB{nf2q;1W*_T0Kz%Vkrgy>ZR@ zdG^v^+d6#ay48h5GV823`wyYRH21NK*YWemr`PdOBqVJYfNm)k@`CVWmTj_`GjT*s zrOV@qSm$_6-{ozVVxKAV1$R-Sg%nrj+@)!=o*7(kfX2h78(S`EdmpCDhQ6Q`bfDkN zNW|T<8}Ph{o@TKtgt8Zr+0kK8tm_&}+!+||ASMT8N*<{}5_)vtq@$}6Nmj_@`rN|O zR!6Op05~g@)*_vBj*%G9VfuRdS`%xPh56;m*6czZ`P|AZ5m~qh46Ai4DHjcUD@8F~2WRoxcDRd%@1A){Wws@9`AsRw6$dApYO!dqVPFe@T zqDAB-1r88r#rPvVZf;|SjN1Lsn{l(xo~JdHA|!~{1}j{ zN53D)P)YB6mI?&i8sP_3m?>_BEm7NYc60+R*jFrI-Q*!W5*4S>u*O#&I!k#)41+_A; zZ7M?Z52Ox&_ytp|AAFKZ`Hv65o7ggteYn*8W???9<^2v9Wo`<*=U)22t) zGd9d%qBI-1ZDLIWPj=OG~uWPQQr8NgdtaUe| z``ChwcjmJ*N_ zyL-}huZZ1;=>kD@8Ht zM|HDkJ*b*%Ccue=hD!G%p)AnTuXac}`jQlU9&FP2Yeg9Cia$T9_S})Whk1mf2bVD2 zBp*>Bd66c{38i-|h}rzMi)|2$moW@PiX?~$JF5YLZa6#I(fZND_QklWH|)geybpej z9(kn0Glzr-j-aN1nGvE}%p2G_#J3LDM66JZFBr&asd;|~7CdTYE{F5*O7BDYo&dh% zLDSBu4QUgm)uNAUh$u_1y1a%^0=h-jrAgCwDb0F=(Frz7zqE^mN$Lk_zalsLtYLg#C07V<+_o% zH970CiMHJ#=78SD7cB#ten2bN5&^zJO;Pi^-jh#KfEVt?k}+ACSJCVdWovX- zFSw9&{&v?DttVQu<-Jz7Lx6oItOnz2Ko zmE5QZSB3PUL4oFe(UGKdYx>4vQR~a^4q9#D=6V&(PJ_HN`0S%t{)6{M$J%CHzhHi1 z^~a}(RPjwW7u_-5(Bz8RFO*Z?6@yf>BGyRD5h+~C9Z`w5!F(+XVS3_bB|+hm`?D-Q z_u5U2eA`%(dKEPR&jhIX;ru*d`^G882Ixj$Dlb7+J^$AszBs z)WIyGNG&4U=S?z$ockz{f$`jZ(zE4;OdHlr%Y~_Z(w zGBkzvJrXk0B=xZh+ZmlFHsYa1kn{s2lkl_>O#U1_eP>#4yO-+3oB0_DOrP2jGIfZ5 zKPL?(@N(oQ~Non9Y9@ZC(|aL#{oL>&F_{!7!)W@ z^_<$voT^lKO7F%U6LA;`3`%}IOJcwXa=K8&FT4_0{E{U$G$7bjzH4w0TVdgH%TNky z7fh=27(cAY$(J{?FXaEBuQnMxAWIy}FWef&Ke`Bo+bu)pL2fjTu|TaBK zrE;`RlCohjY+Y>zdeu0@DvsZHH4jTTAH~Tey^1`a-ADg4i8(qtAod_1Zwhy{x3@`& zYgz7lhzJy4HH&XWlEMp+p5BNlTMnby1yPgRueuq!J2GNn1;NvW0?`p(N*)J};|cA*8H zgvLVvLn=Js*0J7Xr**g>(IGGu-ZMG))A-NCFM41&Sg@m(T|RInM>+=iwnP?w=Vo~I zx&|2%#Y!<=1M?EA7{zN@W#dZZn%%M0E2ryK>Xi{6mz@u=1H} zS>B2ZidA(+ZcZplPR7c}qVQ+5$$}#o5*uY8*FA+;+Co^0w~H2~{}*E6rO7rg|t<>0`YD_&wxaz%xkNB7Fc7{sX+8 zRr`ceS^=!&(rf-H2Zkax3%)r&Y~+)mQ?i(0HV!ac_MZ~vZ*XO0r-Od;B^6~9vxFe5_k!5~obSu$-TJ|{kFv^pLGs>HwvJpDD z3nZS?7Wz6NTA! zCQ@EH3RDPdK9HfSyjHL)FL&Poh? z%z~JHFfsCU3M|HbM%q!ju(?qN?(B5U$t6>L;2=r#yo16iVwvQ#;l-icxopUzP+bU~ zW-{fwbdsFV>8qaFGc~CxL6s@bo-WU(ecBy|?#fYqO3bN7)wvwTMarWndEA9H!4mqF zdJ6RhsbXJ4BUp2rl#_#yqOo3DFH~rbU;^(_cM;{EIY);6Gmcs zuX2j#!)bNid8!M~x#fbJZ zT_8C-&DDl(8xV)xYj@(_@v|Qx=2nzr`nP2fca%G9UP1))2d>v(4^L#}3-eTew93iI zv)#8vRqUxyVR`Wp%DgfK&sE;t?EAVOM|xzzWC2(q^d*`gRzL(({uyAm@W2%hn=BfwVAn2 zjQ(Dr{ybrB<})Fgs{Jt$bS+gTuA4qM@`YrPf$w_zdQmw2+FYTYt`N-%=bRP{bwb?2$?q-iXG|PSq#>Ybr*VZueZkPuPYFJe-+{ih^nV{;j z`0Kl&Fd}754r_u)RQls!g2Y()BU0^o=&SVZnu zGvwwmNPkO&qd<;kWVL%jTI|{+yoP8(ys=6eu2J@W2*GD@N%@%7 z=VlYw+;>l9yr}G&%!0HC2V$sJ&6F?>@oCMylemxSt=f;h%ph^E!nR1p7JRdns0%^dunlyYAVb$UlrP>in%H2(jzYJi$S>8WS+wxGXa^_CI_?sSSeBHP zvz*g5@ube5Gi%o?v0)6B6y(FUua-xHjEht*)acKQ_-78aXTy(ivSX<68^$Y%nB64zC zG%mKinYN;;0o&Z{vbLnH!6TXWsM+;)fg9A%hIY2T^Bx}2e%{#xpFV6aERc7c1#YN~ zFYp?^xCV$`duUxiS$W|0IFA@xd*_~PF_)?^^8n)aqmm!*8dH0+CQ{j4u5tLUrHpqq zC5}&ZG09_}9ga9M3YrNbSo=~qba#fu5M*x*zZKC}W9{P%rn2vzO#-sB9j<9tWJ1312L`HfpIX`?hL;67SL1@P%%EiP4Iezx%rfe7uJk-wB~T zc>km=3;u7V2crK{AtcQl9F4x|jDL0I5~b;-zo`tY_!vSFJgy%lWH~=18bq0LzzWFt zAo6sg676nQV=sHQ$y|R#^Ir%N0Yd<5TwItNJ3rl@{{ie_e}hO?Drp4oWm`&>l*)n< z;jVq0xDC122%%kOnIu!0#)?kbpDCKiGWc0o!&;~^7u7MlKTNLD`b$FZD;m=zQhoP9 zrx%zb?w(UC7xyD_l5#V?Z}-;rkI}4F1{WC?6_pECplrL#dD-^oM%6x%*59PfFAj#ClT&{Rpb<4|@%l-t%OTwL573}Cza z6Z`XVhUfipH}4m~4umrr2!k+99CJj)5Mfc=BzarhVW6n|I>`)CBo@K4xg>&W*H+f{k4&kHIy1wmaf@J*Jr*2ezOP zbNdd1Cyz`#tX>GZN(iI$0(=0TS~_4QtvP#-c`FU`o61F%0?NzmKO?};b< z%1rWELYF%pzB9q{my1lojET;)em{%}4H-%jevjTTHu!5+*v2VDU;64!mLVV|(g!zt zm<~s_J(4B{G>@tjP9ddn3b@jT9S8}(K?rRNr zhT!wfFHyJL(>YtAG9=X!TwKBZRI?s>t%`UCUI-&f6qZ%5q1Xq+ESPP9;4-m9e87JT zJg4YiD3@Cz^9FhbX2zYR@3}PC;m_+`m8%cYihKy*1deuGW7f4j8i*kkQ|Hi&Lpf5b{YePDV!F~ECN=wx;{WaR=`=`%OqSrVrFu+|87tS&ATOK9 z99cXNiBf>GF^Nl|j#xI8EYZrWU4q9dg?RRV#@UUc3;S4}=hK-(vStHNl?y(<{ZmLxhgTWy8dc9IE08pk;>t2j74zi6=w z)AnZTyX6$eQ|#iK0gK&(>lW0z2ncn#FJIzkP!wNTvjzDDV|D@C zz}26)7*!7HdcQn#rl1MSEkwtqJX8S)`&_AF7wHuY`p}{{Bo)M9O-~h_Tsv?BjUwtA zz02Al%~=lbmSI$YHn3l1n`3&86e)R4k2UmxQgwa$)txP97<=yv=mJbD9iCjSXcBgX zyN{303K#4bF}SFmSk7IsA3L~+Q%AI_iT&G4{!z2sEvP)JiNKOj@baRO(SMTB{9`J)24%-rbowp zMhnmM6OmuS;o}13b5StK$KHRb*}8~ooWEjggtCIE-gm3=yGvtiWN&0` zVC10WX!dWfl0?OIc|-{$Eh(Ds>0`~oo?C>FG$7lKjD5L0GT{YNC?r0ranSPA20h2Q z__R~mJNRQlk2^Ko2ipn`+#M0Mt1Lk|KOxb@iK~m%&L{Qq=ZWO%uRpLm4BriL%WAzv z?&>QDiCZ^+32oFarOkt`lFFC8!kfSbp&LQ=s-Hu2FJU%zJ@#3P3S{Z6(>|^)P4~zw{6)Z7^A1aJ_kv#xy?Pc zR681ZQdaaUNi6t=ZDUIUQO22!RC5Sa7gB3wv+~&^$vh@vv{^h0h%^<>WTh&!a^VN>nuUu z!n+P`N1q*kRJUY&0Ocnak7=IgUJ*S)?oC0JIG{+GU;${4>p;8hOVnan;-53gdJov;KUBhyLEbiYXmpqxTTJ|lH`p6{7dGv&_)jtz^+B*sI>{hH2E;$RRKg5^sFgT-(}q(eQmKDru}NdzzUX_lPBx4P>^Ld4iY8KH72W#3tkh ziK1YoDW!$fFCQ1FA>)~zM)giZz80{@TWRH2HytA-K!SLTC#-9v>%zxB+Nk!%wU=6IDP8fO0Gko+8dvbrhou$jFj zti%-fs95tZFfA02-0c*JwBRAh2#YDG;!k99g0L`%ycaH>v-=Y7mtdaH^3Sg849&EH zibXD&yUnfZ&d;r9_xroaF+=hh3uj=;OC;uk9gd3H1pm`X3l{M~#)zxdLviR+tEBUV~R zaHbT5oANbe3WFX8_>j}SK``GiW8hDKM1V$GuS4H*XJ`u9JSRtLEo3+i6_>}Wvn$f@ zlp5~1xysm)#eP-{aFcYuwg3m!-qu=x_W&#QphLm7L045-rUNWa3o@`l^Yw;d$2~v4 zg2*Y;rI~oBll2)4<+%poH)7&zDzUWIh$|xDXLPlBY2nHiKI}MlU`PfUvuKfD?LjS+ zSJw}D3gHyCB z^jbOHi-Te~+ZFpET%6IhxDkQOPj(?T;C~^~EZg$Xz;RX0%$(YvE6ZL-qzMju2#(Fv z6I;;IB+G%#h;oP^70)*zk8&DQFz6ml>?ZJuK47v3yXMVDOrihe)i}ta#taHjlCZ0) z!7JP4Y6x9bnh@JHRgg(#o%hT7(t*b>hIJQesWK)>Zn>@;I0~%yD+>Dqp*&6Fp?&fO z7_z^Kp{$=scZUHTUMo1E8vdbXAZUnm*gS1nv2%~3N&2E$ozrMIki`!|le$|Lh6x5m z!k2BWPQN|{wkIU%Ul-ui9Qv$B1TYf|rHezOQd3$Fq7*4fo1{Oz75f84S5W|z21$9V zL=PV&R4uG*r5}d*l+m8uP1Kb^UCX7|>^N4Q8?tF9L$7j*erSs?I(9BPWUr~AeyMS5 zQ2?WEBs3jHs+h8B#8BRt-$io$u44eTXo5I#Ynd)cppQOy(+z=Ad155u(Xc4(^!QK9 zyMBiE(s+T6w!Jh{B7^Wl!a>WqZQlqOprS{J^u-c2so)eoH_^B;nN6Xo_el&q#^NxP zHL0e?$4d>Eck1}Qv@%gAW@|KvEhG<%!Ei4t8L5;V1V$NK8Qz=owOS+M7Rn*~22PLF zOG^--b!2Be4`qih#+5xQZ|Sbq)x|u-6Vg_jtREZHW&-P-u)S7Ei~GodHH%H4rShOG zy^G<8fN@lnULrG=K_+gtUSNrp_ymsJEqhn30Y(?z$HQ+A`X5RKqhqT|1jxf+-lbaI zo7YYN5@Q0jyf$ha7PXE8$#yn~QTvpV&l$T9lIH-bx19Oqph!?CkrN~SaAIcFj2Lxy zOl$7hU#vPFQhW|eqU#1h$>JV*D9aPd=I>4(0z@mU$Qkj|MCg~EPxyqGq)%@bM73Kp z>`X*R+VbEXRUN6~VP$W&KcaO3jilSr@Al=oy4-}BIPX>~gsm5ohU0&~TXE5>WKr=@ zUig`=nH|wfSg$p7M3SyzDA~)=^PR9awd+UEt5Js%WcAJS4=`5nRvm6yw;M&JPQm~D z@Q@CW=yHMG!}nb^F)OsXT#hfIjzk_TFRjQEQ(Yi4%8T=a-A0Z2{7F5B2#(`eq0{XZ z^#^EOaC`TW7zPt4`1A-Fa|d8@h+QHyiN;1vRl*y0Rc4B-_vbK z1Jr;;V$mmyd2nsr(%_BLcr8TTz*@pN&}cPkLXvO$kNWdl=StECTf)oCnN zrVVR#Y@}8p3skUp!VdmDh*?zv+G}#rIV*g+Z(fx(@HPGhI;(exv?l62{_g4g+ePbz z(U};d7L@vA5L0uJ&uo{z-7)c(e7KfblQl|Wz%u=16(>t@ z9?#4HjRd#>^I!dE)v;Dk)$oo|F$EMN-9<^nHyb`L?zQG_Vk zMBqymnLS-`M(J8jSw{LKd6grnA@wp-f(KeM;VDZJjkH4^pV|&6u$tl2Xaj|4ebOws z?RJ|?h@N}IPkjsLfo9}ZA&{6LLv~g#Vz1a~J?@nGwGC7lG%zqX2O#Fv@mIfMuR&jD zAl|SK+I44)j3xE6;BLDZ>4xoM(lw-Y*+J>KRlPAz!8eGUHO@a7+#CU3Eu_T^6AP}% z;&Bqt6ABJt{=}0y!=lEtWRCXm!evp2l%&Wyrq1jMEo4Jfr}o6!LD^xa`{@jo5|JMu zpc>RyprspWTLwJbpGfT}tN%_gZO^o8@mF+V;Jl7gU+6#P4c?!W1VWXBzk}1nAZt1| z4Aj6JgR)5AbdiBn1H;XcK!#0(j?+(xa4W76Pa%$fOzPbcnc7)8KDk>D7;hgKMc=@$ z5e1duE4=5$hjRyC)`;eVl-W@LsL>D_D4v-*Dhq~7+cBN&EF7;i&D zHv{kGiV*7hcolGEsJI>y7TyDPpMp!AjVI{*w#6|jEU2)zr%!q4vWh?-#!d7h=RhJV zpZ#UE_=Y&us#q|GBp2dhlUtyItKa;^<0md3#3zN5 z6L2?~-yr=iM=85&H{8aMCaRx%RDNcElY#01KvOHw5-5?NW!Mhs0!z8l0C$K!X zco=^B8>u}7$Gus$oEC;g*4WhQo{C+s? ze2>K+ry^x@N-TbvtA4tEBv#LEx4e76_p`(Hr)zNuYH~TY;b_UhWSmZA_CP8%TK^x; z&Z$ecZp+d;Y}>YN+qP}nw#^+oY}>YN+umVRoG&x0)kWoLm38s9^#j)G^POY#@$})O zQuWS7^~pzm^Jg{K52d!VY)CG>^OQnf*SMKSQ^mNLGIG*T*Ea%WsB81OD_M{t;Y||c zXkt*mDV?SP=|NHk$II01l}y;@?@`c_O5^g8S_b7(nwObFM#|^O(5mk4Us0JP_n6)9 zBP8*(_6X4$=vQ!+zGi@)19#qm;{MOchhb8??3v@5B8P3if3C9!1eIw{KRpQ+^ncfv zp#L9SXa5UnN>Q|u!ulz5R)p2qu#$?qE0&wJ#21!REcryWJ>SK!n0rFn_aA3pXvW3{uh^F658dIggZ?zsc z(MZD`oE!*cmk5x5;lyyYlfjKFA02U z9Tf(p1R1CTZY|77{C-Q34+06<|IC3)eAHNN3SRg{dIYu?EVx=4Ej`kQd7vSw<5Tm^ zxYVlmBXZlCOt_-xnM_C`;6$jKwY(591xrd{HSwLgDSOAY+Hi#zy;VQlHYX&=G(tQp zuQ%ufwpc|+wh$%|Q}#i|Re|I3lxj{LkJi`)tAVJ9>OJg$-Y^Z?cz=Xw9$+i+IZZ=q z#c^QwF`KUT?6IU0sacyTw5yrd&&)a=KsP~kwhuDy*(KZ)A+X80PbFX>-?Ex{EgJC>loLA2ITyA3kS#@zBKs|ZidUa`jR+X6f%hG;C{v^X(nlMWz( zGZQp=>K^7Mvo0hboDjc^^^b`kW^hX$2k(i|?w4e6>NpXKB&TrH=E_7jTPBk6!aC5n z{>30c6&XO!NnfZB&w~KWFbSWGj9$Dh8d(w{Xtzd1PKO=#L0p#kUZJ_u6~Fqb2C{?K z&wk{idrS*7LCGG}V30iwEEIgfIW%adR5%u&Ir_P?3j%WDKoCg3U)ZEmDj%pSkY=wT zSWcYk-aH{wPG5`*BYjCOZm1~9AQD4?DI3$_snHj`NCtfsuc)cWnYydIMm>*cdJS)S zy-F{92U#tdZWTuVfM|NgPTyA0lHlAPkcFt>x=7o)S^MS8oM)P;^X}jkN?9(dr zlbWsa$d(#1rWZ~)U_LQXM@!Wa<#lfynyKL;x%i-(;H<&usxYd$!O7O*zO@UY#GMKx zC+=v)1T#(XP(Yd5a>&W;0SN>9Ln;lV4&?Belpx>W00i zABZas?`+qbukdPkda@|6gD)HZ%&YOxn3^N*c#mW_HiQ0pI#fAVDi3|7k4$DUJ->3~ z5y_m>pB{Ic!zdnnwR^}r-8E1Eq)kjU*ZXK?ocJEj5}&vLaJ)mf)mYsVu-=IJNg+Oh zseTUx2Dmd(gQ>m^>x+NtFJ{)7F{D2N+k(d+|;!%#HQ)}v4YFRFaE`cu^;@%PetS><4 z62RVtSG`|ldN_Waa{o!+e8TBP;>Kr2;;}8#EXlCeNQf1oRkYBoV`fSj-QK*4Ce!et z`-PCN#DG*KeQ0s<_g0ydl|Q2P-7b~L#Hb=Bx~jIm0+Pzf0N5{ANWY(1Q50r+wYQD0T8WLY(pG1aIA%~@RD!Qo+MIK zrnLnk33VH2(CPpiZKu|YVE)hUKcJO+y#tmyzr!o(q7etAyIn%%ugbM?)3Y?W4fdLHPj5BR7R#)wK6i`Z!{@j~95~ zX%Si7`10020@uOf+71*B8CQEnwr}n=9};|;(`wuF*#*a#gs0+4ZMhy$)c6jD@p7g- zeESY$*)%XbhG$Q5@*~YAm7ZAuuJ3e@H2|J@Lb)S*{^2sl!QJpjeh$@t^$`DiIEnKA z@n7{n%+44&9y!3D;}i%iEZjRBQwqwcw_nPfg%uqMNtrOT-?l9aTPjwoz9sC=Od~`6 zpM=S+py6p5*X_wHHaoX&Ki-~S(DuUYFiVW#DUnF9(cx)?C=3@r{3VT0_f9fz_^fVt z6F6eRaNmJ-U*4Spsq;4_U3R#9S7I6SO0Z)MpG_`g4=fx@pxH4aQFhGN!zD=`>XG}u?#ST1JV~W@y0+9~8^ho+rV@9D$YRMq!o*AJUG=7H z9LfktPhynFW6I_frVxA`{N^#nKPxC5hdn12$tYI{cE*FdkdiNHuaz&GD*Dz}scXe! zdA@t6J7gqJ*H>5LOO00c1sL|zGfzBuvsZ94L7Hc2YY*HjIH|QMd;XEB$fR&c- z!-foh0a&IltjEIz^W$~C#2LknJ;apIHLxV0(6as4lQ-Xl0wo9IxRsW*jg*5yv6Qty3B`XCZIQ4NXs2AfE*KB;d+xp6hi>oIR*`# z9BqjMn43~-j7GrT6gir3U6z${Qqp%S!&ayVt{!i+pmI}RMwD6d#vq0UvP$LHEac0t zD2mMypn&d$hk09Uo`qXh@)p=3T2=4Y#$#3Bbu1F<6^Vc6Ehz;NC2b6WSj4h=Y0_Onp3_h3%_b4~Zz^jSl6QAtX=d1*IP6tjAQKdc2xL#4QBTPibd z2?wXt_SEJgq7#XUW9DOyHgkb2R%#1!3QjgA+$2LMm~KCt=zeY5>+VKAFjd-Ai=jK$ zi=n$_|Dpf*i%>6n4PL;9S?j{**=%6>M@=Vp2VwkEg~k ze&cd{hWM6AvdKX6i=xD=8*fEV|KqXP`Jm8afyk)kmSbAW*0C#st#ySQs!~EN#d$b( zH=7VT{pC=iNgJZYRR+N~HV8H+91-#L%P;wE+Es*{7?b3BV0yC4Y`W`tGvoE?^Fi$wl6$ZH zcuK%leElAj)}3gt&^CO6PE}8N1o@NkOL#{Db1ypWHS}+*kZMBxkoRsRW*>xbc?TPY zcoX(n0hX`Co?~b?)&zuvo^o!sW}upC?tN=fMTw0R;lmKZeDkMyOC@DBBa*;6%xA5B zezt+fod)z%gHy>;^iX&8xI(oS6YH5q?jBy%j_1O2YFhMDlhcbw%+&$?Lio^VrU`ob z8mI;zT-M9-$j(}aAOks}(m!wPE{Fx{>#z;8VPD*g?7g!(Pl9q=hNGxbERmZKtn`EO z)V5?E&0EUl8sz$`!mwc&j+R4F7}JyA3Ii0+x^&g{ZX&lPdc?2XCZ`BB67sk04Fzg2!CQ3_ySu?yM%j#c;WlPBlQ zDv}W>w1F^_L3W7UyPZzCpf&_TE7fv*EwW!ql$H~*X_$_RuwyH4)IZ3NaL_iS&*I(> zkSFE|S}iyC@RIuGd7(k{&=Wf;tua}VgWEogC1{REB#hr19$I05mY$24o7!kaQQ{(d z=ORr!;Vgb=1^u0tcr{M5Ip~`doorA|cp<1JKn+!;uOTdT#l%TZr*pmHet70$No~!d z7`&KJ;~5iQp(^~qHuO#SS*?xGK&h2Lbj<(+nc>Gf?OceZA-fv}t40)c^u^ zk%CkDApq!{6gKn-c@pc42PO+^lGjEZSA&HcSCG&nUx32>>F+p;B95tN_O956i}YCx zGo(<#jU>j?H4|yX?)tuJ8nxy{H{b?|MX%Gor5H{zB@uGTS0Y=#-d)WwS}%(k?fM3& zXAA7y41w~@sy^97EIl$!=lZVxc511-#9A#m7;kl6Yd@7PNgw(kW(%!6r)ZR6 z1c&TK6?+d-&Y?z=B2S)qjlU<1#dlBknS z3t?nO0OEBDqFXs(glXtlkC;S$NFGJ7p*h`D4eA5c#Kpu+>hZA}#;PF_L&(cXS`E6P z2VrGJE)*Nib;?x!y-f}G(5x!zIT_fiOsF%d)=jD)7^fD(07mGs#YV#tv=Da^4TCeT zo9sTd5UCQ}eZFoAP0?%&FA&5d6Y*jy4t+H8mwT=9HwD+IgO$`Rj94%WkC{0FmoTJS zA-QOXa>`Y@R3~E!&Bw(mxy5^pETm!!_h9@Y#}H(K4{3vYy3myXv~HgYP?|%GL9T-z zr(C>ns3{>^b4by$G4sZ}0jrwehQwn+KfNflF~hjNa_fvvUbr@>Lvq8}Ti>VGJw|@r z!K$^n<3cJDPEAB8MXY~f7*$sgRizWLLzKPRADWt@wBwZmu0H!3hrS*mqR4Cge3TZ4 zG)txWWgp1rIg|K%fEY@$E`A3&kpjuX!{XWB2a>KJDfZ*e5PNp%Xn(MW`bJ=(!GS-iOwklkksb1xQ$jBME$eVT(YeA2l3)RcC(XsVFc(S5Ai$$ zs4>B0qyS-XZ*gf4r=t8#O~e~S3juayC}g=%U+D_oV@QOgz)~@&-chmmq8guL1lz2* zCbqx)st+ZoJA5K?*{eeh(Jja{%s z7{l@Au)fP89we$4kgy%PU{CBeJ~H++Hzp8sSc36m9S!)1t8_st10u}Q2rQc-TS_Gv zhUEcH5Pfq+7Yuq>{9Guopx#kh%%FK6pxwY|qCRuym_K*$V=EeHG}%>I3q(^%r$cp1 z=Be#`x8@n&LJNNn%Wit`deoJ9FY&zY#x^(;XJaS3*21yc8C1{XKaa($Q&cG;w-~cDdV^kX23eB#*!)uVfc$S{%NpK$a8*x)`~}WfvGH(Y1tnJ|EJUWh;TX`lTU}p$92dwbW{IfnG(3 zE&%wq)g+C^)=Fa+k2yfk42@;XSE>R7MCt^3RIn8gA*?_OdNc#!vHHkFks>I`@d{Nn z+UinrDudcmX#{nhx>94?GsgN6PP*3^x=S-1`;NPLh`-Ok(c;*gT zc}DOEo|c&@TSle$s|$gw^Ai>HP@Yt43dU4CE=F}qi>Rmlz|9=IC!(JmCFo5n(bAST zmPctManAn!^rd5Z*0KJUB`94iDHiTdw!-IjMyg%_AC#^L-_}iU&#+^2jKJoYyM9Ap znba9uFsSFrGUS=|ls~Mws8HY>5?ihwdd8k7xC7;O2I@}5+Hp`zFe-@bRQ@@L@to+) zVkd`{;_ny8gSDjztCu&|A<_`maI31-Sua>UQ_|Z+hfU(493g*RP6|#kdkOkFl^&d| zGa2+|Kde!}Bnl2R{OVxHw2znV`WxRMQ@AcfTg0=|Fu>1X3lWITWgCIjDQ^=9WeFeJ zNSWKo0AC9JDj~R+7_-z3io4*i84O?`1-!Pjtxu0UrF$>|i|7bhNg{-|p&RBIz93T? zF^Lo+)v_)9WvB}I-&zbE?!%!C05+zSue&x9}fTQ?Re zKZnqX(l$f0Q$cgM4Qn2swt2ttvXrLFsu;miBxck|sw*lRq6yE!hzq9GmOX=Z|AEeV zD5d(3i_jbFYoCXedOm{JoRlj$>}%YwB7YDX@)HxdLQJh1*>CkRSIgE}*55OSmOC`& zk4J?4dzGPyxSDN`b5&BRtEHIK)TF?KHG!B*qcHtYZWlBO{ga~qE`r>-Z-v>3Eh8R? zPm0}S#4#=OQ{hkM{S-J663hlB5A$CwnN|>6q0uXJABWkVZM0Z-Ad#o_LebS~QrGmk z>Z@6|Onb18ewe66)f-9nA;YdOk?fEif4vNQxU8xy=40lLJzbjTcf>?Wh-Kl_w*o68 z@hfHnv?R=w+!2hJZT`#IDbh>RkIvY;6AC`amWwBU2(MPHz*R$+3e5|cVQ0_T;ILMu zz~6z4dc^a#xnk1p$?wGRzn(3a#*Ik1r+Gw4HH*%@v5aQ_&>Ps1Nh=s4#Wthd-^jQp z^0X>myqyi=Y70G~{uT4yQ}ntxfFtFe*v=4aSCz4K*S&ZJxu2aLt0mHc)j@o$hG2iE zpv$;rlp_?7&{A$7K(3BJ2K!jLKz+6JX*><9%Bjh`#cW9Woogl(H1K*s+SykM0VyDO zgxoLaI}yys;0VY?d`NN_flNjH(UX45dJrdm%A8QjhTc^BZQ0+!y#ws4Yh99lXVhud z|88#}IAPDdF8Dng9LJtctZxx(*VVIrB7S0o#CvAkkI5y<|INIol+#};T&;ssUgyBa zwC6M|lS|x`s8y<$d5bc`0D$$7|M!mZbv@WF-8oQNkWLfwfeY!$W&ggYGM7I$#si0b z4_}I;k6I48E|r-!KOzuJ9N!Ld)+Gj5W0e*u3EfL@WED!#>I2}9$qVxHjJdONNN5Db zq%t6vI(h^WLMCakOTU^(FIE*y$OX-%Gm@7}{)$NBWU%$-HtEwDv3i9(3mvwlb?xnY z=4y@7hdB8xy3J8%PXv&A{%^af;hANl{ao4M(n<}qIKRe-ATEYn${w3L_QgT#*vyjh z(ujeB<}&t{!31Pfi#525t*S4FHj}a?-mRDd2WiaM{<>t}W~Pc96p$+idABf@mf4fa1xP(Z~%1 zHrWGVH)LF?M?+nE%gI<-@DR)Z%PM8a=e55doFH)}{Q-dOJ+4W==@mexc-Z}pftm9i zn^-+uMrLdv!MFj;<}?FElm{GBt6hg}T%%anoJvc>{m=&Pv}2g!jxkliSr>|wyU!&Q zr-j4GECv}B0~fG`O0;`)#oKo?l0*u3$2t*=;VR3NEgLDzgAI2E;WhIaQs-|`g(8<7 z$L$V440k0J?ScnO8gb-GQyqeRMzvByIMo<$`4>U8>L|$EhN@6=T+_l zgIUH1+!TkKR}jl~w5_uEJu{KQk%7K>L+HTzrIH7O#|7x;$;(6U0 z$cWpR*EWUJ_60k02SxjfuW8S~gs?2V4V0|=v>|dKL^E8Y5zk|W=G z1c6J9;J%`oQ7fSu?4Of&Fb^`FD0X7vq@M}9o9=StmOru$q>+f)Cnd5=Vn+HcITT)Bvh4=AZ(C z3aKhTUeX6#jez$`hzs<@JV2@t52e88-0c=hEOBg_hUxgWcC=cWJGn4Z<1+KDK9NB} zaSf5813j9yYx!@9Hz)M>xJqctSgNw-f-H^LBe%Rxgez;`vv|RQj3^f7afR59RW^_Y z*xDNmo_mhniRL`iPBs#}srxf6=&H=03tb9FNNzs$*a>%SzVC3JY2iXJIX6W{N~FCr zrEusf_mnT-z%3}dT&kI(Wzg?o9S+yB2)!=rSVKa)uOvZ^*H8+*_U{-QGhPt37<`23NF7|)OeJl?8 z?e>tf{A@1beHb-Y5=D<_sg0aoZXdw&Hxj#PW|eBVwJ1?4BFJFT@Z5NiVDlXSGXm^6 zqETxayA)R@Xv#t7iY-ITrA3pLZBZRq4|{L>;Nzz-U3o(Xbe}0wdNQ1SeI!8O(mCbb zq6v<-01Sz`ueVKN5M~SknQG+Rv>G~R) zrXZp3S9^vhqTI`ww{V85jFaQlG+EMv*4Ka$9=)}BRACeKarzzn{^jcRYiru2o%B|g z{i?kcETTYtQ!J@RiM#X|2zNtm(-2&_VF|WqkZXJ-sjFgROZ4>0>Od1bg3yXTa^3G{gV3KCIjGWl<1vwYS z^xdD4BM*f1TI75j>9sQ94xET9$M~-J+}|#i?!2M${t~u$ljZ$9IIcQt1=s;P3(an& zd~&tDAF=tv-tvR7h-s<)ExciyzFx#`#3Qdd=#l@J`G(Dao_C#d_^@JPG1n$dHD(AUb z3%i{>K$bS4I%#$JiDy9=Ql%U)a#yoA7xYJ?13?qFus|LO3 zVVn*NlVT8iLm*+k$wpj(Kvp&gurAzk;B8gP*YP*q9m|#ME7KriwU;xbS=G*Fp9bnZ zTt@m}E>hIQ;5~DlawR{yc@5Y!O}My){7tdm!q(PfbuoS&x62lST7=Rng4~f45uyBE zjIeiMvr}yL#x+yVAdM{2HZ%QD=3rR;6&GfHD4Zyfr>(6*)N6K-;&^hoPjPt9bo_1g z7G%q9)WU*&vU*7c<;8V44%{M3H*15T>V-&r6W)H(>Jw%gchBy))?{ob>IpGh3ucX`LwD_+}!#-@qcw>Dz5If+N z^J?=a&UKJykAepMTv!oa82g4kf|a%z@`UHNst2fP`!2<;!rH?tyj2}8WDej{=jK2W z*?W!-AYj{ggwS?AdXis?Cosq%7nmN4%T%5FV&Fo@%&fr5ixbU5$v7kcW)HYp2Ck8< z>&R+%CR@baju`t+Fot*r`+HF%G|k~r?DjS!mVe(S9Liu^XNyoebnr!6o8G~=zq@{T z5zfNQa>3>2(NyQwKDWWpYg9G}UP&ZAG4|uy6Qgh~@zI23Ais`Mx-$s8!cxzyRvwv6 z%H{=fO5%grrK1sa1Df zhBXkOFF5_PpqR!Ev#!gTB%HY;-k$~KV3%bSb)hqM(IvlD)w`ILU==M+p2);5&w^53)JUPt8X$-x4{e$ji0Z=UTkO;aIy1JLI87naXx>ND z27NmkEenbUqjbr*c&Slcn$5#%g3FA+$P%88&fnUreB!hp0pM7 zfkm+f24(+*KING`B~&o(^#S9_QzCEJ6pBk7h($Ol8suV6wP=gu*>a$oIt{Qx(hBT= z+K}m%Z=iwF5N*v9W%Aw^=^l@BBZZE8-{1U_C{~VWayOBSr;SHa2ao7+#JibKdOQkb z42xp$o@#>1IZ0JITx90ZkUg;L_QZOD&ES@W(-nWnC;eSBhS7fKK0bUYjh_$a+O(u7 ziZ4fRfd8lC!C(zAYSmh%N8PElH%~2p<0R2wW#l(q>yh`iCmy+!h4+Tx<~?!ws(mtb zfwI8R$E782*l4u^7(#dE-P+nRZkMiC!vHIfX=Qfm?j1!vpn<(BYf;?AXWp>5)*!eM zEn{EEO2-f>&!*u?v_{=l-W8VFS*y2LrN^->!W(&KNT!w0>bA2AD zizJrjNkPPt<<`S1_E|0ESCJmoy|dx!o5C*@v<7;h51FBPSvu>{+z2~Kv$^F81c+Hz zpAGIuD3t}WsueotD`cx_?5cMCM`7RV{rmm$`H&;qHi4_w(U?I?kJ6?_vWF+I2CQtV zI*NPgaS|)77VN{db;+4LVLq=aHbDh0ub3}g)n>lx9I^qf6^@ODDE5o`E7Gus{Q-6k zY5w3wj~wnllu{l5f?ozMEQcZBL6wXXo+xx{O*}X*NMVIhheH;QQJukpGDbo%pE$bP zHH{idY4%)Q?aNoe>bj(U{28sC*?{|;csu2;6L{rVi}#Y6coGXy-e$~iWxymxm-ZXZ zAzHd5HLOD<_n3_3^ZG8~`-(2iS*0S%4!h_Yksk~gC?fbW@i{duJq>f5!CJNjmXW0j zXPLsO!{&S>es^Nnb*)D-ojBeR9~fb{WL1^cX{C>Y^FNhDNLYJHf3Rb(>ThK1;-t*2fRX7N9;-PxWkTt1uQ%4CF&e)x zl>@!GQ6)oZu=J$Zd}v-C5q}AsE1uOrd6uCPSrl*RQ8XoAQ^GF2urtA`cmYXu8t_4N zNlJD5c@vdQdnPD|zERs%>K#C7(VP{wox2zlFFToROk{|8yT!82&Rl(|B;mf{-5L;m1lM1`}@*nvv?!Ee*B!7HAB_rWne3RYSKi=xjju?6MUVlGJ zKPT3OH7UE+Hle*EiHP0vhz)Y^5WEr54}a^m#Cxh2rMYSq{r-B=c$SU*b5>R`J)n4$ zfagcr-h8{8peuGZ&E4*hM~d)G=VFj|!x<#zOVJospOr)I-hmI=d|LCxCG>Y!)q7?X zUGA%9IOY59E>G;z)v07@*Z}mqy3h)za@Kgkw1|**Wd1?+Eou-!5Jx|oV2+INA*4Kn!*zm`$)`_MRd-Jh?9kIx?gbkemim7+>rX& za1zX7_EpmspnBW7;yXYt6HK2|@b2tnc4)#*PrYwP9y0c(OxX=k(^F$RASXxeiEe!$ zUb)|n=Z!_P_}OCwLM>od}}#(WOjLgugwGBAvQ(ylR({&R`dIK zfWI_@;1FJFLGeg#*)YAcdv`4D=>mEdc9Vd4iv+A1&A`1=HGHZebctU~EPX|SY8)Q$ zSUx}p>?~j8puaT+?3g}cLwAa*{T6oLsSDwlrIXys#vTU5xL}FZTcL*L&|a%QK|V?BPY-^ zgKxp3DVcf3XxLLU^Hz;m_Z&VL3h1UcV;V51_u^iC?u0Kq0Q{66heVbi48PQu(bQ%e zvNKatio+XsEx_4?#CeR1n)G9G`$Ey$nNCoOxqxD4SDC0Ueb4)bhu8ZbvwySqA8f~z z+zjg*(8fmqV_j^&B|!Q*=SppEm5<#~Jld%6L) zDVZ3{x_6A)iJliTNN_67{fF4LdDjH^pd9rBAg{<{p1P3(nu2*hHSBfI$K3|W)}{~3 z9r7o;XiBEl2g@DVPWTv2x;Pcx_)yP=uhr|lkLtRp_aLz+Zw5}^{{rpO}24ceJ=^P@|~NFKMU?2(XLi;J@s@a%2Ip z^~vV8K`0K3GiCvSb^*{~yztR*gN?+e7xtc+mlu#f^nm$35b|$1z45mFC-!6%=e6v) zJ}|CBdU|X2Y*wLedZBLBEPc`rKiOWew}XIgd5inOR^fR2^KRWHL*g7Ly#pKe@YG?y z2T^V*miv2dptgWAu9}tWk z<0tig(y#k_19k^_?EGp2^n+N5k~{y4PH6mV?6Jj8)>gNBrR|Zb( z1eMxL?+u_wOnv>4n!=)TTuxq(KNCqn;-Lzjp_^EiC|D1XIx%G1Q6cmR5cXD(yJVPGdC>oEAX$UXfH**J-`NzP6ZKiELL7c=l z;=34w=eTy7Mo$5%fj8I_AhjQ#8{xhV`gj*GJq*f#K(&{UTVqX}wDl<=q!uKaQ9ttp zD-x43B~flRxhHg?sZ(AaTi+(w(J||m%kTv-^If3N2~7h0h@*4$s3n|P(@#V+>GT;q zc0OVC$U$R&%wQCNV90h-JcX$k0%u4JX8DyGpVy!!h;*K^HmBwiO+%%GF(XK27&CMqe54T( zgSCYwJxX(00a}nLGI9xlB484#Q{i(1@=|H)TGgtIZ^61{VeE4H(xXA*3E$ z*%QPcn6;G?m<0r)XtHqdi{)bxCy?~uNVrgfVALh*W^v>! za!XM)KP|z9g@3N z6s%T_vxfw-hQ~;*YNgccEWS(DMQoEMZ0HeB6Ov=p>o>4#Web6h8vc>KSu8c=i77v; zZahoOfRqu+wphR?*C;ciEKDlp3sIJ|XS`z?%?~j}^o3wD2gnf1^J4%>viyK$jW3=i zeqx0Ro(PdYrwS_!`soWzgV`g0%e4ilmm4UT^#YLRCHKSg3O%0jK*7yhD{_jGIn9aQ zGG|!gR5t-mQKvA~PS`WjcH?D5M@`uf$@w`DR6#Q&h!ra4cZz5mV|=kN^G>|5YxWMp zX%40YLYuwdIxIPQ>7cI6wolTX!O=`j?XQQS*jBrSFQ;gT%^nQK(fF~;YLivRX@$IQ zjiEA8>PGR#(=rms8TZxY_aNp0OP)P;{>hs;*#nJQAf&P{nV-LxzP7EavbmIXi6&S1 z5RSPVv}Fiijx5+OfdAI_1}m)LK|V9E5r-q0U$YV4*&sOUfD?HOWi1p#JX5eSJdqoU zQN7%>Il@hHLcZ$s9!FxY(*L6xqG6!eO=i*3+9tAT;6>4+W5rP(Jw{5#r7exh7T=c} zXbqN4eU03MKUFw@el(mN0E5_r?54o_n$DComTF{CsbP##7JKv1dagAaWLZ`Znma)mpSs_#SP85ulPDph)Ir~6jx`6wPce>UU6%&_;v>0Y0 ztz*cRB#=iF_+ye>5?q5e);iWpD99?pn>4y@a-GdYWGhLF#JVSOY_A7$A9rY9?qM-UcGlE4Xrx4IQuQj} z;c=^t^B5<1A;YfeTW`mIz^kZBM4yY$I**+o5}ai4d}eRC`E zHP*C4$w-!;6nQ9u{kRI}Ph3w60)Yu0CnQN0sF>H}duR`JD6X00CG6qQCwPjsv&jME~& zTBVo{Eg5{?#c{yxq@(rkiK4OR*N)cMXUrz9?iIWf%BHCvEZV2!R?)7i^g=AW3W5Md zf7jSckq0ijaNLDl61q^(0%Hh8O)nxs`Ui+K0PeFy@oeDfu)Z4t- zwcDPCxX8n32gtg3#Lt4pzGGpCr%s{!DOhE@Tf#{S54SORS#4<~fnNEI# z+UOh#Z7f07Vl?9qrW9=52qC%1Ldt;3T>TUy%+>-e0h$0G;)AMvgiSoLt$%mpA}k`C z0>hgVI-P9@#Xf5~g>9Ik1DWz{AUb?caA_;nG-6La^)2o=p_@FZJKj;s^T3iJS%yS| z;w!nh#5&wU3a!q&fqy{7;T+!jlnO^&4QOYG}%4$)HeC5~{#fWu~ zv+JyO6Sch1G~HsmMlarD+(j$HzP2XDu6dG+shk{Ui&IP>u zxD2>0;DO6o60@>dlBoUsA~7N=+u{-4$wrMV*~-_9rZZS8C+g)^m*NFZxfaaGy$$>+ zqd_KKo=^wh;vCPRW->>YN#XnO#|RonfRetDX=K!UD;`efm5v|Mc)#zb2_qgq`y1+HTrc}{e3=q+td5n5&@ zX~2>-^wx_0K8ogyV$v=?Hl~8>b+_~g>;Z=H8N|~Mb%!;LI-xN01W=7YBcRqTp1)Rk z$$25t7O+_fXWJvaLu-}kgn5qW>0j8_ahL4`vx|7%Bf10l(DDvH+c&}%n0O+-!;nSN z+vUC^8Ty2~MZROHMtQGQ!0zQDZ)%TPZZ(ytPXO&ESGtvBMA=C_d+ELm(Hn&hsBb~X zXBv`UlZ%f393r;ll7&TfI@EV4$Ib^F_a41WYWO4Rj4mD}LJN{=hkv0(ztqoYkB1%e zUN&B6PxLlgV}RWC3L8o0Hrp4;K_?H4NgO-RAa*?$*567N2PNG`QQW(zA@8Z@k$N>O z!FBWundIaVppWbjF)bLX*(^Fd`gQmDB5gsYol}CuFcQ1&6%GIHdwQ6fj<*x=v)h;6%PM1!7wBVp%bo{(nwe0Fq(a=_cA zoap4NONC==sAid{W*uhCvGiD|Nw>AW{sLVa=rclJXi+u_&zEKJn#Rl#xR5xF5 z{JH=&kp8n(i=k0GJ6Es z)SmpqsX-cgk!>M)OkFr4WHsBP-G{Q0W2e-ql1?R(OC5+*2DKyH1hi`b34 z;vp`XoSKwBDX_^f+mc-_YAG`DSiNrae<<@l=@HkqnuL19?|A^KY=j_B1+Q|BPD+!G zwEub_8FRUM)@V+wO5e0#t6W>kY(MgvbzA4J!a%N1l~6%?`YY2KKK%lhV%<=$A6lO} z9YQ_?i6>wa%IPe<;+>I-7ZfO2U*EwjxaaYQX8qW@Czy7738+DGUW`^Uf?&bgv3dTM>kAtu#iPNPz82gj=$gk@Y#XUp}`J6j7hEU zza!cH`z^}%i@GTTH&KavlW{O^yu_FjR{%WWScR=dEbho`w;=(GW!R|u)Yy^XDBS>DIOD>Dy zU~$twQ@c*x55Jve{_cE#eS!I}#3#@rN{|5EDO2Xi0~I66QMMzh#q^4*vNaWQwpI&A zFJ|IOjl~mVZz#B?@C(iz1XyWtSq>)erS3Mc$wjC@1W@8GNmE)0FJk?)nY3TYt4X0uB~0?8>HPiWpoQxzE&-WoNQZ5oM=?;Gcjka45r!0j(@D`*Ih<_WpKQN38&QA! zkXfApg}7N@l*RjaP>2S@K9mmfg9l8qM$sNbqXDrW34w8D?e*)g(bnS|twtf#RX}YS zuB5|}InKAjb#YkChjnKo$It59T1lH%YN8fBryms>nrj-0A%w2VtGcB?QIxS*L> zCl%4%lN+Ue%$5XS&*`7-rek$n4S;A{_j%B}1{r|xYPo640-GMS5)5S=U5lp~MX{T( z3DxWt%o4_?$oBKH*Jd(0RuW&B9nI3H3YIJpP!%SHlR&HGH*6jbY_!e?s#M;gqV8Z| zsF7gPt)jvRc_eG8KNAvjuKk{7TzrMh%VB!yaF+w#F&B_mZly3i8QCx@#khdf^$BPQ zi&KhV%Y4Ij;U>51&Z!71HCro12(e_eUa`);$3X=BV`k{kjMhT5QLD8%8b}h?*|Vyf znvs@s?__J+cphCUQ2|GHfWlx;=={G(d&l6&qqf~Qwr$(CZ9AD{V%wb9wr!goYhpVS zPi#9o5B52GSDp8H&#v7cy1G{Nm+pV9d)@0=>-t@pj$Ao)QC*G8Zjwt_26OcoE9k7& zNKS()G*&lhkyH`#!?VdX!CpUnhKkBgtf;a!Rp2)o^>}&^o|^sCd1ybMR-E6Ukkma1 zydx|8k_Ga+*llPMAfs*1w(8L>)lnnuyfZ zKLfdxk;-89?&~yy@%?rVCQ0J zfh-QaNPO92y_O7F77bR6HqxzDkeBBaZRm;E&+Va<1t;D3PIfso0o7BsnZ2RRXWf%> z1N8Tv691NaPM%AgkiVxmg|hi-G|0=bT;?4v}0dh$W4U;?`*wl78w#=YyCR z$OkuZSHwCzK721G-)g1z(e+CNFM-Y*?wTP8AnUFPTmRGNy+!2R)nb&{;|ug3ZoaN@ zi_Xh$0;|(E*!#b>1T+43RtU0x@~2JyS4W@5zpX|aZnoM~$x($Q`9$4BXW;f|5#gEf zX!hhWaR?hNbmt{&cGA}d763AV=bb1C+M?W zv3LAXyJDz0&ZHDIQu29%ZWwpy22Q%3be)+UuQWyD3(A+R-RG`6TqWSN`9rSa>eyX; zv^UHTYHvX3NM4l~Sjy#i<2%-qagEX}RcvS@Z8GQB@E&XoEGzj%y! zuzo%B$i=OfeS8v0ce7KInsG+p(dB6wN4rB3dV5unL)2rv9^O8bIK_hb$t1VmX36u5 znBoqma}HM=$QMr7evOK?_)DmF!m$+3Siw23L=65>3r!eLb2dOL7)pWaSVT`s6z)e! z4tX4=tNmz!G({pkVHN?%_a20^P4v7agQegon$nBC-!pVBbs#apBz%#zh=@A#@GVa{ ze#8TfI@{2K@<=BFJPQOpg32VDJUv^vxkJ;DI&>#n6^VU<2Z!@+ll?idf-y3nz`iR% zmab1RMiSpxiDu8Dr0v{lQ(XR9w z0+`nE!T{nixG%xMG(eAFFA!Hh!|vh=Gi(b&$)uI-V8Fe88-V zGK?9=dt^Q&s1saFA9*aXsq%wulq2dR=#g3zOtTntnigQ(HhaQI_|%l0mE9W-3;p5Q z##0dg@r)EWM8o;NSqN(X@LI}(f}sIHL47|iAY#7nxBurw{{6|=-j>na-rn5Cl+ne| z-1c9XQpW$;MY8>?d#q9aVUPNaK|sx-bA*Gobdc7C0TaCda{z9e<&n3N0|EX{7?&#P ziRoT2o%7;ck1O{*jyo0AEkW_MD5>1Ly6-;C(57@E!E>l6x!KC;aK7w5%FXb7nW^gq zZV#~yfX&I*bqwsgQ6o=P(0rX&HZ*0uF>b4hREHJ$dE2p>g0A?gVA}nm=V}Ct9k4NK zU$gO8dJ551S+W=Nlk&y{FBn4s>cE?UldZb%#n_Q98!i1*s;&rmG1aB8#@`y`kIKS9 z%22JQVZ4qrQhZsedr8{wRrq&}YB!A!`ylU#vo)IP6v%TK*qS#t+QS|N`Z&B1a2imf za;k5Ko7cErgb{r3esvfVT_xGB1$Jh&8%WGY}utK zU8W=22%*eTd}pyh3PXUzcfR$k6P@+r)fOzbOfJvrul;w!Xop8Wi5f6BA9~{Nsi)Xr z50$*lWeaO_?vmL$A8}l^1?+0ufG9MRh;#IOWaN)QqmLi8YTS!_rR%_In zSGu-^-2P?|r1c!V<7lsNBev`frq6(1@_F2ap$8AU|BPnv7-o zM{~5hITk`4H?zsp*#iC+sQwNl4x!545G&s=)ha4Dn~TJBoR;n6ZoXBW*YQ=2h)FPb zUfYtiK7w@#&g0E)Vm8G#uDRLu1yz1U(^|T%W?)Nhw|?V^_pR2Le&xnA``bR~w^8)Q zVe=6uC901_AJo2wbC~)Oiz!xn#ixAJ2OH*Ul{=F9Wd0UR?NwAwlP6#FLdD~(B(G3FWsJA23Z zWc_`ny)(?3^!b%%O2%s1!iGKMEM#e!MMI88 z*27;t=@NxtZXD~3Ig`#Q$gQC55eN|xKrnCpry5!PeR}4SkSbh|neua>&?BNwt|RN- z3VUJzBTqZJ;k~Tc%rcJXBWHwc+Tkz0K-ml4UoKv++kdiV6t)9~!oR0ADD?l`)Mown z)YkZ+imQ(0ry$kn6O58T(yyWujx``zUA_pR9GUNf0SzA2;wr5$xYsyg!y(skZSJu@ z8qKP1o_#ZdjSb0&R|h!WzUhpeS%W7F4wXqTd;%PuzX@DDdEY+gZGQn>^AJF}m|t9| zZMEK&%5`}8>9*YiTvR5+C)is}eZ+#0!tGAFyv7%6d`_1~QYSlcPQfG;FmrzYP5s;2 zP0Kp}qtQLG^m08mG+wB9h^6``c&EbfoD$<~>e5uTNIb09asw2^ z--S=`H6agUaZuh)J&3M!9u49{yDaCa+(GEd zk|6%XIqNiQ1ymLeT&EjiU$*yk53zd8sEU46Ap@di;i2+*1Kj|{iG->2c5Iq;Y5ib!hciymKNY+;$~|k!sfK*VZk#{p9@Q^TVPI;Ke=llq0BE| zp(Im!$D0XZLm?M$!sRVnwof-V zhEk|ekD#EpDz2AOck{?U)k!*lo=^Z{m;*k)&f_7LWq51MBV>E!5Uuknuw@rd??ubU zCA1w~s&-mJokHw-!XhXDQCT1>d6?pZZy+^xCvr~Ba>*ofy4cW)h7bDc8*Bm=184#! zzviTSBdRu|e=fH<$C96Th#stGz($kzp+Qd)FxE6Q0jWz6^XCc(D5~W&BD@3l11Bco z4*Q6cfJ00xjo@WfzEH>{N&l>+@i=1T)5(DeP$-#Ek=-4b@mrTU7KGROTDgN^98eoew{YAWB=2S+dEzF-C^W=1E=lT?bc z46e6(j2IXarNmZg2jg!oX&IBUJUu_o8zQ(1;?wDnq2ew*fa zPHEildrxj$;7gX+Yq*1{c&pk&noK4L!94UpkXO(1T}1h#(K^CuAC3_~S8zHt`Ct$|&(fFESgf;5_ z-nAvy($~~@^b%oK-3OvYVWNrE3)#>mD*atMaa(1uGoQLl_806k*pEUq+6&rU8yYe& z;p?J@2>~h0nlzpH%e$S;YW}n_!)O3}z&tuzxABl|xDiV36p8D~&_SHN=_E?i#E|F- zAHC~q8B=$cJzSEYTUibLLp~MZk=CJ`0;9dw#+>z}H+jn4sG%Zf9b8o-mO4g(iFemG z_>^P?-`mM6k$7%eR5af39ZZic7^|>`tB8P3bi2TWEf`xwwUlj%&A7V)B~5*lql)`a zs*F?$t;dX>CLt^Qutz)AXbXw~_vi0J8fvuS{? zMk95m!I4JL!rE8w(K`=*8+GWTMG^YYjNyZ%`S~706vhRs4~OODLV7{nxk+8 zTL#~7FXf8obr9-jd;#qe-BAowXMzcoR{|D9oW%;CI829Ha*29q#-d=0cjtGmaTX8` z3tB(Qyb^n!DRU(9W-L*ZGQI2*o4N-?>vRqJWjeqpB5g}doiRfNV6jL$U@1Ld zx)86&j<+s3{wc1<0~z}P z5xa{_;hT?-`36Bzi1=BpCln7!W;Fw5ZVR1aXri69mh@6Hr zg03fOK=m(T;m^_(_QUV}ne=~TK>YW;|GzPX-5S#0SWmQ19-)z7F_OI=e=IgSo}F5v zNMZ{%sT5^~+)Qa)9ZJ?kxiFGXVn%(waCHJYfX8+3GbWwEj99X1e58fS!o=BAPuu+p z|J}pes3H)9VQRsOleO8BYgH=~dfjsSVATL?-XcD^H3_eoKeG>Q_rb(wbB8MX5r#gD zY)o2f+k~^V@}z@ohdwE;-1u!vI=m(&NpvpMc8zzcM`>{meKnn983_%m>!`}S>+NQ1 z5g?>Njyt7sPeup#T6)+newNd^v5?G?(X-Y`e!>+kE%q8})Ms4_z_}@1sbD#g@w* zADA2|W(ZCq0jYT z*;2WP8fn`i?Z6${auXk z+MxG7J4xv?h{hNt4X%lxO4Bn)!jh)q36V=Blh+-1tvtO}+k@;;6P` z_LU@*DcgurT*zbEdSKnZK@pJ2C%RET&qqN%%=I$h32~yhI3eys#VV`E?RxM9B#{Y< z-|A-z|Khv(r&js#LMHO_yQe4nk7-f=Nm8*f6?ZeVaW!=LrjMz*m^%F94g-)`PwU72>`Ad65DVta}0Zk@&_x$tG>j8+`wON$ycV zi8Qz(^0J=4rMKl7ToD4*&XVd$t_>(Q(kbuZDqvJ|Cv5&|l#!s-NR`h#nf>ilXwc-H zRjK8KD0V#QIoxBE^BmXIAvQ9Iuxmqm(|CYn$KfH9u*R2Y{@r%WZ6vzV-K?ILtKl@N z5a3Mj{%Q>49~d)$3DzezEA}k)BZLuY6Rg?O8hsRH?%xS^&6yz{R zD96dz2!RMS#U#&$n+3aTICbt8HNX4?by>-;YY2{8HXhMNsUs_(riZ4&=8tnqrC6h| zDv29hS58j{todE$t2UDxgY;qGNJq~o&U9DLiJxuCZ^8z zu1?0L|8G#&l4h6%$!WFPq%FW=&B2-w86!)R{>d&7~J)HK5(t&dIexH6! zPd#tnhj5!s?^hj9{ixTVDKHN(EscQ~fx; zZmTQ#$gB?nGhIWvvpWwfn3L54ignP|$$g7u{6Big49DNyA~K5DWlV6q(UMChG6A1D z-5f0C383Rcu`K8hLK@Odbp;xTR>_ktdYp_IFnbA`n?Ug1%pL1-Oz0!jgY(shs@n|u zOfgZSd(CFQflCBb*VPYm{CB4r;Tdi(nEho8V?1_n6f=c^zx+`OEz77??z7p298 z>K5&1;LSbrGGDH#muL5xu!@+-q=Df0wipu9nOwUeX|@2Q>+}d<&SEm00q|2u9M#4+ zL)79$$Z~Gvk(Mfe31(f@BpVZ>?Rzr5Z2JMFs(E~9@PnWsFleNx8qMMm%jP_i{JGW+nl`mH#R8V*J<(3B>U;fScR z)AWxNwq}{h={a|nJ_O-ekb15;Bw+xt1&1=z$4Y@y9jBM)lQ zysx=2LIA;9V9uG38IEPCC>V^h%yoHLld}vZ?7 zW01S3sWFwAp^2;MrHJL*5Emr2XPGLoDlIO}*FUSe`F5Ny;h2ewtm1aF_xnek#w3UX z3H;7tfXqlWd(av*j_$s*Xh_ln!`uk8y2Hi%MK$&VweTG+p4NIEiq;87*~F%t@g4}d zWb!r^`0HY38}$T!R3W$*lYh=nOv zcuL@PnM;7nOvtso6yi+8?DY7loKG~(4>3&H6u(##=uE~k@W7a@-^C1_!6VW?Gzubv z8PI@wGa*VdfXp+3VbC-M31s7;wZ##=Dj0js?# z2;-fK_gl-JDLtZ8_^Ak4;GHW_16yVB&ZbYJDC?C5OkBKJU9z=HdUOv}+tBlj_-ZnJ zGe*`5l2jpdcB?_kpQud*+m#a^l|=Y+E=h5ZkZ8ei9>yOI%?WxI0l zUP#nsvNTKfWuWGRQcN2(LtxrbTF3E9vvf~~WRX17MP{f!q4jat>}4}+$FV@?wsh7B zrx3XFtx|}78Kmp4m}WTRvvm>Ol6J`J=HQBh)fM0%QiLqrfrL}#3bjMTvxi%ob&>`d zl<8{YQ^qC$Dl(dV$q$8f>0V=*6Qv2uW=M>P>S1wPr+wdW0y7>4jNjD@*Q5$wYC3Mo z^E0YVfbUfGKXxz?h7$rB~ z*J+P31Hx6$)O?_fY#5GWcG z0gKT%Xk?vMzc`KTB2Q_F6YMnVqeVR`Frb8aSaAr53mhQwD^+*5sy=0&;~;+p$$^Vt z>LCO(1uXNUrJy3}K6BW=r1~Z2fq}R2N6k;aAcS$xtz-nrl9Whjo?Kha&nJ`@D#gv& zTY4{DtC!d)yE0yrtE+lX(4Q4>N$tnJ8Qt9X0p}^dhu)-s)fO^doiIP*Zf;Q>eo9HZ z%n3&K|02mTdwU@7+zxWj9e%xyr<@SZ8=NQQ2a;g7S~ZCM6<~pLl3!P>#6HtoLbmjV zLkELm?t$wHi=gKsC8B_UZ#I666I;?Cg2S4qBClpDKA(Lxbem(GfVz5U((h&?BlEYs zvwAGijEgrPKA4Ri=)4Lf%C$EtXWS^8&f94?JJ5oeu^JAQIEqUhSl#$1w8?`q(}3mb zMoLxv44tu42v6|JfiWz+f6Gk#ng87+b+!!t9r4($j7dkM{j?qZJlQ3wGW5VwE0r~c ztMeQ%9i=O9jnkl93^$=@lS95%u06`VssBZwU8`|R`{`jPNbd?E71a-n<)tBqg}7%h z%bSAwj81gsZ6^7PTCz(84b6<1K@ToBNrC_;=n3`qiyNy{4j?ORKA6_Ipnf2kJmT@|Tp`xt70*ia zYVD2iY!Q7vPanT~1&}mNf?Z~o-bxvL4A%f}rnWk_j0l9#l~L2-Wfn=u*mTMR-GVCG z8WzmF$s|X-zpB@8#!?3iXY;kgdL$>M<18pi0Zgq-LANYMGJ93oUG2`PocKPON%Jo* z)$L<|2j?4X7R|dCxzbC7H%Z5o@RIEQ1;_FoY^2M(F!7?%& zK|nKC_jR|Oy^6cJyq%#MQkMJ{NFiOrcV)vYZXgKLIVcIjfpi=pyS(UWfYU|U?VSh> z!68ZQ*(ca;W!JRX$%(u z2OyLsw;@ibaKS!?+bF)Nu4f~d(qh=-n50{#c*7`6Aw6|(t&cKyGCf1mmH-%;w+Ao# z=y_|n5=Qpq+_{V$r=SlQPoj#Nn%m{BXH1A0MrE#_<*Rtz^F#m`@?c@ArTDI>s5)k> z+K}k@aZ*eJdo*QEAzE39;DCzv^tv#f^c@O)M)fXi7~GIF6_22Tr8=atTMGKuJq}~? zHc`$fzRvU*h>e`kVHzIPP20&wYtN|)sVnCAl2w3*>y4Rl;2Y3s!p17qn>}3VHc7=g zv-q|>`_TSowvB~3jPLBuhTUp!;YSsQaZe7?S}Zw@Q(AwdsQp)LwQ?f=NHrpJOpIU< z+A<&A%hR}Xe%lruxoTkPyOeUJmEs*S_v)wf({rvDp^7z#`~@YrlxwVGR60#e*@2@rnbP!-`~jf? zC`q?&5)&e;Z63?H~6PKQy+!k=_7KL zG^cuCiqnbUUYhEyl^bPlgNvp)n>lEgwF8lNsJ|QZl0JpRedD%m;z3hJ(jfBWpfocG zS>gKAfqaPPTa#{!8Jm`FYndPTp+;(P zNrUUL#dH#710C|{!h3w$=K#=cTrSxl591#4tO(|t zkb1VPQ|?&1ak(saD8@txwDuw%aXHi~LSHbOa4EIIkvN)@eu<@;oP4G*4%$uMm6F;# z8`)8Xx5JeVo0``$JKoXQ)EUB%m$f0%y9txDmMm;vsHWuyRTIQmj2T*y8{@;K_i)}E z^g5g3f^O*o8>3+yaZm72T6Kbh^$zrRz6Gssb%HIhc2GA)zeKK+fIo~)@Q)CuQ7)OA zVJ%8KBjV=SiP@wZN;h;yZy{J^5xono7ssrgR9k`hQCr{Ne%>IfAn}&|!gU}TY!KIj z5xFd;QU1las}yvon1eU!xVBkoK#I&WFkVmFJjf!PDIk$N~gh7S&jn%0X5Cum>1~RnwNH7Xs`UKk8c>S zgMAoY6t;pqmcND+^1G4`PUUr&L@nNRr^Q^8kN71j z)Mcx6WEKHNNX12j&Tv&YHM-4ytcg2wdd0KWnc#ax@;(R9G?A>Pv@1h2wYtiWLxo{~ z&^mD7U580Rfyp_UzAuhpTfC5RKg6Wyy4X0`cd<b@cjKcuC74#+d8XVfMga8 zkvXq*YHShB($Nw>*11MN53sw0#xxSC7*ejD%xe@$PVZq|( zMmA#KsfSy0(ab>+BnD&scZfvZXc9qTT@2RPqU82mW!#xj6w(b}81izd1tD{TqPr!I zMNaqe@7BBz$54!6%VvO`cL_4?rshcK9}ueVY>frh#k4nIrNj#G1V4IPk?QkO8*onP z#nALLhkdZw?BSD5!kexTitBGH2p7;6aAMk#xW~&T6r(pOz(2a@T*GBW%Y{?Ng;Uqr zNBAvq15p4m{aRU^Zh z`kfg4RuIVKtC;o0Is2iv7zStwWM^63KLqy$w-cS`&rFylY^j$76TaA6Z2g?~fN)+o z*{cZG+P6wyE%n!EF3UVpK#zEyi|F-Mra3L*6QF@N`V5 zpbEX|G6MTW*Z8cOFkukMCqQ-^B8wKT#w>FXIk$(_T(T$T*vX}x9A)H~N`)-oSb1&i z(ve>3aPB2T`<@8_^t-2&`k6mko+5)C2PAv>m^ivzkQ-}#lIgc5+q(f-Pbn*t8(MQ% zd7Ky1YHTQ|33O0lc->F=>uqEK`?(E?01?cL--0~exaO@6%lhNgGi#EdHDqHFi6*+- zC?%1@pL64P!RJ`Dd!v1yY}#fK*x{WS$uwqj4$8>GJ|%W8qE6$AQKdSl$nq#Ls*SqX zSt=ud^Blg{-E@z>2Jm8 zSU|DX(_rT&TKw5~jpdr9Zm`bzVkv*i3DrrZ##VA7M}}Ke(^lS;T6L4;t+Pb}WPb5wSR(+=fwY%Uyko7c!ioU(76HLhR25;DL1QVt`np(rfk6jni1S8Z9yv+9#!fg ztP)B=VvQ?r`G#f4f~A~XWnI~VgSJ?%xCc#5s$vZM)=9rWu9G7$@;IYByls9GdPiUM z?4`^hl+CJ@4>3Gyj6p-Cv^{tvN?naXV}W~5g-20OVrmbj6Y5GM%5{9_DE@+p!qp<# zo{Sg8$3J5~Y>XDRcNG)oV$q^`JyyF&mMw3wk?xsZ|`)P5hN; zTZ*EMt${ePsPYf0O4f=znOZWk6|EG#2&r11yUFD=r%Bcgkf=+wsM^Uw+O}i6KJ7DQ zarXl8w)sL`+c|#7&QY`wns^DL+Q>Z9QL{HxQFh?R z1ez}TQQuh>ciJw9I=6PM$97G}96Bz9+PC`k$NCM&pC|>WgCw`Xv?7$RV#i{F6Aa%` zQu}c7m6S96)n2#vv}#uxt{kcS(2g8HD`?pgc^2**99*1Q*|>Dm@MaxtC@y4n;8zzw z-Mm0vZ3`xjl=MjMb1xQYk@7Ae*!0?jj#VK1ZsMTH((WRasoOgHW6{*P!4iUB7$rgFFSorkqAz<~C+JT}=}+Ml|flOz5R-!ovu)rili{PUo2o}VnbEQ}H-2E9*{%L|t^rVXzG%1+FFdL>D!nLWVg(aPf~5yJE3d4v=YI0mNmj?K z)$MSO;LzdeSKuJP> zy3{zskEOrfp-0J6^$eGEAs0iNg4L#ANN^gl>@Tc?t;33G&rqAY$9?cRX%R}M9xoxC zMuUc&XSs2XxlVMD$c8w$cQi+22F!YhNVG3q(JR6~)NG>CP;a7dH5=qVs@eaz4ftPN z0Q_InY%*~V2SYm(Qxg?e8`J;oP+>}A#(6^w%lB-^@%%fM*}&Ek);1GjgWfSQm93vR zKD0Qw(=xM}+LF4yHAG1=6-#?Mn78-WbRRH+gTJV^zbNZMVnGHlg5Egc@O90BPGmJU zp5yM%6JLAx?VgXvjg+s~w_ST6iff8Nld;9wA{jB`Yy^5n;hOYc`{!lRy%w(=cIHvz0hbvxv6U$L^crf zDvPk`?U66!6KUIf;XQHS4DY3Rh)cn9uDQw@EwQNoXz+#eXyF@Fb)_VzoN+xcyLQLGep7J=~BgV4sV|kkD&Cd?z^A50anWuum8pItYP> z+ORh$H^HG#m>U`rpN(NRK1e~9>pb6_9M5r9JJjvWHU&@N!nBAlMQZKj=&zHiCzIMj z!IJ>$mfX`Ne9W$=3=ej(yd38H5r19>@h&)EHAH)oxxNjnR5Z&CWlpUg&ap6ur(mKa zfKU@sl=zRH>lZO-j+tmjn|3+_aN}&b@~DgI*!-AN^g6nt>1o8~HYO=S;mXtS;8}V7*WCNno`&5#!1-PiRcen$w6x@oSc3exc>V|S8lEwhr7e>BpUTSP z&B~1A&qvQ9o3tS1y>%Q;b^S$)uBbiISyd%h&k7CdakCuh>UEi@N~mJkPKK;IoH~ua zG_eUuZm;(gv$PD9Oq=;sfdT^`Jsy3Sh_8DeJ>O70f#ZlgBA?%wgpb3LZo|@E^X#&d z@;r`SIRQml86FC8-#mc0s~kA5(Ifak@NOo1d8VJMEfzBZpbYUm{kpyL8v8< zb0)V&e~%eBG6bHjdB;FQ=8U`sgG}v{<9JPM3C}&EcV4qI!v-XtFePrsAG8h8LCBa5 zO8H49ZlgLAFhWWxw32uN*PSQ62ax?DJ2c9lP&L%sA|C4+LIe4s5aRfPD*Y)pSt68H z^gT>EMsJk*Jsj-#;!pOA_|PeTqJfElF!2C^c_M&e=6je|`crrky?T+{UWL(-WYj%g z50g?V8BngQ3{28H&@C02u7Z}iD$yEaec3#nK=6cjf{^vX3f}3ve2OZ+psN)=f8p#q zam2lTjW=ASRp;$y!cj4L=JrLkmH&kF2aS2iQhaYlH(LX(jD2WMz8CEy{`CEy0ZT5< z5l*zRc32i zZqHSKoFy|fz8D5(lRdsRj?ZuZNM&js$hoA=I4Mp*6iPsP#EzeJ#*YpHZk~UgRBQc- zG39AYV^1~}tj`96HUdpBiZT_Ym>#lM9xR$$+Ys>!yKj!aX!5)`VZPfei`6@6%-Ab( zr*g76VQd;#Bx$YBkUwE2aoz|q|EcI`yGE?eNF3i*rY6+gfgW z=Ky+~Ubnn)sVT;j)HBy%%-U4x)3i)^+`}|YJGe47{HoAf-6Zx*=>mjci3m^@)i-V> zNj}qD&X>WK2}I_tlK;_*7i}|iip9&JE6^=Cuw1k*5FwzI>KLuuKv*olJ&+*)0=*j9 zq6AU6mh>$4(lak|GElRZR=FL--GW z*{UDWnhF`>Pi}KJmjdOy-?DjpWJWCgvc{1k53_&7iDlg&EkX0Au~ruce`>M2w{+Y( zQhR{$80V=nMUb+Pcc+Q^99cPJ8{##$)LJzAFcA>SgR2ho6}e!{QH8L`jP;MQz{Ey2 z(1kGIpeHzo5~?!Mf9K&;4bQ_N(&Exnry~~~ofvUN(V{X?aS>DS)$WiH{H)bayk3$w zP||dYvon43))DzLpg{qVu{tv3M&ZWPkQ6~o5t^|&JY+|+1{>6Da`%8;RLN#sO4qG5 zzA#v5$d^@2!ImI@e4@=iBm2TkK_-^!uKzm)QioD*BAw3kFdA@N!F$Ocf&23KrReIc z^uibRIl+C-?9_g`;b_McT>L=)r1&CKz64Ie9 z#`tRpOIIk=%j@GD0{*?ZF1}ZsxZ2Kpy8&zdSDv^Yzl@$tvL*CHdysikE6uszmbTEi zE%bS$kJxNAac#ajg7%PXEdfC_nd`s%Ipl5keukAoaAD0anjyGZTEzz;%cpQ-b;-Ox zctn{ftPbF>f;zZRKE)JtK`l8prz!DEzCp1&uql)$6m>zfJ2vMj`4@o(XL5IjDa99g zLyvi6U1M8?)YKc(p0C32whqMp>*J}LjVo_!C%*0(M4Kx4`BM_^Ls=HJtl~im&%EaS9?--jF>$AXG!seN#B|#Fd$}gmca(?v7uFSr*8>y+IDSBQa4VECvbpFHm;$Q}bha@U zqeH^3X8A1N=XJy+UvRy`!)4OiefoUgD&n|>-|F&yj9V9F~p)7d-=Gm z$6@85oARVnrRFyi@D#nj$?YL>hr7#5{2K71o5Z8PRv#H)p;-r|-&myy#<;OW8>o5y z{k1B97xO)iKIIpU`x9>C9q>~HGNDRWf(@8O3f$ughm>>B{R*v=t3Wu%$nx_0KaDJ5 zl@DpN-_g5-sQ+on5c)q{GXCA%{%^f(ji$9Gsw9>l1u-48Ffcf{jy3f<(vZInxU_I| zelikczY=2&!x#=`Q`d=E&}`O)U?sn}HGf&zw%7XCVy-|E9WitR`8;A$j^|O%QTGh* z%iWcMAGia=HmshUt^}F2_F6WE<6?C|rjg^+lzSm-FTvsS{XG9yOM;K#yzEJ&S@=EKWEqBzlfbg_|w>MRY@^L7|oGc;J0B8;Ae z$ghTu6T>i?cL}r_fs4LB;Q9w`RY3ZdLtcU1e1(86X5)eHXv4e%= znD&MSXGtlH031;;s_=cmHX`BxT^)Xpwbd>&y@msgzw+1v!d8kNGH|mYU-*-QUhNFT zjR3^obJ75eqXq>*4$QFi>NH?wR=W=Ef>Kf*fZ!UW)nJ7`8{-4=G4$|RU2(;3m#|Ot ziJKVnAX2@_XqHAgGo>FjICw6Hse}CI`NDN<_2FiH4PTUU+TUe#oZ6~TqC+M^AtX(3 z1@`L8OfQNs!;A!RTUB7^CS&3Ry{Yw@9vXE53OG~<-B0L|0Z z)FpM4A=gc)_$P|t<<)$mF^74TfTWm#jx4kx+v4?xX!L-95rkAsP2qlg7~M4YWhe12 zwep4p`OOkl_=~QvH{i`-b2bWtVrEqM=(QqG#A~I@vSpXeMj~)aiU-nwu{H%6;9g=@ z-U6KUN4@2gzP&FGM;I`e^=Wkv(&i2Gw`v4aU)*;p^6p)jTxj}pRk^r7SZi%G+RyJ$ z-6=pM+ptS*Go7bf7mXp^;W&9T?l`>u3fJpcTMOV6ir}j(tC@Vb#&jdy_(l-q57iYs z;#q5pC`~MxHc1*fQzdG;nwrk*jqeFL+2AQuI8~V!DqhxWT4t9LlJm+7ipL=C_)@8) z`4i#}{U(MZPF4l^=&Q$k=Vd#`sq&~GS$dvKee4^?roPcZ7Y%IY&Ig*i(HH4>22Lpf zX{5{^ z7|et2+vN9A+IX00C#zVVl#X*LT#{gjz4*I%Of0uBX?$AHUrNB2^!A;7P8?Aad^F;} zxr8V0fW1?av1;4nf6YRjM870pwm*`hFgl!}3q0&_-oHoBq)}O%C(DLYNc~YLdDLpQhiiw;1gB0Z> zKEh0vii83k9dQ;Onbl5Wx2t>GEczZ&9vD5aMPDCES*y)jTb6ts4kz>-SQn`eNAq=XMdr`@ z;-=!9Uum2V;;W;7QBqOVJ`yC)#%vHNS7gtxZ!rfF>d%CJKGt^?rbYNqRro*LPyGK> z6aGE;ujaj5V?q^A9Pbkc1A_vUfHoe9Fu>o@J%CP8S%|GV|2qd2BvN_59xkhYEyL9< zRcMh$Q6Jzj7bH1DkVq3?QAua7@gk}1$IshE*Ccx*bz<20>*MV5>gl!9`}6bYNe~Eb zKcG))e41>&HHYINxT=ITv!vu3pD#PJ=1Ez?*cH0Q+m*9a?dscI4krrLY-6RXGq70g zvUj*%n(H)Jj&4D}?IF*AER{VXwngUC-Iub@88SzdpL&IcB^XlE0Z4Iz-;lAQXqbj> zR?jjl?tccSn2vAB02tPuDPT>3V8*7S9rFXcCej9UbjEyHVW>YK7m!_v zt(K>`Z5>QrTCsqXhLsq2QC(J7%RrObdK_4q@AQ}|7_XbeNRDRCLI~!=ZoH^VXcuu% zgi#PANAy68IokB2~^7df!61L(!Za&5mHOQnyx?f*%PZ|s7 zpD=l0acbGhPFx?VM0oyTi<)kc2zoaR)$7`!xT?rMDv>>J4K(#c8qjB;?8LbSuE;lS z>TF?Pa&VbU`RU?|PACoFrP=Cp4Y_|O^H(Onl!@*_*=J3yuc|`IbFr2mMoDxE_4~s; zcq0fu#vr?$(Uy53yaqRBro}<#SC`>X!^tL}r;ZhtK@$h#Ht@)A1sZ~=@c9zM73 z>$5;D73L|C*!tUKoq9cdPWdEniz zFMwUlxj`o>*i>h{jlMma;<0Tk=8hcqBskLY18xM;jTz4#@xtgHd1Y}AwzVka0c&u= zpEAb#vE&|xe#K+Hs#*I?8nqcGP_$z(54TKBZ(I6D>@E2ss1Yu41E@NmP}Va_&K|8E zhQV7x`@Ii}>L&Ha<>M^&6l!yIq%Ytz9&}fDN!H;8UUy5#FC99zc&zieE?n^b+Jw$Z z_+-?n{zZ4&EVl@o%s1;V(coy1&*-E4q{bTG>qJ9VuQ_j6o6nQ^>cl3yg)zq|jf_?2QKMn^!Cy4Z!c0jL+DqrlIN2 zKX?*vFPi-zg&?BBc#Tbtw$mJD+FY#P4vtr@zfn~=z48;4$@=qTDfKN6acqwmJb9K_1*$ZWz^ZPS^JmutQClH-hczzXN~g6YkU<45;PPRfg*8X)eN z^uq&$fQ&G6=wsXSDs!xqD&m^Z8h^Ad(}h>&=Is+jIgTc($~D&Sn#v=oBst|k*Fmmu zaff;#{M3Mn>E2x~K{-ICKkxwn6GkR;{R;VZ^l|*+2=$JjAHazyBeFG%q$FQtjg)ge z@XJMd2}xkw0RD6o)z+$36JM&!u=mVbs^2L_E@-Mlzc+>0G~OMtnkJ>QXItTD=630^ zCubv?N}D7ju>~~PHR}7?Sjw&$oYu%^`Q6(=|HAW2I63EoqJeBYWu7=b&@{cON)Rid zN;-SlMtqL_L@S$Ql5IF|@yBZIpHv2-wrz>Ct5UrOqfQGb2Cl~63ac1}KdZ`r-VRt_ zQ^#tWKD$riIi&BD{Q^4`&&4enNU;m>R23}XUU8VJ;DXH1>RyI!**_V! z@{@WkZP{0vxiVWbgsjH7jOQDPS9UIhLI#@{bc&>8;o=6lGM*bp zi5D@nj zvWjM?dCSq!6~Ur(zdYB!L^hlcF0Mt|lT0R%D!U77AUn?r+Yrqo?Que1Uyu`L;d@z= z=As`3F2&^go4k{6LP)4pt|idVqdU)oxNss4iLH-B3aw=}7@J>qrzZ5)?K8W83IY_M_b(+h2x7rVfxN z+xIZNmS|=QbL-p7Ih5UChh5A%z^&yqA2}|rD}oWNv;4Hb-z;IxIsPb~(S5%q^<`JW zs8*c{R$!fKpGHxwVpS5Xa?o7cjXqsd?*485x;RfL$!y-)e1V~?${3YjevXJfa0BF| ztL(T`N@>vn=em>xuaD0Dd8cd_xVKEtXehvI+f-h_)RGTwxB{G6rXK z-@E@CXykWPS$MgJRzKc==QT2fd@um+5Z&!Hza-lTox8#zbOiTN<*u1~)COWO`taA@ zkAX#Us!0iSeRT86jbhR`h$+Hg@(WQ9Wv*AnBr#cGYUCgy@{+N>sy7vcH@ z^Gc8D?aMcsmXt^FiuS~_{muI!vtkNH!6(pj452m^&N5OJ7O>IYL8xX}zm9gt+OO7m2X1uHXD z3$TY!wu+R7WOm91Wtr%`8Fo%@J(9`WF|5rJW!X@M7gsy*ZbS#3GdX9^(OIl}NfS-f zShshmcDONE|Kf?p!n-T2#kmUR%z~Lce;3kjz@Wq!H^`b z-24rug&Z{)Doeti{TW|9pY6&$O4}MNU6=sDBJTv^bBiVjuFn5AULQEF#`DTVRpR$V2ht)0MzGMQemy(Sn6j$16 z*VADr;TEYOFigj-_%LYuK>OFNuR!dD3IBcTFa0eknd1NM==m4HGDu1uvY!rl!eL>G zoXrNW-X=naANd{rdkDsf=S*a!pMyIS7pGS+%<1#l1P=lNe`e(aK+g{Vf5M75u}PxA zw6MG|F5IHd#H7Y)dZc)EjSCQAUSYd%cAl(`Gbxi50kHqsh5bzmeBmnx!9DPB_Q)wO zjZPy?OH&oS>G#pEnZUha^y(B1juD!y+yHo8;LtATS>YXz&{2!|c+Yc1%$|Sf5?YBL zX5@eWufy;6f8XE#8#%3iReOrxB0u^jM&eesmjCMJt3&B2Ev0>a(v4+Ffd&SK6aWH= zOmc>YW5P?}?d}rgK?o6=Go-bNgOWHLa3Ri`UrscyTWdx%@kq2dCRtffYKZ&G8>mxP zbyzFs}yZnb!=Q&<2q|ETyJ`qFd!KVRPx!+a3Ax2z2NSAv8AAGyWL^@u=ow+ zKb}jkwqt|-fzZW+aDodh1oYuv*{vOeHKrzaErw1n^9&k7fGJdKE33yQhYKO}cD~{M zbkY|X?B@TvDaC>{CxnN+Wbm7D)r<@|GwmRfV@OAi+azy%#oV^L0tU_87J_D`OP|b` zDDt7XadjmS35eRA03MPE5OdT2uFZ0#mamo#4X)1!1G<&Ho)-aj*i2+Y@6e3#eAZxK z&(!CIX+e1HF?+%q#t6_PJZLw6ZK{b9Av%%u8VgcL(B1TPx5$5~WT$jbs^9R)x^H|z zPiSDxpV8P=%9j7iiu2d2ts1Uboz8?}jYz0{_2_xdg*IlVe|%(>fBELyy9iz*JABv! zsK3WV3uu3wVdQaU#tKf_1Lic6=_5(seDv`25%t#P)K9C9byijvmkaZ=?R^Wh#MctR z3POZ}m1QVY_0EnGH%@tCHTr-tm7Jcorn;gw4^fYMm7OO4{Zy+FYf|&mSAHX7FI|D% z+XY&BLTrY;d{Lx7kBG7kaK&AFP;p~=_dMLGg`~h1RHyNdwd?3o!@taJ!g~kR<#cOU zijYgt-b+~FT7ZxEn(^VLumPPvM6%@@@v&h@Ss>3S?l5AV# zSS4Ymuv=E{SrCGUZ=mho1V)Hd;|qP#ha;v!>Us1&LVN z8Bo(GgxH|4Ap-5ODeB=s{T}se3_+Cw$R|oYy=UBaCs?@hzo$If7d9%0unR*hOLB!O zZt)-_k{qjLHiou_78IijxSumCI4absh*n9wzx5C@`PU^f4t zyS?@J{&t@s*WTq)mm?9bmx$EUdO0ToGF90qh=Y;hR*=pEz5hWN-z2af7Eo&^umyZ^ z5$9s%skHTN@m)kq%~#u_?rCAIAsEyxDM z+cT~b4QJc)n~fG3SS{)nxV6AD60H%6VR#Gd)}HXV3jSVf6>s$_%$|vD(mmuDp@SyR z%jPr7syfHRBR6(vSG}ab_nI5d&aBQXukTUPpT_)C*uOZHZ@#%?&xZ8lx8&)gp~aFK zA6g7T=@Eb%CASzL6*COzj6efuKP5;?Q*qE2Z`d(oKqbKNSIm49gD!q1p5l0J4^=wQ zMi>b{rh}ZnX|egfnQbx|7Krp)2uaU!+~Og8=kty5FGZ1ZR_i&lSO#^ypUs8@g4MJg zw%{;;s#fsjf+AkdV_v;OYo?CO} zjqd_r8$p$g{1Tb0I8x>b%_w(Ev+gW!{Vjta6FIj|XpP-a`9U-hFl5}!EnAoUyxyL% zv9Ynf3u$$ii&*7e=3S0*3jwzoFheqH8Zk)VxUH+E|1wmb%+CxhZGds;#M zsf7ml;O#++$_v1iR>~|l4TU|69qWL~$$wcZXzHs(r(wB7K@qg0*pLlTVI??4%T>L| zH_5vLGx(vKk0Ji17(+AHVIR!VN6%DD6{m8FTrXP?z2eIsx6Hv?ijV}_VlGNmnc5ZO zOXBvfu@3VyYHn)MY&~dBdpeUnAz7!4b)^LA?A&!AC}&r?PrWLbRnOX(3(0fac9Bzv zEPggDr~S4QrR!_@g!`v146lg{qv;QRBkvEBLTT3TbuNy3XwNm1&14O1@99JM#KZ#} z`K+_6UjsBV@u>ixSSSpP^S?U;eaDk~9P*HRO2nN@sjxYg=FT>GCvU7M0;bP4T9UB7 zc7ncC#v1ip5{3fWy3(4`vRIiZ zuYC))Wi6F}!3L?e#1OOp9Mh`ocRm{VfzrKkAhH;(wR1|~Qk_HQx~)LCtlJaPdEVmE z*d+5bY7aWVY@cqMB5uBSiM`fq@ zv&c4ByVyWKxXZrDr(P(zzAw(mjW>@>k-}q;`Ib)p%&pR@1fE?Jwe^sSJ*3dtQ&mp6 z0d`~|h%K|U5R@|2m`ZHjw2>+gaGG3aJE{vZy8^`I)nOwG$8H!bJ}j0V(P_f%jAf48 z0=TFox#LM=zq$Z9?GJN>MFK^NQU%RymUzB6^>s_KKZU4~3my?2KOG`eg=VJW*}eA( z6Uc@1+mR=o<1F;c#vo*rs>!@RQl=JSAI+pFft-B$mw?;6>Wc(oI9o6#^q z>olIj%&`06*a2k5=^;(snBTcY#+ph)v!7na*ntRmXL4A#d7K56s?9u|rz!b3m~tb@ zQ57j(!gskh7m=$FRx5MeI!?Es^5;U~6fvh#mTtQBN4%F;q;0SYA2VS8TOO$>_P4U! zyg`Svbye3Sxn*y;aR*^0#YGd%8nHH&@4^OE_v5Q|isR@XCIfS3;y+R4*_O+Nqz2T) z^0ZYo0-J&JM-n-j0{I<8WSvhInYS-gt}v~In*C=HXq-_5NM1|}0zQxG<0f6j;*%7G z(W6B5Lq_@rf6#DkaxCQ+m3ikdLW#`Y_oHonm}2Q@ zBJYwhRc+MY%)#X@3m0m&zCOj$wtoRVVvV$O%1c+d5o71nEF|M5~ zCC}^tMvBfD3tzM%A|{(6>>y4Nk6eFBJa;0e*pZCMhdSIpYiTeC|508NwG&pJlD90! z6upBY7>Hv1J_edGqh@GBbXr6Zm}wQi$k7;y0ygb~v7oB*cN zK5VvHuSpz7m2?)}43~Zxa|TadXrjcG7k0-~!r~;ybZLSX$sdhum$;&|3(~1K+qSBe zq~a~ppmNe$D?noHMVn)KEmdE$EUN0FBe;3L`G zmI@}O;7@U8J7|y4g;6$ZOi_E}H{6jbxV^~5=f{ve;w6&%55j`hdmbvK4xEvkp95Qo zqVGs-v&zijw2^xNU;0#52}yEl*|RsuWUHn!8Qm`1?b90}H0K>^BdOL%HN;yVB?VG^G-uiiq*t<|A0rYl@AK878Qh&|H+ig*2{>d)FXy z>m7e2$q)LBLZ%?JAF42XioPcdweAv6R|MYd1M86VezrY?6u1WMx*8$ZARusEh0AAJ zmazkUyZ2+9+z09)?D(fr?g*@GrV!2U-f|mDpn2 zFe4W^f8w&bhKt-rJLQ&?@>l#ETGU5oVdGwpdq6N^pjE7gFe8(iLve$QVL~q1*2>-& z&#y^Wzxn0UB=(8LV@Bq7?D+8G(0!bfuUlf{6)vw5+jX>W#y#40SeXKAn({UpFy@Med#Z@VM z$mr;gpaOw)cFm?Un9A)F*y%c4+|htqRLrg?K`SCI;cwE?gObwUS&6qaN8Pdt6*F?p zGl+XGiuE({#XyCfAr)}5uwd+OcqXt4=QE1U9(KUGxu0;eBw%JqVB|0S=03buH$RHF z#b9s?55c4@ePD=f3KRxti%SE8wmF}RZ~Mz{I(~(pxERY=uoXh2NW9Dt#gQvd%dD{r zH-*=;K2CNA0UQQWTBv-82O)YurO`rJhW3cS4ry`6i71%yM5ZK;{X%Ldk5yfY9!oo@ z&qV)(-6Ri1amN;w8S+l6oD?t_21UwS5YtK8wyPHCAq@J=+-RJ{2p%L_{CZ+W65yt) zR;{gk#0%EHXxXtz2zsXI=3mjc7A2jeU#W%r_x)wt7nF};5>EMMW8Auq`Z~R zj1U#~3C#7*Oq8v)(5HbgcuonQMc!jsg})Hr~HU_#t4jzgx_rQp^N#fqk1nR)Mh+CTu}r zUab@h{HP>$3|W0tc?)_{u|?(YC3EY5H9sl~G6}1>N_N?~!%k7x(&2seVbDVVODk^r z_RIoMo|ZcB=JjiB^98u_v)q^I^EHb#MD6NJ0L~-FN*Hi!hvF(+rMR9=uw_X1*}sDy z?~NorI+@$Yw|DZhZyP$e#n<-;i0cR==@g*WC^jT4Uc!t&kZh#nD8%H*WvrB~iJv`! z5_=|04U0?MF(OsTw-&pm1|__tD_JO*IAabeT1G%JmJAJsvcOb8-(|yoTDB~dB{!vu zq&#{E5HwKxy?!xwB*o0RpJ~V?j0ZVQnv}0JA?E28Ezh6173^E2#kNHfiVbY!v(n!0Ppi)_ec4GIR8W6>vp5qr+9f&dXsUK<|C* z%rC$hK|(iIV|rO`u!)FFsj7$(eeQ&7Am^b>EiFkIyB&lL9)&c?_J?@agCkJ(`k!HE?@+F1_pAhdhE zK@_;aTW6s{q=SepH7rO+(UH7`=3aF35YNf3*oxkbD_lt4W;w&sujnfxiCb?nDlL7r zaeVjeBL=1towa`D;HJ=+A&}M*-HDUZ3LU#IGCB@Cz?zk47i=J zR5;g3&M7Y}BQ1l~XB${36i}0Ndno1LATC%?k4eE1DZJomXp>*55g`orIo9Utek}PMfY3?rQ;ZexygXS2-n?__s$NH`;AJv&GStX{3 z!ql%eKvU~XxRzXl1q?@fvq#MjzSfo73p{X7ZE>fBtCBE+O{7V#IgzjZQUt%ZBCo3^ zcUi~boQRb9HU*`pEM1ZijVTZ1i3m1H*(Gu#`14dG{{Wbh44t5$nNsbu1`4!R(6CiN!MnQgHvEyrj-DpHNqZj=sRC0_Duk*-9w>~&3_lm;o5ve_gP zNpf+v0+&#rBJ(_lY}&BwPdiAbDEEj_?V*!w9+;BiSq1g;67&z@@`wkFQf)fwg8&Dl zN?p$>T}zc4aR*nN+9tXzbSspa(Z2sG4b&wev{qIg+4(e5TIz21{KG5*MG8BxjGX#t-&D zR{Ydo+vuT-MDT{}Fqwlsg)SQ4^6;Q=#b}l6@IDCRv?w&P6jKyVx?)G_Lg@t)4*Ac2 zP^L_oKD0Q_Qs+sXuz6L@ZQwau?ExgMBY|EqOarTjup#&11&_+kUyjGZ)DMt(Q&c9f zn&vsjj1vxfAES96Zm3sDtE~#aDq<}3Gre$K_UC!SgcwnAT>tf)xje+5!@i&M?%(o| zSpV&zA!VcgEoG)?U}|LeO+vE&|KZ~%wKossB@AzqhB2aypx979-`FM4O)3be)o@TD zK|DhTZF@H|u1R*r;|`zer!1S7W1sCFfL-{{>b=}k!-RxrMzgR&8!2O$jZ|c7dpTE` zZD!#bgTf$ALzytgZA5E|j0Q(j%w{d7;;2HS)r2udB($K@^U_({*2Y?zk;kY=>dj<^ zUrtKqp<4pS-eocaWlmTf0tPMmuhAaDqD{e^k~n5;9p1!HHm3*ut6{$M(kf%fFB)u!;n-+cUwss{LLVHHEfB2TsJ=E(PO;u$1N4J}RnZ(R)+Earv;;8C$IwuCI<1>?STKi9tR; z1VSNEzs%>{_~e}X5&F=2W;#6mJi_!aGC^=)ELMdYyDSg$)t&{&`Xf&{eNjXxS;Xkv z6j>ZQ5-(5?v;R|R*nm*8$2E-CAy39)sz_Pe3boJm>uwVaAbWEB!lWVk=dLHyfe{JS z+-2)P|9t?790DBlWhh=Gp?o8OykG)ucs>Swn+IFC_A0eYmJ-sAFOZ*qHTapGzgLyi ztCJ&bhZ7L^$;n*p+cMp(U3yB^DdYeeW&QX$Z?{HK&4s%BLw*ga{DY_+>s4GfoRC}y zlDsD{^_^3wm2_g;LGUMBYN3nuDT#*J_;6vK%qq@?a?b%+7DYIH+8q3}a2=RO(OgW! zN@daSQ`h)EHki%i-^aCgbrQp{{^C9t2~%SYWhw`eR99JNj&pW>L91`H2Dp?6!ibES za&WUpzwY`l)21e@(L{QFpfc8aQDS9f5W$;qgkct%=Uz5^avvGC)M`{O5RlJ`z535OVvuJ!<~!)5xCw|1>*f(jvbIx2ejxcrWEx50M%kF6BHQsd zY~GL>XdB5ek~$Cp;*2E;SW zU}E2Gyne7yALga}P%7@8F}mu2m>nPO4u0Z@o0kRumlSOZR5azUi2w|nD5K8QLbTJ5 zC0BK4h_m)^)l+%7>4^#vLD4bvnxcL5XxanHdZO-feN}OIIFtHcfh*b3H4eUKl7AkH zgJqnvPI>#>ZD_WHP}RsjGM-gny8UbR_kDUCK2Y>8vt-B}yWD82 z%j_)FisO?RJsD9+>M&ty{?eXCS->E@j)Gh<@Kyt#3`!RR^+=H+lD~lBc$Xbb)Mc-_ z0B1m+fb8{HGh{9lx;KD?tEIw&SSNZ574but_l(ysmX5zru1*QIVba` zn!88!p0G>thNW`h32zBFa9PFMM5$fxU;$zlf?hz*Ua{Zp1z^Uq+GXe*D*V~*sV6_g zU$(zEtdxlU$SNOojMdC*uI5J2U`)nnhSfKVePur{@F`}SdL}1y|&QjIcK&3iv<)QgU3RH=1$y#x2i6g&|^hdb@3R7 z|7STX?yP`3<6S#>&#hlDau+UO3LD{KcPDGJXGT{6e=`V_{mXU-kCdRvo~ z4^kflPk0P~xeG7#+~O6TQOM#Iz`XKq@cINTn7VjKt3CMdj>TJ>@v{B(gw%mKp*QGg z;>W4oIH3*bXx1R!!Q%5$+=I2TXxn^pt{6s4W83DkJ3%I*>H--Ym}$Ld=|!S+incgZ z6*8z85c>$!M`Y>?a*b8RTDdEc6C1Tl93yDTq@_3ciDP|lAKzr&(5aK&pKSh@Y~!vu zh8OLpGvto>IWqvBd}rFF&$MtAj)9Ju^KtXHc0c8_0BbuCeF(%+rd(ssw_GCu1N1bR z#4uYzaJ&fYU}WEcGJY%2!!ZVmwsCXDki&%CK-18;W5A?B>PTd`koAO^-Gf)*3KJ$V zIXs&8u^Z`vG>Vva7cxmq#n>sIDx_n0dB}Ty51U7rIV0bq_eFUxBKO6SV^q}xDd;i>C#eNSyRp*kvj&?A6X2P9naCJtV=?OuDimOF|td3^x zWC&T|Aw$5#$PjwN?p}VU(a9NkqJ)SeMfD0LuNaQti97&%&a3yr<|Tj!^xY0H=b^Rl z8l0~?F-i`bK@eFG+hgE%*9fAGoZWMGADq^?DfKXV-&Uha$T)aXG}pqcG9kNF7ddA) z$Dz#6lPwlUW%BBHcJKSjk^E9E?$#GS0}7i707CXz?W~Qxa>wlBFTh(r&dizO~-i5SH~PyCQe57afX5Lu=l9Y85HR8sCxN(LG#A zY3-2+Pg#rC*;1HFoJ55dvrkG^RahX5fCO}*pjUM)k?>j*KYF6m^?!NBOm+KI6)r+@ zyX(=dz$*zCQ)YG(SRHJF?aO)XcL%#Fyp%57j4m!e`{zbJX{#SHI^Uy4Z76@2s;d4^ zQW&EDNN5a&U5yN!zFXspM)uBT21X+K2H#)Y{@EcD`j(#{kAH`Fx$pAn>;`D<2iVK@Dv5j= z4i8^M-$mElS@&+e7v$CAQQ^4mzT0CJwc2UB;@a zbQam-t`0H?)9xRmZAKda*}L1}B8Ke0#gz*rgv~GT)I1I`Ij%rp8Cs@zj8+8S&wm}e zO_y6DoXl@p)9ZGW>5p3SqV)o}(DTPP6<8Gv(y!+|B0p+V$=yszMQM=zD@UAy{lT^Q zZN9(q#Q(k)kp3ST_&+2B{+U1iSF_4Ym;>^|S4JDJSR&icdcd-$N(LXE$=?9@=jzTz zU!tdNK`fY~yJw?~(J0Q9111m-!nF-&7nQq+&N=@IHo5w%vW zFxYh>^R8H^TUR~XeH!ePulhPUdZ+k<(_(!Z8MV=wx37jqlbZ*w_zd!lL-ikT(I^lF zPAgLeHb3^umy_u)W@&vb#86_i!Fyb@f-4u{;AiL_^|hb~IA|g7zarLaeQ`%~Pus?&y1jznBYD}O=~$sve&{>bSU#T%9} zRi3j@lI&>6X6H09c6Rppc=<%)gJBL96>z$2RycyZMYq@V%-G<8TYs!rPDWnm3)laBaRs}c+%`+`ZmO4v?=#svl$xFA zZp;Iu9z4lNd-LEBydxX}W?jm)f=V&VFzZa1%-Mc*LAtt_ht_yLy=MsXp;KKhhHJ!2 z^Wx)kH6M-4J>E$^U?n@sj}dI=X3BkDBn?GELnrmq+{D%Est>Dmkj@7%bXvfrvs5lm zh0CMfDY#?^zjH8PAoQZF7~`9jrXT?msA`jm|zm{btCM+8AST+W^vt{+_`2GX&{ zN1}v)D7fFu7Sx&{RSO(L-i=aeH!4rU=@6ujE~Yb#esle}Y?zC3;P%~Rhia1_pQJTo z(AyiW$!GIPdNOY`76pjAi;eWM@W_-*l|hP4!qz7t1jpv(&WejZl2Zqd)qN`&8Vs`` zji~d2_^62QnIa!tS7J+^CZGuwn8EUyE8H-UtH}VtI|)QX+K?jDE;5*#Bh-Ba~Dh~kcI2kjO0 zMgD`?%$6fAi!EBsxEdbmTl7ktf7ULHl34L+A(fIDvt%;DLl3kWt#Lg%Y1)M07vNtJ z-vIMtP~f{F^aTC8i2t8C>c3Ec|Ibka+35X(1zHp5za15|jAT4Rxy)oViU2^8m zb`%A!=vHT3s6<3e_nt338+{Zns%Q5_Y^j)Z%9`j96wT4?co$gk01mi9odEhWjF6LzF>#pyWla=%N0Ud@Z%%#}qN-A9 z`P|rk#`#^h=&>?PY4vsl?0kLYu1w~_?$O6|fGM$OLqup|L%Ef0to9)#7FzaGQ3|XC zo*dO_bB~}Sq&n| zfKETx7)g~vwR*t^11c=Kin#ImzKNEX&uEM5;1fM$Yg_^L<|G12|91WYmwLTatN|sP zGKE31VU^M+#9!WKKP^O zZ>nl^Ua(%iYHoVlUSBHu^17ZfBq8$ucz#ax*y(!8yx(#7{9cvjcGNV216He5iAV-} zCa+sl{UpMaQ}!sbl)7Gl(al0#O&Xbdw6YXyOa>3A=v)p|PMbgM?p3wL*_2y`Cz-2f z*hq~KQMQF3%^%&{CqgGdm@Kyt2;(q}eQ~0{Iv~=#5D3Xi z3oDDGQ{zhqOA9j-i}tYXo9O;eIv9Z})X_f}XhDMK_jECkfLJU}PArWe=^C2AcS}nX zs=BT2Lw8GVYIwElIeQ_oc_D(`+ocaeOywh<5RBNjPQeHI^`-Kx>rJg@n&<$CC#H?Q zt`eZjCFH!idIr>lPUrnCtO;?ahu%jaHZDWmeYkwtwIb^8$6EmKVO( zaTIfBbASm21)QHV$iRd45mT|Y za{rWug{s>;n*J0qY%qUZ8Wzw*Okd2xl|~HZ#~P90m@4J9N1a%HnGxPrUQyBnhwv=G zDcR^eNR_GHy*r(we_|&#zm<|5~mB_7QfkV3cIw%70OXW2lDjYftcO3|=A|3`vO{2HRq z5%dV;=ZCdn!FYf|;_n540I=Vsv0ai}rGr{)7I(XVR4HB3fDIzoO*9D6Y9WFRSk&|K zZRS2bs0CrfMJ1fY7mfI$-Rl8Us|3o82$^LY357DJrvzFX(XegF)#|?jIwu2AoUiTS zRMxvLH@#&U^cy0DCIgCY#?o3KsnF-vk;6>8%!%~Mi0paG%DmMS!dUVr4zFg2SOmBa>ZxttR*-fd=RZ3n54O}*=5OjndQae{2k^XN7F#t0loO? zAlp-_AK*?krig~2yJ1aMB4RQCZeK9|7PpudkO8hU{)s4e#kTeOlK#PzB0JI==L*R- zL1TJuua7_No@R5#3k)WzEnQxwC}m>0_{v@tZr7l`95)`q+ZX^O#0>y1zF5tt-*DfQ z;zfbQn`O*5lB%dHv%TvQRs6~Bih(Nf-L-BVvC3gJ1U?skStQ;$}LIy0)u+Zvd9|R`? z&VgrcUQ|j^b#Qa@G7B7!0oYdqAbVdZj)|AM0BSYZEm78%9(w!U)|Tjx0t1W|1di71 zd=wVl+Jd#pp##S{Fxd&y?Jz~Iw2>Rbbuo{eyz69I$Otjg8zYEEjA98z3&ayut6W`4 z$@F%9#R_IewT&ZABv(waDX_(@S`d)9W z9lHa~XPWcWvJ&`Pcv|WWmE*pr8~?z2FA~)r^T8$&cj?{vc`GNQGUKDb*#*1KB+ zzi>W+(b+){#JJI16b1(JY?36%9O!jf(VsihYd6CqPcw&c?+zlvVqHq*+Yci1y*+`t zM@a8AYSw9BeLNaQS$O9P`-o>YmFtHW){Ekqfm3Slusta#Cvboh3#F7FG-X)xE+g1E zkyHD;#0^Xd36P%z*C&bCCGmf5c(AS)1f*Je=n+ohK|WcjF1YxNpGQj1?-24z5U8~0 zQVmP^4%+e%YVaNaXX4e2`?MWZeyiZ@p9VP$r-~ENeMMdOL3WvFGavc}R$w&c!Drz- zfumW3!4pX8#6;OH651Quu@l&X#FE~*k}g$CmB!t!+O>(c4gr(z<^{)p`u%bHg{ZGd zoU_uLYG{AjS|WJ_)V@ww99m{Qi3;*q7I&MhKGK@9 zFp1v~Lr%~cDK|Q6o5f|3nfIjQVjmCKK9%yen)m+0R<-(m39>hCKwMu3Q ze1ge3g%|juWG-2#QIjz`WBO@Qs;=*|u+*DHHQ(X!Tc=#urS~;nI1s>c z-)pHfru4QIH6&%F(6U&PhAVZz#{pl6y?2gaa@f+$Gyjfg*^)c>jCe%OL5TuE@;HsJqd!Xb5R71!vEpHg=x(ziMrbur5SIBKL zz9luS53*)RZIu?Z{s?$=QgbtMb^1SEm|Fa*M(W&QbQ=O{0%*Q66U6~{>>OdR=^|c1 zLLHIO>H$!7I2H{4gtI|S(G%@u^6Tn+dS~QPTw%M*fS2g6*|9*Ry~jXL=m~NSRHZl` zZ7@szbPIqzu#h9gh|kyp*)i^QQsaD5#O~b$*dA{$#7}Ypf1*H!=^nYlx<148JG6cD{J1FpmwZR@9 z$q<$_8iMeg4@wq9uN;?AIFp|b!+}DEz7MgWP5L-s8Hqk9ta`AM{9dKtDYJVLdU2dk zuX`w~|47$uIDuk}JECeiR#PIGJ=l=wQBC0yrghty_}pjrUPwV&b|YLyYJ%QV8+lDT z>4x0H6XZ5_x)aUUh$e;=#7=r6iUzCy0ba|SgP}H@d~pO%qWpV(tACRe@>2##^mI=_ z#1Q1FRwK(MV1PjMu#Q?dXLYdkgw3=mM7YUvh-A*vYlD7gT1q2`qBGKmuBO@$wky)7 z{s|oL8SQgwTXC{C_DIIW)aLykAy*))ZL;z@S<6B1D~FtuLF!*v)=l>LI4=vjCBH2}Xh1}f zxk}|qH`|;G{CnEVf^E!6ld>HY%xaJ{KxcTV>a7SY z6Yy!81u5OS56qqo8L1M@2z=C}xO_pPGYtor=+}}r_Cm4I7J`Q=MF%(a>927nr`0%i z)D`Uvm0qu$02|;nFMA77A~x9EIRjk5*2QD-u8KLY>#!5xRwzx6oo8lBv*Dp;4=kN( z8kWYx6;l%{FGCbKQ%O)cDw8+ShU%Lwv9vWQ6q||^s3iOvBF?%Fm%O;*VB=Aq8eVzz z049Ph?l$F9B@|MdIa0$F@FZk`;+!8=@xDKX3#mg65>=ew3{!P49`EHrC(zXOb7Y_( zVo}wvJ*)sH4+=;~_bo<1iC;hvIpb4LitG(FeEma{P5G5ibO`##4-)LZD~|r2*JJ7Y zf6#_3iktY};f);pReHJTzOugD72$l~yDOsY%Fkb_vA)k^3@a#MtPnrRr#tlz*wQS` zT~w}N6pZxPla$@f-P;woHU?Bs^Moj}DHD8I)q5e*Dd&n_0%IlDmcN#`&z|-GzbO>gZ*?PGSZm?nZrq|*zg^szt2_Z zqs}SuV%siB-j`uuZ-*Ly0=lRyJP#k*PLvhdfz;s)GraHyAUHs!2|n4)j(DAWlSw*EK3AIcts1HyP_F-q{p0@j;kr0(~4uBW2f%4i(A6+b4e?s3H#<* zyY*A$_BQv|7)nB$snh41qUa@Ou(_^@$ZV04PP=E)>!!m{6|pAhQLXP7Br1AxEwpPj zvewXg;yDN_q&4cM01VQ=qa$tJl{$!^ukcLPKgh37xLoFn-}v6*-!{}4{}wF$>v_r9 z+t?b}JG%WBnt72Nw8uv_$N?qfu08p|HA*8=;uIn${2Eoa=Eh_Oxy|45j z-d@|SF+c)cKF9adnq#q$A3ZwS#opV0Rf-6w0>{CMB(iRc@_bni>wh? zpau^MEbA5Hf?rq|c4%L;a}uS4Ify=!p#s-ykQ;By7(Gen~Vhn->0<4D|{_!J)FfNUvqMXWKwB*IUkR(=} ziNmsICL7!=uW+y1&VP43QvP!$FL>qBxZvHRC&*=dR1OK|A-rf&=yW&N$0E|!VCr0B zQCkn@=b+%ve$-byOTv6keCL!F7-)g3pd4Vc(NogD5ScYm1}U&_b0hw(6aW2G$X`zU zpH2S1Idb1$htIb}aP@NeX0B@x*#adz46i1Ka6eR2%$QTLf}HfN|iqeWo9(3!)0e{Jalhoro1>ET}VHmkiq^2 zEp$S4C;>@mMT4V*TKOxOyZON#(HP3$OkD7ZTdY+h>cVeZtAzs}cB zh+RXtleTtIgm0`?`SCR zHe6rc?mX3^MM^3?vxEmc-QuiW%5J>NMr*~XSiM?aiLDEY3Udi43dbjHGb&APdq7MF zxW?ISq!LxHd_(<9g=@tHW`Q{NH(AVDWKvf6__dOkGLB}Fv$WX>a_i%cJd-1YW1t+M zRazq-0hZ-dZIL3=UAGDQ5Qbm%9oNB9Gc8?Wl1!gNl>qn{L6m8jbx8R54l#kE-qM&7 zqXss2s7nFN2Vho1vcZc3AZbEPpmY`F!%v|(TPeIWOA()0ONpvsJKzA0Uu-xd_z$3C zvfzX7@yaM{86T=sCT{6%k^(WxSt-WiV@?^JO{9uXW8dO_QS&AExK@#K&2b4yX-bdM zSW^!J9ne9wW)&KBzhz|V#2U)?x@>Xv%MLh(ZG$hY)n)p&&IAg49A&!6R0u6Z}Q z`5h>YcGsE)^`hN+!_n*_8ZC|s4xj*J#UcO!mgtz4f(Yh8^Yn&rRPSSQwhCWKx>Q;j zF)A7Erw;wGz7wGzmK>)@ejomY&58+yn=lxVxLK+0j8A5xBg#>zr9v@I4(vb8WWj51 zVh8n16xuqqxS{>5q<6;b$qHl$yj70VQQ(z?T#HMeE>`*pU*}=hXz>WQ=TPJ zV(B5#ei^V&4m+x4BTs1bv(oH;arRBooqpT0>DacNbZo1Wj&0kvZQHhOTOB9Am>t`8 zZuU9*jB)P6{@*d~_|Z`M!BU{{mzX!+N$xhOm5{h{vK$8cqBN+)zLl<;;VG&hu7&?S!C^* zkF*BI&v3Zf$?%V^beD2uX1WFJ{H}oCL(qB#O9dl8OH20(h&|-k=tB04VfnC$UWga> zQW@Q?$ILx_e#cr0xLqIINX1U`bS}C9yJWWgPFqK~CUcCr{X(mm=NGT7eL)=@*{T8g z@|52MAySE`NBNy;u)$&o>xH3WI{zuHrYJXMwgCIVsDy{$E4Vx7p90HNf-BjmT;mCx(F+>0FS5FUu3tq!HbWtFNUGhZ@GVLP

v1CaM__u36mMBUGXa{DTGY`LA?V2J>7jGt|&^iU7@q!vt`u1BE+91b=Z3BmbqP8E_&T zWlW=zV5=VOIKaswJLxL}c*=lwf8tEAx5_)ky{>#|-geFiu+^s5*E6u95DV zTgAB@m-B+ruC|udC^sfL$2y1NN0c#0YFfdxT=lD~S8rYs#Wi8N=loIy3fd1vW#n?u ztwCe|OOj=95wOYVH6jbDp3{zSdo`%2or=D6LkqL*48LD zB>rb@EShRk@*LaLsV_2eIaw-!4ARd-G?m7kFp1#hA?z_HKP z)f%$%jivFXfAwd6`MjOz-er+iwzO!IAIoC8$uHi3^_on{d^Ku0dj+ufxnp7$Y&p7R znnvG1EUVO@Xgem>Gb(&W#go5-@1fP4>+eDgy&2N}l^zj!Gpw~sEIHwwKj>bNQz^LvvjAWi@#K^sR`R$ zZi1t={(~dF7FR-_F0(!+#gAw@Qhd=gKkalZ#ec8J^b+TrV4le1d6m3}!KfcVfj_mm z*=U;U_{e#5Uh{Rgqx%WJ&DTX_Cp%YDB-x@(HPK|5L=|G3RFsIHO2)~2zVdKdChueg zC#WauxM=8Hd7ROb%sPIcRjiSMX5Jo>U}lD-)(dO#1h!W|G}{XqC2*j=OtS<)a~x+4 zS9i8rmcg(FPET<%qvd7POnzC-pr&4j7HOwrZGtx9Lc8aE41{e=g z72nkuY83PNw}jU_7J{Ix?kV9DVB>p;bBoweZ*91Bs!w1K-vQESBxgoL#Y4KpdSFunytaRDvQC`H7p|pZ zx6g+Gjvr+i@DAi(n|3+?^|y^$?9AVhjLQ7=Es57;kF2C8WQGE9!|zejI&s3S^vw!I zh7_09qR_m+nVF3RJXPi^w6o&-zc{TICT!xE0>_kB>SK6UaD=d*PLA(Ex^(rT4b;Dr zrL8+@;3T*E_p>uKCIx_`?z%b3CJB($S`9RWL!0nXWGqjN-`;1f@#sY|N6=6cgjsTo+oFxtBti)p*(~3$vJDMV3WatF3A25(kVDVMNVp(ojFg}#$cbY(!%E- zbaV9V9DGjinA(ic^f31uY?tDkl{=+0rT#ZNyj5@OPhr$2f6#bvhMC>yMrMo&Olo5= zyib&!m)*Kk7a1=fd_;$9Z(#v1^{^q{as+wv31#iHA#RD>{42Eg z^t$%B#h{G9qGY>WdB@=ZH|NH2t7OZufwN=fYsfaAVb&df5x#_jIcWxEL4aU~yv8H* zAhH|3{J~fqZQ7)Lx=zkC_gj<43k2b3wXKms_zF*yEQA=Go&eWtIv0kah>TLKHL5`ce=s=J_+aS{7Gwwh1Fp4e7^7=YPx&r! z2fPKb4^OR&0vTQa+H8df2BQ!R-ZQ<)jWl32+ILs;ym=eZC(?G0TT8W%%qz7UvgDey zXLMC%%?wmrp9iaGk_GvlJUo>cAj3!QS*Oc;w1P9NvtJ);_7?8wx2#G3!7KI_YDO{G zSb>oGlG7RBb7|rC#wkIqeyNY>d$4z+i%AYc(UyqRB|{Bf@HL7LhwV%IwbzSEa65bf zC+p=m9UHb$LsUd`PzmX4qqygq0>vpdCmIA$p$y2ydoE55>?JOirBxCjDofsUe4gX+ zxn*0L*?DpAx@PM*x=vVLWeQd8n)c^>-#EMPZI)BU5hJm~2yD!Gz!)`I`oji5AE@QE zM0y{oMAI={2q(H>{MREVWV;0t^}SPthwx9>`CGsDpHhFm5wn!JwYihA!+!&3>Dc~% zX{kcK7_CW^D|3b-x25Un+#Znus0EDiJR#v<5hSXw2FCX%bF5N3+M znlh_@wWWzP>cyiT7-o^$;|HsAO-+=V>7cI)B_SXCg!#PN4y;bY>wE?=O`@CcG_5#J zT{%Co_C29Mz-& zV}y6{g2Zl9;qn5C>+*vM16G;{i|etEArdr@-nlkuP6kX`t=MKOTDLgffo!|>2e9aiCiR>} zLaFKAQ9e=59NE?r089)ijAL1jTTfkAj$1R1x6Ay!Ur^hmJ!l+eFAsuKPnI>>kjB+? zyy}gC18678AFpXmz`B8m9Q8dbV)KoHqfa5lXDMfmHQKG+a#S%PMSU%7X?yGkct7oW z;KB$my^htQ1vkq>;BQPo`0rs-WD!RjL2{JRvT~cL@1kdG2r?3E=9l8oDL-9N;jsLr zk&*-ow2+T+l}pgHP8Ok)8SJ{&`~)1Tcxp!SD}gmFT-wgf+o|g2Cu@}%>##)7p{y{( z5V&$sateFWa}6+a(3a{X`&gC(v~(xNWwBQLdASshk;Q_UWrXrP?}NY7Zf zI4{=XX_rBn9`QQpgqW!6e@xBV!x%bz1B#Bheb}PVPaO>n4_M3<0Q6{P)ZYq27Tf|x z*z&%eM?@*9zfIM4guN-o+YB*QA_!6-c(eTw!ckL+j%qid8kGHguVDhCR4{ zxH$?1w!AM{XUS{w|3p;0N~0^{%BU!f^$|{=Vgglm@0lNt7;zNt@Msc+NdWr5$k>AP z*>tzrd0XViX%6VI-3Ul5_5zh(3wurz8vM|O-F8Fz;O`7Pttp{8?4Sy{=nSITuL~{G z?+wpNmS}5K<36FZROH{p2sK^16i|_V$SX8uX_hc=MAEwJw6;(P{ruCe_A58pziK@n zuWtEbQJbsnF=C}n@yP9S$OIN2wVtZg@Mk&pq29BB8to^3j-9!6c z*)6H;ZEGB+Xq#24ak~aKI+a`v!E}lJMwn2_4$TD}UCl9l)^>QM54PpRhzr=8KH76E zXW342`*|W$F!_hG>r$iL&IvBA^NH}j_2XonZnJVt*-}N;*wV!o=cy(jJHfy3IIZ22 zHF0YgN0TbQ$%b))F}s?BRW@YG?<{xnt9V=I7j=U?l@D{>Ig%)+eiE+)d}qU49S%pI zD)F!{r}()tJ+rFkVj}7k-dq=yy45fnB#YR^Q59b~nA7KgI^lj=Oj{?%W#6<~gP7DX zYm5TR3xvy8#0CfeiUqB;c~tSyUImlO)epCNWup7TjdtW@ugVBzEH`&frpdgOMvkj? z5AIS$??;ggQ2I|clGsRzdF2hJ@NPl$?C;;D9ilh!K~Rr@{hR!6KP=LM9AgU0VH z#mV{MC}LTR#`a0#*Q>!v_Tw9t?|Gzn4erRZVGV4O9kk*zD3;FJs9kbp)h*GZay8$u^J{wF z({mIyl-VaG)`{7Aewsge#mUYG$X*M*(pd9RiqSyhpR~iic zVZ_4>adr0ziK%1g;GxJ*H8l2F6I9fzcbl5XXrCm17B352G1F$p@XMLumP|Hf-}{IqXDguTF?*`5f0I)ty9uBKLZy!k9RpQt@(w<8zL zhy6lqH?U|+Mhp*@v~`p3?pK2@vp5JZ@DnR62bow2^4xQK$gMU>ez?gDrAw@UDVauv zn#Iov;K$LHKw$O$-H?G;8_60h-j*2iLZfmIappqKOj2-wiM~#A3Rs85{&0nfk8<8# zln$VJG%Vb|9}G-)U=C>Q+E2i_5`5C;=;L&pyKayVbc zf{Si(t}h2XKyjj^zJd-7@)H8gMTKPoEk~aAadyi;O=CPgBnKvyg*uDvp}wEmN=?(> zCTPA`qFIYgN^(M2LY-vPCT!!6g|Q^N1RoF0Y`6GECeAxj#xz(xU_~60D96*mD8Je= zjaB1l<(1`uKigmxfCX}Z!>GX6?dnx&)Hv5;3!?2<#$p~wFf6;*M0n>rSfkUV~mEbGcfjzkn zgqEUiP`KjrOr=!*Rfw~n?j7JlsbyP1qu~UbLN+wAF-!ck79P81;UW2vr8*nx$5O?t zy4GBwv7&``WL;NzO9S0NCG0;KkJF(n>l*KO;yJ?b*1)Q3^a%H;@h-?yu$_Sdwtih_ zI9U*FcG96S>nP1SUyO?KQw43qE@5PePYLEd!G;z`*-|8?)O}GYGNU*h7@^6mQU!@W zw00t_H-9^9Z|JVcNVfTcg^)r)bJ}KaQ*)Bh(J3bcbbEASsI@#9rXq`)c`7|iM`eoC zvC4iw${VS-_^Ni7HT`)xJHi&T2Eo?R!5X`k%^`#0bPIfNx{g~7On0P}lauNoV7da& zGPt%>qx~JehhF>hhjP^04M|1YVa#bm6`p)cX)rDY_YF;HLv!A@6IiYF8ay*5bAyHc zFIHzxJ;mEWp9mz&8w-1JW^EZ&fI{QUbh&XVJZW@A`l5-mIOQC2KBu-EHCnachUfEd zAZ|OLY)FA^UIl#0qRxzCn~gwiX{WgR9C8nWlMm_p9{`BVW1Y(v6FYvg%#F{+qw_EQ zC*PsZiQ&}91pWh?C|_Xc?0oKHglfZUgqDunn@W!Fk*0<3bTf*M4}rb_1!RAsmJp^3 zTD9Exps((%TNb5v-CgUFs-D)K3U5BQ z{`2oKjM}D{x1h2!yBIC>IlhLu2-Zjh_y&4~&kcsE@5y{)puTj6Axn(txL=s_j#q^w zm)bb+mztvY=rUdbclRXdQMDwDlg%;vqisC6=gqO5f&>OPY&AUy+1!g;#vy?a=Yyky z3mKh(2eSnA4KV=PAd9u}g?&5i;G`NkE>fGqwU$HeN^LD`wl~%*4N4X#h`(LYnMpko zk-hmak2W{097?KxFUopPAQg-$NyR%HaHy>iN~_yI4CA#8fm@jITl5nm~wwrVdL;WTXHDr*r$aih>vPgQdT%(YX> zRwx?e{E=LbdjPGb_1X+(=X5hzwY@22fZ7$qC+4h^HrRGc``Nw5@FAq<&w9f8e3ZzG zdVFqX=6YwTc=o&>{WY9-d?~#?yAJsk+SU>GWrb{f!Z9>FSZ`|#!8DwwXdwS#iFMPC zS*~NDP6?rpfh-ll`YZaD^9@E_Q{a|7AuGMPTc>gL86qvTWIDtopMvt>N=J?segQlmX#-3E0+HTh>1 z8fFNDGD{Gq2!uB}Y4A6b{ff;JlHWrJn=jFVbFAr6bn7#cRD8I{i$kVU51&j0+)q{=h;!hHf?3e&7N!yPQVL@i2gHl(9 zS1c4$%(vfRm&{X0&8CRH$cnt5(8KY*4@QJG5;o@Cd(xs)HR0LQhiH zA*v7(yvR_l{94*TaAvT*4CVflNrB1BSIb^by2lbyzJ)ZYaI8n~hFCc+a;>!Z#|%%{ z%6pgnU#+z4C$g8$_jKGsv#F0yx*&X+@{b|d+nSyH(7Q~jG~h` z20Lk&Sa%_=!aoevMysm~anz%a?$!Vjywyq8SKWV(I0s|U^j$AhX8+VZ@Hr`RY}p59 zy3|7}XfP?{O6s2L^(PzEAj8qG?qJFVXa9hmpm5Mgu7Yhh>?s;sfxuSV?v8is7N z4+WxypjDWw=(xvb-Tu_U+WO`5#b-qk#k5cotBoSwn82or{izBI_6Z?hm*5WY4A2)M zR=gc?Y3>h5W)=2zNXVCFs!ZP)H>aRGBfi3FDb{m@&77sXaAB%~idV)LWZxkQc|6rI zB6Xz#Va8mdV3IuE%P~}I_rU@Tx{*d$9~+oWL)reJ2-SBaPXB32qjna%BYz%N01p*W z^okF*nZyvlJ9)o>N|=-_COj9Y1K!;6gu=s)>qWIPJiFU@3ub#;+{TV~XSN|J0TCft`4O^+T6I=v-StF_Bmf3|k=z|+aflra0g5oYa1-wIIMKk%N57?a7rDx|zjA|m@RMBVN;iU^bp`9vKw3)ckV1h&oAzUq61DTi8cc=Dte zXRM*1#U1V{tz}(tV7|D9Hh<&s8b_lxb&hX7Ix|KhT;S} zBaB8R`BNu{(qTk0i;&Zq5H~XQh3rNjvf64&qY8Es zEBj0fVTo=g#bTs+uZ@E{VoeLdy_~;O>lG}HA=6(cvY*1Y9;j1pf$wvJn)!z5uoH9w z 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 z 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[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%0DG5r0XX)Q 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

  • |F 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

    ;C34!#l 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

    Gj0@iTk&nxIZzU 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�N%dIbNE5q*du&tQnu&f@Gmkf4BaCb1BY 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 F{ud33aaaHV literal 0 HcmV?d00001 diff --git a/app/src/main/assets/fonts/face.ttf b/app/src/main/assets/fonts/face.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2e61d8c60993d22eec843aee9b977d7d6be524f0 GIT binary patch literal 4064 zcmdUyeQcK16~NDZp0^)-R9axmD2%>iisd5+l(8+tD$45EUM-=c%$kqXx@ey&DRks*7q5?WsN2`ND2k+VM-1m9A_HAS4 zl9}w@oadged!BR8Irl#I5ho&-$z2l4h8xyY*F15tHw~vBsJfwM-F3T$p8AN0TLyi9 zbe zQ_1>R{pPQTOx}uo`9>J|=fhRd`=Kx2*wS_Ty!`x5=#$W^ZcBBvijyfKMc9|A&24o_ zk!9!>t$_YQOY-)1nJ!mDe+xR^nrulOUOs0F^dtE5N_$&pSJ`b3%n+G!7xE3*5}7Eo zWudrLRf#W4p~T==Gl}!>E6h^J0*@CWofv7FUPjyOEE?ZVU2{_-wh4>38-f?h$Gio{ znC~r=M)YUU$#8_oIo9K(6o>;E3yzpN8TmAiE*s|A8l1#}g!`V58z~>t55pbVGOLI< z*EMz2%e4|8>JBTkLh8c`ITo*!2`A3tPz99Wi$%h`vo$E%3A&6CP-{}o$@y2+t6%Yk`-Rfp}CN`Ned`!gUDM6TGH0n6eL z_J_LBnFNqbd<*FJNJUTnF3Hc>SF`k{^lRxi^hR*A zwON+9^T8%Nher7Yh3pnL+gsTad3hs#()7pa*U|5iYvoz(*Jt&4tyk4ceuS2lau6SE zhp(6Cf?{_<3dViIdTDiE@)qw)KV{=+nt3!<@S=?}Mc3H?ecVRg;G_lr`|*F=47z@O zIQ^Q}wMW+b>}oa;UrvAhjaK5{B>5;cwM>49)zFWez9!QFIOY2HpwbP>C&$JqH*TJO zKws1jd>tfvhlH<;p3wcno|MRP>2hB+THn@Qz2GH_@M1bOU8twnH{XXBxW~NC5}i#f zL)j~wTeKMaX0lxFd60TWpEtJl==J)7VN!tImo%f@Be%jKp;{3%a^~gKAhI90Or1BR zqdGSe{&Wpi7^ zKt8Db;-dm@P~X$PvWoX~(9c=wzce#~1w?5Y^XAiMir+o@Ci7pWpHg4-UVknXz5sc# zuSBbw0rU=enLSto`)C`xf0~^o<7RPu?tizE3;Iv?@;~U8?7URFiHJn2V!!8;`%*m4 zUDZo^`h8ZVdXZE198pTshxGi275X(wuiNH&KKwjM?}$8=4(Ivw`{ao;$mcybPnG&i zcrctQSIXx;P@cwQFL;bEN-6zH{j=`VU+E706VHA65d0bN=R)tJKS&;GIlJzLZ?#(P z=gq`}g|ZIG-RM1o-W+z`tNJ&nLwYYarmq=p<1l`ee4UIa)%^th+t^x4|00!mTz{u~ zvAl+U2XC*SCoBTKl}nARl{@v1`Z)cbESp+5#y4mg=SPbk^=E{gEWg$xdcS-^t~cCd zhBGISf5(oU{Q+uxSoePuGxDq-N{rU%tvNAqat-x#fO?uXeni3pWOgx`x`}bA0eHgm zYF$7*R6|!wtvgCCHj>rJWOO^(Y$cNeWO0Yj)>=77=D2;(IC~8Sf3or$w@g`aRwiYx z=Eo2095Qic)C#KH&fh}vW;6U8e{b~{9o6t)#P>q~C&&2KZKR3xh4OlOlP$xQsCtoVWiX zZ?lA3TISX~_6@?hcW(I2_C3h1Jw~iEc)(dw7U3Z;nj7IU@c9v*$9Ii?7QcXe_@9k% z`@6Y1!i&LIMtBJ}KhHPS#kei9XA%+ai2s%d4~WTkBi#OUc0@SeXn8oo^SC$citv2+ zy%9bU{I?Na4E{odmtgbdn$?MTb$hBcUe(sx6;CwPrCK{v@$%}b#Jq-NU21KrvAa3h zVW>fEs-v^1tu?-6(c&y?gcc{oYP^qAlkJj{RpI0MI@WpG8 zq?fFP-zeQ=IVl~(+^n<~X@_)T#Vp2U2{B$gMk}j5+7_kZvti#lm%(zLc9V8!h2(BB o8IE&q?R0?)UCiaVd{^KK!`HWO4bMFKbKn=iZeS1aB=F0B0dz=FUH||9 literal 0 HcmV?d00001 diff --git a/app/src/main/java/com/hippo/ehviewer/Analytics.java b/app/src/main/java/com/hippo/ehviewer/Analytics.java new file mode 100644 index 000000000..16c82be12 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/Analytics.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer; + +import android.content.Context; + +import com.google.analytics.tracking.android.EasyTracker; +import com.google.analytics.tracking.android.MapBuilder; +import com.hippo.ehviewer.util.Config; + +import org.json.JSONObject; + +public class Analytics { + + private static final String KEY_GALLERY = "gallery"; + private static final String KEY_OPEN = "open"; + private static final String KEY_READ = "read"; + private static final String KEY_DOWNLOAD = "download"; + private static final String KEY_ADD_TO_FAVORITE = "add_to_favorite"; + + private static void doGallery(Context context, int gid, String token, String method) { + if (!Config.getAllowAnalyics()) + return; + + try { + JSONObject js = new JSONObject(); + js.put("gid", gid).put("token", token); + EasyTracker easyTracker = EasyTracker.getInstance(context); + easyTracker.send(MapBuilder.createEvent(KEY_GALLERY, method, js.toString(), null).build()); + } catch (Exception e) {} + } + + public static void openGallery(Context context, int gid, String token) { + doGallery(context, gid, token, KEY_OPEN); + } + + public static void readGallery(Context context, int gid, String token) { + doGallery(context, gid, token, KEY_READ); + } + + public static void downloadGallery(Context context, int gid, String token) { + doGallery(context, gid, token, KEY_DOWNLOAD); + } + + public static void addToFavoriteGallery(Context context, int gid, String token) { + doGallery(context, gid, token, KEY_ADD_TO_FAVORITE); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/AppContext.java b/app/src/main/java/com/hippo/ehviewer/AppContext.java new file mode 100644 index 000000000..fbdc521bf --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/AppContext.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer; + +import android.app.Application; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.Typeface; +import android.view.ContextThemeWrapper; + +import com.hippo.ehviewer.cache.ImageCache; +import com.hippo.ehviewer.data.Data; +import com.hippo.ehviewer.ehclient.EhClient; +import com.hippo.ehviewer.ehclient.EhInfo; +import com.hippo.ehviewer.ehclient.ExDownloaderManager; +import com.hippo.ehviewer.network.HttpHelper; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.Crash; +import com.hippo.ehviewer.util.EhUtils; +import com.hippo.ehviewer.util.Favorite; +import com.hippo.ehviewer.util.Log; +import com.hippo.ehviewer.util.Ui; +import com.hippo.ehviewer.util.Utils; +import com.hippo.ehviewer.util.ViewUtils; +import com.hippo.ehviewer.widget.MaterialToast; +import com.hippo.ehviewer.widget.SlidingDrawerLayout; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.Thread.UncaughtExceptionHandler; + +public class AppContext extends Application implements UncaughtExceptionHandler { + + @SuppressWarnings("unused") + private static final String TAG = AppContext.class.getSimpleName(); + + private Thread.UncaughtExceptionHandler mDefaultHandler; + + private Typeface mFaceTypeface; + + @Override + public void onCreate() { + super.onCreate(); + + // Do catch error prepare + mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler(this); + + Context mContextThemeWrapper = new ContextThemeWrapper(this, R.style.AppTheme); + ViewUtils.initDialogViewInflater(this); + + // Init everything + Ui.init(mContextThemeWrapper); + Config.init(mContextThemeWrapper); + Log.init(mContextThemeWrapper); + Crash.init(mContextThemeWrapper); + EhClient.createInstance(mContextThemeWrapper); + Favorite.init(mContextThemeWrapper); + Data.createInstance(mContextThemeWrapper); + ExDownloaderManager.createInstance(mContextThemeWrapper); + MaterialToast.setContext(mContextThemeWrapper); + + mFaceTypeface = Typeface.createFromAsset(getAssets(),"fonts/face.ttf"); + + // Set drawer margin + SlidingDrawerLayout.setDefaultMinDrawerMargin(Ui.ACTION_BAR_HEIGHT * 2); + + // Add .nomedia or delete it + File nomedia = new File(Config.getDownloadPath(), ".nomedia"); + if (Config.getMediaScan()) { + nomedia.delete(); + } else { + try { + new FileOutputStream(nomedia).close(); + } catch (IOException e) {} + } + + // Update proxy urls + HttpHelper.updateProxyUrls(mContextThemeWrapper); + + String keyVersionCode = "version_code"; + int oldVersion = Config.getInt(keyVersionCode, Integer.MAX_VALUE); + // Fix <=22 login error + if (oldVersion <= 22) + EhInfo.getInstance(mContextThemeWrapper).logout(); + + // Fix <=25 Update + if (oldVersion <= 25) { + File downloadDir = new File(Config.getDownloadPath()); + String cachePath = getExternalCacheDir() != null + ? getExternalCacheDir().getPath() + : getCacheDir().getPath(); + File oldEdInfoDir = new File(cachePath, "ExDownloaderManager"); + String[] list = downloadDir.list(); + if (list != null && list.length != 0) { + for (String str : list) { + int index = str.indexOf('-'); + if (index == -1) + continue; + String gid = str.substring(0, index); + File gDir = new File(downloadDir, str); + // Move download info file + File oldInfoFile = new File(oldEdInfoDir, gid); + File newInfoFile = new File(gDir, EhUtils.EH_DOWNLOAD_FILENAME); + if (!oldInfoFile.exists()) + continue; + try { + FileOutputStream fos = new FileOutputStream(newInfoFile); + // Add last page + byte[] bs = {'0', '0', '0', '0', '0', '0', '0', '0', '\n'}; + fos.write(bs); + Utils.copy(new FileInputStream(oldInfoFile), fos); + } catch (Throwable e) { + continue; + } + } + } + // Delete old exdownload info dir + Utils.deleteDirInThread(oldEdInfoDir); + } + + // Update version code + try { + PackageInfo pi= getPackageManager().getPackageInfo(getPackageName(), 0); + Config.setInt(keyVersionCode, pi.versionCode); + } catch (NameNotFoundException e) {} + } + + /** + * {@inheritDoc} + */ + @Override + public void onLowMemory() { + ImageCache.getInstance(this).evictAll(); + super.onLowMemory(); + } + + @Override + public void uncaughtException(Thread thread, Throwable ex) { + if (!handleException(ex) && mDefaultHandler != null) { + mDefaultHandler.uncaughtException(thread, ex); + } + android.os.Process.killProcess(android.os.Process.myPid()); + System.exit(1); + } + + private boolean handleException(Throwable ex) { + if (ex == null) + return false; + Crash.saveCrashInfo2File(ex); + return true; + } + + public Typeface getFaceTypeface() { + return mFaceTypeface; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/AppHandler.java b/app/src/main/java/com/hippo/ehviewer/AppHandler.java new file mode 100644 index 000000000..a0fa0ad3f --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/AppHandler.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +import com.hippo.ehviewer.network.HttpHelper; +import com.hippo.ehviewer.util.Constants; + +public class AppHandler extends Handler { + + public static final int HTTP_HELPER_TAG = 0x1; + + private static Handler sInstance; + + public AppHandler(Looper mainLooper) { + super(mainLooper); + } + + public final static Handler getInstance() { + if (sInstance == null) + sInstance = new AppHandler(Looper.getMainLooper()); + return sInstance; + } + + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + + case HTTP_HELPER_TAG: + HttpHelper.Package p = (HttpHelper.Package)msg.obj; + HttpHelper.OnRespondListener listener = p.listener; + Object obj = p.obj; + if (msg.arg1 == Constants.TRUE) { + listener.onSuccess(obj); + } else { + listener.onFailure((String)obj); + } + break; + } + + } + +} diff --git a/app/src/main/java/com/hippo/ehviewer/ImageLoader.java b/app/src/main/java/com/hippo/ehviewer/ImageLoader.java new file mode 100644 index 000000000..f5198c0f9 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ImageLoader.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer; + +import android.content.Context; +import android.graphics.Bitmap; + +import com.hippo.ehviewer.cache.ImageCache; +import com.hippo.ehviewer.network.HttpHelper; +import com.hippo.ehviewer.util.BgThread; + +import java.util.Stack; + +public class ImageLoader { + @SuppressWarnings("unused") + private static final String TAG = ImageLoader.class.getSimpleName(); + + public static final int STATE_NONE = 0; + public static final int STATE_FROM_MEMORY = 1; + public static final int STATE_FROM_DISK = 2; + public static final int STATE_FROM_NETWORK = 3; + + private static ImageLoader sInstance; + + class LoadTask { + public String url; + public String key; + public OnGetImageListener listener; + public Bitmap bitmap; + public int state; + + public LoadTask(String url, String key, OnGetImageListener listener) { + this.url = url; + this.key = key; + this.listener = listener; + } + } + + private final Context mContext; + private final Stack mLoadTasks; + private final ImageCache mImageCache; + private final ImageDownloader mImageDownloader; + + private ImageLoader(Context context) { + mLoadTasks = new Stack(); + mImageDownloader = new ImageDownloader(); + + mContext = context; + mImageCache = ImageCache.getInstance(mContext); + + new BgThread(new LoadFromCacheTask()).start(); + } + + public final static ImageLoader getInstance(final Context context) { + if (sInstance == null) { + sInstance = new ImageLoader(context.getApplicationContext()); + } + return sInstance; + } + + public void add(String url, String key, OnGetImageListener listener) { + synchronized (mLoadTasks) { + mLoadTasks.push(new LoadTask(url, key, listener)); + mLoadTasks.notify(); + } + } + + private class LoadImageCallback implements Runnable { + + private final LoadTask mLoadTask; + + private LoadImageCallback(LoadTask task) { + mLoadTask = task; + } + + @Override + public void run() { + mLoadTask.listener.onGetImage(mLoadTask.key, mLoadTask.bitmap, mLoadTask.state); + } + } + + private class LoadFromCacheTask implements Runnable { + @Override + public void run() { + LoadTask loadTask; + while (true) { + synchronized (mLoadTasks) { + if (mLoadTasks.isEmpty()) { + try { + mLoadTasks.wait(); + } catch (InterruptedException e) {} + continue; + } + loadTask = mLoadTasks.pop(); + } + + int[] state = new int[1]; + String key = loadTask.key; + loadTask.bitmap = mImageCache.getCachedBitmap(key, state); + loadTask.state = state[0]; + + if (loadTask.bitmap != null) + AppHandler.getInstance().post(new LoadImageCallback(loadTask)); + else + mImageDownloader.add(loadTask); + } + } + } + + private class ImageDownloader { + private static final int MAX_DOWNLOAD_THREADS = 5; + private final Stack mDownloadTasks; + private int mWorkingThreadNum = 0; + + public ImageDownloader() { + mDownloadTasks = new Stack(); + } + + public void add(LoadTask loadTask) { + synchronized (mDownloadTasks) { + mDownloadTasks.push(loadTask); + if (mWorkingThreadNum < MAX_DOWNLOAD_THREADS) { + new BgThread(new DownloadImageTask()).start(); + mWorkingThreadNum++; + } + } + } + + private class DownloadImageTask implements Runnable { + @Override + public void run() { + LoadTask loadTask; + HttpHelper httpHelper = new HttpHelper(mContext); + while (true) { + synchronized (mDownloadTasks) { + if (mDownloadTasks.isEmpty()) { + loadTask = null; + mWorkingThreadNum--; + break; + } + loadTask = mDownloadTasks.pop(); + } + + // TODO use proxy to get image + loadTask.bitmap = httpHelper.getImage(loadTask.url); + if (loadTask.bitmap != null) { + mImageCache.addBitmapToCache(loadTask.key, loadTask.bitmap); + loadTask.state = STATE_FROM_NETWORK; + } else { + loadTask.state = STATE_NONE; + } + + AppHandler.getInstance().post(new LoadImageCallback(loadTask)); + } + } + } + } + + public interface OnGetImageListener { + /** + * bmp is null for fail + * @param key + * @param bmp + */ + void onGetImage(String key, Bitmap bmp, int state); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/NotificationId.java b/app/src/main/java/com/hippo/ehviewer/NotificationId.java new file mode 100644 index 000000000..278aa6210 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/NotificationId.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer; + +public class NotificationId { + public static final int UPDATE_ID = -1; + public static final int DOWNLOAD_ID = -2; +} diff --git a/app/src/main/java/com/hippo/ehviewer/SimpleSuggestionProvider.java b/app/src/main/java/com/hippo/ehviewer/SimpleSuggestionProvider.java new file mode 100644 index 000000000..981ef12dd --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/SimpleSuggestionProvider.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer; + +import android.content.SearchRecentSuggestionsProvider; + +public class SimpleSuggestionProvider extends SearchRecentSuggestionsProvider { + public final static String AUTHORITY = "com.hippo.ehviewer.SimpleSuggestionProvider"; + public final static int MODE = DATABASE_MODE_QUERIES; + + public SimpleSuggestionProvider() { + setupSuggestions(AUTHORITY, MODE); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/StartActivity.java b/app/src/main/java/com/hippo/ehviewer/StartActivity.java new file mode 100644 index 000000000..1633095ff --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/StartActivity.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer; + +import android.app.AlertDialog; +import android.content.Intent; +import android.graphics.Typeface; +import android.net.Uri; +import android.os.Bundle; +import android.text.util.Linkify; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import com.hippo.ehviewer.app.MaterialAlertDialog; +import com.hippo.ehviewer.network.Network; +import com.hippo.ehviewer.ui.AbsActivity; +import com.hippo.ehviewer.ui.GalleryListActivity; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.Crash; +import com.hippo.ehviewer.util.Secret; +import com.hippo.ehviewer.util.ViewUtils; +import com.larvalabs.svgandroid.SVGBuilder; + +public class StartActivity extends AbsActivity { + + @SuppressWarnings("unused") + private static final String TAG = StartActivity.class.getSimpleName(); + + private static final int CHECK_WARING = 0; + private static final int CHECK_ANALYTICS = 1; + private static final int CHECK_NETWORK = 2; + private static final int CHECK_CRASH = 3; + + private static final String KEY_ALLOWED = "allowed"; + private static final String KEY_SET_ANALYTICS = "set_analyics"; + + private String lastCrash; + private boolean isAnimationOver = false; + private boolean isCheckOver = false; + + private MaterialAlertDialog createWarningDialog() { + return new MaterialAlertDialog.Builder(this).setCancelable(false) + .setTitle(R.string.dailog_waring_title) + .setMessage(R.string.dailog_waring_plain) + .setPositiveButton(android.R.string.ok) + .setNegativeButton(android.R.string.cancel) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + switch (which) { + case MaterialAlertDialog.POSITIVE: + Config.setBoolean(KEY_ALLOWED, true); + check(CHECK_ANALYTICS); + break; + case MaterialAlertDialog.NEGATIVE: + finish(); + break; + } + return true; + } + }).create(); + } + + private AlertDialog createAllowAnalyicsDialog() { + return new MaterialAlertDialog.Builder(this).setCancelable(false) + .setTitle(R.string.dailog_analyics_title) + .setMessageAutoLink(Linkify.WEB_URLS) + .setMessage(R.string.dailog_analyics_plain) + .setPositiveButton(android.R.string.ok) + .setNegativeButton(android.R.string.cancel) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + switch (which) { + case MaterialAlertDialog.POSITIVE: + Config.setAllowAnalyics(true); + check(CHECK_NETWORK); + break; + case MaterialAlertDialog.NEGATIVE: + Config.setAllowAnalyics(false); + check(CHECK_NETWORK); + break; + } + return true; + } + }).create(); + } + + private AlertDialog createNetworkDialog(int state) { + int mesgId; + switch (state) { + case Network.NETWORK_STATE_MOBILE: + mesgId = R.string.dailog_network_mobile_title; + break; + case Network.NETWORK_STATE_NONE: + default: + mesgId = R.string.dailog_network_none_title; + break; + } + + return new MaterialAlertDialog.Builder(this).setCancelable(false) + .setTitle(R.string.warning).setMessage(mesgId) + .setPositiveButton(android.R.string.ok) + .setNegativeButton(android.R.string.cancel) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + switch (which) { + case MaterialAlertDialog.POSITIVE: + check(CHECK_CRASH); + break; + case MaterialAlertDialog.NEGATIVE: + finish(); + break; + } + return true; + } + }).create(); + } + + private AlertDialog createSendCrashDialog() { + View view = ViewUtils.inflateDialogView(R.layout.send_crash, false); + final EditText et = (EditText) view.findViewById(R.id.description_of_user); + return new MaterialAlertDialog.Builder(this).setCancelable(false) + .setTitle(R.string.dialog_send_crash_title) + .setView(view, true) + .setDefaultButton(MaterialAlertDialog.POSITIVE | MaterialAlertDialog.NEGATIVE) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + switch (which) { + case MaterialAlertDialog.POSITIVE: + // A wait dialog + new MaterialAlertDialog.Builder(StartActivity.this) + .setCancelable(false) + .setTitle(R.string.wait) + .setMessage(R.string.dialog_wait_send_crash_msg) + .setPositiveButton(android.R.string.ok) + .setButtonListener( + new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + checkOver(); + return true; + } + }).show(); + Intent i = new Intent(Intent.ACTION_SENDTO); + i.setData(Uri.parse("mailto:ehviewersu@gmail.com")); + i.putExtra(Intent.EXTRA_SUBJECT, "I found a bug in EhViewer !"); + i.putExtra(Intent.EXTRA_TEXT, lastCrash + "======== Description ========\n" + et.getText().toString()); + startActivity(i); + lastCrash = null; + break; + case MaterialAlertDialog.NEGATIVE: + lastCrash = null; + checkOver(); + break; + } + return true; + } + }).create(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + // Disable animate + overridePendingTransition(0, 0); + + super.onCreate(savedInstanceState); + setContentView(R.layout.start_activity); + + TextView text = (TextView) findViewById(R.id.text); + text.setTypeface(Typeface.createFromAsset(getAssets(),"fonts/GloriaHallelujah.ttf")); + ImageView image = (ImageView) findViewById(R.id.image); + image.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + image.setImageDrawable(new SVGBuilder().readFromResource(getResources(), R.raw.sad_pandroid).build().getDrawable()); + + // Update secret image here + int state = Network.getNetworkState(this); + if (state == Network.NETWORK_STATE_WIFI) { + Secret.updateSecretImage(this); + } + + AppHandler.getInstance().postDelayed(new Runnable() { + @Override + public void run() { + if (isCheckOver) + redirectTo(); + isAnimationOver = true; + } + }, 3000); + check(CHECK_WARING); + } + + /** + * Order is 1. check waring 2. check analyics 3. check network 4. check + * crash 5. check external storage + * + * @param order + */ + private void check(int order) { + switch (order) { + case CHECK_WARING: + if (!Config.getBoolean(KEY_ALLOWED, false)) { + createWarningDialog().show(); + return; + } + case CHECK_ANALYTICS: + if (!Config.getBoolean(KEY_SET_ANALYTICS, false)) { + Config.setBoolean(KEY_SET_ANALYTICS, true); + createAllowAnalyicsDialog().show(); + return; + } + case CHECK_NETWORK: + int state = Network.getNetworkState(this); + if (state != Network.NETWORK_STATE_WIFI) { + createNetworkDialog(state).show(); + return; + } + case CHECK_CRASH: + if ((lastCrash = Crash.getLastCrash()) != null) { + createSendCrashDialog().show(); + return; + } + } + checkOver(); + } + + private void checkOver() { + if (isAnimationOver) + redirectTo(); + isCheckOver = true; + } + + private void redirectTo() { + Intent intent = new Intent(this, GalleryListActivity.class); + startActivity(intent); + finish(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/UpdateHelper.java b/app/src/main/java/com/hippo/ehviewer/UpdateHelper.java new file mode 100644 index 000000000..a0fddc623 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/UpdateHelper.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer; + +import java.io.File; + +import org.json.JSONException; +import org.json.JSONObject; + +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.Uri; +import android.support.v4.app.NotificationCompat; + +import com.hippo.ehviewer.ehclient.EhClient; +import com.hippo.ehviewer.network.HttpHelper; +import com.hippo.ehviewer.util.BgThread; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.Utils; + +// TODO return onFailure when downloading update or check update +// TODO add disable to achieve + +public class UpdateHelper implements Runnable { + @SuppressWarnings("unused") + private static final String TAG = "UpdateHelper"; + + private static final String HEADER = "Ehviewer-"; + private static final String FOOTER = ".apk"; + + + private final AppContext mAppContext; + private OnCheckUpdateListener mListener; + + private static boolean mEnabled = true; + + public interface OnCheckUpdateListener { + public void onSuccess(String version, long size, String url, String fileName, String info); + public void onNoUpdate(); + public void onFailure(String eMsg); + } + + public UpdateHelper(AppContext appContext) { + mAppContext = appContext; + } + + public UpdateHelper SetOnCheckUpdateListener(OnCheckUpdateListener listener) { + this.mListener = listener; + return this; + } + + public void autoCheckUpdate() { + if (Config.getUpdateDate() < Utils.getDate()) + checkUpdate(); + } + + public static void setEnabled(boolean enabled) { + mEnabled = enabled; + } + + + class CheckUpdatePackage { + public OnCheckUpdateListener listener; + public String body; + public String emesg; + } + + private void send(CheckUpdatePackage _package) { + AppHandler.getInstance().post(new Respond(_package)); + } + + @Override + public void run() { + CheckUpdatePackage _package = new CheckUpdatePackage(); + _package.listener = mListener; + final HttpHelper hp = new HttpHelper(mAppContext); + if (!mEnabled) { + _package.body = "working"; + send(_package); + return; + } + + mEnabled = false; + + PackageManager pm = mAppContext.getPackageManager(); + PackageInfo pi = null; + try { + pi = pm.getPackageInfo(mAppContext.getPackageName(), PackageManager.GET_ACTIVITIES); + + JSONObject jo = new JSONObject(); + jo.put("method", "update"); + + JSONObject detailJO = new JSONObject(); + detailJO.put("version", pi.versionName); + detailJO.put("server", Config.getUpdateServer()); + + jo.put("detail", detailJO); + + _package.body = hp.postJson(EhClient.API_EHVIEWER, jo); + _package.emesg = hp.getEMsg(); + send(_package); + } catch (NameNotFoundException e) { + e.printStackTrace(); + _package.body = "NameNotFound"; + send(_package); + } catch (JSONException e) { + e.printStackTrace(); + _package.body = "JSONException"; + send(_package); + } + } + + public void checkUpdate() { + new BgThread(this).start(); + } + + private class Respond implements Runnable { + + CheckUpdatePackage mPackage; + + public Respond(CheckUpdatePackage _package) { + mPackage = _package; + } + + @Override + public void run() { + OnCheckUpdateListener listener = mPackage.listener; + String body = mPackage.body; + if (body != null) { + Config.setUpdateDate(); + + if (listener == null) + return; + + if (body.equals("working")) { + listener.onFailure("正在检查更新或者下载更新"); // TODO + } else if (body.equals("NameNotFound")) { + listener.onFailure("NameNotFound"); // TODO + } else if (body.equals("JSONException")) { + listener.onFailure("JSONException"); // TODO + } else { + try { + JSONObject jo = new JSONObject(body); + JSONObject updateJO = jo.getJSONObject("update"); + + if (updateJO.has("error")) { + listener.onFailure(updateJO.getString("error")); + } else { + String version = updateJO.getString("version"); + if (version.equals("none")) { + listener.onNoUpdate(); + } else { + long size = updateJO.getLong("size"); + String url = updateJO.getString("url"); + String info = updateJO.getString("info"); + + listener.onSuccess(version, size, url, HEADER + version + FOOTER, info); + } + } + } catch (JSONException e) { + e.printStackTrace(); + listener.onFailure("JSONException"); + } + } + } + } + } + + public static class UpdateListener implements HttpHelper.OnDownloadListener { + private final Context mContext; + private final String mFileName; + + private final NotificationManager mNotifyManager; + + public UpdateListener(Context context, String fileName) { + mContext = context; + mFileName = fileName; + mNotifyManager = (NotificationManager) + mContext.getSystemService(Context.NOTIFICATION_SERVICE); + } + + @Override + public void onDownloadStartConnect() { + NotificationCompat.Builder builder = + new NotificationCompat.Builder(mContext) + .setSmallIcon(android.R.drawable.stat_sys_download) + .setContentTitle(mContext.getString(R.string.start_update)) + .setProgress(0, 0, true) + .setOngoing(true) + .setTicker(mContext.getString(R.string.start_update)); + mNotifyManager.notify(NotificationId.UPDATE_ID, builder.build()); + } + + @Override + public void onDownloadStartDownload(int totalSize) { + NotificationCompat.Builder builder = + new NotificationCompat.Builder(mContext) + .setSmallIcon(android.R.drawable.stat_sys_download) + .setContentTitle(mContext.getString(R.string.download_update)) + .setContentText(String.format("%.2f / %.2f KB", 0/1024.0f, totalSize/1024.0f)) + .setProgress(totalSize, 0, false) + .setOngoing(true); + mNotifyManager.notify(NotificationId.UPDATE_ID, builder.build()); + } + + @Override + public void onDownloadStatusUpdate( + int downloadSize, int totalSize) { + NotificationCompat.Builder builder = + new NotificationCompat.Builder(mContext) + .setSmallIcon(android.R.drawable.stat_sys_download) + .setContentTitle(mContext.getString(R.string.download_update)) + .setContentText(String.format("%.2f / %.2f KB", downloadSize/1024.0f, totalSize/1024.0f)) + .setProgress(totalSize, downloadSize, false) + .setOngoing(true); + mNotifyManager.notify(NotificationId.UPDATE_ID, builder.build()); + } + + @Override + public void onDownloadOver(int status, String eMsg) { + if (status == HttpHelper.DOWNLOAD_OK_CODE) { + mNotifyManager.cancel(NotificationId.UPDATE_ID); + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(Uri.fromFile(new File(Config.getDownloadPath(), mFileName)), + "application/vnd.android.package-archive"); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + } else { + NotificationCompat.Builder builder = + new NotificationCompat.Builder(mContext) + .setSmallIcon(android.R.drawable.stat_sys_warning) + .setContentTitle(mContext.getString(R.string.update_failed)) + .setContentText(eMsg) + .setOngoing(false); + mNotifyManager.notify(NotificationId.UPDATE_ID, builder.build()); + } + setEnabled(true); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/app/DirSelectDialog.java b/app/src/main/java/com/hippo/ehviewer/app/DirSelectDialog.java new file mode 100644 index 000000000..57a017b74 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/app/DirSelectDialog.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.app; + +import android.view.View; +import android.widget.AdapterView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.util.Ui; +import com.hippo.ehviewer.util.ViewUtils; +import com.hippo.ehviewer.widget.FileExplorerView; + +public class DirSelectDialog extends MaterialAlertDialog { + + private final FileExplorerView mFileExplorerView; + + protected DirSelectDialog(Builder builder, FileExplorerView fileExplorerView) { + super(builder); + + mFileExplorerView = fileExplorerView; + } + + public static DirSelectDialog create(Builder builder, String path) { + View view = ViewUtils.inflateDialogView(R.layout.dir_selection, builder.mDarkTheme); + final FileExplorerView fileExplorerView = (FileExplorerView) view.findViewById(R.id.file_list); + final TextView warningTextView = (TextView) view.findViewById(R.id.warning); + final TextView pathTextView = (TextView) view.findViewById(R.id.path); + + if (path != null) + fileExplorerView.setPath(path); + fileExplorerView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + fileExplorerView.onItemClick(parent, view, position, id); + pathTextView.setText(fileExplorerView.getCurPath()); + if (fileExplorerView.canWrite()) + warningTextView.setVisibility(View.GONE); + else + warningTextView.setVisibility(View.VISIBLE); + } + }); + + pathTextView.setText(fileExplorerView.getCurPath()); + if (fileExplorerView.canWrite()) + warningTextView.setVisibility(View.GONE); + else + warningTextView.setVisibility(View.VISIBLE); + + builder.setView(view, false, + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, Ui.dp2pix(360))); + + return new DirSelectDialog(builder, fileExplorerView); + } + + public String getCurrentPath() { + return mFileExplorerView.getCurPath(); + } + + public void refresh() { + mFileExplorerView.refresh(); + } + + public boolean canWrite() { + return mFileExplorerView.canWrite(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/app/MaterialAlertDialog.java b/app/src/main/java/com/hippo/ehviewer/app/MaterialAlertDialog.java new file mode 100644 index 000000000..ae75cf85a --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/app/MaterialAlertDialog.java @@ -0,0 +1,504 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.app; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.util.ViewUtils; + +public class MaterialAlertDialog extends AlertDialog implements + View.OnClickListener, AdapterView.OnItemClickListener { + + public final static int ACTION = 0x1; + public final static int POSITIVE = 0x2; + public final static int NEGATIVE = 0x4; + public final static int NEUTRAL = 0x8; + + private final Context mContext; + + private final Builder mBuilder; + + private View mRootView; + private TextView mTitle; + private Button mActionButton; + private Button mPositiveButton; + private Button mNegativeButton; + private Button mNeutralButton; + + protected MaterialAlertDialog(Builder builder) { + super(builder.mContext); + mContext = builder.mContext; + mBuilder = builder; + + init(); + } + + @SuppressLint("InflateParams") + private void init() { + LayoutInflater inflater = LayoutInflater.from(mContext); + mRootView = inflater.inflate(R.layout.alert_dialog_material, null); + + // Check in scroll view or not + boolean inScrollView; + if (mBuilder.mCustomView != null) + inScrollView = mBuilder.mInScrollView; + else if (mBuilder.mAdapter != null) + inScrollView = false; + else + inScrollView = true; + // Get widget + LinearLayout topPanel; + TextView message = null; + ListView list = null; + if (inScrollView) { + mRootView.findViewById(R.id.top_panel_noscroll).setVisibility( + View.GONE); + mTitle = (TextView) mRootView.findViewById(R.id.title); + topPanel = (LinearLayout) mRootView.findViewById(R.id.top_panel); + message = (TextView) mRootView.findViewById(R.id.message); + } else { + mRootView.findViewById(R.id.scroll_view).setVisibility(View.GONE); + mTitle = (TextView) mRootView.findViewById(R.id.title2); + topPanel = (LinearLayout) mRootView + .findViewById(R.id.top_panel_noscroll); + list = (ListView) mRootView.findViewById(R.id.list); + } + // Title + if (mBuilder.mTitle == null) + mTitle.setVisibility(View.GONE); + else + mTitle.setText(mBuilder.mTitle); + // Content + if (mBuilder.mCustomView != null) { + // CustomView + if (message != null) + message.setVisibility(View.GONE); + if (list != null) + list.setVisibility(View.GONE); + LinearLayout.LayoutParams lp = mBuilder.mCustomLp; + if (lp == null) + lp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + mBuilder.mCustomView.setId(R.id.custom); + topPanel.addView(mBuilder.mCustomView, lp); + } else if (mBuilder.mAdapter != null) { + // List + if (message != null) + message.setVisibility(View.GONE); + list.setAdapter(mBuilder.mAdapter); + list.setOnItemClickListener(this); + if (mBuilder.mIsSingleChoice) { + list.setChoiceMode(ListView.CHOICE_MODE_SINGLE); + if (mBuilder.mCheckedItem > -1) { + list.setItemChecked(mBuilder.mCheckedItem, true); + list.setSelection(mBuilder.mCheckedItem); + } + } + } else { + // Message + if (list != null) + list.setVisibility(View.GONE); + if (mBuilder.mAutoLinkMask != -1) + message.setAutoLinkMask(mBuilder.mAutoLinkMask); + message.setText(mBuilder.mMessage); + } + + // Buttons + View buttonPanel = mRootView.findViewById(R.id.button_panel); + View nonActionPanel = mRootView.findViewById(R.id.non_action_panel); + mActionButton = (Button) mRootView.findViewById(R.id.button_action); + mPositiveButton = (Button) mRootView.findViewById(R.id.button_positive); + mNegativeButton = (Button) mRootView.findViewById(R.id.button_negative); + mNeutralButton = (Button) mRootView.findViewById(R.id.button_neutral); + boolean hasAction = mBuilder.mActionButtonText != null; + boolean hasNonAction = mBuilder.mPositiveButtonText != null + || mBuilder.mNegativeButtonText != null + || mBuilder.mNeutralButtonText != null; + boolean hasButton = hasAction || hasNonAction; + if (hasButton) { + if (hasAction) { + mActionButton.setText(mBuilder.mActionButtonText); + mActionButton.setOnClickListener(this); + } else { + mActionButton.setVisibility(View.GONE); + } + if (hasNonAction) { + // Positive + if (mBuilder.mPositiveButtonText != null) { + mPositiveButton.setText(mBuilder.mPositiveButtonText); + mPositiveButton.setOnClickListener(this); + } else { + mPositiveButton.setVisibility(View.GONE); + } + // Negative + if (mBuilder.mNegativeButtonText != null) { + mNegativeButton.setText(mBuilder.mNegativeButtonText); + mNegativeButton.setOnClickListener(this); + } else { + mNegativeButton.setVisibility(View.GONE); + } + // Neutral + if (mBuilder.mNeutralButtonText != null) { + mNeutralButton.setText(mBuilder.mNeutralButtonText); + mNeutralButton.setOnClickListener(this); + } else { + mNeutralButton.setVisibility(View.GONE); + } + } else { + nonActionPanel.setVisibility(View.GONE); + } + } else { + buttonPanel.setVisibility(View.GONE); + } + + setView(mRootView); + + setCancelable(mBuilder.mCancelable); + if (mBuilder.mOnCancelListener != null) + setOnCancelListener(mBuilder.mOnCancelListener); + if (mBuilder.mOnDismissListener != null) + setOnDismissListener(mBuilder.mOnDismissListener); + } + + @Override + public void setTitle(CharSequence title) { + mTitle.setText(title); + } + + @Override + public void setTitle(int resId) { + setTitle(getContext().getString(resId)); + } + + public void setView(View view, boolean inScrollView) { + setView(view, inScrollView, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + } + + public void setView(View view, boolean inScrollView, + LinearLayout.LayoutParams lp) { + mRootView.findViewById(inScrollView ? R.id.top_panel_noscroll : R.id.scroll_view).setVisibility(View.GONE); + mRootView.findViewById(inScrollView ? R.id.scroll_view : R.id.top_panel_noscroll).setVisibility(View.VISIBLE); + mRootView.findViewById(R.id.message).setVisibility(View.GONE); + mRootView.findViewById(R.id.list).setVisibility(View.GONE); + LinearLayout topPanel = (LinearLayout) mRootView.findViewById(inScrollView ? R.id.top_panel : R.id.top_panel_noscroll); + topPanel.addView(view, lp); + // Title + TextView oldTitle = mTitle; + mTitle = (TextView) mRootView.findViewById(inScrollView ? R.id.title : R.id.title2); + if (mTitle != oldTitle && oldTitle != null) + mTitle.setText(oldTitle.getText()); + } + + @Override + public void onClick(View v) { + boolean close = true; + + if (mBuilder.mButtonListener != null) { + if (v == mActionButton) + close = mBuilder.mButtonListener.onClick(this, ACTION); + else if (v == mPositiveButton) + close = mBuilder.mButtonListener.onClick(this, POSITIVE); + else if (v == mNegativeButton) + close = mBuilder.mButtonListener.onClick(this, NEGATIVE); + else if (v == mNeutralButton) + close = mBuilder.mButtonListener.onClick(this, NEUTRAL); + } + + if (close) + dismiss(); + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, + long id) { + + boolean close = false; + + if (mBuilder.mOnClickListener != null) + close = mBuilder.mOnClickListener.onClick(this, position); + + if (close) + dismiss(); + } + + public static interface OnClickListener { + public boolean onClick(MaterialAlertDialog dialog, int which); + } + + public static class Builder { + + protected Context mContext; + + protected boolean mDarkTheme = false; + protected CharSequence mTitle; + protected CharSequence mMessage; + protected CharSequence mActionButtonText; + protected CharSequence mPositiveButtonText; + protected CharSequence mNegativeButtonText; + protected CharSequence mNeutralButtonText; + protected OnClickListener mButtonListener; + protected View mCustomView; + protected LinearLayout.LayoutParams mCustomLp; + protected boolean mInScrollView = true; + protected boolean mCancelable = true; + protected int mAutoLinkMask = -1; + + protected OnCancelListener mOnCancelListener; + + protected ListAdapter mAdapter; + protected OnClickListener mOnClickListener; + protected int mCheckedItem; + protected boolean mIsSingleChoice; + + protected DialogInterface.OnDismissListener mOnDismissListener; + + public Builder(Context context) { + mContext = context; + } + + public Builder setDrakTheme(boolean darkTheme) { + mDarkTheme = darkTheme; + return this; + } + + public Builder setTitle(CharSequence title) { + mTitle = title; + return this; + } + + public Builder setTitle(int resId) { + return setTitle(mContext.getString(resId)); + } + + public Builder setMessage(CharSequence message) { + mMessage = message; + return this; + } + + public Builder setMessage(int resId) { + return setMessage(mContext.getString(resId)); + } + + public Builder setActionButton(CharSequence text) { + mActionButtonText = text; + return this; + } + + public Builder setActionButton(int resId) { + return setActionButton(mContext.getString(resId)); + } + + public Builder setPositiveButton(CharSequence text) { + mPositiveButtonText = text; + return this; + } + + public Builder setPositiveButton(int resId) { + return setPositiveButton(mContext.getString(resId)); + } + + public Builder setNegativeButton(CharSequence text) { + mNegativeButtonText = text; + return this; + } + + public Builder setNegativeButton(int resId) { + return setNegativeButton(mContext.getString(resId)); + } + + public Builder setNeutralButton(CharSequence text) { + mNeutralButtonText = text; + return this; + } + + public Builder setNeutralButton(int resId) { + return setNeutralButton(mContext.getString(resId)); + } + + public Builder setDefaultButton(int b) { + if ((b & POSITIVE) == POSITIVE) + mPositiveButtonText = mContext.getString(android.R.string.ok); + if ((b & NEGATIVE) == NEGATIVE) + mNegativeButtonText = mContext.getString(android.R.string.cancel); + return this; + } + + public Builder setButtonListener(OnClickListener l) { + mButtonListener = l; + return this; + } + + /** + * Custom view id will be R.id.custom + * + * @param view + * @param inScrollView + * @return + */ + public Builder setView(View view, boolean inScrollView) { + mCustomView = view; + mInScrollView = inScrollView; + return this; + } + + /** + * Custom view id will be R.id.custom + * + * @param view + * @param inScrollView + * @param lp + * @return + */ + public Builder setView(View view, boolean inScrollView, + LinearLayout.LayoutParams lp) { + mCustomView = view; + mInScrollView = inScrollView; + mCustomLp = lp; + return this; + } + + public Builder setView(int resId, boolean inScrollView, ViewHolder vh) { + mCustomView = ViewUtils.inflateDialogView(resId, mDarkTheme); + mInScrollView = inScrollView; + if (vh != null) + vh.setView(mCustomView); + return this; + } + + public Builder setCancelable(boolean cancelable) { + mCancelable = cancelable; + return this; + } + + public Builder setOnCancelListener(OnCancelListener onCancelListener) { + mOnCancelListener = onCancelListener; + return this; + } + + public Builder setMessageAutoLink(int mask) { + mAutoLinkMask = mask; + return this; + } + + public Builder setItems(int itemsId, final OnClickListener listener) { + mAdapter = new ArrayAdapter( + mDarkTheme ? ViewUtils.sDarkContextThemeWrapper : ViewUtils.sContextThemeWrapper, + R.layout.select_dialog_item, android.R.id.text1, mContext + .getResources().getTextArray(itemsId)); + mOnClickListener = listener; + mIsSingleChoice = false; + return this; + } + + public Builder setItems(CharSequence[] items, + final OnClickListener listener) { + mAdapter = new ArrayAdapter( + mDarkTheme ? ViewUtils.sDarkContextThemeWrapper : ViewUtils.sContextThemeWrapper, + R.layout.select_dialog_item, android.R.id.text1, items); + mOnClickListener = listener; + mIsSingleChoice = false; + return this; + } + + public Builder setAdapter(final ListAdapter adapter, + final OnClickListener listener) { + mAdapter = adapter; + mOnClickListener = listener; + mIsSingleChoice = false; + return this; + } + + public Builder setSingleChoiceItems(int itemsId, int checkedItem, + final OnClickListener listener) { + mAdapter = new ArrayAdapter( + mDarkTheme ? ViewUtils.sDarkContextThemeWrapper : ViewUtils.sContextThemeWrapper, + R.layout.select_dialog_singlechoice, android.R.id.text1, + mContext.getResources().getTextArray(itemsId)); + mOnClickListener = listener; + mCheckedItem = checkedItem; + mIsSingleChoice = true; + return this; + } + + public Builder setSingleChoiceItems(CharSequence[] items, + int checkedItem, final OnClickListener listener) { + mAdapter = new ArrayAdapter( + mDarkTheme ? ViewUtils.sDarkContextThemeWrapper : ViewUtils.sContextThemeWrapper, + R.layout.select_dialog_singlechoice, android.R.id.text1, + items); + mOnClickListener = listener; + mCheckedItem = checkedItem; + mIsSingleChoice = true; + return this; + } + + public Builder setSingleChoiceItems(ListAdapter adapter, + int checkedItem, final OnClickListener listener) { + mAdapter = adapter; + mOnClickListener = listener; + mCheckedItem = checkedItem; + mIsSingleChoice = true; + return this; + } + + public Builder setOnDismissListener(DialogInterface.OnDismissListener l) { + mOnDismissListener = l; + return this; + } + + public MaterialAlertDialog create() { + mContext = new ContextThemeWrapper(mContext, mDarkTheme ? + R.style.AppTheme_Dark_Dialog : R.style.AppTheme_Dialog); + return new MaterialAlertDialog(this); + } + + public MaterialAlertDialog show() { + MaterialAlertDialog dialog = create(); + dialog.show(); + return dialog; + } + } + + public static class ViewHolder { + private View mView; + + private void setView(View v) { + mView = v; + } + + public View getView() { + return mView; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/app/MaterialProgressDialog.java b/app/src/main/java/com/hippo/ehviewer/app/MaterialProgressDialog.java new file mode 100644 index 000000000..2fb17b543 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/app/MaterialProgressDialog.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.app; + +import java.text.NumberFormat; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Message; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.style.StyleSpan; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.hippo.ehviewer.R; + +public class MaterialProgressDialog extends MaterialAlertDialog { + + + /** Creates a ProgressDialog with a circular, spinning progress + * bar. This is the default. + */ + public static final int STYLE_SPINNER = 0; + + /** Creates a ProgressDialog with a horizontal progress bar. + */ + public static final int STYLE_HORIZONTAL = 1; + + private ProgressBar mProgress; + private TextView mMessageView; + + private int mProgressStyle = STYLE_HORIZONTAL; + private TextView mProgressNumber; + private String mProgressNumberFormat; + private TextView mProgressPercent; + private NumberFormat mProgressPercentFormat; + + private int mMax; + private int mProgressVal; + private int mSecondaryProgressVal; + private boolean mIndeterminate; + + private boolean mHasStarted; + private Handler mViewUpdateHandler; + + + @SuppressLint({ "InflateParams", "HandlerLeak" }) + protected MaterialProgressDialog(Builder builder) { + super(builder); + initFormats(); + + LayoutInflater inflater = LayoutInflater.from(builder.mContext); + if (mProgressStyle == STYLE_HORIZONTAL) { + + /* Use a separate handler to update the text views as they + * must be updated on the same thread that created them. + */ + mViewUpdateHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + + /* Update the number and percent */ + int progress = mProgress.getProgress(); + int max = mProgress.getMax(); + if (mProgressNumberFormat != null) { + String format = mProgressNumberFormat; + mProgressNumber.setText(String.format(format, progress, max)); + } else { + mProgressNumber.setText(""); + } + if (mProgressPercentFormat != null) { + double percent = (double) progress / (double) max; + SpannableString tmp = new SpannableString(mProgressPercentFormat.format(percent)); + tmp.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), + 0, tmp.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + mProgressPercent.setText(tmp); + } else { + mProgressPercent.setText(""); + } + } + }; + View view = inflater.inflate(R.layout.alert_dialog_progress, null); + mProgress = (ProgressBar) view.findViewById(R.id.progress); + mProgressNumber = (TextView) view.findViewById(R.id.progress_number); + mProgressPercent = (TextView) view.findViewById(R.id.progress_percent); + setView(view, false); + } else { + View view = inflater.inflate(R.layout.progress_dialog, null); + mProgress = (ProgressBar) view.findViewById(R.id.progress); + mMessageView = (TextView) view.findViewById(R.id.message); + setView(view, false); + } + } + + private void initFormats() { + mProgressNumberFormat = "%1d/%2d"; + mProgressPercentFormat = NumberFormat.getPercentInstance(); + mProgressPercentFormat.setMaximumFractionDigits(0); + } + + + public static MaterialProgressDialog create(Context context, CharSequence title) { + return create(context, title, false, null); + } + + public static MaterialProgressDialog create(Context context, CharSequence title, + boolean cancelable) { + return create(context, title, cancelable, null); + } + + public static MaterialProgressDialog create(Context context, CharSequence title, + boolean cancelable, OnCancelListener cancelListener) { + + MaterialProgressDialog.Builder builder = new MaterialProgressDialog.Builder(context); + builder.setTitle(title); + builder.setCancelable(cancelable); + builder.setOnCancelListener(cancelListener); + return new MaterialProgressDialog(builder); + } + + @Override + public void onStart() { + super.onStart(); + mHasStarted = true; + } + + @Override + protected void onStop() { + super.onStop(); + mHasStarted = false; + } + + public void setProgress(int value) { + if (mHasStarted) { + mProgress.setProgress(value); + onProgressChanged(); + } else { + mProgressVal = value; + } + } + + public void setSecondaryProgress(int secondaryProgress) { + if (mProgress != null) { + mProgress.setSecondaryProgress(secondaryProgress); + onProgressChanged(); + } else { + mSecondaryProgressVal = secondaryProgress; + } + } + + public int getProgress() { + if (mProgress != null) { + return mProgress.getProgress(); + } + return mProgressVal; + } + + public int getSecondaryProgress() { + if (mProgress != null) { + return mProgress.getSecondaryProgress(); + } + return mSecondaryProgressVal; + } + + public int getMax() { + if (mProgress != null) { + return mProgress.getMax(); + } + return mMax; + } + + public void setMax(int max) { + if (mProgress != null) { + mProgress.setMax(max); + onProgressChanged(); + } else { + mMax = max; + } + } + + public void incrementProgressBy(int diff) { + if (mProgress != null) { + mProgress.incrementProgressBy(diff); + onProgressChanged(); + } + } + + public void incrementSecondaryProgressBy(int diff) { + if (mProgress != null) { + mProgress.incrementSecondaryProgressBy(diff); + onProgressChanged(); + } + } + + public void setProgressDrawable(Drawable d) { + if (mProgress != null) { + mProgress.setProgressDrawable(d); + } + } + + public void setIndeterminateDrawable(Drawable d) { + if (mProgress != null) { + mProgress.setIndeterminateDrawable(d); + } + } + + public void setIndeterminate(boolean indeterminate) { + if (mProgress != null) { + mProgress.setIndeterminate(indeterminate); + } + } + + public boolean isIndeterminate() { + if (mProgress != null) { + return mProgress.isIndeterminate(); + } + return mIndeterminate; + } + + @Override + public void setMessage(CharSequence message) { + if (mProgress != null) { + if (mProgressStyle == STYLE_HORIZONTAL) { + super.setMessage(message); + } else { + mMessageView.setText(message); + } + } + } + + public void setProgressStyle(int style) { + mProgressStyle = style; + } + + /** + * Change the format of the small text showing current and maximum units + * of progress. The default is "%1d/%2d". + * Should not be called during the number is progressing. + * @param format A string passed to {@link String#format String.format()}; + * use "%1d" for the current number and "%2d" for the maximum. If null, + * nothing will be shown. + */ + public void setProgressNumberFormat(String format) { + mProgressNumberFormat = format; + onProgressChanged(); + } + + /** + * Change the format of the small text showing the percentage of progress. + * The default is + * {@link NumberFormat#getPercentInstance() NumberFormat.getPercentageInstnace().} + * Should not be called during the number is progressing. + * @param format An instance of a {@link NumberFormat} to generate the + * percentage text. If null, nothing will be shown. + */ + public void setProgressPercentFormat(NumberFormat format) { + mProgressPercentFormat = format; + onProgressChanged(); + } + + private void onProgressChanged() { + if (mProgressStyle == STYLE_HORIZONTAL) { + if (mViewUpdateHandler != null && !mViewUpdateHandler.hasMessages(0)) { + mViewUpdateHandler.sendEmptyMessage(0); + } + } + } + +} diff --git a/app/src/main/java/com/hippo/ehviewer/cache/DiskLruCache.java b/app/src/main/java/com/hippo/ehviewer/cache/DiskLruCache.java new file mode 100644 index 000000000..f351fc180 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/cache/DiskLruCache.java @@ -0,0 +1,969 @@ +/* + * Copyright (C) 2011 The Android Open Source Project Licensed under the Apache + * License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law + * or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +package com.hippo.ehviewer.cache; + +import java.io.BufferedInputStream; +import java.io.BufferedWriter; +import java.io.Closeable; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Array; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + ****************************************************************************** Taken from the JB source code, can be found in: + * libcore/luni/src/main/java/libcore/io/DiskLruCache.java or direct link: + * https: + * //android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/ + * main/java/libcore/io/DiskLruCache.java A cache that uses a bounded amount of + * space on a filesystem. Each cache entry has a string key and a fixed number + * of values. Values are byte sequences, accessible as streams or files. Each + * value must be between {@code 0} and {@code Integer.MAX_VALUE} bytes in + * length. + *

    + * The cache stores its data in a directory on the filesystem. This directory + * must be exclusive to the cache; the cache may delete or overwrite files from + * its directory. It is an error for multiple processes to use the same cache + * directory at the same time. + *

    + * This cache limits the number of bytes that it will store on the filesystem. + * When the number of stored bytes exceeds the limit, the cache will remove + * entries in the background until the limit is satisfied. The limit is not + * strict: the cache may temporarily exceed it while waiting for files to be + * deleted. The limit does not include filesystem overhead or the cache journal + * so space-sensitive applications should set a conservative limit. + *

    + * Clients call {@link #edit} to create or update the values of an entry. An + * entry may have only one editor at one time; if a value is not available to be + * edited then {@link #edit} will return null. + *

      + *
    • When an entry is being created it is necessary to supply + * a full set of values; the empty value should be used as a placeholder if + * necessary. + *
    • When an entry is being edited, it is not necessary to + * supply data for every value; values default to their previous value. + *
    + * Every {@link #edit} call must be matched by a call to {@link Editor#commit} + * or {@link Editor#abort}. Committing is atomic: a read observes the full set + * of values as they were before or after the commit, but never a mix of values. + *

    + * Clients call {@link #get} to read a snapshot of an entry. The read will + * observe the value at the time that {@link #get} was called. Updates and + * removals after the call do not impact ongoing reads. + *

    + * This class is tolerant of some I/O errors. If files are missing from the + * filesystem, the corresponding entries will be dropped from the cache. If an + * error occurs while writing a cache value, the edit will fail silently. + * Callers should handle other problems by catching {@code IOException} and + * responding appropriately. + */ +public final class DiskLruCache implements Closeable { + static final String JOURNAL_FILE = "journal"; + + static final String JOURNAL_FILE_TMP = "journal.tmp"; + + static final String MAGIC = "libcore.io.DiskLruCache"; + + static final String VERSION_1 = "1"; + + static final long ANY_SEQUENCE_NUMBER = -1; + + private static final String CLEAN = "CLEAN"; + + private static final String DIRTY = "DIRTY"; + + private static final String REMOVE = "REMOVE"; + + private static final String READ = "READ"; + + private static final Charset UTF_8 = Charset.forName("UTF-8"); + + private static final int IO_BUFFER_SIZE = 8 * 1024; + + /* + * This cache uses a journal file named "journal". A typical journal file + * looks like this: libcore.io.DiskLruCache 1 100 2 CLEAN + * 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 DIRTY + * 335c4c6028171cfddfbaae1a9c313c52 CLEAN 335c4c6028171cfddfbaae1a9c313c52 + * 3934 2342 REMOVE 335c4c6028171cfddfbaae1a9c313c52 DIRTY + * 1ab96a171faeeee38496d8b330771a7a CLEAN 1ab96a171faeeee38496d8b330771a7a + * 1600 234 READ 335c4c6028171cfddfbaae1a9c313c52 READ + * 3400330d1dfc7f3f7f4b8d4d803dfcf6 The first five lines of the journal form + * its header. They are the constant string "libcore.io.DiskLruCache", the + * disk cache's version, the application's version, the value count, and a + * blank line. Each of the subsequent lines in the file is a record of the + * state of a cache entry. Each line contains space-separated values: a + * state, a key, and optional state-specific values. o DIRTY lines track + * that an entry is actively being created or updated. Every successful + * DIRTY action should be followed by a CLEAN or REMOVE action. DIRTY lines + * without a matching CLEAN or REMOVE indicate that temporary files may need + * to be deleted. o CLEAN lines track a cache entry that has been + * successfully published and may be read. A publish line is followed by the + * lengths of each of its values. o READ lines track accesses for LRU. o + * REMOVE lines track entries that have been deleted. The journal file is + * appended to as cache operations occur. The journal may occasionally be + * compacted by dropping redundant lines. A temporary file named + * "journal.tmp" will be used during compaction; that file should be deleted + * if it exists when the cache is opened. + */ + + private final File directory; + + private final File journalFile; + + private final File journalFileTmp; + + private final int appVersion; + + private final long maxSize; + + private final int valueCount; + + private long size = 0; + + private Writer journalWriter; + + private final LinkedHashMap lruEntries = new LinkedHashMap(0, + 0.75f, true); + + private int redundantOpCount; + + /** + * To differentiate between old and current snapshots, each entry is given a + * sequence number each time an edit is committed. A snapshot is stale if + * its sequence number is not equal to its entry's sequence number. + */ + private long nextSequenceNumber = 0; + + /* From java.util.Arrays */ + @SuppressWarnings("unchecked") + private static T[] copyOfRange(final T[] original, final int start, final int end) { + final int originalLength = original.length; // For exception priority + // compatibility. + if (start > end) { + throw new IllegalArgumentException(); + } + if (start < 0 || start > originalLength) { + throw new ArrayIndexOutOfBoundsException(); + } + final int resultLength = end - start; + final int copyLength = Math.min(resultLength, originalLength - start); + final T[] result = (T[])Array.newInstance(original.getClass().getComponentType(), + resultLength); + System.arraycopy(original, start, result, 0, copyLength); + return result; + } + + /** + * Returns the remainder of 'reader' as a string, closing it when done. + */ + public static String readFully(final Reader reader) throws IOException { + try { + final StringWriter writer = new StringWriter(); + final char[] buffer = new char[1024]; + int count; + while ((count = reader.read(buffer)) != -1) { + writer.write(buffer, 0, count); + } + return writer.toString(); + } finally { + reader.close(); + } + } + + /** + * Returns the ASCII characters up to but not including the next "\r\n", or + * "\n". + * + * @throws java.io.EOFException if the stream is exhausted before the next + * newline character. + */ + public static String readAsciiLine(final InputStream in) throws IOException { + // TODO: support UTF-8 here instead + + final StringBuilder result = new StringBuilder(80); + while (true) { + final int c = in.read(); + if (c == -1) { + throw new EOFException(); + } else if (c == '\n') { + break; + } + + result.append((char)c); + } + final int length = result.length(); + if (length > 0 && result.charAt(length - 1) == '\r') { + result.setLength(length - 1); + } + return result.toString(); + } + + /** + * Closes 'closeable', ignoring any checked exceptions. Does nothing if + * 'closeable' is null. + */ + public static void closeQuietly(final Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (final RuntimeException rethrown) { + throw rethrown; + } catch (final Exception ignored) { + } + } + } + + /** + * Recursively delete everything in {@code dir}. + */ + // TODO: this should specify paths as Strings rather than as Files + public static void deleteContents(final File dir) throws IOException { + final File[] files = dir.listFiles(); + if (files == null) { + throw new IllegalArgumentException("not a directory: " + dir); + } + for (final File file : files) { + if (file.isDirectory()) { + deleteContents(file); + } + if (!file.delete()) { + throw new IOException("failed to delete file: " + file); + } + } + } + + /** This cache uses a single background thread to evict entries. */ + private final ExecutorService executorService = new ThreadPoolExecutor(0, 1, 60L, + TimeUnit.SECONDS, new LinkedBlockingQueue()); + + private final Callable cleanupCallable = new Callable() { + @Override + public Void call() throws Exception { + synchronized (DiskLruCache.this) { + if (journalWriter == null) { + return null; // closed + } + trimToSize(); + if (journalRebuildRequired()) { + rebuildJournal(); + redundantOpCount = 0; + } + } + return null; + } + }; + + private DiskLruCache(final File directory, final int appVersion, final int valueCount, + final long maxSize) { + this.directory = directory; + this.appVersion = appVersion; + journalFile = new File(directory, JOURNAL_FILE); + journalFileTmp = new File(directory, JOURNAL_FILE_TMP); + this.valueCount = valueCount; + this.maxSize = maxSize; + } + + /** + * Opens the cache in {@code directory}, creating a cache if none exists + * there. + * + * @param directory a writable directory + * @param appVersion + * @param valueCount the number of values per cache entry. Must be positive. + * @param maxSize the maximum number of bytes this cache should use to store + * @throws IOException if reading or writing the cache directory fails + */ + public static DiskLruCache open(final File directory, final int appVersion, + final int valueCount, final long maxSize) throws IOException { + if (maxSize <= 0) { + throw new IllegalArgumentException("maxSize <= 0"); + } + if (valueCount <= 0) { + throw new IllegalArgumentException("valueCount <= 0"); + } + + // prefer to pick up where we left off + DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); + if (cache.journalFile.exists()) { + try { + cache.readJournal(); + cache.processJournal(); + cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true), + IO_BUFFER_SIZE); + return cache; + } catch (final IOException journalIsCorrupt) { + // System.logW("DiskLruCache " + directory + " is corrupt: " + // + journalIsCorrupt.getMessage() + ", removing"); + cache.delete(); + } + } + + // create a new empty cache + directory.mkdirs(); + cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); + cache.rebuildJournal(); + return cache; + } + + private void readJournal() throws IOException { + final InputStream in = new BufferedInputStream(new FileInputStream(journalFile), + IO_BUFFER_SIZE); + try { + final String magic = readAsciiLine(in); + final String version = readAsciiLine(in); + final String appVersionString = readAsciiLine(in); + final String valueCountString = readAsciiLine(in); + final String blank = readAsciiLine(in); + if (!MAGIC.equals(magic) || !VERSION_1.equals(version) + || !Integer.toString(appVersion).equals(appVersionString) + || !Integer.toString(valueCount).equals(valueCountString) || !"".equals(blank)) { + throw new IOException("unexpected journal header: [" + magic + ", " + version + + ", " + valueCountString + ", " + blank + "]"); + } + + while (true) { + try { + readJournalLine(readAsciiLine(in)); + } catch (final EOFException endOfJournal) { + break; + } + } + } finally { + closeQuietly(in); + } + } + + private void readJournalLine(final String line) throws IOException { + final String[] parts = line.split(" "); + if (parts.length < 2) { + throw new IOException("unexpected journal line: " + line); + } + + final String key = parts[1]; + if (parts[0].equals(REMOVE) && parts.length == 2) { + lruEntries.remove(key); + return; + } + + Entry entry = lruEntries.get(key); + if (entry == null) { + entry = new Entry(key); + lruEntries.put(key, entry); + } + + if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) { + entry.readable = true; + entry.currentEditor = null; + entry.setLengths(copyOfRange(parts, 2, parts.length)); + } else if (parts[0].equals(DIRTY) && parts.length == 2) { + entry.currentEditor = new Editor(entry); + } else if (parts[0].equals(READ) && parts.length == 2) { + // this work was already done by calling lruEntries.get() + } else { + throw new IOException("unexpected journal line: " + line); + } + } + + /** + * Computes the initial size and collects garbage as a part of opening the + * cache. Dirty entries are assumed to be inconsistent and will be deleted. + */ + private void processJournal() throws IOException { + deleteIfExists(journalFileTmp); + for (final Iterator i = lruEntries.values().iterator(); i.hasNext();) { + final Entry entry = i.next(); + if (entry.currentEditor == null) { + for (int t = 0; t < valueCount; t++) { + size += entry.lengths[t]; + } + } else { + entry.currentEditor = null; + for (int t = 0; t < valueCount; t++) { + deleteIfExists(entry.getCleanFile(t)); + deleteIfExists(entry.getDirtyFile(t)); + } + i.remove(); + } + } + } + + /** + * Creates a new journal that omits redundant information. This replaces the + * current journal if it exists. + */ + private synchronized void rebuildJournal() throws IOException { + if (journalWriter != null) { + journalWriter.close(); + } + + final Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE); + writer.write(MAGIC); + writer.write("\n"); + writer.write(VERSION_1); + writer.write("\n"); + writer.write(Integer.toString(appVersion)); + writer.write("\n"); + writer.write(Integer.toString(valueCount)); + writer.write("\n"); + writer.write("\n"); + + for (final Entry entry : lruEntries.values()) { + if (entry.currentEditor != null) { + writer.write(DIRTY + ' ' + entry.key + '\n'); + } else { + writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); + } + } + + writer.close(); + journalFileTmp.renameTo(journalFile); + journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE); + } + + private static void deleteIfExists(final File file) throws IOException { + // try { + // Libcore.os.remove(file.getPath()); + // } catch (ErrnoException errnoException) { + // if (errnoException.errno != OsConstants.ENOENT) { + // throw errnoException.rethrowAsIOException(); + // } + // } + if (file.exists() && !file.delete()) { + throw new IOException(); + } + } + + /** + * Returns a snapshot of the entry named {@code key}, or null if it doesn't + * exist is not currently readable. If a value is returned, it is moved to + * the head of the LRU queue. + */ + public synchronized Snapshot get(final String key) throws IOException { + checkNotClosed(); + validateKey(key); + final Entry entry = lruEntries.get(key); + if (entry == null) { + return null; + } + + if (!entry.readable) { + return null; + } + + /* + * Open all streams eagerly to guarantee that we see a single published + * snapshot. If we opened streams lazily then the streams could come + * from different edits. + */ + final InputStream[] ins = new InputStream[valueCount]; + try { + for (int i = 0; i < valueCount; i++) { + ins[i] = new FileInputStream(entry.getCleanFile(i)); + } + } catch (final FileNotFoundException e) { + // a file must have been deleted manually! + return null; + } + + redundantOpCount++; + journalWriter.append(READ + ' ' + key + '\n'); + if (journalRebuildRequired()) { + executorService.submit(cleanupCallable); + } + + return new Snapshot(key, entry.sequenceNumber, ins); + } + + /** + * Returns an editor for the entry named {@code key}, or null if another + * edit is in progress. + */ + public Editor edit(final String key) throws IOException { + return edit(key, ANY_SEQUENCE_NUMBER); + } + + private synchronized Editor edit(final String key, final long expectedSequenceNumber) + throws IOException { + checkNotClosed(); + validateKey(key); + Entry entry = lruEntries.get(key); + if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER + && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) { + return null; // snapshot is stale + } + if (entry == null) { + entry = new Entry(key); + lruEntries.put(key, entry); + } else if (entry.currentEditor != null) { + return null; // another edit is in progress + } + + final Editor editor = new Editor(entry); + entry.currentEditor = editor; + + // flush the journal before creating files to prevent file leaks + journalWriter.write(DIRTY + ' ' + key + '\n'); + journalWriter.flush(); + return editor; + } + + /** + * Returns the directory where this cache stores its data. + */ + public File getDirectory() { + return directory; + } + + /** + * Returns the maximum number of bytes that this cache should use to store + * its data. + */ + public long maxSize() { + return maxSize; + } + + /** + * Returns the number of bytes currently being used to store the values in + * this cache. This may be greater than the max size if a background + * deletion is pending. + */ + public synchronized long size() { + return size; + } + + private synchronized void completeEdit(final Editor editor, final boolean success) + throws IOException { + final Entry entry = editor.entry; + if (entry.currentEditor != editor) { + throw new IllegalStateException(); + } + + // if this edit is creating the entry for the first time, every index + // must have a value + if (success && !entry.readable) { + for (int i = 0; i < valueCount; i++) { + if (!entry.getDirtyFile(i).exists()) { + editor.abort(); + throw new IllegalStateException("edit didn't create file " + i); + } + } + } + + for (int i = 0; i < valueCount; i++) { + final File dirty = entry.getDirtyFile(i); + if (success) { + if (dirty.exists()) { + final File clean = entry.getCleanFile(i); + dirty.renameTo(clean); + final long oldLength = entry.lengths[i]; + final long newLength = clean.length(); + entry.lengths[i] = newLength; + size = size - oldLength + newLength; + } + } else { + deleteIfExists(dirty); + } + } + + redundantOpCount++; + entry.currentEditor = null; + if (entry.readable | success) { + entry.readable = true; + journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); + if (success) { + entry.sequenceNumber = nextSequenceNumber++; + } + } else { + lruEntries.remove(entry.key); + journalWriter.write(REMOVE + ' ' + entry.key + '\n'); + } + + if (size > maxSize || journalRebuildRequired()) { + executorService.submit(cleanupCallable); + } + } + + /** + * We only rebuild the journal when it will halve the size of the journal + * and eliminate at least 2000 ops. + */ + private boolean journalRebuildRequired() { + final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000; + return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD + && redundantOpCount >= lruEntries.size(); + } + + /** + * Drops the entry for {@code key} if it exists and can be removed. Entries + * actively being edited cannot be removed. + * + * @return true if an entry was removed. + */ + public synchronized boolean remove(final String key) throws IOException { + checkNotClosed(); + validateKey(key); + final Entry entry = lruEntries.get(key); + if (entry == null || entry.currentEditor != null) { + return false; + } + + for (int i = 0; i < valueCount; i++) { + final File file = entry.getCleanFile(i); + if (!file.delete()) { + throw new IOException("failed to delete " + file); + } + size -= entry.lengths[i]; + entry.lengths[i] = 0; + } + + redundantOpCount++; + journalWriter.append(REMOVE + ' ' + key + '\n'); + lruEntries.remove(key); + + if (journalRebuildRequired()) { + executorService.submit(cleanupCallable); + } + + return true; + } + + /** + * Returns true if this cache has been closed. + */ + public boolean isClosed() { + return journalWriter == null; + } + + private void checkNotClosed() { + if (journalWriter == null) { + throw new IllegalStateException("cache is closed"); + } + } + + /** + * Force buffered operations to the filesystem. + */ + public synchronized void flush() throws IOException { + checkNotClosed(); + trimToSize(); + journalWriter.flush(); + } + + /** + * Closes this cache. Stored values will remain on the filesystem. + */ + @Override + public synchronized void close() throws IOException { + if (journalWriter == null) { + return; // already closed + } + for (final Entry entry : new ArrayList(lruEntries.values())) { + if (entry.currentEditor != null) { + entry.currentEditor.abort(); + } + } + trimToSize(); + journalWriter.close(); + journalWriter = null; + } + + private void trimToSize() throws IOException { + while (size > maxSize) { + // Map.Entry toEvict = lruEntries.eldest(); + final Map.Entry toEvict = lruEntries.entrySet().iterator().next(); + remove(toEvict.getKey()); + } + } + + /** + * Closes the cache and deletes all of its stored values. This will delete + * all files in the cache directory including files that weren't created by + * the cache. + */ + public void delete() throws IOException { + close(); + deleteContents(directory); + } + + private void validateKey(final String key) { + if (key.contains(" ") || key.contains("\n") || key.contains("\r")) { + throw new IllegalArgumentException("keys must not contain spaces or newlines: \"" + key + + "\""); + } + } + + private static String inputStreamToString(final InputStream in) throws IOException { + return readFully(new InputStreamReader(in, UTF_8)); + } + + /** + * A snapshot of the values for an entry. + */ + public final class Snapshot implements Closeable { + private final String key; + + private final long sequenceNumber; + + private final InputStream[] ins; + + private Snapshot(final String key, final long sequenceNumber, final InputStream[] ins) { + this.key = key; + this.sequenceNumber = sequenceNumber; + this.ins = ins; + } + + /** + * Returns an editor for this snapshot's entry, or null if either the + * entry has changed since this snapshot was created or if another edit + * is in progress. + */ + public Editor edit() throws IOException { + return DiskLruCache.this.edit(key, sequenceNumber); + } + + /** + * Returns the unbuffered stream with the value for {@code index}. + */ + public InputStream getInputStream(final int index) { + return ins[index]; + } + + /** + * Returns the string value for {@code index}. + */ + public String getString(final int index) throws IOException { + return inputStreamToString(getInputStream(index)); + } + + @Override + public void close() { + for (final InputStream in : ins) { + closeQuietly(in); + } + } + } + + /** + * Edits the values for an entry. + */ + public final class Editor { + private final Entry entry; + + private boolean hasErrors; + + private Editor(final Entry entry) { + this.entry = entry; + } + + /** + * Returns an unbuffered input stream to read the last committed value, + * or null if no value has been committed. + */ + public InputStream newInputStream(final int index) throws IOException { + synchronized (DiskLruCache.this) { + if (entry.currentEditor != this) { + throw new IllegalStateException(); + } + if (!entry.readable) { + return null; + } + return new FileInputStream(entry.getCleanFile(index)); + } + } + + /** + * Returns the last committed value as a string, or null if no value has + * been committed. + */ + public String getString(final int index) throws IOException { + final InputStream in = newInputStream(index); + return in != null ? inputStreamToString(in) : null; + } + + /** + * Returns a new unbuffered output stream to write the value at + * {@code index}. If the underlying output stream encounters errors when + * writing to the filesystem, this edit will be aborted when + * {@link #commit} is called. The returned output stream does not throw + * IOExceptions. + */ + public OutputStream newOutputStream(final int index) throws IOException { + synchronized (DiskLruCache.this) { + if (entry.currentEditor != this) { + throw new IllegalStateException(); + } + return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index))); + } + } + + /** + * Sets the value at {@code index} to {@code value}. + */ + public void set(final int index, final String value) throws IOException { + Writer writer = null; + try { + writer = new OutputStreamWriter(newOutputStream(index), UTF_8); + writer.write(value); + } finally { + closeQuietly(writer); + } + } + + /** + * Commits this edit so it is visible to readers. This releases the edit + * lock so another edit may be started on the same key. + */ + public void commit() throws IOException { + if (hasErrors) { + completeEdit(this, false); + remove(entry.key); // the previous entry is stale + } else { + completeEdit(this, true); + } + } + + /** + * Aborts this edit. This releases the edit lock so another edit may be + * started on the same key. + */ + public void abort() throws IOException { + completeEdit(this, false); + } + + private class FaultHidingOutputStream extends FilterOutputStream { + private FaultHidingOutputStream(final OutputStream out) { + super(out); + } + + @Override + public void write(final int oneByte) { + try { + out.write(oneByte); + } catch (final IOException e) { + hasErrors = true; + } + } + + @Override + public void write(final byte[] buffer, final int offset, final int length) { + try { + out.write(buffer, offset, length); + } catch (final IOException e) { + hasErrors = true; + } + } + + @Override + public void close() { + try { + out.close(); + } catch (final IOException e) { + hasErrors = true; + } + } + + @Override + public void flush() { + try { + out.flush(); + } catch (final IOException e) { + hasErrors = true; + } + } + } + } + + private final class Entry { + private final String key; + + /** Lengths of this entry's files. */ + private final long[] lengths; + + /** True if this entry has ever been published */ + private boolean readable; + + /** The ongoing edit or null if this entry is not being edited. */ + private Editor currentEditor; + + /** + * The sequence number of the most recently committed edit to this + * entry. + */ + private long sequenceNumber; + + private Entry(final String key) { + this.key = key; + lengths = new long[valueCount]; + } + + public String getLengths() throws IOException { + final StringBuilder result = new StringBuilder(); + for (final long size : lengths) { + result.append(' ').append(size); + } + return result.toString(); + } + + /** + * Set lengths using decimal numbers like "10123". + */ + private void setLengths(final String[] strings) throws IOException { + if (strings.length != valueCount) { + throw invalidLengths(strings); + } + + try { + for (int i = 0; i < strings.length; i++) { + lengths[i] = Long.parseLong(strings[i]); + } + } catch (final NumberFormatException e) { + throw invalidLengths(strings); + } + } + + private IOException invalidLengths(final String[] strings) throws IOException { + throw new IOException("unexpected journal line: " + Arrays.toString(strings)); + } + + public File getCleanFile(final int i) { + return new File(directory, key + "." + i); + } + + public File getDirtyFile(final int i) { + return new File(directory, key + "." + i + ".tmp"); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/cache/ImageCache.java b/app/src/main/java/com/hippo/ehviewer/cache/ImageCache.java new file mode 100644 index 000000000..f7bf74c5e --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/cache/ImageCache.java @@ -0,0 +1,721 @@ +/* + * Copyright (C) 2012 Andrew Neal Licensed under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law + * or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +package com.hippo.ehviewer.cache; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.ActivityManager; +import android.app.Fragment; +import android.app.FragmentManager; +import android.content.ComponentCallbacks2; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Bitmap.CompressFormat; +import android.graphics.BitmapFactory; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Environment; +import android.os.Looper; +import android.support.v4.app.FragmentActivity; +import android.util.Log; + +import com.hippo.ehviewer.util.Utils; + +/** + * This class holds the memory and disk bitmap caches. + */ +public final class ImageCache { + + private static final String TAG = ImageCache.class.getSimpleName(); + + /** + * Default memory cache size as a percent of device memory class + */ + private static final float MEM_CACHE_DIVIDER = 0.25f; + + /** + * Default disk cache size 10MB + */ + private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; + + /** + * Compression settings when writing images to disk cache + */ + private static final CompressFormat COMPRESS_FORMAT = CompressFormat.JPEG; + + /** + * Disk cache index to read from + */ + private static final int DISK_CACHE_INDEX = 0; + + /** + * Image compression quality + */ + private static final int COMPRESS_QUALITY = 98; + + /** + * LRU cache + */ + private MemoryCache mLruCache; + + /** + * Disk LRU cache + */ + private DiskLruCache mDiskCache; + + private static ImageCache sInstance; + + /** + * Used to temporarily pause the disk cache while scrolling + */ + public boolean mPauseDiskAccess = false; + private final Object mPauseLock = new Object(); + + /** + * Constructor of ImageCache + * + * @param context The {@link Context} to use + */ + public ImageCache(final Context context) { + init(context); + } + + /** + * Used to create a singleton of {@link ImageCache} + * + * @param context The {@link Context} to use + * @return A new instance of this class. + */ + public final static ImageCache getInstance(final Context context) { + if (sInstance == null) { + sInstance = new ImageCache(context.getApplicationContext()); + } + return sInstance; + } + + /** + * Initialize the cache, providing all parameters. + * + * @param context The {@link Context} to use + * @param cacheParams The cache parameters to initialize the cache + */ + private void init(final Context context) { + Utils.execute(false, new AsyncTask() { + + @Override + protected Void doInBackground(final Void... unused) { + // Initialize the disk cahe in a background thread + initDiskCache(context); + return null; + } + }, (Void[])null); + // Set up the memory cache + initLruCache(context); + } + + /** + * Initializes the disk cache. Note that this includes disk access so this + * should not be executed on the main/UI thread. By default an ImageCache + * does not initialize the disk cache when it is created, instead you should + * call initDiskCache() to initialize it on a background thread. + * + * @param context The {@link Context} to use + */ + private synchronized void initDiskCache(final Context context) { + // Set up disk cache + if (mDiskCache == null || mDiskCache.isClosed()) { + File diskCacheDir = getDiskCacheDir(context, TAG); + if (diskCacheDir != null) { + if (!diskCacheDir.exists()) { + diskCacheDir.mkdirs(); + } + if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) { + try { + mDiskCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE); + } catch (final IOException e) { + diskCacheDir = null; + } + } + } + } + } + + /** + * Sets up the Lru cache + * + * @param context The {@link Context} to use + */ + @SuppressLint("NewApi") + public void initLruCache(final Context context) { + final ActivityManager activityManager = (ActivityManager)context + .getSystemService(Context.ACTIVITY_SERVICE); + final int lruCacheSize = Math.round(MEM_CACHE_DIVIDER * activityManager.getMemoryClass() + * 1024 * 1024); + mLruCache = new MemoryCache(lruCacheSize); + + // Release some memory as needed + context.registerComponentCallbacks(new ComponentCallbacks2() { + + /** + * {@inheritDoc} + */ + @Override + public void onTrimMemory(final int level) { + if (level >= TRIM_MEMORY_MODERATE) { + evictAll(); + } else if (level >= TRIM_MEMORY_BACKGROUND) { + mLruCache.trimToSize(mLruCache.size() / 2); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onLowMemory() { + // Nothing to do + } + + /** + * {@inheritDoc} + */ + @Override + public void onConfigurationChanged(final Configuration newConfig) { + // Nothing to do + } + }); + } + + /** + * Find and return an existing ImageCache stored in a {@link RetainFragment} + * , if not found a new one is created using the supplied params and saved + * to a {@link RetainFragment} + * + * @param activity The calling {@link FragmentActivity} + * @return An existing retained ImageCache object or a new one if one did + * not exist + */ + public static final ImageCache findOrCreateCache(final Activity activity) { + + // Search for, or create an instance of the non-UI RetainFragment + final RetainFragment retainFragment = findOrCreateRetainFragment( + activity.getFragmentManager()); + + // See if we already have an ImageCache stored in RetainFragment + ImageCache cache = (ImageCache)retainFragment.getObject(); + + // No existing ImageCache, create one and store it in RetainFragment + if (cache == null) { + cache = getInstance(activity); + retainFragment.setObject(cache); + } + return cache; + } + + /** + * Locate an existing instance of this {@link Fragment} or if not found, + * create and add it using {@link FragmentManager} + * + * @param fm The {@link FragmentManager} to use + * @return The existing instance of the {@link Fragment} or the new instance + * if just created + */ + public static final RetainFragment findOrCreateRetainFragment(final FragmentManager fm) { + // Check to see if we have retained the worker fragment + RetainFragment retainFragment = (RetainFragment)fm.findFragmentByTag(TAG); + + // If not retained, we need to create and add it + if (retainFragment == null) { + retainFragment = new RetainFragment(); + fm.beginTransaction().add(retainFragment, TAG).commit(); + } + return retainFragment; + } + + /** + * Adds a new image to the memory and disk caches + * + * @param data The key used to store the image + * @param bitmap The {@link Bitmap} to cache + */ + public void addBitmapToCache(final String data, final Bitmap bitmap) { + if (data == null || bitmap == null) { + return; + } + + // Add to memory cache + addBitmapToMemCache(data, bitmap); + + // Add to disk cache + if (mDiskCache != null) { + final String key = hashKeyForDisk(data); + OutputStream out = null; + try { + final DiskLruCache.Snapshot snapshot = mDiskCache.get(key); + if (snapshot == null) { + final DiskLruCache.Editor editor = mDiskCache.edit(key); + if (editor != null) { + out = editor.newOutputStream(DISK_CACHE_INDEX); + bitmap.compress(COMPRESS_FORMAT, COMPRESS_QUALITY, out); + editor.commit(); + out.close(); + flush(); + } + } else { + snapshot.getInputStream(DISK_CACHE_INDEX).close(); + } + } catch (final IOException e) { + Log.e(TAG, "addBitmapToCache - " + e); + } finally { + try { + if (out != null) { + out.close(); + out = null; + } + } catch (final IOException e) { + Log.e(TAG, "addBitmapToCache - " + e); + } catch (final IllegalStateException e) { + Log.e(TAG, "addBitmapToCache - " + e); + } + } + } + } + + /** + * Called to add a new image to the memory cache + * + * @param data The key identifier + * @param bitmap The {@link Bitmap} to cache + */ + public void addBitmapToMemCache(final String data, final Bitmap bitmap) { + if (data == null || bitmap == null) { + return; + } + // Add to memory cache + if (getBitmapFromMemCache(data) == null) { + mLruCache.put(data, bitmap); + } + } + + /** + * Fetches a cached image from the memory cache + * + * @param data Unique identifier for which item to get + * @return The {@link Bitmap} if found in cache, null otherwise + */ + public final Bitmap getBitmapFromMemCache(final String data) { + if (data == null) { + return null; + } + if (mLruCache != null) { + final Bitmap lruBitmap = mLruCache.get(data); + if (lruBitmap != null) { + return lruBitmap; + } + } + return null; + } + + /** + * Fetches a cached image from the disk cache + * + * @param data Unique identifier for which item to get + * @return The {@link Bitmap} if found in cache, null otherwise + */ + public final Bitmap getBitmapFromDiskCache(final String data) { + if (data == null) { + return null; + } + + // Check in the memory cache here to avoid going to the disk cache less + // often + if (getBitmapFromMemCache(data) != null) { + return getBitmapFromMemCache(data); + } + + waitUntilUnpaused(); + final String key = hashKeyForDisk(data); + if (mDiskCache != null) { + InputStream inputStream = null; + try { + final DiskLruCache.Snapshot snapshot = mDiskCache.get(key); + if (snapshot != null) { + inputStream = snapshot.getInputStream(DISK_CACHE_INDEX); + if (inputStream != null) { + final Bitmap bitmap = BitmapFactory.decodeStream(inputStream); + if (bitmap != null) { + return bitmap; + } + } + } + } catch (final IOException e) { + Log.e(TAG, "getBitmapFromDiskCache - " + e); + } finally { + try { + if (inputStream != null) { + inputStream.close(); + } + } catch (final IOException e) { + } + } + } + return null; + } + + public static final int STATE_NONE = 0; + public static final int STATE_FROM_MEMORY = 1; + public static final int STATE_FROM_DISK = 2; + + + /** + * Tries to return a cached image from memory cache before fetching from the + * disk cache + * + * @param data Unique identifier for which item to get + * @return The {@link Bitmap} if found in cache, null otherwise + */ + public final Bitmap getCachedBitmap(final String data, int[] state) { + if (data == null) { + return null; + } + Bitmap cachedImage = getBitmapFromMemCache(data); + if (cachedImage == null) { + cachedImage = getBitmapFromDiskCache(data); + if (cachedImage != null) { + addBitmapToMemCache(data, cachedImage); + if (state != null) + state[0] = STATE_FROM_DISK; + } else { + if (state != null) + state[0] = STATE_NONE; + } + } else { + if (state != null) + state[0] = STATE_FROM_MEMORY; + } + return cachedImage; + } + + /** + * flush() is called to synchronize up other methods that are accessing the + * cache first + */ + public void flush() { + Utils.execute(false, new AsyncTask() { + + @Override + protected Void doInBackground(final Void... unused) { + if (mDiskCache != null) { + try { + if (!mDiskCache.isClosed()) { + mDiskCache.flush(); + } + } catch (final IOException e) { + Log.e(TAG, "flush - " + e); + } + } + return null; + } + }, (Void[])null); + } + + /** + * Clears the disk and memory caches + */ + public void clearCaches() { + Utils.execute(false, new AsyncTask() { + + @Override + protected Void doInBackground(final Void... unused) { + // Clear the disk cache + try { + if (mDiskCache != null) { + mDiskCache.delete(); + mDiskCache = null; + } + } catch (final IOException e) { + Log.e(TAG, "clearCaches - " + e); + } + // Clear the memory cache + evictAll(); + return null; + } + }, (Void[])null); + } + + /** + * Closes the disk cache associated with this ImageCache object. Note that + * this includes disk access so this should not be executed on the main/UI + * thread. + */ + public void close() { + Utils.execute(false, new AsyncTask() { + + @Override + protected Void doInBackground(final Void... unused) { + if (mDiskCache != null) { + try { + if (!mDiskCache.isClosed()) { + mDiskCache.close(); + mDiskCache = null; + } + } catch (final IOException e) { + Log.e(TAG, "close - " + e); + } + } + return null; + } + }, (Void[])null); + } + + /** + * Evicts all of the items from the memory cache and lets the system know + * now would be a good time to garbage collect + */ + public void evictAll() { + if (mLruCache != null) { + mLruCache.evictAll(); + } + System.gc(); + } + + /** + * @param key The key used to identify which cache entries to delete. + */ + public void removeFromCache(final String key) { + if (key == null) { + return; + } + // Remove the Lru entry + if (mLruCache != null) { + mLruCache.remove(key); + } + + try { + // Remove the disk entry + if (mDiskCache != null) { + mDiskCache.remove(hashKeyForDisk(key)); + } + } catch (final IOException e) { + Log.e(TAG, "remove - " + e); + } + flush(); + } + + /** + * Used to temporarily pause the disk cache while the user is scrolling to + * improve scrolling. + * + * @param pause True to temporarily pause the disk cache, false otherwise. + */ + public void setPauseDiskCache(final boolean pause) { + synchronized (mPauseLock) { + if (mPauseDiskAccess != pause) { + mPauseDiskAccess = pause; + if (!pause) { + mPauseLock.notify(); + } + } + } + } + + private void waitUntilUnpaused() { + synchronized (mPauseLock) { + if (Looper.myLooper() != Looper.getMainLooper()) { + while (mPauseDiskAccess) { + try { + mPauseLock.wait(); + } catch (InterruptedException e) { + // ignored, we'll start waiting again + } + } + } + } + } + + /** + * @return True if the user is scrolling, false otherwise. + */ + public boolean isDiskCachePaused() { + return mPauseDiskAccess; + } + + /** + * Get a usable cache directory (external if available, internal otherwise) + * + * @param context The {@link Context} to use + * @param uniqueName A unique directory name to append to the cache + * directory + * @return The cache directory + */ + public static final File getDiskCacheDir(final Context context, final String uniqueName) { + // getExternalCacheDir(context) returns null if external storage is not ready + final String cachePath = getExternalCacheDir(context) != null + ? getExternalCacheDir(context).getPath() + : context.getCacheDir().getPath(); + return new File(cachePath, uniqueName); + } + + /** + * Check if external storage is built-in or removable + * + * @return True if external storage is removable (like an SD card), false + * otherwise + */ + public static final boolean isExternalStorageRemovable() { + return Environment.isExternalStorageRemovable(); + } + + /** + * Get the external app cache directory + * + * @param context The {@link Context} to use + * @return The external cache directory + */ + public static final File getExternalCacheDir(final Context context) { + return context.getExternalCacheDir(); + } + + /** + * Check how much usable space is available at a given path. + * + * @param path The path to check + * @return The space available in bytes + */ + public static final long getUsableSpace(final File path) { + return path.getUsableSpace(); + } + + /** + * A hashing method that changes a string (like a URL) into a hash suitable + * for using as a disk filename. + * + * @param key The key used to store the file + */ + public static final String hashKeyForDisk(final String key) { + String cacheKey; + try { + final MessageDigest digest = MessageDigest.getInstance("MD5"); + digest.update(key.getBytes()); + cacheKey = bytesToHexString(digest.digest()); + } catch (final NoSuchAlgorithmException e) { + cacheKey = String.valueOf(key.hashCode()); + } + return cacheKey; + } + + /** + * http://stackoverflow.com/questions/332079 + * + * @param bytes The bytes to convert. + * @return A {@link String} converted from the bytes of a hashable key used + * to store a filename on the disk, to hex digits. + */ + private static final String bytesToHexString(final byte[] bytes) { + final StringBuilder builder = new StringBuilder(); + for (final byte b : bytes) { + final String hex = Integer.toHexString(0xFF & b); + if (hex.length() == 1) { + builder.append('0'); + } + builder.append(hex); + } + return builder.toString(); + } + + /** + * A simple non-UI Fragment that stores a single Object and is retained over + * configuration changes. In this sample it will be used to retain an + * {@link ImageCache} object. + */ + public static final class RetainFragment extends Fragment { + + /** + * The object to be stored + */ + private Object mObject; + + /** + * Empty constructor as per the {@link Fragment} documentation + */ + public RetainFragment() { + } + + /** + * {@inheritDoc} + */ + @Override + public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // Make sure this Fragment is retained over a configuration change + setRetainInstance(true); + } + + /** + * Store a single object in this {@link Fragment} + * + * @param object The object to store + */ + public void setObject(final Object object) { + mObject = object; + } + + /** + * Get the stored object + * + * @return The stored object + */ + public Object getObject() { + return mObject; + } + } + + /** + * Used to cache images via {@link LruCache}. + */ + public static final class MemoryCache extends LruCache { + + /** + * Constructor of MemoryCache + * + * @param maxSize The allowed size of the {@link LruCache} + */ + public MemoryCache(final int maxSize) { + super(maxSize); + } + + /** + * Get the size in bytes of a bitmap. + */ + public static final int getBitmapSize(final Bitmap bitmap) { + return bitmap.getByteCount(); + } + + /** + * {@inheritDoc} + */ + @Override + protected int sizeOf(final String paramString, final Bitmap paramBitmap) { + return getBitmapSize(paramBitmap); + } + + } + +} diff --git a/app/src/main/java/com/hippo/ehviewer/cache/LruCache.java b/app/src/main/java/com/hippo/ehviewer/cache/LruCache.java new file mode 100644 index 000000000..448e8a059 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/cache/LruCache.java @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2011 The Android Open Source Project Licensed under the Apache + * License, Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law + * or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +package com.hippo.ehviewer.cache; + +// NOTE: upstream of this class is android.util.LruCache, changes below +// expose trimToSize() to be called externally. + +import android.annotation.SuppressLint; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Static library version of {@link android.util.LruCache}. Used to write apps + * that run on API levels prior to 12. When running on API level 12 or above, + * this implementation is still used; it does not try to switch to the + * framework's implementation. See the framework SDK documentation for a class + * overview. + */ +public class LruCache { + + private final LinkedHashMap map; + + private final int maxSize; + + /** Size of this cache in units. Not necessarily the number of elements. */ + private int size; + + private int putCount; + + private int createCount; + + private int evictionCount; + + private int hitCount; + + private int missCount; + + /** + * @param maxSize for caches that do not override {@link #sizeOf}, this is + * the maximum number of entries in the cache. For all other + * caches, this is the maximum sum of the sizes of the entries in + * this cache. + */ + public LruCache(final int maxSize) { + if (maxSize <= 0) { + throw new IllegalArgumentException("maxSize <= 0"); + } + this.maxSize = maxSize; + this.map = new LinkedHashMap(0, 0.75f, true); + } + + /** + * Returns the value for {@code key} if it exists in the cache or can be + * created by {@code #create}. If a value was returned, it is moved to the + * head of the queue. This returns null if a value is not cached and cannot + * be created. + */ + public final V get(final K key) { + if (key == null) { + throw new NullPointerException("key == null"); + } + + V mapValue; + synchronized (this) { + mapValue = map.get(key); + if (mapValue != null) { + this.hitCount++; + return mapValue; + } + this.missCount++; + } + + /* + * Attempt to create a value. This may take a long time, and the map may + * be different when create() returns. If a conflicting value was added + * to the map while create() was working, we leave that value in the map + * and release the created value. + */ + + final V createdValue = create(key); + if (createdValue == null) { + return null; + } + + synchronized (this) { + this.createCount++; + mapValue = map.put(key, createdValue); + + if (mapValue != null) { + /* There was a conflict so undo that last put */ + this.map.put(key, mapValue); + } else { + this.size += safeSizeOf(key, createdValue); + } + } + + if (mapValue != null) { + entryRemoved(false, key, createdValue, mapValue); + return mapValue; + } else { + trimToSize(maxSize); + return createdValue; + } + } + + /** + * Caches {@code value} for {@code key}. The value is moved to the head of + * the queue. + * + * @return the previous value mapped by {@code key}. + */ + public final V put(final K key, final V value) { + if (key == null || value == null) { + throw new NullPointerException("key == null || value == null"); + } + + V previous; + synchronized (this) { + this.putCount++; + this.size += safeSizeOf(key, value); + previous = this.map.put(key, value); + if (previous != null) { + this.size -= safeSizeOf(key, previous); + } + } + + if (previous != null) { + entryRemoved(false, key, previous, value); + } + + trimToSize(maxSize); + return previous; + } + + /** + * @param maxSize the maximum size of the cache before returning. May be -1 + * to evict even 0-sized elements. + */ + public void trimToSize(final int maxSize) { + while (true) { + K key; + V value; + synchronized (this) { + if (this.size < 0 || this.map.isEmpty() && size != 0) { + throw new IllegalStateException(getClass().getName() + + ".sizeOf() is reporting inconsistent results!"); + } + + if (this.size <= maxSize || this.map.isEmpty()) { + break; + } + + final Map.Entry toEvict = this.map.entrySet().iterator().next(); + key = toEvict.getKey(); + value = toEvict.getValue(); + this.map.remove(key); + this.size -= safeSizeOf(key, value); + this.evictionCount++; + } + + entryRemoved(true, key, value, null); + } + } + + /** + * Removes the entry for {@code key} if it exists. + * + * @return the previous value mapped by {@code key}. + */ + public final V remove(final K key) { + if (key == null) { + throw new NullPointerException("key == null"); + } + + V previous; + synchronized (this) { + previous = this.map.remove(key); + if (previous != null) { + this.size -= safeSizeOf(key, previous); + } + } + + if (previous != null) { + entryRemoved(false, key, previous, null); + } + + return previous; + } + + /** + * Called for entries that have been evicted or removed. This method is + * invoked when a value is evicted to make space, removed by a call to + * {@link #remove}, or replaced by a call to {@link #put}. The default + * implementation does nothing. + *

    + * The method is called without synchronization: other threads may access + * the cache while this method is executing. + * + * @param evicted true if the entry is being removed to make space, false if + * the removal was caused by a {@link #put} or {@link #remove}. + * @param newValue the new value for {@code key}, if it exists. If non-null, + * this removal was caused by a {@link #put}. Otherwise it was + * caused by an eviction or a {@link #remove}. + */ + protected void entryRemoved(final boolean evicted, final K key, final V oldValue, + final V newValue) { + } + + /** + * Called after a cache miss to compute a value for the corresponding key. + * Returns the computed value or null if no value can be computed. The + * default implementation returns null. + *

    + * The method is called without synchronization: other threads may access + * the cache while this method is executing. + *

    + * If a value for {@code key} exists in the cache when this method returns, + * the created value will be released with {@link #entryRemoved} and + * discarded. This can occur when multiple threads request the same key at + * the same time (causing multiple values to be created), or when one thread + * calls {@link #put} while another is creating a value for the same key. + */ + protected V create(final K key) { + return null; + } + + private int safeSizeOf(final K key, final V value) { + final int result = sizeOf(key, value); + if (result < 0) { + throw new IllegalStateException("Negative size: " + key + "=" + value); + } + return result; + } + + /** + * Returns the size of the entry for {@code key} and {@code value} in + * user-defined units. The default implementation returns 1 so that size is + * the number of entries and max size is the maximum number of entries. + *

    + * An entry's size must not change while it is in the cache. + */ + protected int sizeOf(final K key, final V value) { + return 1; + } + + /** + * Clear the cache, calling {@link #entryRemoved} on each removed entry. + */ + public final void evictAll() { + trimToSize(-1); // -1 will evict 0-sized elements + } + + /** + * For caches that do not override {@link #sizeOf}, this returns the number + * of entries in the cache. For all other caches, this returns the sum of + * the sizes of the entries in this cache. + */ + public synchronized final int size() { + return this.size; + } + + /** + * For caches that do not override {@link #sizeOf}, this returns the maximum + * number of entries in the cache. For all other caches, this returns the + * maximum sum of the sizes of the entries in this cache. + */ + public synchronized final int maxSize() { + return this.maxSize; + } + + /** + * Returns the number of times {@link #get} returned a value. + */ + public synchronized final int hitCount() { + return this.hitCount; + } + + /** + * Returns the number of times {@link #get} returned null or required a new + * value to be created. + */ + public synchronized final int missCount() { + return this.missCount; + } + + /** + * Returns the number of times {@link #create(Object)} returned a value. + */ + public synchronized final int createCount() { + return this.createCount; + } + + /** + * Returns the number of times {@link #put} was called. + */ + public synchronized final int putCount() { + return this.putCount; + } + + /** + * Returns the number of values that have been evicted. + */ + public synchronized final int evictionCount() { + return this.evictionCount; + } + + /** + * Returns a copy of the current contents of the cache, ordered from least + * recently accessed to most recently accessed. + */ + public synchronized final Map snapshot() { + return new LinkedHashMap(this.map); + } + + @SuppressLint("DefaultLocale") + @Override + public synchronized final String toString() { + final int accesses = this.hitCount + this.missCount; + final int hitPercent = accesses != 0 ? 100 * this.hitCount / accesses : 0; + return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", this.maxSize, + this.hitCount, this.missCount, hitPercent); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/cardview/CardViewSalon.java b/app/src/main/java/com/hippo/ehviewer/cardview/CardViewSalon.java new file mode 100644 index 000000000..df423fcda --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/cardview/CardViewSalon.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.cardview; + +import android.view.View; + +/** + * @author Hippo + * + * A salon to make views look like card + */ +public final class CardViewSalon { + + @SuppressWarnings("deprecation") + public static void reform(View view, int bgColor, int boundColor) { + view.setBackgroundDrawable(new RoundRectDrawable( + bgColor, boundColor)); + } + + @SuppressWarnings("deprecation") + public static void reform(View view, int[][] stateSets, int[] bgColors, + int[] boundColors) { + view.setBackgroundDrawable(new RoundRectDrawable( + stateSets, bgColors, boundColors)); + } + + @SuppressWarnings("deprecation") + public static void reformWithShadow(View view, int bgColor, int boundColor, + boolean keepPadding) { + view.setBackgroundDrawable(new RoundRectDrawableWithShadow( + bgColor, boundColor, keepPadding)); + } + + @SuppressWarnings("deprecation") + public static void reformWithShadow(View view, int[][] stateSets, int[] bgColors, + int[] boundColors, boolean keepPadding) { + view.setBackgroundDrawable(new RoundRectDrawableWithShadow( + stateSets, bgColors, boundColors, keepPadding)); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/cardview/RoundRectDrawable.java b/app/src/main/java/com/hippo/ehviewer/cardview/RoundRectDrawable.java new file mode 100644 index 000000000..cb910255f --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/cardview/RoundRectDrawable.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.cardview; + +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.StateSet; + +import com.hippo.ehviewer.util.Ui; + +// Get most code from android.support.v7.widget.CardViewEclairMr1 +public class RoundRectDrawable extends Drawable { + + protected final float mCornerRadius; + private final int[][] mStateSets; + private final int[] mBgColors; + private final int[] mBoundColors; + + private final Paint mBgPaint; + private final Paint mBoundPaint; + + private int mLastIndex = -1; + protected final RectF mTempRectF = new RectF(); + + public RoundRectDrawable(int bgColor, int boundColor) { + mCornerRadius = Ui.dp2pix(2); + mStateSets = null; + mBgColors = null; + mBoundColors = null; + + mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); + mBgPaint.setColor(bgColor); + if (boundColor == 0) { + mBoundPaint = null; + } else { + mBoundPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); + mBoundPaint.setColor(boundColor); + } + } + + public RoundRectDrawable(int[][] stateSets, int[] bgColors, int[] boundColors) { + mCornerRadius = Ui.dp2pix(2); + mStateSets = stateSets; + mBgColors = bgColors; + mBoundColors = boundColors; + + mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); + if (mBoundColors == null) { + mBoundPaint = null; + } else { + mBoundPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); + } + } + + @Override + public boolean isStateful() { + return mStateSets != null; + } + + @Override + protected boolean onStateChange(int[] stateSet) { + final int N = mStateSets.length; + for (int i = 0; i < N; i++) { + if (StateSet.stateSetMatches(mStateSets[i], stateSet)) { + if (mLastIndex == i) + return false; + mLastIndex = i; + + mBgPaint.setColor(mBgColors[i]); + if (mBoundColors != null) + mBoundPaint.setColor(mBoundColors[i]); + invalidateSelf(); + return true; + } + } + return false; + } + + @Override + public void draw(Canvas canvas) { + drawBody(canvas, new RectF(getBounds())); + } + + protected void drawBody(Canvas canvas, RectF bounds) { + if (mBoundPaint == null && Build.VERSION.SDK_INT >= 17) { + canvas.drawRoundRect(bounds, mCornerRadius, mCornerRadius, mBgPaint); + + } else { + final float twoRadius = mCornerRadius * 2; + final float innerWidth = bounds.width() - twoRadius; + final float innerHeight = bounds.height() - twoRadius; + Paint drawBoundsPaint = mBoundPaint == null ? mBgPaint : mBoundPaint; + + // Draw four corner + mTempRectF.set(bounds.left, bounds.top, + bounds.left + mCornerRadius * 2, bounds.top + mCornerRadius * 2); + canvas.drawArc(mTempRectF, 180, 90, true, drawBoundsPaint); + mTempRectF.offset(innerWidth, 0); + canvas.drawArc(mTempRectF, 270, 90, true, drawBoundsPaint); + mTempRectF.offset(0, innerHeight); + canvas.drawArc(mTempRectF, 0, 90, true, drawBoundsPaint); + mTempRectF.offset(-innerWidth, 0); + canvas.drawArc(mTempRectF, 90, 90, true, drawBoundsPaint); + + //draw top and bottom pieces + canvas.drawRect(bounds.left + mCornerRadius, bounds.top, + bounds.right - mCornerRadius, bounds.top + mCornerRadius, + drawBoundsPaint); + canvas.drawRect(bounds.left + mCornerRadius, + bounds.bottom - mCornerRadius, bounds.right - mCornerRadius, + bounds.bottom, drawBoundsPaint); + + if (mBoundPaint == null) { + // No bounds color, just draw center and left and right at same time + canvas.drawRect(bounds.left, bounds.top + mCornerRadius, + bounds.right, bounds.bottom - mCornerRadius, mBgPaint); + } else { + // Draw left and right pieces + canvas.drawRect(bounds.left, bounds.top + mCornerRadius, + bounds.left + mCornerRadius, bounds.bottom - mCornerRadius, + drawBoundsPaint); + canvas.drawRect(bounds.right - mCornerRadius, bounds.top + mCornerRadius, + bounds.right, bounds.bottom - mCornerRadius, drawBoundsPaint); + // Draw center + canvas.drawRect(bounds.left + mCornerRadius, bounds.top + mCornerRadius, + bounds.right - mCornerRadius, bounds.bottom - mCornerRadius, mBgPaint); + } + } + } + + @Override + public void setAlpha(int alpha) { + // not supported + } + + @Override + public void setColorFilter(ColorFilter cf) { + // not supported + } + + @Override + public int getOpacity() { + return PixelFormat.OPAQUE; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/cardview/RoundRectDrawableWithShadow.java b/app/src/main/java/com/hippo/ehviewer/cardview/RoundRectDrawableWithShadow.java new file mode 100644 index 000000000..c9137ab13 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/cardview/RoundRectDrawableWithShadow.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.cardview; + +import android.graphics.Canvas; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.RadialGradient; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Shader; + +import com.hippo.ehviewer.util.Ui; + +// Get most code from android.support.v7.widget.RoundRectDrawableWithShadow +public class RoundRectDrawableWithShadow extends RoundRectDrawable { + private final static float SHADOW_MULTIPLIER = 1.5f; + + private int mShadowStartColor; + private int mShadowEndColor; + private float mShadowSize; + + private Paint mCornerShadowPaint; + private Paint mEdgeShadowPaint; + + private final RectF mPreShadowBounds = new RectF(); + private final Path mCornerShadowPath = new Path(); + + private boolean mDirty = true; + private final boolean mKeepPadding; + + public RoundRectDrawableWithShadow(int bgColor, int boundColor, + boolean keepPadding) { + super(bgColor, boundColor); + mKeepPadding = keepPadding; + init(); + } + + public RoundRectDrawableWithShadow(int[][] stateSets, + int[] bgColors, int[] boundColors, boolean keepPadding) { + super(stateSets, bgColors, boundColors); + mKeepPadding = keepPadding; + init(); + } + + private void init() { + mShadowStartColor = 0x37000000; + mShadowEndColor = 0x03000000; + mShadowSize = Ui.dp2pix(2) * SHADOW_MULTIPLIER; + + mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); + mCornerShadowPaint.setStyle(Paint.Style.FILL); + mCornerShadowPaint.setDither(true); + mEdgeShadowPaint = new Paint(mCornerShadowPaint); + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + mDirty = true; + } + + @Override + public boolean getPadding(Rect padding) { + if (mKeepPadding) { + return false; + } else { + padding.set(getSuggestionPadding()); + return true; + } + } + + public Rect getSuggestionPadding() { + Rect padding = new Rect(); + final int topShadow = (int) Math.ceil(mShadowSize * (1 / (SHADOW_MULTIPLIER * 2))); + final int sideShadow = (int) Math.ceil(mShadowSize - topShadow); + padding.set(sideShadow, topShadow, sideShadow, (int) Math.ceil(mShadowSize)); + return padding; + } + + private void buildShadowCorners() { + RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius); + RectF outerBounds = new RectF(innerBounds); + outerBounds.inset(-mShadowSize, -mShadowSize); + + mCornerShadowPath.reset(); + mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD); + mCornerShadowPath.moveTo(-mCornerRadius, 0); + mCornerShadowPath.rLineTo(-mShadowSize, 0); + // outer arc + mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false); + // inner arc + mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false); + mCornerShadowPath.close(); + + float startRatio = mCornerRadius / (mCornerRadius + mShadowSize); + mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize, + new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor}, + new float[]{0f, startRatio, 1f} + , Shader.TileMode.CLAMP)); + + // we offset the content shadowSize/2 pixels up to make it more realistic. + // this is why edge shadow shader has some extra space + // When drawing bottom edge shadow, we use that extra space. + mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0, + -mCornerRadius - mShadowSize, + new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor}, + new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP)); + } + + private void buildComponents(Rect bounds) { + mPreShadowBounds.set(bounds.left + mShadowSize, bounds.top + mShadowSize, + bounds.right - mShadowSize, bounds.bottom - mShadowSize); + buildShadowCorners(); + } + + @Override + public void draw(Canvas canvas) { + if (mDirty) { + buildComponents(getBounds()); + mDirty = false; + } + drawShadow(canvas); + final float offset = mShadowSize * (1 - 1 / (SHADOW_MULTIPLIER * 2)); + final float horizontalOffset = mShadowSize - offset; + canvas.translate(0, -offset); + mPreShadowBounds.bottom += offset; + mPreShadowBounds.left -= horizontalOffset; + mPreShadowBounds.right += horizontalOffset; + drawBody(canvas, mPreShadowBounds); + mPreShadowBounds.bottom -= offset; + mPreShadowBounds.left += horizontalOffset; + mPreShadowBounds.right -= horizontalOffset; + canvas.translate(0, offset); + } + + private void drawShadow(Canvas canvas) { + int saved = canvas.save(); + + float cornerPathSize = 2 * (mCornerRadius + mShadowSize); + float edgeShadowTop = -mCornerRadius - mShadowSize; + final Rect bounds = getBounds(); + + // LT + canvas.translate(mPreShadowBounds.left + mCornerRadius, + mPreShadowBounds.top + mCornerRadius); + canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); + canvas.drawRect(0, edgeShadowTop, + bounds.right - cornerPathSize, -mCornerRadius, + mEdgeShadowPaint); + // RB + canvas.rotate(180f); + canvas.translate(-bounds.width() + cornerPathSize, -bounds.height() + cornerPathSize); + canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); + canvas.drawRect(0, edgeShadowTop, + bounds.right - cornerPathSize, -mCornerRadius + mShadowSize, + mEdgeShadowPaint); + + // LB + canvas.rotate(90f); + canvas.translate(0, -bounds.width() + cornerPathSize); + canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); + canvas.drawRect(0, edgeShadowTop, + bounds.bottom - cornerPathSize, -mCornerRadius, + mEdgeShadowPaint); + + // RT + canvas.rotate(180f); + canvas.translate(-bounds.height() + cornerPathSize, -bounds.width() + cornerPathSize); + canvas.drawPath(mCornerShadowPath, mCornerShadowPaint); + canvas.drawRect(0, edgeShadowTop, + bounds.bottom - cornerPathSize, -mCornerRadius, + mEdgeShadowPaint); + + canvas.restoreToCount(saved); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/ApiGalleryDetail.java b/app/src/main/java/com/hippo/ehviewer/data/ApiGalleryDetail.java new file mode 100644 index 000000000..78ea9c174 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/data/ApiGalleryDetail.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.data; + +import java.util.ArrayList; +import java.util.List; + +public class ApiGalleryDetail extends ApiGalleryInfo + implements LofiDetailImpl { + public int previewPageNum; + public int previewPerPage; + private final List previewLists = new ArrayList(); + + public ApiGalleryDetail() {} + + public ApiGalleryDetail(ApiGalleryInfo apiGalleryInfo) { + gid = apiGalleryInfo.gid; + token = apiGalleryInfo.token; + title = apiGalleryInfo.title; + posted = apiGalleryInfo.posted; + category = apiGalleryInfo.category; + thumb = apiGalleryInfo.thumb; + uploader = apiGalleryInfo.uploader; + rating = apiGalleryInfo.rating; + simpleLanguage = apiGalleryInfo.simpleLanguage; + archiver_key = apiGalleryInfo.archiver_key; + title_jpn = apiGalleryInfo.title_jpn; + filecount = apiGalleryInfo.filecount; + filesize = apiGalleryInfo.filesize; + expunged = apiGalleryInfo.expunged; + torrentcount = apiGalleryInfo.torrentcount; + apiTags = apiGalleryInfo.apiTags; + } + + @Override + public int getGid() { + return gid; + } + + @Override + public String getToken() { + return token; + } + + @Override + public String getTitle() { + return title; + } + + @Override + public int getPreviewPageNum() { + return previewPageNum; + } + + @Override + public void setPreviewPageNum(int pageNum) { + previewPageNum = pageNum; + } + + @Override + public int getPreviewPerPage() { + return previewPerPage; + } + + @Override + public PreviewList getPreview(int page) { + if (page >= 0 && page < previewLists.size()) + return previewLists.get(page); + else + return null; + } + + @Override + public void setPreview(int page, PreviewList previewList) { + if (page >= 0 && page < previewLists.size()) + previewLists.set(page, previewList); + else if (page == previewLists.size()) + previewLists.add(previewList); + else + ; // Do nothing + } + + @Override + public String[] getTags() { + return apiTags; + } + + @Override + public GalleryInfo toGalleryInfo() { + return new GalleryInfo(this); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/ApiGalleryInfo.java b/app/src/main/java/com/hippo/ehviewer/data/ApiGalleryInfo.java new file mode 100644 index 000000000..b399a6efe --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/data/ApiGalleryInfo.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.data; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.hippo.ehviewer.util.Utils; + + +public class ApiGalleryInfo extends GalleryInfo{ + public String archiver_key; + public String title_jpn; + public String filecount; + public long filesize; + public boolean expunged; + public String torrentcount; + public String[] apiTags; + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public ApiGalleryInfo createFromParcel(Parcel source) { + ApiGalleryInfo p = new ApiGalleryInfo(); + p.gid = source.readInt(); + p.token = source.readString(); + p.title = source.readString(); + p.posted = source.readString(); + p.category = source.readInt(); + p.thumb = source.readString(); + p.uploader = source.readString(); + p.rating = source.readFloat(); + p.simpleLanguage = source.readString(); + p.archiver_key = source.readString(); + p.title_jpn = source.readString(); + p.filecount = source.readString(); + p.filesize = source.readLong(); + p.expunged = Utils.int2boolean(source.readInt()); + p.torrentcount = source.readString(); + int apiTagsLength = source.readInt(); + p.apiTags = new String[apiTagsLength]; + for (int i = 0; i < apiTagsLength; i++) + p.apiTags[i] = source.readString(); + return p; + } + + @Override + public ApiGalleryInfo[] newArray(int size) { + return new ApiGalleryInfo[size]; + } + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeString(archiver_key); + dest.writeString(title_jpn); + dest.writeString(filecount); + dest.writeLong(filesize); + dest.writeInt(Utils.boolean2int(expunged)); + dest.writeString(torrentcount); + dest.writeInt(apiTags.length); + for (String str : apiTags) + dest.writeString(str); + } + +} + diff --git a/app/src/main/java/com/hippo/ehviewer/data/Comment.java b/app/src/main/java/com/hippo/ehviewer/data/Comment.java new file mode 100644 index 000000000..ba872c418 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/data/Comment.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.data; + +public class Comment { + public String time; + public String user; + public String comment; + + public Comment (String time, String user, String comment) { + this.time = time; + this.user = user; + this.comment = comment; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/Data.java b/app/src/main/java/com/hippo/ehviewer/data/Data.java new file mode 100644 index 000000000..2d3b216b3 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/data/Data.java @@ -0,0 +1,934 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.data; + +import java.util.ArrayList; +import java.util.List; + +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.SparseArray; + +import com.hippo.ehviewer.Analytics; +import com.hippo.ehviewer.service.DownloadService; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.Log; +import com.hippo.ehviewer.util.MathUtils; +import com.hippo.ehviewer.util.SqlUtils; + +// TODO +// what a mess +// refactoring + +/** + * @author Hippo + * + * Ver 2: 移除 read 表,添加 Download 表 + * Ver 3: Download 表添加下载时间项 + * Ver 4: 添历史记录 + */ +public class Data { + private static final String TAG = "Data"; + + private static final int VERSION = 4; + private static final String DB_NAME = "data"; + + private static final String TABLE_GALLERY = "gallery"; + private static final String TABLE_LOCAL_FAVOURITE = "local_favourite"; + private static final String TABLE_TAG = "tag"; + private static final String TABLE_DOWNLOAD = "download"; + private static final String TABLE_HISTORY = "history"; + + private static final String COLUMN_GID = "gid"; + private static final String COLUMN_TOKEN = "token"; + private static final String COLUMN_TITLE = "title"; + private static final String COLUMN_POSTED = "posted"; + private static final String COLUMN_CATEGORY = "category"; + private static final String COLUMN_THUMB = "thumb"; + private static final String COLUMN_UPLOADER = "uploader"; + private static final String COLUMN_RATING = "rating"; + private static final String COLUMN_REFERENCE = "reference"; + + private static final String COLUMN_ID = "id"; + private static final String COLUMN_NAME = "name"; + private static final String COLUMN_MODE = "mode"; + private static final String COLUMN_SEARCH = "search"; + private static final String COLUMN_ADVANCE = "advance"; + private static final String COLUMN_MIN_RATING = "min_rating"; + private static final String COLUMN_TAG = "tag"; + + private static final String COLUMN_STATE = "state"; + private static final String COLUMN_LEGACY = "legacy"; + + private static final String COLUMN_DATE = "date"; + + private final SparseArray mGallerys; + + private long mTagRowNum; + private List mTags; + private List mLocalFavourites; + private List mDownloads; + + private final Context mContext; + + private final DBHelper mDBHelper; + private final SQLiteDatabase mDatabase; + + private static Data sInstance; + + public static void createInstance(Context context) { + sInstance = new Data(context); + } + + public static Data getInstance() { + return sInstance; + } + + public Data(Context context) { + mContext = context; + mGallerys = new SparseArray(); + + mDBHelper = new DBHelper(mContext); + mDatabase = mDBHelper.getWritableDatabase(); + + getTags(); + getLocalFavourites(); + getDownloads(); + } + + @Override + public void finalize() { + mDBHelper.close(); + } + + /****** gallery ******/ + + /** + * Get galleryInfo in table, if do not exits return null + * + * @param gid + * @return + */ + public GalleryInfo getGallery(SQLiteDatabase db, int gid) { + GalleryInfo galleryInfo = mGallerys.get(gid); + if (galleryInfo == null) { + Cursor cursor = db.rawQuery("select * from " + TABLE_GALLERY + " where " + COLUMN_GID + "=?", + new String[]{String.valueOf(gid)}); + if (cursor.moveToFirst()) { + galleryInfo = new GalleryInfo(); + galleryInfo.gid = gid; + galleryInfo.token = cursor.getString(1); + galleryInfo.title = cursor.getString(2); + galleryInfo.posted = cursor.getString(3); + galleryInfo.category = cursor.getInt(4); + galleryInfo.thumb = cursor.getString(5); + galleryInfo.uploader = cursor.getString(6); + galleryInfo.rating = cursor.getFloat(7); + galleryInfo.generateSLang(); + + // add to map + mGallerys.put(gid, galleryInfo); + } + cursor.close(); + } + return galleryInfo; + } + + @SuppressWarnings("unused") + private synchronized boolean containsGallery(int gid) { + boolean re = false; + Cursor cursor = mDatabase.rawQuery("select * from " + + TABLE_GALLERY + " where " + COLUMN_GID + "=?", + new String[]{String.valueOf(gid)}); + if (cursor.moveToFirst()) + re = true; + cursor.close(); + return re; + } + + /** + * If do not contain the gallery return -1; + * @param gid + * @return + */ + private synchronized int getGalleryReference(int gid) { + int reference = -1; + Cursor cursor = mDatabase.rawQuery("select " + COLUMN_REFERENCE + " from " + + TABLE_GALLERY + " where " + COLUMN_GID + "=?", + new String[]{String.valueOf(gid)}); + if (cursor.moveToFirst()) { + reference = cursor.getInt(0); + } + cursor.close(); + return reference; + } + + /** + * + * If exits same gid, add the reference by 1. + * If exits no same gid, add it to table reference is 1 + * + * @param galleryInfo + */ + private void addGallery(GalleryInfo galleryInfo) { + boolean isInsert; + int gid = galleryInfo.gid; + // add to map + mGallerys.put(gid, galleryInfo); + + // add to sql or update and add reference by 1 + int reference; + if ((reference = getGalleryReference(gid)) != -1) { + isInsert = false; + reference++; + } + else { + isInsert = true; + reference = 1; + } + + ContentValues values = new ContentValues(); + values.put(COLUMN_GID, gid); + values.put(COLUMN_TOKEN, galleryInfo.token); + values.put(COLUMN_TITLE, galleryInfo.title); + values.put(COLUMN_POSTED, galleryInfo.posted); + values.put(COLUMN_CATEGORY, galleryInfo.category); + values.put(COLUMN_THUMB, galleryInfo.thumb); + values.put(COLUMN_UPLOADER, galleryInfo.uploader); + values.put(COLUMN_RATING, galleryInfo.rating); + values.put(COLUMN_REFERENCE, reference); + + if (isInsert) { // Set + mDatabase.insert(TABLE_GALLERY, null, values); + } else { // Update + mDatabase.update(TABLE_GALLERY, values, COLUMN_GID + "=?", new String[]{String.valueOf(gid)}); + } + } + + /** + * True if update + * + * @param gid + * @return + */ + private boolean updateGallery(GalleryInfo galleryInfo) { + ContentValues values = new ContentValues(); + values.put(COLUMN_GID, galleryInfo.gid); + values.put(COLUMN_TOKEN, galleryInfo.token); + values.put(COLUMN_TITLE, galleryInfo.title); + values.put(COLUMN_POSTED, galleryInfo.posted); + values.put(COLUMN_CATEGORY, galleryInfo.category); + values.put(COLUMN_THUMB, galleryInfo.thumb); + values.put(COLUMN_UPLOADER, galleryInfo.uploader); + values.put(COLUMN_RATING, galleryInfo.rating); + + return mDatabase.update(TABLE_GALLERY, values, + COLUMN_GID + "=?", new String[]{String.valueOf(galleryInfo.gid)}) != 0; + } + + /** + * decrease reference by 1, only delete when reference is 0 + * @param gid + */ + private boolean deleteGallery(int gid) { + int reference = getGalleryReference(gid); + if (reference == -1) { + Log.w(TAG, "Can't get reference when deleteGallery, gid is " + gid); + return false; + } + reference--; + if (reference == 0) { // delete gallery + // delete in sql + mDatabase.delete(TABLE_GALLERY, COLUMN_GID + "=?", new String[]{String.valueOf(gid)}); + // delete in list + mGallerys.remove(gid); + } else { // update reference + ContentValues values = new ContentValues(); + values.put(COLUMN_REFERENCE, reference); + mDatabase.update(TABLE_GALLERY, values, COLUMN_GID + "=?", new String[]{String.valueOf(gid)}); + } + return true; + } + + /****** download ******/ + private synchronized void getDownloads() { + boolean startService = false; + boolean keepDownloadService = Config.getKeepDownloadService(); + mDownloads = new ArrayList(); + + Cursor cursor = mDatabase.rawQuery("select * from " + TABLE_DOWNLOAD + + " order by " + COLUMN_DATE + " asc", null); + + if (cursor.moveToFirst()) { + while (!cursor.isAfterLast()) { + + int gid = cursor.getInt(0); + GalleryInfo galleryInfo = getGallery(mDatabase, gid); + if (galleryInfo == null) { + cursor.moveToNext(); + continue; + } + int mode = cursor.getInt(1); + int state = cursor.getInt(2); + int legacy = cursor.getInt(3); + if (state == DownloadInfo.STATE_WAIT || state == DownloadInfo.STATE_DOWNLOAD) { + if (keepDownloadService) { + state = DownloadInfo.STATE_WAIT; + startService = true; + } else { + state = DownloadInfo.STATE_NONE; + } + } + + DownloadInfo downloadInfo = new DownloadInfo( + galleryInfo, mode, state, legacy); + mDownloads.add(downloadInfo); + + cursor.moveToNext(); + } + } + cursor.close(); + + if (startService) { + Intent it = new Intent(mContext, DownloadService.class); + mContext.startService(it); + } + } + + /** + * Return null if not found + * @return + */ + public synchronized DownloadInfo getFirstWaitDownloadInfo() { + for (DownloadInfo di : mDownloads) { + if (di.state == DownloadInfo.STATE_WAIT) + return di; + } + return null; + } + + /** + * True if contain this item + * + * @param gid + * @return + */ + public synchronized boolean containsDownload(int gid) { + boolean re = false; + Cursor cursor = mDatabase.rawQuery("select * from " + + TABLE_DOWNLOAD + " where " + COLUMN_GID + "=?", + new String[]{String.valueOf(gid)}); + if (cursor.moveToFirst()) + re = true; + cursor.close(); + return re; + } + + /** + * If exits, just update + * + * @param downloadInfo + * @return + */ + public synchronized boolean addDownload(DownloadInfo downloadInfo) { + GalleryInfo galleryInfo = downloadInfo.galleryInfo; + int gid = galleryInfo.gid; + + // Add to download + boolean update; + if (containsDownload(gid)) { + update = true; + } else { + update = false; + addGallery(galleryInfo); + } + + ContentValues values = new ContentValues(); + values.put(COLUMN_GID, gid); + values.put(COLUMN_MODE, downloadInfo.mode); + values.put(COLUMN_STATE, downloadInfo.state); + values.put(COLUMN_LEGACY, downloadInfo.legacy); + + if (update) { + mDatabase.update(TABLE_DOWNLOAD, values, + COLUMN_GID + "=?", new String[]{String.valueOf(galleryInfo.gid)}); + return false; + } else { + // If not update, set date + values.put(COLUMN_DATE, System.currentTimeMillis()); + + if (mDatabase.insert(TABLE_DOWNLOAD, null, + values) != -1) { // If add ok + mDownloads.add(downloadInfo); + return true; + } else { // If add fail + deleteGallery(gid); + return false; + } + } + } + + public synchronized boolean deleteDownload(int gid) { + int deleteNum = mDatabase.delete(TABLE_DOWNLOAD, COLUMN_GID + "=?", new String[]{String.valueOf(gid)}); + if (deleteNum > 1) + Log.w(TAG, "WTF? more than one gid is " + gid); + + if (deleteNum == 0) + return false; + + // delete from list + for (DownloadInfo di : mDownloads) { + if (gid == di.galleryInfo.gid) { + mDownloads.remove(di); + break; + } + } + + // sub reference + deleteGallery(gid); + + return true; + } + + public synchronized List getAllDownloads() { + return mDownloads; + } + + /** + * Return null if not found + * + * @param gid + * @return + */ + public synchronized DownloadInfo getDownload(int gid) { + for (DownloadInfo di : mDownloads) { + if (gid == di.galleryInfo.gid) + return di; + } + return null; + } + + public synchronized void setDownloadState(int gid, int state) { + setDownloadState(getDownload(gid), state); + } + + public synchronized void setDownloadState(DownloadInfo di, int state) { + if (di == null || di.state == state) + return; + + di.state = state; + // Update in db + ContentValues values = new ContentValues(); + values.put(COLUMN_STATE, state); + mDatabase.update(TABLE_DOWNLOAD, values, + COLUMN_GID + "=?", new String[]{String.valueOf(di.galleryInfo.gid)}); + } + + /** + * If state is STATE_NONE or state is STATE_FINISH and legacy is not 0, + * make state STATE_WAIT + */ + public synchronized void startAllDownload() { + for (DownloadInfo di : mDownloads) { + if (di.state == DownloadInfo.STATE_NONE || + (di.state == DownloadInfo.STATE_FINISH && di.legacy != 0)) { + setDownloadState(di, DownloadInfo.STATE_WAIT); + } + } + } + + /** + * If state is STATE_WAIT or STATE_DOWNLOAD, + * make state STATE_NONE + */ + public synchronized void stopAllDownload() { + for (DownloadInfo di : mDownloads) { + if (di.state == DownloadInfo.STATE_WAIT || + di.state == DownloadInfo.STATE_DOWNLOAD) { + setDownloadState(di, DownloadInfo.STATE_NONE); + } + } + } + + /****** local favourite ******/ + private synchronized void getLocalFavourites() { + mLocalFavourites = new ArrayList(); + Cursor cursor = mDatabase.rawQuery("select * from " + + TABLE_LOCAL_FAVOURITE, null); + + if (cursor.moveToFirst()) { + while (!cursor.isAfterLast()) { + int gid = cursor.getInt(0); + GalleryInfo galleryInfo = getGallery(mDatabase, gid); + if (galleryInfo != null) + mLocalFavourites.add(galleryInfo); + cursor.moveToNext(); + } + } + cursor.close(); + } + + public synchronized boolean containsLocalFavourite(int gid) { + boolean re = false; + Cursor cursor = mDatabase.rawQuery("select * from " + + TABLE_LOCAL_FAVOURITE + " where " + COLUMN_GID + "=?", + new String[]{String.valueOf(gid)}); + if (cursor.moveToFirst()) + re = true; + cursor.close(); + return re; + } + + /** + * galleryInfo can't be null + * @param galleryInfo + */ + public synchronized void addLocalFavourite(GalleryInfo galleryInfo) { + int gid = galleryInfo.gid; + + if (containsLocalFavourite(gid)) { + updateGallery(galleryInfo); + } else { + addGallery(galleryInfo); + // add to sql + ContentValues values = new ContentValues(); + values.put(COLUMN_GID, galleryInfo.gid); + mDatabase.insert(TABLE_LOCAL_FAVOURITE, null, values); + // add to list + mLocalFavourites.add(galleryInfo); + + // Analytics + Analytics.addToFavoriteGallery(mContext, gid, galleryInfo.token); + } + } + + public synchronized void deleteLocalFavourite(int gid) { + // delete from list + for (GalleryInfo galleryInfo : mLocalFavourites) { + if (galleryInfo.gid == gid) { + mLocalFavourites.remove(galleryInfo); + break; + } + } + + // delete from sql + mDatabase.delete(TABLE_LOCAL_FAVOURITE, COLUMN_GID + "=?", new String[]{String.valueOf(gid)}); + deleteGallery(gid); + } + + public synchronized List getAllLocalFavourites() { + return mLocalFavourites; + } + + /****** tag ******/ + private synchronized void getTags() { + mTags = new ArrayList(); + mTagRowNum = 0; + Cursor cursor = mDatabase.rawQuery("select * from " + TABLE_TAG + + " order by " + COLUMN_ID + " asc", null); + if (cursor.moveToFirst()) { + while (!cursor.isAfterLast()) { + + String name = cursor.getString(1); + int mode = cursor.getInt(2); + int category = cursor.getInt(3); + String search = cursor.getString(4); + int advance = cursor.getInt(5); + int min_rating = cursor.getInt(6); + String tagStr = cursor.getString(7); + + Tag tag = new Tag(name, category, search); + tag.setAdvance(advance, min_rating); + tag.setTag(tagStr); + tag.setMode(mode); + + mTags.add(tag); + + cursor.moveToNext(); + mTagRowNum++; + } + } + cursor.close(); + } + + public synchronized boolean addTag(Tag tag) { + ContentValues values = new ContentValues(); + values.put(COLUMN_ID, mTagRowNum); + values.put(COLUMN_NAME, tag.getName()); + values.put(COLUMN_MODE, tag.getMode()); + values.put(COLUMN_CATEGORY, tag.getCategory()); + values.put(COLUMN_SEARCH, tag.getSearch()); + values.put(COLUMN_ADVANCE, tag.getAdvanceType()); + values.put(COLUMN_MIN_RATING, tag.getMinRating()); + values.put(COLUMN_TAG, tag.getTag()); + + boolean ok = mDatabase.insert( + TABLE_TAG, null, values) != -1; + if (ok) { + mTagRowNum++; + mTags.add(tag); + } + return ok; + } + + public synchronized boolean deleteTag(int id) { + int deleteNum = mDatabase.delete(TABLE_TAG, COLUMN_ID + "=?", new String[]{String.valueOf(id)}); + if (deleteNum > 1) + Log.e(TAG, "WTF? more than one id is " + id); + + if (deleteNum == 0) + return false; + if (id < 0 || id >= mTags.size()) { + Log.e(TAG, "id is out of bounds, id is " + id + ", mTags.size() is " + mTags.size()); + return false; + } + + // Update sql + ContentValues values = new ContentValues(); + for (int i = id + 1; i < mTagRowNum; i++) { + values.put(COLUMN_ID, i - 1); + mDatabase.update(TABLE_TAG, values, COLUMN_ID + "=?", new String[]{String.valueOf(i)}); + } + mTagRowNum -= deleteNum; + + // Update list + mTags.remove(id); + + return true; + } + + public synchronized List getAllTags() { + return mTags; + } + + public synchronized Tag getTag(int location) { + return mTags.get(location); + } + + public synchronized List getAllTagNames() { + List names = new ArrayList(); + for (Tag tag : mTags) + names.add(tag.getName()); + return names; + } + + /** + * location >= 0 && location <= tagRowNum please + * @param location + * @param tag + */ + public synchronized void setTag(int location, Tag tag) { + if (location == mTagRowNum) { + addTag(tag); + } else { + // Update list + mTags.set(location, tag); + + // Update sql + ContentValues values = new ContentValues(); + values.put(COLUMN_NAME, tag.getName()); + values.put(COLUMN_CATEGORY, tag.getCategory()); + values.put(COLUMN_SEARCH, tag.getSearch()); + values.put(COLUMN_ADVANCE, tag.getAdvanceType()); + values.put(COLUMN_MIN_RATING, tag.getMinRating()); + mDatabase.update(TABLE_TAG, values, COLUMN_ID + "=?", new String[]{String.valueOf(location)}); + } + } + + public synchronized boolean swapTag(int indexOne, int indexTwo) { + if (indexOne < 0 || indexOne > mTagRowNum + || indexTwo < 0 || indexTwo > mTagRowNum) + return false; + + if (indexOne == indexTwo) + return true; + + // Update list + Tag temp = mTags.get(indexOne); + mTags.set(indexOne, mTags.get(indexTwo)); + mTags.set(indexTwo, temp); + // Update sql + ContentValues values = new ContentValues(); + values.put(COLUMN_ID, -1); + mDatabase.update(TABLE_TAG, values, COLUMN_ID + "=?", new String[]{String.valueOf(indexOne)}); + values.put(COLUMN_ID, indexOne); + mDatabase.update(TABLE_TAG, values, COLUMN_ID + "=?", new String[]{String.valueOf(indexTwo)}); + values.put(COLUMN_ID, indexTwo); + mDatabase.update(TABLE_TAG, values, COLUMN_ID + "=?", new String[]{String.valueOf(-1)}); + return true; + } + + public static final int NONE = 0x0; + public static final int BROWSE = 0x1; + public static final int READ = 0x2; + + public synchronized List getHistory(int mode, boolean desc) { + List history = new ArrayList(); + + StringBuilder sb = new StringBuilder(); + sb.append("select * from ").append(TABLE_HISTORY); + boolean first = true; + if (mode != 0) { + sb.append(" where " + COLUMN_MODE + " in ("); + for (int b = 1; b <= READ; b <<= 1) { + if ((mode & b) != 0) { + sb.append(first ? "" : ", ").append(b); + first = false; + } + } + sb.append(")"); + } + sb.append(" order by ").append(COLUMN_DATE).append(desc ? " desc" : " asc"); + + Log.d(TAG, sb.toString()); + + Cursor cursor = mDatabase.rawQuery(sb.toString(), null); + if (cursor.moveToFirst()) { + while (!cursor.isAfterLast()) { + int gid = cursor.getInt(0); + GalleryInfo gi = getGallery(mDatabase, gid); + if (gi != null) { + history.add(new HistoryItem(gi, cursor.getInt(1), cursor.getLong(2))); + } + cursor.moveToNext(); + } + } + cursor.close(); + + return history; + } + + public synchronized int containsHistory(int gid) { + int re; + Cursor cursor = mDatabase.rawQuery("select * from " + + TABLE_HISTORY + " where " + COLUMN_GID + "=?", + new String[]{String.valueOf(gid)}); + if (cursor.moveToFirst()) + re = cursor.getInt(1); + else + re = NONE; + cursor.close(); + return re; + } + + private int checkMode(int mode) { + return MathUtils.clamp(mode, NONE, READ); + } + + public synchronized void addHistory(GalleryInfo gi, int mode) { + mode = checkMode(mode); + + long curDate = System.currentTimeMillis(); + ContentValues values = new ContentValues(); + values.put(COLUMN_GID, gi.gid); + values.put(COLUMN_DATE, curDate); + + int curMode = checkMode(containsHistory(gi.gid)); + if (curMode == NONE) { + // Add new record + values.put(COLUMN_MODE, mode); + mDatabase.insert(TABLE_HISTORY, null, values); + addGallery(gi); + + truncateHistory(Config.getMaxHistoryCount()); + } else { + // Update record + int newMode = Math.max(curMode, mode); + values.put(COLUMN_MODE, newMode); + mDatabase.update(TABLE_HISTORY, values, + COLUMN_GID + "=?", new String[]{String.valueOf(gi.gid)}); + updateGallery(gi); + } + } + + public void clearHistory() { + truncateHistory(0); + } + + protected synchronized void truncateHistory(int maxEntries) { + if (maxEntries < 0) { + return; + } else if (maxEntries == 0) { + mDatabase.delete(TABLE_HISTORY, null, null); + } else { + try { + mDatabase.execSQL("delete from " + TABLE_HISTORY + " where " + COLUMN_GID + + " in " + "(select " + COLUMN_GID + " from " + TABLE_HISTORY + + " order by " + COLUMN_DATE + " desc limit " + maxEntries + + ", -1);"); + } catch(Throwable e){} + } + } + + public class DBHelper extends SQLiteOpenHelper { + + public DBHelper(Context context) { + super(context, DB_NAME, null, VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + // gallery + String CreateGallery = "create table " + TABLE_GALLERY + "(" + + COLUMN_GID + " integer primary key," + + COLUMN_TOKEN + " text," + + COLUMN_TITLE + " text," + + COLUMN_POSTED + " text," + + COLUMN_CATEGORY + " integer," + + COLUMN_THUMB + " text," + + COLUMN_UPLOADER + " text," + + COLUMN_RATING + " float," + + COLUMN_REFERENCE + " integer);"; + db.execSQL(CreateGallery); + + // local favourite + String CreateLocalFavourite = "create table " + + TABLE_LOCAL_FAVOURITE + "(" + + COLUMN_GID + " integer primary key," + + "foreign key(" + COLUMN_GID + ") references " + TABLE_GALLERY + "(" + COLUMN_GID + "));"; + db.execSQL(CreateLocalFavourite); + + // tag + String CreateTag = "create table " + + TABLE_TAG + "(" + + COLUMN_ID + " integer primary key," + + COLUMN_NAME + " text," + + COLUMN_MODE + " integer," + + COLUMN_CATEGORY + " integer," + + COLUMN_SEARCH + " text," + + COLUMN_ADVANCE + " integer," + + COLUMN_MIN_RATING + " integer," + + COLUMN_TAG + " text);"; + db.execSQL(CreateTag); + + // download + createDownloadTableVer3(db); + + // History + createHistoryTable(db); + } + + private void createDownloadTableVer2(SQLiteDatabase db) { + String createDownload = "create table " + + TABLE_DOWNLOAD + "(" + + COLUMN_GID + " integer primary key," + + COLUMN_MODE + " integer," + + COLUMN_STATE + " integer," + + COLUMN_LEGACY + " integer," + + "foreign key(" + COLUMN_GID + ") references " + TABLE_GALLERY + "(" + COLUMN_GID + "));"; + + SqlUtils.exeSQLSafely(db, createDownload); + } + + private synchronized List getDownloadsVer2(SQLiteDatabase db) { + List list = new ArrayList(); + + Cursor cursor = db.rawQuery("select * from " + TABLE_DOWNLOAD, + null); + + if (cursor.moveToFirst()) { + while (!cursor.isAfterLast()) { + + int gid = cursor.getInt(0); + GalleryInfo galleryInfo = getGallery(db, gid); + if (galleryInfo == null) { + cursor.moveToNext(); + continue; + } + int mode = cursor.getInt(1); + int state = cursor.getInt(2); + int legacy = cursor.getInt(3); + if (state == DownloadInfo.STATE_WAIT || state == DownloadInfo.STATE_DOWNLOAD) + state = DownloadInfo.STATE_NONE; + + DownloadInfo downloadInfo = new DownloadInfo( + galleryInfo, mode, state, legacy); + list.add(downloadInfo); + + cursor.moveToNext(); + } + } + cursor.close(); + + return list; + } + + private void createDownloadTableVer3(SQLiteDatabase db) { + String createDownload = "create table " + + TABLE_DOWNLOAD + "(" + + COLUMN_GID + " integer primary key," + + COLUMN_MODE + " integer," + + COLUMN_STATE + " integer," + + COLUMN_LEGACY + " integer," + + COLUMN_DATE + " long," + + "foreign key(" + COLUMN_GID + ") references " + TABLE_GALLERY + "(" + COLUMN_GID + "));"; + + SqlUtils.exeSQLSafely(db, createDownload); + } + + public void addDownloadInfoVer3(SQLiteDatabase db, List l) { + for (int i = 0; i < l.size(); i++) { + DownloadInfo di = l.get(i); + ContentValues values = new ContentValues(); + values.put(COLUMN_GID, di.galleryInfo.gid); + values.put(COLUMN_MODE, di.mode); + values.put(COLUMN_STATE, di.state); + values.put(COLUMN_LEGACY, di.legacy); + values.put(COLUMN_DATE, (long) i); + db.insert(TABLE_DOWNLOAD, null, values); + } + } + + private void createHistoryTable(SQLiteDatabase db) { + String createDownload = "create table " + + TABLE_HISTORY + "(" + + COLUMN_GID + " integer primary key," + + COLUMN_MODE + " integer," + + COLUMN_DATE + " long," + + "foreign key(" + COLUMN_GID + ") references " + TABLE_GALLERY + "(" + COLUMN_GID + "));"; + + SqlUtils.exeSQLSafely(db, createDownload); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + switch (oldVersion) { + case 1: + SqlUtils.dropTable(db, TABLE_DOWNLOAD); + SqlUtils.dropTable(db, "read"); + createDownloadTableVer2(db); + case 2: + List l = getDownloadsVer2(db); + SqlUtils.dropTable(db, TABLE_DOWNLOAD); + createDownloadTableVer3(db); + addDownloadInfoVer3(db, l); + case 3: + createHistoryTable(db); + case VERSION: + break; + default: + SqlUtils.dropAllTable(db); + onCreate(db); + break; + } + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/DownloadInfo.java b/app/src/main/java/com/hippo/ehviewer/data/DownloadInfo.java new file mode 100644 index 000000000..ea4d68469 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/data/DownloadInfo.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.data; + +import com.hippo.ehviewer.widget.ExpandableListItem; + + +public class DownloadInfo implements ExpandableListItem { + @SuppressWarnings("unused") + private static final String TAG = DownloadInfo.class.getSimpleName(); + + public static final int STATE_NONE = 0x0; + public static final int STATE_WAIT = 0x1; + public static final int STATE_DOWNLOAD = 0x2; + public static final int STATE_FINISH = 0x3; + + public final GalleryInfo galleryInfo; + public final int mode; + public volatile int state = STATE_NONE; + /** byte/second **/ + public volatile int speed; + public volatile int download = -1; + public volatile int total = -1; + public volatile int legacy; + + private boolean mExpanded = false; + + public DownloadInfo(GalleryInfo gi, int mode) { + this.galleryInfo = gi; + this.mode = mode; + } + + public DownloadInfo(GalleryInfo gi, int mode, int state, int legacy) { + this.galleryInfo = gi; + this.mode = mode; + this.state = state; + this.legacy = legacy; + } + + @Override + public void setExpanded(boolean expanded) { + mExpanded = expanded; + } + + @Override + public boolean getExpanded() { + return mExpanded; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/GalleryDetail.java b/app/src/main/java/com/hippo/ehviewer/data/GalleryDetail.java new file mode 100644 index 000000000..aa957edf7 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/data/GalleryDetail.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.data; + +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; + +public class GalleryDetail extends GalleryInfo + implements PreviewImpl { + + public String title_jpn; + public int pages; + public String size; + public String resized; + public String parent; + public String visible; + public String language; + public int people; + public String firstPage; + /** + * Can't be null, just can be empty + */ + public LinkedHashMap> tags; + + // For Preview + public int previewSum; + public int previewPerPage; + public PreviewList[] previewLists; + public List comments; + + public GalleryDetail() {} + + public GalleryDetail(GalleryInfo galleryInfo) { + gid = galleryInfo.gid; + token = galleryInfo.token; + title = galleryInfo.title; + posted = galleryInfo.posted; + category = galleryInfo.category; + thumb = galleryInfo.thumb; + uploader = galleryInfo.uploader; + rating = galleryInfo.rating; + simpleLanguage = galleryInfo.simpleLanguage; + } + + @Override + public int getGid() { + return gid; + } + + @Override + public String getToken() { + return token; + } + + @Override + public String getTitle() { + return title; + } + + @Override + public int getPreviewPageNum() { + return previewSum; + } + + @Override + public int getPreviewPerPage() { + return previewPerPage; + } + + @Override + public PreviewList getPreview(int page) { + if (page >= 0 && previewLists != null && page < previewLists.length) + return previewLists[page]; + else + return null; + } + + @Override + public void setPreview(int page, PreviewList previewList) { + if (previewLists == null && previewSum > 0) + previewLists = new PreviewList[previewSum]; + + if (page >= 0 && previewLists != null && page < previewLists.length) + previewLists[page] = previewList; + } + + @Override + public GalleryInfo toGalleryInfo() { + return new GalleryInfo(this); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/GalleryInfo.java b/app/src/main/java/com/hippo/ehviewer/data/GalleryInfo.java new file mode 100644 index 000000000..89b15a382 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/data/GalleryInfo.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.data; + +import java.util.regex.Pattern; + +import android.os.Parcel; +import android.os.Parcelable; + +public class GalleryInfo implements Parcelable { + + /** + * ISO 639-1 + */ + public static final String S_LANG_JA = "JA"; + public static final String S_LANG_EN = "EN"; + public static final String S_LANG_ZH = "ZH"; + public static final String S_LANG_NL = "NL"; + public static final String S_LANG_FR = "FR"; + public static final String S_LANG_DE = "DE"; + public static final String S_LANG_HU = "HU"; + public static final String S_LANG_IT = "IT"; + public static final String S_LANG_KO = "KO"; + public static final String S_LANG_PL = "PL"; + public static final String S_LANG_PT = "PT"; + public static final String S_LANG_RU = "RU"; + public static final String S_LANG_ES = "ES"; + public static final String S_LANG_TH = "TH"; + public static final String S_LANG_VI = "VI"; + + public static final String[] S_LANGS = { + S_LANG_EN, + S_LANG_ZH, + S_LANG_ES, + S_LANG_KO, + S_LANG_RU, + S_LANG_FR, + S_LANG_PT, + S_LANG_TH, + S_LANG_DE, + S_LANG_IT, + S_LANG_VI, + S_LANG_PL, + S_LANG_HU, + S_LANG_NL, + }; + + public static final String[] S_LANG_PATTERNS = { + "[(\\[]eng(?:lish)?[)\\]]", + "[((\\[]chinese[))\\]]|[汉漢]化|中[国國][语語]|中文|[(\\[]CN[)\\]]", + "[(\\[]spanish[)\\]]|[(\\[]Español[)\\]]", + "[(\\[]korean?[)\\]]", + "[(\\[]rus(?:sian)?[)\\]]", + "[(\\[]fr(?:ench)?[)\\]]", + "[(\\[]portuguese", + "[(\\[]thai(?: ภาษาไทย)?[)\\]]", + "[(\\[]german[)\\]]", + "[(\\[]italiano?[)\\]]", + "[(\\[]vietnamese(?: Tiếng Việt)?[)\\]]", + "[(\\[]polish[)\\]]", + "[(\\[]hun(?:garian)?[)\\]]", + "[(\\[]dutch[)\\]]", + }; + + public int gid; + public String token; + public String title; + public String posted; + public int category; + public String thumb; + public String uploader; + public float rating; + /** + * Will not be recond + */ + public int thumbWidth; + /** + * Will not be recond + */ + public int thumbHeight; + /** + * language get from title + */ + public String simpleLanguage; + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public GalleryInfo createFromParcel(Parcel source) { + GalleryInfo p = new GalleryInfo(); + p.gid = source.readInt(); + p.token = source.readString(); + p.title = source.readString(); + p.posted = source.readString(); + p.category = source.readInt(); + p.thumb = source.readString(); + p.uploader = source.readString(); + p.rating = source.readFloat(); + p.simpleLanguage = source.readString(); + return p; + } + + @Override + public GalleryInfo[] newArray(int size) { + return new GalleryInfo[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(gid); + dest.writeString(token); + dest.writeString(title); + dest.writeString(posted); + dest.writeInt(category); + dest.writeString(thumb); + dest.writeString(uploader); + dest.writeFloat(rating); + dest.writeString(simpleLanguage); + } + + public GalleryInfo() { + // Empty + } + + public GalleryInfo(GalleryInfo gi) { + gid = gi.gid; + token = gi.token; + title = gi.title; + posted = gi.posted; + category = gi.category; + thumb = gi.thumb; + uploader = gi.uploader; + rating = gi.rating; + simpleLanguage = gi.simpleLanguage; + } + + @Override + public boolean equals(Object o) { + if (o instanceof GalleryInfo && ((GalleryInfo) o).gid == gid) + return true; + else + return false; + } + + public final void generateSLang() { + for (int i = 0; i < S_LANGS.length; i++) { + if (Pattern.compile(S_LANG_PATTERNS[i], Pattern.CASE_INSENSITIVE).matcher(title).find()) { + simpleLanguage = S_LANGS[i]; + return; + } + } + simpleLanguage = null; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/GalleryPopular.java b/app/src/main/java/com/hippo/ehviewer/data/GalleryPopular.java new file mode 100644 index 000000000..6395bb9ce --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/data/GalleryPopular.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.data; + +import android.os.Parcel; +import android.os.Parcelable; + +public class GalleryPopular extends GalleryInfo { + public long count; + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public GalleryPopular createFromParcel(Parcel source) { + GalleryPopular p = new GalleryPopular(); + p.gid = source.readInt(); + p.token = source.readString(); + p.title = source.readString(); + p.posted = source.readString(); + p.category = source.readInt(); + p.thumb = source.readString(); + p.uploader = source.readString(); + p.rating = source.readFloat(); + p.simpleLanguage = source.readString(); + p.count = source.readLong(); + return p; + } + + @Override + public GalleryPopular[] newArray(int size) { + return new GalleryPopular[size]; + } + }; + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeLong(count); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/HistoryItem.java b/app/src/main/java/com/hippo/ehviewer/data/HistoryItem.java new file mode 100644 index 000000000..f2a8bb28a --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/data/HistoryItem.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.data; + +import android.os.Parcel; +import android.os.Parcelable; + +public class HistoryItem extends GalleryInfo { + public int mode; + public long date; + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public HistoryItem createFromParcel(Parcel source) { + HistoryItem p = new HistoryItem(); + p.gid = source.readInt(); + p.token = source.readString(); + p.title = source.readString(); + p.posted = source.readString(); + p.category = source.readInt(); + p.thumb = source.readString(); + p.uploader = source.readString(); + p.rating = source.readFloat(); + p.simpleLanguage = source.readString(); + p.mode = source.readInt(); + p.date = source.readLong(); + return p; + } + + @Override + public HistoryItem[] newArray(int size) { + return new HistoryItem[size]; + } + }; + + public HistoryItem() { + // Empty + } + + public HistoryItem(GalleryInfo gi, int m, long d) { + super(gi); + mode = m; + date = d; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mode); + dest.writeLong(date); + } + +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/LargePreviewList.java b/app/src/main/java/com/hippo/ehviewer/data/LargePreviewList.java new file mode 100644 index 000000000..6ca2e55d5 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/data/LargePreviewList.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.data; + +import java.util.ArrayList; +import java.util.List; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.graphics.Bitmap; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.hippo.ehviewer.ImageLoader; +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.ui.GalleryActivity; +import com.hippo.ehviewer.widget.SimpleGridLayout; + +public class LargePreviewList extends PreviewList { + + private class Item { + public String mImageUrl; + public String mPageUrl; + + public Item(String imageUrl, String pageUrl) { + mImageUrl = imageUrl; + mPageUrl = pageUrl; + } + } + + public List mItemList = new ArrayList(); + + public void addItem(String imageUrl, String pageUrl) { + mItemList.add(new Item(imageUrl, pageUrl)); + } + + @Override + public String getPageUrl(int index) { + if (index >= 0 && index < mItemList.size()) + return mItemList.get(index).mPageUrl; + return null; + } + + @Override + public int size() { + return mItemList.size(); + } + + @SuppressLint("InflateParams") + @Override + public void addPreview(SimpleGridLayout viewGroup) { + if (mTargetPage != mHolder.getCurPreviewPage()) + return; + + viewGroup.removeAllViews(); + LayoutInflater inflater = LayoutInflater.from(mActivity); + int startIndex = mTargetPage * mPi.getPreviewPerPage(); + int index = startIndex; + for (Item item : mItemList) { + + View view = inflater.inflate(R.layout.preview_item, null); + ((TextView)view.findViewById(R.id.text)).setText(String.valueOf(index + 1)); + final int _index = index; + view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // Add to read in Data + Intent intent = new Intent(mActivity, + GalleryActivity.class); + intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, mPi.toGalleryInfo()); + intent.putExtra(GalleryActivity.KEY_START_INDEX, _index); + mActivity.startActivity(intent); + } + }); + viewGroup.addView(view); + // TODO I need a better key + ImageLoader.getInstance(mActivity).add(item.mImageUrl, mPi.getGid() + + "-preview-" + index, + new PreviewImageGetListener(viewGroup, index - startIndex)); + + index++; + } + } + + private class PreviewImageGetListener implements ImageLoader.OnGetImageListener { + private final SimpleGridLayout mViewGroup; + private final int mIndex; + + /** + * index is view index in viewgroup + * @param viewGroup + * @param index + */ + public PreviewImageGetListener(SimpleGridLayout viewGroup, int index) { + mViewGroup = viewGroup; + mIndex = index; + } + + @Override + public void onGetImage(String key, Bitmap bmp, int state) { + if (mTargetPage != mHolder.getCurPreviewPage()) + return; + + if (bmp == null) + mHolder.onGetPreviewImageFailure(); + else + ((ImageView)mViewGroup.getChildAt(mIndex).findViewById(R.id.image)).setImageBitmap(bmp); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/ListUrls.java b/app/src/main/java/com/hippo/ehviewer/data/ListUrls.java new file mode 100644 index 000000000..100c1e3c3 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/data/ListUrls.java @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.data; + +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +import com.hippo.ehviewer.ehclient.EhClient; +import com.hippo.ehviewer.network.UrlBuilder; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.Utils; + +public class ListUrls { + + public static final boolean NUM_CATEGORY = false; + + // Mode + public static final int MODE_NORMAL = 0x0; + public static final int MODE_UPLOADER = 0x1; + public static final int MODE_TAG = 0x2; + public static final int MODE_POPULAR = 0x3; + public static final int MODE_IMAGE_SEARCH = 0x4; + + // Category + public static final int NONE = -1; // Use it for homepage + public static final int MISC = 0x1; + public static final int DOUJINSHI = 0x2; + public static final int MANGA = 0x4; + public static final int ARTIST_CG = 0x8; + public static final int GAME_CG = 0x10; + public static final int IMAGE_SET = 0x20; + public static final int COSPLAY = 0x40; + public static final int ASIAN_PORN = 0x80; + public static final int NON_H = 0x100; + public static final int WESTERN = 0x200; + public static final int UNKNOWN = 0x400; + + public static final int ALL_CATEGORT = UNKNOWN - 1; + //DOUJINSHI|MANGA|ARTIST_CG|GAME_CG|WESTERN|NON_H|IMAGE_SET|COSPLAY|ASIAN_PORN|MISC; + + // advance search + public static final int SNAME = 0x1; + public static final int STAGS = 0x2; + public static final int SDESC = 0x4; + public static final int STORR = 0x8; + public static final int STO = 0x10; + public static final int STD1 = 0x20; + public static final int STD2 = 0x40; + public static final int SH = 0x80; + + public static final int DEFAULT_ADVANCE = SNAME | STAGS; + public static final int DEFAULT_MIN_RATING = 2; + + private int mMode = MODE_NORMAL; + + private int page = 0; + private int category = NONE; + + private int advsearchType = -1; + private int minRating = -1; + + private String search = null; + private String mTag = null; + + private int mImageSearchMode; + private String mImageKey; + private String mImageUrl; + private File mSearchFile; + private String mResultUrl = null; + + public ListUrls() {} + + public ListUrls(int category) { + this.category = category; + } + + public ListUrls(int category, int page) { + this.category = category; + this.page = page; + } + + public ListUrls(int category, String search) { + this.category = category; + this.search = search; + } + + public ListUrls(int category, String search, int page) { + this.category = category; + this.search = search; + this.page = page; + } + + public void setCategory(int category) { + this.category = category; + } + + public int getCategory() { + return category; + } + + public String getSearch() { + return search; + } + + public void setAdvance(boolean enabled) { + if (enabled && advsearchType == -1) + advsearchType = DEFAULT_ADVANCE; + else if (!enabled) + advsearchType = -1; + } + + public void setAdvance(int advanceType) { + advsearchType = advanceType; + } + + public void setAdvance(int advanceType, int minRating) { + advsearchType = advanceType; + this.minRating = minRating; + } + + public boolean isAdvance() { + return advsearchType != -1; + } + + public int getAdvanceType() { + return advsearchType; + } + + public boolean isMinRating() { + return minRating != -1; + } + + public int getMinRating() { + return minRating; + } + + public void setPage(int p) { + page = p; + } + + public int getPage() { + return page; + } + + public int getMode() { + return mMode; + } + + public void setMode(int mode) { + mMode = mode; + } + + // For tag mode + public void setTag(String tag) { + mMode = MODE_TAG; + mTag = tag; + } + + public String getTag() { + return mTag; + } + + // For image search mode + public void setSearchImage(String key, String url, int imageSearchMode) { + mMode = MODE_IMAGE_SEARCH; + mImageKey = key; + mImageUrl = url; + mImageSearchMode = imageSearchMode; + } + + public void setSearchFile(File searchFile, int imageSearchMode) { + mMode = MODE_IMAGE_SEARCH; + mSearchFile = searchFile; + mImageSearchMode = imageSearchMode; + } + + public void setSearchResult(String resultUrl) { + mResultUrl = resultUrl; + } + + public File getSearchFile() { + return mSearchFile; + } + + public String getSearchImageKey() { + return mImageKey; + } + + public String getSearchImageUrl() { + return mImageUrl; + } + + public int getImageSearchMode() { + return mImageSearchMode; + } + + + public String getUrl() { + return getUrl(Config.getMode()); + } + + public String getUrl(int mode) { + + switch (mMode) { + case MODE_POPULAR: + return null; + + case MODE_IMAGE_SEARCH: + if (mResultUrl == null) + return null; + else + return mResultUrl + "&page=" + page; + + case MODE_TAG: + // Add tag + StringBuilder sb = new StringBuilder(EhClient.getUrlHeader(mode)); + sb.append("tag/"); + if (mTag != null) { + String[] tag = mTag.split("\\s+"); + try { + for (int i = 0; i < tag.length; i++) + tag[i] = URLEncoder.encode(tag[i], "UTF-8"); + } catch (UnsupportedEncodingException e) {} + sb.append(Utils.join(tag, '+')); + + // Add page + sb.append("/").append(page); + } else { + sb.append(page); + } + return sb.toString(); + + case MODE_NORMAL: + case MODE_UPLOADER: + default: + String header = EhClient.getUrlHeader(mode); + boolean isNeedFooter = false; + UrlBuilder ub; + // Add category + if (category != NONE) { + isNeedFooter = true; + if (NUM_CATEGORY) { + ub = new UrlBuilder(header + category); + } else { + ub = new UrlBuilder(header); + ub.addQuery("f_doujinshi", ((category & DOUJINSHI) == 0) ? "0" : "1"); + ub.addQuery("f_manga", ((category & MANGA) == 0) ? "0" : "1"); + ub.addQuery("f_artistcg", ((category & ARTIST_CG) == 0) ? "0" : "1"); + ub.addQuery("f_gamecg", ((category & GAME_CG) == 0) ? "0" : "1"); + ub.addQuery("f_western", ((category & WESTERN) == 0) ? "0" : "1"); + ub.addQuery("f_non-h", ((category & NON_H) == 0) ? "0" : "1"); + ub.addQuery("f_imageset", ((category & IMAGE_SET) == 0) ? "0" : "1"); + ub.addQuery("f_cosplay", ((category & COSPLAY) == 0) ? "0" : "1"); + ub.addQuery("f_asianporn", ((category & ASIAN_PORN) == 0) ? "0" : "1"); + ub.addQuery("f_misc", ((category & MISC) == 0) ? "0" : "1"); + } + } else { + ub = new UrlBuilder(header); + } + + // Add search item + if (search != null) { + isNeedFooter = true; + + String[] tag = search.split("\\s+"); + try { + for (int i = 0; i < tag.length; i++) + tag[i] = URLEncoder.encode(tag[i], "UTF-8"); + } catch (UnsupportedEncodingException e) {} + + ub.addQuery("f_search", Utils.join(tag, '+')); + } + + // Add page + if (page != 0) { + ub.addQuery("page", page); + } + + // Add foot + if (isNeedFooter) ub.addQuery("f_apply", "Apply+Filter"); + + // Add advance search + if (isAdvance()) { + ub.addQuery("advsearch", "1"); + if((advsearchType & SNAME) != 0) ub.addQuery("f_sname", "on"); + if((advsearchType & STAGS) != 0) ub.addQuery("f_stags", "on"); + if((advsearchType & SDESC) != 0) ub.addQuery("f_sdesc", "on"); + if((advsearchType & STORR) != 0) ub.addQuery("f_storr", "on"); + if((advsearchType & STO) != 0) ub.addQuery("f_sto", "on"); + if((advsearchType & STD1) != 0) ub.addQuery("f_sdt1", "on"); + if((advsearchType & STD2) != 0) ub.addQuery("f_sdt2", "on"); + if((advsearchType & SH) != 0) ub.addQuery("f_sh", "on"); + + // Set min star + if (isMinRating()) { + ub.addQuery("f_sr", "on"); + ub.addQuery("f_srdd", minRating); + } + } + return ub.build(); + } + } + + + @Override + public String toString() { + switch (mMode) { + case MODE_NORMAL: + case MODE_UPLOADER: + return search; + case MODE_TAG: + return mTag; + case MODE_POPULAR: + case MODE_IMAGE_SEARCH: + default: + return null; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/LofiDetailImpl.java b/app/src/main/java/com/hippo/ehviewer/data/LofiDetailImpl.java new file mode 100644 index 000000000..f18780596 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/data/LofiDetailImpl.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.data; + +public interface LofiDetailImpl extends PreviewImpl { + + public void setPreviewPageNum(int pageNum); + + /** + * Check null and empty before use + * + * @return + */ + public String[] getTags(); + +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/LofiGalleryDetail.java b/app/src/main/java/com/hippo/ehviewer/data/LofiGalleryDetail.java new file mode 100644 index 000000000..2e24b35a5 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/data/LofiGalleryDetail.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.data; + +import java.util.ArrayList; +import java.util.List; + +public class LofiGalleryDetail extends LofiGalleryInfo + implements LofiDetailImpl { + public int previewPageNum; + public int previewPerPage; + private final List previewLists = new ArrayList(); + + public LofiGalleryDetail(GalleryInfo galleryInfo) { + this(new LofiGalleryInfo(galleryInfo)); + } + + public LofiGalleryDetail(LofiGalleryInfo lofiGalleryInfo) { + gid = lofiGalleryInfo.gid; + token = lofiGalleryInfo.token; + title = lofiGalleryInfo.title; + posted = lofiGalleryInfo.posted; + category = lofiGalleryInfo.category; + thumb = lofiGalleryInfo.thumb; + uploader = lofiGalleryInfo.uploader; + rating = lofiGalleryInfo.rating; + simpleLanguage = lofiGalleryInfo.simpleLanguage; + lofiTags = lofiGalleryInfo.lofiTags; + + previewPageNum = Integer.MAX_VALUE; + } + + @Override + public int getGid() { + return gid; + } + + @Override + public String getToken() { + return token; + } + + @Override + public String getTitle() { + return title; + } + + @Override + public int getPreviewPageNum() { + return previewPageNum; + } + + @Override + public void setPreviewPageNum(int pageNum) { + previewPageNum = pageNum; + } + + @Override + public int getPreviewPerPage() { + return previewPerPage; + } + + @Override + public PreviewList getPreview(int page) { + if (page >= 0 && page < previewLists.size()) + return previewLists.get(page); + else + return null; + } + + @Override + public void setPreview(int page, PreviewList previewList) { + if (page >= 0 && page < previewLists.size()) + previewLists.set(page, previewList); + else if (page == previewLists.size()) + previewLists.add(previewList); + else + ; // Do nothing + } + + @Override + public String[] getTags() { + return lofiTags; + } + + @Override + public GalleryInfo toGalleryInfo() { + return new GalleryInfo(this); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/LofiGalleryInfo.java b/app/src/main/java/com/hippo/ehviewer/data/LofiGalleryInfo.java new file mode 100644 index 000000000..99b7cc295 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/data/LofiGalleryInfo.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.data; + +import android.os.Parcel; +import android.os.Parcelable; + +public class LofiGalleryInfo extends GalleryInfo { + + /** + * It could be null, please check before use. + */ + public String[] lofiTags; + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public LofiGalleryInfo createFromParcel(Parcel source) { + int length; + + LofiGalleryInfo p = new LofiGalleryInfo(); + p.gid = source.readInt(); + p.token = source.readString(); + p.title = source.readString(); + p.posted = source.readString(); + p.category = source.readInt(); + p.thumb = source.readString(); + p.uploader = source.readString(); + p.rating = source.readFloat(); + p.simpleLanguage = source.readString(); + + length = source.readInt(); + if (length > 0) { + p.lofiTags = new String[length]; + for (int i = 0; i < length; i++) + p.lofiTags[i] = source.readString(); + } + + return p; + } + + @Override + public LofiGalleryInfo[] newArray(int size) { + return new LofiGalleryInfo[size]; + } + }; + + public LofiGalleryInfo() {} + + public LofiGalleryInfo(GalleryInfo galleryInfo) { + gid = galleryInfo.gid; + token = galleryInfo.token; + title = galleryInfo.title; + posted = galleryInfo.posted; + category = galleryInfo.category; + thumb = galleryInfo.thumb; + uploader = galleryInfo.uploader; + rating = galleryInfo.rating; + simpleLanguage = galleryInfo.simpleLanguage; + lofiTags = null; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + + int length = lofiTags == null ? 0 : lofiTags.length; + dest.writeInt(length); + for (int i = 0; i < length; i++) + dest.writeString(lofiTags[i]); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/NormalPreviewList.java b/app/src/main/java/com/hippo/ehviewer/data/NormalPreviewList.java new file mode 100644 index 000000000..1dd6701dc --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/data/NormalPreviewList.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.data; + +import java.util.ArrayList; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.graphics.Bitmap; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.hippo.ehviewer.ImageLoader; +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.ui.GalleryActivity; +import com.hippo.ehviewer.widget.SimpleGridLayout; + +public class NormalPreviewList extends PreviewList{ + + public class Item { + public int xOffset; + public int yOffset; + public int width; + public int height; + public String url; + + public Item(int xOffset, int yOffset, int width, int height, String url) { + this.xOffset = xOffset; + this.yOffset = yOffset; + this.width = width; + this.height = height; + this.url = url; + } + } + + public class Row { + public String imageUrl; + public ArrayList itemArray = new ArrayList(); + public int startIndex; + public Row(String imageUrl) { + this.imageUrl = imageUrl; + } + + public void addItem(int xOffset, int yOffset, int width, int height, + String url) { + itemArray.add(new Item(xOffset, yOffset, width, height, url)); + } + } + + private Row curRow; + public ArrayList rowArray = new ArrayList(); + + public void addItem(String imageUrl, String xOffset, String yOffset, String width, String height, + String url) { + if (curRow == null) { + curRow = new Row(imageUrl); + curRow.startIndex = 0; + rowArray.add(curRow); + } else if (!curRow.imageUrl.equals(imageUrl)) { + Row lastRow = curRow; + curRow = new Row(imageUrl); + curRow.startIndex = lastRow.startIndex + lastRow.itemArray.size(); + rowArray.add(curRow); + } + + curRow.addItem(Integer.parseInt(xOffset), + Integer.parseInt(yOffset), Integer.parseInt(width), + Integer.parseInt(height), url); + } + + @Override + public int size() { + int sum = 0; + for (Row row : rowArray) + sum += row.itemArray.size(); + return sum; + } + + @Override + public String getPageUrl(int index) { + for (Row row : rowArray) { + if (index < row.startIndex + row.itemArray.size() && index >= row.startIndex) + return row.itemArray.get(index - row.startIndex).url; + } + return null; + } + + // TODO reuse the TextView in AutoWrapLayout + @SuppressLint("InflateParams") + @Override + public void addPreview(SimpleGridLayout viewGroup) { + if (mTargetPage != mHolder.getCurPreviewPage()) + return; + + viewGroup.removeAllViews(); + LayoutInflater inflater = LayoutInflater.from(mActivity); + int index = mTargetPage * mPi.getPreviewPerPage(); // it is display index + int rowIndex = 0; + for (NormalPreviewList.Row row : rowArray) { + for (int i = 0; i < row.itemArray.size(); i++) { + View view = inflater.inflate(R.layout.preview_item, null); + + ((TextView)view.findViewById(R.id.text)).setText(String.valueOf(index + 1)); + + final int _index = index; + view.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // Add to read in Data + Intent intent = new Intent(mActivity, + GalleryActivity.class); + intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, mPi.toGalleryInfo()); + intent.putExtra(GalleryActivity.KEY_START_INDEX, _index); + mActivity.startActivity(intent); + } + }); + + viewGroup.addView(view); + index++; + } + // TODO I need a better key + ImageLoader.getInstance(mActivity).add(row.imageUrl, mPi.getGid() + + "-preview-" + mTargetPage + "-" + rowIndex, + new PreviewImageGetListener(viewGroup, rowIndex)); + rowIndex++; + } + } + + private class PreviewImageGetListener implements ImageLoader.OnGetImageListener { + private final SimpleGridLayout mViewGroup; + private final int mRowIndex; + + public PreviewImageGetListener(SimpleGridLayout viewGroup, int rowIndex) { + mViewGroup = viewGroup; + mRowIndex = rowIndex; + } + + @Override + public void onGetImage(String key, Bitmap bmp, int state) { + if (mTargetPage != mHolder.getCurPreviewPage()) + return; + + if (bmp == null) { + mHolder.onGetPreviewImageFailure(); + } else { + int maxWidth = bmp.getWidth(); + int maxHeight = bmp.getHeight(); + + NormalPreviewList.Row row = rowArray.get(mRowIndex); + int i = row.startIndex; + for(Item item : row.itemArray) { + + if (item.xOffset + item.width > maxWidth) + item.width = maxWidth - item.xOffset; + if (item.yOffset + item.height > maxHeight) + item.height = maxHeight - item.yOffset; + Bitmap bitmap = Bitmap.createBitmap(bmp, item.xOffset, item.yOffset, item.width, item.height); + + ((ImageView)mViewGroup.getChildAt(i).findViewById(R.id.image)).setImageBitmap(bitmap); + + i++; + } + } + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/PreviewImpl.java b/app/src/main/java/com/hippo/ehviewer/data/PreviewImpl.java new file mode 100644 index 000000000..1bc513f73 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/data/PreviewImpl.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.data; + +public interface PreviewImpl { + + public int getGid(); + + public String getToken(); + + public String getTitle(); + + /** + * If it is unkonwn, return Integer.MAX_VALUE + * + * @return + */ + public int getPreviewPageNum(); + + public int getPreviewPerPage(); + + /** + * If page is error, return null + * + * @param page + * @return + */ + public PreviewList getPreview(int page); + + /** + * + * @param previewList + */ + public void setPreview(int page, PreviewList previewList); + + public GalleryInfo toGalleryInfo(); + +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/PreviewList.java b/app/src/main/java/com/hippo/ehviewer/data/PreviewList.java new file mode 100644 index 000000000..ae33f88d8 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/data/PreviewList.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.data; + +import android.app.Activity; +import android.content.Context; +import android.widget.TextView; + +import com.hippo.ehviewer.widget.SimpleGridLayout; + +/** + * This interface describe the previews in one page + * + * @author Hippo + * + */ +public abstract class PreviewList { + + protected PreviewHolder mHolder; + protected Activity mActivity; + protected PreviewImpl mPi; + protected int mTargetPage; + + /** + * Add preview to view group + * @param viewGroup + * @return + */ + public abstract void addPreview(SimpleGridLayout viewGroup); + + public abstract int size(); + + public void setData(PreviewHolder holder, Activity activity, PreviewImpl pi, int targetPage) { + mHolder = holder; + mActivity = activity; + mPi = pi; + mTargetPage = targetPage; + } + + public void setTargetPage(int targetPage) { + mTargetPage = targetPage; + } + + /** + * Return page url at target index, null for invalid index + * @param index + * @return + */ + public abstract String getPageUrl(int index); + + protected class TextViewWithUrl extends TextView { + public String url; + + public TextViewWithUrl(Context context) { + super(context); + } + } + + public interface PreviewHolder { + public int getCurPreviewPage(); + public void onGetPreviewImageFailure(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/data/Tag.java b/app/src/main/java/com/hippo/ehviewer/data/Tag.java new file mode 100644 index 000000000..5009f233d --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/data/Tag.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.data; + + +public class Tag extends ListUrls { + private String mName; + + public Tag(String name, int category, String search) { + super(category, search); + mName = name; + } + + public Tag(String name, ListUrls lus) { + this(name, lus.getCategory(), lus.getSearch()); + setAdvance(lus.getAdvanceType(), lus.getMinRating()); + setTag(lus.getTag()); + setMode(lus.getMode()); + } + + public void setName(String name) { + mName = name; + } + + public String getName() { + return mName; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/drawable/BatteryDrawable.java b/app/src/main/java/com/hippo/ehviewer/drawable/BatteryDrawable.java new file mode 100644 index 000000000..a4d52db1f --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/drawable/BatteryDrawable.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.drawable; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; + +import com.hippo.ehviewer.util.MathUtils; + +public class BatteryDrawable extends Drawable { + + @SuppressWarnings("unused") + private static final String TAG = BatteryDrawable.class.getSimpleName(); + + public static final int WARN_LIMIT = 15; + + private int mColor = Color.WHITE; + private int mWarnColor = Color.RED; + private int mElect = -1; + private final Paint mPaint; + + private final Rect mTopRect; + private final Rect mBottomRect; + private final Rect mRightRect; + private final Rect mHeadRect; + private final Rect mElectRect; + private int mStart; + private int mStop; + + public BatteryDrawable() { + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); + mPaint.setStyle(Paint.Style.FILL); + + mTopRect = new Rect(); + mBottomRect = new Rect(); + mRightRect = new Rect(); + mHeadRect = new Rect(); + mElectRect = new Rect(); + updatePaint(); + } + + @Override + protected void onBoundsChange(Rect bounds) { + int width = bounds.width(); + int height = bounds.height(); + int strokeWidth = (int) (Math.sqrt(width * width + height * height) * 0.06f); + int turn1 = width * 6 / 7; + int turn2 = height / 3; + int secBottom = height - strokeWidth; + mStart = strokeWidth; + mStop = turn1 - strokeWidth; + + mTopRect.set(0, 0, turn1, strokeWidth); + mBottomRect.set(0, secBottom, turn1, height); + mRightRect.set(turn1 - strokeWidth, strokeWidth, turn1, secBottom); + mHeadRect.set(turn1, turn2, width, height - turn2); + mElectRect.set(0, strokeWidth, mStop, secBottom); + } + + /** + * How to draw:
    + * |------------------------------|
    + * |\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|
    + * |------------------------------|---|
    + * |/////////////////| |//|\\\|
    + * |/////////////////| |//|\\\|
    + * |------------------------------|---|
    + * |\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\|
    + * |------------------------------| + */ + @Override + public void draw(Canvas canvas) { + if (mElect == -1) + return; + + mElectRect.right = MathUtils.lerp(mStart, mStop, mElect / 100.0f); + + canvas.drawRect(mTopRect, mPaint); + canvas.drawRect(mBottomRect, mPaint); + canvas.drawRect(mRightRect, mPaint); + canvas.drawRect(mHeadRect, mPaint); + canvas.drawRect(mElectRect, mPaint); + } + + private boolean isWarn() { + return mElect <= WARN_LIMIT; + } + + public void setColor(int color) { + if (mColor == color) + return; + + mColor = color; + if (!isWarn()) { + mPaint.setColor(mColor); + invalidateSelf(); + } + } + + public void setWarnColor(int color) { + if (mWarnColor == color) + return; + + mWarnColor = color; + if (isWarn()) { + mPaint.setColor(mWarnColor); + invalidateSelf(); + } + } + + public void setElect(int elect) { + if (mElect == elect) + return; + + mElect = elect; + updatePaint(); + } + + public void setElect(int elect, boolean warn) { + if (mElect == elect) + return; + + mElect = elect; + updatePaint(warn); + } + + private void updatePaint() { + updatePaint(isWarn()); + } + + private void updatePaint(boolean warn) { + if (warn) + mPaint.setColor(mWarnColor); + else + mPaint.setColor(mColor); + invalidateSelf(); + } + + @Override + public void setAlpha(int alpha) { + // Empty + } + + @Override + public void setColorFilter(ColorFilter cf) { + // Empty + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/drawable/FreeMaterialDrawable.java b/app/src/main/java/com/hippo/ehviewer/drawable/FreeMaterialDrawable.java new file mode 100644 index 000000000..01f81ba95 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/drawable/FreeMaterialDrawable.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.drawable; + +import java.util.Arrays; + +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; + +import com.hippo.ehviewer.util.MathUtils; +import com.hippo.ehviewer.util.Ui; + +public class FreeMaterialDrawable extends Drawable { + + private static final String TAG = FreeMaterialDrawable.class.getSimpleName(); + + private static final int MAX_SPRITE_NUM = 6; + + private static final int SPRITE_SHAPE_RECTANGLE = 0; + private static final int SPRITE_SHAPE_ROUND = 1; + private static final int SPRITE_SHAPE_MAX = 2; + + private boolean mKeepTop; + private int mBgIndex; + private int mBgColor; + private final Paint mPaint; + private int mSpriteNum; + private final Sprite[] mSpriteArray = new Sprite[MAX_SPRITE_NUM]; + + private static final int[] sColorArray = new int[] { + 0xff9c27b0, 0xff673ab7, + 0xff3f51b5, 0xff2196f3, 0xff03a9f4, 0xff00bcd4, + 0xff4caf50 + }; + + public FreeMaterialDrawable () { + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mPaint.setStyle(Paint.Style.FILL); + mPaint.setShadowLayer(Ui.dp2pix(3), 0, 0, 0x8a000000); + } + + public FreeMaterialDrawable setBgIndex(int bgIndex) { + mBgIndex = bgIndex; + return this; + } + + public FreeMaterialDrawable setKeepTop(boolean keepTop) { + mKeepTop = keepTop; + return this; + } + + private int getColor(int[] copyOfColorArray) { + int index = MathUtils.random(0, copyOfColorArray.length); + int color = copyOfColorArray[index]; + while (color == -1) + color = copyOfColorArray[++index < copyOfColorArray.length ? index : (index = 0)]; + copyOfColorArray[index] = -1; + return color; + } + + @Override + protected void onBoundsChange(Rect bounds) { + int w = bounds.width(); + int h = bounds.height(); + Sprite s; + int[] copyOfColorArray = Arrays.copyOf(sColorArray, sColorArray.length); + if (mBgIndex >= 0) { + mBgColor = copyOfColorArray[mBgIndex]; + copyOfColorArray[mBgIndex] = -1; + } else { + mBgColor = getColor(copyOfColorArray); + } + mSpriteNum = MathUtils.random(4, MAX_SPRITE_NUM + 1); + for (int i = 0; i < mSpriteNum; i++) { + switch (SPRITE_SHAPE_ROUND) {//MathUtils.random(0, SPRITE_SHAPE_MAX)) { + case SPRITE_SHAPE_RECTANGLE: + s = new Rectangle(w, h, getColor(copyOfColorArray)); + break; + default: + case SPRITE_SHAPE_ROUND: + s = new Round(w, h, getColor(copyOfColorArray)); + break; + } + mSpriteArray[i] = s; + } + } + + @Override + public void draw(Canvas canvas) { + canvas.drawColor(mBgColor); + + for (int i = 0; i < mSpriteNum; i++) + mSpriteArray[i].draw(canvas, mPaint); + } + + @Override + public void setAlpha(int alpha) { + // Empty + } + + @Override + public void setColorFilter(ColorFilter cf) { + // Empty + } + + @Override + public int getOpacity() { + return PixelFormat.OPAQUE; + } + + private abstract class Sprite { + + private final int mColor; + + public Sprite(int w, int h, int color) { + mColor = color; + } + + public void draw(Canvas canvas, Paint paint) { + paint.setColor(mColor); + doDraw(canvas, paint); + } + + public abstract void doDraw(Canvas canvas, Paint paint); + } + + private class Rectangle extends Sprite { + + private final int left; + private final int top; + private final int right; + private final int bottom; + + public Rectangle(int w, int h, int color) { + super(w, h, color); + + int width = MathUtils.random(w / 4, w / 2); + int height = MathUtils.random(h / 4, h / 2); + left = MathUtils.random(w - width / 2); + top = MathUtils.random(h - height / 2); + right = left + width; + bottom = top + height; + } + + @Override + public void doDraw(Canvas canvas, Paint paint) { + canvas.drawRect(left, top, right, bottom, paint); + } + } + + private class Round extends Sprite { + + int mX, mY, mRadius; + + public Round(int w, int h, int color) { + super(w, h, color); + + int t = (w + h) / 2; + mRadius = MathUtils.random(t / 8, t / 4); + mX = MathUtils.random(w); + mY = MathUtils.random(mKeepTop ? mRadius + 10 : 0, h); + } + + @Override + public void doDraw(Canvas canvas, Paint paint) { + canvas.drawCircle(mX, mY, mRadius, paint); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/drawable/MaterialIndicatorDrawable.java b/app/src/main/java/com/hippo/ehviewer/drawable/MaterialIndicatorDrawable.java new file mode 100644 index 000000000..b0cde2dd9 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/drawable/MaterialIndicatorDrawable.java @@ -0,0 +1,695 @@ +/* + * Copyright (C) 2014 Balys Valentukevicius + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.drawable; + + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Paint.Style; +import android.graphics.PixelFormat; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.util.Property; +import android.util.TypedValue; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; + + +public class MaterialIndicatorDrawable extends Drawable implements Animatable { + + private static final String STATE_KEY = "material_menu_icon_state"; + + public enum IconState { + BURGER, ARROW + } + + public enum AnimationState { + BURGER_ARROW; + + public IconState getFirstState() { + switch (this) { + case BURGER_ARROW: + return IconState.BURGER; + default: + return null; + } + } + + public IconState getSecondState() { + switch (this) { + case BURGER_ARROW: + return IconState.ARROW; + default: + return null; + } + } + } + + public enum Stroke { + /** + * 3 dip + */ + REGULAR(3), + /** + * 2 dip + */ + THIN(2), + /** + * 1 dip + */ + EXTRA_THIN(1); + + private final int strokeWidth; + + Stroke(int strokeWidth) { + this.strokeWidth = strokeWidth; + } + + protected static Stroke valueOf(int strokeWidth) { + switch (strokeWidth) { + case 3: + return REGULAR; + case 2: + return THIN; + case 1: + return EXTRA_THIN; + default: + return THIN; + } + } + } + + public static final int DEFAULT_COLOR = Color.WHITE; + public static final int DEFAULT_SCALE = 1; + public static final int DEFAULT_TRANSFORM_DURATION = 800; + public static final int DEFAULT_PRESSED_DURATION = 400; + + private static final int BASE_DRAWABLE_WIDTH = 40; + private static final int BASE_DRAWABLE_HEIGHT = 40; + private static final int BASE_ICON_WIDTH = 20; + private static final int BASE_CIRCLE_RADIUS = 18; + + private static final float ARROW_MID_LINE_ANGLE = 180; + private static final float ARROW_TOP_LINE_ANGLE = 135; + private static final float ARROW_BOT_LINE_ANGLE = 225; + + private static final float TRANSFORMATION_START = 0; + private static final float TRANSFORMATION_MID = 1.0f; + private static final float TRANSFORMATION_END = 2.0f; + + private static final int DEFAULT_CIRCLE_ALPHA = 200; + + private final float diph; + private final float dip1; + private final float dip2; + private final float dip3; + private final float dip4; + + private final int width; + private final int height; + private final float strokeWidth; + private final float iconWidth; + private final float topPadding; + private final float sidePadding; + private final float circleRadius; + + private final Stroke stroke; + + private final Object lock = new Object(); + + private final Paint gridPaint = new Paint(); + private final Paint iconPaint = new Paint(); + private final Paint circlePaint = new Paint(); + + private float transformationValue = 0f; + private float pressedProgressValue = 0f; + private boolean transformationRunning = false; + + private IconState currentIconState = IconState.BURGER; + private AnimationState animationState = AnimationState.BURGER_ARROW; + + private IconState animatingIconState; + private boolean drawTouchCircle; + private boolean neverDrawTouch; + private boolean rtlEnabled; + + private ObjectAnimator transformation; + private ObjectAnimator pressedCircle; + + private MaterialMenuState materialMenuState; + + public MaterialIndicatorDrawable(Context context, int color, Stroke stroke) { + this(context, color, stroke, DEFAULT_SCALE, DEFAULT_TRANSFORM_DURATION, DEFAULT_PRESSED_DURATION); + } + + public MaterialIndicatorDrawable(Context context, int color, Stroke stroke, int transformDuration, int pressedDuration) { + this(context, color, stroke, DEFAULT_SCALE, transformDuration, pressedDuration); + } + + public MaterialIndicatorDrawable(Context context, int color, Stroke stroke, int scale, int transformDuration, int pressedDuration) { + Resources resources = context.getResources(); + // convert each separately due to various densities + this.dip1 = dpToPx(resources, 1) * scale; + this.dip2 = dpToPx(resources, 2) * scale; + this.dip3 = dpToPx(resources, 3) * scale; + this.dip4 = dpToPx(resources, 4) * scale; + this.diph = dip1 / 2; + + this.stroke = stroke; + this.width = (int) (dpToPx(resources, BASE_DRAWABLE_WIDTH) * scale); + this.height = (int) (dpToPx(resources, BASE_DRAWABLE_HEIGHT) * scale); + this.iconWidth = dpToPx(resources, BASE_ICON_WIDTH) * scale; + this.circleRadius = dpToPx(resources, BASE_CIRCLE_RADIUS) * scale; + this.strokeWidth = dpToPx(resources, stroke.strokeWidth) * scale; + + this.sidePadding = (width - iconWidth) / 2; + this.topPadding = (height - 5 * dip3) / 2; + + initPaint(color); + initAnimations(transformDuration, pressedDuration); + + materialMenuState = new MaterialMenuState(); + } + + private MaterialIndicatorDrawable(int color, Stroke stroke, long transformDuration, long pressedDuration, + int width, int height, float iconWidth, float circleRadius, float strokeWidth, float dip1 + ) { + this.dip1 = dip1; + this.dip2 = dip1 * 2; + this.dip3 = dip1 * 3; + this.dip4 = dip1 * 4; + this.diph = dip1 / 2; + this.stroke = stroke; + this.width = width; + this.height = height; + this.iconWidth = iconWidth; + this.circleRadius = circleRadius; + this.strokeWidth = strokeWidth; + this.sidePadding = (width - iconWidth) / 2; + this.topPadding = (height - 5 * dip3) / 2; + + initPaint(color); + initAnimations((int) transformDuration, (int) pressedDuration); + + materialMenuState = new MaterialMenuState(); + } + + private void initPaint(int color) { + gridPaint.setAntiAlias(false); + gridPaint.setColor(Color.GREEN); + gridPaint.setStrokeWidth(1); + + iconPaint.setAntiAlias(true); + iconPaint.setStyle(Style.STROKE); + iconPaint.setStrokeWidth(strokeWidth); + iconPaint.setColor(color); + + circlePaint.setAntiAlias(true); + circlePaint.setStyle(Style.FILL); + circlePaint.setColor(color); + circlePaint.setAlpha(DEFAULT_CIRCLE_ALPHA); + + setBounds(0, 0, width, height); + } + + /* + * Drawing + */ + + @Override public void draw(Canvas canvas) { + final float ratio = transformationValue <= 1 ? transformationValue : 2 - transformationValue; + + if (rtlEnabled) { + canvas.save(); + canvas.scale(-1, 1, 0, 0); + canvas.translate(-getIntrinsicWidth(), 0); + } + + drawTopLine(canvas, ratio); + drawMiddleLine(canvas, ratio); + drawBottomLine(canvas, ratio); + + if (rtlEnabled) { + canvas.restore(); + } + + if (drawTouchCircle) drawTouchCircle(canvas); + } + + private void drawTouchCircle(Canvas canvas) { + canvas.restore(); + canvas.drawCircle(width / 2, height / 2, pressedProgressValue, circlePaint); + } + + private void drawMiddleLine(Canvas canvas, float ratio) { + canvas.restore(); + canvas.save(); + + float rotation = 0; + float pivotX = width / 2; + float pivotY = width / 2; + float startX = sidePadding; + float startY = topPadding + dip3 / 2 * 5; + float stopX = width - sidePadding; + float stopY = topPadding + dip3 / 2 * 5; + int alpha = 255; + + switch (animationState) { + case BURGER_ARROW: + // rotate by 180 + if (isMorphingForward()) { + rotation = ratio * ARROW_MID_LINE_ANGLE; + } else { + rotation = ARROW_MID_LINE_ANGLE + (1 - ratio) * ARROW_MID_LINE_ANGLE; + } + // shorten one end + stopX -= ratio * resolveStrokeModifier(ratio) / 2; + break; + } + + iconPaint.setAlpha(alpha); + canvas.rotate(rotation, pivotX, pivotY); + canvas.drawLine(startX, startY, stopX, stopY, iconPaint); + iconPaint.setAlpha(255); + } + + private void drawTopLine(Canvas canvas, float ratio) { + canvas.save(); + + float rotation = 0, pivotX = 0, pivotY = 0; + float rotation2 = 0; + // pivot at center of line + float pivotX2 = width / 2 + dip3 / 2; + float pivotY2 = topPadding + dip2; + + float startX = sidePadding; + float startY = topPadding + dip2; + float stopX = width - sidePadding; + float stopY = topPadding + dip2; + int alpha = 255; + + switch (animationState) { + case BURGER_ARROW: + if (isMorphingForward()) { + // rotate until required angle + rotation = ratio * ARROW_BOT_LINE_ANGLE; + } else { + // rotate back to start doing a 360 + rotation = ARROW_BOT_LINE_ANGLE + (1 - ratio) * ARROW_TOP_LINE_ANGLE; + } + // rotate by middle + pivotX = width / 2; + pivotY = height / 2; + + // shorten both ends + stopX -= resolveStrokeModifier(ratio); + startX += dip3 * ratio; + + break; + } + + iconPaint.setAlpha(alpha); + canvas.rotate(rotation, pivotX, pivotY); + canvas.rotate(rotation2, pivotX2, pivotY2); + canvas.drawLine(startX, startY, stopX, stopY, iconPaint); + iconPaint.setAlpha(255); + } + + private void drawBottomLine(Canvas canvas, float ratio) { + canvas.restore(); + canvas.save(); + + float rotation = 0, pivotX = 0, pivotY = 0; + float rotation2 = 0; + // pivot at center of line + float pivotX2 = width / 2 + dip3 / 2; + float pivotY2 = height - topPadding - dip2; + + float startX = sidePadding; + float startY = height - topPadding - dip2; + float stopX = width - sidePadding; + float stopY = height - topPadding - dip2; + + switch (animationState) { + case BURGER_ARROW: + if (isMorphingForward()) { + // rotate to required angle + rotation = ARROW_TOP_LINE_ANGLE * ratio; + } else { + // rotate back to start doing a 360 + rotation = ARROW_TOP_LINE_ANGLE + (1 - ratio) * ARROW_BOT_LINE_ANGLE; + } + // pivot center of canvas + pivotX = width / 2; + pivotY = height / 2; + + // shorten both ends + stopX = width - sidePadding - resolveStrokeModifier(ratio); + startX = sidePadding + dip3 * ratio; + break; + } + + canvas.rotate(rotation, pivotX, pivotY); + canvas.rotate(rotation2, pivotX2, pivotY2); + canvas.drawLine(startX, startY, stopX, stopY, iconPaint); + } + + private boolean isMorphingForward() { + return transformationValue <= TRANSFORMATION_MID; + } + + private float resolveStrokeModifier(float ratio) { + switch (stroke) { + case REGULAR: + return ratio * dip3; + case THIN: + return ratio * (dip3 + diph); + case EXTRA_THIN: + return ratio * dip4; + } + return 0; + } + + @Override public void setAlpha(int alpha) { + iconPaint.setAlpha(alpha); + } + + @Override public void setColorFilter(ColorFilter cf) { + iconPaint.setColorFilter(cf); + } + + @Override public int getOpacity() { + return PixelFormat.TRANSPARENT; + } + + /* + * Accessor methods + */ + + public void setColor(int color) { + iconPaint.setColor(color); + circlePaint.setColor(color); + invalidateSelf(); + } + + public void setTransformationDuration(int duration) { + transformation.setDuration(duration); + } + + public void setPressedDuration(int duration) { + pressedCircle.setDuration(duration); + } + + public void setInterpolator(Interpolator interpolator) { + transformation.setInterpolator(interpolator); + } + + public void setNeverDrawTouch(boolean neverDrawTouch) { + this.neverDrawTouch = neverDrawTouch; + } + + public void setIconState(IconState iconState) { + synchronized (lock) { + if (transformationRunning) { + transformation.cancel(); + transformationRunning = false; + } + + if (currentIconState == iconState) return; + + switch (iconState) { + case BURGER: + animationState = AnimationState.BURGER_ARROW; + transformationValue = TRANSFORMATION_START; + break; + case ARROW: + animationState = AnimationState.BURGER_ARROW; + transformationValue = TRANSFORMATION_MID; + break; + } + currentIconState = iconState; + invalidateSelf(); + } + } + + public void animateIconState(IconState state, boolean drawTouch) { + synchronized (lock) { + if (transformationRunning) { + transformation.end(); + pressedCircle.end(); + } + drawTouchCircle = drawTouch; + animatingIconState = state; + start(); + } + } + + public IconState setTransformationOffset(AnimationState animationState, float offset) { + if (offset < TRANSFORMATION_START || offset > TRANSFORMATION_END) { + throw new IllegalArgumentException( + String.format("Value must be between %s and %s", TRANSFORMATION_START, TRANSFORMATION_END) + ); + } + + this.animationState = animationState; + + final boolean isFirstIcon = offset < TRANSFORMATION_MID || offset == TRANSFORMATION_END; + + currentIconState = isFirstIcon ? animationState.getFirstState() : animationState.getSecondState(); + animatingIconState = isFirstIcon ? animationState.getSecondState() : animationState.getFirstState(); + + setTransformationValue(offset); + + return currentIconState; + } + + public void setRTLEnabled(boolean rtlEnabled) { + this.rtlEnabled = rtlEnabled; + invalidateSelf(); + } + + public IconState getIconState() { + return currentIconState; + } + + /* + * Animations + */ + private final Property transformationProperty + = new Property(Float.class, "transformation") { + @Override + public Float get(MaterialIndicatorDrawable object) { + return object.getTransformationValue(); + } + + @Override + public void set(MaterialIndicatorDrawable object, Float value) { + object.setTransformationValue(value); + } + }; + + private final Property pressedProgressProperty + = new Property(Float.class, "pressedProgress") { + @Override + public Float get(MaterialIndicatorDrawable object) { + return object.getPressedProgress(); + } + + @Override + public void set(MaterialIndicatorDrawable object, Float value) { + object.setPressedProgress(value); + } + }; + + public Float getTransformationValue() { + return transformationValue; + } + + public void setTransformationValue(Float value) { + this.transformationValue = value; + invalidateSelf(); + } + + public Float getPressedProgress() { + return pressedProgressValue; + } + + public void setPressedProgress(Float value) { + this.pressedProgressValue = value; + circlePaint.setAlpha((int) (DEFAULT_CIRCLE_ALPHA * (1 - value / (circleRadius * 1.22f)))); + invalidateSelf(); + } + + private void initAnimations(int transformDuration, int pressedDuration) { + transformation = ObjectAnimator.ofFloat(this, transformationProperty, 0); + transformation.setInterpolator(new DecelerateInterpolator(3)); + transformation.setDuration(transformDuration); + transformation.addListener(new AnimatorListenerAdapter() { + @Override public void onAnimationEnd(Animator animation) { + transformationRunning = false; + setIconState(animatingIconState); + } + }); + + + pressedCircle = ObjectAnimator.ofFloat(this, pressedProgressProperty, 0, 0); + pressedCircle.setDuration(pressedDuration); + pressedCircle.setInterpolator(new DecelerateInterpolator()); + pressedCircle.addListener(new AnimatorListenerAdapter() { + @Override public void onAnimationEnd(Animator animation) { + pressedProgressValue = 0; + } + + @Override public void onAnimationCancel(Animator animation) { + pressedProgressValue = 0; + } + }); + } + + private boolean resolveTransformation() { + boolean isCurrentBurger = currentIconState == IconState.BURGER; + boolean isCurrentArrow = currentIconState == IconState.ARROW; + boolean isAnimatingBurger = animatingIconState == IconState.BURGER; + boolean isAnimatingArrow = animatingIconState == IconState.ARROW; + + if ((isCurrentBurger && isAnimatingArrow) || (isCurrentArrow && isAnimatingBurger)) { + animationState = AnimationState.BURGER_ARROW; + return isCurrentBurger; + } + + throw new IllegalStateException( + String.format("Animating from %s to %s is not supported", currentIconState, animatingIconState) + ); + } + + @Override public void start() { + if (transformationRunning || animatingIconState == null || animatingIconState == currentIconState) return; + transformationRunning = true; + + final boolean direction = resolveTransformation(); + transformation.setFloatValues( + direction ? TRANSFORMATION_START : TRANSFORMATION_MID, + direction ? TRANSFORMATION_MID : TRANSFORMATION_END + ); + transformation.start(); + + if (pressedCircle.isRunning()) { + pressedCircle.cancel(); + } + if (drawTouchCircle && !neverDrawTouch) { + pressedCircle.setFloatValues(0, circleRadius * 1.22f); + pressedCircle.start(); + } + + invalidateSelf(); + } + + @Override public void stop() { + if (isRunning() && transformation.isRunning()) { + transformation.end(); + } else { + transformationRunning = false; + invalidateSelf(); + } + } + + @Override public boolean isRunning() { + return transformationRunning; + } + + @Override + public int getIntrinsicWidth() { + return width; + } + + @Override + public int getIntrinsicHeight() { + return height; + } + + + @Override + public ConstantState getConstantState() { + materialMenuState.changingConfigurations = getChangingConfigurations(); + return materialMenuState; + } + + @Override + public Drawable mutate() { + materialMenuState = new MaterialMenuState(); + return this; + } + + /** + * Call from {@link android.app.Activity#onSaveInstanceState(android.os.Bundle)} to store current icon state + * + * @param outState outState + */ + public void onSaveInstanceState(Bundle outState) { + outState.putString(STATE_KEY, currentIconState.name()); + } + + /** + * Call from {@link android.app.Activity#onPostCreate(android.os.Bundle)} to restore icon state + * + * @param state state + */ + public void syncState(Bundle state) { + if (state != null) { + String iconStateName = state.getString(STATE_KEY); + if (iconStateName == null) { + iconStateName = MaterialIndicatorDrawable.IconState.BURGER.name(); + } + setIconState(MaterialIndicatorDrawable.IconState.valueOf(iconStateName)); + } + } + + private final class MaterialMenuState extends ConstantState { + private int changingConfigurations; + + private MaterialMenuState() { + } + + @Override + public Drawable newDrawable() { + MaterialIndicatorDrawable drawable = new MaterialIndicatorDrawable( + circlePaint.getColor(), stroke, transformation.getDuration(), + pressedCircle.getDuration(), width, height, iconWidth, circleRadius, strokeWidth, dip1 + ); + drawable.setIconState(animatingIconState != null ? animatingIconState : currentIconState); + drawable.setRTLEnabled(rtlEnabled); + return drawable; + } + + @Override + public int getChangingConfigurations() { + return changingConfigurations; + } + } + + static float dpToPx(Resources resources, float dp) { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.getDisplayMetrics()); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/drawable/MaterialProgressDrawable.java b/app/src/main/java/com/hippo/ehviewer/drawable/MaterialProgressDrawable.java new file mode 100644 index 000000000..d404d6f7f --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/drawable/MaterialProgressDrawable.java @@ -0,0 +1,446 @@ +package com.hippo.ehviewer.drawable; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.annotation.TargetApi; +import android.content.res.ColorStateList; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.LinearInterpolator; + +import java.util.ArrayList; + +// Get from android.graphics.drawable.MaterialProgressDrawable +public class MaterialProgressDrawable extends Drawable implements Animatable { + private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); + private static final TimeInterpolator END_CURVE_INTERPOLATOR = new EndCurveInterpolator(); + private static final TimeInterpolator START_CURVE_INTERPOLATOR = new StartCurveInterpolator(); + + private static final long ANIMATION_DURATION = 1333; + + private static final int NUM_POINTS = 5; + + private final ArrayList mAnimators = new ArrayList(); + + private final Ring mRing; + + private MaterialProgressState mState; + + private float mRotation; + + private boolean mMutated; + + public MaterialProgressDrawable() { + this(new MaterialProgressState(null)); + } + + public MaterialProgressDrawable(int width, int height, int color, float strokeWidth, float innerRadius) { + this(new MaterialProgressState(width, height, color, strokeWidth, innerRadius)); + } + + private MaterialProgressDrawable(MaterialProgressState state) { + mState = state; + mRing = new Ring(mCallback); + mMutated = false; + + initializeFromState(); + setupAnimators(); + } + + private void initializeFromState() { + MaterialProgressState state = mState; + + Ring ring = mRing; + ring.setStrokeWidth(state.mStrokeWidth); + + int color = state.mColor.getColorForState(getState(), 0); + ring.setColor(color); + + float minEdge = Math.min(state.mWidth, state.mHeight); + if ((state.mInnerRadius <= 0.0F) || (minEdge < 0.0F)) { + ring.setInsets((int) Math.ceil(state.mStrokeWidth / 2.0F)); + } else { + float insets = minEdge / 2.0F - state.mInnerRadius; + ring.setInsets(insets); + } + } + + @Override + public Drawable mutate() { + if ((!mMutated) && (super.mutate() == this)) { + mState = new MaterialProgressState(mState); + mMutated = true; + } + return this; + } + + @Override + protected boolean onStateChange(int[] state) { + boolean changed = super.onStateChange(state); + + int color = mState.mColor.getColorForState(state, 0); + if (color != mRing.getColor()) { + mRing.setColor(color); + changed = true; + } + + return changed; + } + + @Override + public boolean isStateful() { + return (super.isStateful()) || (mState.mColor.isStateful()); + } + + @Override + public int getIntrinsicHeight() { + return mState.mHeight; + } + + @Override + public int getIntrinsicWidth() { + return mState.mWidth; + } + + @Override + public void draw(Canvas c) { + Rect bounds = getBounds(); + int saveCount = c.save(); + c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY()); + mRing.draw(c, bounds); + c.restoreToCount(saveCount); + } + + public void setColor(int color) { + mState.mColor = ColorStateList.valueOf(color); + mRing.setColor(color); + } + + @Override + public void setAlpha(int alpha) { + mRing.setAlpha(alpha); + } + + @Override + public int getAlpha() { + return mRing.getAlpha(); + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + mRing.setColorFilter(colorFilter); + } + + private void setRotation(float rotation) { + mRotation = rotation; + invalidateSelf(); + } + + private float getRotation() { + return mRotation; + } + + @Override + public int getOpacity() { + return -3; + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + boolean changed = super.setVisible(visible, restart); + if (visible) { + if ((changed) || (restart)) { + start(); + } + } else { + stop(); + } + return changed; + } + + @Override + public boolean isRunning() { + ArrayList animators = mAnimators; + int N = animators.size(); + for (int i = 0; i < N; i++) { + Animator animator = animators.get(i); + if (animator.isRunning()) { + return true; + } + } + return false; + } + + @Override + @TargetApi(Build.VERSION_CODES.KITKAT) + public void start() { + ArrayList animators = mAnimators; + int N = animators.size(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + for (int i = 0; i < N; i++) { + Animator animator = animators.get(i); + if (animator.isPaused()) + animator.resume(); + else if (!animator.isRunning()) + animator.start(); + } + } else { + for (int i = 0; i < N; i++) { + Animator animator = animators.get(i); + if (!animator.isRunning()) + animator.start(); + } + } + } + + @Override + @TargetApi(Build.VERSION_CODES.KITKAT) + public void stop() { + ArrayList animators = mAnimators; + int N = animators.size(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + for (int i = 0; i < N; i++) { + animators.get(i).pause(); + } + } else { + for (int i = 0; i < N; i++) { + animators.get(i).cancel(); + } + } + } + + private void setupAnimators() { + Ring ring = mRing; + + ObjectAnimator endTrim = ObjectAnimator.ofFloat(ring, "endTrim", + new float[] { 0.0F, 0.75F }); + endTrim.setDuration(ANIMATION_DURATION); + endTrim.setInterpolator(START_CURVE_INTERPOLATOR); + endTrim.setRepeatCount(-1); + endTrim.setRepeatMode(1); + + ObjectAnimator startTrim = ObjectAnimator.ofFloat(ring, "startTrim", + new float[] { 0.0F, 0.75F }); + startTrim.setDuration(ANIMATION_DURATION); + startTrim.setInterpolator(END_CURVE_INTERPOLATOR); + startTrim.setRepeatCount(-1); + startTrim.setRepeatMode(1); + + ObjectAnimator rotation = ObjectAnimator.ofFloat(ring, "rotation", + new float[] { 0.0F, 0.25F }); + rotation.setDuration(ANIMATION_DURATION); + rotation.setInterpolator(LINEAR_INTERPOLATOR); + rotation.setRepeatCount(-1); + rotation.setRepeatMode(1); + + ObjectAnimator groupRotation = ObjectAnimator.ofFloat(this, "rotation", + new float[] { 0.0F, 360.0F }); + groupRotation.setDuration(6665L); + groupRotation.setInterpolator(LINEAR_INTERPOLATOR); + groupRotation.setRepeatCount(-1); + groupRotation.setRepeatMode(1); + + mAnimators.add(endTrim); + mAnimators.add(startTrim); + mAnimators.add(rotation); + mAnimators.add(groupRotation); + } + + private final Drawable.Callback mCallback = new Drawable.Callback() { + @Override + public void invalidateDrawable(Drawable d) { + invalidateSelf(); + } + + @Override + public void scheduleDrawable(Drawable d, Runnable what, long when) { + scheduleSelf(what, when); + } + + @Override + public void unscheduleDrawable(Drawable d, Runnable what) { + unscheduleSelf(what); + } + }; + + private static class MaterialProgressState extends Drawable.ConstantState { + private float mStrokeWidth = 5.0F; + private float mInnerRadius = -1.0F; + private int mWidth = -1; + private int mHeight = -1; + private ColorStateList mColor = ColorStateList.valueOf(0); + + public MaterialProgressState(int width, int height, int color, float strokeWidth, float innerRadius) { + mWidth = width; + mHeight = height; + mColor = ColorStateList.valueOf(color); + mStrokeWidth = strokeWidth; + mInnerRadius = innerRadius; + } + + public MaterialProgressState(MaterialProgressState orig) { + if (orig != null) { + mStrokeWidth = orig.mStrokeWidth; + mInnerRadius = orig.mInnerRadius; + mWidth = orig.mWidth; + mHeight = orig.mHeight; + mColor = orig.mColor; + } + } + + @Override + public int getChangingConfigurations() { + return 0; + } + + @Override + public Drawable newDrawable() { + return new MaterialProgressDrawable(this); + } + } + + private static class Ring { + private final RectF mTempBounds = new RectF(); + private final Paint mPaint = new Paint(); + + private final Drawable.Callback mCallback; + + private float mStartTrim = 0.0F; + private float mEndTrim = 0.0F; + private float mRotation = 0.0F; + private float mStrokeWidth = 5.0F; + private float mStrokeInset = 2.5F; + + private int mAlpha = 255; + private int mColor = -16777216; + + public Ring(Drawable.Callback callback) { + mCallback = callback; + + mPaint.setStrokeCap(Paint.Cap.ROUND); + mPaint.setAntiAlias(true); + mPaint.setStyle(Paint.Style.STROKE); + } + + public void draw(Canvas c, Rect bounds) { + RectF arcBounds = mTempBounds; + arcBounds.set(bounds); + arcBounds.inset(mStrokeInset, mStrokeInset); + + float startAngle = (mStartTrim + mRotation) * 360.0F; + float endAngle = (mEndTrim + mRotation) * 360.0F; + float sweepAngle = endAngle - startAngle; + + float diameter = Math.min(arcBounds.width(), arcBounds.height()); + float minAngle = (float) (360.0D / (diameter * 3.141592653589793D)); + if ((sweepAngle < minAngle) && (sweepAngle > -minAngle)) { + sweepAngle = Math.signum(sweepAngle) * minAngle; + } + + c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint); + } + + public void setColorFilter(ColorFilter filter) { + mPaint.setColorFilter(filter); + invalidateSelf(); + } + + public ColorFilter getColorFilter() { + return mPaint.getColorFilter(); + } + + public void setAlpha(int alpha) { + mAlpha = alpha; + mPaint.setColor(mColor & 0xFFFFFF | alpha << 24); + invalidateSelf(); + } + + public int getAlpha() { + return mAlpha; + } + + public void setColor(int color) { + mColor = color; + mPaint.setColor(color & 0xFFFFFF | mAlpha << 24); + invalidateSelf(); + } + + public int getColor() { + return mColor; + } + + public void setStrokeWidth(float strokeWidth) { + mStrokeWidth = strokeWidth; + mPaint.setStrokeWidth(strokeWidth); + invalidateSelf(); + } + + public float getStrokeWidth() { + return mStrokeWidth; + } + + public void setStartTrim(float startTrim) { + mStartTrim = startTrim; + invalidateSelf(); + } + + public float getStartTrim() { + return mStartTrim; + } + + public void setEndTrim(float endTrim) { + mEndTrim = endTrim; + invalidateSelf(); + } + + public float getEndTrim() { + return mEndTrim; + } + + public void setRotation(float rotation) { + mRotation = rotation; + invalidateSelf(); + } + + public float getRotation() { + return mRotation; + } + + public void setInsets(float insets) { + mStrokeInset = insets; + } + + public float getInsets() { + return mStrokeInset; + } + + private void invalidateSelf() { + mCallback.invalidateDrawable(null); + } + } + + private static class EndCurveInterpolator extends + AccelerateDecelerateInterpolator { + @Override + public float getInterpolation(float input) { + return super + .getInterpolation(Math.max(0.0F, (input - 0.5F) * 2.0F)); + } + } + + private static class StartCurveInterpolator extends + AccelerateDecelerateInterpolator { + @Override + public float getInterpolation(float input) { + return super.getInterpolation(Math.min(1.0F, input * 2.0F)); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/drawable/OvalDrawable.java b/app/src/main/java/com/hippo/ehviewer/drawable/OvalDrawable.java new file mode 100644 index 000000000..fb610fe11 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/drawable/OvalDrawable.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.drawable; + +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; + +public class OvalDrawable extends Drawable { + + private final Paint mPaint; + private final RectF mRectF; + + public OvalDrawable(int color) { + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); + mPaint.setColor(color); + mRectF = new RectF(); + } + + @Override + protected void onBoundsChange(Rect bounds) { + mRectF.set(bounds); + } + + @Override + public void draw(Canvas canvas) { + canvas.drawOval(mRectF, mPaint); + } + + @Override + public void setAlpha(int alpha) { + mPaint.setColor(mPaint.getColor() & 0x00ffffff | (alpha << 24)); + invalidateSelf(); + } + + @Override + public void setColorFilter(ColorFilter cf) { + // Not support + } + + @Override + public int getOpacity() { + return PixelFormat.OPAQUE; + } + + public void setColor(int color) { + mPaint.setColor(color); + invalidateSelf(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/effect/DrawableTransition.java b/app/src/main/java/com/hippo/ehviewer/effect/DrawableTransition.java new file mode 100644 index 000000000..e42c92de0 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/effect/DrawableTransition.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.effect; + +import android.animation.ValueAnimator; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.drawable.Drawable; + +public class DrawableTransition { + + public static void transit(final Drawable drawable, boolean fromDark, long duration) { + final AlphaSatColorMatrixEvaluator evaluator = new AlphaSatColorMatrixEvaluator(fromDark); + evaluator.setFraction(0.0f); + drawable.setColorFilter(new ColorMatrixColorFilter(evaluator.getColorMatrix())); + + ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float f = (Float) animation.getAnimatedValue(); + evaluator.setFraction(f); + drawable.setColorFilter(new ColorMatrixColorFilter(evaluator.getColorMatrix())); + } + }); + animator.setDuration(duration); + animator.start(); + } + + // From rnr.tests.imageloadingpattern.AlphaSatColorMatrixEvaluator + // with modification + private static class AlphaSatColorMatrixEvaluator { + private final ColorMatrix colorMatrix; + private final float[] elements = new float[20]; + private final boolean mFromDark; + + public AlphaSatColorMatrixEvaluator(boolean fromDark) { + colorMatrix = new ColorMatrix (); + mFromDark = fromDark; + } + + public ColorMatrix getColorMatrix() { + return colorMatrix; + } + + public void setFraction(float fraction) { + // There are 4 phases so we multiply fraction by that amount + float phase = fraction * 4; + + // Compute the alpha change over period [0, 2] + float alpha = Math.min(phase, 2.0f) / 2.0f; + elements [19] = Math.round(alpha * 255); + + // We substract to make the picture look darker, it will automatically clamp + // This is spread over period [0, 3] + final int MaxBlacker = 50; + float blackening = Math.round((1 - Math.min(phase, 3.0f) / 3.0f) * MaxBlacker); + elements [4] = elements [9] = elements [14] = mFromDark ? -blackening : blackening; + + // Finally we desaturate over [0, 4], taken from ColorMatrix.SetSaturation + float invSat = 1 - Math.max(0.2f, fraction); + float R = 0.213f * invSat; + float G = 0.715f * invSat; + float B = 0.072f * invSat; + + elements[0] = R + fraction; elements[1] = G; elements[2] = B; + elements[5] = R; elements[6] = G + fraction; elements[7] = B; + elements[10] = R; elements[11] = G; elements[12] = B + fraction; + + colorMatrix.set(elements); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/effect/ripple/Ripple.java b/app/src/main/java/com/hippo/ehviewer/effect/ripple/Ripple.java new file mode 100644 index 000000000..035cfed01 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/effect/ripple/Ripple.java @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.effect.ripple; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.Style; +import android.graphics.Rect; +import android.os.Build; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; + +import com.hippo.ehviewer.util.MathUtils; + +/** + * Draws a Material ripple. + */ +class Ripple { + @SuppressWarnings("unused") + private static final String TAG = Ripple.class.getSimpleName(); + + private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); + private static final Interpolator DECEL_INTERPOLATOR = new LogInterpolator(); + + private static final float GLOBAL_SPEED = 1.0f; + private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024.0f * GLOBAL_SPEED; + private static final float WAVE_TOUCH_UP_ACCELERATION = 3400.0f * GLOBAL_SPEED; + private static final float WAVE_OPACITY_DECAY_VELOCITY = 3.0f / GLOBAL_SPEED; + + private static final long RIPPLE_ENTER_DELAY = 80; + + private final RippleOwner mOwner; + + /** Bounds used for computing max radius. */ + private final Rect mBounds; + + /** Full-opacity color for drawing this ripple. */ + private int mColorOpaque; + + /** Maximum ripple radius. */ + private float mOuterRadius; + + /** Screen density used to adjust pixel-based velocities. */ + private float mDensity; + + private float mStartingX; + private float mStartingY; + private float mClampedStartingX; + private float mClampedStartingY; + + // Software animators. + private ObjectAnimator mAnimRadius; + private ObjectAnimator mAnimOpacity; + private ObjectAnimator mAnimX; + private ObjectAnimator mAnimY; + + // Software rendering properties. + private float mOpacity = 1; + private float mOuterX; + private float mOuterY; + + // Values used to tween between the start and end positions. + private float mTweenRadius = 0; + private float mTweenX = 0; + private float mTweenY = 0; + + /** Whether we have an explicit maximum radius. */ + private boolean mHasMaxRadius; + + /** Whether we were canceled externally and should avoid self-removal. */ + private boolean mCanceled; + + /** + * Creates a new ripple. + */ + public Ripple(RippleOwner owner, Rect bounds, float startingX, float startingY) { + mOwner = owner; + mBounds = bounds; + + mStartingX = startingX; + mStartingY = startingY; + } + + public void setup(int maxRadius, int color, float density) { + mColorOpaque = color | 0xFF000000; + mOpacity = Color.alpha(color) / 2 / 255.0f; + + if (maxRadius != RippleOwner.RADIUS_AUTO) { + mHasMaxRadius = true; + mOuterRadius = maxRadius; + } else { + final float halfWidth = mBounds.width() / 2.0f; + final float halfHeight = mBounds.height() / 2.0f; + mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight); + } + + mOuterX = 0; + mOuterY = 0; + mDensity = density; + + clampStartingPosition(); + } + + private void clampStartingPosition() { + final float cX = mBounds.exactCenterX(); + final float cY = mBounds.exactCenterY(); + final float dX = mStartingX - cX; + final float dY = mStartingY - cY; + final float r = mOuterRadius; + if (dX * dX + dY * dY > r * r) { + // Point is outside the circle, clamp to the circumference. + final double angle = Math.atan2(dY, dX); + mClampedStartingX = cX + (float) (Math.cos(angle) * r); + mClampedStartingY = cY + (float) (Math.sin(angle) * r); + } else { + mClampedStartingX = mStartingX; + mClampedStartingY = mStartingY; + } + } + + public void onHotspotBoundsChanged() { + if (!mHasMaxRadius) { + final float halfWidth = mBounds.width() / 2.0f; + final float halfHeight = mBounds.height() / 2.0f; + mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight); + + clampStartingPosition(); + } + } + + public void setOpacity(float a) { + mOpacity = a; + invalidateSelf(); + } + + public float getOpacity() { + return mOpacity; + } + + public void setRadiusGravity(float r) { + mTweenRadius = r; + invalidateSelf(); + } + + public float getRadiusGravity() { + return mTweenRadius; + } + + public void setXGravity(float x) { + mTweenX = x; + invalidateSelf(); + } + + public float getXGravity() { + return mTweenX; + } + + public void setYGravity(float y) { + mTweenY = y; + invalidateSelf(); + } + + public float getYGravity() { + return mTweenY; + } + + /** + * Draws the ripple centered at (0,0) using the specified paint. + */ + public boolean draw(Canvas c, Paint p) { + return drawSoftware(c, p); + } + + private boolean drawSoftware(Canvas c, Paint p) { + boolean hasContent = false; + + p.setColor(mColorOpaque); + final int alpha = MathUtils.clamp((int) (255 * mOpacity + 0.5f), 0, 255); + final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius); + if (alpha > 0 && radius > 0) { + final float x = MathUtils.lerp( + mClampedStartingX - mBounds.exactCenterX(), mOuterX, mTweenX); + final float y = MathUtils.lerp( + mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY); + p.setAlpha(alpha); + p.setStyle(Style.FILL); + c.drawCircle(x, y, radius, p); + hasContent = true; + } + + return hasContent; + } + + /** + * Returns the maximum bounds of the ripple relative to the ripple center. + */ + public void getBounds(Rect bounds) { + final int outerX = (int) mOuterX; + final int outerY = (int) mOuterY; + final int r = (int) mOuterRadius + 1; + bounds.set(outerX - r, outerY - r, outerX + r, outerY + r); + } + + /** + * Specifies the starting position relative to the drawable bounds. No-op if + * the ripple has already entered. + */ + public void move(float x, float y) { + mStartingX = x; + mStartingY = y; + + clampStartingPosition(); + } + + /** + * Starts the enter animation. + */ + public void enter() { + cancel(); + + final int radiusDuration = (int) + (1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5); + + final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radiusGravity", 1); + radius.setDuration(radiusDuration); + radius.setInterpolator(LINEAR_INTERPOLATOR); + radius.setStartDelay(RIPPLE_ENTER_DELAY); + + final ObjectAnimator cX = ObjectAnimator.ofFloat(this, "xGravity", 1); + cX.setDuration(radiusDuration); + cX.setInterpolator(LINEAR_INTERPOLATOR); + cX.setStartDelay(RIPPLE_ENTER_DELAY); + + final ObjectAnimator cY = ObjectAnimator.ofFloat(this, "yGravity", 1); + cY.setDuration(radiusDuration); + cY.setInterpolator(LINEAR_INTERPOLATOR); + cY.setStartDelay(RIPPLE_ENTER_DELAY); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + radius.setAutoCancel(true); + cX.setAutoCancel(true); + cY.setAutoCancel(true); + } + + mAnimRadius = radius; + mAnimX = cX; + mAnimY = cY; + + // Enter animations always run on the UI thread, since it's unlikely + // that anything interesting is happening until the user lifts their + // finger. + radius.start(); + cX.start(); + cY.start(); + } + + /** + * Starts the exit animation. + */ + public void exit() { + cancel(); + + final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius); + final float remaining; + if (mAnimRadius != null && mAnimRadius.isRunning()) { + remaining = mOuterRadius - radius; + } else { + remaining = mOuterRadius; + } + + final int radiusDuration = (int) (1000 * Math.sqrt(remaining / (WAVE_TOUCH_UP_ACCELERATION + + WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5); + final int opacityDuration = (int) (1000 * 1 / WAVE_OPACITY_DECAY_VELOCITY + 0.5f); + + exitSoftware(radiusDuration, opacityDuration); + } + + /** + * Jump all animations to their end state. The caller is responsible for + * removing the ripple from the list of animating ripples. + */ + public void jump() { + mCanceled = true; + endSoftwareAnimations(); + mCanceled = false; + } + + private void endSoftwareAnimations() { + if (mAnimRadius != null) { + mAnimRadius.end(); + mAnimRadius = null; + } + + if (mAnimOpacity != null) { + mAnimOpacity.end(); + mAnimOpacity = null; + } + + if (mAnimX != null) { + mAnimX.end(); + mAnimX = null; + } + + if (mAnimY != null) { + mAnimY.end(); + mAnimY = null; + } + } + + private void exitSoftware(int radiusDuration, int opacityDuration) { + final ObjectAnimator radiusAnim = ObjectAnimator.ofFloat(this, "radiusGravity", 1); + radiusAnim.setDuration(radiusDuration); + radiusAnim.setInterpolator(DECEL_INTERPOLATOR); + + final ObjectAnimator xAnim = ObjectAnimator.ofFloat(this, "xGravity", 1); + xAnim.setDuration(radiusDuration); + xAnim.setInterpolator(DECEL_INTERPOLATOR); + + final ObjectAnimator yAnim = ObjectAnimator.ofFloat(this, "yGravity", 1); + yAnim.setDuration(radiusDuration); + yAnim.setInterpolator(DECEL_INTERPOLATOR); + + final ObjectAnimator opacityAnim = ObjectAnimator.ofFloat(this, "opacity", 0); + opacityAnim.setDuration(opacityDuration); + opacityAnim.setInterpolator(LINEAR_INTERPOLATOR); + opacityAnim.addListener(mAnimationListener); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + radiusAnim.setAutoCancel(true); + xAnim.setAutoCancel(true); + yAnim.setAutoCancel(true); + opacityAnim.setAutoCancel(true); + } + + mAnimRadius = radiusAnim; + mAnimOpacity = opacityAnim; + mAnimX = xAnim; + mAnimY = yAnim; + + radiusAnim.start(); + opacityAnim.start(); + xAnim.start(); + yAnim.start(); + } + + /** + * Cancels all animations. The caller is responsible for removing + * the ripple from the list of animating ripples. + */ + public void cancel() { + mCanceled = true; + cancelSoftwareAnimations(); + mCanceled = false; + } + + private void cancelSoftwareAnimations() { + if (mAnimRadius != null) { + mAnimRadius.cancel(); + mAnimRadius = null; + } + + if (mAnimOpacity != null) { + mAnimOpacity.cancel(); + mAnimOpacity = null; + } + + if (mAnimX != null) { + mAnimX.cancel(); + mAnimX = null; + } + + if (mAnimY != null) { + mAnimY.cancel(); + mAnimY = null; + } + } + + private void removeSelf() { + // The owner will invalidate itself. + if (!mCanceled) { + mOwner.removeRipple(this); + } + } + + private void invalidateSelf() { + mOwner.invalidateSelf(); + } + + private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + removeSelf(); + } + }; + + /** + * Interpolator with a smooth log deceleration + */ + private static final class LogInterpolator implements Interpolator { + @Override + public float getInterpolation(float input) { + return 1 - (float) Math.pow(400, -input * 1.4); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/effect/ripple/RippleBackground.java b/app/src/main/java/com/hippo/ehviewer/effect/ripple/RippleBackground.java new file mode 100644 index 000000000..0ac0d031b --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/effect/ripple/RippleBackground.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.effect.ripple; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.Style; +import android.graphics.Rect; +import android.os.Build; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; + +import com.hippo.ehviewer.util.MathUtils; + +/** + * Draws a Material ripple. + */ +class RippleBackground { + private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); + + private static final float GLOBAL_SPEED = 1.0f; + private static final float WAVE_OPACITY_DECAY_VELOCITY = 3.0f / GLOBAL_SPEED; + private static final float WAVE_OUTER_OPACITY_EXIT_VELOCITY_MAX = 4.5f * GLOBAL_SPEED; + private static final float WAVE_OUTER_OPACITY_EXIT_VELOCITY_MIN = 1.5f * GLOBAL_SPEED; + private static final float WAVE_OUTER_OPACITY_ENTER_VELOCITY = 10.0f * GLOBAL_SPEED; + private static final float WAVE_OUTER_SIZE_INFLUENCE_MAX = 200f; + private static final float WAVE_OUTER_SIZE_INFLUENCE_MIN = 40f; + + private final RippleOwner mOwner; + + /** Bounds used for computing max radius. */ + private final Rect mBounds; + + /** Full-opacity color for drawing this ripple. */ + private int mColorOpaque; + + /** Maximum alpha value for drawing this ripple. */ + private int mColorAlpha; + + /** Maximum ripple radius. */ + private float mOuterRadius; + + /** Screen density used to adjust pixel-based velocities. */ + private float mDensity; + + // Software animators. + private ObjectAnimator mAnimOuterOpacity; + + // Software rendering properties. + private float mOuterOpacity = 0; + private float mOuterX; + private float mOuterY; + + /** Whether we have an explicit maximum radius. */ + private boolean mHasMaxRadius; + + /** + * Creates a new ripple. + */ + public RippleBackground(RippleOwner owner, Rect bounds) { + mOwner = owner; + mBounds = bounds; + } + + public void setup(int maxRadius, int color, float density) { + mColorOpaque = color | 0xFF000000; + mColorAlpha = Color.alpha(color) / 2; + + if (maxRadius != RippleOwner.RADIUS_AUTO) { + mHasMaxRadius = true; + mOuterRadius = maxRadius; + } else { + final float halfWidth = mBounds.width() / 2.0f; + final float halfHeight = mBounds.height() / 2.0f; + mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight); + } + + mOuterX = 0; + mOuterY = 0; + mDensity = density; + } + + public void onHotspotBoundsChanged() { + if (!mHasMaxRadius) { + final float halfWidth = mBounds.width() / 2.0f; + final float halfHeight = mBounds.height() / 2.0f; + mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight); + } + } + + public void setOuterOpacity(float a) { + mOuterOpacity = a; + invalidateSelf(); + } + + public float getOuterOpacity() { + return mOuterOpacity; + } + + /** + * Draws the ripple centered at (0,0) using the specified paint. + */ + public boolean draw(Canvas c, Paint p) { + return drawSoftware(c, p); + } + + public boolean shouldDraw() { + final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f); + return outerAlpha > 0 && mOuterRadius > 0; + } + + private boolean drawSoftware(Canvas c, Paint p) { + boolean hasContent = false; + + p.setColor(mColorOpaque); + final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f); + if (outerAlpha > 0 && mOuterRadius > 0) { + p.setAlpha(outerAlpha); + p.setStyle(Style.FILL); + c.drawCircle(mOuterX, mOuterY, mOuterRadius, p); + hasContent = true; + } + + return hasContent; + } + + /** + * Returns the maximum bounds of the ripple relative to the ripple center. + */ + public void getBounds(Rect bounds) { + final int outerX = (int) mOuterX; + final int outerY = (int) mOuterY; + final int r = (int) mOuterRadius + 1; + bounds.set(outerX - r, outerY - r, outerX + r, outerY + r); + } + + /** + * Starts the enter animation. + */ + public void enter() { + cancel(); + + final int outerDuration = (int) (1000 * 1.0f / WAVE_OUTER_OPACITY_ENTER_VELOCITY); + final ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerOpacity", 0, 1); + outer.setDuration(outerDuration); + outer.setInterpolator(LINEAR_INTERPOLATOR); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + outer.setAutoCancel(true); + } + + mAnimOuterOpacity = outer; + + // Enter animations always run on the UI thread, since it's unlikely + // that anything interesting is happening until the user lifts their + // finger. + outer.start(); + } + + /** + * Starts the exit animation. + */ + public void exit() { + cancel(); + + // Scale the outer max opacity and opacity velocity based + // on the size of the outer radius. + final int opacityDuration = (int) (1000 / WAVE_OPACITY_DECAY_VELOCITY + 0.5f); + final float outerSizeInfluence = MathUtils.clamp( + (mOuterRadius - WAVE_OUTER_SIZE_INFLUENCE_MIN * mDensity) + / (WAVE_OUTER_SIZE_INFLUENCE_MAX * mDensity), 0, 1); + final float outerOpacityVelocity = MathUtils.lerp(WAVE_OUTER_OPACITY_EXIT_VELOCITY_MIN, + WAVE_OUTER_OPACITY_EXIT_VELOCITY_MAX, outerSizeInfluence); + + // Determine at what time the inner and outer opacity intersect. + // inner(t) = mOpacity - t * WAVE_OPACITY_DECAY_VELOCITY / 1000 + // outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000 + final int inflectionDuration = Math.max(0, (int) (1000 * (1 - mOuterOpacity) + / (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f)); + final int inflectionOpacity = (int) (mColorAlpha * (mOuterOpacity + + inflectionDuration * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f); + + exitSoftware(opacityDuration, inflectionDuration, inflectionOpacity); + } + + /** + * Jump all animations to their end state. The caller is responsible for + * removing the ripple from the list of animating ripples. + */ + public void jump() { + endSoftwareAnimations(); + } + + private void endSoftwareAnimations() { + if (mAnimOuterOpacity != null) { + mAnimOuterOpacity.end(); + mAnimOuterOpacity = null; + } + } + + private void exitSoftware(int opacityDuration, int inflectionDuration, int inflectionOpacity) { + final ObjectAnimator outerOpacityAnim; + if (inflectionDuration > 0) { + // Outer opacity continues to increase for a bit. + outerOpacityAnim = ObjectAnimator.ofFloat(this, + "outerOpacity", inflectionOpacity / 255.0f); + outerOpacityAnim.setDuration(inflectionDuration); + outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + outerOpacityAnim.setAutoCancel(true); + } + + // Chain the outer opacity exit animation. + final int outerDuration = opacityDuration - inflectionDuration; + if (outerDuration > 0) { + outerOpacityAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + final ObjectAnimator outerFadeOutAnim = ObjectAnimator.ofFloat( + RippleBackground.this, "outerOpacity", 0); + outerFadeOutAnim.setDuration(outerDuration); + outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + outerFadeOutAnim.setAutoCancel(true); + } + + mAnimOuterOpacity = outerFadeOutAnim; + + outerFadeOutAnim.start(); + } + + @Override + public void onAnimationCancel(Animator animation) { + animation.removeListener(this); + } + }); + } + } else { + outerOpacityAnim = ObjectAnimator.ofFloat(this, "outerOpacity", 0); + outerOpacityAnim.setDuration(opacityDuration); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + outerOpacityAnim.setAutoCancel(true); + } + } + + mAnimOuterOpacity = outerOpacityAnim; + + outerOpacityAnim.start(); + } + + /** + * Cancel all animations. The caller is responsible for removing + * the ripple from the list of animating ripples. + */ + public void cancel() { + cancelSoftwareAnimations(); + } + + private void cancelSoftwareAnimations() { + if (mAnimOuterOpacity != null) { + mAnimOuterOpacity.cancel(); + mAnimOuterOpacity = null; + } + } + + private void invalidateSelf() { + mOwner.invalidateSelf(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/effect/ripple/RippleDrawable.java b/app/src/main/java/com/hippo/ehviewer/effect/ripple/RippleDrawable.java new file mode 100644 index 000000000..80d98723e --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/effect/ripple/RippleDrawable.java @@ -0,0 +1,579 @@ +/* + * Copyright (C) 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.effect.ripple; + +import android.annotation.TargetApi; +import android.content.res.ColorStateList; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.annotation.NonNull; + +import com.hippo.ehviewer.widget.Hotspotable; + +import java.util.Arrays; + +/** + * Drawable that shows a ripple effect in response to state changes. The + * anchoring position of the ripple for a given state may be specified by + * calling {@link #setHotspot(float, float)} with the corresponding state + * attribute identifier. + *

    + * A touch feedback drawable may contain multiple child layers, including a + * special mask layer that is not drawn to the screen. A single layer may be set + * as the mask by specifying its android:id value as {@link android.R.id#mask}. + *

    + * <!-- A red ripple masked against an opaque rectangle. --/>
    + * <ripple android:color="#ffff0000">
    + *   <item android:id="@android:id/mask"
    + *         android:drawable="@android:color/white" />
    + * <ripple />
    + * 
    + *

    + * If a mask layer is set, the ripple effect will be masked against that layer + * before it is drawn over the composite of the remaining child layers. + *

    + * If no mask layer is set, the ripple effect is masked against the composite + * of the child layers. + *

    + * <!-- A blue ripple drawn atop a black rectangle. --/>
    + * <ripple android:color="#ff00ff00">
    + *   <item android:drawable="@android:color/black" />
    + * <ripple />
    + *
    + * <!-- A red ripple drawn atop a drawable resource. --/>
    + * <ripple android:color="#ff00ff00">
    + *   <item android:drawable="@drawable/my_drawable" />
    + * <ripple />
    + * 
    + *

    + * If no child layers or mask is specified and the ripple is set as a View + * background, the ripple will be drawn atop the first available parent + * background within the View's hierarchy. In this case, the drawing region + * may extend outside of the Drawable bounds. + *

    + * <!-- An unbounded green ripple. --/>
    + * <ripple android:color="#ff0000ff" />
    + * 
    + * + * @attr ref android.R.styleable#RippleDrawable_color + */ +public class RippleDrawable extends Drawable implements RippleOwner, Hotspotable { + @SuppressWarnings("unused") + private static final String TAG = RippleDrawable.class.getSimpleName(); + + /** The maximum number of ripples supported. */ + private static final int MAX_RIPPLES = 10; + + /** Current ripple effect bounds, used to constrain ripple effects. */ + private final Rect mHotspotBounds = new Rect(); + + /** The current background. May be actively animating or pending entry. */ + private RippleBackground mBackground; + + /** Whether we expect to draw a background when visible. */ + private boolean mBackgroundActive; + + /** The current ripple. May be actively animating or pending entry. */ + private Ripple mRipple; + + /** Whether we expect to draw a ripple when visible. */ + private boolean mRippleActive; + + // Hotspot coordinates that are awaiting activation. + private float mPendingX; + private float mPendingY; + private boolean mHasPending; + + /** + * Lazily-created array of actively animating ripples. Inactive ripples are + * pruned during draw(). The locations of these will not change. + */ + private Ripple[] mExitingRipples; + private int mExitingRipplesCount = 0; + + /** Paint used to control appearance of ripples. */ + private Paint mRipplePaint; + + /** Target density of the display into which ripples are drawn. */ + private final float mDensity = 1.0f; + + /** Whether bounds are being overridden. */ + private boolean mOverrideBounds; + + /** + * Whether the next draw MUST draw something to canvas. Used to work around + * a bug in hardware invalidation following a render thread-accelerated + * animation. + */ + private boolean mNeedsDraw; + + private ColorStateList mColor = ColorStateList.valueOf(Color.MAGENTA); + + private int mMaxRadius = RADIUS_AUTO; + + private final Drawable mContent; + + RippleDrawable(ColorStateList color, Drawable content) { + setColor(color); + + mContent = content; + } + + @Override + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public void jumpToCurrentState() { + if(Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) { + super.jumpToCurrentState(); + } + + boolean needsDraw = false; + + if (mRipple != null) { + mRipple.jump(); + } + + if (mBackground != null) { + mBackground.jump(); + } + + needsDraw |= cancelExitingRipples(); + + mNeedsDraw = needsDraw; + invalidateSelf(); + } + + private boolean cancelExitingRipples() { + final int count = mExitingRipplesCount; + final Ripple[] ripples = mExitingRipples; + for (int i = 0; i < count; i++) { + ripples[i].cancel(); + } + + if (ripples != null) { + Arrays.fill(ripples, 0, count, null); + } + mExitingRipplesCount = 0; + + return false; + } + + @Override + public void setAlpha(int alpha) { + mColor.withAlpha(alpha); + } + + @Override + public int getAlpha() { + return Color.alpha(mColor.getDefaultColor()); + } + + @Override + public void setColorFilter(ColorFilter cf) { + //TODO Paint.setColorFilter(filter) ? + } + + @Override + public int getOpacity() { + // Worst-case scenario. + return PixelFormat.TRANSLUCENT; + } + + @Override + protected boolean onStateChange(int[] stateSet) { + boolean changed = false; + if (mContent != null) + changed |= mContent.setState(stateSet); + + boolean enabled = false; + boolean pressed = false; + boolean focused = false; + + for (int state : stateSet) { + if (state == android.R.attr.state_enabled) { + enabled = true; + } + if (state == android.R.attr.state_focused) { + focused = true; + } + if (state == android.R.attr.state_pressed) { + pressed = true; + } + } + + setRippleActive(enabled && pressed); + setBackgroundActive(focused || (enabled && pressed)); + + return changed; + } + + private void setRippleActive(boolean active) { + if (mRippleActive != active) { + mRippleActive = active; + if (active) { + tryRippleEnter(); + } else { + tryRippleExit(); + } + } + } + + private void setBackgroundActive(boolean active) { + if (mBackgroundActive != active) { + mBackgroundActive = active; + if (active) { + tryBackgroundEnter(); + } else { + tryBackgroundExit(); + } + } + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + + if (!mOverrideBounds) { + mHotspotBounds.set(bounds); + onHotspotBoundsChanged(); + } + + if (mContent != null) + mContent.setBounds(bounds); + + invalidateSelf(); + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + final boolean changed = super.setVisible(visible, restart); + + if (!visible) { + clearHotspots(); + } else if (changed) { + // If we just became visible, ensure the background and ripple + // visibilities are consistent with their internal states. + if (mRippleActive) { + tryRippleEnter(); + } + + if (mBackgroundActive) { + tryBackgroundEnter(); + } + } + + return changed; + } + + @Override + public boolean isStateful() { + return true; + } + + public void setColor(ColorStateList color) { + int alpha = Color.alpha(color.getDefaultColor()); + mColor = color.withAlpha((int) (alpha * (1 - 0.001f * alpha))); + invalidateSelf(); + } + + @Override + public void setHotspot(float x, float y) { + if (mRipple == null || mBackground == null) { + mPendingX = x; + mPendingY = y; + mHasPending = true; + } + + if (mRipple != null) { + mRipple.move(x, y); + } + } + + /** + * Creates an active hotspot at the specified location. + */ + private void tryBackgroundEnter() { + if (mBackground == null) { + mBackground = new RippleBackground(this, mHotspotBounds); + } + + final int color = mColor.getColorForState(getState(), Color.TRANSPARENT); + mBackground.setup(mMaxRadius, color, mDensity); + mBackground.enter(); + } + + private void tryBackgroundExit() { + if (mBackground != null) { + // Don't null out the background, we need it to draw! + mBackground.exit(); + } + } + + /** + * Attempts to start an enter animation for the active hotspot. Fails if + * there are too many animating ripples. + */ + private void tryRippleEnter() { + if (mExitingRipplesCount >= MAX_RIPPLES) { + // This should never happen unless the user is tapping like a maniac + // or there is a bug that's preventing ripples from being removed. + return; + } + + if (mRipple == null) { + final float x; + final float y; + if (mHasPending) { + mHasPending = false; + x = mPendingX; + y = mPendingY; + } else { + x = mHotspotBounds.exactCenterX(); + y = mHotspotBounds.exactCenterY(); + } + mRipple = new Ripple(this, mHotspotBounds, x, y); + } + + final int color = mColor.getColorForState(getState(), Color.TRANSPARENT); + mRipple.setup(mMaxRadius, color, mDensity); + mRipple.enter(); + } + + /** + * Attempts to start an exit animation for the active hotspot. Fails if + * there is no active hotspot. + */ + private void tryRippleExit() { + if (mRipple != null) { + if (mExitingRipples == null) { + mExitingRipples = new Ripple[MAX_RIPPLES]; + } + mExitingRipples[mExitingRipplesCount++] = mRipple; + mRipple.exit(); + mRipple = null; + } + } + + /** + * Cancels and removes the active ripple, all exiting ripples, and the + * background. Nothing will be drawn after this method is called. + */ + private void clearHotspots() { + boolean needsDraw = false; + + if (mRipple != null) { + mRipple.cancel(); + mRipple = null; + } + + if (mBackground != null) { + mBackground.cancel(); + mBackground = null; + } + + needsDraw |= cancelExitingRipples(); + + mNeedsDraw = needsDraw; + invalidateSelf(); + } + + @Override + public void setHotspotBounds(int left, int top, int right, int bottom) { + mOverrideBounds = true; + mHotspotBounds.set(left, top, right, bottom); + + onHotspotBoundsChanged(); + } + + /** @hide */ + public void getHotspotBounds(Rect outRect) { + outRect.set(mHotspotBounds); + } + + /** + * Notifies all the animating ripples that the hotspot bounds have changed. + */ + private void onHotspotBoundsChanged() { + final int count = mExitingRipplesCount; + final Ripple[] ripples = mExitingRipples; + for (int i = 0; i < count; i++) { + ripples[i].onHotspotBoundsChanged(); + } + + if (mRipple != null) { + mRipple.onHotspotBoundsChanged(); + } + + if (mBackground != null) { + mBackground.onHotspotBoundsChanged(); + } + } + + @Override + public void draw(@NonNull Canvas canvas) { + final boolean drawNonMaskContent = mContent != null; + final Rect bounds = getDirtyBounds(); + final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG); + canvas.clipRect(bounds); + + // If we have content, draw it into a layer first. + if (drawNonMaskContent) { + drawContentLayer(canvas, bounds); + } + + // If we have a background and a non-opaque mask, draw the masking layer. + drawBackgroundLayer(canvas, bounds); + + // If we have ripples and a non-opaque mask, draw the masking layer. + drawRippleLayer(canvas, bounds); + + // If we failed to draw anything and we just canceled animations, at + // least draw a color so that hardware invalidation works correctly. + if (mNeedsDraw) { + canvas.drawColor(Color.TRANSPARENT); + + // Request another draw so we can avoid adding a transparent layer + // during the next display list refresh. + invalidateSelf(); + } + mNeedsDraw = false; + + canvas.restoreToCount(saveCount); + } + + /** + * Removes a ripple from the exiting ripple list. + * + * @param ripple the ripple to remove + */ + @Override + public void removeRipple(Ripple ripple) { + // Ripple ripple ripple ripple. Ripple ripple. + final Ripple[] ripples = mExitingRipples; + final int count = mExitingRipplesCount; + final int index = getRippleIndex(ripple); + if (index >= 0) { + System.arraycopy(ripples, index + 1, ripples, index, count - (index + 1)); + ripples[count - 1] = null; + mExitingRipplesCount--; + + invalidateSelf(); + } + } + + private int getRippleIndex(Ripple ripple) { + final Ripple[] ripples = mExitingRipples; + final int count = mExitingRipplesCount; + for (int i = 0; i < count; i++) { + if (ripples[i] == ripple) { + return i; + } + } + return -1; + } + + private void drawContentLayer(Canvas canvas, Rect bounds) { + mContent.draw(canvas); + } + + private void drawBackgroundLayer( + Canvas canvas, Rect bounds) { + if (mBackground != null && mBackground.shouldDraw()) { + final float x = mHotspotBounds.exactCenterX(); + final float y = mHotspotBounds.exactCenterY(); + canvas.translate(x, y); + mBackground.draw(canvas, getRipplePaint()); + canvas.translate(-x, -y); + } + } + + private void drawRippleLayer(Canvas canvas, Rect bounds) { + int restoreTranslate = -1; + + // Translate the canvas to the current hotspot bounds. + restoreTranslate = canvas.save(); + canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY()); + + // Draw ripples and update the animating ripples array. + final int count = mExitingRipplesCount; + final Ripple[] ripples = mExitingRipples; + for (int i = 0; i <= count; i++) { + final Ripple ripple; + if (i < count) { + ripple = ripples[i]; + } else if (mRipple != null) { + ripple = mRipple; + } else { + continue; + } + + ripple.draw(canvas, getRipplePaint()); + } + + // Always restore the translation. + if (restoreTranslate >= 0) { + canvas.restoreToCount(restoreTranslate); + } + } + + private Paint getRipplePaint() { + if (mRipplePaint == null) { + mRipplePaint = new Paint(); + mRipplePaint.setAntiAlias(true); + } + return mRipplePaint; + } + + @Override + public Rect getDirtyBounds() { + return getBounds(); + } + + /** + * Sets the maximum ripple radius in pixels. The default value of + * {@link #RADIUS_AUTO} defines the radius as the distance from the center + * of the drawable bounds (or hotspot bounds, if specified) to a corner. + * + * @param maxRadius the maximum ripple radius in pixels or + * {@link #RADIUS_AUTO} to automatically determine the maximum + * radius based on the bounds + * @see #getMaxRadius() + * @see #setHotspotBounds(int, int, int, int) + * @hide + */ + public void setMaxRadius(int maxRadius) { + if (maxRadius != RADIUS_AUTO && maxRadius < 0) { + throw new IllegalArgumentException("maxRadius must be RADIUS_AUTO or >= 0"); + } + + mMaxRadius = maxRadius; + } + + /** + * @return the maximum ripple radius in pixels, or {@link #RADIUS_AUTO} if + * the radius is determined automatically + * @see #setMaxRadius(int) + * @hide + */ + public int getMaxRadius() { + return mMaxRadius; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/effect/ripple/RippleOwner.java b/app/src/main/java/com/hippo/ehviewer/effect/ripple/RippleOwner.java new file mode 100644 index 000000000..de9cdb0ee --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/effect/ripple/RippleOwner.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.effect.ripple; + + +public interface RippleOwner { + /** + * Constant for automatically determining the maximum ripple radius. + */ + public static final int RADIUS_AUTO = -1; + + public void removeRipple(Ripple ripple); + + public void invalidateSelf(); + +} diff --git a/app/src/main/java/com/hippo/ehviewer/effect/ripple/RippleSalon.java b/app/src/main/java/com/hippo/ehviewer/effect/ripple/RippleSalon.java new file mode 100644 index 000000000..a0fc3c649 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/effect/ripple/RippleSalon.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.effect.ripple; + +import android.annotation.TargetApi; +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.view.View; + +import com.hippo.ehviewer.widget.HotspotTouchHelper; + + +public final class RippleSalon { + + private static final boolean USE_OLD_RIPPLE = + Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP; + + private static final Drawable MASK = new ColorDrawable(Color.BLACK); + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public static void addRipple(View v, ColorStateList color) { + addRipple(v, color, v.getBackground()); + } + + @SuppressWarnings("deprecation") + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public static void addRipple(View v, ColorStateList color, Drawable content) { + if (USE_OLD_RIPPLE) { + RippleDrawable rippleDrawable = new RippleDrawable(color, content); + v.setOnTouchListener(new HotspotTouchHelper(rippleDrawable)); + v.setBackgroundDrawable(rippleDrawable); + } else { + android.graphics.drawable.RippleDrawable rippleDrawable = new android.graphics.drawable.RippleDrawable(color, content, MASK); + v.setBackgroundDrawable(rippleDrawable); + } + } + +} diff --git a/app/src/main/java/com/hippo/ehviewer/ehclient/AddToFavoriteParser.java b/app/src/main/java/com/hippo/ehviewer/ehclient/AddToFavoriteParser.java new file mode 100644 index 000000000..4b81bd403 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ehclient/AddToFavoriteParser.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ehclient; + +public class AddToFavoriteParser { + + public boolean parser(String body) { + if (body == null) + return false; + + if (body.contains("Close Window")) + return true; + else + return false; + } + +} diff --git a/app/src/main/java/com/hippo/ehviewer/ehclient/DetailParser.java b/app/src/main/java/com/hippo/ehviewer/ehclient/DetailParser.java new file mode 100644 index 000000000..a68193049 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ehclient/DetailParser.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ehclient; + +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.hippo.ehviewer.data.Comment; +import com.hippo.ehviewer.data.LargePreviewList; +import com.hippo.ehviewer.data.NormalPreviewList; +import com.hippo.ehviewer.data.PreviewList; +import com.hippo.ehviewer.util.EhUtils; +import com.hippo.ehviewer.util.Utils; + +public class DetailParser { + + private static final String OFFENSIVE_STRING = + "

    (And if you choose to ignore this warning, you lose all rights to complain about it in the future.)

    "; + private static final String PINING_STRING = + "

    This gallery is pining for the fjords.

    "; + + public static final int DETAIL = 0x1; + public static final int TAG = 0x2; + public static final int PREVIEW_INFO = 0x4; + public static final int PREVIEW = 0x8; + public static final int COMMENT = 0x10; + public static final int OFFENSIVE = 0x20; + public static final int PINING = 0x40; + public static final int ERROR = 0x80; + + private static final CommentSort cs = new CommentSort(); + + public String eMesg; + public String thumb; + public String title; + public String title_jpn; + public int category; + public String uploader; + public String posted; + public int pages; + public String size; + public String resized; + public String parent; + public String visible; + public String language; + public int people; + public float rating; + public String firstPage; + public int previewPerPage; + public int previewSum; + public LinkedHashMap> tags; + public PreviewList previewList; + public LinkedList comments; + + public void reset() { + eMesg = null; + thumb = null; + title = null; + title_jpn = null; + category = 0; + uploader = null; + posted = null; + pages = 0; + size = null; + resized = null; + parent = null; + visible = null; + language = null; + people = 0; + rating = 0; + firstPage = null; + previewPerPage = 0; + previewSum = 0; + tags = null; + previewList = null; + comments = null; + } + + public int parser(String body, int mode) { + int re = 0; + Pattern p; + Matcher m; + + if (body == null) + return 0; + + if (!body.contains("<")) { + eMesg = body; + return ERROR; + } + + if (body.contains(OFFENSIVE_STRING)) { + return OFFENSIVE; + } + + if (body.contains(PINING_STRING)) { + return PINING; + } + + p = Pattern.compile("
    \n

    ([^<]+)

    "); + m = p.matcher(body); + if (m.find()) { + eMesg = m.group(1); + return ERROR; + } + + // Get detail + if ((mode & DETAIL) != 0) { + p = Pattern + .compile("
    ]+/>
    " // thumb + + "
    " + + "
    " + + "

    ([^<>]+)

    " // title + + "

    ([^<>]*)

    " // title_jpn might be empty string + + "
    " + + ".+" + + "
    " // category + + "
    ]+>([^<>]+)" // uploader + + ".+" + + "]*>Posted:]*>([\\w|\\-|\\s|:]+)" // posted + + "]*>Images:]*>([\\d]+) @ ([\\w|\\.|\\s]+)" // pages and size + + "]*>Resized:]*>([^<>]+)" // resized + + "]*>Parent:]*>(?:]*>)?([^<>]+)(?:)?" // parent + + "]*>Visible:]*>([^<>]+)" // visible + + "]*>Language:]*>([^<>]+)" // language + + ".+" + + "\\(([\\d|,]+)\\)" // people + + "" + + "]*>([^<>]+)" // rating + + ".+" + + "
    ]*>(?:]*>)?]*href=\"([^<>\"]+)\"[^<>]*>"); // get firstPage + m = p.matcher(body); + if (m.find()) { + re |= DETAIL; + + thumb = Utils.unescapeXml(m.group(1)); + title = Utils.unescapeXml(m.group(2)); + title_jpn = Utils.unescapeXml(m.group(3)); + category = EhUtils.getCategory(m.group(4)); + uploader = m.group(5); + posted = m.group(6); + pages = Integer.parseInt(m.group(7)); + size = m.group(8); + resized = m.group(9); + parent = m.group(10); + visible = m.group(11); + language = m.group(12); + people = Integer.parseInt(m.group(13).replace(",", "")); + + Pattern pattern = Pattern.compile("([\\d|\\.]+)"); + Matcher matcher = pattern.matcher(m.group(14)); + if (matcher.find()) + rating = Float.parseFloat(matcher.group(1)); + else + rating = Float.NaN; + + firstPage = m.group(15); + } + } + // Get tag + if ((mode & TAG) != 0) { + tags = new LinkedHashMap>(); + p = Pattern + .compile("]+>([\\w\\s]+):(?:]+>]+>[\\w\\s]+
    )+"); + m = p.matcher(body); + while (m.find()) { + re |= TAG; + String groupName = m.group(1); + LinkedList group = getTagGroup(m.group(0)); + if (group != null) { + tags.put(groupName, group); + } + } + } + + // Get preview info + if ((mode & PREVIEW_INFO) != 0) { + p = Pattern.compile("]+>]+>([\\d,]+)]+>(?:]+>)?>(?:)?"); + m = p.matcher(body); + if (m.find()) { + re |= PREVIEW_INFO; + previewSum = Integer.valueOf(m.group(1).replace(",", "")); + } + } + + // Get preview + if ((mode & PREVIEW) != 0) { + boolean isLargePreview = false; + if (body.contains("
    ]*class=\"gdtm\"[^<>]*>]*width:(\\d+)[^<>]*height:(\\d+)[^<>]*\\((.+?)\\)[^<>]*-(\\d+)px[^<>]*>]*href=\"(.+?)\"[^<>]*>"); + m = p.matcher(body); + while (m.find()) { + re |= PREVIEW; + ((NormalPreviewList)previewList).addItem(m.group(3), m.group(4), "0", m.group(1), + m.group(2), m.group(5)); + } + } + // Set previewPerPage + if (previewList != null) + previewPerPage = previewList.size(); + } + // Get comment + if ((mode & COMMENT) != 0) { + p = Pattern + .compile("
    Posted on ([^<>]+) by ]+>([^<>]+).+?
    ]*>(.+?)
    (); + while (m.find()) { + re |= COMMENT; + comments.add(new Comment(m.group(1), m.group(2), m.group(3))); + } + Collections.sort(comments, cs); + } + return re; + } + + private LinkedList getTagGroup(String pageContent) { + LinkedList list = new LinkedList(); + Pattern p = Pattern.compile("]+>]+>([\\w\\s]+)
    "); + Matcher m = p.matcher(pageContent); + while (m.find()) + list.add(m.group(1)); + if (list.size() == 0) + return null; + else + return list; + } + + + + static class CommentSort implements Comparator { + private int compareNum(String n1, String n2, int median) { + int re = 0; + for (int i = 0; i < median; i++) { + re *= 10; + re += n1.charAt(i) - n2.charAt(i); + } + return re; + } + private int getMonth(String m) { + if (m.equals("January")) + return 1; + else if (m.equals("February")) + return 2; + else if (m.equals("March")) + return 3; + else if (m.equals("April")) + return 4; + else if (m.equals("May")) + return 5; + else if (m.equals("June")) + return 6; + else if (m.equals("July")) + return 7; + else if (m.equals("August")) + return 8; + else if (m.equals("September")) + return 9; + else if (m.equals("October")) + return 10; + else if (m.equals("November")) + return 11; + else if (m.equals("December")) + return 12; + else + return 0; + } + + @Override + public int compare(Comment c1, Comment c2) { + int re = 0; + Pattern p = Pattern.compile("(\\d{2}) (\\w+) (\\d{4}), (\\d{2}):(\\d{2})"); + Matcher m1 = p.matcher(c1.time); + Matcher m2 = p.matcher(c2.time); + if (!m1.find() || !m2.find()) + return 0; + // year + re += compareNum(m1.group(3), m2.group(3), 4); + re <<= 4; + // Month + re += getMonth(m1.group(2)) - getMonth(m2.group(2)); + re <<= 5; + // Day + re += compareNum(m1.group(1), m2.group(1), 2); + re <<= 5; + // Hour + re += compareNum(m1.group(4), m2.group(4), 2); + re <<= 6; + // Minute + re += compareNum(m1.group(5), m2.group(5), 2); + return re; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ehclient/DetailUrlParser.java b/app/src/main/java/com/hippo/ehviewer/ehclient/DetailUrlParser.java new file mode 100644 index 000000000..99393e335 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ehclient/DetailUrlParser.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ehclient; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class DetailUrlParser { + + public int gid; + public String token; + + public boolean parser(String url) { + if (url == null) + return false; + + Pattern p = Pattern.compile("/(\\d+)/(\\w+)"); + Matcher m = p.matcher(url); + if (m.find()) { + gid = Integer.parseInt(m.group(1)); + token = m.group(2); + return true; + } else { + return false; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ehclient/EdDetailParser.java b/app/src/main/java/com/hippo/ehviewer/ehclient/EdDetailParser.java new file mode 100644 index 000000000..0b2644aa5 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ehclient/EdDetailParser.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ehclient; + +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.hippo.ehviewer.util.Log; + +public class EdDetailParser { + + int previewPageNum; + int previewStartIndex; + int imageNum; + boolean isLastPage; + List pageTokenArray; + String emsg; + + public boolean parser(String body, int mode, boolean needPreviewInfo) { + Pattern p; + Matcher m; + + if (body == null) + return false; + + if (!body.contains("<")) + emsg = body; + + if (mode == EhClient.MODE_LOFI) { + // Check is last page + p = Pattern.compile("]+>Next Page >"); + m = p.matcher(body); + if (m.find()) + isLastPage = false; + else + isLastPage = true; + + // Parser preview + p = Pattern.compile("
    ]*>]*>"); + m = p.matcher(body); + boolean first = true; + while(m.find()) { + if (first) { + pageTokenArray = new LinkedList(); + previewStartIndex = Integer.parseInt(m.group(2)) - 1; + first = false; + } + pageTokenArray.add(m.group(1)); + } + if (first) + return false; + else + return true; + + } else { + // Parser preview info + if (needPreviewInfo) { + p = Pattern.compile("]+>]+>([\\d,]+)]+>(?:]+>)?>(?:)?"); + m = p.matcher(body); + if (m.find()) + previewPageNum = Integer.valueOf(m.group(1).replace(",", "")); + else { + Log.e("TAG", "get needPreviewInfo failed"); + return false; + } + + p = Pattern.compile("Images:([\\d,]+) "); + m = p.matcher(body); + if (m.find()) + imageNum = Integer.valueOf(m.group(1).replace(",", "")); + else { + Log.e("TAG", "get needPreviewInfo failed"); + return false; + } + } + + // Parser preview + + boolean first = true; + if (body.contains("
    ]*>"); + m = p.matcher(body); + while(m.find()) { + if (first) { + pageTokenArray = new LinkedList(); + previewStartIndex = Integer.parseInt(m.group(2)) - 1; + first = false; + } + pageTokenArray.add(m.group(1)); + } + + } else { + p = Pattern.compile("
    ]*>]*>"); + m = p.matcher(body); + while(m.find()) { + if (first) { + pageTokenArray = new LinkedList(); + previewStartIndex = Integer.parseInt(m.group(2)) - 1; + first = false; + } + pageTokenArray.add(m.group(1)); + } + } + + + + if (first) { + Log.e("TAG", "get gdtm"); + return false; + } else + return true; + } + } + +} diff --git a/app/src/main/java/com/hippo/ehviewer/ehclient/EhClient.java b/app/src/main/java/com/hippo/ehviewer/ehclient/EhClient.java new file mode 100644 index 000000000..b2c5a0a5c --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ehclient/EhClient.java @@ -0,0 +1,1424 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ehclient; + +import java.io.File; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Process; + +import com.hippo.ehviewer.Analytics; +import com.hippo.ehviewer.AppHandler; +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.data.ApiGalleryInfo; +import com.hippo.ehviewer.data.Comment; +import com.hippo.ehviewer.data.GalleryDetail; +import com.hippo.ehviewer.data.GalleryInfo; +import com.hippo.ehviewer.data.GalleryPopular; +import com.hippo.ehviewer.data.LofiGalleryDetail; +import com.hippo.ehviewer.data.PreviewList; +import com.hippo.ehviewer.network.HttpHelper; +import com.hippo.ehviewer.util.BgThread; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.EhUtils; +import com.hippo.ehviewer.util.Utils; + +public class EhClient { + @SuppressWarnings("unused") + private static final String TAG = EhClient.class.getSimpleName(); + + public static final int FAVORITE_SLOT_NUM = 10; + + public static final int MODE_G = 0x0; + public static final int MODE_EX = 0x1; + public static final int MODE_LOFI = 0x2; + + public static final String API_G = "http://g.e-hentai.org/api.php"; + public static final String API_EX = "http://exhentai.org/api.php"; + public static final long APIUID = 1363542; + public static final String APIKEY = "f4b5407ab1727b9d08d7"; + + private static final String LOGIN_URL = "http://forums.e-hentai.org/index.php?act=Login&CODE=01"; + private static final String FORUMS_URL = "http://forums.e-hentai.org/index.php"; + + public static final String HEADER_G = "http://g.e-hentai.org/"; + public static final String HEADER_EX = "http://exhentai.org/"; + public static final String HEADER_LOFI = "http://lofi.e-hentai.org/"; + + public static final String API_EHVIEWER = "http://www.ehviewer.com/API"; + + /* Avatar get code */ + public static final int GET_AVATAR_OK = 0x0; + public static final int GET_AVATAR_ERROR = 0x1; + public static final int NO_AVATAR = 0x2; + + private final Context mContext; + private final Handler mHandler; + private final EhInfo mInfo; + + private static EhClient sInstance; + + public static void createInstance(Context context) { + sInstance = new EhClient(context); + } + + public static EhClient getInstance() { + return sInstance; + } + + private EhClient(Context context) { + mContext = context; + mHandler = AppHandler.getInstance(); + mInfo = EhInfo.getInstance(context); + } + + public static String getUrlHeader() { + return getUrlHeader(Config.getMode()); + } + + public static String getUrlHeader(int mode) { + switch (mode) { + case MODE_EX: + return HEADER_EX; + case MODE_LOFI: + return HEADER_LOFI; + case MODE_G: + default: + return HEADER_G; + } + } + + /** + * Get gellary detail url in default mode + * @param gid + * @param token + * @return + */ + public String getDetailUrl(int gid, String token) { + return getDetailUrl(gid, token, 0, Config.getMode()); + } + + /** + * Get gellary detail url in default mode + * @param gid + * @param token + * @return + */ + public String getDetailUrl(int gid, String token, int pageNum) { + return getDetailUrl(gid, token, pageNum, Config.getMode()); + } + + /** + * Get gellary detail url in target mode + * @param gid + * @param token + * @return + */ + public static String getDetailUrl(int gid, String token, int pageNum, int mode) { + StringBuilder sb = new StringBuilder(); + switch (mode) { + case MODE_EX: + sb.append(HEADER_EX); + break; + case MODE_LOFI: + sb.append(HEADER_LOFI); + break; + case MODE_G: + default: + sb.append(HEADER_G); + break; + } + return sb.append("g/").append(gid).append("/").append(token) + .append("/?p=").append(pageNum).toString(); + } + + public String getPageUrl(int gid, String token, int pageNum) { + return getPageUrl(gid, token, pageNum, Config.getMode()); + } + + public static String getPageUrl(int gid, String token, int pageNum, int mode) { + StringBuilder sb = new StringBuilder(); + switch (mode) { + case MODE_EX: + sb.append(HEADER_EX); + break; + case MODE_LOFI: + sb.append(HEADER_LOFI); + break; + case MODE_G: + default: + sb.append(HEADER_G); + break; + } + return sb.append("s/").append(token).append("/").append(gid) + .append("-").append(pageNum).toString(); + } + + public String getApiUrl() { + return getApiUrl(Config.getApiMode()); + } + + public static String getApiUrl(int mode) { + switch (mode) { + case MODE_EX: + return API_EX; + case MODE_G: + default: + return API_G; + } + } + + public String getFavoriteUrl(int page) { + return getFavoriteUrl(Config.getApiMode(), page); + } + + public static String getFavoriteUrl(int mode, int page) { + StringBuilder sb = new StringBuilder(); + switch (mode) { + case MODE_EX: + sb.append(HEADER_EX); + break; + case MODE_G: + default: + sb.append(HEADER_G); + break; + } + return sb.append("favorites.php?page=").append(page).toString(); + } + + public String getFavoriteUrlWithCat(int index, int page) { + return getFavoriteUrlWithCat(Config.getApiMode(), index, page); + } + + public static String getFavoriteUrlWithCat(int mode, int index, int page) { + StringBuilder sb = new StringBuilder(); + switch (mode) { + case MODE_EX: + sb.append(HEADER_EX); + break; + case MODE_G: + default: + sb.append(HEADER_G); + break; + } + return sb.append("favorites.php?favcat=").append(index).append("&page=") + .append(page).toString(); + } + + public interface OnGetMangaUrlListener { + public void onSuccess(Object checkFlag, String[] arg); + public void onFailure(Object checkFlag, String eMsg); + } + + public interface OnGetImageListener { + public void onSuccess(Object checkFlag, Object res); + public void onFailure(int eMsgId); + } + + public interface OnGetGalleryMetadataListener { + public void onSuccess(Map lmds); + public void onFailure(String eMsg); + } + + public interface OnGetGalleryTokensListener { + public void onSuccess(Map tokens); + public void onFailure(String eMsg); + } + + /** + * True if Login + * @return + */ + public boolean isLogin() { + return mInfo.isLogin(); + } + + /** + * Just remove cookie + */ + public void logout() { + mInfo.logout(); + } + + /** + * Return username, if not return default string + */ + public String getUsername() { + return mInfo.getUsername(); + } + + /** + * Return displayname, if not return default string + */ + public String getDisplayname() { + return mInfo.getDisplayname(); + } + + /** + * Return avatar, if not return default avatar + */ + public Drawable getAvatar() { + return mInfo.getAvatar(); + } + + public interface OnLoginListener { + public void onSuccess(); + public void onGetAvatar(int code); + public void onFailure(String eMesg); + } + + private class LoginResponder implements Runnable { + private final boolean isOk; + private final OnLoginListener listener; + private final String eMesg; + + public LoginResponder(OnLoginListener listener) { + this.isOk = true; + this.listener = listener; + this.eMesg = null; + } + + public LoginResponder(OnLoginListener listener, String eMesg) { + this.isOk = false; + this.listener = listener; + this.eMesg = eMesg; + } + + @Override + public void run() { + if (isOk) { + listener.onSuccess(); + } else { + listener.onFailure(eMesg); + } + } + } + + public void login(final String username, final String password, + final OnLoginListener listener) { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + final HttpHelper hh = new HttpHelper(mContext); + String body = hh.postForm(LOGIN_URL, new String[][] { + new String[] { "UserName", username }, + new String[] { "PassWord", password }, + new String[] { "submit", "Log me in" }, + new String[] { "CookieDate", "1" }, + new String[] { "temporary_https", "on" }}); + LoginResponder responder; + if (body == null) { + responder = new LoginResponder(listener, hh.getEMsg()); + } else if (!body.contains("<")) { + responder = new LoginResponder(listener, body); + } else { + LoginParser parser = new LoginParser(); + if (parser.parser(body)) { + mInfo.login(username, parser.displayname); + getAvatar(listener); + responder = new LoginResponder(listener); + } else { + responder = new LoginResponder(listener, parser.eMesg); + } + } + mHandler.post(responder); + } + }); + thread.setPriority(Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + } + + private void getAvatar(final OnLoginListener listener) { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + String body; + Pattern p; + Matcher m; + // Get user profile url + HttpHelper hp = new HttpHelper(mContext); + body = hp.get(FORUMS_URL); + if (body == null) { + listener.onGetAvatar(GET_AVATAR_ERROR); + return; + } + p = Pattern.compile("

    .+?"); + m = p.matcher(body); + if (!m.find()) { + listener.onGetAvatar(GET_AVATAR_ERROR); + return; + } + String profileUrl = Utils.unescapeXml(m.group(1)); + // Get avatar url + body = hp.get(profileUrl); + if (body == null) { + listener.onGetAvatar(GET_AVATAR_ERROR); + return; + } + p = Pattern.compile("

    "); + m = p.matcher(body); + if (!m.find()) { + listener.onGetAvatar(NO_AVATAR); + return; + } + String avatarUrl = m.group(1); + if (!avatarUrl.startsWith("http")) + avatarUrl = "http://forums.e-hentai.org/" + avatarUrl; + // Get avatar + Bitmap avatar = hp.getImage(avatarUrl); + // Set avatar if not null + if (avatar != null) + mInfo.setAvatar(new BitmapDrawable(EhClient.this.mContext.getResources(), avatar)); + // notification + listener.onGetAvatar(GET_AVATAR_OK); + } + }); + thread.setPriority(Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + } + + // Get Gallery List + public interface OnGetGListListener { + public void onSuccess(Object checkFlag, List giList, + int maxPage); + public void onFailure(Object checkFlag, String eMsg); + } + + private class GetGListResponder implements Runnable { + private final boolean isOk; + private final OnGetGListListener listener; + private final Object checkFlag; + private final List giList; + private final int maxPage; + private final String eMesg; + + public GetGListResponder(OnGetGListListener listener, Object checkFlag, + List giList, int maxPage) { + this.isOk = true; + this.listener = listener; + this.checkFlag = checkFlag; + this.giList = giList; + this.maxPage = maxPage; + this.eMesg = null; + } + + public GetGListResponder(OnGetGListListener listener, Object checkFlag, String eMesg) { + this.isOk = false; + this.listener = listener; + this.checkFlag = checkFlag; + this.giList = null; + this.maxPage = 0; + this.eMesg = eMesg; + } + + @Override + public void run() { + if (isOk) + listener.onSuccess(checkFlag, giList, maxPage); + else + listener.onFailure(checkFlag, eMesg); + } + } + + public void getGList(String url, Object checkFlag, + OnGetGListListener listener) { + getGList(url, Config.getMode(), checkFlag, listener); + } + + public void getGList(final String url, final int mode, final Object checkFlag, + final OnGetGListListener listener) { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + HttpHelper hp = new HttpHelper(mContext); + String body = hp.get(url); + GetGListResponder responder; + if (body == null) { + responder = new GetGListResponder(listener, checkFlag, hp.getEMsg()); + } else if (!body.contains("<")) { + responder = new GetGListResponder(listener, checkFlag, body); + } else { + final ListParser parser = new ListParser(); + switch (parser.parser(body, mode)) { + case ListParser.ALL: + responder = new GetGListResponder(listener, checkFlag, + parser.giList, parser.pageNum); + break; + case ListParser.NOT_FOUND: + responder = new GetGListResponder(listener, checkFlag, + parser.giList, 0); + break; + case ListParser.INDEX_ERROR: + responder = new GetGListResponder(listener, checkFlag, + mContext.getString(R.string.em_index_error)); + break; + case ListParser.PARSER_ERROR: + default: + responder = new GetGListResponder(listener, checkFlag, + mContext.getString(R.string.em_parser_error)); + break; + } + } + mHandler.post(responder); + } + }); + thread.setPriority(Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + } + + // Get Gallery List from file search + + public static final int IMAGE_SEARCH_USE_SIMILARITY_SCAN = 0x1; + public static final int IMAGE_SEARCH_ONLY_SEARCH_COVERS = 0x2; + public static final int IMAGE_SEARCH_SHOW_EXPUNGED = 0x4; + + public interface OnGetGListFromImageSearchListener { + public void onSuccess(Object checkFlag, List giList, + int maxPage, String newUrl); + public void onFailure(Object checkFlag, String eMsg); + } + + private class GetGListFromImageSearchResponder implements Runnable { + + private final boolean isOk; + private final OnGetGListFromImageSearchListener listener; + private final Object checkFlag; + private final List giList; + private final int maxPage; + private final String newUrl; + private final String eMesg; + + public GetGListFromImageSearchResponder(OnGetGListFromImageSearchListener listener, + Object checkFlag, List giList, int maxPage, String newUrl) { + this.isOk = true; + this.listener = listener; + this.checkFlag = checkFlag; + this.giList = giList; + this.maxPage = maxPage; + this.newUrl = newUrl; + this.eMesg = null; + } + + public GetGListFromImageSearchResponder(OnGetGListFromImageSearchListener listener, + Object checkFlag, String eMesg) { + this.isOk = false; + this.listener = listener; + this.checkFlag = checkFlag; + this.giList = null; + this.maxPage = 0; + this.newUrl = null; + this.eMesg = eMesg; + } + + @Override + public void run() { + if (isOk) + listener.onSuccess(checkFlag, giList, maxPage, newUrl); + else + listener.onFailure(checkFlag, eMesg); + } + } + + public static String getFileSearchUrl(int apiMode) { + switch (apiMode) { + case MODE_EX: + return "http://ul.exhentai.org/image_lookup.php"; + case MODE_G: + default: + return "http://ul.e-hentai.org/image_lookup.php"; + } + } + + public void getGListFromImageSearch(File file, int searchMode, + Object checkFlag, OnGetGListFromImageSearchListener listener) { + getGListFromImageSearch(file, null, searchMode, + Config.getApiMode(), checkFlag, listener); + } + + public void getGListFromImageSearch(Bitmap bmp, int searchMode, + Object checkFlag, OnGetGListFromImageSearchListener listener) { + getGListFromImageSearch(null, bmp, searchMode, + Config.getApiMode(), checkFlag, listener); + } + + private void getGListFromImageSearch(final File file, final Bitmap bitmap, + final int searchMode, final int apiMode, final Object checkFlag, + final OnGetGListFromImageSearchListener listener) { + if (file == null && bitmap == null) + listener.onFailure(checkFlag, mContext.getString(R.string.invalid_input)); + + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + HttpHelper hh = new HttpHelper(mContext); + + List dataList = new LinkedList(); + HttpHelper.FormData data; + // Add file or bitmap + if (file != null) { + data = new HttpHelper.FileData(file); + data.setProperty("Content-Disposition", "form-data; name=\"sfile\"; filename=\"" + file.getName() + "\""); + dataList.add(data); + } else { + data = new HttpHelper.BitmapData(bitmap); + data.setProperty("Content-Disposition", "form-data; name=\"sfile\"; filename=\"hehe.jpg\""); + dataList.add(data); + } + // Add + data = new HttpHelper.StringData("File Search"); + data.setProperty("Content-Disposition", "form-data; name=\"f_sfile\""); + dataList.add(data); + // Add FILE_SEARCH_USE_SIMILARITY_SCAN + if ((searchMode & IMAGE_SEARCH_USE_SIMILARITY_SCAN) != 0) { + data = new HttpHelper.StringData("on"); + data.setProperty("Content-Disposition", "form-data; name=\"fs_similar\""); + dataList.add(data); + } + // Add FILE_SEARCH_ONLY_SEARCH_COVERS + if ((searchMode & IMAGE_SEARCH_ONLY_SEARCH_COVERS) != 0) { + data = new HttpHelper.StringData("on"); + data.setProperty("Content-Disposition", "form-data; name=\"fs_covers\""); + dataList.add(data); + } + // Add FILE_SEARCH_SHOW_EXPUNGED + if ((searchMode & IMAGE_SEARCH_SHOW_EXPUNGED) != 0) { + data = new HttpHelper.StringData("on"); + data.setProperty("Content-Disposition", "form-data; name=\"fs_exp\""); + dataList.add(data); + } + + String body = hh.postFormData(getFileSearchUrl(apiMode), dataList); + GetGListFromImageSearchResponder responder; + if (body == null) { + responder = new GetGListFromImageSearchResponder(listener, checkFlag, hh.getEMsg()); + } else if (!body.contains("<")) { + responder = new GetGListFromImageSearchResponder(listener, checkFlag, body); + } else { + String newUrl = hh.getLastUrl(); + if (newUrl == null) { + responder = new GetGListFromImageSearchResponder(listener, checkFlag, "Location is null"); + } else { + final ListParser parser = new ListParser(); + switch (parser.parser(body, apiMode)) { + case ListParser.ALL: + responder = new GetGListFromImageSearchResponder(listener, checkFlag, + parser.giList, parser.pageNum, newUrl); + break; + case ListParser.NOT_FOUND: + responder = new GetGListFromImageSearchResponder(listener, checkFlag, + parser.giList, 0, newUrl); + break; + case ListParser.INDEX_ERROR: + responder = new GetGListFromImageSearchResponder(listener, checkFlag, + mContext.getString(R.string.em_index_error)); + break; + case ListParser.PARSER_ERROR: + default: + responder = new GetGListFromImageSearchResponder(listener, checkFlag, + mContext.getString(R.string.em_parser_error)); + break; + } + } + } + mHandler.post(responder); + } + }); + thread.setPriority(Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + } + + // Get gallery Detail + public interface OnGetGDetailListener { + public void onSuccess(GalleryDetail md); + public void onFailure(String eMsg); + } + + private class GetGDetaiResponder implements Runnable { + private final boolean isOk; + private final OnGetGDetailListener listener; + private final GalleryDetail gd; + private final String eMesg; + + public GetGDetaiResponder(OnGetGDetailListener listener, GalleryDetail gd) { + this.isOk = true; + this.listener = listener; + this.gd = gd; + this.eMesg = null; + } + + public GetGDetaiResponder(OnGetGDetailListener listener, String eMsg) { + this.isOk = false; + this.listener = listener; + this.gd = null; + this.eMesg = eMsg; + } + + @Override + public void run() { + if (isOk) + listener.onSuccess(gd); + else + listener.onFailure(eMesg); + } + } + + public void getGDetail(final String url, final GalleryDetail md, + final OnGetGDetailListener listener) { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + HttpHelper hh = new HttpHelper(mContext); + String body = hh.get(url); + GetGDetaiResponder responder; + if (body == null) { + responder = new GetGDetaiResponder(listener, hh.getEMsg()); + } else if (!body.contains("<")) { + responder = new GetGDetaiResponder(listener, body); + } else { + DetailParser parser = new DetailParser(); + int result = parser.parser(body, DetailParser.DETAIL | DetailParser.TAG + | DetailParser.PREVIEW_INFO | DetailParser.PREVIEW + | DetailParser.COMMENT); + if (result == DetailParser.OFFENSIVE) { + md.firstPage = "offensive"; + responder = new GetGDetaiResponder(listener, md); + } else if (result == DetailParser.PINING) { + md.firstPage = "pining"; + responder = new GetGDetaiResponder(listener, md); + } else if ((result & (DetailParser.DETAIL | DetailParser.PREVIEW_INFO | + DetailParser.PREVIEW)) == (DetailParser.DETAIL | DetailParser.PREVIEW_INFO | + DetailParser.PREVIEW)) { + // At least get detail and preview + md.thumb = parser.thumb; + md.title = parser.title; + md.title_jpn = parser.title_jpn; + md.category = parser.category; + md.uploader = parser.uploader; + md.posted = parser.posted; + md.pages = parser.pages; + md.size = parser.size; + md.resized = parser.resized; + md.parent = parser.parent; + md.visible = parser.visible; + md.language = parser.language; + md.people = parser.people; + md.rating = parser.rating; + md.firstPage = parser.firstPage; + md.previewPerPage = parser.previewPerPage; + md.previewSum = parser.previewSum; + + md.tags = parser.tags; + md.previewLists = new PreviewList[md.previewSum]; + md.previewLists[0] = parser.previewList; + md.comments = parser.comments; + responder = new GetGDetaiResponder(listener, md); + } else if (result == DetailParser.ERROR) { + responder = new GetGDetaiResponder(listener, parser.eMesg); + } else { + responder = new GetGDetaiResponder(listener, + mContext.getString(R.string.em_parser_error)); + } + } + mHandler.post(responder); + } + }); + thread.setPriority(Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + } + + // Get lofi gallery Detail + public interface OnGetLGDetailListener { + public void onSuccess(LofiGalleryDetail md, boolean isLastPage); + public void onFailure(String eMsg); + } + + private class GetLGDetaiResponder implements Runnable { + private final boolean isOk; + private final OnGetLGDetailListener listener; + private final LofiGalleryDetail gd; + private final boolean isLastPage; + private final String eMesg; + + public GetLGDetaiResponder(OnGetLGDetailListener listener, LofiGalleryDetail gd, boolean isLastPage) { + this.isOk = true; + this.listener = listener; + this.gd = gd; + this.isLastPage = isLastPage; + this.eMesg = null; + } + + public GetLGDetaiResponder(OnGetLGDetailListener listener, String eMsg) { + this.isOk = false; + this.listener = listener; + this.gd = null; + this.isLastPage = false; + this.eMesg = eMsg; + } + + @Override + public void run() { + if (isOk) + listener.onSuccess(gd, isLastPage); + else + listener.onFailure(eMesg); + } + } + + public void getLGDetail(final String url, final LofiGalleryDetail lgd, + final OnGetLGDetailListener listener) { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + HttpHelper hh = new HttpHelper(mContext); + String body = hh.get(url); + GetLGDetaiResponder responder; + if (body == null) { + responder = new GetLGDetaiResponder(listener, hh.getEMsg()); + } else if (!body.contains("<")) { + responder = new GetLGDetaiResponder(listener, body); + } else { + LofiDetailParser parser = new LofiDetailParser(); + if (parser.parser(body)) { + lgd.setPreview(0, parser.preview); + lgd.previewPerPage = parser.preview.size(); + responder = new GetLGDetaiResponder(listener, lgd, parser.isLastPage); + } else { + responder = new GetLGDetaiResponder(listener, + mContext.getString(R.string.em_parser_error)); + } + } + mHandler.post(responder); + } + }); + thread.setPriority(Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + } + + + // Get preview list + public interface OnGetPreviewListListener { + public void onSuccess(Object checkFlag, PreviewList pageList, boolean isLastPage); + public void onFailure(Object checkFlag, String eMsg); + } + + private class GetPreviewListResponder implements Runnable { + private final boolean isOk; + private final OnGetPreviewListListener listener; + private final Object checkFlag; + private final PreviewList pageList; + private final boolean isLastPage; + private final String eMesg; + + public GetPreviewListResponder(OnGetPreviewListListener listener, + Object checkFlag, PreviewList pageList, boolean isLastPage) { + this.isOk = true; + this.listener = listener; + this.checkFlag = checkFlag; + this.pageList = pageList; + this.isLastPage = isLastPage; + this.eMesg = null; + } + + public GetPreviewListResponder(OnGetPreviewListListener listener, + Object checkFlag, String eMsg) { + this.isOk = false; + this.listener = listener; + this.checkFlag = checkFlag; + this.pageList = null; + this.isLastPage = false; + this.eMesg = eMsg; + } + + @Override + public void run() { + if (isOk) + listener.onSuccess(checkFlag, pageList, isLastPage); + else + listener.onFailure(checkFlag, eMesg); + } + } + + public void getPreviewList(final String url, final int mode, final Object checkFlag, + final OnGetPreviewListListener listener) { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + HttpHelper hh = new HttpHelper(mContext); + String body = hh.get(url); + GetPreviewListResponder responder; + if (body != null) { + if (mode != EhClient.MODE_LOFI) { + DetailParser parser = new DetailParser(); + if (parser.parser(body, DetailParser.PREVIEW) == + DetailParser.PREVIEW) + responder = new GetPreviewListResponder(listener, checkFlag, parser.previewList, false); + else + responder = new GetPreviewListResponder(listener, checkFlag, "Parser error"); + } else { + LofiDetailParser parser = new LofiDetailParser(); + if (parser.parser(body)) + responder = new GetPreviewListResponder(listener, checkFlag, parser.preview, parser.isLastPage); + else + responder = new GetPreviewListResponder(listener, checkFlag, "Parser error"); + } + } else { + responder = new GetPreviewListResponder(listener, checkFlag, hh.getEMsg()); + } + mHandler.post(responder); + } + }); + thread.setPriority(Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + } + + // Post comment + public interface OnCommentListener { + void onSuccess(List comments); + void onFailure(String eMsg); + } + + public void comment(final String detailUrl, final String comment, + final OnCommentListener listener) { + new BgThread(new Runnable() { + @Override + public void run() { + HttpHelper hp = new HttpHelper(mContext); + hp.setOnRespondListener(new HttpHelper.OnRespondListener() { + @Override + public void onSuccess(Object obj) { + String body = (String)obj; + DetailParser parser = new DetailParser(); + if (parser.parser(body, DetailParser.COMMENT) == + DetailParser.COMMENT) { + listener.onSuccess(parser.comments); + } else { + listener.onFailure( mContext.getString(R.string.em_parser_error)); + } + } + + @Override + public void onFailure(String eMsg) { + listener.onFailure(eMsg); + } + }); + hp.postForm(detailUrl, new String[][]{ + new String[]{"commenttext", comment}, + new String[]{"postcomment", "Post New"}}); + } + }).start(); + } + + /********** Use E-hentai API ************/ + + // rate for gallery + public interface OnRateListener { + void onSuccess(float ratingAvg, int ratingCnt); + void onFailure(String eMsg); + } + + public void rate(final int gid, final String token, + final int rating, final OnRateListener listener) { + final JSONObject json = new JSONObject(); + try { + json.put("method", "rategallery"); + json.put("apiuid", APIUID); + json.put("apikey", APIKEY); + json.put("gid", gid); + json.put("token", token); + json.put("rating", rating); + } catch (JSONException e) { + listener.onFailure(e.getMessage()); + return; + } + + new Thread(new Runnable() { + @Override + public void run() { + HttpHelper hp = new HttpHelper(mContext); + hp.setOnRespondListener(new HttpHelper.OnRespondListener() { + @Override + public void onSuccess(Object obj) { + String body = (String)obj; + RateParser parser = new RateParser(); + if (parser.parser(body)) { + listener.onSuccess(parser.mRatingAvg, parser.mRatingCnt); + } else { + listener.onFailure(mContext.getString(R.string.em_parser_error)); + } + } + @Override + public void onFailure(String eMsg) { + listener.onFailure(eMsg); + } + }); + hp.postJson(getApiUrl(), json); + } + }).start(); + } + + // vote for tag + public interface OnVoteListener { + void onSuccess(String tagPane); + void onFailure(String eMsg); + } + + public void vote(final int gid, final String token, + final String group, final String tag, + final boolean isUp, final OnVoteListener listener) { + final JSONObject json = new JSONObject(); + try { + json.put("method", "taggallery"); + json.put("apiuid", APIUID); + json.put("apikey", APIKEY); + json.put("gid", gid); + json.put("token", token); + json.put("tags", group + ":" + tag); + if (isUp) + json.put("vote", 1); + else + json.put("vote", -1); + } catch (JSONException e) { + listener.onFailure(e.getMessage()); + return; + } + + new Thread(new Runnable() { + @Override + public void run() { + HttpHelper hp = new HttpHelper(mContext); + hp.setOnRespondListener(new HttpHelper.OnRespondListener() { + @Override + public void onSuccess(Object obj) { + String body = (String)obj; + VoteParser parser = new VoteParser(); + if (parser.parser(body)) { + listener.onSuccess(parser.mTagPane); + } else { + listener.onFailure( mContext.getString(R.string.em_parser_error)); + } + } + @Override + public void onFailure(String eMsg) { + listener.onFailure(eMsg); + } + }); + hp.postJson(getApiUrl(), json); + } + }).start(); + } + + // Add to favorite + public interface OnAddToFavoriteListener { + void onSuccess(); + void onFailure(String eMsg); + } + + public String getAddFavouriteUrl(int gid, String token) { + return getAddFavouriteUrl(gid, token, Config.getApiMode()); + } + + public static String getAddFavouriteUrl(int gid, String token, int mode) { + StringBuilder sb = new StringBuilder(); + switch (mode) { + case MODE_EX: + sb.append(HEADER_EX); + break; + case MODE_G: + default: + sb.append(HEADER_G); + break; + } + return sb.append("gallerypopups.php?gid=").append(gid).append("&t=") + .append(token).append("&act=addfav").toString(); + } + + /** + * + * @param gid + * @param token + * @param cat -1 for delete, number bigger than 9 or smaller than -1 go to 0 + * @param note max 250 characters + * @param listener + */ + public void addToFavorite(final int gid, final String token, + final int cat, final String note, + final OnAddToFavoriteListener listener) { + + new Thread(new Runnable() { + @Override + public void run() { + HttpHelper hp = new HttpHelper(mContext); + hp.setOnRespondListener(new HttpHelper.OnRespondListener() { + @Override + public void onSuccess(Object obj) { + String body = (String)obj; + AddToFavoriteParser parser = new AddToFavoriteParser(); + if (parser.parser(body)) { + listener.onSuccess(); + // Analytics + Analytics.addToFavoriteGallery(mContext, gid, token); + } else { + listener.onFailure( mContext.getString(R.string.em_parser_error)); + } + } + @Override + public void onFailure(String eMsg) { + listener.onFailure(eMsg); + } + }); + + String catStr; + if (cat == -1) + catStr = "favdel"; + else if (cat >= 0 && cat <= 9) + catStr = String.valueOf(cat); + else + catStr = "0"; + + // submit=Add+to+Favorites is not necessary, just use submit=Apply+Changes all the time + hp.postForm(getAddFavouriteUrl(gid, token), new String[][]{ + new String[]{"favcat", catStr}, + new String[]{"favnote", note == null ? "" : note}, + new String[]{"submit", "Apply Changes"}}); + } + }).start(); + } + + // modifyFavorite + public interface OnModifyFavoriteListener { + void onSuccess(List gis, int pageNum); + void onFailure(String eMsg); + } + + public String getModifyFavoriteUrl() { + return getModifyFavoriteUrl(Config.getMode()); + } + + public static String getModifyFavoriteUrl(int mode) { + StringBuilder sb = new StringBuilder(); + switch (mode) { + case MODE_EX: + sb.append(HEADER_EX); + break; + case MODE_G: + default: + sb.append(HEADER_G); + break; + } + return sb.append("favorites.php").toString(); + } + + /** + * + * @param gids + * @param dstCat -1 for delete + * @param srcCat + * @param listener + */ + public void modifyFavorite(final int[] gids, final int dstCat, final int srcCat, + final OnModifyFavoriteListener listener) { + new BgThread(new Runnable() { + @Override + public void run() { + int i; + + HttpHelper hp = new HttpHelper(mContext); + hp.setOnRespondListener(new HttpHelper.OnRespondListener() { + @Override + public void onSuccess(Object obj) { + String body = (String)obj; + ListParser parser = new ListParser(); + int re = parser.parser(body, MODE_G); // Only MODE_G and MODE_EX + if (re == ListParser.ALL) { + listener.onSuccess(parser.giList, parser.pageNum); + } else if (re == ListParser.NOT_FOUND) { + listener.onSuccess(parser.giList, parser.pageNum); + } else if (re == ListParser.PARSER_ERROR) { + listener.onFailure( mContext.getString(R.string.em_parser_error)); + } + } + @Override + public void onFailure(String eMsg) { + listener.onFailure(eMsg); + } + }); + + String catStr; + if (dstCat == -1) + catStr = "delete"; + else if (dstCat >= 0 && dstCat <= 9) + catStr = "fav" + String.valueOf(dstCat); + else + catStr = "fav0"; + + String[][] args = new String[gids.length + 2][]; + args[0] = new String[]{"ddact", catStr}; + for (i = 1; i <= gids.length; i++) + args[i] = new String[]{"modifygids[]", String.valueOf(gids[i-1])}; + args[i] = new String[]{"apply", "Apply"}; + + hp.postForm(getFavoriteUrlWithCat(srcCat, 0), args); + } + }).start(); + } + + public interface OnGetPopularListener { + void onSuccess(List gis, long timeStamp); + void onFailure(String eMsg); + } + + public void getPopular(final OnGetPopularListener listener) { + final JSONObject json = new JSONObject(); + try { + json.put("method", "popular"); + } catch (JSONException e) { + listener.onFailure(e.getMessage()); + return; + } + new BgThread(new Runnable() { + @Override + public void run() { + HttpHelper hp = new HttpHelper(mContext); + hp.setOnRespondListener(new HttpHelper.OnRespondListener() { + @Override + public void onSuccess(Object obj) { + String body = (String)obj; + try { + List gis = new ArrayList(); + JSONObject js = new JSONObject(body); + js = js.getJSONObject("popular"); + + if (!js.has("galleries")) { + listener.onFailure(js.getString("error")); + return; + } + + JSONArray ja = js.getJSONArray("galleries"); + for (int i = 0; i < ja.length(); i++) { + JSONObject j = ja.getJSONObject(i); + GalleryPopular gi = new GalleryPopular(); + if (j.has("count")) + gi.count = j.getLong("count"); + else + gi.count = -1; + gi.gid = j.getInt("gid"); + gi.token = j.getString("token"); + gi.title = Utils.unescapeXml(j.getString("title")); + gi.posted = Utils.sDate.format(Long.parseLong(j.getString("posted")) * 1000); + gi.thumb = j.getString("thumb"); + gi.category = EhUtils.getCategory(j.getString("category")); + gi.uploader = j.getString("uploader"); + gi.rating = Float.parseFloat(j.getString("rating")); + gi.generateSLang(); + gis.add(gi); + } + + long timeStamp; + if (js.has("time")) + timeStamp = js.getLong("time"); + else + timeStamp = -1; + + listener.onSuccess(gis, timeStamp); + } catch (JSONException e) { + listener.onFailure(e.getMessage()); + } + } + @Override + public void onFailure(String eMsg) { + listener.onFailure(eMsg); + } + }); + hp.postJson(API_EHVIEWER, json); + } + }).start(); + } + + public interface OnGetApiGalleryInfoListener { + public void onSuccess(Object checkFlag, ApiGalleryInfo[] agiArray); + public void onFailure(Object checkFlag, String eMsg); + } + + private class GetApiGalleryInfoResponder implements Runnable { + private final boolean isOk; + private final OnGetApiGalleryInfoListener listener; + private final Object checkFlag; + private final ApiGalleryInfo[] agiArray; + private final String eMesg; + + public GetApiGalleryInfoResponder(OnGetApiGalleryInfoListener listener, + Object checkFlag, ApiGalleryInfo[] agiArray) { + this.isOk = true; + this.listener = listener; + this.checkFlag = checkFlag; + this.agiArray = agiArray; + this.eMesg = null; + } + + public GetApiGalleryInfoResponder(OnGetApiGalleryInfoListener listener, + Object checkFlag, String eMsg) { + this.isOk = false; + this.listener = listener; + this.checkFlag = checkFlag; + this.agiArray = null; + this.eMesg = eMsg; + } + + @Override + public void run() { + if (isOk) + listener.onSuccess(checkFlag, agiArray); + else + listener.onFailure(checkFlag, eMesg); + } + } + + private ApiGalleryInfo Json2Agi(JSONObject js) throws JSONException { + ApiGalleryInfo agi = new ApiGalleryInfo(); + agi.gid = js.getInt("gid"); + agi.token = js.getString("token"); + agi.archiver_key = js.getString("archiver_key"); + agi.title = js.getString("title"); + agi.title_jpn = js.getString("title_jpn"); + agi.category = EhUtils.getCategory(js.getString("category")); + agi.thumb = js.getString("thumb"); + agi.uploader = js.getString("uploader"); + agi.posted = Utils.sDate.format(Long.parseLong(js.getString("posted")) * 1000); + agi.filecount = js.getString("filecount"); + agi.filesize = js.getLong("filesize"); + agi.expunged = js.getBoolean("expunged"); + agi.rating = Float.parseFloat(js.getString("rating")); + agi.torrentcount = js.getString("torrentcount"); + JSONArray tags = js.getJSONArray("tags"); + agi.apiTags = new String[tags.length()]; + for (int j = 0; j < tags.length(); j++) + agi.apiTags[j] = tags.getString(j); + return agi; + } + + public void getApiGalleryInfo(final Object checkFlag, final int[] gids, final String[] tokens, final OnGetApiGalleryInfoListener l) { + new BgThread() { + @Override + public void run() { + GetApiGalleryInfoResponder responder; + ApiGalleryInfo[] agiArray; + int requstLen; + // Create json + JSONObject json = new JSONObject(); + try { + json.put("method", "gdata"); + JSONArray jsonA = new JSONArray(); + requstLen = Math.min(gids.length, tokens.length); + agiArray = new ApiGalleryInfo[requstLen]; + for (int i = 0; i < requstLen; i++) { + JSONArray ja = new JSONArray(); + ja.put(gids[i]); + ja.put(tokens[i]); + jsonA.put(ja); + } + json.put("gidlist", jsonA); + } catch (Throwable e) { + responder = new GetApiGalleryInfoResponder(l, null, "Create json error"); + mHandler.post(responder); + return; + } + // Post + HttpHelper hh = new HttpHelper(mContext); + String body = hh.postJson(getApiUrl(), json); + if (body != null) { + try { + json = new JSONObject(body); + JSONArray jsonA = (JSONArray) json.get("gmetadata"); + for (int i = 0; i < jsonA.length(); i++) { + ApiGalleryInfo agi = Json2Agi(jsonA.getJSONObject(i)); + // Add to agiArray + for (int j = 0; j < requstLen; j++) { + if (agi.gid == gids[j]) + agiArray[j] = agi; + } + } + responder = new GetApiGalleryInfoResponder(l, checkFlag, agiArray); + } catch (Throwable e) { + responder = new GetApiGalleryInfoResponder(l, checkFlag, "Json error"); + } + } else { + responder = new GetApiGalleryInfoResponder(l, checkFlag, hh.getEMsg()); + } + mHandler.post(responder); + } + }.start(); + } + + public ApiGalleryInfo[] getApiGalleryInfo(int[] gids, String[] tokens) { + ApiGalleryInfo[] agiArray; + int requstLen; + // Create json + JSONObject json = new JSONObject(); + try { + json.put("method", "gdata"); + JSONArray jsonA = new JSONArray(); + requstLen = Math.min(gids.length, tokens.length); + agiArray = new ApiGalleryInfo[requstLen]; + for (int i = 0; i < requstLen; i++) { + JSONArray ja = new JSONArray(); + ja.put(gids[i]); + ja.put(tokens[i]); + jsonA.put(ja); + } + json.put("gidlist", jsonA); + } catch (Throwable e) { + return null; + } + // Post api + HttpHelper hh = new HttpHelper(mContext); + String body = hh.postJson(getApiUrl(), json); + if (body == null) { + return null; + } else { + try { + json = new JSONObject(body); + JSONArray jsonA = (JSONArray) json.get("gmetadata"); + for (int i = 0; i < jsonA.length(); i++) { + ApiGalleryInfo agi = Json2Agi(jsonA.getJSONObject(i)); + // Add to agiArray + for (int j = 0; j < requstLen; j++) { + if (agi.gid == gids[j]) + agiArray[j] = agi; + } + } + return agiArray; + } catch (Throwable e) { + return null; + } + } + } + +} diff --git a/app/src/main/java/com/hippo/ehviewer/ehclient/EhInfo.java b/app/src/main/java/com/hippo/ehviewer/ehclient/EhInfo.java new file mode 100644 index 000000000..5f23550cf --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ehclient/EhInfo.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ehclient; + +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.Utils; +import com.larvalabs.svgandroid.SVGBuilder; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.util.List; + +/** + * + * @author Hippo + * + */ +public class EhInfo { + + private static final String TAG = EhInfo.class.getSimpleName(); + private static final String PREF_NAME = "eh_info"; + private static final String DIR_NAME = "file"; + private static final String AVATAR_NAME = "avatar.png"; + private static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.PNG; + private static Drawable DEFAULT_AVATAR; + + public static final String EX_HOST = "exhentai.org"; + public static final String[] COOKIABLE_HOSTS = {"exhentai.org", + "g.e-hentai.org", "forums.e-hentai.org", "ul.exhentai.org", + "ul.e-hentai.org", "lofi.e-hentai.org"}; + + private static final String KEY_LOGIN = "login"; + private static final boolean DEFAULT_LOGIN = false; + private static final String KEY_USERNAME = "username"; + private static final String KEY_DISPLAYNAME = "displayname"; + private static final String DEFAULT_NAME = "Hippo"; + private static final String KEY_MEMBER_ID = "ipb_member_id"; + private static final String DEFAULT_MEMBER_ID = null; + private static final String KEY_PASS_HASH = "ipb_pass_hash"; + private static final String DEFAULT_PASS_HASH = null; + + private final Context mContext; + private final SharedPreferences mInfoPref; + private String mUconfig; + private boolean mIsLogin; + private String mUsername; + private String mDisplayname; + private BitmapDrawable mAvatar; + private int mDefaultCat; + private String mPreviewMode; + private int mExculdeTagGroup; + private String mExculdeLanguage; + private static EhInfo sInstance; + + private BitmapDrawable getAvatarFromFile() { + File dir = mContext.getDir(DIR_NAME, 0); + File avatarFile = new File(dir, AVATAR_NAME); + if (!avatarFile.exists()) + return null; + + InputStream is = null; + try { + is = new FileInputStream(avatarFile); + return (BitmapDrawable) BitmapDrawable.createFromStream(is, null); + } catch (FileNotFoundException e) { + return null; + } finally { + Utils.closeQuietly(is); + } + } + + private EhInfo(final Context context){ + mContext = context; + mInfoPref = mContext.getSharedPreferences(PREF_NAME, 0); + if (DEFAULT_AVATAR == null) + DEFAULT_AVATAR = new SVGBuilder().readFromResource(context.getResources(), R.raw.default_avatar).build().getDrawable(); + + mIsLogin = mInfoPref.getBoolean(KEY_LOGIN, DEFAULT_LOGIN); + mUsername = mInfoPref.getString(KEY_USERNAME, DEFAULT_NAME); + mDisplayname = mInfoPref.getString(KEY_DISPLAYNAME, DEFAULT_NAME); + mAvatar = getAvatarFromFile(); + + mDefaultCat = Config.getDefaultCat(); + mPreviewMode = Config.getPreviewMode(); + mExculdeTagGroup = Config.getExculdeTagGroup(); + mExculdeLanguage = Config.getExculdeLanguage(); + updateUconfig(); + }; + + private String getUconfigString(String previewMode) { + return new StringBuilder().append("cats_").append(mDefaultCat) + .append("-ts_").append(previewMode == null ? mPreviewMode : previewMode) + .append("-xns_").append(mExculdeTagGroup) + .append("-xl_").append(mExculdeLanguage) + .append("-tl_m-uh_y-tr_2-prn_n-dm_l-ar_0-rc_0-rx_0-ry_0-sa_y-oi_n-qb_n-tf_n-hp_-hk_-ms_n-mt_n") + .toString(); + } + + private void updateUconfig() { + mUconfig = getUconfigString(null); + } + + public final static EhInfo getInstance(final Context context) { + if (sInstance == null) + sInstance = new EhInfo(context.getApplicationContext()); + return sInstance; + } + + private String getCookie(String cookieStr, String key) { + String value = null; + int index1 = -1; + int index2 = -1; + + if ((index1 = cookieStr.indexOf(key + "=")) != -1) { + index1 += key.length() + 1; // size of key + "=" + if ((index2 = cookieStr.indexOf(";", index1)) != -1) + value = cookieStr.substring(index1, index2); + else + value = cookieStr.substring(index1); + } + return value; + } + + public void storeCookie(HttpURLConnection conn) { + String ipb_member_id; + String ipb_pass_hash; + + List cookieList = conn.getHeaderFields().get("Set-Cookie"); + if (cookieList == null) + return; + SharedPreferences.Editor editor = mInfoPref.edit(); + for (String str : cookieList) { + ipb_member_id = getCookie(str, KEY_MEMBER_ID); + ipb_pass_hash = getCookie(str, KEY_PASS_HASH); + + if (ipb_member_id != null) + editor.putString(KEY_MEMBER_ID, ipb_member_id); + if (ipb_pass_hash != null) + editor.putString(KEY_PASS_HASH, ipb_pass_hash); + } + editor.apply(); + } + + public void setCookie(HttpURLConnection conn) { + setCookie(conn, null); + } + + public void setCookie(HttpURLConnection conn, String previewMode) { + StringBuilder sb = new StringBuilder(); + String id = mInfoPref.getString(KEY_MEMBER_ID, DEFAULT_MEMBER_ID); + String hash = mInfoPref.getString(KEY_PASS_HASH, DEFAULT_PASS_HASH); + sb.append("uconfig=").append(previewMode == null ? mUconfig : getUconfigString(previewMode)) + .append("; xres=").append(Config.getLofiResolution()); + if (id != null) + sb.append("; ipb_member_id=").append(id); + if (hash != null) + sb.append("; ipb_pass_hash=").append(hash); + // Fake + sb.append("; igneous="); + sb.append("; lv="); + conn.setRequestProperty("Cookie", sb.toString()); + } + + public boolean isLogin() { + return mIsLogin; + } + + public void login(String username, String displayname) { + mIsLogin = true; + mUsername = username; + mDisplayname = displayname; + + mInfoPref.edit().putBoolean(KEY_LOGIN, true) + .putString(KEY_USERNAME, username) + .putString(KEY_DISPLAYNAME, displayname).apply(); + } + + public void logout() { + mIsLogin = false; + // Remove avatar + if (mAvatar != null) { + mAvatar.getBitmap().recycle(); + mAvatar = null; + File dir = mContext.getDir(DIR_NAME, 0); + File avatarFile = new File(dir, AVATAR_NAME); + avatarFile.delete(); + } + + mInfoPref.edit().putBoolean(KEY_LOGIN, false) + .putString(KEY_MEMBER_ID, DEFAULT_MEMBER_ID) + .putString(KEY_PASS_HASH, DEFAULT_PASS_HASH).apply(); + } + + public String getUsername() { + return mUsername; + } + + public String getDisplayname() { + return mDisplayname; + } + + public Drawable getAvatar() { + return isLogin() && mAvatar != null ? mAvatar : DEFAULT_AVATAR; + } + + public void setAvatar(BitmapDrawable avatar) { + mAvatar = avatar; + + File dir = mContext.getDir(DIR_NAME, 0); + File avatarFile = new File(dir, AVATAR_NAME); + OutputStream os = null; + try { + os = new FileOutputStream(avatarFile); + avatar.getBitmap().compress(AVATAR_FORMAT, 100, os); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } finally { + Utils.closeQuietly(os); + } + } + + public void setDefaultCat(int defaultCat) { + mDefaultCat = defaultCat; + updateUconfig(); + } + + public void setPreviewMode(String previewMode) { + mPreviewMode = previewMode; + updateUconfig(); + } + + public void setExculdeTagGroup(int exculdeTagGroup) { + mExculdeTagGroup = exculdeTagGroup; + updateUconfig(); + } + + public void setExculdeLanguage(String exculdeLanguage) { + mExculdeLanguage = exculdeLanguage; + updateUconfig(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ehclient/ExDownloader.java b/app/src/main/java/com/hippo/ehviewer/ehclient/ExDownloader.java new file mode 100644 index 000000000..eef98b30a --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ehclient/ExDownloader.java @@ -0,0 +1,922 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ehclient; + +import java.io.BufferedInputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.util.HashSet; +import java.util.List; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; + +import android.app.NotificationManager; +import android.content.Context; +import android.os.Process; +import android.support.v4.app.NotificationCompat; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.network.HttpHelper; +import com.hippo.ehviewer.util.AutoExpandArray; +import com.hippo.ehviewer.util.BgThread; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.EhUtils; +import com.hippo.ehviewer.util.Log; +import com.hippo.ehviewer.util.Utils; + +// TODO what a mess +public final class ExDownloader implements Runnable { + + private static final String TAG = ExDownloader.class.getSimpleName(); + + private static final int IO_BUFFER_SIZE = 8 * 1024; + + private static final String[] URL_ARRAY_509 = new String[] { + "http://ehgt.org/g/509.gif", + "http://ehgt.org/g/509s.gif", + "http://st.exhentai.net/img/509.gif", + "http://st.exhentai.net/img/509s.gif" + }; + + private final Context mContext; + private final ExDownloaderManager mManager; + + private final int mGid; + private final String mToken; + private final String mTitle; + private final int mMode; + private volatile int mPreviewPageNum = -1; + private volatile int mPreviewPerPage = -1; + private volatile int mImageNum = -1; + private final AutoExpandArray mPageTokeArray = new AutoExpandArray(); + private final AutoExpandArray mImageFilenameArray = new AutoExpandArray(); + + private volatile int mOwnerNum = 0; + private Thread mMainThread = null; + /** Only work for EhClient.MODE_LOFI when do not know image number **/ + private int mCurMaxPreviewPage = -1; + + private volatile int mStartIndex = 0; + private final Queue mRequestIndexArray = new ConcurrentLinkedQueue(); + private final Queue mNoTokenIndexArray = new ConcurrentLinkedQueue(); + private final Queue mRequestPageIndexArray = new ConcurrentLinkedQueue(); + + /** The set contains the requsting index **/ + private final Set mRequstingIndexSet = new HashSet(); + + private volatile int mCurRequestPageIndex = -1; + + private final int mWorkerNum; + private final Worker[] mWorkerArray; + private final HttpHelper.DownloadControlor[] mControlorArray; + + private final Object mPageTokenLock = new Object(); + private final Object mWorkerLock = new Object(); + private final Object mWorkerGetIndexLock = new Object(); + + private volatile boolean mDownloadMode = false; + private volatile boolean mPauseWork = false; + private volatile boolean mStopWork = false; + + private final File mDir; + private ListenerForImageSet mLfis; + private ListenerForDownload mLfd; + + private static final long UPDATE_SPEED_INTERVAL = 500; + private volatile long mLastUpdateSpeedTime = 0; + private volatile long mAccumulateSize; + private volatile HashSet mDownloadIndexSet; + private volatile Object mDownloadLock = new Object(); + + private int mCurReadIndex = 0; + + /** + * Do no work in UI thread + * @author Hippo + */ + public interface ListenerForImageSet { + public void onDownloadStart(int index); + public void onDownloading(int index, float percent); + public void onDownloadComplete(int index); + public void onDownloadFail(int index); + } + + /** + * Do no work in UI thread + * @author Hippo + */ + public interface ListenerForDownload { + public void onStart(int gid); + public void onGetSum(int gid, int sum); + public void onDownload(int gid, int downloadSize, int totalSize); + public void onUpdateSpeed(int gid, int speed); + public void onDownloadOver(int gid, int legacy); + } + + ExDownloader(Context context, int gid, String token, String title, int mode) { + mContext = context; + mManager = ExDownloaderManager.getInstance(); + mGid = gid; + mToken = token; + mTitle = title; + mMode = mode; + + mWorkerNum = Config.getDownloadThread(); + mWorkerArray = new Worker[mWorkerNum]; + mControlorArray = new HttpHelper.DownloadControlor[mWorkerNum]; + + // Make sure dir + mDir = EhUtils.getGalleryDir(mGid, mTitle); + Utils.ensureDir(mDir, true); + } + + public void setListenerForImageSet(ListenerForImageSet l) { + mLfis = l; + } + + public void setListenerDownload(ListenerForDownload l) { + mLfd = l; + } + + void occupy() { + mOwnerNum++; + } + + void free() { + mOwnerNum--; + } + + boolean isOrphans() { + return mOwnerNum == 0; + } + + void stop() { + // TODO stop all thread, release all resources + mStopWork = true; + // Stop download + for (HttpHelper.DownloadControlor c : mControlorArray) { + if (c != null) + c.stop(); + } + // Wake all thread + synchronized(mPageTokenLock) {mPageTokenLock.notifyAll();} + synchronized(mWorkerGetIndexLock) {mWorkerGetIndexLock.notifyAll();} + synchronized(mWorkerLock) {mWorkerLock.notifyAll();} + } + + boolean pause(boolean pause) { + if (mOwnerNum > 1) + return false; + + mPauseWork = pause; + if (mPauseWork) { + // Stop download + for (HttpHelper.DownloadControlor c : mControlorArray) + if (c != null) c.stop(); + // Wake all thread + synchronized(mPageTokenLock) {mPageTokenLock.notifyAll();} + synchronized(mWorkerGetIndexLock) {mWorkerGetIndexLock.notifyAll();} + synchronized(mWorkerLock) {mWorkerLock.notifyAll();} + } else { + // Reset download + for (HttpHelper.DownloadControlor c : mControlorArray) + if (c != null) c.reset(); + ensureStart(); + ensureWorkers(); + } + return true; + } + + public int getGid() { + return mGid; + } + + public String getToken() { + return mToken; + } + + public String getTitle() { + return mTitle; + } + + public void setDownloadMode(boolean downloadMode) { + synchronized (mDownloadLock) { + if (downloadMode) { + setStartIndex(0); + if (mDownloadIndexSet == null) + mDownloadIndexSet = new HashSet(); + if (mDownloadMode && mLfd != null) + mLfd.onStart(mGid); + } else { + mDownloadIndexSet = null; + } + mDownloadMode = downloadMode; + } + } + + public void setStartIndex(int startIndex) { + if (mDownloadMode) { + addTargetIndex(startIndex); + } else { + if (mImageNum != -1 && startIndex >= mImageNum) + mStartIndex = 0; + else + mStartIndex = startIndex; + + ensureStart(); + ensureWorkers(); + } + } + + public void addTargetIndex(int index) { + if (mImageNum != -1 && index >= mImageNum) + return; + + mRequestIndexArray.offer(index); + ensureStart(); + ensureWorkers(); + } + + public void setCurReadIndex(final int index) { + new BgThread() { + @Override + public void run() { + writeCurReadIndex(index); + } + }.start(); + } + + public int getMaxEnsureIndex() { + if (mImageNum != -1) { + return mImageNum - 1; + } else { + return Math.max(Math.max(Math.max(mStartIndex, + Math.max(mCurMaxPreviewPage, mPreviewPageNum - 1) * mPreviewPerPage), + mPageTokeArray.maxValidIndex()), 0); + } + } + + /** + * If do not know image num, return -1. + * You should call getMaxEnsureIndex(). + * + * @return + */ + public int getImageNum() { + return mImageNum; + } + + public AutoExpandArray getImageFilenameArray() { + return mImageFilenameArray; + } + + /** + * If targer index image is downloading, return true + * + * @param index + * @return + */ + public boolean isDownloading(int index) { + return mRequstingIndexSet.contains(index); + } + + private synchronized void ensureStart() { + if (mMainThread != null) + return; + + mMainThread = new BgThread(this); + mMainThread.setPriority(Process.THREAD_PRIORITY_BACKGROUND); + mMainThread.start(); + } + + private File getExDownloadInfoFile() { + return new File(mDir, EhUtils.EH_DOWNLOAD_FILENAME); + } + + private boolean checkMode(int samlpe, int target) { + return samlpe <= EhClient.MODE_EX && target <= EhClient.MODE_EX + || samlpe == EhClient.MODE_LOFI && target == EhClient.MODE_LOFI; + } + + /** + * This function parser the download info file.
    + * The file look like this:
    + * + * 00000000
    + * 728874
    + * 306429c222
    + * 1
    + * 4
    + * 40
    + * 128
    + * 1 43a64e6e79
    + *

    + * Fist line is gid, a integer. + * Second line is token, a ten-character string. + * Third line is mode. + * Fourth line is preview page num. + * Fifth line is preview per page. + * + * @param ediFile + * @return + */ + private synchronized boolean parserEdiFile(File ediFile) { + if (!ediFile.exists() || !ediFile.isFile() || !ediFile.canRead() + || !ediFile.canWrite()) + return false; + + InputStream is = null; + try { + is = new BufferedInputStream(new FileInputStream(ediFile), + IO_BUFFER_SIZE); + // skip read index + Utils.readAsciiLine(is); + if (mGid != Integer.valueOf(Utils.readAsciiLine(is)) || + !mToken.equals(Utils.readAsciiLine(is))) + return false; + + if (!checkMode(Integer.valueOf(Utils.readAsciiLine(is)), mMode)) { + // If preview info is not same + // skip preview page num and preview per page + Utils.readAsciiLine(is); + Utils.readAsciiLine(is); + } else { + mPreviewPageNum = Integer.valueOf(Utils.readAsciiLine(is)); + mPreviewPerPage = Integer.valueOf(Utils.readAsciiLine(is)); + } + + mImageNum = Integer.parseInt(Utils.readAsciiLine(is)); + if (mImageNum != -1) { + mPageTokeArray.setCapacity(mImageNum); + mImageFilenameArray.setCapacity(mImageNum); + } + else if (mPreviewPageNum != -1 && mPreviewPerPage != -1) { + mPageTokeArray.setCapacity(mPreviewPageNum * mPreviewPerPage); + mImageFilenameArray.setCapacity(mPreviewPageNum * mPreviewPerPage); + } + + // read page token info + try { + while(true) { + String line = Utils.readAsciiLine(is); + int pos = line.indexOf(" "); + if (pos == -1 && line.length() - pos != 11) + continue; + try { + int index = Integer.parseInt(line.substring(0, pos)); + String token = line.substring(pos + 1); + mPageTokeArray.set(index, token); + } catch (NumberFormatException e) { + continue; + } + } + } catch (EOFException e) { + // Empty + } + return true; + } catch (Throwable e) { + // If read error + return false; + } finally { + Utils.closeQuietly(is); + } + } + + private synchronized void writeEdiFile(File ediFile) { + try { + FileWriter writer = new FileWriter(ediFile); + writer.write(String.format("%08x", mCurReadIndex)); + writer.write("\n"); + writer.write(String.valueOf(mGid)); + writer.write("\n"); + writer.write(mToken); + writer.write("\n"); + writer.write(mMode + '0'); + writer.write("\n"); + writer.write(String.valueOf(mPreviewPageNum)); + writer.write("\n"); + writer.write(String.valueOf(mPreviewPerPage)); + writer.write("\n"); + writer.write(String.valueOf(mImageNum)); + writer.write("\n"); + for (int i = 0; i < mPageTokeArray.length(); i++) { + String pageToken = mPageTokeArray.get(i); + if (pageToken != null) { + writer.write(String.valueOf(i)); + writer.write(" "); + writer.write(pageToken); + writer.write("\n"); + } + } + writer.flush(); + writer.close(); + } catch (IOException e) {} + } + + private synchronized void writeCurReadIndex(int index) { + mCurReadIndex = index; + try { + RandomAccessFile raf = new RandomAccessFile(getExDownloadInfoFile(), "rw"); + raf.write(String.format("%08x", mCurReadIndex).getBytes()); + raf.close(); + } catch (Throwable e) {} + } + + public static int readCurReadIndex(int gid, String title) { + try { + RandomAccessFile raf = new RandomAccessFile(new File(EhUtils.getGalleryDir(gid, title), + EhUtils.EH_DOWNLOAD_FILENAME), "r"); + int index = Integer.parseInt(raf.readLine(), 16); + raf.close(); + return index; + } catch (Throwable e) { + return 0; + } + } + + private void getDetailInfo(int pageIndex, File ediFile, boolean needPreviewInfo) throws Exception { + HttpHelper hh = new HttpHelper(mContext); + EdDetailParser edp = new EdDetailParser(); + String url = EhClient.getDetailUrl(mGid, mToken, pageIndex, mMode); + hh.setPreviewMode("m"); + String body = hh.get(url); + if (body == null) + throw new Exception(hh.getEMsg() != null ? hh.getEMsg() : "Http error, body == null"); + if (!edp.parser(body, mMode, needPreviewInfo)) + throw new Exception(edp.emsg != null ? edp.emsg : "Parser error, body is \n" + body); + if (edp.previewStartIndex != pageIndex * mPreviewPerPage) + throw new Exception("预测与实际不匹配, 预测: " + edp.previewStartIndex + + ", 实际: " + pageIndex * mPreviewPerPage + ", body is \n" + body); + + List pageTokenArray = edp.pageTokenArray; + if (needPreviewInfo) + mPreviewPerPage = pageTokenArray.size(); + if (mMode == EhClient.MODE_LOFI) { + // update mCurMaxPreviewPage + if (edp.isLastPage) { + mPreviewPageNum = pageIndex + 1; + mImageNum = pageIndex * mPreviewPerPage + pageTokenArray.size(); + } else if (!edp.isLastPage && mPreviewPageNum == -1) { + mCurMaxPreviewPage = Math.max(mCurMaxPreviewPage, pageIndex + 1); + } + } else { + if (needPreviewInfo) { + mPreviewPageNum = edp.previewPageNum; + mImageNum = edp.imageNum; + mPageTokeArray.setCapacity(mPreviewPageNum * mPreviewPerPage); + mImageFilenameArray.setCapacity(mPreviewPageNum * mPreviewPerPage); + } + } + for (int i = 0; i < pageTokenArray.size(); i++) + mPageTokeArray.set(i + edp.previewStartIndex, pageTokenArray.get(i)); + + writeEdiFile(ediFile); + } + + private void ensureWorkers() { + if (mPreviewPerPage == -1) + return; + + synchronized(mWorkerArray) { + for (int i = 0; i < mWorkerArray.length; i++) { + if (mWorkerArray[i] == null) { + Worker worker = new Worker(i); + worker.start(); + mWorkerArray[i] = worker; + mControlorArray[i] = new HttpHelper.DownloadControlor(); + } + } + } + } + + private void updateDownload(int index) { + synchronized (mDownloadLock) { + if (mDownloadIndexSet != null && !mDownloadIndexSet.contains(index)) { + mDownloadIndexSet.add(index); + if (mDownloadMode && mLfd != null) + mLfd.onDownload(mGid, mDownloadIndexSet.size(), getMaxEnsureIndex() + 1); + } + } + } + + private static final int BE_NOTIFY_ID = -3; + + private static final void show509Notification(Context context) { + NotificationManager notifyManager = (NotificationManager) context + .getSystemService(Context.NOTIFICATION_SERVICE); + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context); + mBuilder.setSmallIcon(R.drawable.ic_stat_eh).setContentTitle(context.getString(R.string.notification_be_title)) + .setContentText(context.getString(R.string.notification_be_mesg)) + .setOngoing(false).setAutoCancel(true); + notifyManager.notify(BE_NOTIFY_ID, mBuilder.build()); + } + + // TODO for download , when to claim download completed + @Override + public void run() { + // Try to get info from file + try { + File ediFile = getExDownloadInfoFile(); + if (!parserEdiFile(ediFile) || mPreviewPerPage == -1 || + ((mMode == EhClient.MODE_EX || mMode == EhClient.MODE_G) && mImageNum == -1)) { + getDetailInfo(0, ediFile, true); + } + + if (mLfd != null) + mLfd.onGetSum(mGid, mImageNum); + + ensureWorkers(); + + // A loop to get page token + while (true) { + // Check stop + if (mStopWork) + break; + + int noTokenIndex = -1; + int pageIndex = -1; + synchronized(mPageTokenLock) { + if (mNoTokenIndexArray.isEmpty()) { + if (mRequestPageIndexArray.isEmpty()) { + try { + mPageTokenLock.wait(); + } catch (InterruptedException e) {} + continue; + } else { + pageIndex = mRequestPageIndexArray.poll(); + mCurRequestPageIndex = pageIndex; + } + } else { + noTokenIndex = mNoTokenIndexArray.poll(); + } + } + + if (noTokenIndex != -1) { + if (mPageTokeArray.get(noTokenIndex) == null) { + getDetailInfo(noTokenIndex / mPreviewPerPage, ediFile, false); + } else { + // The token is already got, no need to re + } + } else if (noTokenIndex == -1){ + getDetailInfo(pageIndex, ediFile, false); + mCurRequestPageIndex = -1; + } + + ensureWorkers(); + + synchronized(mWorkerGetIndexLock) {mWorkerGetIndexLock.notifyAll();} + synchronized(mWorkerLock) {mWorkerLock.notifyAll();} + } + } catch (Exception e) { + + Log.f(e); + + synchronized (mDownloadLock) { + if (mDownloadMode && mLfd != null && mDownloadIndexSet != null) { + mLfd.onDownloadOver(mGid, + getMaxEnsureIndex() + 1 - mDownloadIndexSet.size()); + } + } + + } finally { + synchronized (this) { + mMainThread = null; + } + } + + Log.d(TAG, "ExDownloader over"); + } + + private class Worker extends BgThread { + + private final int mIndex; + + public Worker(int index) { + super(); + mIndex = index; + setPriority(Process.THREAD_PRIORITY_BACKGROUND); + } + + private void addNoTokenIndex(int index) { + synchronized(mPageTokenLock) { + mNoTokenIndexArray.offer(index); + mPageTokenLock.notify(); + } + } + + private void addRequstPageIndex(int pageIndex) { + synchronized(mPageTokenLock) { + if (mCurRequestPageIndex != pageIndex && + !mRequestPageIndexArray.contains(pageIndex)) { + mRequestPageIndexArray.offer(pageIndex); + mPageTokenLock.notify(); + } + } + } + + private int getTargetIndex() { + synchronized(mWorkerGetIndexLock) { + while (true) { + + // Check stop + if (mStopWork || mPauseWork) + return -1; + + if (!mRequestIndexArray.isEmpty()) { + return mRequestIndexArray.poll(); + } else if (mImageNum != -1 && mStartIndex == mImageNum) { + return -1; + } else if (mImageNum == -1 && mStartIndex > getMaxEnsureIndex()) { + // If do not sure the index is valid + + Log.d(TAG, "mPreviewPerPage = " + mPreviewPerPage); + + addRequstPageIndex(getMaxEnsureIndex() / mPreviewPerPage); + try { + mWorkerGetIndexLock.wait(); + } catch (InterruptedException e) {} + } else { + return mStartIndex++; + } + } + } + } + + // TODO what if download failed ? what to do ? + @Override + public void run() { + HttpDownloadListener listener = new HttpDownloadListener(); + HttpHelper hh = new HttpHelper(mContext); + ImagePageParser ipp = new ImagePageParser(); + + while (true) { + // Check stop + if (mStopWork || mPauseWork) + break; + + int targetIndex = getTargetIndex(); + if (targetIndex == -1) + break; + + // Check is the index is requsted + if (mRequstingIndexSet.contains(targetIndex)) + continue; + + // Check is this already downloaded + boolean isAlreadyDownloaded = false; + // First check mImageFilenameArray + String imageFilename = mImageFilenameArray.get(targetIndex); + if (imageFilename != null) { + File file = new File(mDir, imageFilename); + if (file.exists()) + isAlreadyDownloaded = true; + } else { + // Can not find image file name, just guess + for (String possibleFilename : EhUtils.getPossibleImageFilenames(targetIndex)) { + File file = new File(mDir, possibleFilename); + if (file.exists()) { + isAlreadyDownloaded = true; + mImageFilenameArray.set(targetIndex, possibleFilename); + break; + } + } + } + + if (isAlreadyDownloaded) { + Log.d(TAG, "isAlreadyDownloaded"); + updateDownload(targetIndex); + continue; + } + + mRequstingIndexSet.add(targetIndex); + + listener.index = targetIndex; + + String pageToken = null; + while (true) { + // Check stop + if (mStopWork || mPauseWork) + break; + + pageToken = mPageTokeArray.get(targetIndex); + if (pageToken == null) { + addNoTokenIndex(targetIndex); + synchronized(mWorkerLock) { + try {mWorkerLock.wait();} + catch (InterruptedException e) {} + } + } else { + // Get valid page token, jump out of loop + break; + } + } + + // Parser image page + for (int i = 0; i < 2 && !mStopWork && !mPauseWork; i++) { + hh.reset(); + ipp.reset(); + String body = hh.get(EhClient.getPageUrl(mGid, pageToken, targetIndex + 1, mMode) + + ((i == 1) ? "?nl=48" : "")); + if (ipp.parser(body, mMode)) { + // Download image + HttpHelper.DownloadControlor c = mControlorArray[mIndex]; + + if (Config.getDownloadOriginImage() && ipp.originalImageUrl != null) { + + Log.d(TAG, "Download origin image"); + + // Download orgin image + String imageUrl = ipp.originalImageUrl; + // Guess filename + String filename = EhUtils.getImageFilename(targetIndex, "jpg"); + // Download + hh.reset(); + if (hh.downloadOriginEhImage(imageUrl, mDir, filename, false, c, listener) == null) { + if (hh.getException() instanceof HttpHelper.BandwidthExceededException) { + // Get 509 + show509Notification(mContext); + break; + } else { + String newImageUrl = hh.getLastUrl(); + if (newImageUrl != null) { + // Try download from proxy + hh.reset(); + if (hh.downloadEhImage(newImageUrl, mDir, filename, true, c, listener) == null) { + // Download failed + Log.d(TAG, "Download failed"); + } else { + // Download successful + Log.d(TAG, "Download successful"); + } + } else { + // Download failed + Log.d(TAG, "Download failed"); + } + } + } else { + // Download successful + Log.d(TAG, "Download successful"); + } + + // No need to refresh image url + break; + } else { + // Download scale image + String imageUrl = ipp.imageUrl; + + //Check 509 gif + boolean get509 = false; + int length = URL_ARRAY_509.length; + for (int j = 0; j < length; j++) { + if (URL_ARRAY_509[j].equals(imageUrl)) { + // Get 509 gif here + get509 = true; + break; + } + } + if (get509) { + show509Notification(mContext); + break; + } + + // Guess filename + String filename = EhUtils.getImageFilename(targetIndex, Utils.getExtension(imageUrl, "jpg")); + // Don't put into mImageFilenameArray, because extension maybe be wrong + // mImageFilenameArray.set(targetIndex, filename); + hh.reset(); + if (hh.downloadEhImage(imageUrl, mDir, filename, false, c, listener) == null) { + hh.reset(); + if (hh.downloadEhImage(imageUrl, mDir, filename, true, c, listener) == null) { + if (i == 1) { + // TODO download error + Log.d(TAG, "download error"); + } + } else { + // download ok + Log.d(TAG, "download ok proxy"); + break; + } + } else { + // download ok + Log.d(TAG, "download ok"); + break; + } + } + } else { + // TODO parser error, Do somthing + Log.d(TAG, "parser error"); + break; + } + } + + // Remove from requsting set + mRequstingIndexSet.remove(targetIndex); + } + + // Worker over + synchronized(mWorkerArray) { + // Remove from mWorkerArray + mWorkerArray[mIndex] = null; + mControlorArray[mIndex] = null; + + boolean allNull = true; + for (Worker w : mWorkerArray) + if (w != null) {allNull = false; break;} + + synchronized (mDownloadLock) { + if (allNull && mDownloadMode && mLfd != null && mDownloadIndexSet != null) { + mLfd.onDownloadOver(mGid, + getMaxEnsureIndex() + 1 - mDownloadIndexSet.size()); + } + } + } + Log.d(TAG, "ExDownloader Worker over"); + } + } + + public class HttpDownloadListener implements HttpHelper.OnDownloadListener { + + public int index; + private float lastPercent; + private int lastDownloadSize; + + @Override + public void onDownloadStartConnect() { + lastPercent = 0.0f; + lastDownloadSize = 0; + + if (mLfis != null) + mLfis.onDownloadStart(index); + } + + @Override + public void onDownloadStartDownload(int totalSize) { + lastPercent = 0.0f; + lastDownloadSize = 0; + } + + @Override + public void onDownloadStatusUpdate(int downloadSize, int totalSize) { + if (totalSize == -1) + return; + + float percent = (float)downloadSize / totalSize; + if (percent - lastPercent >= 0.05f) { + lastPercent = percent; + + if (mLfis != null) + mLfis.onDownloading(index, percent); + } + + // Update download size + int accumulateSize = downloadSize - lastDownloadSize; + lastDownloadSize = downloadSize; + mAccumulateSize += accumulateSize; + long curTime = System.currentTimeMillis(); + long interval = curTime - mLastUpdateSpeedTime; + if (interval > UPDATE_SPEED_INTERVAL) { + if (mDownloadMode && mLfd != null) + mLfd.onUpdateSpeed(mGid, (int)(mAccumulateSize * 1000 / interval)); + mLastUpdateSpeedTime = curTime; + mAccumulateSize = 0; + } + } + + @Override + public void onDownloadOver(int status, String eMsg) { + if (status == HttpHelper.DOWNLOAD_OK_CODE) { + mRequstingIndexSet.remove(index); + + updateDownload(index); + } + + if (mLfis != null && status == HttpHelper.DOWNLOAD_OK_CODE) + mLfis.onDownloadComplete(index); + else if (mLfis != null && status == HttpHelper.DOWNLOAD_FAIL_CODE) + mLfis.onDownloadFail(index); + + lastPercent = 0.0f; + lastDownloadSize = 0; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ehclient/ExDownloaderManager.java b/app/src/main/java/com/hippo/ehviewer/ehclient/ExDownloaderManager.java new file mode 100644 index 000000000..d4c5dc290 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ehclient/ExDownloaderManager.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ehclient; + +import android.content.Context; +import android.util.SparseArray; + +public class ExDownloaderManager { + + private static final String TAG = ExDownloaderManager.class.getSimpleName(); + + private final Context mContext; + private final SparseArray mExDownloadList; + + private static ExDownloaderManager sInstance; + + public static void createInstance(Context context) { + sInstance = new ExDownloaderManager(context.getApplicationContext()); + } + + public static ExDownloaderManager getInstance() { + return sInstance; + } + + private ExDownloaderManager(Context context) { + mContext = context; + mExDownloadList = new SparseArray(); + } + + public synchronized ExDownloader getExDownloader(int gid, String token, String title, int mode) { + ExDownloader exDownloader = mExDownloadList.get(gid); + if (exDownloader == null) { + exDownloader = new ExDownloader(mContext, gid, token, title, mode); + mExDownloadList.append(gid, exDownloader); + } + exDownloader.occupy(); + return exDownloader; + } + + public synchronized void freeExDownloader(ExDownloader exDownloader) { + exDownloader.free(); + if (exDownloader.isOrphans()) { + exDownloader.stop(); + mExDownloadList.remove(exDownloader.getGid()); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ehclient/ImagePageParser.java b/app/src/main/java/com/hippo/ehviewer/ehclient/ImagePageParser.java new file mode 100644 index 000000000..16b13def7 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ehclient/ImagePageParser.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ehclient; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.hippo.ehviewer.util.Utils; + +public class ImagePageParser { + + public String imageUrl; + public String originalImageUrl; + public int gid; + public String token; + + public void reset() { + imageUrl = null; + originalImageUrl = null; + gid = 0; + token = null; + } + + public boolean parser(String body, int mode) { + Pattern p; + Matcher m; + + if (body == null) + return false; + + if (mode ==EhClient.MODE_LOFI) { + p = Pattern.compile("]+>"); + m = p.matcher(body); + if (m.find()) + imageUrl = Utils.unescapeXml(m.group(1)); + else + return false; + + p = Pattern.compile("
    Back"); + m = p.matcher(body); + if (m.find()) { + gid = Integer.parseInt(m.group(1)); + token = m.group(2); + return true; + } else { + return false; + } + + } else { + p = Pattern.compile("
    .+?
    "); + m = p.matcher(body); + if (m.find()) { + originalImageUrl = Utils.unescapeXml( + new StringBuilder(m.group(1)).append("fullimg.php").append(m.group(2)).toString()); + } + return true; + } else { + return false; + } + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ehclient/ListParser.java b/app/src/main/java/com/hippo/ehviewer/ehclient/ListParser.java new file mode 100644 index 000000000..a48bf7df9 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ehclient/ListParser.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ehclient; + +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.hippo.ehviewer.data.GalleryInfo; +import com.hippo.ehviewer.data.LofiGalleryInfo; +import com.hippo.ehviewer.util.EhUtils; +import com.hippo.ehviewer.util.Utils; + +public class ListParser { + + public static final int TARGET_PAGE_IS_LAST = -1; + + public static final int ALL = 0x1; + public static final int NOT_FOUND = 0x2; + public static final int PARSER_ERROR = -1; + public static final int INDEX_ERROR = -2; + + public int pageNum; + + public ArrayList giList; + + /** + * If NOT_FOUND maxPage is 0, + * if can not get maxPage, set it Integer.MAX_VALUE + * + * @param pageContext + * @param mode + * @return + */ + public int parser(String body, int mode) { + + Pattern p; + Matcher m; + + if (body == null) + return 0; + + giList = new ArrayList(25); + + switch (mode) { + case EhClient.MODE_LOFI: + p = Pattern.compile("" // detail url + + "" // thumb url + + ".+?(.+?)" // title + + ".+?Posted:(.+?)" // Posted and uploader + + "Category:(.+?)" // Category + + "Tags:(.+?)" // Tags + + "Rating:(.+?)"); // rating + m = p.matcher(body); + DetailUrlParser dup = new DetailUrlParser(); + while (m.find()) { + LofiGalleryInfo lgi = new LofiGalleryInfo(); + String[] pau = new String[2]; + + if (dup.parser(m.group(1))) { + lgi.gid = dup.gid; + lgi.token = dup.token; + } else { + continue; + } + lgi.thumb = Utils.unescapeXml(m.group(2)); + lgi.title = Utils.unescapeXml(m.group(3)); + if (getPostedAndUploader(m.group(4), pau)) { + lgi.posted = pau[0]; + lgi.uploader = pau[1]; + } else { + continue; + } + lgi.category = EhUtils.getCategory(m.group(5)); + String tags = m.group(6); + if (tags.equals("-")) + lgi.lofiTags = null; + else + lgi.lofiTags = tags.split(", "); + String rating = m.group(7); + if (rating.equals("-")) + lgi.rating = Float.NaN; + else + lgi.rating = getStartNum(rating); + lgi.generateSLang(); + + giList.add(lgi); + } + + if (giList.size() == 0) { + if (body.contains("No hits found
    ")) { + pageNum = 0; + return NOT_FOUND; + } else if (body.contains("No more hits found
    ")) { + return INDEX_ERROR; + } else { + return PARSER_ERROR; + } + } else { + if (!body.contains("Next Page >")) + pageNum = TARGET_PAGE_IS_LAST; + else + pageNum = Integer.MAX_VALUE; + return ALL; + } + + case EhClient.MODE_G: + case EhClient.MODE_EX: + default: + p = Pattern.compile("]+>([\\d]+)]+>(?:]+>)?&"); + m = p.matcher(body); + if (m.find()) { + pageNum = Integer.parseInt(m.group(1)); + } else if (body.contains("No hits found

    ")) { + pageNum = 0; + return NOT_FOUND; + } else { + return PARSER_ERROR; + } + + p = Pattern.compile("(?:)?(?:)?" // category + + "(.+?)" // posted + + "" + + "(?:" + + "|init~([^<>\"~]+~[^<>\"~]+)~([^<>]+))" // thumb and title + + "
    " + + ".+?" + + "" // url and title + + ".+?" + + "
    \"]+)\">" // rating + + ".+?" + + "
    (.+?)"); // uploader + m = p.matcher(body); + while (m.find()) { + GalleryInfo gi = new GalleryInfo(); + + gi.category = EhUtils.getCategory(m.group(1)); + gi.posted = m.group(2); + gi.thumbHeight = Integer.parseInt(m.group(3)); + gi.thumbWidth = Integer.parseInt(m.group(4)); + + if (m.group(5) == null) { + gi.thumb = Utils.unescapeXml("http://" + + m.group(7).replace('~', '/')); + gi.title = Utils.unescapeXml(m.group(8)); + } else { + gi.thumb = Utils.unescapeXml(m.group(5)); + gi.title = Utils.unescapeXml(m.group(6)); + } + + Pattern pattern = Pattern + .compile("/(\\d+)/(\\w+)"); + Matcher matcher = pattern.matcher(m.group(9)); + if (matcher.find()) { + gi.gid = Integer.parseInt(matcher.group(1)); + gi.token = matcher.group(2); + } else + continue; + + gi.rating = Float.parseFloat(getRate(m.group(11))); + gi.uploader = m.group(12); + gi.generateSLang(); + + giList.add(gi); + } + return ALL; + } + } + + private String getRate(String rawRate) { + Pattern p = Pattern.compile("\\d+px"); + Matcher m = p.matcher(rawRate); + int num1; + int num2; + int rate = 5; + String re; + if (m.find()) + num1 = Integer.parseInt(m.group().replace("px", "")); + else + return null; + if (m.find()) + num2 = Integer.parseInt(m.group().replace("px", "")); + else + return null; + rate = rate - num1 / 16; + if (num2 == 21) { + rate--; + re = Integer.toString(rate); + re = re + ".5"; + } else + re = Integer.toString(rate); + return re; + } + + private static final String PAU_SPACER = " by "; + + private boolean getPostedAndUploader(String raw, String[] pau) { + int index = raw.indexOf(PAU_SPACER); + if (index == -1) { + return false; + } else { + pau[0] = raw.substring(0, index); + pau[1] = raw.substring(index + PAU_SPACER.length()); + return true; + } + } + + private int getStartNum(String str) { + int startNum = 0; + for (int i = 0; i < str.length(); i++) { + if (str.charAt(i) == '*') + startNum++; + } + return startNum; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ehclient/LofiDetailParser.java b/app/src/main/java/com/hippo/ehviewer/ehclient/LofiDetailParser.java new file mode 100644 index 000000000..5c9848cb9 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ehclient/LofiDetailParser.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ehclient; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.hippo.ehviewer.data.LargePreviewList; + + +public class LofiDetailParser { + + public boolean isLastPage; + public LargePreviewList preview; + public String emesg; + + public boolean parser(String body) { + Pattern p; + Matcher m; + + if (body == null) + return false; + + // Get preview + p = Pattern.compile("
    ]+>]+href=\"([^>\"]+)\"[^>]+>]+src=\"([^>\"]+)\"[^>]+>"); + m = p.matcher(body); + while (m.find()) { + if (preview == null) + preview = new LargePreviewList(); + preview.addItem(m.group(2), m.group(1)); + } + // Check is last page + p = Pattern.compile("]+>Next Page >"); + m = p.matcher(body); + if (m.find()) + isLastPage = false; + else + isLastPage = true; + + return preview != null; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ehclient/LoginParser.java b/app/src/main/java/com/hippo/ehviewer/ehclient/LoginParser.java new file mode 100644 index 000000000..8877a310c --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ehclient/LoginParser.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ehclient; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class LoginParser { + public String displayname; + public String eMesg; + + public boolean parser(String body) { + Pattern p; + Matcher m; + + if (body == null) + return false; + + p = Pattern.compile("

    You are now logged in as: (.+?)
    "); + m = p.matcher(body); + if (m.find()) { + displayname = m.group(1); + return true; + } else { + p = Pattern.compile("(?:

    The error returned was:

    \\s*

    (.+?)

    )" + + "|(?:(.+?))"); + m = p.matcher(body); + + if (m.find()) + eMesg = m.group(1) == null ? m.group(2) : m.group(1); + else + eMesg = "Unknown error"; + + return false; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ehclient/RateParser.java b/app/src/main/java/com/hippo/ehviewer/ehclient/RateParser.java new file mode 100644 index 000000000..83a82832a --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ehclient/RateParser.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ehclient; + +import org.json.JSONException; +import org.json.JSONObject; + +public class RateParser { + + public float mRatingAvg; + public int mRatingCnt; + + boolean parser(String body) { + if (body == null) + return false; + + try { + JSONObject jsonObject = new JSONObject(body); + mRatingAvg = (float)jsonObject.getDouble("rating_avg"); + mRatingCnt = jsonObject.getInt("rating_cnt"); + return true; + } catch (JSONException e) { + return false; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ehclient/VoteParser.java b/app/src/main/java/com/hippo/ehviewer/ehclient/VoteParser.java new file mode 100644 index 000000000..4de217429 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ehclient/VoteParser.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ehclient; + +import org.json.JSONException; +import org.json.JSONObject; + +public class VoteParser { + + public String mTagPane; + + boolean parser(String body) { + if (body == null) + return false; + + try { + JSONObject jsonObject = new JSONObject(body); + mTagPane = jsonObject.getString("tagpane"); + return true; + } catch (JSONException e) { + return false; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/exception/FileException.java b/app/src/main/java/com/hippo/ehviewer/exception/FileException.java new file mode 100644 index 000000000..f021b8011 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/exception/FileException.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.exception; + +public class FileException extends Exception { + + public static final int MKDIR_ERROR = 0x0; + public static final int CREATE_FILE_ERROR = 0x1; + public static final int NOT_DIR = 0x2; + public static final int NOT_FILE = 0x3; + public static final int CANNOT_EXECUTE = 0x4; + public static final int CANNOT_READ = 0x5; + public static final int CANNOT_WRITE = 0x6; + + private int mErrorCode; + + private static final long serialVersionUID = 1L; + + public FileException(int errorCode) { + mErrorCode = errorCode; + } + + public int getErrorCode() { + return mErrorCode; + } + + + // TODO + public String getMessage() { + switch (mErrorCode) { + case MKDIR_ERROR: + return "mkdir error"; + case CREATE_FILE_ERROR: + return "create file error"; + case NOT_DIR: + return "it is not a dir"; + case NOT_FILE: + return "it is not a file"; + case CANNOT_EXECUTE: + return "can not execute"; + case CANNOT_READ: + return "can not read"; + case CANNOT_WRITE: + return "can not write"; + default: + return "unknown error"; + } + } + +} diff --git a/app/src/main/java/com/hippo/ehviewer/exception/NoSdCardException.java b/app/src/main/java/com/hippo/ehviewer/exception/NoSdCardException.java new file mode 100644 index 000000000..a2354de30 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/exception/NoSdCardException.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.exception; + +public class NoSdCardException extends Exception { + private static final long serialVersionUID = 1L; +} diff --git a/app/src/main/java/com/hippo/ehviewer/exception/ParserException.java b/app/src/main/java/com/hippo/ehviewer/exception/ParserException.java new file mode 100644 index 000000000..53ed894ae --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/exception/ParserException.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.exception; + +public class ParserException extends Exception{ + + private static final long serialVersionUID = 1L; + +} diff --git a/app/src/main/java/com/hippo/ehviewer/exception/StopRequestException.java b/app/src/main/java/com/hippo/ehviewer/exception/StopRequestException.java new file mode 100644 index 000000000..055f35a01 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/exception/StopRequestException.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.exception; + +public class StopRequestException extends Exception { + private static final long serialVersionUID = 1L; +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/GalleryView.java b/app/src/main/java/com/hippo/ehviewer/gallery/GalleryView.java new file mode 100644 index 000000000..21d4a4585 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/GalleryView.java @@ -0,0 +1,1969 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery; + +import java.util.Arrays; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Movie; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.RectF; +import android.view.MotionEvent; + +import com.hippo.ehviewer.AppHandler; +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.gallery.data.ImageSet; +import com.hippo.ehviewer.gallery.glrenderer.BasicTexture; +import com.hippo.ehviewer.gallery.glrenderer.BitmapTexture; +import com.hippo.ehviewer.gallery.glrenderer.GLCanvas; +import com.hippo.ehviewer.gallery.glrenderer.GLPaint; +import com.hippo.ehviewer.gallery.glrenderer.ImageTexture; +import com.hippo.ehviewer.gallery.glrenderer.MovieTexture; +import com.hippo.ehviewer.gallery.glrenderer.StringTexture; +import com.hippo.ehviewer.gallery.glrenderer.Uploaded; +import com.hippo.ehviewer.gallery.image.Image; +import com.hippo.ehviewer.gallery.ui.GLRoot; +import com.hippo.ehviewer.gallery.ui.GLView; +import com.hippo.ehviewer.gallery.ui.GestureRecognizer; +import com.hippo.ehviewer.gallery.ui.SynchronizedHandler; +import com.hippo.ehviewer.gallery.ui.TimeRunner; +import com.hippo.ehviewer.gallery.util.GalleryUtils; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.Constants; +import com.hippo.ehviewer.util.MathUtils; +import com.hippo.ehviewer.util.Ui; +import com.hippo.ehviewer.util.Utils; +import com.hippo.ehviewer.util.ZInterpolator; +import com.hippo.ehviewer.widget.MaterialToast; + +public class GalleryView extends GLView implements ImageSet.ImageListener, + Runnable { + @SuppressWarnings("unused") + private static final String TAG = GalleryView.class.getSimpleName(); + + public static final int INVALID_ID = -1; + + private static final int STATE_FIRST = 0x1; + private static final int STATE_LAST = 0x2; + private static final int STATE_NONE = 0x0; + + // scale mode + private static final int ORGIN = 0; + private static final int FIT_WIDTH = 1; + private static final int FIT_HEIGHT = 2; + private static final int FIT = 3; + private static final int FIXED = 4; + + // Reading direction + private static final int DIRECTION_LEFT_RIGHT = 0; + private static final int DIRECTION_RIGHT_LEFT = 1; + private static final int DIRECTION_TOP_BOTTOM = 2; + private static final int DIRECTION_BOTTOM_TOP = 3; + + // start position mode + private static final int TOP_LEFT = 0; + private static final int TOP_RIGHT = 1; + private static final int BOTTOM_LEFT = 2; + private static final int BOTTOM_RIGHT = 3; + private static final int CENTER = 4; + + // Scroll state + private static final int SCROLL_NONE = 0; + private static final int SCROLL_PRE = 1; + private static final int SCROLL_NEXT = 2; + private static final int SCROLL_ANIME_PRE = 3; + private static final int SCROLL_ANIME_NEXT = 4; + + private static final int PRE_TARGET_INDEX = 0; + private static final int CUR_TARGET_INDEX = 1; + private static final int NEXT_TARGET_INDEX = 2; + private static final int TARGET_INDEX_SIZE = 3; + + // Area + private static final float[] LEFT_AREA_SCALE = { 0, 0, 2 / 7.0f, 1 }; + private static final float[] TOP_AREA_SCALE = { 2 / 7.0f, 0, 5 / 7.0f, + 3 / 8.0f }; + private static final float[] RIGHT_AREA_SCALE = { 5 / 7.0f, 0, 1, 1 }; + private static final float[] BOTTOM_AREA_SCALE = { 2 / 7.0f, 5 / 8.0f, + 5 / 7.0f, 1 }; + private static final float[] CENTER_AREA_SCALE = { 2 / 7.0f, 3 / 8.0f, + 5 / 7.0f, 5 / 8.0f }; + private static final float[] LIGHTNESS_SLIDING_AREA = { + 0.0f, 0.0f, 0.166f, 1.0f }; + + private static final float MILLSEC_PER_DIX = 0.2f; + private static final float CHANGE_PAGE_PROPORTION = 0.1f; + private static final float BORDER_TIP_SENSITIVITY = Ui.dp2pix(48); + + private static final int ANIMATE_MIN = 200; + + // TODO 根据图片的大小调整缩放界限 + private static final float SCALE_MIN = 1 / 10.0f; + private static final float SCALE_MAX = 10.0f; + + private static final float LINE_WIDTH = Ui.dp2pix(3); + private static final int LINE_COLOR = -1; // White + private static final int TAP_AREA_TEXT_COLOR = -1; // White + private static final int TAP_AREA_TEXT_SIZE = Ui.dp2pix(24); + private static final int TAP_AREA_MASK_COLOR = 0x88000000; + + private static final int BACKGROUND_COLOR = 0xff212121; + @SuppressWarnings("unused") + private static final int MASK_COLOR = 0x88000000; + + private GestureRecognizer mGestureRecognizer; + private final Context mContext; + + private final ImageSet mImageSet; + private int mState; + + private int mReadingDirection; + private int mPageScaling; + private int mStartPosition; + + private int mScrollState = SCROLL_NONE; + private int scrollXOffset = 0; + private int scrollYOffset = 0; + private int stopScrollXOffset = 0; + private int stopScrollYOffset = 0; + private float mScale = 1; + + private int mScreenWidth = -1; + private int mScreenHeight = -1; + + private final int mSumOffsetXStore[]; + private final int mSumOffsetYStore[]; + private final int mToNextOffsetXStore[]; + private final int mToNextOffsetYStore[]; + + // at most keep three item + private volatile ShowItem[] showItems; + private int mCurIndex; + private int mSize; + private boolean isEnsureSize; + private boolean mInit = false; + + private final int[] leftArea; + private final int[] topArea; + private final int[] rightArea; + private final int[] bottomArea; + private final int[] centerArea; + private final int[] mLightnessSlidingArea; + + private final Text leftText; + private final Text topText; + private final Text rightText; + private final Text bottomText; + private final Text centerText; + + private boolean mDoubleTapAnimating = false; + + private final boolean mLightnessSliding = false; + + private final boolean mShowTapArea = false; + private boolean mShowTapAreaTurn = false; + + private OnTapTextListener mOnTapTextListener; + private GalleryViewListener mGalleryViewListener; + + public interface GalleryViewListener { + public void onTapCenter(); + + public void onPageChanged(int index); + + public void onSizeUpdate(int size); + + public void onSlideBottom(float dx); + + public void onSlideBottomOver(); + + public void onRightToLeftChanged(boolean value); + } + + public interface OnTapTextListener { + public void onTapText(int index); + + public void onTapDoubleText(int index); + } + + public void setOnTapTextListener(OnTapTextListener l) { + mOnTapTextListener = l; + } + + public void setGalleryViewListener(GalleryViewListener l) { + mGalleryViewListener = l; + } + + public void setReadingDirection(int value) { + // TODO It is not safe when scroll + boolean oldIsRTL = isRightToLeft(); + mReadingDirection = value; + boolean newIsRTL = isRightToLeft(); + if (oldIsRTL != newIsRTL && mGalleryViewListener != null) + mGalleryViewListener.onRightToLeftChanged(newIsRTL); + } + + public void setPageScaling(int value) { + mPageScaling = value; + } + + public void setStartPosition(int value) { + mStartPosition = value; + } + + public GalleryView(Context context, ImageSet imageSet, int startIndex) { + + mSumOffsetXStore = new int[12]; + mSumOffsetYStore = new int[12]; + mToNextOffsetXStore = new int[4]; + mToNextOffsetYStore = new int[4]; + Arrays.fill(mSumOffsetXStore, 0, 12, 0); + Arrays.fill(mSumOffsetYStore, 0, 12, 0); + Arrays.fill(mToNextOffsetXStore, 0, 4, 0); + Arrays.fill(mToNextOffsetYStore, 0, 4, 0); + + leftArea = new int[4]; + topArea = new int[4]; + rightArea = new int[4]; + bottomArea = new int[4]; + centerArea = new int[4]; + mLightnessSlidingArea = new int[4]; + + leftText = new Text(context.getString(R.string.pre_page), + TAP_AREA_TEXT_SIZE, TAP_AREA_TEXT_COLOR); + topText = new Text(context.getString(R.string.zoom_in), + TAP_AREA_TEXT_SIZE, TAP_AREA_TEXT_COLOR); + rightText = new Text(context.getString(R.string.next_page), + TAP_AREA_TEXT_SIZE, TAP_AREA_TEXT_COLOR); + bottomText = new Text(context.getString(R.string.zoom_out), + TAP_AREA_TEXT_SIZE, TAP_AREA_TEXT_COLOR); + centerText = new Text(context.getString(R.string.menu), + TAP_AREA_TEXT_SIZE, TAP_AREA_TEXT_COLOR); + + mContext = context; + mImageSet = imageSet; + mCurIndex = startIndex; + + showItems = new ShowItem[TARGET_INDEX_SIZE]; + + checkSize(); + + // adjust mCurIndex + mCurIndex = MathUtils.clamp(mCurIndex, 0, mSize - 1); + + setState(); + + setBackgroundColor(GalleryUtils + .intColorToFloatARGBArray(BACKGROUND_COLOR)); + + // Init config + mReadingDirection = Config.getReadingDirection(); + mPageScaling = Config.getPageScaling(); + mStartPosition = Config.getStartPosition(); + + mImageSet.setImageListener(this); + + // Start task to check size + AppHandler.getInstance().postDelayed(this, 500); + + // Try to make current index show image + if (mCurIndex != startIndex) + mImageSet.addTargetIndex(mCurIndex); + + // First time to load image in onLayout() + } + + // The task to update size + @Override + public void run() { + checkSize(); + setState(); + if (!isEnsureSize && !mImageSet.isStop()) + AppHandler.getInstance().postDelayed(this, 500); + } + + /** + * Check size + */ + private void checkSize() { + int oldSize = mSize; + int size = mImageSet.getSize(); + if (size == -1) { + isEnsureSize = false; + mSize = mImageSet.getEnsureSize(); + } else { + isEnsureSize = true; + mSize = size; + } + + if (mSize > oldSize && mCurIndex == oldSize - 1 + && showItems[NEXT_TARGET_INDEX] == null) { + mState = STATE_NONE; + loadImage(mCurIndex + 1); + } + + if (mSize != oldSize && mGalleryViewListener != null) + mGalleryViewListener.onSizeUpdate(mSize); + } + + /** + * Set current page state, first page or last page + */ + private void setState() { + mState = STATE_NONE; + if (mCurIndex == 0) + mState = STATE_FIRST; + if (mCurIndex == mSize - 1) + mState = STATE_LAST; + } + + private boolean isIndexLoadable(int index) { + int targetIndex = index - mCurIndex + 1; + return targetIndex == CUR_TARGET_INDEX + || (mState != STATE_FIRST && targetIndex == PRE_TARGET_INDEX) + || (mState != STATE_LAST && targetIndex == NEXT_TARGET_INDEX); + } + + private boolean isIndexValid(int index) { + return index >= 0 && index < mSize; + } + + @Override + public void onGetImage(int index) { + if (isIndexLoadable(index)) { + loadImage(index); + } + } + + @Override + public synchronized void onDownloading(int index, float percent) { + int targetIndex = index - mCurIndex + 1; + if (isIndexLoadable(index)) { + // Free what do not need + if (showItems[targetIndex] != null) + showItems[targetIndex].recycle(); + + showItems[targetIndex] = new Text(String.format( + mContext.getString(R.string.loading_percent), + Math.round(percent * 100))); + resetSizePosition(targetIndex); + } + } + + @Override + public synchronized void onDecodeOver(int index, Object res) { + int targetIndex = index - mCurIndex + 1; + + if (!isIndexLoadable(index)) { + // If it do not need any more, free + if (res != null && res instanceof Bitmap) + ((Bitmap) res).recycle(); + } else { + // Free what do not need + if (showItems[targetIndex] != null) + showItems[targetIndex].recycle(); + + if (res == null) { + showItems[targetIndex] = new Text( + mContext.getString(R.string.read_image_error)); + } else { + if (res instanceof Bitmap) { + BitmapItem bi = new BitmapItem(); + bi.load((Bitmap) res); + showItems[targetIndex] = bi; + } else if (res instanceof Image) { + ImageItem ii = new ImageItem(); + ii.load((Image) res); + showItems[targetIndex] = ii; + } else { + MovieItem mi = new MovieItem(); + mi.load((Movie) res); + showItems[targetIndex] = mi; + } + } + resetSizePosition(targetIndex); + } + } + + public int getCurIndex() { + return mCurIndex; + } + + public int getSize() { + return mSize; + } + + private void drawTapArea(GLCanvas canvas) { + // Background + canvas.fillRect(0, 0, mScreenWidth, mScreenHeight, TAP_AREA_MASK_COLOR); + + drawRect(canvas, leftArea); + drawRect(canvas, topArea); + drawRect(canvas, rightArea); + drawRect(canvas, bottomArea); + drawRect(canvas, centerArea); + leftText.draw(canvas); + topText.draw(canvas); + rightText.draw(canvas); + bottomText.draw(canvas); + centerText.draw(canvas); + } + + private void drawRect(GLCanvas canvas, int[] rect) { + + int left = rect[0]; + int top = rect[1]; + int right = rect[2]; + int bottom = rect[3]; + + GLPaint paint = new GLPaint(LINE_COLOR, LINE_WIDTH); + canvas.drawLine(left, top, right, top, paint); + canvas.drawLine(left, bottom, right, bottom, paint); + canvas.drawLine(left, top, left, bottom, paint); + canvas.drawLine(right, top, right, bottom, paint); + } + + @Override + protected synchronized void render(GLCanvas canvas) { + // If it is not render thread, do not render + super.render(canvas); + + // First of all, upload if needed + for (int i = 0; i < TARGET_INDEX_SIZE; i++) { + ShowItem item = showItems[i]; + if (item instanceof UploadedItem) { + UploadedItem ui = (UploadedItem) item; + if (!ui.isContentValid()) + ui.updateContent(canvas); + } + } + + boolean needRefresh = false; + ShowItem item; + switch (mScrollState) { + case SCROLL_NONE: + item = showItems[CUR_TARGET_INDEX]; + if (item != null) + item.draw(canvas); + if (item instanceof ImageItem && ((ImageItem) item).isAnimated()) + needRefresh |= true; + if (item instanceof MovieItem) + needRefresh |= true; + break; + + case SCROLL_PRE: + case SCROLL_ANIME_PRE: + if (scrollXOffset != 0 || scrollYOffset != 0) { + item = showItems[PRE_TARGET_INDEX]; + if (item != null) + item.draw(canvas, scrollXOffset, scrollYOffset); + if (item instanceof ImageItem && ((ImageItem) item).isAnimated()) + needRefresh |= true; + if (item instanceof MovieItem) + needRefresh |= true; + } + + item = showItems[CUR_TARGET_INDEX]; + if (item != null) + item.draw(canvas, scrollXOffset, scrollYOffset); + if (item instanceof ImageItem && ((ImageItem) item).isAnimated()) + needRefresh |= true; + if (item instanceof MovieItem) + needRefresh |= true; + break; + + case SCROLL_NEXT: + case SCROLL_ANIME_NEXT: + item = showItems[CUR_TARGET_INDEX]; + if (item != null) + item.draw(canvas, scrollXOffset, scrollYOffset); + if (item instanceof ImageItem && ((ImageItem) item).isAnimated()) + needRefresh |= true; + if (item instanceof MovieItem) + needRefresh |= true; + + if (scrollXOffset != 0 || scrollYOffset != 0) { + item = showItems[NEXT_TARGET_INDEX]; + if (item != null) + item.draw(canvas, scrollXOffset, scrollYOffset); + if (item instanceof ImageItem && ((ImageItem) item).isAnimated()) + needRefresh |= true; + if (item instanceof MovieItem) + needRefresh |= true; + } + break; + } + + if (needRefresh) + invalidate(); + } + + private void resetSizePosition() { + resetSizePosition(PRE_TARGET_INDEX); + resetSizePosition(CUR_TARGET_INDEX); + resetSizePosition(NEXT_TARGET_INDEX); + } + + /** + * Reset target index show item position base on mReadingDirection + * or current next previous + * + * @param targetIndex + */ + private void resetSizePosition(int targetIndex) { + ShowItem showItem = showItems[targetIndex]; + if (showItem == null) + return; + + int index = mReadingDirection * TARGET_INDEX_SIZE + targetIndex; + int sumXOffset = mSumOffsetXStore[index]; + int sumYOffset = mSumOffsetYStore[index]; + + if (showItem instanceof Text) { + // Make text in center + Text text = (Text) showItem; + int xOffset; + int yOffset; + Rect rect = text.mRect; + xOffset = (mScreenWidth - text.width) / 2; + yOffset = (mScreenHeight - text.height) / 2; + rect.left = sumXOffset + xOffset; + rect.top = sumYOffset + yOffset; + rect.right = rect.left + text.width; + rect.bottom = rect.top + text.height; + } else if (showItem instanceof BasicItem) { + BasicItem image = (BasicItem) showItem; + + // Set scale + int showWidth = 0; + int showHeight = 0; + Rect rect = image.mRect; + switch (mPageScaling) { + case ORGIN: + image.imageScale = 1; + showWidth = image.width; + showHeight = image.height; + break; + case FIT_WIDTH: + image.imageScale = (float) mScreenWidth / image.width; + showWidth = mScreenWidth; + showHeight = (int) (image.height * image.imageScale); + break; + case FIT_HEIGHT: + image.imageScale = (float) mScreenHeight / image.height; + showWidth = (int) (image.width * image.imageScale); + showHeight = mScreenHeight; + break; + case FIT: + float scaleX = (float) mScreenWidth / image.width; + float scaleY = (float) mScreenHeight / image.height; + if (scaleX < scaleY) { + image.imageScale = scaleX; + showWidth = mScreenWidth; + showHeight = (int) (image.height * image.imageScale); + } else { + image.imageScale = scaleY; + showWidth = (int) (image.width * image.imageScale); + showHeight = mScreenHeight; + break; + } + break; + case FIXED: + default: + image.imageScale = mScale; + showWidth = (int) (image.width * mScale); + showHeight = (int) (image.height * mScale); + break; + } + + // adjust scale + if (image.imageScale < SCALE_MIN) { + image.imageScale = SCALE_MIN; + showWidth = (int) (image.width * SCALE_MIN); + showHeight = (int) (image.height * SCALE_MIN); + } else if (image.imageScale > SCALE_MAX) { + image.imageScale = SCALE_MAX; + showWidth = (int) (image.width * SCALE_MAX); + showHeight = (int) (image.height * SCALE_MAX); + } + + // set start position + int xOffset; + int yOffset; + switch (mStartPosition) { + case TOP_LEFT: + xOffset = 0; + yOffset = 0; + break; + case TOP_RIGHT: + xOffset = mScreenWidth - showWidth; + yOffset = 0; + break; + case BOTTOM_LEFT: + xOffset = 0; + yOffset = mScreenHeight - showHeight; + break; + case BOTTOM_RIGHT: + xOffset = mScreenWidth - showWidth; + yOffset = mScreenHeight - showHeight; + break; + case CENTER: + default: + xOffset = (mScreenWidth - showWidth) / 2; + yOffset = (mScreenHeight - showHeight) / 2; + break; + } + + rect.left = sumXOffset + xOffset; + rect.right = rect.left + showWidth; + rect.top = sumYOffset + yOffset; + rect.bottom = rect.top + showHeight; + + // adjust position + adjustPosition(image); + } + + invalidate(); + } + + private void setCenterInArea(int[] area, ShowItem showItem) { + int xOffset = ((area[2] - area[0]) - showItem.width) / 2; + int yOffset = ((area[3] - area[1]) - showItem.height) / 2; + + Rect rect = showItem.mRect; + + rect.left = area[0] + xOffset; + rect.right = rect.left + showItem.width; + rect.top = area[1] + yOffset; + rect.bottom = rect.top + showItem.height; + } + + private void setTapArea() { + for (int i = 0; i < 4; i++) { + int length = (i % 2 == 0 ? mScreenWidth : mScreenHeight); + leftArea[i] = (int) (LEFT_AREA_SCALE[i] * length); + topArea[i] = (int) (TOP_AREA_SCALE[i] * length); + rightArea[i] = (int) (RIGHT_AREA_SCALE[i] * length); + bottomArea[i] = (int) (BOTTOM_AREA_SCALE[i] * length); + centerArea[i] = (int) (CENTER_AREA_SCALE[i] * length); + mLightnessSlidingArea[i] = (int) (LIGHTNESS_SLIDING_AREA[i] * length); + } + + setCenterInArea(leftArea, leftText); + setCenterInArea(topArea, topText); + setCenterInArea(rightArea, rightText); + setCenterInArea(bottomArea, bottomText); + setCenterInArea(centerArea, centerText); + } + + @Override + protected void onLayout(boolean changeSize, int left, int top, int right, + int bottom) { + // Get View size + mScreenWidth = right - left; + mScreenHeight = bottom - top; + + // Fill store + mSumOffsetXStore[0] = -mScreenWidth; + mSumOffsetXStore[2] = mScreenWidth; + mSumOffsetXStore[3] = mScreenWidth; + mSumOffsetXStore[5] = -mScreenWidth; + + mSumOffsetYStore[6] = -mScreenHeight; + mSumOffsetYStore[8] = mScreenHeight; + mSumOffsetYStore[9] = mScreenHeight; + mSumOffsetYStore[11] = -mScreenHeight; + + mToNextOffsetXStore[DIRECTION_LEFT_RIGHT] = mScreenWidth; + mToNextOffsetXStore[DIRECTION_RIGHT_LEFT] = -mScreenWidth; + mToNextOffsetYStore[DIRECTION_TOP_BOTTOM] = mScreenHeight; + mToNextOffsetYStore[DIRECTION_BOTTOM_TOP] = -mScreenHeight; + + if (!mInit) { + mInit = true; + loadImage(mCurIndex); + if ((mState & STATE_LAST) == 0) + loadImage(mCurIndex + 1); + if ((mState & STATE_FIRST) == 0) + loadImage(mCurIndex - 1); + } else { + resetSizePosition(); + } + + // When screen orientation change, need to reset tap area + setTapArea(); + + invalidate(); + } + + /** + * Requset target image from ImageSet, + * + * @param index + */ + private synchronized void loadImage(int index) { + if (!isIndexLoadable(index)) + return; + + int targetIndex = index - mCurIndex + 1; + Object obj = mImageSet.getImage(index); + + if (showItems[targetIndex] != null) + showItems[targetIndex].recycle(); + + if (obj instanceof Integer) { + int state = (Integer) obj; + if (state == ImageSet.RESULT_DECODE) { + showItems[targetIndex] = null; + } else if (state == ImageSet.RESULT_DOWNLOADING) { + showItems[targetIndex] = new Text( + mContext.getString(R.string.downloading)); + } else if (state == ImageSet.RESULT_NONE) { + showItems[targetIndex] = new Text( + mContext.getString(R.string.not_loaded)); + } + } else if (obj instanceof Float) { + showItems[targetIndex] = new Text(String.format( + mContext.getString(R.string.loading_percent), + Math.round((Float) obj * 100))); + } + + resetSizePosition(targetIndex); + } + + /** + * If cur page is first page return true + * + * @return + */ + private boolean isFirstPage() { + return (mState & STATE_FIRST) != 0; + } + + /** + * If cur page is last page return true + * + * @return + */ + private boolean isLastPage() { + return (mState & STATE_LAST) != 0; + } + + /** + * + * @param mode + * true zoom in, false zoom out + * @return + */ + private boolean zoom(boolean mode) { + ShowItem curShowItem; + curShowItem = showItems[CUR_TARGET_INDEX]; + + if (curShowItem == null || !(curShowItem instanceof BasicItem)) + return false; + BasicItem image = (BasicItem) curShowItem; + float newScale; + if (mode) { + newScale = image.imageScale * 1.1f; + if (newScale == SCALE_MAX) + return false; + if (newScale > SCALE_MAX) + newScale = SCALE_MAX; + } else { + newScale = image.imageScale * 0.9f; + if (newScale == SCALE_MIN) + return false; + if (newScale < SCALE_MIN) + newScale = SCALE_MIN; + } + image.imageScale = newScale; + mScale = newScale; + + Rect rect = image.mRect; + int width = (int) (image.width * newScale); + int height = (int) (image.height * newScale); + int xOffset = (width - rect.width()) / 2; + int yOffset = (height - rect.height()) / 2; + + rect.set(rect.left - xOffset, rect.top - yOffset, rect.right + xOffset, + rect.bottom + yOffset); + + adjustPosition(curShowItem); + + invalidate(); + return true; + } + + /** + * You'd better resetSizePosition(PRE_TARGET_INDEX) before + * + * @return + */ + private synchronized boolean goToPrePage() { + if (isFirstPage()) + return false; + + ShowItem showItem; + if (showItems[NEXT_TARGET_INDEX] != null) { + showItems[NEXT_TARGET_INDEX].recycle(); + showItems[NEXT_TARGET_INDEX] = null; + } + showItems[NEXT_TARGET_INDEX] = showItems[CUR_TARGET_INDEX]; + showItems[CUR_TARGET_INDEX] = showItems[PRE_TARGET_INDEX]; + showItems[PRE_TARGET_INDEX] = null; + + // adjust rect + showItem = showItems[NEXT_TARGET_INDEX]; + if (showItem != null) + showItem.mRect.offset(mToNextOffsetXStore[mReadingDirection], mToNextOffsetYStore[mReadingDirection]); + showItem = showItems[CUR_TARGET_INDEX]; + if (showItem != null) + showItem.mRect.offset(mToNextOffsetXStore[mReadingDirection], mToNextOffsetYStore[mReadingDirection]); + + mCurIndex--; + setState(); + loadImage(mCurIndex - 1); + + mImageSet.setCurReadIndex(mCurIndex); + if (mGalleryViewListener != null) { + mGalleryViewListener.onPageChanged(mCurIndex); + } + + invalidate(); + return true; + } + + /** + * You'd better resetSizePosition(NEXT_TARGET_INDEX) before + * + * @return + */ + private synchronized boolean goToNextPage() { + if (isLastPage()) + return false; + + ShowItem showItem; + if (showItems[PRE_TARGET_INDEX] != null) { + showItems[PRE_TARGET_INDEX].recycle(); + showItems[PRE_TARGET_INDEX] = null; + } + showItems[PRE_TARGET_INDEX] = showItems[CUR_TARGET_INDEX]; + showItems[CUR_TARGET_INDEX] = showItems[NEXT_TARGET_INDEX]; + showItems[NEXT_TARGET_INDEX] = null; + // adjust rect + showItem = showItems[PRE_TARGET_INDEX]; + if (showItem != null) + showItem.mRect.offset(-mToNextOffsetXStore[mReadingDirection], -mToNextOffsetYStore[mReadingDirection]); + showItem = showItems[CUR_TARGET_INDEX]; + if (showItem != null) + showItem.mRect.offset(-mToNextOffsetXStore[mReadingDirection], -mToNextOffsetYStore[mReadingDirection]); + + mCurIndex++; + setState(); + loadImage(mCurIndex + 1); + + mImageSet.setCurReadIndex(mCurIndex); + if (mGalleryViewListener != null) { + mGalleryViewListener.onPageChanged(mCurIndex); + } + + invalidate(); + return true; + } + + public synchronized boolean goToPage(int index) { + if (!isIndexValid(index)) + return false; + + if (index == mCurIndex) { + return true; + } else if (index == mCurIndex - 1) { + return goToPrePage(); + } else if (index == mCurIndex + 1) { + return goToNextPage(); + } else { + // Free + if (showItems[PRE_TARGET_INDEX] != null) { + showItems[PRE_TARGET_INDEX].recycle(); + showItems[PRE_TARGET_INDEX] = null; + } + if (showItems[CUR_TARGET_INDEX] != null) { + showItems[CUR_TARGET_INDEX].recycle(); + showItems[CUR_TARGET_INDEX] = null; + } + if (showItems[NEXT_TARGET_INDEX] != null) { + showItems[NEXT_TARGET_INDEX].recycle(); + showItems[NEXT_TARGET_INDEX] = null; + } + + mCurIndex = index; + setState(); + loadImage(mCurIndex - 1); + loadImage(mCurIndex); + loadImage(mCurIndex + 1); + + mImageSet.setCurReadIndex(mCurIndex); + if (mGalleryViewListener != null) { + mGalleryViewListener.onPageChanged(mCurIndex); + } + + invalidate(); + return true; + } + } + + public synchronized void free() { + for (int i = 0; i < TARGET_INDEX_SIZE; i++) { + if (showItems[i] != null) { + showItems[i].recycle(); + showItems[i] = null; + } + } + } + + @Override + protected void onAttachToRoot(GLRoot root) { + super.onAttachToRoot(root); + mGestureRecognizer = new GestureRecognizer(mContext, + new MyGestureListener()); + } + + @Override + protected boolean onTouch(MotionEvent event) { + if (mGestureRecognizer != null) + mGestureRecognizer.onTouchEvent(event); + return true; + } + + public boolean isRightToLeft() { + if (mReadingDirection == DIRECTION_RIGHT_LEFT || mReadingDirection == DIRECTION_BOTTOM_TOP) + return true; + else + return false; + } + + private class MyGestureListener implements GestureRecognizer.Listener { + + private boolean isScale = false; + private final SynchronizedHandler mSynchronizedHandler; + + private final TimeRunner mToPreTimeRunner = new TimeRunner() { + @Override + protected void onStart() { + // Empty + } + + @Override + protected void onRun(float interpolatedTime, int runningTime) { + scrollXOffset = (int) (stopScrollXOffset + ((mToNextOffsetXStore[mReadingDirection] - stopScrollXOffset) * interpolatedTime)); + scrollYOffset = (int) (stopScrollYOffset + ((mToNextOffsetYStore[mReadingDirection] - stopScrollYOffset) * interpolatedTime)); + invalidate(); + } + + @Override + protected void onEnd() { + // Start animated image + ShowItem showItem; + for (int i = PRE_TARGET_INDEX; i < TARGET_INDEX_SIZE; i++) { + showItem = showItems[i]; + if (showItem instanceof ImageItem) + ((ImageItem) showItem).start(); + } + + scrollXOffset = 0; + scrollYOffset = 0; + goToPrePage(); + mScrollState = SCROLL_NONE; + } + + @Override + protected void onCancel() { + // Empty + } + }; + + private final TimeRunner mToNextTimeRunner = new TimeRunner() { + @Override + protected void onStart() { + // Empty + } + + @Override + protected void onRun(float interpolatedTime, int runningTime) { + scrollXOffset = (int) (stopScrollXOffset + ((-mToNextOffsetXStore[mReadingDirection] - stopScrollXOffset) * interpolatedTime)); + scrollYOffset = (int) (stopScrollYOffset + ((-mToNextOffsetYStore[mReadingDirection] - stopScrollYOffset) * interpolatedTime)); + invalidate(); + } + + @Override + protected void onEnd() { + // Start animated image + ShowItem showItem; + for (int i = PRE_TARGET_INDEX; i < TARGET_INDEX_SIZE; i++) { + showItem = showItems[i]; + if (showItem instanceof ImageItem) + ((ImageItem) showItem).start(); + } + + scrollXOffset = 0; + scrollYOffset = 0; + goToNextPage(); + mScrollState = SCROLL_NONE; + } + + @Override + protected void onCancel() { + // Empty + } + }; + + private final TimeRunner mReturnTimeRunner = new TimeRunner() { + @Override + protected void onStart() { + // Empty + } + + @Override + protected void onRun(float interpolatedTime, int runningTime) { + scrollXOffset = (int) ((1 - interpolatedTime) * stopScrollXOffset); + scrollYOffset = (int) ((1 - interpolatedTime) * stopScrollYOffset); + invalidate(); + } + + @Override + protected void onEnd() { + // Start animated image + ShowItem showItem; + for (int i = PRE_TARGET_INDEX; i < TARGET_INDEX_SIZE; i++) { + showItem = showItems[i]; + if (showItem instanceof ImageItem) + ((ImageItem) showItem).start(); + } + + scrollXOffset = 0; + scrollYOffset = 0; + mScrollState = SCROLL_NONE; + invalidate(); + } + + @Override + protected void onCancel() { + // Empty + } + }; + + private final TimeRunner mDoubleTapRunner = new TimeRunner() { + + private BasicItem image; + private float startScale; + private float endScale; + private final Point startPosition = new Point(); + private final Point endPosition = new Point(); + + private boolean chooseFirst(float a, float b, float standard) { + float offset1 = standard - a; + float offset2 = standard - b; + if ((offset1 > 0.0f && offset2 > 0.0f) + || (offset1 < 0.0f && offset2 < 0.0f)) { + return Math.abs(offset1) < Math.abs(offset2); + } else { + float aa = a / standard; + float bb = b / standard; + aa = aa > 1.0f ? 1 / aa : aa; + bb = bb > 1.0f ? 1 / bb : bb; + return aa < bb; + } + } + + @Override + protected void onStart() { + ShowItem showItem = showItems[CUR_TARGET_INDEX]; + if (showItem == null || !(showItem instanceof BasicItem)) { + mDoubleTapRunner.cancel(); + return; + } + image = (BasicItem) showItem; + int width = image.width; + int height = image.height; + if (width == -1 || height == -1) { + mDoubleTapRunner.cancel(); + return; + } + + // Init start info + Rect rect = image.mRect; + startScale = image.imageScale; + startPosition.x = (rect.left + rect.right) / 2; + startPosition.y = (rect.top + rect.bottom) / 2; + + float fitScreenScale; + float scaleX = (float) mScreenWidth / width; + float scaleY = (float) mScreenHeight / height; + if (scaleX < scaleY) + fitScreenScale = scaleX; + else + fitScreenScale = scaleY; + + if (chooseFirst(fitScreenScale, 1, startScale)) + // To fix screen + endScale = fitScreenScale; + else + // To origin + endScale = 1; + endPosition.x = mScreenWidth / 2; + endPosition.y = mScreenHeight / 2; + } + + @Override + protected void onRun(float interpolatedTime, int runningTime) { + int x = (int) (startPosition.x + (endPosition.x - startPosition.x) + * interpolatedTime); + int y = (int) (startPosition.y + (endPosition.y - startPosition.y) + * interpolatedTime); + float scale = startScale + (endScale - startScale) + * interpolatedTime; + setImageRect(x, y, scale); + invalidate(); + } + + @Override + protected void onEnd() { + // Update global scale + mScale = image.imageScale; + + mDoubleTapAnimating = false; + setImageRect(mScreenWidth / 2, mScreenHeight / 2, endScale); + invalidate(); + } + + @Override + protected void onCancel() { + // Update global scale + mScale = image.imageScale; + + mDoubleTapAnimating = false; + } + + private void setImageRect(int x, int y, float scale) { + image.imageScale = scale; + int showWidth = (int) (image.width * scale); + int showHeight = (int) (image.height * scale); + Rect rect = image.mRect; + rect.left = x - showWidth / 2; + rect.top = y - showHeight / 2; + rect.right = x + showWidth / 2; + rect.bottom = y + showHeight / 2; + } + }; + + public MyGestureListener() { + + mSynchronizedHandler = new SynchronizedHandler(getGLRoot()); + + mToPreTimeRunner.setHandler(mSynchronizedHandler); + mToNextTimeRunner.setHandler(mSynchronizedHandler); + mReturnTimeRunner.setHandler(mSynchronizedHandler); + mDoubleTapRunner.setHandler(mSynchronizedHandler); + mDoubleTapRunner.setDuration(Constants.ANIMATE_TIME); + mDoubleTapRunner.setInterpolator(new ZInterpolator(0.5f)); + } + + private void onTapPre() { + // TODO goto bottom first the to pre page + resetSizePosition(PRE_TARGET_INDEX); + if (!goToPrePage()) { + // Get to first page + MaterialToast.showToast(mContext.getString(R.string.first_page)); + } + } + + private void onTapNext() { + // TODO goto bottom first the to pre page + resetSizePosition(NEXT_TARGET_INDEX); + if (!goToNextPage()) { + // Get to last page + MaterialToast.showToast(mContext.getString(isEnsureSize ? + R.string.the_last_page : R.string.wait_for_more)); + } + } + + @Override + public boolean onSingleTapConfirmed(float x, float y) { + if (mScrollState != SCROLL_NONE) { + return false; + } + if (mShowTapAreaTurn) { + mShowTapAreaTurn = false; + return false; + } + if (mDoubleTapAnimating) + return false; + + if (showItems[CUR_TARGET_INDEX] == null + || showItems[CUR_TARGET_INDEX] instanceof Text) { + if (mOnTapTextListener != null) + mOnTapTextListener.onTapText(mCurIndex); + // return true; + } + + if (Utils.isInArea(leftArea, (int) x, (int) y)) { + if (isRightToLeft()) + onTapNext(); + else + onTapPre(); + + } else if (Utils.isInArea(topArea, (int) x, (int) y)) { + zoom(true); + + } else if (Utils.isInArea(rightArea, (int) x, (int) y)) { + if (isRightToLeft()) + onTapPre(); + else + onTapNext(); + + } else if (Utils.isInArea(bottomArea, (int) x, (int) y)) { + zoom(false); + + } else if (Utils.isInArea(centerArea, (int) x, (int) y)) { + if (mGalleryViewListener != null) + mGalleryViewListener.onTapCenter(); + + } else { + // Can't catch tap + } + + return true; + } + + @Override + public boolean onDoubleTap(float x, float y) { + return false; + } + + @Override + public boolean onDoubleTapConfirmed(float x, float y) { + if (isScale) + return true; + + ShowItem curShowItem = showItems[CUR_TARGET_INDEX]; + if (curShowItem == null || !(curShowItem instanceof BasicItem)) + return true; + + mDoubleTapAnimating = true; + mDoubleTapRunner.start(); + return true; + } + + @Override + public void onLongPress(MotionEvent e) { + if (isScale) + return; + + Utils.vibrator(mContext, 100); + mImageSet.setStartIndex(mCurIndex); + } + + @Override + public boolean onScrollBegin(float dx, float dy, MotionEvent e1, MotionEvent e2) { + //mLightnessSliding = Utils.isInArea(mLightnessSlidingArea, + //(int) e2.getX(), (int) e2.getY()) && Math.abs(dy / dx) > 1.0f; + return onScroll(dx, dy, e1, e2); + } + + /** + * SCROLL_NONE, SCROLL_PRE or SCROLL_NEXT + * + * @param showItem + * @param dx + * @param dy + * @param totalX + * @param totalY + * @return + */ + private int getScrollState(ShowItem showItem, float dx, float dy, float totalX, float totalY) { + Rect rect = showItem.mRect; + boolean needCheckBound = showItem instanceof BasicItem; + + switch (mReadingDirection) { + case DIRECTION_LEFT_RIGHT: + case DIRECTION_RIGHT_LEFT: + if (Math.abs(totalX / totalY) > 1) { + if (dx < 0 && (rect.left >= 0 || !needCheckBound)) + return mReadingDirection == DIRECTION_LEFT_RIGHT ? SCROLL_PRE : SCROLL_NEXT; + else if (dx > 0 && (rect.right <= mScreenWidth || !needCheckBound)) + return mReadingDirection == DIRECTION_LEFT_RIGHT ? SCROLL_NEXT : SCROLL_PRE; + else + return SCROLL_NONE; + } else { + return SCROLL_NONE; + } + + case DIRECTION_TOP_BOTTOM: + case DIRECTION_BOTTOM_TOP: + if (Math.abs(totalY / totalX) > 1) { + if (dy < 0 && (rect.top >= 0 || !needCheckBound)) + return mReadingDirection == DIRECTION_TOP_BOTTOM ? SCROLL_PRE : SCROLL_NEXT; + else if (dy > 0 && (rect.bottom <= mScreenHeight || !needCheckBound)) + return mReadingDirection == DIRECTION_TOP_BOTTOM ? SCROLL_NEXT : SCROLL_PRE; + else + return SCROLL_NONE; + } else { + return SCROLL_NONE; + } + + default: + return SCROLL_NONE; + } + } + + /** + * dx 和 totalX 符号相反,为啥 + */ + @Override + public boolean onScroll(float dx, float dy, MotionEvent e1, MotionEvent e2) { + + if (isScale) + return false; + + ShowItem curShowItem = showItems[CUR_TARGET_INDEX]; + // TODO what if current show item is always null + if (curShowItem == null) + return true; + + if (mDoubleTapAnimating) + return false; + + if (mLightnessSliding) { + if (mGalleryViewListener != null) + mGalleryViewListener.onSlideBottom(dy); + return true; + } + + float totalX = e2.getX() - e1.getX(); + float totalY = e2.getY() - e1.getY(); + + switch (mScrollState) { + case SCROLL_ANIME_PRE: + case SCROLL_ANIME_NEXT: + return false; + case SCROLL_NONE: + // Stop animated image + ShowItem showItem; + for (int i = PRE_TARGET_INDEX; i < TARGET_INDEX_SIZE; i++) { + showItem = showItems[i]; + if (showItem instanceof ImageItem) + ((ImageItem) showItem).stop(); + } + + Rect rect = curShowItem.mRect; + mScrollState = getScrollState(curShowItem, dx, dy, totalX, totalY); + + switch (mScrollState) { + case SCROLL_PRE: + if (isFirstPage()) { + mScrollState = SCROLL_NONE; + } else { + scrollXOffset = 0; + scrollYOffset = 0; + // Prepare previous show item + resetSizePosition(PRE_TARGET_INDEX); + } + break; + case SCROLL_NEXT: + if (isLastPage()) { + mScrollState = SCROLL_NONE; + } else { + scrollXOffset = 0; + scrollYOffset = 0; + // Prepare next show item + resetSizePosition(NEXT_TARGET_INDEX); + } + break; + } + if (mScrollState == SCROLL_NONE) { + int actDx = -(int) dx; + int actDy = -(int) dy; + if (rect.width() <= mScreenWidth) + actDx = 0; + if (rect.height() <= mScreenHeight) + actDy = 0; + rect.offset(actDx, actDy); + + // Fix position + adjustPosition(curShowItem); + } + break; + + case SCROLL_PRE: + case SCROLL_NEXT: + if ((mScrollState == SCROLL_PRE && mReadingDirection == DIRECTION_LEFT_RIGHT) || + (mScrollState == SCROLL_NEXT && mReadingDirection == DIRECTION_RIGHT_LEFT)) { + scrollXOffset -= dx; + if (scrollXOffset <= 0) { + scrollXOffset = 0; + mScrollState = SCROLL_NONE; + } + } else if ((mScrollState == SCROLL_NEXT && mReadingDirection == DIRECTION_LEFT_RIGHT) || + (mScrollState == SCROLL_PRE && mReadingDirection == DIRECTION_RIGHT_LEFT)) { + scrollXOffset -= dx; + if (scrollXOffset >= 0) { + scrollXOffset = 0; + mScrollState = SCROLL_NONE; + } + } else if ((mScrollState == SCROLL_PRE && mReadingDirection == DIRECTION_TOP_BOTTOM) || + (mScrollState == SCROLL_NEXT && mReadingDirection == DIRECTION_BOTTOM_TOP)) { + scrollYOffset -= dy; + if (scrollYOffset <= 0) { + scrollYOffset = 0; + mScrollState = SCROLL_NONE; + } + } else if ((mScrollState == SCROLL_NEXT && mReadingDirection == DIRECTION_TOP_BOTTOM) || + (mScrollState == SCROLL_PRE && mReadingDirection == DIRECTION_BOTTOM_TOP)) { + scrollYOffset -= dy; + if (scrollYOffset >= 0) { + scrollYOffset = 0; + mScrollState = SCROLL_NONE; + } + } + break; + } + + invalidate(); + return true; + } + + private boolean atPreEdge() { + ShowItem showItem = showItems[CUR_TARGET_INDEX]; + if (showItem == null) + return true; + + Rect rect = showItem.mRect; + switch (mReadingDirection) { + case DIRECTION_LEFT_RIGHT: + if (rect.left >= 0) + return true; + else + return false; + case DIRECTION_RIGHT_LEFT: + if (rect.right <= mScreenWidth) + return true; + else + return false; + case DIRECTION_TOP_BOTTOM: + if (rect.top >= 0) + return true; + else + return false; + case DIRECTION_BOTTOM_TOP: + if (rect.bottom <= mScreenHeight) + return true; + else + return false; + } + return true; + } + + private boolean atNextEdge() { + ShowItem showItem = showItems[CUR_TARGET_INDEX]; + if (showItem == null) + return true; + + Rect rect = showItem.mRect; + switch (mReadingDirection) { + case DIRECTION_LEFT_RIGHT: + if (rect.right <= mScreenWidth) + return true; + else + return false; + case DIRECTION_RIGHT_LEFT: + if (rect.left >= 0) + return true; + else + return false; + case DIRECTION_TOP_BOTTOM: + if (rect.bottom <= mScreenHeight) + return true; + else + return false; + case DIRECTION_BOTTOM_TOP: + if (rect.top >= 0) + return true; + else + return false; + } + return true; + } + + @Override + public boolean onScrollEnd(float totalX, float totalY) { + + if (isScale) + return false; + if (mDoubleTapAnimating) + return false; + if (mLightnessSliding) { + if (mGalleryViewListener != null) + mGalleryViewListener.onSlideBottomOver(); + return true; + } + + boolean isHorizon = mReadingDirection == DIRECTION_TOP_BOTTOM || mReadingDirection == DIRECTION_BOTTOM_TOP; + switch (mScrollState) { + case SCROLL_ANIME_PRE: + case SCROLL_ANIME_NEXT: + return false; + + case SCROLL_PRE: + case SCROLL_NEXT: + stopScrollXOffset = scrollXOffset; + stopScrollYOffset = scrollYOffset; + mScrollState = mScrollState == SCROLL_PRE ? SCROLL_ANIME_PRE : SCROLL_ANIME_NEXT; + int absStopScrollXOffset = Math.abs(stopScrollXOffset); + int absStopScrollYOffset = Math.abs(stopScrollYOffset); + + if (Math.abs(stopScrollXOffset) > mScreenWidth * CHANGE_PAGE_PROPORTION || + Math.abs(stopScrollYOffset) > mScreenHeight * CHANGE_PAGE_PROPORTION) { + TimeRunner runner = mScrollState == SCROLL_ANIME_PRE ? mToPreTimeRunner : mToNextTimeRunner; + if (isHorizon) + runner.setDuration(Math.max((int) (MILLSEC_PER_DIX * (mScreenHeight - absStopScrollYOffset)), ANIMATE_MIN)); + else + runner.setDuration(Math.max((int) (MILLSEC_PER_DIX * (mScreenWidth - absStopScrollXOffset)), ANIMATE_MIN)); + runner.start(); + } else { + mReturnTimeRunner.setDuration(Math.max((int) (MILLSEC_PER_DIX * (isHorizon ? absStopScrollYOffset : absStopScrollXOffset)), ANIMATE_MIN)); + mReturnTimeRunner.start(); + } + break; + + case SCROLL_NONE: + // Start animated image + ShowItem showItem; + for (int i = PRE_TARGET_INDEX; i < TARGET_INDEX_SIZE; i++) { + showItem = showItems[i]; + if (showItem instanceof ImageItem) + ((ImageItem) showItem).start(); + } + + showItem = showItems[CUR_TARGET_INDEX]; + if (isHorizon ? Math.abs(totalY) - BORDER_TIP_SENSITIVITY > 0 : Math.abs(totalX) - BORDER_TIP_SENSITIVITY > 0) { + if (isFirstPage() && atPreEdge()) + MaterialToast.showToast(mContext.getString(R.string.first_page)); + else if (isLastPage() && atNextEdge()) + MaterialToast.showToast(mContext.getString(isEnsureSize ? R.string.the_last_page : R.string.wait_for_more)); + } + } + return true; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + return true; + } + + @Override + public boolean onScaleBegin(float focusX, float focusY) { + if (mDoubleTapAnimating) + return false; + + isScale = true; + return true; + } + + @Override + public boolean onScale(float focusX, float focusY, float scale) { + if (mDoubleTapAnimating) + return false; + + if (mScrollState == SCROLL_ANIME_PRE + || mScrollState == SCROLL_ANIME_NEXT) + return true; + + if (mScrollState == SCROLL_PRE || mScrollState == SCROLL_NEXT) { + scrollXOffset = 0; + scrollYOffset = 0; + mScrollState = SCROLL_NONE; + } + + ShowItem curShowItem = showItems[CUR_TARGET_INDEX]; + if (curShowItem == null || !(curShowItem instanceof BasicItem)) + return true; + + BasicItem image = (BasicItem) curShowItem; + + float newScale = image.imageScale * scale; + if (newScale > SCALE_MAX || newScale < SCALE_MIN) + return true; + image.imageScale = newScale; + mScale = newScale; + + Rect rect = image.mRect; + int left = rect.left; + int top = rect.top; + + left = (int) (focusX - ((focusX - left) * scale)); + top = (int) (focusY - ((focusY - top) * scale)); + + rect.set(left, top, + (int) (left + (image.width * image.imageScale)), + (int) (top + (image.height * image.imageScale))); + + // adjust + adjustPosition(image); + + invalidate(); + + return true; + } + + @Override + public void onScaleEnd() { + isScale = false; + } + + @Override + public void onDown(float x, float y) { + // Empty + } + + @Override + public void onUp() { + // Empty + } + } + + /** + * + * If side is shorter then parent's, make it in parent's center If side is + * longer then parent's, make sure it fill parent + * + * @param showItem + */ + private void adjustPosition(ShowItem showItem) { + int targetIndex = getTargetIndex(showItem); + if (targetIndex == INVALID_ID) + return; + + Rect rect = showItem.mRect; + int showWidth = rect.width(); + int showHeight = rect.height(); + + int index = mReadingDirection * TARGET_INDEX_SIZE + targetIndex; + int sumXOffset = mSumOffsetXStore[index]; + int sumYOffset = mSumOffsetYStore[index]; + + if (showWidth > mScreenWidth) { + int fixXOffset = rect.left - sumXOffset; + if (fixXOffset > 0) { + rect.left -= fixXOffset; + rect.right -= fixXOffset; + } else if ((fixXOffset = sumXOffset + mScreenWidth - rect.right) > 0) { + rect.left += fixXOffset; + rect.right += fixXOffset; + } + } else { + int left = sumXOffset + (mScreenWidth - showWidth) / 2; + rect.offsetTo(left, rect.top); + } + if (showHeight > mScreenHeight) { + int fixYOffset = rect.top - sumYOffset; + if (fixYOffset > 0) { + rect.top -= fixYOffset; + rect.bottom -= fixYOffset; + } else if ((fixYOffset = sumYOffset + mScreenHeight - rect.bottom) > 0) { + rect.top += fixYOffset; + rect.bottom += fixYOffset; + } + } else { + int top = sumYOffset + (mScreenHeight - showHeight) / 2; + rect.offsetTo(rect.left, top); + } + } + + public int getTargetIndex(ShowItem showItem) { + if (mImageSet == null) + return INVALID_ID; + for (int i = 0; i < TARGET_INDEX_SIZE; i++) { + if (showItem == showItems[i]) + return i; + } + return INVALID_ID; + } + + private abstract class ShowItem { + protected int width = -1; + protected int height = -1; + public Rect mRect = new Rect(); + + public void draw(GLCanvas canvas) { + this.draw(canvas, 0, 0); + } + + public abstract void draw(GLCanvas canvas, int xOffset, int yOffset); + + public abstract void recycle(); + } + + private abstract class BasicItem extends ShowItem { + private BasicTexture mTexture; + public float imageScale = 1; + private final RectF source = new RectF(); + private final RectF target = new RectF(); + + + /** + * You must call init before draw + * + * @param texture + */ + public void init(BasicTexture texture) { + mTexture = texture; + imageScale = 1; + } + + public BasicTexture getTexture() { + return mTexture; + } + + @Override + public void draw(GLCanvas canvas, int offsetX, int offsetY) { + if (mTexture == null) + return; + + int index = getTargetIndex(this); + if (index == INVALID_ID) + mTexture.draw(canvas, mRect.left + offsetX, + mRect.top + offsetY, mRect.width(), mRect.height()); + else { + int leftBound = 0; + int topBound = 0; + int rightBound = mScreenWidth; + int bottomBound = mScreenHeight; + // Fix show bound + switch (index) { + case CUR_TARGET_INDEX: + if (offsetX < 0) + rightBound = rightBound + offsetX; + else if (offsetX > 0) + leftBound = offsetX; // leftBound + offsetX + if (offsetY < 0) + bottomBound = bottomBound + offsetX; + else if (offsetY > 0) + topBound = offsetY; // topBound + offsetY + break; + case PRE_TARGET_INDEX: + case NEXT_TARGET_INDEX: + if (offsetX < 0) + leftBound = rightBound + offsetX; + else if (offsetX > 0) + rightBound = offsetX; // leftBound + offsetX + if (offsetY < 0) + topBound = bottomBound + offsetY; + else if (offsetY > 0) + bottomBound = offsetY; // topBound + offsetY + break; + } + + int left = mRect.left + offsetX; + int top = mRect.top + offsetY; + int right = mRect.right + offsetX; + int bottom = mRect.bottom + offsetY; + + // Only show what in the own box + // Only what can be seen + if (left < leftBound || top < topBound || right > rightBound + || bottom > bottomBound) { + + if (left < leftBound) { + target.left = leftBound; + source.left = (leftBound - left) / imageScale; + } else { + target.left = left; + source.left = 0; + } + if (top < topBound) { + target.top = topBound; + source.top = (topBound - top) / imageScale; + } else { + target.top = top; + source.top = 0; + } + if (right > rightBound) { + target.right = rightBound; + source.right = width - ((right - rightBound) / imageScale); + } else { + target.right = right; + source.right = width; + } + if (bottom > bottomBound) { + target.bottom = bottomBound; + source.bottom = height - ((bottom - bottomBound) / imageScale); + } else { + target.bottom = bottom; + source.bottom = height; + } + + mTexture.draw(canvas, source, target); + } else { + mTexture.draw(canvas, left, top, right - left, bottom - top); + } + } + } + } + + private abstract class UploadedItem extends BasicItem { + + public boolean isContentValid() { + return ((Uploaded) getTexture()).isContentValid(); + } + + public void updateContent(GLCanvas canvas) { + ((Uploaded) getTexture()).updateContent(canvas); + } + } + + private class BitmapItem extends UploadedItem { + private BitmapTexture mTexture; + private Bitmap mContextBmp; + + public void load(Bitmap bmp) { + if (mTexture != null) + recycle(); + mContextBmp = bmp; + mTexture = new BitmapTexture(bmp); + width = mContextBmp.getWidth(); + height = mContextBmp.getHeight(); + + super.init(mTexture); + } + + @Override + public void recycle() { + if (mTexture == null) + return; + + mTexture.recycle(); + mContextBmp.recycle(); + + mTexture = null; + mContextBmp = null; + } + } + + private class ImageItem extends UploadedItem { + private ImageTexture mTexture; + + public void load(Image image) { + mTexture = new ImageTexture(image); + width = mTexture.getWidth(); + height = mTexture.getHeight(); + + super.init(mTexture); + } + + @Override + public void draw(GLCanvas canvas, int xOffset, int yOffset) { + super.draw(canvas, xOffset, yOffset); + } + + @Override + public void recycle() { + mTexture.recycle(); + } + + public void start() { + mTexture.start(); + } + + public void stop() { + mTexture.stop(); + } + + public boolean isAnimated() { + return mTexture.isAnimated(); + } + } + + private class MovieItem extends BasicItem { + private MovieTexture mTexture; + private Movie mContextMovie; + + public void load(Movie movie) { + if (mTexture != null) + recycle(); + mContextMovie = movie; + mTexture = MovieTexture.newInstance(mContextMovie); + + width = mContextMovie.width(); + height = mContextMovie.height(); + + super.init(mTexture); + } + + @Override + public void recycle() { + if (mTexture == null) + return; + + mTexture.recycle(); + + mTexture = null; + mContextMovie = null; + } + } + + private class Text extends ShowItem { + private final int mSize = Ui.sp2pix(32); + + private StringTexture mTexture; + + public Text(String str) { + load(str); + } + + public Text(String str, float size, int color) { + load(str, size, color); + } + + public void load(String str) { + load(str, mSize, 0xdeffffff); + } + + public void load(String str, float size, int color) { + if (mTexture != null) + recycle(); + mTexture = StringTexture.newInstance(str, size, color); + width = mTexture.getWidth(); + height = mTexture.getHeight(); + } + + @Override + public void draw(GLCanvas canvas, int xOffset, int yOffset) { + if (mTexture != null) + mTexture.draw(canvas, mRect.left + xOffset, mRect.top + yOffset); + } + + @Override + public void recycle() { + if (mTexture == null) + return; + mTexture.recycle(); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/anim/Animation.java b/app/src/main/java/com/hippo/ehviewer/gallery/anim/Animation.java new file mode 100644 index 000000000..5f2f1046d --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/anim/Animation.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.anim; + +import android.view.animation.Interpolator; + +import com.hippo.ehviewer.util.MathUtils; + +// Animation calculates a value according to the current input time. +// +// 1. First we need to use setDuration(int) to set the duration of the +// animation. The duration is in milliseconds. +// 2. Then we should call start(). The actual start time is the first value +// passed to calculate(long). +// 3. Each time we want to get an animation value, we call +// calculate(long currentTimeMillis) to ask the Animation to calculate it. +// The parameter passed to calculate(long) should be nonnegative. +// 4. Use get() to get that value. +// +// In step 3, onCalculate(float progress) is called so subclasses can calculate +// the value according to progress (progress is a value in [0,1]). +// +// Before onCalculate(float) is called, There is an optional interpolator which +// can change the progress value. The interpolator can be set by +// setInterpolator(Interpolator). If the interpolator is used, the value passed +// to onCalculate may be (for example, the overshoot effect). +// +// The isActive() method returns true after the animation start() is called and +// before calculate is passed a value which reaches the duration of the +// animation. +// +// The start() method can be called again to restart the Animation. +// +abstract public class Animation { + private static final long ANIMATION_START = -1; + private static final long NO_ANIMATION = -2; + + private long mStartTime = NO_ANIMATION; + private int mDuration; + private Interpolator mInterpolator; + + public void setInterpolator(Interpolator interpolator) { + mInterpolator = interpolator; + } + + public void setDuration(int duration) { + mDuration = duration; + } + + public void start() { + mStartTime = ANIMATION_START; + } + + public void setStartTime(long time) { + mStartTime = time; + } + + public boolean isActive() { + return mStartTime != NO_ANIMATION; + } + + public void forceStop() { + mStartTime = NO_ANIMATION; + } + + public boolean calculate(long currentTimeMillis) { + if (mStartTime == NO_ANIMATION) return false; + if (mStartTime == ANIMATION_START) mStartTime = currentTimeMillis; + int elapse = (int) (currentTimeMillis - mStartTime); + float x = MathUtils.clamp((float) elapse / mDuration, 0f, 1f); + Interpolator i = mInterpolator; + onCalculate(i != null ? i.getInterpolation(x) : x); + if (elapse >= mDuration) mStartTime = NO_ANIMATION; + return mStartTime != NO_ANIMATION; + } + + abstract protected void onCalculate(float progress); +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/anim/CanvasAnimation.java b/app/src/main/java/com/hippo/ehviewer/gallery/anim/CanvasAnimation.java new file mode 100644 index 000000000..6a809f833 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/anim/CanvasAnimation.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.anim; + +import com.hippo.ehviewer.gallery.glrenderer.GLCanvas; + +public abstract class CanvasAnimation extends Animation { + + public abstract int getCanvasSaveFlags(); + public abstract void apply(GLCanvas canvas); +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/anim/StateTransitionAnimation.java b/app/src/main/java/com/hippo/ehviewer/gallery/anim/StateTransitionAnimation.java new file mode 100644 index 000000000..9f3b565bc --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/anim/StateTransitionAnimation.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.anim; + +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; + +import com.hippo.ehviewer.gallery.glrenderer.GLCanvas; +import com.hippo.ehviewer.gallery.glrenderer.RawTexture; +import com.hippo.ehviewer.gallery.ui.GLView; + +public class StateTransitionAnimation extends Animation { + + public static class Spec { + public static final Spec OUTGOING; + public static final Spec INCOMING; + public static final Spec PHOTO_INCOMING; + + private static final Interpolator DEFAULT_INTERPOLATOR = + new DecelerateInterpolator(); + + public int duration = 330; + public float backgroundAlphaFrom = 0; + public float backgroundAlphaTo = 0; + public float backgroundScaleFrom = 0; + public float backgroundScaleTo = 0; + public float contentAlphaFrom = 1; + public float contentAlphaTo = 1; + public float contentScaleFrom = 1; + public float contentScaleTo = 1; + public float overlayAlphaFrom = 0; + public float overlayAlphaTo = 0; + public float overlayScaleFrom = 0; + public float overlayScaleTo = 0; + public Interpolator interpolator = DEFAULT_INTERPOLATOR; + + static { + OUTGOING = new Spec(); + OUTGOING.backgroundAlphaFrom = 0.5f; + OUTGOING.backgroundAlphaTo = 0f; + OUTGOING.backgroundScaleFrom = 1f; + OUTGOING.backgroundScaleTo = 0f; + OUTGOING.contentAlphaFrom = 0.5f; + OUTGOING.contentAlphaTo = 1f; + OUTGOING.contentScaleFrom = 3f; + OUTGOING.contentScaleTo = 1f; + + INCOMING = new Spec(); + INCOMING.overlayAlphaFrom = 1f; + INCOMING.overlayAlphaTo = 0f; + INCOMING.overlayScaleFrom = 1f; + INCOMING.overlayScaleTo = 3f; + INCOMING.contentAlphaFrom = 0f; + INCOMING.contentAlphaTo = 1f; + INCOMING.contentScaleFrom = 0.25f; + INCOMING.contentScaleTo = 1f; + + PHOTO_INCOMING = INCOMING; + } + + private static Spec specForTransition(Transition t) { + switch (t) { + case Outgoing: + return Spec.OUTGOING; + case Incoming: + return Spec.INCOMING; + case PhotoIncoming: + return Spec.PHOTO_INCOMING; + case None: + default: + return null; + } + } + } + + public static enum Transition { None, Outgoing, Incoming, PhotoIncoming } + + private final Spec mTransitionSpec; + private float mCurrentContentScale; + private float mCurrentContentAlpha; + private float mCurrentBackgroundScale; + private float mCurrentBackgroundAlpha; + private float mCurrentOverlayScale; + private float mCurrentOverlayAlpha; + private RawTexture mOldScreenTexture; + + public StateTransitionAnimation(Transition t, RawTexture oldScreen) { + this(Spec.specForTransition(t), oldScreen); + } + + public StateTransitionAnimation(Spec spec, RawTexture oldScreen) { + mTransitionSpec = spec != null ? spec : Spec.OUTGOING; + setDuration(mTransitionSpec.duration); + setInterpolator(mTransitionSpec.interpolator); + mOldScreenTexture = oldScreen; + } + + @Override + public boolean calculate(long currentTimeMillis) { + boolean retval = super.calculate(currentTimeMillis); + if (!isActive()) { + if (mOldScreenTexture != null) { + mOldScreenTexture.recycle(); + mOldScreenTexture = null; + } + } + return retval; + } + + @Override + protected void onCalculate(float progress) { + mCurrentContentScale = mTransitionSpec.contentScaleFrom + + (mTransitionSpec.contentScaleTo - mTransitionSpec.contentScaleFrom) * progress; + mCurrentContentAlpha = mTransitionSpec.contentAlphaFrom + + (mTransitionSpec.contentAlphaTo - mTransitionSpec.contentAlphaFrom) * progress; + mCurrentBackgroundAlpha = mTransitionSpec.backgroundAlphaFrom + + (mTransitionSpec.backgroundAlphaTo - mTransitionSpec.backgroundAlphaFrom) + * progress; + mCurrentBackgroundScale = mTransitionSpec.backgroundScaleFrom + + (mTransitionSpec.backgroundScaleTo - mTransitionSpec.backgroundScaleFrom) + * progress; + mCurrentOverlayScale = mTransitionSpec.overlayScaleFrom + + (mTransitionSpec.overlayScaleTo - mTransitionSpec.overlayScaleFrom) * progress; + mCurrentOverlayAlpha = mTransitionSpec.overlayAlphaFrom + + (mTransitionSpec.overlayAlphaTo - mTransitionSpec.overlayAlphaFrom) * progress; + } + + private void applyOldTexture(GLView view, GLCanvas canvas, float alpha, float scale, boolean clear) { + if (mOldScreenTexture == null) + return; + if (clear) canvas.clearBuffer(view.getBackgroundColor()); + canvas.save(); + canvas.setAlpha(alpha); + int xOffset = view.getWidth() / 2; + int yOffset = view.getHeight() / 2; + canvas.translate(xOffset, yOffset); + canvas.scale(scale, scale, 1); + mOldScreenTexture.draw(canvas, -xOffset, -yOffset); + canvas.restore(); + } + + public void applyBackground(GLView view, GLCanvas canvas) { + if (mCurrentBackgroundAlpha > 0f) { + applyOldTexture(view, canvas, mCurrentBackgroundAlpha, mCurrentBackgroundScale, true); + } + } + + public void applyContentTransform(GLView view, GLCanvas canvas) { + int xOffset = view.getWidth() / 2; + int yOffset = view.getHeight() / 2; + canvas.translate(xOffset, yOffset); + canvas.scale(mCurrentContentScale, mCurrentContentScale, 1); + canvas.translate(-xOffset, -yOffset); + canvas.setAlpha(mCurrentContentAlpha); + } + + public void applyOverlay(GLView view, GLCanvas canvas) { + if (mCurrentOverlayAlpha > 0f) { + applyOldTexture(view, canvas, mCurrentOverlayAlpha, mCurrentOverlayScale, false); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/data/ImageSet.java b/app/src/main/java/com/hippo/ehviewer/gallery/data/ImageSet.java new file mode 100644 index 000000000..75307d24c --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/data/ImageSet.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.data; + +import java.io.File; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import android.graphics.BitmapFactory; +import android.graphics.Movie; +import android.util.SparseArray; + +import com.hippo.ehviewer.AppHandler; +import com.hippo.ehviewer.ehclient.ExDownloader; +import com.hippo.ehviewer.ehclient.ExDownloaderManager; +import com.hippo.ehviewer.gallery.image.Image; +import com.hippo.ehviewer.util.AutoExpandArray; +import com.hippo.ehviewer.util.BgThread; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.EhUtils; +import com.hippo.ehviewer.util.Log; +import com.hippo.ehviewer.util.Utils; + +public class ImageSet implements ExDownloader.ListenerForImageSet { + + private static final String TAG = ImageSet.class.getSimpleName(); + + /** + * These are code reture by getImage() + */ + public static final int RESULT_NONE = 0x0; + public static final int RESULT_DOWNLOADING = 0x1; + public static final int RESULT_DECODE = 0x2; + + private final ExDownloaderManager mEdManager; + private final ExDownloader mExDownloader; + + private final File mDir; + private final AutoExpandArray mImageFilenameArray; + private ImageListener mListener; + private final Queue mDecodeQueue = new ConcurrentLinkedQueue(); + private final SparseArray mPercentMap = new SparseArray(5); + private final DecodeWorker mDecodeWorker; + /** If true, then wake Worker, worker will stop **/ + private volatile boolean mStopWork = false; + + public class DecodeInfo { + public int index; + public String filename; + + public DecodeInfo(int index, String filename) { + this.index = index; + this.filename = filename; + } + } + + public interface ImageListener { + /** + * It will be invoke when ExDownloader download successful + * + * @param index + */ + void onGetImage(int index); + + void onDownloading(int index, float percent); + + /** + * It will be invoke when decode image over + * + * @param index + * @param res + */ + void onDecodeOver(int index, Object res); + } + + public ImageSet(int gid, String token, String title, int startIndex) { + + mDir = EhUtils.getGalleryDir(gid, title); + Utils.ensureDir(mDir, true); + + // Init ExDownloader + mEdManager = ExDownloaderManager.getInstance(); + mExDownloader = mEdManager.getExDownloader(gid, token, title, Config.getMode()); + mExDownloader.setListenerForImageSet(this); + mExDownloader.setStartIndex(startIndex); + + mImageFilenameArray = mExDownloader.getImageFilenameArray(); + + // Start decode worker + mDecodeWorker = new DecodeWorker(); + mDecodeWorker.start(); + } + + public boolean isStop() { + return mStopWork; + } + + public int getSize() { + return mExDownloader.getImageNum(); + } + + public int getEnsureSize() { + return mExDownloader.getMaxEnsureIndex() + 1; + } + + public void setImageListener(ImageListener l) { + mListener = l; + } + + public void addTargetIndex(int index) { + mExDownloader.addTargetIndex(index); + } + + public void setStartIndex(int startIndex) { + mExDownloader.setStartIndex(startIndex); + } + + public void setCurReadIndex(int index) { + mExDownloader.setCurReadIndex(index); + } + + public String getTitle() { + return mExDownloader.getTitle(); + } + + public File getImageFile(int index) { + File file = null; + String name = getImageFilename(index); + if (name != null) + file = new File(mDir, name); + return file; + } + + private String getImageFilename(int index) { + String filename = mImageFilenameArray.get(index); + if (filename == null) { + // Just guess filename + for (String possibleFilename : EhUtils.getPossibleImageFilenames(index)) { + if (new File(mDir, possibleFilename).exists()) { + // Get filename + filename = possibleFilename; + mImageFilenameArray.set(index, filename); + break; + } + } + } + return filename; + } + + public void redownload(int index) { + String filename = getImageFilename(index); + if (filename != null) { + // Delete file + new File(mDir, filename).delete(); + } + mExDownloader.addTargetIndex(index); + mListener.onGetImage(index); + } + + public Object getImage(int index) { + String filename = getImageFilename(index); + Float percent; + + if ((percent = mPercentMap.get(index)) != null) { + return percent; + } else if (mExDownloader.isDownloading(index)) { + // downloading + return RESULT_DOWNLOADING; + } else if (filename == null || !new File(mDir, filename).exists()) { + // Target index has not being downloading + return RESULT_NONE; + } else { + mDecodeQueue.offer(new DecodeInfo(index, filename)); + // wake decode worker + synchronized(mDecodeQueue) {mDecodeQueue.notify();} + return RESULT_DECODE; + } + } + + /** + * You must call it when you do not need it any more + */ + public void free() { + mStopWork = true; + // wake decode worker to wash wash sleep + synchronized(mDecodeQueue) {mDecodeQueue.notify();} + + // Remove listener for ImageSet + mExDownloader.setListenerForImageSet(null); + // Free ExDownloader + mEdManager.freeExDownloader(mExDownloader); + } + + private class DecodeWorker extends BgThread { + @Override + public void run() { + DecodeInfo decodeInfo; + while (true) { + // Check stop work or not + if (mStopWork) + break; + + // Get decode task + synchronized (mDecodeQueue) { + if (mDecodeQueue.isEmpty()) { + try { + mDecodeQueue.wait(); + } catch (InterruptedException e) {} + continue; + } + decodeInfo = mDecodeQueue.poll(); + } + + // do decode + final int index = decodeInfo.index; + String pathName = Utils.getPathName(mDir.getPath(), decodeInfo.filename); + Object res = null; + + if (Utils.SUPPORT_IMAGE && Config.getCustomCodec()) { + res = Image.decodeFile(pathName, Config.getDecodeFormat()); + } else { + if (Utils.getExtension(pathName, "jpg").equals("gif")) + res = Movie.decodeFile(pathName); + else + res = BitmapFactory.decodeFile(pathName); + } + + final Object _res = res; + // Post to UI thread + AppHandler.getInstance().post(new Runnable() { + @Override + public void run() { + if (mListener != null) + mListener.onDecodeOver(index, _res); + } + }); + } + Log.d(TAG, "DecodeWorker stop working"); + } + } + + @Override + public void onDownloadStart(final int index) { + AppHandler.getInstance().post(new Runnable() { + @Override + public void run() { + if (mListener != null) + mListener.onDownloading(index, 0.0f); + } + }); + } + + @Override + public void onDownloading(final int index, final float percent) { + mPercentMap.append(index, percent); + + AppHandler.getInstance().post(new Runnable() { + @Override + public void run() { + if (mListener != null) + mListener.onDownloading(index, percent); + } + }); + } + + @Override + public void onDownloadComplete(final int index) { + mPercentMap.remove(index); + + AppHandler.getInstance().post(new Runnable() { + @Override + public void run() { + if (mListener != null) + mListener.onGetImage(index); + } + }); + } + + @Override + public void onDownloadFail(int index) { + mPercentMap.remove(index); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/BasicTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/BasicTexture.java new file mode 100644 index 000000000..9e1caa27e --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/BasicTexture.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import java.util.WeakHashMap; + +import android.graphics.RectF; +import android.util.Log; + +import com.hippo.ehviewer.util.MathUtils; + +// BasicTexture is a Texture corresponds to a real GL texture. +// The state of a BasicTexture indicates whether its data is loaded to GL memory. +// If a BasicTexture is loaded into GL memory, it has a GL texture id. +public abstract class BasicTexture implements Texture { + + private static final String TAG = "BasicTexture"; + protected static final int UNSPECIFIED = -1; + + protected static final int STATE_UNLOADED = 0; + protected static final int STATE_LOADED = 1; + protected static final int STATE_ERROR = -1; + + // Log a warning if a texture is larger along a dimension + private static final int MAX_TEXTURE_SIZE = 4096; + + protected int mId = -1; + protected int mState; + + protected int mWidth = UNSPECIFIED; + protected int mHeight = UNSPECIFIED; + + protected int mTextureWidth; + protected int mTextureHeight; + + private boolean mHasBorder; + + protected GLCanvas mCanvasRef = null; + private static WeakHashMap sAllTextures + = new WeakHashMap(); + @SuppressWarnings("rawtypes") + private static ThreadLocal sInFinalizer = new ThreadLocal(); + + protected BasicTexture(GLCanvas canvas, int id, int state) { + setAssociatedCanvas(canvas); + mId = id; + mState = state; + synchronized (sAllTextures) { + sAllTextures.put(this, null); + } + } + + protected BasicTexture() { + this(null, 0, STATE_UNLOADED); + } + + protected void setAssociatedCanvas(GLCanvas canvas) { + mCanvasRef = canvas; + } + + /** + * Sets the content size of this texture. In OpenGL, the actual texture + * size must be of power of 2, the size of the content may be smaller. + */ + public void setSize(int width, int height) { + mWidth = width; + mHeight = height; + mTextureWidth = width > 0 ? MathUtils.nextPowerOf2(width) : 0; + mTextureHeight = height > 0 ? MathUtils.nextPowerOf2(height) : 0; + if (mTextureWidth > MAX_TEXTURE_SIZE || mTextureHeight > MAX_TEXTURE_SIZE) { + Log.w(TAG, String.format("texture is too large: %d x %d", + mTextureWidth, mTextureHeight), new Exception()); + } + } + + public boolean isFlippedVertically() { + return false; + } + + public int getId() { + return mId; + } + + @Override + public int getWidth() { + return mWidth; + } + + @Override + public int getHeight() { + return mHeight; + } + + // Returns the width rounded to the next power of 2. + public int getTextureWidth() { + return mTextureWidth; + } + + // Returns the height rounded to the next power of 2. + public int getTextureHeight() { + return mTextureHeight; + } + + // Returns true if the texture has one pixel transparent border around the + // actual content. This is used to avoid jigged edges. + // + // The jigged edges appear because we use GL_CLAMP_TO_EDGE for texture wrap + // mode (GL_CLAMP is not available in OpenGL ES), so a pixel partially + // covered by the texture will use the color of the edge texel. If we add + // the transparent border, the color of the edge texel will be mixed with + // appropriate amount of transparent. + // + // Currently our background is black, so we can draw the thumbnails without + // enabling blending. + public boolean hasBorder() { + return mHasBorder; + } + + protected void setBorder(boolean hasBorder) { + mHasBorder = hasBorder; + } + + @Override + public void draw(GLCanvas canvas, int x, int y) { + canvas.drawTexture(this, x, y, getWidth(), getHeight()); + } + + @Override + public void draw(GLCanvas canvas, int x, int y, int w, int h) { + canvas.drawTexture(this, x, y, w, h); + } + + public void draw(GLCanvas canvas, RectF source, RectF target) { + canvas.drawTexture(this, source, target); + } + + // onBind is called before GLCanvas binds this texture. + // It should make sure the data is uploaded to GL memory. + abstract protected boolean onBind(GLCanvas canvas); + + // Returns the GL texture target for this texture (e.g. GL_TEXTURE_2D). + abstract protected int getTarget(); + + public boolean isLoaded() { + return mState == STATE_LOADED; + } + + // recycle() is called when the texture will never be used again, + // so it can free all resources. + public void recycle() { + freeResource(); + } + + // yield() is called when the texture will not be used temporarily, + // so it can free some resources. + // The default implementation unloads the texture from GL memory, so + // the subclass should make sure it can reload the texture to GL memory + // later, or it will have to override this method. + public void yield() { + freeResource(); + } + + private void freeResource() { + GLCanvas canvas = mCanvasRef; + if (canvas != null && mId != -1) { + canvas.unloadTexture(this); + mId = -1; // Don't free it again. + } + mState = STATE_UNLOADED; + setAssociatedCanvas(null); + } + + @Override + @SuppressWarnings("unchecked") + protected void finalize() { + sInFinalizer.set(BasicTexture.class); + recycle(); + sInFinalizer.set(null); + } + + // This is for deciding if we can call Bitmap's recycle(). + // We cannot call Bitmap's recycle() in finalizer because at that point + // the finalizer of Bitmap may already be called so recycle() will crash. + public static boolean inFinalizer() { + return sInFinalizer.get() != null; + } + + public static void yieldAllTextures() { + synchronized (sAllTextures) { + for (BasicTexture t : sAllTextures.keySet()) { + t.yield(); + } + } + } + + public static void invalidateAllTextures() { + synchronized (sAllTextures) { + for (BasicTexture t : sAllTextures.keySet()) { + t.mState = STATE_UNLOADED; + t.setAssociatedCanvas(null); + } + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/BitmapTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/BitmapTexture.java new file mode 100644 index 000000000..120f62c28 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/BitmapTexture.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import junit.framework.Assert; +import android.graphics.Bitmap; + +// BitmapTexture is a texture whose content is specified by a fixed Bitmap. +// +// The texture does not own the Bitmap. The user should make sure the Bitmap +// is valid during the texture's lifetime. When the texture is recycled, it +// does not free the Bitmap. +public class BitmapTexture extends UploadedTexture { + protected Bitmap mContentBitmap; + + public BitmapTexture(Bitmap bitmap) { + this(bitmap, false); + } + + public BitmapTexture(Bitmap bitmap, boolean hasBorder) { + super(hasBorder); + Assert.assertTrue(bitmap != null && !bitmap.isRecycled()); + mContentBitmap = bitmap; + } + + @Override + protected void onFreeBitmap(Bitmap bitmap) { + // Do nothing. + } + + @Override + protected Bitmap onGetBitmap() { + return mContentBitmap; + } + + public Bitmap getBitmap() { + return mContentBitmap; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/CanvasTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/CanvasTexture.java new file mode 100644 index 000000000..24b23d90a --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/CanvasTexture.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import android.annotation.SuppressLint; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; + +// CanvasTexture is a texture whose content is the drawing on a Canvas. +// The subclasses should override onDraw() to draw on the bitmap. +// By default CanvasTexture is not opaque. +abstract class CanvasTexture extends UploadedTexture { + protected Canvas mCanvas; + private final Config mConfig; + + public CanvasTexture(int width, int height) { + mConfig = Config.ARGB_8888; + setSize(width, height); + setOpaque(false); + } + + @Override + @SuppressLint("WrongCall") + protected Bitmap onGetBitmap() { + Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, mConfig); + mCanvas = new Canvas(bitmap); + onDraw(mCanvas, bitmap); + return bitmap; + } + + @Override + protected void onFreeBitmap(Bitmap bitmap) { + if (!inFinalizer()) { + bitmap.recycle(); + } + } + + abstract protected void onDraw(Canvas canvas, Bitmap backing); +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/ColorTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/ColorTexture.java new file mode 100644 index 000000000..86f6ea8e3 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/ColorTexture.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import com.hippo.ehviewer.util.Utils; + +// ColorTexture is a texture which fills the rectangle with the specified color. +public class ColorTexture implements Texture { + + private final int mColor; + private int mWidth; + private int mHeight; + + public ColorTexture(int color) { + mColor = color; + mWidth = 1; + mHeight = 1; + } + + @Override + public void draw(GLCanvas canvas, int x, int y) { + draw(canvas, x, y, mWidth, mHeight); + } + + @Override + public void draw(GLCanvas canvas, int x, int y, int w, int h) { + canvas.fillRect(x, y, w, h, mColor); + } + + @Override + public boolean isOpaque() { + return Utils.isOpaque(mColor); + } + + public void setSize(int width, int height) { + mWidth = width; + mHeight = height; + } + + @Override + public int getWidth() { + return mWidth; + } + + @Override + public int getHeight() { + return mHeight; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/ExtTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/ExtTexture.java new file mode 100644 index 000000000..9b249cc4c --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/ExtTexture.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +// ExtTexture is a texture whose content comes from a external texture. +// Before drawing, setSize() should be called. +public class ExtTexture extends BasicTexture { + + private final int mTarget; + + public ExtTexture(GLCanvas canvas, int target) { + GLId glId = canvas.getGLId(); + mId = glId.generateTexture(); + mTarget = target; + } + + private void uploadToCanvas(GLCanvas canvas) { + canvas.setTextureParameters(this); + setAssociatedCanvas(canvas); + mState = STATE_LOADED; + } + + @Override + protected boolean onBind(GLCanvas canvas) { + if (!isLoaded()) { + uploadToCanvas(canvas); + } + + return true; + } + + @Override + public int getTarget() { + return mTarget; + } + + @Override + public boolean isOpaque() { + return true; + } + + @Override + public void yield() { + // we cannot free the texture because we have no backup. + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/FadeInTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/FadeInTexture.java new file mode 100644 index 000000000..9d33ef7f3 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/FadeInTexture.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + + +// FadeInTexture is a texture which begins with a color, then gradually animates +// into a given texture. +public class FadeInTexture extends FadeTexture implements Texture { + @SuppressWarnings("unused") + private static final String TAG = "FadeInTexture"; + + private final int mColor; + private final TiledTexture mTexture; + + public FadeInTexture(int color, TiledTexture texture) { + super(texture.getWidth(), texture.getHeight(), texture.isOpaque()); + mColor = color; + mTexture = texture; + } + + @Override + public void draw(GLCanvas canvas, int x, int y, int w, int h) { + if (isAnimating()) { + mTexture.drawMixed(canvas, mColor, getRatio(), x, y, w, h); + } else { + mTexture.draw(canvas, x, y, w, h); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/FadeOutTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/FadeOutTexture.java new file mode 100644 index 000000000..230293ac3 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/FadeOutTexture.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + + +// FadeOutTexture is a texture which begins with a given texture, then gradually animates +// into fading out totally. +public class FadeOutTexture extends FadeTexture { + @SuppressWarnings("unused") + private static final String TAG = "FadeOutTexture"; + + private final BasicTexture mTexture; + + public FadeOutTexture(BasicTexture texture) { + super(texture.getWidth(), texture.getHeight(), texture.isOpaque()); + mTexture = texture; + } + + @Override + public void draw(GLCanvas canvas, int x, int y, int w, int h) { + if (isAnimating()) { + canvas.save(GLCanvas.SAVE_FLAG_ALPHA); + canvas.setAlpha(getRatio()); + mTexture.draw(canvas, x, y, w, h); + canvas.restore(); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/FadeTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/FadeTexture.java new file mode 100644 index 000000000..e8f177866 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/FadeTexture.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import com.hippo.ehviewer.gallery.ui.AnimationTime; +import com.hippo.ehviewer.util.MathUtils; + +// FadeTexture is a texture which fades the given texture along the time. +public abstract class FadeTexture implements Texture { + @SuppressWarnings("unused") + private static final String TAG = "FadeTexture"; + + // The duration of the fading animation in milliseconds + public static final int DURATION = 180; + + private final long mStartTime; + private final int mWidth; + private final int mHeight; + private final boolean mIsOpaque; + private boolean mIsAnimating; + + public FadeTexture(int width, int height, boolean opaque) { + mWidth = width; + mHeight = height; + mIsOpaque = opaque; + mStartTime = now(); + mIsAnimating = true; + } + + @Override + public void draw(GLCanvas canvas, int x, int y) { + draw(canvas, x, y, mWidth, mHeight); + } + + @Override + public boolean isOpaque() { + return mIsOpaque; + } + + @Override + public int getWidth() { + return mWidth; + } + + @Override + public int getHeight() { + return mHeight; + } + + public boolean isAnimating() { + if (mIsAnimating) { + if (now() - mStartTime >= DURATION) { + mIsAnimating = false; + } + } + return mIsAnimating; + } + + protected float getRatio() { + float r = (float)(now() - mStartTime) / DURATION; + return MathUtils.clamp(1.0f - r, 0.0f, 1.0f); + } + + private long now() { + return AnimationTime.get(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLCanvas.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLCanvas.java new file mode 100644 index 000000000..83cfd5685 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLCanvas.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import android.graphics.Bitmap; +import android.graphics.Rect; +import android.graphics.RectF; + +// +// GLCanvas gives a convenient interface to draw using OpenGL. +// +// When a rectangle is specified in this interface, it means the region +// [x, x+width) * [y, y+height) +// +public interface GLCanvas { + + public GLId getGLId(); + + // Tells GLCanvas the size of the underlying GL surface. This should be + // called before first drawing and when the size of GL surface is changed. + // This is called by GLRoot and should not be called by the clients + // who only want to draw on the GLCanvas. Both width and height must be + // nonnegative. + public abstract void setSize(int width, int height); + + // Clear the drawing buffers. This should only be used by GLRoot. + public abstract void clearBuffer(); + + public abstract void clearBuffer(float[] argb); + + // Sets and gets the current alpha, alpha must be in [0, 1]. + public abstract void setAlpha(float alpha); + + public abstract float getAlpha(); + + // (current alpha) = (current alpha) * alpha + public abstract void multiplyAlpha(float alpha); + + // Change the current transform matrix. + public abstract void translate(float x, float y, float z); + + public abstract void translate(float x, float y); + + public abstract void scale(float sx, float sy, float sz); + + public abstract void rotate(float angle, float x, float y, float z); + + public abstract void multiplyMatrix(float[] mMatrix, int offset); + + // Pushes the configuration state (matrix, and alpha) onto + // a private stack. + public abstract void save(); + + // Same as save(), but only save those specified in saveFlags. + public abstract void save(int saveFlags); + + public static final int SAVE_FLAG_ALL = 0xFFFFFFFF; + public static final int SAVE_FLAG_ALPHA = 0x01; + public static final int SAVE_FLAG_MATRIX = 0x02; + + // Pops from the top of the stack as current configuration state (matrix, + // alpha, and clip). This call balances a previous call to save(), and is + // used to remove all modifications to the configuration state since the + // last save call. + public abstract void restore(); + + // Draws a line using the specified paint from (x1, y1) to (x2, y2). + // (Both end points are included). + public abstract void drawLine(float x1, float y1, float x2, float y2, GLPaint paint); + + // Draws a rectangle using the specified paint from (x1, y1) to (x2, y2). + // (Both end points are included). + public abstract void drawRect(float x1, float y1, float x2, float y2, GLPaint paint); + + // Fills the specified rectangle with the specified color. + public abstract void fillRect(float x, float y, float width, float height, int color); + + // Draws a texture to the specified rectangle. + public abstract void drawTexture( + BasicTexture texture, int x, int y, int width, int height); + + public abstract void drawMesh(BasicTexture tex, int x, int y, int xyBuffer, + int uvBuffer, int indexBuffer, int indexCount); + + // Draws the source rectangle part of the texture to the target rectangle. + public abstract void drawTexture(BasicTexture texture, RectF source, RectF target); + + // Draw a texture with a specified texture transform. + public abstract void drawTexture(BasicTexture texture, float[] mTextureTransform, + int x, int y, int w, int h); + + // Draw two textures to the specified rectangle. The actual texture used is + // from * (1 - ratio) + to * ratio + // The two textures must have the same size. + public abstract void drawMixed(BasicTexture from, int toColor, + float ratio, int x, int y, int w, int h); + + // Draw a region of a texture and a specified color to the specified + // rectangle. The actual color used is from * (1 - ratio) + to * ratio. + // The region of the texture is defined by parameter "src". The target + // rectangle is specified by parameter "target". + public abstract void drawMixed(BasicTexture from, int toColor, + float ratio, RectF src, RectF target); + + // Unloads the specified texture from the canvas. The resource allocated + // to draw the texture will be released. The specified texture will return + // to the unloaded state. This function should be called only from + // BasicTexture or its descendant + public abstract boolean unloadTexture(BasicTexture texture); + + // Delete the specified buffer object, similar to unloadTexture. + public abstract void deleteBuffer(int bufferId); + + // Delete the textures and buffers in GL side. This function should only be + // called in the GL thread. + public abstract void deleteRecycledResources(); + + // Dump statistics information and clear the counters. For debug only. + public abstract void dumpStatisticsAndClear(); + + public abstract void beginRenderTarget(RawTexture texture); + + public abstract void endRenderTarget(); + + /** + * Sets texture parameters to use GL_CLAMP_TO_EDGE for both + * GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T. Sets texture parameters to be + * GL_LINEAR for GL_TEXTURE_MIN_FILTER and GL_TEXTURE_MAG_FILTER. + * bindTexture() must be called prior to this. + * + * @param texture The texture to set parameters on. + */ + public abstract void setTextureParameters(BasicTexture texture); + + /** + * Initializes the texture to a size by calling texImage2D on it. + * + * @param texture The texture to initialize the size. + * @param format The texture format (e.g. GL_RGBA) + * @param type The texture type (e.g. GL_UNSIGNED_BYTE) + */ + public abstract void initializeTextureSize(BasicTexture texture, int format, int type); + + /** + * Initializes the texture to a size by calling texImage2D on it. + * + * @param texture The texture to initialize the size. + * @param bitmap The bitmap to initialize the bitmap with. + */ + public abstract void initializeTexture(BasicTexture texture, Bitmap bitmap); + + /** + * Calls glTexSubImage2D to upload a bitmap to the texture. + * + * @param texture The target texture to write to. + * @param xOffset Specifies a texel offset in the x direction within the + * texture array. + * @param yOffset Specifies a texel offset in the y direction within the + * texture array. + * @param format The texture format (e.g. GL_RGBA) + * @param type The texture type (e.g. GL_UNSIGNED_BYTE) + */ + public abstract void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, + Bitmap bitmap, + int format, int type); + + /** + * Generates buffers and uploads the buffer data. + * + * @param buffer The buffer to upload + * @return The buffer ID that was generated. + */ + public abstract int uploadBuffer(java.nio.FloatBuffer buffer); + + /** + * Generates buffers and uploads the element array buffer data. + * + * @param buffer The buffer to upload + * @return The buffer ID that was generated. + */ + public abstract int uploadBuffer(java.nio.ByteBuffer buffer); + + /** + * After LightCycle makes GL calls, this method is called to restore the GL + * configuration to the one expected by GLCanvas. + */ + public abstract void recoverFromLightCycle(); + + /** + * Gets the bounds given by x, y, width, and height as well as the internal + * matrix state. There is no special handling for non-90-degree rotations. + * It only considers the lower-left and upper-right corners as the bounds. + * + * @param bounds The output bounds to write to. + * @param x The left side of the input rectangle. + * @param y The bottom of the input rectangle. + * @param width The width of the input rectangle. + * @param height The height of the input rectangle. + */ + public abstract void getBounds(Rect bounds, int x, int y, int width, int height); +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLES11Canvas.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLES11Canvas.java new file mode 100644 index 000000000..79fdf820b --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLES11Canvas.java @@ -0,0 +1,995 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.util.ArrayList; + +import javax.microedition.khronos.opengles.GL10; +import javax.microedition.khronos.opengles.GL11; +import javax.microedition.khronos.opengles.GL11Ext; +import javax.microedition.khronos.opengles.GL11ExtensionPack; + +import junit.framework.Assert; +import android.annotation.SuppressLint; +import android.graphics.Bitmap; +import android.graphics.Rect; +import android.graphics.RectF; +import android.opengl.GLU; +import android.opengl.GLUtils; +import android.opengl.Matrix; +import android.util.Log; + +import com.hippo.ehviewer.util.IntArray; +import com.hippo.ehviewer.util.Utils; + +public class GLES11Canvas implements GLCanvas { + private static final String TAG = "GLCanvasImp"; + + private static final float OPAQUE_ALPHA = 0.95f; + + private static final int OFFSET_FILL_RECT = 0; + private static final int OFFSET_DRAW_LINE = 4; + private static final int OFFSET_DRAW_RECT = 6; + private static final float[] BOX_COORDINATES = { + 0, 0, 1, 0, 0, 1, 1, 1, // used for filling a rectangle + 0, 0, 1, 1, // used for drawing a line + 0, 0, 0, 1, 1, 1, 1, 0}; // used for drawing the outline of a rectangle + + private final GL11 mGL; + + private final float mMatrixValues[] = new float[16]; + private final float mTextureMatrixValues[] = new float[16]; + + // The results of mapPoints are stored in this buffer, and the order is + // x1, y1, x2, y2. + private final float mMapPointsBuffer[] = new float[4]; + + private final float mTextureColor[] = new float[4]; + + private final int mBoxCoords; + + private final GLState mGLState; + private final ArrayList mTargetStack = new ArrayList(); + + private float mAlpha; + private final ArrayList mRestoreStack = new ArrayList(); + private ConfigState mRecycledRestoreAction; + + private final RectF mDrawTextureSourceRect = new RectF(); + private final RectF mDrawTextureTargetRect = new RectF(); + private final float[] mTempMatrix = new float[32]; + private final IntArray mUnboundTextures = new IntArray(); + private final IntArray mDeleteBuffers = new IntArray(); + private int mScreenWidth; + private int mScreenHeight; + private final boolean mBlendEnabled = true; + private final int mFrameBuffer[] = new int[1]; + private static float[] sCropRect = new float[4]; + + private RawTexture mTargetTexture; + + // Drawing statistics + int mCountDrawLine; + int mCountFillRect; + int mCountDrawMesh; + int mCountTextureRect; + int mCountTextureOES; + + private static GLId mGLId = new GLES11IdImpl(); + + public GLES11Canvas(GL11 gl) { + mGL = gl; + mGLState = new GLState(gl); + // First create an nio buffer, then create a VBO from it. + int size = BOX_COORDINATES.length * Float.SIZE / Byte.SIZE; + FloatBuffer xyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer(); + xyBuffer.put(BOX_COORDINATES, 0, BOX_COORDINATES.length).position(0); + + int[] name = new int[1]; + mGLId.glGenBuffers(1, name, 0); + mBoxCoords = name[0]; + + gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBoxCoords); + gl.glBufferData(GL11.GL_ARRAY_BUFFER, xyBuffer.capacity() * (Float.SIZE / Byte.SIZE), + xyBuffer, GL11.GL_STATIC_DRAW); + + gl.glVertexPointer(2, GL11.GL_FLOAT, 0, 0); + gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0); + + // Enable the texture coordinate array for Texture 1 + gl.glClientActiveTexture(GL11.GL_TEXTURE1); + gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0); + gl.glClientActiveTexture(GL11.GL_TEXTURE0); + gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); + + // mMatrixValues and mAlpha will be initialized in setSize() + } + + @Override + public void setSize(int width, int height) { + Assert.assertTrue(width >= 0 && height >= 0); + + if (mTargetTexture == null) { + mScreenWidth = width; + mScreenHeight = height; + } + mAlpha = 1.0f; + + GL11 gl = mGL; + gl.glViewport(0, 0, width, height); + gl.glMatrixMode(GL11.GL_PROJECTION); + gl.glLoadIdentity(); + GLU.gluOrtho2D(gl, 0, width, 0, height); + + gl.glMatrixMode(GL11.GL_MODELVIEW); + gl.glLoadIdentity(); + + float matrix[] = mMatrixValues; + Matrix.setIdentityM(matrix, 0); + // to match the graphic coordinate system in android, we flip it vertically. + if (mTargetTexture == null) { + Matrix.translateM(matrix, 0, 0, height, 0); + Matrix.scaleM(matrix, 0, 1, -1, 1); + } + } + + @Override + public void setAlpha(float alpha) { + Assert.assertTrue(alpha >= 0 && alpha <= 1); + mAlpha = alpha; + } + + @Override + public float getAlpha() { + return mAlpha; + } + + @Override + public void multiplyAlpha(float alpha) { + Assert.assertTrue(alpha >= 0 && alpha <= 1); + mAlpha *= alpha; + } + + private static ByteBuffer allocateDirectNativeOrderBuffer(int size) { + return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); + } + + @Override + public void drawRect(float x, float y, float width, float height, GLPaint paint) { + GL11 gl = mGL; + + mGLState.setColorMode(paint.getColor(), mAlpha); + mGLState.setLineWidth(paint.getLineWidth()); + + saveTransform(); + translate(x, y); + scale(width, height, 1); + + gl.glLoadMatrixf(mMatrixValues, 0); + gl.glDrawArrays(GL11.GL_LINE_LOOP, OFFSET_DRAW_RECT, 4); + + restoreTransform(); + mCountDrawLine++; + } + + @Override + public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint) { + GL11 gl = mGL; + + mGLState.setColorMode(paint.getColor(), mAlpha); + mGLState.setLineWidth(paint.getLineWidth()); + + saveTransform(); + translate(x1, y1); + scale(x2 - x1, y2 - y1, 1); + + gl.glLoadMatrixf(mMatrixValues, 0); + gl.glDrawArrays(GL11.GL_LINE_STRIP, OFFSET_DRAW_LINE, 2); + + restoreTransform(); + mCountDrawLine++; + } + + @Override + public void fillRect(float x, float y, float width, float height, int color) { + mGLState.setColorMode(color, mAlpha); + GL11 gl = mGL; + + saveTransform(); + translate(x, y); + scale(width, height, 1); + + gl.glLoadMatrixf(mMatrixValues, 0); + gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, 4); + + restoreTransform(); + mCountFillRect++; + } + + @Override + public void translate(float x, float y, float z) { + Matrix.translateM(mMatrixValues, 0, x, y, z); + } + + // This is a faster version of translate(x, y, z) because + // (1) we knows z = 0, (2) we inline the Matrix.translateM call, + // (3) we unroll the loop + @Override + public void translate(float x, float y) { + float[] m = mMatrixValues; + m[12] += m[0] * x + m[4] * y; + m[13] += m[1] * x + m[5] * y; + m[14] += m[2] * x + m[6] * y; + m[15] += m[3] * x + m[7] * y; + } + + @Override + public void scale(float sx, float sy, float sz) { + Matrix.scaleM(mMatrixValues, 0, sx, sy, sz); + } + + @Override + public void rotate(float angle, float x, float y, float z) { + if (angle == 0) return; + float[] temp = mTempMatrix; + Matrix.setRotateM(temp, 0, angle, x, y, z); + Matrix.multiplyMM(temp, 16, mMatrixValues, 0, temp, 0); + System.arraycopy(temp, 16, mMatrixValues, 0, 16); + } + + @Override + public void multiplyMatrix(float matrix[], int offset) { + float[] temp = mTempMatrix; + Matrix.multiplyMM(temp, 0, mMatrixValues, 0, matrix, offset); + System.arraycopy(temp, 0, mMatrixValues, 0, 16); + } + + private void textureRect(float x, float y, float width, float height) { + GL11 gl = mGL; + + saveTransform(); + translate(x, y); + scale(width, height, 1); + + gl.glLoadMatrixf(mMatrixValues, 0); + gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, 4); + + restoreTransform(); + mCountTextureRect++; + } + + @Override + public void drawMesh(BasicTexture tex, int x, int y, int xyBuffer, + int uvBuffer, int indexBuffer, int indexCount) { + float alpha = mAlpha; + if (!bindTexture(tex)) return; + + mGLState.setBlendEnabled(mBlendEnabled + && (!tex.isOpaque() || alpha < OPAQUE_ALPHA)); + mGLState.setTextureAlpha(alpha); + + // Reset the texture matrix. We will set our own texture coordinates + // below. + setTextureCoords(0, 0, 1, 1); + + saveTransform(); + translate(x, y); + + mGL.glLoadMatrixf(mMatrixValues, 0); + + mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, xyBuffer); + mGL.glVertexPointer(2, GL11.GL_FLOAT, 0, 0); + + mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, uvBuffer); + mGL.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0); + + mGL.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, indexBuffer); + mGL.glDrawElements(GL11.GL_TRIANGLE_STRIP, + indexCount, GL11.GL_UNSIGNED_BYTE, 0); + + mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBoxCoords); + mGL.glVertexPointer(2, GL11.GL_FLOAT, 0, 0); + mGL.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0); + + restoreTransform(); + mCountDrawMesh++; + } + + // Transforms two points by the given matrix m. The result + // {x1', y1', x2', y2'} are stored in mMapPointsBuffer and also returned. + private float[] mapPoints(float m[], int x1, int y1, int x2, int y2) { + float[] r = mMapPointsBuffer; + + // Multiply m and (x1 y1 0 1) to produce (x3 y3 z3 w3). z3 is unused. + float x3 = m[0] * x1 + m[4] * y1 + m[12]; + float y3 = m[1] * x1 + m[5] * y1 + m[13]; + float w3 = m[3] * x1 + m[7] * y1 + m[15]; + r[0] = x3 / w3; + r[1] = y3 / w3; + + // Same for x2 y2. + float x4 = m[0] * x2 + m[4] * y2 + m[12]; + float y4 = m[1] * x2 + m[5] * y2 + m[13]; + float w4 = m[3] * x2 + m[7] * y2 + m[15]; + r[2] = x4 / w4; + r[3] = y4 / w4; + + return r; + } + + private void drawBoundTexture( + BasicTexture texture, int x, int y, int width, int height) { + // Test whether it has been rotated or flipped, if so, glDrawTexiOES + // won't work + if (isMatrixRotatedOrFlipped(mMatrixValues)) { + if (texture.hasBorder()) { + setTextureCoords( + 1.0f / texture.getTextureWidth(), + 1.0f / texture.getTextureHeight(), + (texture.getWidth() - 1.0f) / texture.getTextureWidth(), + (texture.getHeight() - 1.0f) / texture.getTextureHeight()); + } else { + setTextureCoords(0, 0, + (float) texture.getWidth() / texture.getTextureWidth(), + (float) texture.getHeight() / texture.getTextureHeight()); + } + textureRect(x, y, width, height); + } else { + // draw the rect from bottom-left to top-right + float points[] = mapPoints( + mMatrixValues, x, y + height, x + width, y); + x = (int) (points[0] + 0.5f); + y = (int) (points[1] + 0.5f); + width = (int) (points[2] + 0.5f) - x; + height = (int) (points[3] + 0.5f) - y; + if (width > 0 && height > 0) { + ((GL11Ext) mGL).glDrawTexiOES(x, y, 0, width, height); + mCountTextureOES++; + } + } + } + + @Override + public void drawTexture( + BasicTexture texture, int x, int y, int width, int height) { + drawTexture(texture, x, y, width, height, mAlpha); + } + + private void drawTexture(BasicTexture texture, + int x, int y, int width, int height, float alpha) { + if (width <= 0 || height <= 0) return; + + mGLState.setBlendEnabled(mBlendEnabled + && (!texture.isOpaque() || alpha < OPAQUE_ALPHA)); + if (!bindTexture(texture)) return; + mGLState.setTextureAlpha(alpha); + drawBoundTexture(texture, x, y, width, height); + } + + @Override + public void drawTexture(BasicTexture texture, RectF source, RectF target) { + if (target.width() <= 0 || target.height() <= 0) return; + + // Copy the input to avoid changing it. + mDrawTextureSourceRect.set(source); + mDrawTextureTargetRect.set(target); + source = mDrawTextureSourceRect; + target = mDrawTextureTargetRect; + + mGLState.setBlendEnabled(mBlendEnabled + && (!texture.isOpaque() || mAlpha < OPAQUE_ALPHA)); + if (!bindTexture(texture)) return; + convertCoordinate(source, target, texture); + setTextureCoords(source); + mGLState.setTextureAlpha(mAlpha); + textureRect(target.left, target.top, target.width(), target.height()); + } + + @Override + public void drawTexture(BasicTexture texture, float[] mTextureTransform, + int x, int y, int w, int h) { + mGLState.setBlendEnabled(mBlendEnabled + && (!texture.isOpaque() || mAlpha < OPAQUE_ALPHA)); + if (!bindTexture(texture)) return; + setTextureCoords(mTextureTransform); + mGLState.setTextureAlpha(mAlpha); + textureRect(x, y, w, h); + } + + // This function changes the source coordinate to the texture coordinates. + // It also clips the source and target coordinates if it is beyond the + // bound of the texture. + private static void convertCoordinate(RectF source, RectF target, + BasicTexture texture) { + + int width = texture.getWidth(); + int height = texture.getHeight(); + int texWidth = texture.getTextureWidth(); + int texHeight = texture.getTextureHeight(); + // Convert to texture coordinates + source.left /= texWidth; + source.right /= texWidth; + source.top /= texHeight; + source.bottom /= texHeight; + + // Clip if the rendering range is beyond the bound of the texture. + float xBound = (float) width / texWidth; + if (source.right > xBound) { + target.right = target.left + target.width() * + (xBound - source.left) / source.width(); + source.right = xBound; + } + float yBound = (float) height / texHeight; + if (source.bottom > yBound) { + target.bottom = target.top + target.height() * + (yBound - source.top) / source.height(); + source.bottom = yBound; + } + } + + @Override + public void drawMixed(BasicTexture from, + int toColor, float ratio, int x, int y, int w, int h) { + drawMixed(from, toColor, ratio, x, y, w, h, mAlpha); + } + + private boolean bindTexture(BasicTexture texture) { + if (!texture.onBind(this)) return false; + int target = texture.getTarget(); + mGLState.setTextureTarget(target); + mGL.glBindTexture(target, texture.getId()); + return true; + } + + private void setTextureColor(float r, float g, float b, float alpha) { + float[] color = mTextureColor; + color[0] = r; + color[1] = g; + color[2] = b; + color[3] = alpha; + } + + private void setMixedColor(int toColor, float ratio, float alpha) { + // + // The formula we want: + // alpha * ((1 - ratio) * from + ratio * to) + // + // The formula that GL supports is in the form of: + // combo * from + (1 - combo) * to * scale + // + // So, we have combo = alpha * (1 - ratio) + // and scale = alpha * ratio / (1 - combo) + // + float combo = alpha * (1 - ratio); + float scale = alpha * ratio / (1 - combo); + + // Specify the interpolation factor via the alpha component of + // GL_TEXTURE_ENV_COLORs. + // RGB component are get from toColor and will used as SRC1 + float colorScale = scale * (toColor >>> 24) / (0xff * 0xff); + setTextureColor(((toColor >>> 16) & 0xff) * colorScale, + ((toColor >>> 8) & 0xff) * colorScale, + (toColor & 0xff) * colorScale, combo); + GL11 gl = mGL; + gl.glTexEnvfv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, mTextureColor, 0); + + gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_RGB, GL11.GL_INTERPOLATE); + gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_ALPHA, GL11.GL_INTERPOLATE); + gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC1_RGB, GL11.GL_CONSTANT); + gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND1_RGB, GL11.GL_SRC_COLOR); + gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC1_ALPHA, GL11.GL_CONSTANT); + gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND1_ALPHA, GL11.GL_SRC_ALPHA); + + // Wire up the interpolation factor for RGB. + gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_RGB, GL11.GL_CONSTANT); + gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_RGB, GL11.GL_SRC_ALPHA); + + // Wire up the interpolation factor for alpha. + gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_ALPHA, GL11.GL_CONSTANT); + gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_ALPHA, GL11.GL_SRC_ALPHA); + + } + + @Override + public void drawMixed(BasicTexture from, int toColor, float ratio, + RectF source, RectF target) { + if (target.width() <= 0 || target.height() <= 0) return; + + if (ratio <= 0.01f) { + drawTexture(from, source, target); + return; + } else if (ratio >= 1) { + fillRect(target.left, target.top, target.width(), target.height(), toColor); + return; + } + + float alpha = mAlpha; + + // Copy the input to avoid changing it. + mDrawTextureSourceRect.set(source); + mDrawTextureTargetRect.set(target); + source = mDrawTextureSourceRect; + target = mDrawTextureTargetRect; + + mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque() + || !Utils.isOpaque(toColor) || alpha < OPAQUE_ALPHA)); + + if (!bindTexture(from)) return; + + // Interpolate the RGB and alpha values between both textures. + mGLState.setTexEnvMode(GL11.GL_COMBINE); + setMixedColor(toColor, ratio, alpha); + convertCoordinate(source, target, from); + setTextureCoords(source); + textureRect(target.left, target.top, target.width(), target.height()); + mGLState.setTexEnvMode(GL11.GL_REPLACE); + } + + private void drawMixed(BasicTexture from, int toColor, + float ratio, int x, int y, int width, int height, float alpha) { + // change from 0 to 0.01f to prevent getting divided by zero below + if (ratio <= 0.01f) { + drawTexture(from, x, y, width, height, alpha); + return; + } else if (ratio >= 1) { + fillRect(x, y, width, height, toColor); + return; + } + + mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque() + || !Utils.isOpaque(toColor) || alpha < OPAQUE_ALPHA)); + + if (!bindTexture(from)) return; + + // Interpolate the RGB and alpha values between both textures. + mGLState.setTexEnvMode(GL11.GL_COMBINE); + setMixedColor(toColor, ratio, alpha); + + drawBoundTexture(from, x, y, width, height); + mGLState.setTexEnvMode(GL11.GL_REPLACE); + } + + // TODO: the code only work for 2D should get fixed for 3D or removed + private static final int MSKEW_X = 4; + private static final int MSKEW_Y = 1; + private static final int MSCALE_X = 0; + private static final int MSCALE_Y = 5; + + private static boolean isMatrixRotatedOrFlipped(float matrix[]) { + final float eps = 1e-5f; + return Math.abs(matrix[MSKEW_X]) > eps + || Math.abs(matrix[MSKEW_Y]) > eps + || matrix[MSCALE_X] < -eps + || matrix[MSCALE_Y] > eps; + } + + private static class GLState { + + private final GL11 mGL; + + private int mTexEnvMode = GL11.GL_REPLACE; + private float mTextureAlpha = 1.0f; + private int mTextureTarget = GL11.GL_TEXTURE_2D; + private boolean mBlendEnabled = true; + private float mLineWidth = 1.0f; + + public GLState(GL11 gl) { + mGL = gl; + + // Disable unused state + gl.glDisable(GL11.GL_LIGHTING); + + // Enable used features + gl.glEnable(GL11.GL_DITHER); + + gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); + gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); + gl.glEnable(GL11.GL_TEXTURE_2D); + + gl.glTexEnvf(GL11.GL_TEXTURE_ENV, + GL11.GL_TEXTURE_ENV_MODE, GL11.GL_REPLACE); + + // Set the background color + gl.glClearColor(0f, 0f, 0f, 0f); + + gl.glEnable(GL11.GL_BLEND); + gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA); + + // We use 565 or 8888 format, so set the alignment to 2 bytes/pixel. + gl.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 2); + } + + public void setTexEnvMode(int mode) { + if (mTexEnvMode == mode) return; + mTexEnvMode = mode; + mGL.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, mode); + } + + public void setLineWidth(float width) { + if (mLineWidth == width) return; + mLineWidth = width; + mGL.glLineWidth(width); + } + + public void setTextureAlpha(float alpha) { + if (mTextureAlpha == alpha) return; + mTextureAlpha = alpha; + if (alpha >= OPAQUE_ALPHA) { + // The alpha is need for those texture without alpha channel + mGL.glColor4f(1, 1, 1, 1); + setTexEnvMode(GL11.GL_REPLACE); + } else { + mGL.glColor4f(alpha, alpha, alpha, alpha); + setTexEnvMode(GL11.GL_MODULATE); + } + } + + public void setColorMode(int color, float alpha) { + setBlendEnabled(!Utils.isOpaque(color) || alpha < OPAQUE_ALPHA); + + // Set mTextureAlpha to an invalid value, so that it will reset + // again in setTextureAlpha(float) later. + mTextureAlpha = -1.0f; + + setTextureTarget(0); + + float prealpha = (color >>> 24) * alpha * 65535f / 255f / 255f; + mGL.glColor4x( + Math.round(((color >> 16) & 0xFF) * prealpha), + Math.round(((color >> 8) & 0xFF) * prealpha), + Math.round((color & 0xFF) * prealpha), + Math.round(255 * prealpha)); + } + + // target is a value like GL_TEXTURE_2D. If target = 0, texturing is disabled. + public void setTextureTarget(int target) { + if (mTextureTarget == target) return; + if (mTextureTarget != 0) { + mGL.glDisable(mTextureTarget); + } + mTextureTarget = target; + if (mTextureTarget != 0) { + mGL.glEnable(mTextureTarget); + } + } + + public void setBlendEnabled(boolean enabled) { + if (mBlendEnabled == enabled) return; + mBlendEnabled = enabled; + if (enabled) { + mGL.glEnable(GL11.GL_BLEND); + } else { + mGL.glDisable(GL11.GL_BLEND); + } + } + } + + @Override + public void clearBuffer(float[] argb) { + if(argb != null && argb.length == 4) { + mGL.glClearColor(argb[1], argb[2], argb[3], argb[0]); + } else { + mGL.glClearColor(0, 0, 0, 1); + } + mGL.glClear(GL10.GL_COLOR_BUFFER_BIT); + } + + @Override + public void clearBuffer() { + clearBuffer(null); + } + + private void setTextureCoords(RectF source) { + setTextureCoords(source.left, source.top, source.right, source.bottom); + } + + private void setTextureCoords(float left, float top, + float right, float bottom) { + mGL.glMatrixMode(GL11.GL_TEXTURE); + mTextureMatrixValues[0] = right - left; + mTextureMatrixValues[5] = bottom - top; + mTextureMatrixValues[10] = 1; + mTextureMatrixValues[12] = left; + mTextureMatrixValues[13] = top; + mTextureMatrixValues[15] = 1; + mGL.glLoadMatrixf(mTextureMatrixValues, 0); + mGL.glMatrixMode(GL11.GL_MODELVIEW); + } + + private void setTextureCoords(float[] mTextureTransform) { + mGL.glMatrixMode(GL11.GL_TEXTURE); + mGL.glLoadMatrixf(mTextureTransform, 0); + mGL.glMatrixMode(GL11.GL_MODELVIEW); + } + + // unloadTexture and deleteBuffer can be called from the finalizer thread, + // so we synchronized on the mUnboundTextures object. + @Override + public boolean unloadTexture(BasicTexture t) { + synchronized (mUnboundTextures) { + if (!t.isLoaded()) return false; + mUnboundTextures.add(t.mId); + return true; + } + } + + @Override + public void deleteBuffer(int bufferId) { + synchronized (mUnboundTextures) { + mDeleteBuffers.add(bufferId); + } + } + + @Override + public void deleteRecycledResources() { + synchronized (mUnboundTextures) { + IntArray ids = mUnboundTextures; + if (ids.size() > 0) { + mGLId.glDeleteTextures(mGL, ids.size(), ids.getInternalArray(), 0); + ids.clear(); + } + + ids = mDeleteBuffers; + if (ids.size() > 0) { + mGLId.glDeleteBuffers(mGL, ids.size(), ids.getInternalArray(), 0); + ids.clear(); + } + } + } + + @Override + public void save() { + save(SAVE_FLAG_ALL); + } + + @Override + public void save(int saveFlags) { + ConfigState config = obtainRestoreConfig(); + + if ((saveFlags & SAVE_FLAG_ALPHA) != 0) { + config.mAlpha = mAlpha; + } else { + config.mAlpha = -1; + } + + if ((saveFlags & SAVE_FLAG_MATRIX) != 0) { + System.arraycopy(mMatrixValues, 0, config.mMatrix, 0, 16); + } else { + config.mMatrix[0] = Float.NEGATIVE_INFINITY; + } + + mRestoreStack.add(config); + } + + @Override + public void restore() { + if (mRestoreStack.isEmpty()) throw new IllegalStateException(); + ConfigState config = mRestoreStack.remove(mRestoreStack.size() - 1); + config.restore(this); + freeRestoreConfig(config); + } + + private void freeRestoreConfig(ConfigState action) { + action.mNextFree = mRecycledRestoreAction; + mRecycledRestoreAction = action; + } + + private ConfigState obtainRestoreConfig() { + if (mRecycledRestoreAction != null) { + ConfigState result = mRecycledRestoreAction; + mRecycledRestoreAction = result.mNextFree; + return result; + } + return new ConfigState(); + } + + private static class ConfigState { + float mAlpha; + float mMatrix[] = new float[16]; + ConfigState mNextFree; + + public void restore(GLES11Canvas canvas) { + if (mAlpha >= 0) canvas.setAlpha(mAlpha); + if (mMatrix[0] != Float.NEGATIVE_INFINITY) { + System.arraycopy(mMatrix, 0, canvas.mMatrixValues, 0, 16); + } + } + } + + @Override + @SuppressLint("DefaultLocale") + public void dumpStatisticsAndClear() { + String line = String.format( + "MESH:%d, TEX_OES:%d, TEX_RECT:%d, FILL_RECT:%d, LINE:%d", + mCountDrawMesh, mCountTextureRect, mCountTextureOES, + mCountFillRect, mCountDrawLine); + mCountDrawMesh = 0; + mCountTextureRect = 0; + mCountTextureOES = 0; + mCountFillRect = 0; + mCountDrawLine = 0; + Log.d(TAG, line); + } + + private void saveTransform() { + System.arraycopy(mMatrixValues, 0, mTempMatrix, 0, 16); + } + + private void restoreTransform() { + System.arraycopy(mTempMatrix, 0, mMatrixValues, 0, 16); + } + + private void setRenderTarget(RawTexture texture) { + GL11ExtensionPack gl11ep = (GL11ExtensionPack) mGL; + + if (mTargetTexture == null && texture != null) { + mGLId.glGenBuffers(1, mFrameBuffer, 0); + gl11ep.glBindFramebufferOES( + GL11ExtensionPack.GL_FRAMEBUFFER_OES, mFrameBuffer[0]); + } + if (mTargetTexture != null && texture == null) { + gl11ep.glBindFramebufferOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES, 0); + gl11ep.glDeleteFramebuffersOES(1, mFrameBuffer, 0); + } + + mTargetTexture = texture; + if (texture == null) { + setSize(mScreenWidth, mScreenHeight); + } else { + setSize(texture.getWidth(), texture.getHeight()); + + if (!texture.isLoaded()) texture.prepare(this); + + gl11ep.glFramebufferTexture2DOES( + GL11ExtensionPack.GL_FRAMEBUFFER_OES, + GL11ExtensionPack.GL_COLOR_ATTACHMENT0_OES, + GL11.GL_TEXTURE_2D, texture.getId(), 0); + + checkFramebufferStatus(gl11ep); + } + } + + @Override + public void endRenderTarget() { + RawTexture texture = mTargetStack.remove(mTargetStack.size() - 1); + setRenderTarget(texture); + restore(); // restore matrix and alpha + } + + @Override + public void beginRenderTarget(RawTexture texture) { + save(); // save matrix and alpha + mTargetStack.add(mTargetTexture); + setRenderTarget(texture); + } + + private static void checkFramebufferStatus(GL11ExtensionPack gl11ep) { + int status = gl11ep.glCheckFramebufferStatusOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES); + if (status != GL11ExtensionPack.GL_FRAMEBUFFER_COMPLETE_OES) { + String msg = ""; + switch (status) { + case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES: + msg = "FRAMEBUFFER_FORMATS"; + break; + case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES: + msg = "FRAMEBUFFER_ATTACHMENT"; + break; + case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES: + msg = "FRAMEBUFFER_MISSING_ATTACHMENT"; + break; + case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_OES: + msg = "FRAMEBUFFER_DRAW_BUFFER"; + break; + case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_OES: + msg = "FRAMEBUFFER_READ_BUFFER"; + break; + case GL11ExtensionPack.GL_FRAMEBUFFER_UNSUPPORTED_OES: + msg = "FRAMEBUFFER_UNSUPPORTED"; + break; + case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES: + msg = "FRAMEBUFFER_INCOMPLETE_DIMENSIONS"; + break; + } + throw new RuntimeException(msg + ":" + Integer.toHexString(status)); + } + } + + @Override + public void setTextureParameters(BasicTexture texture) { + int width = texture.getWidth(); + int height = texture.getHeight(); + // Define a vertically flipped crop rectangle for OES_draw_texture. + // The four values in sCropRect are: left, bottom, width, and + // height. Negative value of width or height means flip. + sCropRect[0] = 0; + sCropRect[1] = height; + sCropRect[2] = width; + sCropRect[3] = -height; + + // Set texture parameters. + int target = texture.getTarget(); + mGL.glBindTexture(target, texture.getId()); + mGL.glTexParameterfv(target, GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0); + mGL.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE); + mGL.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE); + mGL.glTexParameterf(target, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); + mGL.glTexParameterf(target, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); + } + + @Override + public void initializeTextureSize(BasicTexture texture, int format, int type) { + int target = texture.getTarget(); + mGL.glBindTexture(target, texture.getId()); + int width = texture.getTextureWidth(); + int height = texture.getTextureHeight(); + mGL.glTexImage2D(target, 0, format, width, height, 0, format, type, null); + } + + @Override + public void initializeTexture(BasicTexture texture, Bitmap bitmap) { + int target = texture.getTarget(); + mGL.glBindTexture(target, texture.getId()); + GLUtils.texImage2D(target, 0, bitmap, 0); + } + + @Override + public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap, + int format, int type) { + int target = texture.getTarget(); + mGL.glBindTexture(target, texture.getId()); + GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type); + } + + @Override + public int uploadBuffer(FloatBuffer buf) { + return uploadBuffer(buf, Float.SIZE / Byte.SIZE); + } + + @Override + public int uploadBuffer(ByteBuffer buf) { + return uploadBuffer(buf, 1); + } + + private int uploadBuffer(Buffer buf, int elementSize) { + int[] bufferIds = new int[1]; + mGLId.glGenBuffers(bufferIds.length, bufferIds, 0); + int bufferId = bufferIds[0]; + mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, bufferId); + mGL.glBufferData(GL11.GL_ARRAY_BUFFER, buf.capacity() * elementSize, buf, + GL11.GL_STATIC_DRAW); + return bufferId; + } + + @Override + public void recoverFromLightCycle() { + // This is only required for GLES20 + } + + @Override + public void getBounds(Rect bounds, int x, int y, int width, int height) { + // This is only required for GLES20 + } + + @Override + public GLId getGLId() { + return mGLId; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLES11IdImpl.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLES11IdImpl.java new file mode 100644 index 000000000..ab4130fe4 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLES11IdImpl.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hippo.ehviewer.gallery.glrenderer; + +import javax.microedition.khronos.opengles.GL11; +import javax.microedition.khronos.opengles.GL11ExtensionPack; + +/** + * Open GL ES 1.1 implementation for generating and destroying texture IDs and + * buffer IDs + */ +public class GLES11IdImpl implements GLId { + private static int sNextId = 1; + // Mutex for sNextId + private static Object sLock = new Object(); + + @Override + public int generateTexture() { + synchronized (sLock) { + return sNextId++; + } + } + + @Override + public void glGenBuffers(int n, int[] buffers, int offset) { + synchronized (sLock) { + while (n-- > 0) { + buffers[offset + n] = sNextId++; + } + } + } + + @Override + public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) { + synchronized (sLock) { + gl.glDeleteTextures(n, textures, offset); + } + } + + @Override + public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) { + synchronized (sLock) { + gl.glDeleteBuffers(n, buffers, offset); + } + } + + @Override + public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) { + synchronized (sLock) { + gl11ep.glDeleteFramebuffersOES(n, buffers, offset); + } + } + + +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLES20Canvas.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLES20Canvas.java new file mode 100644 index 000000000..8bbbdea48 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLES20Canvas.java @@ -0,0 +1,1011 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hippo.ehviewer.gallery.glrenderer; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.Arrays; + +import android.annotation.SuppressLint; +import android.graphics.Bitmap; +import android.graphics.Rect; +import android.graphics.RectF; +import android.opengl.GLES20; +import android.opengl.GLUtils; +import android.opengl.Matrix; +import android.util.Log; + +import com.hippo.ehviewer.util.IntArray; + +public class GLES20Canvas implements GLCanvas { + // ************** Constants ********************** + private static final String TAG = GLES20Canvas.class.getSimpleName(); + private static final int FLOAT_SIZE = Float.SIZE / Byte.SIZE; + private static final float OPAQUE_ALPHA = 0.95f; + + private static final int COORDS_PER_VERTEX = 2; + private static final int VERTEX_STRIDE = COORDS_PER_VERTEX * FLOAT_SIZE; + + private static final int COUNT_FILL_VERTEX = 4; + private static final int COUNT_LINE_VERTEX = 2; + private static final int COUNT_RECT_VERTEX = 4; + private static final int OFFSET_FILL_RECT = 0; + private static final int OFFSET_DRAW_LINE = OFFSET_FILL_RECT + COUNT_FILL_VERTEX; + private static final int OFFSET_DRAW_RECT = OFFSET_DRAW_LINE + COUNT_LINE_VERTEX; + + private static final float[] BOX_COORDINATES = { + 0, 0, // Fill rectangle + 1, 0, + 0, 1, + 1, 1, + 0, 0, // Draw line + 1, 1, + 0, 0, // Draw rectangle outline + 0, 1, + 1, 1, + 1, 0, + }; + + private static final float[] BOUNDS_COORDINATES = { + 0, 0, 0, 1, + 1, 1, 0, 1, + }; + + private static final String POSITION_ATTRIBUTE = "aPosition"; + private static final String COLOR_UNIFORM = "uColor"; + private static final String MATRIX_UNIFORM = "uMatrix"; + private static final String TEXTURE_MATRIX_UNIFORM = "uTextureMatrix"; + private static final String TEXTURE_SAMPLER_UNIFORM = "uTextureSampler"; + private static final String ALPHA_UNIFORM = "uAlpha"; + private static final String TEXTURE_COORD_ATTRIBUTE = "aTextureCoordinate"; + + private static final String DRAW_VERTEX_SHADER = "" + + "uniform mat4 " + MATRIX_UNIFORM + ";\n" + + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n" + + "void main() {\n" + + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n" + + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n" + + "}\n"; + + private static final String DRAW_FRAGMENT_SHADER = "" + + "precision mediump float;\n" + + "uniform vec4 " + COLOR_UNIFORM + ";\n" + + "void main() {\n" + + " gl_FragColor = " + COLOR_UNIFORM + ";\n" + + "}\n"; + + private static final String TEXTURE_VERTEX_SHADER = "" + + "uniform mat4 " + MATRIX_UNIFORM + ";\n" + + "uniform mat4 " + TEXTURE_MATRIX_UNIFORM + ";\n" + + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n" + + "varying vec2 vTextureCoord;\n" + + "void main() {\n" + + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n" + + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n" + + " vTextureCoord = (" + TEXTURE_MATRIX_UNIFORM + " * pos).xy;\n" + + "}\n"; + + private static final String MESH_VERTEX_SHADER = "" + + "uniform mat4 " + MATRIX_UNIFORM + ";\n" + + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n" + + "attribute vec2 " + TEXTURE_COORD_ATTRIBUTE + ";\n" + + "varying vec2 vTextureCoord;\n" + + "void main() {\n" + + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n" + + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n" + + " vTextureCoord = " + TEXTURE_COORD_ATTRIBUTE + ";\n" + + "}\n"; + + private static final String TEXTURE_FRAGMENT_SHADER = "" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform float " + ALPHA_UNIFORM + ";\n" + + "uniform sampler2D " + TEXTURE_SAMPLER_UNIFORM + ";\n" + + "void main() {\n" + + " gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", vTextureCoord);\n" + + " gl_FragColor *= " + ALPHA_UNIFORM + ";\n" + + "}\n"; + + private static final String OES_TEXTURE_FRAGMENT_SHADER = "" + + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform float " + ALPHA_UNIFORM + ";\n" + + "uniform samplerExternalOES " + TEXTURE_SAMPLER_UNIFORM + ";\n" + + "void main() {\n" + + " gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", vTextureCoord);\n" + + " gl_FragColor *= " + ALPHA_UNIFORM + ";\n" + + "}\n"; + + private static final int INITIAL_RESTORE_STATE_SIZE = 8; + private static final int MATRIX_SIZE = 16; + + // Keep track of restore state + private float[] mMatrices = new float[INITIAL_RESTORE_STATE_SIZE * MATRIX_SIZE]; + private float[] mAlphas = new float[INITIAL_RESTORE_STATE_SIZE]; + private final IntArray mSaveFlags = new IntArray(); + + private int mCurrentAlphaIndex = 0; + private int mCurrentMatrixIndex = 0; + + // Viewport size + private int mWidth; + private int mHeight; + + // Projection matrix + private final float[] mProjectionMatrix = new float[MATRIX_SIZE]; + + // Screen size for when we aren't bound to a texture + private int mScreenWidth; + private int mScreenHeight; + + // GL programs + private final int mDrawProgram; + private final int mTextureProgram; + private final int mOesTextureProgram; + private final int mMeshProgram; + + // GL buffer containing BOX_COORDINATES + private final int mBoxCoordinates; + + // Handle indices -- common + private static final int INDEX_POSITION = 0; + private static final int INDEX_MATRIX = 1; + + // Handle indices -- draw + private static final int INDEX_COLOR = 2; + + // Handle indices -- texture + private static final int INDEX_TEXTURE_MATRIX = 2; + private static final int INDEX_TEXTURE_SAMPLER = 3; + private static final int INDEX_ALPHA = 4; + + // Handle indices -- mesh + private static final int INDEX_TEXTURE_COORD = 2; + + private abstract static class ShaderParameter { + public int handle; + protected final String mName; + + public ShaderParameter(String name) { + mName = name; + } + + public abstract void loadHandle(int program); + } + + private static class UniformShaderParameter extends ShaderParameter { + public UniformShaderParameter(String name) { + super(name); + } + + @Override + public void loadHandle(int program) { + handle = GLES20.glGetUniformLocation(program, mName); + checkError(); + } + } + + private static class AttributeShaderParameter extends ShaderParameter { + public AttributeShaderParameter(String name) { + super(name); + } + + @Override + public void loadHandle(int program) { + handle = GLES20.glGetAttribLocation(program, mName); + checkError(); + } + } + + ShaderParameter[] mDrawParameters = { + new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION + new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX + new UniformShaderParameter(COLOR_UNIFORM), // INDEX_COLOR + }; + ShaderParameter[] mTextureParameters = { + new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION + new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX + new UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX + new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER + new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA + }; + ShaderParameter[] mOesTextureParameters = { + new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION + new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX + new UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX + new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER + new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA + }; + ShaderParameter[] mMeshParameters = { + new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION + new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX + new AttributeShaderParameter(TEXTURE_COORD_ATTRIBUTE), // INDEX_TEXTURE_COORD + new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER + new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA + }; + + private final IntArray mUnboundTextures = new IntArray(); + private final IntArray mDeleteBuffers = new IntArray(); + + // Keep track of statistics for debugging + private int mCountDrawMesh = 0; + private int mCountTextureRect = 0; + private int mCountFillRect = 0; + private int mCountDrawLine = 0; + + // Buffer for framebuffer IDs -- we keep track so we can switch the attached + // texture. + private final int[] mFrameBuffer = new int[1]; + + // Bound textures. + private final ArrayList mTargetTextures = new ArrayList(); + + // Temporary variables used within calculations + private final float[] mTempMatrix = new float[32]; + private final float[] mTempColor = new float[4]; + private final RectF mTempSourceRect = new RectF(); + private final RectF mTempTargetRect = new RectF(); + private final float[] mTempTextureMatrix = new float[MATRIX_SIZE]; + private final int[] mTempIntArray = new int[1]; + + private static final GLId mGLId = new GLES20IdImpl(); + + public GLES20Canvas() { + Matrix.setIdentityM(mTempTextureMatrix, 0); + Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex); + mAlphas[mCurrentAlphaIndex] = 1f; + mTargetTextures.add(null); + + FloatBuffer boxBuffer = createBuffer(BOX_COORDINATES); + mBoxCoordinates = uploadBuffer(boxBuffer); + + int drawVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DRAW_VERTEX_SHADER); + int textureVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, TEXTURE_VERTEX_SHADER); + int meshVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, MESH_VERTEX_SHADER); + int drawFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, DRAW_FRAGMENT_SHADER); + int textureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, TEXTURE_FRAGMENT_SHADER); + int oesTextureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, + OES_TEXTURE_FRAGMENT_SHADER); + + mDrawProgram = assembleProgram(drawVertexShader, drawFragmentShader, mDrawParameters); + mTextureProgram = assembleProgram(textureVertexShader, textureFragmentShader, + mTextureParameters); + mOesTextureProgram = assembleProgram(textureVertexShader, oesTextureFragmentShader, + mOesTextureParameters); + mMeshProgram = assembleProgram(meshVertexShader, textureFragmentShader, mMeshParameters); + GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA); + checkError(); + } + + private static FloatBuffer createBuffer(float[] values) { + // First create an nio buffer, then create a VBO from it. + int size = values.length * FLOAT_SIZE; + FloatBuffer buffer = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()) + .asFloatBuffer(); + buffer.put(values, 0, values.length).position(0); + return buffer; + } + + private int assembleProgram(int vertexShader, int fragmentShader, ShaderParameter[] params) { + int program = GLES20.glCreateProgram(); + checkError(); + if (program == 0) { + throw new RuntimeException("Cannot create GL program: " + GLES20.glGetError()); + } + GLES20.glAttachShader(program, vertexShader); + checkError(); + GLES20.glAttachShader(program, fragmentShader); + checkError(); + GLES20.glLinkProgram(program); + checkError(); + int[] mLinkStatus = mTempIntArray; + GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, mLinkStatus, 0); + if (mLinkStatus[0] != GLES20.GL_TRUE) { + Log.e(TAG, "Could not link program: "); + Log.e(TAG, GLES20.glGetProgramInfoLog(program)); + GLES20.glDeleteProgram(program); + program = 0; + } + for (int i = 0; i < params.length; i++) { + params[i].loadHandle(program); + } + return program; + } + + private static int loadShader(int type, String shaderCode) { + // create a vertex shader type (GLES20.GL_VERTEX_SHADER) + // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER) + int shader = GLES20.glCreateShader(type); + + // add the source code to the shader and compile it + GLES20.glShaderSource(shader, shaderCode); + checkError(); + GLES20.glCompileShader(shader); + checkError(); + + return shader; + } + + @Override + public void setSize(int width, int height) { + mWidth = width; + mHeight = height; + GLES20.glViewport(0, 0, mWidth, mHeight); + checkError(); + Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex); + Matrix.orthoM(mProjectionMatrix, 0, 0, width, 0, height, -1, 1); + if (getTargetTexture() == null) { + mScreenWidth = width; + mScreenHeight = height; + Matrix.translateM(mMatrices, mCurrentMatrixIndex, 0, height, 0); + Matrix.scaleM(mMatrices, mCurrentMatrixIndex, 1, -1, 1); + } + } + + @Override + public void clearBuffer() { + GLES20.glClearColor(0f, 0f, 0f, 1f); + checkError(); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + checkError(); + } + + @Override + public void clearBuffer(float[] argb) { + GLES20.glClearColor(argb[1], argb[2], argb[3], argb[0]); + checkError(); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + checkError(); + } + + @Override + public float getAlpha() { + return mAlphas[mCurrentAlphaIndex]; + } + + @Override + public void setAlpha(float alpha) { + mAlphas[mCurrentAlphaIndex] = alpha; + } + + @Override + public void multiplyAlpha(float alpha) { + setAlpha(getAlpha() * alpha); + } + + @Override + public void translate(float x, float y, float z) { + Matrix.translateM(mMatrices, mCurrentMatrixIndex, x, y, z); + } + + // This is a faster version of translate(x, y, z) because + // (1) we knows z = 0, (2) we inline the Matrix.translateM call, + // (3) we unroll the loop + @Override + public void translate(float x, float y) { + int index = mCurrentMatrixIndex; + float[] m = mMatrices; + m[index + 12] += m[index + 0] * x + m[index + 4] * y; + m[index + 13] += m[index + 1] * x + m[index + 5] * y; + m[index + 14] += m[index + 2] * x + m[index + 6] * y; + m[index + 15] += m[index + 3] * x + m[index + 7] * y; + } + + @Override + public void scale(float sx, float sy, float sz) { + Matrix.scaleM(mMatrices, mCurrentMatrixIndex, sx, sy, sz); + } + + @Override + public void rotate(float angle, float x, float y, float z) { + if (angle == 0f) { + return; + } + float[] temp = mTempMatrix; + Matrix.setRotateM(temp, 0, angle, x, y, z); + float[] matrix = mMatrices; + int index = mCurrentMatrixIndex; + Matrix.multiplyMM(temp, MATRIX_SIZE, matrix, index, temp, 0); + System.arraycopy(temp, MATRIX_SIZE, matrix, index, MATRIX_SIZE); + } + + @Override + public void multiplyMatrix(float[] matrix, int offset) { + float[] temp = mTempMatrix; + float[] currentMatrix = mMatrices; + int index = mCurrentMatrixIndex; + Matrix.multiplyMM(temp, 0, currentMatrix, index, matrix, offset); + System.arraycopy(temp, 0, currentMatrix, index, 16); + } + + @Override + public void save() { + save(SAVE_FLAG_ALL); + } + + @Override + public void save(int saveFlags) { + boolean saveAlpha = (saveFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA; + if (saveAlpha) { + float currentAlpha = getAlpha(); + mCurrentAlphaIndex++; + if (mAlphas.length <= mCurrentAlphaIndex) { + mAlphas = Arrays.copyOf(mAlphas, mAlphas.length * 2); + } + mAlphas[mCurrentAlphaIndex] = currentAlpha; + } + boolean saveMatrix = (saveFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX; + if (saveMatrix) { + int currentIndex = mCurrentMatrixIndex; + mCurrentMatrixIndex += MATRIX_SIZE; + if (mMatrices.length <= mCurrentMatrixIndex) { + mMatrices = Arrays.copyOf(mMatrices, mMatrices.length * 2); + } + System.arraycopy(mMatrices, currentIndex, mMatrices, mCurrentMatrixIndex, MATRIX_SIZE); + } + mSaveFlags.add(saveFlags); + } + + @Override + public void restore() { + int restoreFlags = mSaveFlags.removeLast(); + boolean restoreAlpha = (restoreFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA; + if (restoreAlpha) { + mCurrentAlphaIndex--; + } + boolean restoreMatrix = (restoreFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX; + if (restoreMatrix) { + mCurrentMatrixIndex -= MATRIX_SIZE; + } + } + + @Override + public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint) { + draw(GLES20.GL_LINE_STRIP, OFFSET_DRAW_LINE, COUNT_LINE_VERTEX, x1, y1, x2 - x1, y2 - y1, + paint); + mCountDrawLine++; + } + + @Override + public void drawRect(float x, float y, float width, float height, GLPaint paint) { + draw(GLES20.GL_LINE_LOOP, OFFSET_DRAW_RECT, COUNT_RECT_VERTEX, x, y, width, height, paint); + mCountDrawLine++; + } + + private void draw(int type, int offset, int count, float x, float y, float width, float height, + GLPaint paint) { + draw(type, offset, count, x, y, width, height, paint.getColor(), paint.getLineWidth()); + } + + private void draw(int type, int offset, int count, float x, float y, float width, float height, + int color, float lineWidth) { + prepareDraw(offset, color, lineWidth); + draw(mDrawParameters, type, count, x, y, width, height); + } + + private void prepareDraw(int offset, int color, float lineWidth) { + GLES20.glUseProgram(mDrawProgram); + checkError(); + if (lineWidth > 0) { + GLES20.glLineWidth(lineWidth); + checkError(); + } + float[] colorArray = getColor(color); + boolean blendingEnabled = (colorArray[3] < 1f); + enableBlending(blendingEnabled); + if (blendingEnabled) { + GLES20.glBlendColor(colorArray[0], colorArray[1], colorArray[2], colorArray[3]); + checkError(); + } + + GLES20.glUniform4fv(mDrawParameters[INDEX_COLOR].handle, 1, colorArray, 0); + setPosition(mDrawParameters, offset); + checkError(); + } + + private float[] getColor(int color) { + float alpha = ((color >>> 24) & 0xFF) / 255f * getAlpha(); + float red = ((color >>> 16) & 0xFF) / 255f * alpha; + float green = ((color >>> 8) & 0xFF) / 255f * alpha; + float blue = (color & 0xFF) / 255f * alpha; + mTempColor[0] = red; + mTempColor[1] = green; + mTempColor[2] = blue; + mTempColor[3] = alpha; + return mTempColor; + } + + private void enableBlending(boolean enableBlending) { + if (enableBlending) { + GLES20.glEnable(GLES20.GL_BLEND); + checkError(); + } else { + GLES20.glDisable(GLES20.GL_BLEND); + checkError(); + } + } + + private void setPosition(ShaderParameter[] params, int offset) { + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mBoxCoordinates); + checkError(); + GLES20.glVertexAttribPointer(params[INDEX_POSITION].handle, COORDS_PER_VERTEX, + GLES20.GL_FLOAT, false, VERTEX_STRIDE, offset * VERTEX_STRIDE); + checkError(); + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); + checkError(); + } + + private void draw(ShaderParameter[] params, int type, int count, float x, float y, float width, + float height) { + setMatrix(params, x, y, width, height); + int positionHandle = params[INDEX_POSITION].handle; + GLES20.glEnableVertexAttribArray(positionHandle); + checkError(); + GLES20.glDrawArrays(type, 0, count); + checkError(); + GLES20.glDisableVertexAttribArray(positionHandle); + checkError(); + } + + private void setMatrix(ShaderParameter[] params, float x, float y, float width, float height) { + Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f); + Matrix.scaleM(mTempMatrix, 0, width, height, 1f); + Matrix.multiplyMM(mTempMatrix, MATRIX_SIZE, mProjectionMatrix, 0, mTempMatrix, 0); + GLES20.glUniformMatrix4fv(params[INDEX_MATRIX].handle, 1, false, mTempMatrix, MATRIX_SIZE); + checkError(); + } + + @Override + public void fillRect(float x, float y, float width, float height, int color) { + draw(GLES20.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, COUNT_FILL_VERTEX, x, y, width, height, + color, 0f); + mCountFillRect++; + } + + @Override + public void drawTexture(BasicTexture texture, int x, int y, int width, int height) { + if (width <= 0 || height <= 0) { + return; + } + copyTextureCoordinates(texture, mTempSourceRect); + mTempTargetRect.set(x, y, x + width, y + height); + convertCoordinate(mTempSourceRect, mTempTargetRect, texture); + drawTextureRect(texture, mTempSourceRect, mTempTargetRect); + } + + private static void copyTextureCoordinates(BasicTexture texture, RectF outRect) { + int left = 0; + int top = 0; + int right = texture.getWidth(); + int bottom = texture.getHeight(); + if (texture.hasBorder()) { + left = 1; + top = 1; + right -= 1; + bottom -= 1; + } + outRect.set(left, top, right, bottom); + } + + @Override + public void drawTexture(BasicTexture texture, RectF source, RectF target) { + if (target.width() <= 0 || target.height() <= 0) { + return; + } + mTempSourceRect.set(source); + mTempTargetRect.set(target); + + convertCoordinate(mTempSourceRect, mTempTargetRect, texture); + drawTextureRect(texture, mTempSourceRect, mTempTargetRect); + } + + @Override + public void drawTexture(BasicTexture texture, float[] textureTransform, int x, int y, int w, + int h) { + if (w <= 0 || h <= 0) { + return; + } + mTempTargetRect.set(x, y, x + w, y + h); + drawTextureRect(texture, textureTransform, mTempTargetRect); + } + + private void drawTextureRect(BasicTexture texture, RectF source, RectF target) { + setTextureMatrix(source); + drawTextureRect(texture, mTempTextureMatrix, target); + } + + private void setTextureMatrix(RectF source) { + mTempTextureMatrix[0] = source.width(); + mTempTextureMatrix[5] = source.height(); + mTempTextureMatrix[12] = source.left; + mTempTextureMatrix[13] = source.top; + } + + // This function changes the source coordinate to the texture coordinates. + // It also clips the source and target coordinates if it is beyond the + // bound of the texture. + private static void convertCoordinate(RectF source, RectF target, BasicTexture texture) { + int width = texture.getWidth(); + int height = texture.getHeight(); + int texWidth = texture.getTextureWidth(); + int texHeight = texture.getTextureHeight(); + // Convert to texture coordinates + source.left /= texWidth; + source.right /= texWidth; + source.top /= texHeight; + source.bottom /= texHeight; + + // Clip if the rendering range is beyond the bound of the texture. + float xBound = (float) width / texWidth; + if (source.right > xBound) { + target.right = target.left + target.width() * (xBound - source.left) / source.width(); + source.right = xBound; + } + float yBound = (float) height / texHeight; + if (source.bottom > yBound) { + target.bottom = target.top + target.height() * (yBound - source.top) / source.height(); + source.bottom = yBound; + } + } + + private void drawTextureRect(BasicTexture texture, float[] textureMatrix, RectF target) { + ShaderParameter[] params = prepareTexture(texture); + setPosition(params, OFFSET_FILL_RECT); + GLES20.glUniformMatrix4fv(params[INDEX_TEXTURE_MATRIX].handle, 1, false, textureMatrix, 0); + checkError(); + if (texture.isFlippedVertically()) { + save(SAVE_FLAG_MATRIX); + translate(0, target.centerY()); + scale(1, -1, 1); + translate(0, -target.centerY()); + } + draw(params, GLES20.GL_TRIANGLE_STRIP, COUNT_FILL_VERTEX, target.left, target.top, + target.width(), target.height()); + if (texture.isFlippedVertically()) { + restore(); + } + mCountTextureRect++; + } + + private ShaderParameter[] prepareTexture(BasicTexture texture) { + ShaderParameter[] params; + int program; + if (texture.getTarget() == GLES20.GL_TEXTURE_2D) { + params = mTextureParameters; + program = mTextureProgram; + } else { + params = mOesTextureParameters; + program = mOesTextureProgram; + } + prepareTexture(texture, program, params); + return params; + } + + private void prepareTexture(BasicTexture texture, int program, ShaderParameter[] params) { + GLES20.glUseProgram(program); + checkError(); + enableBlending(!texture.isOpaque() || getAlpha() < OPAQUE_ALPHA); + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + checkError(); + texture.onBind(this); + GLES20.glBindTexture(texture.getTarget(), texture.getId()); + checkError(); + GLES20.glUniform1i(params[INDEX_TEXTURE_SAMPLER].handle, 0); + checkError(); + GLES20.glUniform1f(params[INDEX_ALPHA].handle, getAlpha()); + checkError(); + } + + @Override + public void drawMesh(BasicTexture texture, int x, int y, int xyBuffer, int uvBuffer, + int indexBuffer, int indexCount) { + prepareTexture(texture, mMeshProgram, mMeshParameters); + + GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer); + checkError(); + + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, xyBuffer); + checkError(); + int positionHandle = mMeshParameters[INDEX_POSITION].handle; + GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, + VERTEX_STRIDE, 0); + checkError(); + + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, uvBuffer); + checkError(); + int texCoordHandle = mMeshParameters[INDEX_TEXTURE_COORD].handle; + GLES20.glVertexAttribPointer(texCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, + false, VERTEX_STRIDE, 0); + checkError(); + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); + checkError(); + + GLES20.glEnableVertexAttribArray(positionHandle); + checkError(); + GLES20.glEnableVertexAttribArray(texCoordHandle); + checkError(); + + setMatrix(mMeshParameters, x, y, 1, 1); + GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, indexCount, GLES20.GL_UNSIGNED_BYTE, 0); + checkError(); + + GLES20.glDisableVertexAttribArray(positionHandle); + checkError(); + GLES20.glDisableVertexAttribArray(texCoordHandle); + checkError(); + GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0); + checkError(); + mCountDrawMesh++; + } + + @Override + public void drawMixed(BasicTexture texture, int toColor, float ratio, int x, int y, int w, int h) { + copyTextureCoordinates(texture, mTempSourceRect); + mTempTargetRect.set(x, y, x + w, y + h); + drawMixed(texture, toColor, ratio, mTempSourceRect, mTempTargetRect); + } + + @Override + public void drawMixed(BasicTexture texture, int toColor, float ratio, RectF source, RectF target) { + if (target.width() <= 0 || target.height() <= 0) { + return; + } + save(SAVE_FLAG_ALPHA); + + float currentAlpha = getAlpha(); + float cappedRatio = Math.min(1f, Math.max(0f, ratio)); + + float textureAlpha = (1f - cappedRatio) * currentAlpha; + setAlpha(textureAlpha); + drawTexture(texture, source, target); + + float colorAlpha = cappedRatio * currentAlpha; + setAlpha(colorAlpha); + fillRect(target.left, target.top, target.width(), target.height(), toColor); + + restore(); + } + + @Override + public boolean unloadTexture(BasicTexture texture) { + boolean unload = texture.isLoaded(); + if (unload) { + synchronized (mUnboundTextures) { + mUnboundTextures.add(texture.getId()); + } + } + return unload; + } + + @Override + public void deleteBuffer(int bufferId) { + synchronized (mUnboundTextures) { + mDeleteBuffers.add(bufferId); + } + } + + @Override + public void deleteRecycledResources() { + synchronized (mUnboundTextures) { + IntArray ids = mUnboundTextures; + if (mUnboundTextures.size() > 0) { + mGLId.glDeleteTextures(null, ids.size(), ids.getInternalArray(), 0); + ids.clear(); + } + + ids = mDeleteBuffers; + if (ids.size() > 0) { + mGLId.glDeleteBuffers(null, ids.size(), ids.getInternalArray(), 0); + ids.clear(); + } + } + } + + @Override + @SuppressLint("DefaultLocale") + public void dumpStatisticsAndClear() { + String line = String.format("MESH:%d, TEX_RECT:%d, FILL_RECT:%d, LINE:%d", mCountDrawMesh, + mCountTextureRect, mCountFillRect, mCountDrawLine); + mCountDrawMesh = 0; + mCountTextureRect = 0; + mCountFillRect = 0; + mCountDrawLine = 0; + Log.d(TAG, line); + } + + @Override + public void endRenderTarget() { + RawTexture oldTexture = mTargetTextures.remove(mTargetTextures.size() - 1); + RawTexture texture = getTargetTexture(); + setRenderTarget(oldTexture, texture); + restore(); // restore matrix and alpha + } + + @Override + public void beginRenderTarget(RawTexture texture) { + save(); // save matrix and alpha and blending + RawTexture oldTexture = getTargetTexture(); + mTargetTextures.add(texture); + setRenderTarget(oldTexture, texture); + } + + private RawTexture getTargetTexture() { + return mTargetTextures.get(mTargetTextures.size() - 1); + } + + private void setRenderTarget(BasicTexture oldTexture, RawTexture texture) { + if (oldTexture == null && texture != null) { + GLES20.glGenFramebuffers(1, mFrameBuffer, 0); + checkError(); + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer[0]); + checkError(); + } else if (oldTexture != null && texture == null) { + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + checkError(); + GLES20.glDeleteFramebuffers(1, mFrameBuffer, 0); + checkError(); + } + + if (texture == null) { + setSize(mScreenWidth, mScreenHeight); + } else { + setSize(texture.getWidth(), texture.getHeight()); + + if (!texture.isLoaded()) { + texture.prepare(this); + } + + GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, + texture.getTarget(), texture.getId(), 0); + checkError(); + + checkFramebufferStatus(); + } + } + + private static void checkFramebufferStatus() { + int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); + if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) { + String msg = ""; + switch (status) { + case GLES20.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + msg = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; + break; + case GLES20.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: + msg = "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS"; + break; + case GLES20.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + msg = "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"; + break; + case GLES20.GL_FRAMEBUFFER_UNSUPPORTED: + msg = "GL_FRAMEBUFFER_UNSUPPORTED"; + break; + } + throw new RuntimeException(msg + ":" + Integer.toHexString(status)); + } + } + + @Override + public void setTextureParameters(BasicTexture texture) { + int target = texture.getTarget(); + GLES20.glBindTexture(target, texture.getId()); + checkError(); + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + } + + @Override + public void initializeTextureSize(BasicTexture texture, int format, int type) { + int target = texture.getTarget(); + GLES20.glBindTexture(target, texture.getId()); + checkError(); + int width = texture.getTextureWidth(); + int height = texture.getTextureHeight(); + GLES20.glTexImage2D(target, 0, format, width, height, 0, format, type, null); + } + + @Override + public void initializeTexture(BasicTexture texture, Bitmap bitmap) { + int target = texture.getTarget(); + GLES20.glBindTexture(target, texture.getId()); + checkError(); + GLUtils.texImage2D(target, 0, bitmap, 0); + } + + @Override + public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap, + int format, int type) { + int target = texture.getTarget(); + GLES20.glBindTexture(target, texture.getId()); + checkError(); + GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type); + } + + @Override + public int uploadBuffer(FloatBuffer buf) { + return uploadBuffer(buf, FLOAT_SIZE); + } + + @Override + public int uploadBuffer(ByteBuffer buf) { + return uploadBuffer(buf, 1); + } + + private int uploadBuffer(Buffer buffer, int elementSize) { + mGLId.glGenBuffers(1, mTempIntArray, 0); + checkError(); + int bufferId = mTempIntArray[0]; + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferId); + checkError(); + GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, buffer.capacity() * elementSize, buffer, + GLES20.GL_STATIC_DRAW); + checkError(); + return bufferId; + } + + public static void checkError() { + int error = GLES20.glGetError(); + if (error != 0) { + Throwable t = new Throwable(); + Log.e(TAG, "GL error: " + error, t); + } + } + + @SuppressWarnings("unused") + private static void printMatrix(String message, float[] m, int offset) { + StringBuilder b = new StringBuilder(message); + for (int i = 0; i < MATRIX_SIZE; i++) { + b.append(' '); + if (i % 4 == 0) { + b.append('\n'); + } + b.append(m[offset + i]); + } + Log.v(TAG, b.toString()); + } + + @Override + public void recoverFromLightCycle() { + GLES20.glViewport(0, 0, mWidth, mHeight); + GLES20.glDisable(GLES20.GL_DEPTH_TEST); + GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA); + checkError(); + } + + @Override + public void getBounds(Rect bounds, int x, int y, int width, int height) { + Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f); + Matrix.scaleM(mTempMatrix, 0, width, height, 1f); + Matrix.multiplyMV(mTempMatrix, MATRIX_SIZE, mTempMatrix, 0, BOUNDS_COORDINATES, 0); + Matrix.multiplyMV(mTempMatrix, MATRIX_SIZE + 4, mTempMatrix, 0, BOUNDS_COORDINATES, 4); + bounds.left = Math.round(mTempMatrix[MATRIX_SIZE]); + bounds.right = Math.round(mTempMatrix[MATRIX_SIZE + 4]); + bounds.top = Math.round(mTempMatrix[MATRIX_SIZE + 1]); + bounds.bottom = Math.round(mTempMatrix[MATRIX_SIZE + 5]); + bounds.sort(); + } + + @Override + public GLId getGLId() { + return mGLId; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLES20IdImpl.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLES20IdImpl.java new file mode 100644 index 000000000..11ce2142e --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLES20IdImpl.java @@ -0,0 +1,42 @@ +package com.hippo.ehviewer.gallery.glrenderer; + +import javax.microedition.khronos.opengles.GL11; +import javax.microedition.khronos.opengles.GL11ExtensionPack; + +import android.opengl.GLES20; + +public class GLES20IdImpl implements GLId { + private final int[] mTempIntArray = new int[1]; + + @Override + public int generateTexture() { + GLES20.glGenTextures(1, mTempIntArray, 0); + GLES20Canvas.checkError(); + return mTempIntArray[0]; + } + + @Override + public void glGenBuffers(int n, int[] buffers, int offset) { + GLES20.glGenBuffers(n, buffers, offset); + GLES20Canvas.checkError(); + } + + @Override + public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) { + GLES20.glDeleteTextures(n, textures, offset); + GLES20Canvas.checkError(); + } + + + @Override + public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) { + GLES20.glDeleteBuffers(n, buffers, offset); + GLES20Canvas.checkError(); + } + + @Override + public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) { + GLES20.glDeleteFramebuffers(n, buffers, offset); + GLES20Canvas.checkError(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLId.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLId.java new file mode 100644 index 000000000..9015f2b41 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLId.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import javax.microedition.khronos.opengles.GL11; +import javax.microedition.khronos.opengles.GL11ExtensionPack; + +// This mimics corresponding GL functions. +public interface GLId { + public int generateTexture(); + + public void glGenBuffers(int n, int[] buffers, int offset); + + public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset); + + public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset); + + public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset); +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLPaint.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLPaint.java new file mode 100644 index 000000000..84e93554f --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/GLPaint.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import junit.framework.Assert; + +public class GLPaint { + private float mLineWidth = 1f; + private int mColor = 0; + + public GLPaint(int color, float lineWidth) { + setColor(color); + setLineWidth(lineWidth); + } + + public void setColor(int color) { + mColor = color; + } + + public int getColor() { + return mColor; + } + + public void setLineWidth(float width) { + Assert.assertTrue(width >= 0); + mLineWidth = width; + } + + public float getLineWidth() { + return mLineWidth; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/ImageTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/ImageTexture.java new file mode 100644 index 000000000..bba87effc --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/ImageTexture.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import javax.microedition.khronos.opengles.GL11; + +import android.opengl.GLES20; +import android.util.Log; + +import com.hippo.ehviewer.gallery.image.Image; + +public class ImageTexture extends BasicTexture implements Uploaded { + + private static final String TAG = ImageTexture.class.getSimpleName(); + + private Image mImage; + private boolean mContentValid = true; + private boolean mOpaque = true; + + public ImageTexture(Image image) { + mImage = image; + setSize(image.getWidth(), image.getHeight()); + } + + public void start() { + if (mImage != null) + mImage.start(); + } + + public void stop() { + if (mImage != null) + mImage.stop(); + } + + public boolean isAnimated() { + if (mImage == null || mImage.isRecycled()) + return false; + else + return mImage.isAnimated(); + } + + @Override + public boolean isOpaque() { + return mOpaque; + } + + public void setOpaque(boolean opaque) { + mOpaque = opaque; + } + + /** + * Whether the content on GPU is valid. + */ + @Override + public boolean isContentValid() { + return isLoaded() && mContentValid; + } + + @Override + protected int getTarget() { + return GL11.GL_TEXTURE_2D; + } + + public int getFormat() { + return mImage.getFormat(); + } + + public int getType() { + return mImage.getType(); + } + + public static void checkError() { + int error = GLES20.glGetError(); + if (error != 0) { + Throwable t = new Throwable(); + Log.e(TAG, "GL error: " + error, t); + } + } + + private void uploadToCanvas(GLCanvas canvas) { + mId = canvas.getGLId().generateTexture(); + int format = getFormat(); + int type = getType(); + + canvas.setTextureParameters(this); + canvas.initializeTextureSize(this, format, type); + + GLES20.glBindTexture(getTarget(), mId); + checkError(); + mImage.render(); + + setAssociatedCanvas(canvas); + mState = STATE_LOADED; + mContentValid = true; + } + + @Override + public void updateContent(GLCanvas canvas) { + if (!isLoaded()) { + uploadToCanvas(canvas); + } else if (isAnimated()) { + GLES20.glBindTexture(getTarget(), mId); + checkError(); + mImage.render(); + + mContentValid = true; + } + } + + @Override + protected boolean onBind(GLCanvas canvas) { + if (mImage == null || mImage.isRecycled()) { + return false; + } else { + updateContent(canvas); + return isContentValid(); + } + } + + @Override + public void recycle() { + super.recycle(); + if (mImage != null) { + mImage.recycle(); + mImage = null; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/MovieTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/MovieTexture.java new file mode 100644 index 000000000..bc7e55a3b --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/MovieTexture.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import junit.framework.Assert; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Movie; + +public class MovieTexture extends CanvasTexture { + private static final int DEFAULT_MOVIE_DURATION = 1000; + + private final Movie mMovie; + private final int mDuration; + private final long mMovieStart; + + private MovieTexture(Movie movie, int width, int height) { + super(width, height); + mMovie = movie; + int dur = mMovie.duration(); + if (dur == 0) + mDuration = DEFAULT_MOVIE_DURATION; + else + mDuration = dur; + mMovieStart = System.currentTimeMillis(); + } + + public static MovieTexture newInstance(Movie movie) { + Assert.assertTrue(movie != null); + return new MovieTexture(movie, movie.width(), movie.height()); + } + + @Override + protected boolean onBind(GLCanvas canvas) { + invalidateContent(); + setSize(mMovie.width(), mMovie.height()); + return super.onBind(canvas); + } + + @Override + protected void onDraw(Canvas canvas, Bitmap backing) { + mMovie.setTime((int)((System.currentTimeMillis() - mMovieStart) % mDuration)); + mMovie.draw(canvas, 0, 0); + } + +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/MultiLineTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/MultiLineTexture.java new file mode 100644 index 000000000..13f3dca9f --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/MultiLineTexture.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; + + +// MultiLineTexture is a texture shows the content of a specified String. +// +// To create a MultiLineTexture, use the newInstance() method and specify +// the String, the font size, and the color. +class MultiLineTexture extends CanvasTexture { + private final Layout mLayout; + + private MultiLineTexture(Layout layout) { + super(layout.getWidth(), layout.getHeight()); + mLayout = layout; + } + + public static MultiLineTexture newInstance( + String text, int maxWidth, float textSize, int color, + Layout.Alignment alignment) { + TextPaint paint = StringTexture.getDefaultPaint(textSize, color); + Layout layout = new StaticLayout(text, 0, text.length(), paint, + maxWidth, alignment, 1, 0, true, null, 0); + + return new MultiLineTexture(layout); + } + + @Override + protected void onDraw(Canvas canvas, Bitmap backing) { + mLayout.draw(canvas); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/NinePatchChunk.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/NinePatchChunk.java new file mode 100644 index 000000000..df204e852 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/NinePatchChunk.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import android.graphics.Rect; + +// See "frameworks/base/include/utils/ResourceTypes.h" for the format of +// NinePatch chunk. +class NinePatchChunk { + + public static final int NO_COLOR = 0x00000001; + public static final int TRANSPARENT_COLOR = 0x00000000; + + public Rect mPaddings = new Rect(); + + public int mDivX[]; + public int mDivY[]; + public int mColor[]; + + private static void readIntArray(int[] data, ByteBuffer buffer) { + for (int i = 0, n = data.length; i < n; ++i) { + data[i] = buffer.getInt(); + } + } + + private static void checkDivCount(int length) { + if (length == 0 || (length & 0x01) != 0) { + throw new RuntimeException("invalid nine-patch: " + length); + } + } + + public static NinePatchChunk deserialize(byte[] data) { + ByteBuffer byteBuffer = + ByteBuffer.wrap(data).order(ByteOrder.nativeOrder()); + + byte wasSerialized = byteBuffer.get(); + if (wasSerialized == 0) return null; + + NinePatchChunk chunk = new NinePatchChunk(); + chunk.mDivX = new int[byteBuffer.get()]; + chunk.mDivY = new int[byteBuffer.get()]; + chunk.mColor = new int[byteBuffer.get()]; + + checkDivCount(chunk.mDivX.length); + checkDivCount(chunk.mDivY.length); + + // skip 8 bytes + byteBuffer.getInt(); + byteBuffer.getInt(); + + chunk.mPaddings.left = byteBuffer.getInt(); + chunk.mPaddings.right = byteBuffer.getInt(); + chunk.mPaddings.top = byteBuffer.getInt(); + chunk.mPaddings.bottom = byteBuffer.getInt(); + + // skip 4 bytes + byteBuffer.getInt(); + + readIntArray(chunk.mDivX, byteBuffer); + readIntArray(chunk.mDivY, byteBuffer); + readIntArray(chunk.mColor, byteBuffer); + + return chunk; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/NinePatchTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/NinePatchTexture.java new file mode 100644 index 000000000..dd2d38a05 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/NinePatchTexture.java @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Rect; + +import com.hippo.ehviewer.util.MathUtils; + +// NinePatchTexture is a texture backed by a NinePatch resource. +// +// getPaddings() returns paddings specified in the NinePatch. +// getNinePatchChunk() returns the layout data specified in the NinePatch. +// +public class NinePatchTexture extends ResourceTexture { + @SuppressWarnings("unused") + private static final String TAG = "NinePatchTexture"; + private NinePatchChunk mChunk; + private final SmallCache mInstanceCache + = new SmallCache(); + + public NinePatchTexture(Context context, int resId) { + super(context, resId); + } + + @Override + protected Bitmap onGetBitmap() { + if (mBitmap != null) return mBitmap; + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Bitmap.Config.ARGB_8888; + Bitmap bitmap = BitmapFactory.decodeResource( + mContext.getResources(), mResId, options); + mBitmap = bitmap; + setSize(bitmap.getWidth(), bitmap.getHeight()); + byte[] chunkData = bitmap.getNinePatchChunk(); + mChunk = chunkData == null + ? null + : NinePatchChunk.deserialize(bitmap.getNinePatchChunk()); + if (mChunk == null) { + throw new RuntimeException("invalid nine-patch image: " + mResId); + } + return bitmap; + } + + public Rect getPaddings() { + // get the paddings from nine patch + if (mChunk == null) onGetBitmap(); + return mChunk.mPaddings; + } + + public NinePatchChunk getNinePatchChunk() { + if (mChunk == null) onGetBitmap(); + return mChunk; + } + + // This is a simple cache for a small number of things. Linear search + // is used because the cache is small. It also tries to remove less used + // item when the cache is full by moving the often-used items to the front. + private static class SmallCache { + private static final int CACHE_SIZE = 16; + private static final int CACHE_SIZE_START_MOVE = CACHE_SIZE / 2; + private final int[] mKey = new int[CACHE_SIZE]; + @SuppressWarnings("unchecked") + private final V[] mValue = (V[]) new Object[CACHE_SIZE]; + private int mCount; // number of items in this cache + + // Puts a value into the cache. If the cache is full, also returns + // a less used item, otherwise returns null. + public V put(int key, V value) { + if (mCount == CACHE_SIZE) { + V old = mValue[CACHE_SIZE - 1]; // remove the last item + mKey[CACHE_SIZE - 1] = key; + mValue[CACHE_SIZE - 1] = value; + return old; + } else { + mKey[mCount] = key; + mValue[mCount] = value; + mCount++; + return null; + } + } + + public V get(int key) { + for (int i = 0; i < mCount; i++) { + if (mKey[i] == key) { + // Move the accessed item one position to the front, so it + // will less likely to be removed when cache is full. Only + // do this if the cache is starting to get full. + if (mCount > CACHE_SIZE_START_MOVE && i > 0) { + int tmpKey = mKey[i]; + mKey[i] = mKey[i - 1]; + mKey[i - 1] = tmpKey; + + V tmpValue = mValue[i]; + mValue[i] = mValue[i - 1]; + mValue[i - 1] = tmpValue; + } + return mValue[i]; + } + } + return null; + } + + public void clear() { + for (int i = 0; i < mCount; i++) { + mValue[i] = null; // make sure it's can be garbage-collected. + } + mCount = 0; + } + + public int size() { + return mCount; + } + + public V valueAt(int i) { + return mValue[i]; + } + } + + private NinePatchInstance findInstance(GLCanvas canvas, int w, int h) { + int key = w; + key = (key << 16) | h; + NinePatchInstance instance = mInstanceCache.get(key); + + if (instance == null) { + instance = new NinePatchInstance(this, w, h); + NinePatchInstance removed = mInstanceCache.put(key, instance); + if (removed != null) { + removed.recycle(canvas); + } + } + + return instance; + } + + @Override + public void draw(GLCanvas canvas, int x, int y, int w, int h) { + if (!isLoaded()) { + mInstanceCache.clear(); + } + + if (w != 0 && h != 0) { + findInstance(canvas, w, h).draw(canvas, this, x, y); + } + } + + @Override + public void recycle() { + super.recycle(); + GLCanvas canvas = mCanvasRef; + if (canvas == null) return; + int n = mInstanceCache.size(); + for (int i = 0; i < n; i++) { + NinePatchInstance instance = mInstanceCache.valueAt(i); + instance.recycle(canvas); + } + mInstanceCache.clear(); + } +} + +// This keeps data for a specialization of NinePatchTexture with the size +// (width, height). We pre-compute the coordinates for efficiency. +class NinePatchInstance { + + @SuppressWarnings("unused") + private static final String TAG = "NinePatchInstance"; + + // We need 16 vertices for a normal nine-patch image (the 4x4 vertices) + private static final int VERTEX_BUFFER_SIZE = 16 * 2; + + // We need 22 indices for a normal nine-patch image, plus 2 for each + // transparent region. Current there are at most 1 transparent region. + private static final int INDEX_BUFFER_SIZE = 22 + 2; + + private FloatBuffer mXyBuffer; + private FloatBuffer mUvBuffer; + private ByteBuffer mIndexBuffer; + + // Names for buffer names: xy, uv, index. + private int mXyBufferName = -1; + private int mUvBufferName; + private int mIndexBufferName; + + private int mIdxCount; + + public NinePatchInstance(NinePatchTexture tex, int width, int height) { + NinePatchChunk chunk = tex.getNinePatchChunk(); + + if (width <= 0 || height <= 0) { + throw new RuntimeException("invalid dimension"); + } + + // The code should be easily extended to handle the general cases by + // allocating more space for buffers. But let's just handle the only + // use case. + if (chunk.mDivX.length != 2 || chunk.mDivY.length != 2) { + throw new RuntimeException("unsupported nine patch"); + } + + float divX[] = new float[4]; + float divY[] = new float[4]; + float divU[] = new float[4]; + float divV[] = new float[4]; + + int nx = stretch(divX, divU, chunk.mDivX, tex.getWidth(), width); + int ny = stretch(divY, divV, chunk.mDivY, tex.getHeight(), height); + + prepareVertexData(divX, divY, divU, divV, nx, ny, chunk.mColor); + } + + /** + * Stretches the texture according to the nine-patch rules. It will + * linearly distribute the strechy parts defined in the nine-patch chunk to + * the target area. + * + *
    +     *                      source
    +     *          /--------------^---------------\
    +     *         u0    u1       u2  u3     u4   u5
    +     * div ---> |fffff|ssssssss|fff|ssssss|ffff| ---> u
    +     *          |    div0    div1 div2   div3  |
    +     *          |     |       /   /      /    /
    +     *          |     |      /   /     /    /
    +     *          |     |     /   /    /    /
    +     *          |fffff|ssss|fff|sss|ffff| ---> x
    +     *         x0    x1   x2  x3  x4   x5
    +     *          \----------v------------/
    +     *                  target
    +     *
    +     * f: fixed segment
    +     * s: stretchy segment
    +     * 
    + * + * @param div the stretch parts defined in nine-patch chunk + * @param source the length of the texture + * @param target the length on the drawing plan + * @param u output, the positions of these dividers in the texture + * coordinate + * @param x output, the corresponding position of these dividers on the + * drawing plan + * @return the number of these dividers. + */ + private static int stretch( + float x[], float u[], int div[], int source, int target) { + int textureSize = MathUtils.nextPowerOf2(source); + float textureBound = (float) source / textureSize; + + float stretch = 0; + for (int i = 0, n = div.length; i < n; i += 2) { + stretch += div[i + 1] - div[i]; + } + + float remaining = target - source + stretch; + + float lastX = 0; + float lastU = 0; + + x[0] = 0; + u[0] = 0; + for (int i = 0, n = div.length; i < n; i += 2) { + // Make the stretchy segment a little smaller to prevent sampling + // on neighboring fixed segments. + // fixed segment + x[i + 1] = lastX + (div[i] - lastU) + 0.5f; + u[i + 1] = Math.min((div[i] + 0.5f) / textureSize, textureBound); + + // stretchy segment + float partU = div[i + 1] - div[i]; + float partX = remaining * partU / stretch; + remaining -= partX; + stretch -= partU; + + lastX = x[i + 1] + partX; + lastU = div[i + 1]; + x[i + 2] = lastX - 0.5f; + u[i + 2] = Math.min((lastU - 0.5f)/ textureSize, textureBound); + } + // the last fixed segment + x[div.length + 1] = target; + u[div.length + 1] = textureBound; + + // remove segments with length 0. + int last = 0; + for (int i = 1, n = div.length + 2; i < n; ++i) { + if ((x[i] - x[last]) < 1f) continue; + x[++last] = x[i]; + u[last] = u[i]; + } + return last + 1; + } + + private void prepareVertexData(float x[], float y[], float u[], float v[], + int nx, int ny, int[] color) { + /* + * Given a 3x3 nine-patch image, the vertex order is defined as the + * following graph: + * + * (0) (1) (2) (3) + * | /| /| /| + * | / | / | / | + * (4) (5) (6) (7) + * | \ | \ | \ | + * | \| \| \| + * (8) (9) (A) (B) + * | /| /| /| + * | / | / | / | + * (C) (D) (E) (F) + * + * And we draw the triangle strip in the following index order: + * + * index: 04152637B6A5948C9DAEBF + */ + int pntCount = 0; + float xy[] = new float[VERTEX_BUFFER_SIZE]; + float uv[] = new float[VERTEX_BUFFER_SIZE]; + for (int j = 0; j < ny; ++j) { + for (int i = 0; i < nx; ++i) { + int xIndex = (pntCount++) << 1; + int yIndex = xIndex + 1; + xy[xIndex] = x[i]; + xy[yIndex] = y[j]; + uv[xIndex] = u[i]; + uv[yIndex] = v[j]; + } + } + + int idxCount = 1; + boolean isForward = false; + byte index[] = new byte[INDEX_BUFFER_SIZE]; + for (int row = 0; row < ny - 1; row++) { + --idxCount; + isForward = !isForward; + + int start, end, inc; + if (isForward) { + start = 0; + end = nx; + inc = 1; + } else { + start = nx - 1; + end = -1; + inc = -1; + } + + for (int col = start; col != end; col += inc) { + int k = row * nx + col; + if (col != start) { + int colorIdx = row * (nx - 1) + col; + if (isForward) colorIdx--; + if (color[colorIdx] == NinePatchChunk.TRANSPARENT_COLOR) { + index[idxCount] = index[idxCount - 1]; + ++idxCount; + index[idxCount++] = (byte) k; + } + } + + index[idxCount++] = (byte) k; + index[idxCount++] = (byte) (k + nx); + } + } + + mIdxCount = idxCount; + + int size = (pntCount * 2) * (Float.SIZE / Byte.SIZE); + mXyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer(); + mUvBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer(); + mIndexBuffer = allocateDirectNativeOrderBuffer(mIdxCount); + + mXyBuffer.put(xy, 0, pntCount * 2).position(0); + mUvBuffer.put(uv, 0, pntCount * 2).position(0); + mIndexBuffer.put(index, 0, idxCount).position(0); + } + + private static ByteBuffer allocateDirectNativeOrderBuffer(int size) { + return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); + } + + private void prepareBuffers(GLCanvas canvas) { + mXyBufferName = canvas.uploadBuffer(mXyBuffer); + mUvBufferName = canvas.uploadBuffer(mUvBuffer); + mIndexBufferName = canvas.uploadBuffer(mIndexBuffer); + + // These buffers are never used again. + mXyBuffer = null; + mUvBuffer = null; + mIndexBuffer = null; + } + + public void draw(GLCanvas canvas, NinePatchTexture tex, int x, int y) { + if (mXyBufferName == -1) { + prepareBuffers(canvas); + } + canvas.drawMesh(tex, x, y, mXyBufferName, mUvBufferName, mIndexBufferName, mIdxCount); + } + + public void recycle(GLCanvas canvas) { + if (mXyBuffer == null) { + canvas.deleteBuffer(mXyBufferName); + canvas.deleteBuffer(mUvBufferName); + canvas.deleteBuffer(mIndexBufferName); + mXyBufferName = -1; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/RawTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/RawTexture.java new file mode 100644 index 000000000..44e1390b8 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/RawTexture.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import javax.microedition.khronos.opengles.GL11; + +import android.util.Log; + +public class RawTexture extends BasicTexture { + private static final String TAG = "RawTexture"; + + private final boolean mOpaque; + private boolean mIsFlipped; + + public RawTexture(int width, int height, boolean opaque) { + mOpaque = opaque; + setSize(width, height); + } + + @Override + public boolean isOpaque() { + return mOpaque; + } + + @Override + public boolean isFlippedVertically() { + return mIsFlipped; + } + + public void setIsFlippedVertically(boolean isFlipped) { + mIsFlipped = isFlipped; + } + + protected void prepare(GLCanvas canvas) { + GLId glId = canvas.getGLId(); + mId = glId.generateTexture(); + canvas.initializeTextureSize(this, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE); + canvas.setTextureParameters(this); + mState = STATE_LOADED; + setAssociatedCanvas(canvas); + } + + @Override + protected boolean onBind(GLCanvas canvas) { + if (isLoaded()) return true; + Log.w(TAG, "lost the content due to context change"); + return false; + } + + @Override + public void yield() { + // we cannot free the texture because we have no backup. + } + + @Override + protected int getTarget() { + return GL11.GL_TEXTURE_2D; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/ResourceTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/ResourceTexture.java new file mode 100644 index 000000000..c2580e630 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/ResourceTexture.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import junit.framework.Assert; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +// ResourceTexture is a texture whose Bitmap is decoded from a resource. +// By default ResourceTexture is not opaque. +public class ResourceTexture extends UploadedTexture { + + protected final Context mContext; + protected final int mResId; + + public ResourceTexture(Context context, int resId) { + Assert.assertNotNull(context); + mContext = context; + mResId = resId; + setOpaque(false); + } + + @Override + protected Bitmap onGetBitmap() { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Bitmap.Config.ARGB_8888; + return BitmapFactory.decodeResource( + mContext.getResources(), mResId, options); + } + + @Override + protected void onFreeBitmap(Bitmap bitmap) { + if (!inFinalizer()) { + bitmap.recycle(); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/StringTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/StringTexture.java new file mode 100644 index 000000000..24f364765 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/StringTexture.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint.FontMetricsInt; +import android.graphics.Typeface; +import android.text.TextPaint; +import android.text.TextUtils; + +// StringTexture is a texture shows the content of a specified String. +// +// To create a StringTexture, use the newInstance() method and specify +// the String, the font size, and the color. +public class StringTexture extends CanvasTexture { + private final String mText; + private final TextPaint mPaint; + private final FontMetricsInt mMetrics; + + private StringTexture(String text, TextPaint paint, + FontMetricsInt metrics, int width, int height) { + super(width, height); + mText = text; + mPaint = paint; + mMetrics = metrics; + } + + public static TextPaint getDefaultPaint(float textSize, int color) { + TextPaint paint = new TextPaint(); + paint.setTextSize(textSize); + paint.setAntiAlias(true); + paint.setColor(color); + paint.setShadowLayer(2f, 0f, 0f, Color.BLACK); + return paint; + } + + public static StringTexture newInstance( + String text, float textSize, int color) { + return newInstance(text, getDefaultPaint(textSize, color)); + } + + public static StringTexture newInstance( + String text, float textSize, int color, + float lengthLimit, boolean isBold) { + TextPaint paint = getDefaultPaint(textSize, color); + if (isBold) { + paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD)); + } + if (lengthLimit > 0) { + text = TextUtils.ellipsize( + text, paint, lengthLimit, TextUtils.TruncateAt.END).toString(); + } + return newInstance(text, paint); + } + + private static StringTexture newInstance(String text, TextPaint paint) { + FontMetricsInt metrics = paint.getFontMetricsInt(); + int width = (int)Math.ceil(paint.measureText(text)); + int height = metrics.bottom - metrics.top; + // The texture size needs to be at least 1x1. + if (width <= 0) width = 1; + if (height <= 0) height = 1; + return new StringTexture(text, paint, metrics, width, height); + } + + @Override + protected void onDraw(Canvas canvas, Bitmap backing) { + canvas.translate(0, -mMetrics.ascent); + canvas.drawText(mText, 0, 0, mPaint); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/Texture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/Texture.java new file mode 100644 index 000000000..2531a1e7c --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/Texture.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + + +// Texture is a rectangular image which can be drawn on GLCanvas. +// The isOpaque() function gives a hint about whether the texture is opaque, +// so the drawing can be done faster. +// +// This is the current texture hierarchy: +// +// Texture +// -- ColorTexture +// -- FadeInTexture +// -- BasicTexture +// -- UploadedTexture +// -- BitmapTexture +// -- Tile +// -- ResourceTexture +// -- NinePatchTexture +// -- CanvasTexture +// -- StringTexture +// +public interface Texture { + public int getWidth(); + public int getHeight(); + public void draw(GLCanvas canvas, int x, int y); + public void draw(GLCanvas canvas, int x, int y, int w, int h); + public boolean isOpaque(); +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/TextureUploader.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/TextureUploader.java new file mode 100644 index 000000000..14332e6e5 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/TextureUploader.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import java.util.ArrayDeque; + +import com.hippo.ehviewer.gallery.ui.GLRoot; +import com.hippo.ehviewer.gallery.ui.GLRoot.OnGLIdleListener; + +public class TextureUploader implements OnGLIdleListener { + private static final int INIT_CAPACITY = 64; + private static final int QUOTA_PER_FRAME = 1; + + private final ArrayDeque mFgTextures = + new ArrayDeque(INIT_CAPACITY); + private final ArrayDeque mBgTextures = + new ArrayDeque(INIT_CAPACITY); + private final GLRoot mGLRoot; + private volatile boolean mIsQueued = false; + + public TextureUploader(GLRoot root) { + mGLRoot = root; + } + + public synchronized void clear() { + while (!mFgTextures.isEmpty()) { + mFgTextures.pop().setIsUploading(false); + } + while (!mBgTextures.isEmpty()) { + mBgTextures.pop().setIsUploading(false); + } + } + + // caller should hold synchronized on "this" + private void queueSelfIfNeed() { + if (mIsQueued) return; + mIsQueued = true; + mGLRoot.addOnGLIdleListener(this); + } + + public synchronized void addBgTexture(UploadedTexture t) { + if (t.isContentValid()) return; + mBgTextures.addLast(t); + t.setIsUploading(true); + queueSelfIfNeed(); + } + + public synchronized void addFgTexture(UploadedTexture t) { + if (t.isContentValid()) return; + mFgTextures.addLast(t); + t.setIsUploading(true); + queueSelfIfNeed(); + } + + private int upload(GLCanvas canvas, ArrayDeque deque, + int uploadQuota, boolean isBackground) { + while (uploadQuota > 0) { + UploadedTexture t; + synchronized (this) { + if (deque.isEmpty()) break; + t = deque.removeFirst(); + t.setIsUploading(false); + if (t.isContentValid()) continue; + + // this has to be protected by the synchronized block + // to prevent the inner bitmap get recycled + t.updateContent(canvas); + } + + // It will took some more time for a texture to be drawn for + // the first time. + // Thus, when scrolling, if a new column appears on screen, + // it may cause a UI jank even these textures are uploaded. + if (isBackground) t.draw(canvas, 0, 0); + --uploadQuota; + } + return uploadQuota; + } + + @Override + public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) { + int uploadQuota = QUOTA_PER_FRAME; + uploadQuota = upload(canvas, mFgTextures, uploadQuota, false); + if (uploadQuota < QUOTA_PER_FRAME) mGLRoot.requestRender(); + upload(canvas, mBgTextures, uploadQuota, true); + synchronized (this) { + mIsQueued = !mFgTextures.isEmpty() || !mBgTextures.isEmpty(); + return mIsQueued; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/TiledTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/TiledTexture.java new file mode 100644 index 000000000..3831a02e0 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/TiledTexture.java @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import java.util.ArrayDeque; +import java.util.ArrayList; + +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffXfermode; +import android.graphics.RectF; +import android.os.SystemClock; + +import com.hippo.ehviewer.gallery.ui.GLRoot; +import com.hippo.ehviewer.gallery.ui.GLRoot.OnGLIdleListener; + +// This class is similar to BitmapTexture, except the bitmap is +// split into tiles. By doing so, we may increase the time required to +// upload the whole bitmap but we reduce the time of uploading each tile +// so it make the animation more smooth and prevents jank. +public class TiledTexture implements Texture { + private static final int CONTENT_SIZE = 254; + private static final int BORDER_SIZE = 1; + private static final int TILE_SIZE = CONTENT_SIZE + 2 * BORDER_SIZE; + private static final int INIT_CAPACITY = 8; + + // We are targeting at 60fps, so we have 16ms for each frame. + // In this 16ms, we use about 4~8 ms to upload tiles. + private static final long UPLOAD_TILE_LIMIT = 4; // ms + + private static Tile sFreeTileHead = null; + private static final Object sFreeTileLock = new Object(); + + private static Bitmap sUploadBitmap; + private static Canvas sCanvas; + private static Paint sBitmapPaint; + private static Paint sPaint; + + private int mUploadIndex = 0; + + private final Tile[] mTiles; // Can be modified in different threads. + // Should be protected by "synchronized." + private final int mWidth; + private final int mHeight; + private final RectF mSrcRect = new RectF(); + private final RectF mDestRect = new RectF(); + + public static class Uploader implements OnGLIdleListener { + private final ArrayDeque mTextures = + new ArrayDeque(INIT_CAPACITY); + + private final GLRoot mGlRoot; + private boolean mIsQueued = false; + + public Uploader(GLRoot glRoot) { + mGlRoot = glRoot; + } + + public synchronized void clear() { + mTextures.clear(); + } + + public synchronized void addTexture(TiledTexture t) { + if (t.isReady()) return; + mTextures.addLast(t); + + if (mIsQueued) return; + mIsQueued = true; + mGlRoot.addOnGLIdleListener(this); + } + + @Override + public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) { + ArrayDeque deque = mTextures; + synchronized (this) { + long now = SystemClock.uptimeMillis(); + long dueTime = now + UPLOAD_TILE_LIMIT; + while (now < dueTime && !deque.isEmpty()) { + TiledTexture t = deque.peekFirst(); + if (t.uploadNextTile(canvas)) { + deque.removeFirst(); + mGlRoot.requestRender(); + } + now = SystemClock.uptimeMillis(); + } + mIsQueued = !mTextures.isEmpty(); + + // return true to keep this listener in the queue + return mIsQueued; + } + } + } + + private static class Tile extends UploadedTexture { + public int offsetX; + public int offsetY; + public Bitmap bitmap; + public Tile nextFreeTile; + public int contentWidth; + public int contentHeight; + + @Override + public void setSize(int width, int height) { + contentWidth = width; + contentHeight = height; + mWidth = width + 2 * BORDER_SIZE; + mHeight = height + 2 * BORDER_SIZE; + mTextureWidth = TILE_SIZE; + mTextureHeight = TILE_SIZE; + } + + @Override + protected Bitmap onGetBitmap() { + // make a local copy of the reference to the bitmap, + // since it might be null'd in a different thread. b/8694871 + Bitmap localBitmapRef = bitmap; + bitmap = null; + + if (localBitmapRef != null) { + int x = BORDER_SIZE - offsetX; + int y = BORDER_SIZE - offsetY; + int r = localBitmapRef.getWidth() + x; + int b = localBitmapRef.getHeight() + y; + sCanvas.drawBitmap(localBitmapRef, x, y, sBitmapPaint); + localBitmapRef = null; + + // draw borders if need + if (x > 0) sCanvas.drawLine(x - 1, 0, x - 1, TILE_SIZE, sPaint); + if (y > 0) sCanvas.drawLine(0, y - 1, TILE_SIZE, y - 1, sPaint); + if (r < CONTENT_SIZE) sCanvas.drawLine(r, 0, r, TILE_SIZE, sPaint); + if (b < CONTENT_SIZE) sCanvas.drawLine(0, b, TILE_SIZE, b, sPaint); + } + + return sUploadBitmap; + } + + @Override + protected void onFreeBitmap(Bitmap bitmap) { + // do nothing + } + } + + private static void freeTile(Tile tile) { + tile.invalidateContent(); + tile.bitmap = null; + synchronized (sFreeTileLock) { + tile.nextFreeTile = sFreeTileHead; + sFreeTileHead = tile; + } + } + + private static Tile obtainTile() { + synchronized (sFreeTileLock) { + Tile result = sFreeTileHead; + if (result == null) return new Tile(); + sFreeTileHead = result.nextFreeTile; + result.nextFreeTile = null; + return result; + } + } + + private boolean uploadNextTile(GLCanvas canvas) { + if (mUploadIndex == mTiles.length) return true; + + synchronized (mTiles) { + Tile next = mTiles[mUploadIndex++]; + + // Make sure tile has not already been recycled by the time + // this is called (race condition in onGLIdle) + if (next.bitmap != null) { + boolean hasBeenLoad = next.isLoaded(); + next.updateContent(canvas); + + // It will take some time for a texture to be drawn for the first + // time. When scrolling, we need to draw several tiles on the screen + // at the same time. It may cause a UI jank even these textures has + // been uploaded. + if (!hasBeenLoad) next.draw(canvas, 0, 0); + } + } + return mUploadIndex == mTiles.length; + } + + public TiledTexture(Bitmap bitmap) { + mWidth = bitmap.getWidth(); + mHeight = bitmap.getHeight(); + ArrayList list = new ArrayList(); + + for (int x = 0, w = mWidth; x < w; x += CONTENT_SIZE) { + for (int y = 0, h = mHeight; y < h; y += CONTENT_SIZE) { + Tile tile = obtainTile(); + tile.offsetX = x; + tile.offsetY = y; + tile.bitmap = bitmap; + tile.setSize( + Math.min(CONTENT_SIZE, mWidth - x), + Math.min(CONTENT_SIZE, mHeight - y)); + list.add(tile); + } + } + mTiles = list.toArray(new Tile[list.size()]); + } + + public boolean isReady() { + return mUploadIndex == mTiles.length; + } + + // Can be called in UI thread. + public void recycle() { + synchronized (mTiles) { + for (int i = 0, n = mTiles.length; i < n; ++i) { + freeTile(mTiles[i]); + } + } + } + + public static void freeResources() { + sUploadBitmap = null; + sCanvas = null; + sBitmapPaint = null; + sPaint = null; + } + + public static void prepareResources() { + sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Config.ARGB_8888); + sCanvas = new Canvas(sUploadBitmap); + sBitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG); + sBitmapPaint.setXfermode(new PorterDuffXfermode(Mode.SRC)); + sPaint = new Paint(); + sPaint.setXfermode(new PorterDuffXfermode(Mode.SRC)); + sPaint.setColor(Color.TRANSPARENT); + } + + // We want to draw the "source" on the "target". + // This method is to find the "output" rectangle which is + // the corresponding area of the "src". + // (x,y) target + // (x0,y0) source +---------------+ + // +----------+ | | + // | src | | output | + // | +--+ | linear map | +----+ | + // | +--+ | ----------> | | | | + // | | by (scaleX, scaleY) | +----+ | + // +----------+ | | + // Texture +---------------+ + // Canvas + private static void mapRect(RectF output, + RectF src, float x0, float y0, float x, float y, float scaleX, + float scaleY) { + output.set(x + (src.left - x0) * scaleX, + y + (src.top - y0) * scaleY, + x + (src.right - x0) * scaleX, + y + (src.bottom - y0) * scaleY); + } + + // Draws a mixed color of this texture and a specified color onto the + // a rectangle. The used color is: from * (1 - ratio) + to * ratio. + public void drawMixed(GLCanvas canvas, int color, float ratio, + int x, int y, int width, int height) { + RectF src = mSrcRect; + RectF dest = mDestRect; + float scaleX = (float) width / mWidth; + float scaleY = (float) height / mHeight; + synchronized (mTiles) { + for (int i = 0, n = mTiles.length; i < n; ++i) { + Tile t = mTiles[i]; + src.set(0, 0, t.contentWidth, t.contentHeight); + src.offset(t.offsetX, t.offsetY); + mapRect(dest, src, 0, 0, x, y, scaleX, scaleY); + src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); + canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect); + } + } + } + + // Draws the texture on to the specified rectangle. + @Override + public void draw(GLCanvas canvas, int x, int y, int width, int height) { + RectF src = mSrcRect; + RectF dest = mDestRect; + float scaleX = (float) width / mWidth; + float scaleY = (float) height / mHeight; + synchronized (mTiles) { + for (int i = 0, n = mTiles.length; i < n; ++i) { + Tile t = mTiles[i]; + src.set(0, 0, t.contentWidth, t.contentHeight); + src.offset(t.offsetX, t.offsetY); + mapRect(dest, src, 0, 0, x, y, scaleX, scaleY); + src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); + canvas.drawTexture(t, mSrcRect, mDestRect); + } + } + } + + // Draws a sub region of this texture on to the specified rectangle. + public void draw(GLCanvas canvas, RectF source, RectF target) { + RectF src = mSrcRect; + RectF dest = mDestRect; + float x0 = source.left; + float y0 = source.top; + float x = target.left; + float y = target.top; + float scaleX = target.width() / source.width(); + float scaleY = target.height() / source.height(); + + synchronized (mTiles) { + for (int i = 0, n = mTiles.length; i < n; ++i) { + Tile t = mTiles[i]; + src.set(0, 0, t.contentWidth, t.contentHeight); + src.offset(t.offsetX, t.offsetY); + if (!src.intersect(source)) continue; + mapRect(dest, src, x0, y0, x, y, scaleX, scaleY); + src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); + canvas.drawTexture(t, src, dest); + } + } + } + + @Override + public int getWidth() { + return mWidth; + } + + @Override + public int getHeight() { + return mHeight; + } + + @Override + public void draw(GLCanvas canvas, int x, int y) { + draw(canvas, x, y, mWidth, mHeight); + } + + @Override + public boolean isOpaque() { + return false; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/Uploaded.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/Uploaded.java new file mode 100644 index 000000000..53082666a --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/Uploaded.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +public interface Uploaded { + + public boolean isContentValid(); + + public void updateContent(GLCanvas canvas); + +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/UploadedTexture.java b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/UploadedTexture.java new file mode 100644 index 000000000..11be8dc9b --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/glrenderer/UploadedTexture.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.glrenderer; + +import java.util.HashMap; + +import javax.microedition.khronos.opengles.GL11; + +import junit.framework.Assert; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.opengl.GLUtils; + +// UploadedTextures use a Bitmap for the content of the texture. +// +// Subclasses should implement onGetBitmap() to provide the Bitmap and +// implement onFreeBitmap(mBitmap) which will be called when the Bitmap +// is not needed anymore. +// +// isContentValid() is meaningful only when the isLoaded() returns true. +// It means whether the content needs to be updated. +// +// The user of this class should call recycle() when the texture is not +// needed anymore. +// +// By default an UploadedTexture is opaque (so it can be drawn faster without +// blending). The user or subclass can override it using setOpaque(). +public abstract class UploadedTexture extends BasicTexture implements Uploaded { + + // To prevent keeping allocation the borders, we store those used borders here. + // Since the length will be power of two, it won't use too much memory. + private static HashMap sBorderLines = + new HashMap(); + private static BorderKey sBorderKey = new BorderKey(); + + @SuppressWarnings("unused") + private static final String TAG = "Texture"; + private boolean mContentValid = true; + + // indicate this textures is being uploaded in background + private boolean mIsUploading = false; + private boolean mOpaque = true; + private boolean mThrottled = false; + private static int sUploadedCount; + private static final int UPLOAD_LIMIT = 100; + + protected Bitmap mBitmap; + private int mBorder; + + protected UploadedTexture() { + this(false); + } + + protected UploadedTexture(boolean hasBorder) { + super(null, 0, STATE_UNLOADED); + if (hasBorder) { + setBorder(true); + mBorder = 1; + } + } + + protected void setIsUploading(boolean uploading) { + mIsUploading = uploading; + } + + public boolean isUploading() { + return mIsUploading; + } + + private static class BorderKey implements Cloneable { + public boolean vertical; + public Config config; + public int length; + + @Override + public int hashCode() { + int x = config.hashCode() ^ length; + return vertical ? x : -x; + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof BorderKey)) return false; + BorderKey o = (BorderKey) object; + return vertical == o.vertical + && config == o.config && length == o.length; + } + + @Override + public BorderKey clone() { + try { + return (BorderKey) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(e); + } + } + } + + protected void setThrottled(boolean throttled) { + mThrottled = throttled; + } + + private static Bitmap getBorderLine( + boolean vertical, Config config, int length) { + BorderKey key = sBorderKey; + key.vertical = vertical; + key.config = config; + key.length = length; + Bitmap bitmap = sBorderLines.get(key); + if (bitmap == null) { + bitmap = vertical + ? Bitmap.createBitmap(1, length, config) + : Bitmap.createBitmap(length, 1, config); + sBorderLines.put(key.clone(), bitmap); + } + return bitmap; + } + + private Bitmap getBitmap() { + if (mBitmap == null) { + mBitmap = onGetBitmap(); + int w = mBitmap.getWidth() + mBorder * 2; + int h = mBitmap.getHeight() + mBorder * 2; + if (mWidth == UNSPECIFIED) { + setSize(w, h); + } + } + return mBitmap; + } + + private void freeBitmap() { + Assert.assertTrue(mBitmap != null); + onFreeBitmap(mBitmap); + mBitmap = null; + } + + @Override + public int getWidth() { + if (mWidth == UNSPECIFIED) getBitmap(); + return mWidth; + } + + @Override + public int getHeight() { + if (mWidth == UNSPECIFIED) getBitmap(); + return mHeight; + } + + protected abstract Bitmap onGetBitmap(); + + protected abstract void onFreeBitmap(Bitmap bitmap); + + protected void invalidateContent() { + if (mBitmap != null) freeBitmap(); + mContentValid = false; + mWidth = UNSPECIFIED; + mHeight = UNSPECIFIED; + } + + /** + * Whether the content on GPU is valid. + */ + @Override + public boolean isContentValid() { + return isLoaded() && mContentValid; + } + + /** + * Updates the content on GPU's memory. + * @param canvas + */ + @Override + public void updateContent(GLCanvas canvas) { + if (!isLoaded()) { + if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) { + return; + } + uploadToCanvas(canvas); + } else if (!mContentValid) { + Bitmap bitmap = getBitmap(); + int format = GLUtils.getInternalFormat(bitmap); + int type = GLUtils.getType(bitmap); + canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type); + freeBitmap(); + mContentValid = true; + } + } + + public static void resetUploadLimit() { + sUploadedCount = 0; + } + + public static boolean uploadLimitReached() { + return sUploadedCount > UPLOAD_LIMIT; + } + + private void uploadToCanvas(GLCanvas canvas) { + + Bitmap bitmap = getBitmap(); + if (bitmap != null) { + try { + int bWidth = bitmap.getWidth(); + int bHeight = bitmap.getHeight(); + int texWidth = getTextureWidth(); + int texHeight = getTextureHeight(); + + Assert.assertTrue(bWidth <= texWidth && bHeight <= texHeight); + + // Upload the bitmap to a new texture. + mId = canvas.getGLId().generateTexture(); + canvas.setTextureParameters(this); + + if (bWidth == texWidth && bHeight == texHeight) { + canvas.initializeTexture(this, bitmap); + } else { + int format = GLUtils.getInternalFormat(bitmap); + int type = GLUtils.getType(bitmap); + Config config = bitmap.getConfig(); + + canvas.initializeTextureSize(this, format, type); + canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type); + + if (mBorder > 0) { + // Left border + Bitmap line = getBorderLine(true, config, texHeight); + canvas.texSubImage2D(this, 0, 0, line, format, type); + + // Top border + line = getBorderLine(false, config, texWidth); + canvas.texSubImage2D(this, 0, 0, line, format, type); + } + + // Right border + if (mBorder + bWidth < texWidth) { + Bitmap line = getBorderLine(true, config, texHeight); + canvas.texSubImage2D(this, mBorder + bWidth, 0, line, format, type); + } + + // Bottom border + if (mBorder + bHeight < texHeight) { + Bitmap line = getBorderLine(false, config, texWidth); + canvas.texSubImage2D(this, 0, mBorder + bHeight, line, format, type); + } + } + } finally { + freeBitmap(); + } + // Update texture state. + setAssociatedCanvas(canvas); + mState = STATE_LOADED; + mContentValid = true; + } else { + mState = STATE_ERROR; + throw new RuntimeException("Texture load fail, no bitmap"); + } + } + + @Override + protected boolean onBind(GLCanvas canvas) { + updateContent(canvas); + return isContentValid(); + } + + @Override + protected int getTarget() { + return GL11.GL_TEXTURE_2D; + } + + public void setOpaque(boolean isOpaque) { + mOpaque = isOpaque; + } + + @Override + public boolean isOpaque() { + return mOpaque; + } + + @Override + public void recycle() { + super.recycle(); + if (mBitmap != null) freeBitmap(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/image/GifImage.java b/app/src/main/java/com/hippo/ehviewer/gallery/image/GifImage.java new file mode 100644 index 000000000..b2140a487 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/image/GifImage.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.image; + +import com.hippo.ehviewer.util.Log; + +public class GifImage extends Image { + + private static final String TAG = Image.class.getSimpleName(); + + private final int mImageCount; + private final int[] mDelayArray; + private long mDelaySum; + private volatile long mStartTime; + private volatile long mStopTime = -1; + private volatile boolean mRunning = true; + + private int mLastIndex = -1; + + protected GifImage(long nativeImage, int fileFormat, int width, int height, + int format, int type, int[] delayArray) { + super(nativeImage, fileFormat, width, height, format, type); + mImageCount = delayArray.length; + mDelayArray = delayArray; + + mDelaySum = 0; + for (int delay : delayArray) + mDelaySum += delay; + } + + private int getCurIndex() { + int index = 0; + if (mRunning) { + if (mLastIndex == -1) { + index = 0; + mStartTime = System.currentTimeMillis(); + } else { + long curTime = System.currentTimeMillis(); + long time = (curTime - mStartTime) % mDelaySum; + int end = mImageCount - 1; + for (; index < end && (time -= mDelayArray[index]) > 0; index++) + ; + } + return index; + } else { + return mLastIndex == -1 ? index : mLastIndex; + } + } + + @Override + public void start() { + if (!mRunning) { + mRunning = true; + long curTime = System.currentTimeMillis(); + mStartTime = curTime - (mStopTime == -1 ? curTime : mStopTime) + + mStartTime; + } + } + + @Override + public void stop() { + if (mRunning) { + mRunning = false; + mStopTime = System.currentTimeMillis(); + } + } + + @Override + public boolean isAnimated() { + return mImageCount > 1; + } + + @Override + public void render() { + render(getCurIndex()); + } + + public void render(int index) { + if (mNativeImage != 0) { + if (index >= mImageCount || index < 0) { + Log.d(TAG, "GifImage try render index " + index + ", but image count is " + mImageCount); + return; + } + + if (mLastIndex != index) { + nativeRender(mFormat, mType, mNativeImage, FILE_FORMAT_GIF, + index); + mLastIndex = index; + } + } + } + + static { + System.loadLibrary("image"); + } + + private static native void nativeRender(int format, int type, + long nativeImage, int fileFormat, int index); +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/image/Image.java b/app/src/main/java/com/hippo/ehviewer/gallery/image/Image.java new file mode 100644 index 000000000..8371a0b4f --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/image/Image.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.image; + +import java.io.InputStream; + +import javax.microedition.khronos.opengles.GL11; + +public class Image { + + private static final String TAG = Image.class.getSimpleName(); + + public static final int FILE_FORMAT_JPEG = 0x0; + public static final int FILE_FORMAT_PNG = 0x1; + public static final int FILE_FORMAT_BMP = 0x2; + public static final int FILE_FORMAT_GIF = 0x3; + + public static final int FORMAT_AUTO = 0x0; + public static final int FORMAT_GRAY = GL11.GL_LUMINANCE; + public static final int FORMAT_GRAY_ALPHA = GL11.GL_LUMINANCE_ALPHA; + public static final int FORMAT_RGB = GL11.GL_RGB; + public static final int FORMAT_RGBA = GL11.GL_RGBA; + + protected long mNativeImage; + protected final int mFileFormat; + protected final int mWidth; + protected final int mHeight; + + protected final int mFormat; + protected final int mType; + + protected Image(long nativeImage, int fileFormat, int width, int height, + int format, int type) { + mNativeImage = nativeImage; + mFileFormat = fileFormat; + mWidth = width; + mHeight = height; + mFormat = format; + mType = type; + } + + public int getFileFormat() { + return mFileFormat; + } + + public int getWidth() { + return mWidth; + } + + public int getHeight() { + return mHeight; + } + + public int getFormat() { + return mFormat; + } + + public int getType() { + return mType; + } + + public boolean isAnimated() { + return false; + } + + public void start() { + // Empty + } + + public void stop() { + // Empty + } + + public void render() { + if (mNativeImage != 0) { + nativeRender(mFormat, mType, mNativeImage, mFileFormat); + } + } + + public void recycle() { + if (mNativeImage != 0) { + nativeFree(mNativeImage, mFileFormat); + mNativeImage = 0; + } + } + + public boolean isRecycled() { + return mNativeImage == 0; + } + + /** + * format can be GL11.GL_RGB, GL11.GL_RGBA, GL11.GL_LUMINANCE, + * GL11.GL_LUMINANCE_ALPHA + * + * @param is + * @param format + * @return + */ + public static final Image decodeStream(InputStream is, int format) { + return nativeDecodeStream(is, format); + } + + public static final Image decodeFile(String pathName, int format) { + return nativeDecodeFile(pathName, format); + } + + static { + System.loadLibrary("image"); + } + + private static native Image nativeDecodeStream(InputStream is, int format); + + private static native Image nativeDecodeFile(String pathName, int format); + + private static native void nativeFree(long nativeImage, int format); + + private static native void nativeRender(int format, int type, + long nativeImage, int fileFormat); +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/AnimationTime.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/AnimationTime.java new file mode 100644 index 000000000..19bb91ca2 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/AnimationTime.java @@ -0,0 +1,45 @@ + +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.ui; + +import android.os.SystemClock; + +// +// The animation time should ideally be the vsync time the frame will be +// displayed, but that is an unknown time in the future. So we use the system +// time just after eglSwapBuffers (when GLSurfaceView.onDrawFrame is called) +// as a approximation. +// +public class AnimationTime { + private static volatile long sTime; + + // Sets current time as the animation time. + public static void update() { + sTime = SystemClock.uptimeMillis(); + } + + // Returns the animation time. + public static long get() { + return sTime; + } + + public static long startTime() { + sTime = SystemClock.uptimeMillis(); + return sTime; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/DownUpDetector.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/DownUpDetector.java new file mode 100644 index 000000000..ebb0bb401 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/DownUpDetector.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.ui; + +import android.view.MotionEvent; + +public class DownUpDetector { + public interface DownUpListener { + void onDown(MotionEvent e); + void onUp(MotionEvent e); + } + + private boolean mStillDown; + private final DownUpListener mListener; + + public DownUpDetector(DownUpListener listener) { + mListener = listener; + } + + private void setState(boolean down, MotionEvent e) { + if (down == mStillDown) return; + mStillDown = down; + if (down) { + mListener.onDown(e); + } else { + mListener.onUp(e); + } + } + + public void onTouchEvent(MotionEvent ev) { + switch (ev.getAction() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + setState(true, ev); + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_POINTER_DOWN: // Multitouch event - abort. + setState(false, ev); + break; + } + } + + public boolean isDown() { + return mStillDown; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/GLRoot.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GLRoot.java new file mode 100644 index 000000000..9a56a4395 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GLRoot.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.ui; + +import android.content.Context; +import android.graphics.Matrix; + +import com.hippo.ehviewer.gallery.anim.CanvasAnimation; +import com.hippo.ehviewer.gallery.glrenderer.GLCanvas; + +public interface GLRoot { + + // Listener will be called when GL is idle AND before each frame. + // Mainly used for uploading textures. + public static interface OnGLIdleListener { + public boolean onGLIdle( + GLCanvas canvas, boolean renderRequested); + } + + public void addOnGLIdleListener(OnGLIdleListener listener); + public void registerLaunchedAnimation(CanvasAnimation animation); + public void requestRenderForced(); + public void requestRender(); + public void requestLayoutContentPane(); + + public void lockRenderThread(); + public void unlockRenderThread(); + + public void setContentPane(GLView content); + public void setOrientationSource(OrientationSource source); + public int getDisplayRotation(); + public int getCompensation(); + public Matrix getCompensationMatrix(); + public void freeze(); + public void unfreeze(); + public void setLightsOutMode(boolean enabled); + + public Context getContext(); +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/GLRootView.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GLRootView.java new file mode 100644 index 000000000..b47e5a4e9 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GLRootView.java @@ -0,0 +1,585 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.ui; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; +import javax.microedition.khronos.opengles.GL11; + +import android.annotation.TargetApi; +import android.app.ActivityManager; +import android.content.Context; +import android.content.pm.ConfigurationInfo; +import android.graphics.Matrix; +import android.opengl.GLSurfaceView; +import android.os.Build; +import android.os.Process; +import android.os.SystemClock; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.View; + +import com.hippo.ehviewer.gallery.anim.CanvasAnimation; +import com.hippo.ehviewer.gallery.glrenderer.BasicTexture; +import com.hippo.ehviewer.gallery.glrenderer.GLCanvas; +import com.hippo.ehviewer.gallery.glrenderer.GLES11Canvas; +import com.hippo.ehviewer.gallery.glrenderer.GLES20Canvas; +import com.hippo.ehviewer.gallery.glrenderer.UploadedTexture; +import com.hippo.ehviewer.gallery.util.GalleryUtils; +import com.hippo.ehviewer.gallery.util.MotionEventHelper; +import com.hippo.ehviewer.util.Log; +import com.hippo.ehviewer.util.Utils; + +// The root component of all GLViews. The rendering is done in GL +// thread while the event handling is done in the main thread. To synchronize +// the two threads, the entry points of this package need to synchronize on the +// GLRootView instance unless it can be proved that the rendering +// thread won't access the same thing as the method. The entry points include: +// (1) The public methods of HeadUpDisplay +// (2) The public methods of CameraHeadUpDisplay +// (3) The overridden methods in GLRootView. +public class GLRootView extends GLSurfaceView + implements GLSurfaceView.Renderer, GLRoot { + private static final String TAG = "GLRootView"; + + private static final boolean DEBUG_FPS = false; + private int mFrameCount = 0; + private long mFrameCountingStart = 0; + + private static final boolean DEBUG_INVALIDATE = false; + private int mInvalidateColor = 0; + + private static final boolean DEBUG_DRAWING_STAT = false; + + private static final int FLAG_INITIALIZED = 1; + private static final int FLAG_NEED_LAYOUT = 2; + + private GL11 mGL; + private GLCanvas mCanvas; + private GLView mContentView; + + private OrientationSource mOrientationSource; + // mCompensation is the difference between the UI orientation on GLCanvas + // and the framework orientation. See OrientationManager for details. + private int mCompensation; + // mCompensationMatrix maps the coordinates of touch events. It is kept sync + // with mCompensation. + private final Matrix mCompensationMatrix = new Matrix(); + private int mDisplayRotation; + + private int mFlags = FLAG_NEED_LAYOUT; + private volatile boolean mRenderRequested = false; + + private final ArrayList mAnimations = + new ArrayList(); + + private final ArrayDeque mIdleListeners = + new ArrayDeque(); + + private final IdleRunner mIdleRunner = new IdleRunner(); + + private final ReentrantLock mRenderLock = new ReentrantLock(); + private final Condition mFreezeCondition = + mRenderLock.newCondition(); + private boolean mFreeze; + + private boolean mInDownState = false; + + private final boolean mSupportGLES20; + + public GLRootView(Context context) { + this(context, null); + } + + private static boolean checkGLES20Support(Context context) { + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + ConfigurationInfo info = am.getDeviceConfigurationInfo(); + return info.reqGlEsVersion >= 0x20000; + } + + @SuppressWarnings("deprecation") + public GLRootView(Context context, AttributeSet attrs) { + super(context, attrs); + mSupportGLES20 = checkGLES20Support(context); + mFlags |= FLAG_INITIALIZED; + setBackgroundDrawable(null); + setEGLContextClientVersion(mSupportGLES20 ? 2 : 1); + + // We can't detect 888_PIXEL_FORMAT by Build.VERSION.SDK_INT, + // for example CyanogenMod use ro.opengles.surface.rgb565, + // so just use default EGLConfigChooser. + // And we do not need depth buffer + setEGLConfigChooser(false); + setRenderer(this); + + // Uncomment this to enable gl error check. + // setDebugFlags(DEBUG_CHECK_GL_ERROR); + } + + @Override + public void registerLaunchedAnimation(CanvasAnimation animation) { + // Register the newly launched animation so that we can set the start + // time more precisely. (Usually, it takes much longer for first + // rendering, so we set the animation start time as the time we + // complete rendering) + mAnimations.add(animation); + } + + @Override + public void addOnGLIdleListener(OnGLIdleListener listener) { + synchronized (mIdleListeners) { + mIdleListeners.addLast(listener); + mIdleRunner.enable(); + } + } + + @Override + public void setContentPane(GLView content) { + if (mContentView == content) return; + if (mContentView != null) { + if (mInDownState) { + long now = SystemClock.uptimeMillis(); + MotionEvent cancelEvent = MotionEvent.obtain( + now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0); + mContentView.dispatchTouchEvent(cancelEvent); + cancelEvent.recycle(); + mInDownState = false; + } + mContentView.detachFromRoot(); + BasicTexture.yieldAllTextures(); + } + mContentView = content; + if (content != null) { + content.attachToRoot(this); + requestLayoutContentPane(); + } + } + + @Override + public void requestRenderForced() { + superRequestRender(); + } + + @Override + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + public void requestRender() { + if (DEBUG_INVALIDATE) { + StackTraceElement e = Thread.currentThread().getStackTrace()[4]; + String caller = e.getFileName() + ":" + e.getLineNumber() + " "; + Log.d(TAG, "invalidate: " + caller); + } + if (mRenderRequested) return; + mRenderRequested = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + postOnAnimation(mRequestRenderOnAnimationFrame); + } else { + super.requestRender(); + } + } + + private final Runnable mRequestRenderOnAnimationFrame = new Runnable() { + @Override + public void run() { + superRequestRender(); + } + }; + + private void superRequestRender() { + super.requestRender(); + } + + @Override + public void requestLayoutContentPane() { + mRenderLock.lock(); + try { + if (mContentView == null || (mFlags & FLAG_NEED_LAYOUT) != 0) return; + + // "View" system will invoke onLayout() for initialization(bug ?), we + // have to ignore it since the GLThread is not ready yet. + if ((mFlags & FLAG_INITIALIZED) == 0) return; + + mFlags |= FLAG_NEED_LAYOUT; + requestRender(); + } finally { + mRenderLock.unlock(); + } + } + + private void layoutContentPane() { + mFlags &= ~FLAG_NEED_LAYOUT; + + int w = getWidth(); + int h = getHeight(); + int displayRotation = 0; + int compensation = 0; + + // Get the new orientation values + if (mOrientationSource != null) { + displayRotation = mOrientationSource.getDisplayRotation(); + compensation = mOrientationSource.getCompensation(); + } else { + displayRotation = 0; + compensation = 0; + } + + if (mCompensation != compensation) { + mCompensation = compensation; + if (mCompensation % 180 != 0) { + mCompensationMatrix.setRotate(mCompensation); + // move center to origin before rotation + mCompensationMatrix.preTranslate(-w / 2, -h / 2); + // align with the new origin after rotation + mCompensationMatrix.postTranslate(h / 2, w / 2); + } else { + mCompensationMatrix.setRotate(mCompensation, w / 2, h / 2); + } + } + mDisplayRotation = displayRotation; + + // Do the actual layout. + if (mCompensation % 180 != 0) { + int tmp = w; + w = h; + h = tmp; + } + Log.i(TAG, "layout content pane " + w + "x" + h + + " (compensation " + mCompensation + ")"); + if (mContentView != null && w != 0 && h != 0) { + mContentView.layout(0, 0, w, h); + } + // Uncomment this to dump the view hierarchy. + //mContentView.dumpTree(""); + } + + @Override + protected void onLayout( + boolean changed, int left, int top, int right, int bottom) { + if (changed) requestLayoutContentPane(); + } + + /** + * Called when the context is created, possibly after automatic destruction. + */ + // This is a GLSurfaceView.Renderer callback + @Override + public void onSurfaceCreated(GL10 gl1, EGLConfig config) { + GL11 gl = (GL11) gl1; + if (mGL != null) { + // The GL Object has changed + Log.i(TAG, "GLObject has changed from " + mGL + " to " + gl); + } + mRenderLock.lock(); + try { + mGL = gl; + mCanvas = mSupportGLES20 ? new GLES20Canvas() : new GLES11Canvas(gl); + BasicTexture.invalidateAllTextures(); + } finally { + mRenderLock.unlock(); + } + + if (DEBUG_FPS) { + setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); + } else { + setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); + } + } + + /** + * Called when the OpenGL surface is recreated without destroying the + * context. + */ + // This is a GLSurfaceView.Renderer callback + @Override + public void onSurfaceChanged(GL10 gl1, int width, int height) { + Log.i(TAG, "onSurfaceChanged: " + width + "x" + height + + ", gl10: " + gl1.toString()); + Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY); + GalleryUtils.setRenderThread(); + + GL11 gl = (GL11) gl1; + Utils.assertTrue(mGL == gl); + + mCanvas.setSize(width, height); + } + + private void outputFps() { + long now = System.nanoTime(); + if (mFrameCountingStart == 0) { + mFrameCountingStart = now; + } else if ((now - mFrameCountingStart) > 1000000000) { + Log.d(TAG, "fps: " + (double) mFrameCount + * 1000000000 / (now - mFrameCountingStart)); + mFrameCountingStart = now; + mFrameCount = 0; + } + ++mFrameCount; + } + + @Override + public void onDrawFrame(GL10 gl) { + AnimationTime.update(); + mRenderLock.lock(); + + while (mFreeze) { + mFreezeCondition.awaitUninterruptibly(); + } + + try { + onDrawFrameLocked(gl); + } finally { + mRenderLock.unlock(); + } + } + + private void onDrawFrameLocked(GL10 gl) { + if (DEBUG_FPS) outputFps(); + + // release the unbound textures and deleted buffers. + mCanvas.deleteRecycledResources(); + + // reset texture upload limit + UploadedTexture.resetUploadLimit(); + + mRenderRequested = false; + + if ((mOrientationSource != null + && mDisplayRotation != mOrientationSource.getDisplayRotation()) + || (mFlags & FLAG_NEED_LAYOUT) != 0) { + layoutContentPane(); + } + + mCanvas.save(GLCanvas.SAVE_FLAG_ALL); + rotateCanvas(-mCompensation); + if (mContentView != null) { + mContentView.render(mCanvas); + } else { + // Make sure we always draw something to prevent displaying garbage + mCanvas.clearBuffer(); + } + mCanvas.restore(); + + if (!mAnimations.isEmpty()) { + long now = AnimationTime.get(); + for (int i = 0, n = mAnimations.size(); i < n; i++) { + mAnimations.get(i).setStartTime(now); + } + mAnimations.clear(); + } + + if (UploadedTexture.uploadLimitReached()) { + requestRender(); + } + + synchronized (mIdleListeners) { + if (!mIdleListeners.isEmpty()) mIdleRunner.enable(); + } + + if (DEBUG_INVALIDATE) { + mCanvas.fillRect(10, 10, 5, 5, mInvalidateColor); + mInvalidateColor = ~mInvalidateColor; + } + + if (DEBUG_DRAWING_STAT) { + mCanvas.dumpStatisticsAndClear(); + } + } + + private void rotateCanvas(int degrees) { + if (degrees == 0) return; + int w = getWidth(); + int h = getHeight(); + int cx = w / 2; + int cy = h / 2; + mCanvas.translate(cx, cy); + mCanvas.rotate(degrees, 0, 0, 1); + if (degrees % 180 != 0) { + mCanvas.translate(-cy, -cx); + } else { + mCanvas.translate(-cx, -cy); + } + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + if (!isEnabled()) return false; + + int action = event.getAction(); + if (action == MotionEvent.ACTION_CANCEL + || action == MotionEvent.ACTION_UP) { + mInDownState = false; + } else if (!mInDownState && action != MotionEvent.ACTION_DOWN) { + return false; + } + + if (mCompensation != 0) { + event = MotionEventHelper.transformEvent(event, mCompensationMatrix); + } + + mRenderLock.lock(); + try { + // If this has been detached from root, we don't need to handle event + boolean handled = mContentView != null + && mContentView.dispatchTouchEvent(event); + if (action == MotionEvent.ACTION_DOWN && handled) { + mInDownState = true; + } + return handled; + } finally { + mRenderLock.unlock(); + } + } + + private class IdleRunner implements Runnable { + // true if the idle runner is in the queue + private boolean mActive = false; + + @Override + public void run() { + OnGLIdleListener listener; + synchronized (mIdleListeners) { + mActive = false; + if (mIdleListeners.isEmpty()) return; + listener = mIdleListeners.removeFirst(); + } + mRenderLock.lock(); + boolean keepInQueue; + try { + keepInQueue = listener.onGLIdle(mCanvas, mRenderRequested); + } finally { + mRenderLock.unlock(); + } + synchronized (mIdleListeners) { + if (keepInQueue) mIdleListeners.addLast(listener); + if (!mRenderRequested && !mIdleListeners.isEmpty()) enable(); + } + } + + public void enable() { + // Who gets the flag can add it to the queue + if (mActive) return; + mActive = true; + queueEvent(this); + } + } + + @Override + public void lockRenderThread() { + mRenderLock.lock(); + } + + @Override + public void unlockRenderThread() { + mRenderLock.unlock(); + } + + @Override + public void onPause() { + unfreeze(); + super.onPause(); + } + + @Override + public void setOrientationSource(OrientationSource source) { + mOrientationSource = source; + } + + @Override + public int getDisplayRotation() { + return mDisplayRotation; + } + + @Override + public int getCompensation() { + return mCompensation; + } + + @Override + public Matrix getCompensationMatrix() { + return mCompensationMatrix; + } + + @Override + public void freeze() { + mRenderLock.lock(); + mFreeze = true; + mRenderLock.unlock(); + } + + @Override + public void unfreeze() { + mRenderLock.lock(); + mFreeze = false; + mFreezeCondition.signalAll(); + mRenderLock.unlock(); + } + + @Override + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + public void setLightsOutMode(boolean enabled) { + int flags = 0; + if (enabled) { + flags = View.SYSTEM_UI_FLAG_LOW_PROFILE ; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + flags |= (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + } + } + setSystemUiVisibility(flags); + } + + // We need to unfreeze in the following methods and in onPause(). + // These methods will wait on GLThread. If we have freezed the GLRootView, + // the GLThread will wait on main thread to call unfreeze and cause dead + // lock. + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { + unfreeze(); + super.surfaceChanged(holder, format, w, h); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + unfreeze(); + super.surfaceCreated(holder); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + unfreeze(); + super.surfaceDestroyed(holder); + } + + @Override + protected void onDetachedFromWindow() { + unfreeze(); + super.onDetachedFromWindow(); + } + + @Override + protected void finalize() throws Throwable { + try { + unfreeze(); + } finally { + super.finalize(); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/GLView.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GLView.java new file mode 100644 index 000000000..203e3373b --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GLView.java @@ -0,0 +1,469 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.ui; + +import java.util.ArrayList; + +import android.annotation.SuppressLint; +import android.graphics.Rect; +import android.os.SystemClock; +import android.view.MotionEvent; + +import com.hippo.ehviewer.gallery.anim.CanvasAnimation; +import com.hippo.ehviewer.gallery.anim.StateTransitionAnimation; +import com.hippo.ehviewer.gallery.glrenderer.GLCanvas; +import com.hippo.ehviewer.util.Log; +import com.hippo.ehviewer.util.Utils; + +// GLView is a UI component. It can render to a GLCanvas and accept touch +// events. A GLView may have zero or more child GLView and they form a tree +// structure. The rendering and event handling will pass through the tree +// structure. +// +// A GLView tree should be attached to a GLRoot before event dispatching and +// rendering happens. GLView asks GLRoot to re-render or re-layout the +// GLView hierarchy using requestRender() and requestLayoutContentPane(). +// +// The render() method is called in a separate thread. Before calling +// dispatchTouchEvent() and layout(), GLRoot acquires a lock to avoid the +// rendering thread running at the same time. If there are other entry points +// from main thread (like a Handler) in your GLView, you need to call +// lockRendering() if the rendering thread should not run at the same time. +// +public class GLView { + private static final String TAG = "GLView"; + + public static final int VISIBLE = 0; + public static final int INVISIBLE = 1; + + private static final int FLAG_INVISIBLE = 1; + private static final int FLAG_SET_MEASURED_SIZE = 2; + private static final int FLAG_LAYOUT_REQUESTED = 4; + + public interface OnClickListener { + void onClick(GLView v); + } + + protected final Rect mBounds = new Rect(); + protected final Rect mPaddings = new Rect(); + + private GLRoot mRoot; + protected GLView mParent; + private ArrayList mComponents; + private GLView mMotionTarget; + + private CanvasAnimation mAnimation; + + private int mViewFlags = 0; + + protected int mMeasuredWidth = 0; + protected int mMeasuredHeight = 0; + + private int mLastWidthSpec = -1; + private int mLastHeightSpec = -1; + + protected int mScrollY = 0; + protected int mScrollX = 0; + protected int mScrollHeight = 0; + protected int mScrollWidth = 0; + + private float [] mBackgroundColor; + private StateTransitionAnimation mTransition; + + public void startAnimation(CanvasAnimation animation) { + GLRoot root = getGLRoot(); + if (root == null) throw new IllegalStateException(); + mAnimation = animation; + if (mAnimation != null) { + mAnimation.start(); + root.registerLaunchedAnimation(mAnimation); + } + invalidate(); + } + + // Sets the visiblity of this GLView (either GLView.VISIBLE or + // GLView.INVISIBLE). + public void setVisibility(int visibility) { + if (visibility == getVisibility()) return; + if (visibility == VISIBLE) { + mViewFlags &= ~FLAG_INVISIBLE; + } else { + mViewFlags |= FLAG_INVISIBLE; + } + onVisibilityChanged(visibility); + invalidate(); + } + + // Returns GLView.VISIBLE or GLView.INVISIBLE + public int getVisibility() { + return (mViewFlags & FLAG_INVISIBLE) == 0 ? VISIBLE : INVISIBLE; + } + + // This should only be called on the content pane (the topmost GLView). + public void attachToRoot(GLRoot root) { + Utils.assertTrue(mParent == null && mRoot == null); + onAttachToRoot(root); + } + + // This should only be called on the content pane (the topmost GLView). + public void detachFromRoot() { + Utils.assertTrue(mParent == null && mRoot != null); + onDetachFromRoot(); + } + + // Returns the number of children of the GLView. + public int getComponentCount() { + return mComponents == null ? 0 : mComponents.size(); + } + + // Returns the children for the given index. + public GLView getComponent(int index) { + if (mComponents == null) { + throw new ArrayIndexOutOfBoundsException(index); + } + return mComponents.get(index); + } + + // Adds a child to this GLView. + public void addComponent(GLView component) { + // Make sure the component doesn't have a parent currently. + if (component.mParent != null) throw new IllegalStateException(); + + // Build parent-child links + if (mComponents == null) { + mComponents = new ArrayList(); + } + mComponents.add(component); + component.mParent = this; + + // If this is added after we have a root, tell the component. + if (mRoot != null) { + component.onAttachToRoot(mRoot); + } + } + + // Removes a child from this GLView. + public boolean removeComponent(GLView component) { + if (mComponents == null) return false; + if (mComponents.remove(component)) { + removeOneComponent(component); + return true; + } + return false; + } + + // Removes all children of this GLView. + public void removeAllComponents() { + for (int i = 0, n = mComponents.size(); i < n; ++i) { + removeOneComponent(mComponents.get(i)); + } + mComponents.clear(); + } + + private void removeOneComponent(GLView component) { + if (mMotionTarget == component) { + long now = SystemClock.uptimeMillis(); + MotionEvent cancelEvent = MotionEvent.obtain( + now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0); + dispatchTouchEvent(cancelEvent); + cancelEvent.recycle(); + } + component.onDetachFromRoot(); + component.mParent = null; + } + + public Rect bounds() { + return mBounds; + } + + public int getWidth() { + return mBounds.right - mBounds.left; + } + + public int getHeight() { + return mBounds.bottom - mBounds.top; + } + + public GLRoot getGLRoot() { + return mRoot; + } + + // Request re-rendering of the view hierarchy. + // This is used for animation or when the contents changed. + public void invalidate() { + GLRoot root = getGLRoot(); + if (root != null) root.requestRender(); + } + + // Request re-layout of the view hierarchy. + public void requestLayout() { + mViewFlags |= FLAG_LAYOUT_REQUESTED; + mLastHeightSpec = -1; + mLastWidthSpec = -1; + if (mParent != null) { + mParent.requestLayout(); + } else { + // Is this a content pane ? + GLRoot root = getGLRoot(); + if (root != null) root.requestLayoutContentPane(); + } + } + + protected void render(GLCanvas canvas) { + boolean transitionActive = false; + if (mTransition != null && mTransition.calculate(AnimationTime.get())) { + invalidate(); + transitionActive = mTransition.isActive(); + } + renderBackground(canvas); + canvas.save(); + if (transitionActive) { + mTransition.applyContentTransform(this, canvas); + } + for (int i = 0, n = getComponentCount(); i < n; ++i) { + renderChild(canvas, getComponent(i)); + } + canvas.restore(); + if (transitionActive) { + mTransition.applyOverlay(this, canvas); + } + } + + public void setIntroAnimation(StateTransitionAnimation intro) { + mTransition = intro; + if (mTransition != null) mTransition.start(); + } + + public float [] getBackgroundColor() { + return mBackgroundColor; + } + + public void setBackgroundColor(float [] color) { + mBackgroundColor = color; + } + + protected void renderBackground(GLCanvas view) { + if (mBackgroundColor != null) { + view.clearBuffer(mBackgroundColor); + } + if (mTransition != null && mTransition.isActive()) { + mTransition.applyBackground(this, view); + return; + } + } + + protected void renderChild(GLCanvas canvas, GLView component) { + if (component.getVisibility() != GLView.VISIBLE + && component.mAnimation == null) return; + + int xoffset = component.mBounds.left - mScrollX; + int yoffset = component.mBounds.top - mScrollY; + + canvas.translate(xoffset, yoffset); + + CanvasAnimation anim = component.mAnimation; + if (anim != null) { + canvas.save(anim.getCanvasSaveFlags()); + if (anim.calculate(AnimationTime.get())) { + invalidate(); + } else { + component.mAnimation = null; + } + anim.apply(canvas); + } + component.render(canvas); + if (anim != null) canvas.restore(); + canvas.translate(-xoffset, -yoffset); + } + + protected boolean onTouch(MotionEvent event) { + return false; + } + + protected boolean dispatchTouchEvent(MotionEvent event, + int x, int y, GLView component, boolean checkBounds) { + Rect rect = component.mBounds; + int left = rect.left; + int top = rect.top; + if (!checkBounds || rect.contains(x, y)) { + event.offsetLocation(-left, -top); + if (component.dispatchTouchEvent(event)) { + event.offsetLocation(left, top); + return true; + } + event.offsetLocation(left, top); + } + return false; + } + + protected boolean dispatchTouchEvent(MotionEvent event) { + int x = (int) event.getX(); + int y = (int) event.getY(); + int action = event.getAction(); + if (mMotionTarget != null) { + if (action == MotionEvent.ACTION_DOWN) { + MotionEvent cancel = MotionEvent.obtain(event); + cancel.setAction(MotionEvent.ACTION_CANCEL); + dispatchTouchEvent(cancel, x, y, mMotionTarget, false); + mMotionTarget = null; + } else { + dispatchTouchEvent(event, x, y, mMotionTarget, false); + if (action == MotionEvent.ACTION_CANCEL + || action == MotionEvent.ACTION_UP) { + mMotionTarget = null; + } + return true; + } + } + if (action == MotionEvent.ACTION_DOWN) { + // in the reverse rendering order + for (int i = getComponentCount() - 1; i >= 0; --i) { + GLView component = getComponent(i); + if (component.getVisibility() != GLView.VISIBLE) continue; + if (dispatchTouchEvent(event, x, y, component, true)) { + mMotionTarget = component; + return true; + } + } + } + return onTouch(event); + } + + public Rect getPaddings() { + return mPaddings; + } + + @SuppressLint("WrongCall") + public void layout(int left, int top, int right, int bottom) { + boolean sizeChanged = setBounds(left, top, right, bottom); + mViewFlags &= ~FLAG_LAYOUT_REQUESTED; + // We call onLayout no matter sizeChanged is true or not because the + // orientation may change without changing the size of the View (for + // example, rotate the device by 180 degrees), and we want to handle + // orientation change in onLayout. + onLayout(sizeChanged, left, top, right, bottom); + } + + private boolean setBounds(int left, int top, int right, int bottom) { + boolean sizeChanged = (right - left) != (mBounds.right - mBounds.left) + || (bottom - top) != (mBounds.bottom - mBounds.top); + mBounds.set(left, top, right, bottom); + return sizeChanged; + } + + @SuppressLint("WrongCall") + public void measure(int widthSpec, int heightSpec) { + if (widthSpec == mLastWidthSpec && heightSpec == mLastHeightSpec + && (mViewFlags & FLAG_LAYOUT_REQUESTED) == 0) { + return; + } + + mLastWidthSpec = widthSpec; + mLastHeightSpec = heightSpec; + + mViewFlags &= ~FLAG_SET_MEASURED_SIZE; + onMeasure(widthSpec, heightSpec); + if ((mViewFlags & FLAG_SET_MEASURED_SIZE) == 0) { + throw new IllegalStateException(getClass().getName() + + " should call setMeasuredSize() in onMeasure()"); + } + } + + protected void onMeasure(int widthSpec, int heightSpec) { + } + + protected void setMeasuredSize(int width, int height) { + mViewFlags |= FLAG_SET_MEASURED_SIZE; + mMeasuredWidth = width; + mMeasuredHeight = height; + } + + public int getMeasuredWidth() { + return mMeasuredWidth; + } + + public int getMeasuredHeight() { + return mMeasuredHeight; + } + + protected void onLayout( + boolean changeSize, int left, int top, int right, int bottom) { + } + + /** + * Gets the bounds of the given descendant that relative to this view. + */ + public boolean getBoundsOf(GLView descendant, Rect out) { + int xoffset = 0; + int yoffset = 0; + GLView view = descendant; + while (view != this) { + if (view == null) return false; + Rect bounds = view.mBounds; + xoffset += bounds.left; + yoffset += bounds.top; + view = view.mParent; + } + out.set(xoffset, yoffset, xoffset + descendant.getWidth(), + yoffset + descendant.getHeight()); + return true; + } + + protected void onVisibilityChanged(int visibility) { + for (int i = 0, n = getComponentCount(); i < n; ++i) { + GLView child = getComponent(i); + if (child.getVisibility() == GLView.VISIBLE) { + child.onVisibilityChanged(visibility); + } + } + } + + protected void onAttachToRoot(GLRoot root) { + mRoot = root; + for (int i = 0, n = getComponentCount(); i < n; ++i) { + getComponent(i).onAttachToRoot(root); + } + } + + protected void onDetachFromRoot() { + for (int i = 0, n = getComponentCount(); i < n; ++i) { + getComponent(i).onDetachFromRoot(); + } + mRoot = null; + } + + public void lockRendering() { + if (mRoot != null) { + mRoot.lockRenderThread(); + } + } + + public void unlockRendering() { + if (mRoot != null) { + mRoot.unlockRenderThread(); + } + } + + // This is for debugging only. + // Dump the view hierarchy into log. + void dumpTree(String prefix) { + Log.d(TAG, prefix + getClass().getSimpleName()); + for (int i = 0, n = getComponentCount(); i < n; ++i) { + getComponent(i).dumpTree(prefix + "...."); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/GestureRecognizer.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GestureRecognizer.java new file mode 100644 index 000000000..74377d942 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/GestureRecognizer.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.ui; + +import android.content.Context; +import android.os.SystemClock; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; + +// This class aggregates three gesture detectors: GestureDetector, +// ScaleGestureDetector, and DownUpDetector. +public class GestureRecognizer { + @SuppressWarnings("unused") + private static final String TAG = "GestureRecognizer"; + + public interface Listener { + boolean onSingleTapConfirmed(float x, float y); + boolean onDoubleTap(float x, float y); + boolean onDoubleTapConfirmed(float x, float y); + void onLongPress(MotionEvent e); + boolean onScrollBegin(float dx, float dy, MotionEvent e1, MotionEvent e2); + boolean onScroll(float dx, float dy, MotionEvent e1, MotionEvent e2); + boolean onScrollEnd(float totalX, float totalY); + boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY); + boolean onScaleBegin(float focusX, float focusY); + boolean onScale(float focusX, float focusY, float scale); + void onScaleEnd(); + void onDown(float x, float y); + void onUp(); + } + + private final GestureDetector mGestureDetector; + private final ScaleGestureDetector mScaleDetector; + private final DownUpDetector mDownUpDetector; + private final Listener mListener; + + private boolean mStillScroll = false; + + public GestureRecognizer(Context context, Listener listener) { + mListener = listener; + mScaleDetector = new ScaleGestureDetector( + context, new MyScaleListener()); + mGestureDetector = new GestureDetector(context, new MyGestureListener(), + null, true /* ignoreMultitouch */); + mDownUpDetector = new DownUpDetector(new MyDownUpListener()); + } + + public void onTouchEvent(MotionEvent event) { + mGestureDetector.onTouchEvent(event); + mScaleDetector.onTouchEvent(event); + mDownUpDetector.onTouchEvent(event); + } + + public boolean isDown() { + return mDownUpDetector.isDown(); + } + + public void cancelScale() { + long now = SystemClock.uptimeMillis(); + MotionEvent cancelEvent = MotionEvent.obtain( + now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0); + mScaleDetector.onTouchEvent(cancelEvent); + cancelEvent.recycle(); + } + + private class MyGestureListener + extends GestureDetector.SimpleOnGestureListener { + float firstX = 0; + float firstY = 0; + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + return mListener.onSingleTapConfirmed(e.getX(), e.getY()); + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + return mListener.onDoubleTap(e.getX(), e.getY()); + } + + @Override + public boolean onDoubleTapEvent(MotionEvent e) { + if (e.getAction() == MotionEvent.ACTION_UP || + e.getAction() == MotionEvent.ACTION_CANCEL) + return mListener.onDoubleTapConfirmed(e.getX(), e.getY()); + else + return super.onDoubleTapEvent(e); + } + + @Override + public void onLongPress(MotionEvent e) { + mListener.onLongPress(e); + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy) { + if (e1.getX() != firstX || e1.getY() != firstY) { + mStillScroll = true; + firstX = e1.getX(); + firstY = e1.getY(); + return mListener.onScrollBegin(dx, dy, e1, e2); + } else { + return mListener.onScroll(dx, dy, e1, e2); + } + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + return mListener.onFling(e1, e2, velocityX, velocityY); + } + } + + private class MyScaleListener + extends ScaleGestureDetector.SimpleOnScaleGestureListener { + @Override + public boolean onScaleBegin(ScaleGestureDetector detector) { + return mListener.onScaleBegin( + detector.getFocusX(), detector.getFocusY()); + } + + @Override + public boolean onScale(ScaleGestureDetector detector) { + return mListener.onScale(detector.getFocusX(), + detector.getFocusY(), detector.getScaleFactor()); + } + + @Override + public void onScaleEnd(ScaleGestureDetector detector) { + mListener.onScaleEnd(); + } + } + + private class MyDownUpListener implements DownUpDetector.DownUpListener { + + private float mFirstX; + private float mFirstY; + + @Override + public void onDown(MotionEvent e) { + mListener.onDown(mFirstX = e.getX(), mFirstY = e.getY()); + } + + @Override + public void onUp(MotionEvent e) { + mListener.onUp(); + if (mStillScroll) { + mStillScroll = false; + mListener.onScrollEnd(e.getX() - mFirstX, e.getY() - mFirstY); + } + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/OrientationSource.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/OrientationSource.java new file mode 100644 index 000000000..2765a130c --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/OrientationSource.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.ui; + +public interface OrientationSource { + public int getDisplayRotation(); + public int getCompensation(); +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/SynchronizedHandler.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/SynchronizedHandler.java new file mode 100644 index 000000000..798d9cb06 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/SynchronizedHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.ui; + +import android.os.Handler; +import android.os.Message; + +import com.hippo.ehviewer.util.Utils; + +public class SynchronizedHandler extends Handler { + + private final GLRoot mRoot; + + public SynchronizedHandler(GLRoot root) { + mRoot = Utils.checkNotNull(root); + } + + @Override + public void dispatchMessage(Message message) { + mRoot.lockRenderThread(); + try { + super.dispatchMessage(message); + } finally { + mRoot.unlockRenderThread(); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ui/TimeRunner.java b/app/src/main/java/com/hippo/ehviewer/gallery/ui/TimeRunner.java new file mode 100644 index 000000000..87d44bb38 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ui/TimeRunner.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.ui; + +import android.animation.TimeInterpolator; +import android.os.Handler; + +public abstract class TimeRunner { + private static final int INTERVAL = 5; + + private static final int RUNNING = 0x0; + private static final int START = 0x1; + private static final int END = 0x2; + private static final int CANCEL = 0x3; + + private boolean mDoInThread = false; + private int mDuration = 0; + private int mDelay = 0; + private TimeInterpolator mInterpolator; + private Handler mHandler; + private volatile boolean mCancel = false; + + private class Respond implements Runnable { + private final int state; + private float interpolatedTime; + private int runningTime; + + public Respond(int state) { + this.state = state; + } + + public Respond(float interpolatedTime, int runningTime) { + this.state = RUNNING; + this.interpolatedTime = interpolatedTime; + this.runningTime = runningTime; + } + + @Override + public void run() { + switch (state) { + case RUNNING: + TimeRunner.this.onRun(interpolatedTime, runningTime); + break; + case START: + TimeRunner.this.onStart(); + break; + case END: + TimeRunner.this.onEnd(); + break; + case CANCEL: + TimeRunner.this.onCancel(); + } + } + } + + public void setDuration(int duration) { + mDuration = duration; + } + + public void setDoInThread(boolean doInThread) { + mDoInThread = doInThread; + } + + public void setHandler(Handler handler) { + mHandler = handler; + mDoInThread = false; + } + + public void setInterpolator(TimeInterpolator interpolator) { + mInterpolator = interpolator; + } + + public void cancel() { + mCancel = true; + } + + public void start() { + start(0); + } + + public void start(int delay) { + mCancel = false; + mDelay = delay; + new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(mDelay); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + if (mDoInThread || mHandler == null) + onStart(); + else + mHandler.post(new Respond(START)); + + // Check cancel + if (mCancel) { + if (mDoInThread || mHandler == null) + onStart(); + else + mHandler.post(new Respond(CANCEL)); + return; + } + + long startTime = System.currentTimeMillis(); + for (int runningTime = 0; runningTime < mDuration; runningTime = (int)(System.currentTimeMillis() - startTime)) { + // Check cancel + if (mCancel) { + if (mDoInThread || mHandler == null) + onStart(); + else + mHandler.post(new Respond(CANCEL)); + return; + } + + float percent = runningTime / (float)mDuration; + if (mInterpolator != null) + percent = mInterpolator.getInterpolation(percent); + + if (mDoInThread || mHandler == null) + TimeRunner.this.onRun(percent, runningTime); + else + mHandler.post(new Respond(percent, runningTime)); + try { + Thread.sleep(INTERVAL); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + if (mDoInThread || mHandler == null) + onEnd(); + else + mHandler.post(new Respond(END)); + } + }).start(); + } + + protected abstract void onStart(); + protected abstract void onRun(float percent, int runningTime); + protected abstract void onEnd(); + protected abstract void onCancel(); +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/util/GalleryUtils.java b/app/src/main/java/com/hippo/ehviewer/gallery/util/GalleryUtils.java new file mode 100644 index 000000000..348ce71b2 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/util/GalleryUtils.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.gallery.util; + +import android.graphics.Color; + +import com.hippo.ehviewer.util.Log; + +public class GalleryUtils { + private static final String TAG = GalleryUtils.class.getSimpleName(); + + public static float[] intColorToFloatARGBArray(int from) { + return new float[] { + Color.alpha(from) / 255f, + Color.red(from) / 255f, + Color.green(from) / 255f, + Color.blue(from) / 255f + }; + } + + // Below are used the detect using database in the render thread. It only + // works most of the time, but that's ok because it's for debugging only. + + private static volatile Thread sCurrentThread; + private static volatile boolean sWarned; + + public static void setRenderThread() { + sCurrentThread = Thread.currentThread(); + } + + public static void assertNotInRenderThread() { + if (!sWarned) { + if (Thread.currentThread() == sCurrentThread) { + sWarned = true; + Log.w(TAG, new Throwable("Should not do this in render thread")); + } + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/util/MotionEventHelper.java b/app/src/main/java/com/hippo/ehviewer/gallery/util/MotionEventHelper.java new file mode 100644 index 000000000..fe882fb02 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/gallery/util/MotionEventHelper.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.hippo.ehviewer.gallery.util; + +import android.annotation.SuppressLint; +import android.graphics.Matrix; +import android.os.Build; +import android.view.MotionEvent; +import android.view.MotionEvent.PointerCoords; + +public final class MotionEventHelper { + private MotionEventHelper() {} + + public static MotionEvent transformEvent(MotionEvent e, Matrix m) { + // We try to use the new transform method if possible because it uses + // less memory. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + return transformEventNew(e, m); + } else { + return transformEventOld(e, m); + } + } + + @SuppressLint("Recycle") + private static MotionEvent transformEventNew(MotionEvent e, Matrix m) { + MotionEvent newEvent = MotionEvent.obtain(e); + newEvent.transform(m); + return newEvent; + } + + // This is copied from Input.cpp in the android framework. + @SuppressLint("Recycle") + private static MotionEvent transformEventOld(MotionEvent e, Matrix m) { + long downTime = e.getDownTime(); + long eventTime = e.getEventTime(); + int action = e.getAction(); + int pointerCount = e.getPointerCount(); + int[] pointerIds = getPointerIds(e); + PointerCoords[] pointerCoords = getPointerCoords(e); + int metaState = e.getMetaState(); + float xPrecision = e.getXPrecision(); + float yPrecision = e.getYPrecision(); + int deviceId = e.getDeviceId(); + int edgeFlags = e.getEdgeFlags(); + int source = e.getSource(); + int flags = e.getFlags(); + + // Copy the x and y coordinates into an array, map them, and copy back. + float[] xy = new float[pointerCoords.length * 2]; + for (int i = 0; i < pointerCount;i++) { + xy[2 * i] = pointerCoords[i].x; + xy[2 * i + 1] = pointerCoords[i].y; + } + m.mapPoints(xy); + for (int i = 0; i < pointerCount;i++) { + pointerCoords[i].x = xy[2 * i]; + pointerCoords[i].y = xy[2 * i + 1]; + pointerCoords[i].orientation = transformAngle( + m, pointerCoords[i].orientation); + } + + @SuppressWarnings("deprecation") + MotionEvent n = MotionEvent.obtain(downTime, eventTime, action, + pointerCount, pointerIds, pointerCoords, metaState, xPrecision, + yPrecision, deviceId, edgeFlags, source, flags); + + return n; + } + + private static int[] getPointerIds(MotionEvent e) { + int n = e.getPointerCount(); + int[] r = new int[n]; + for (int i = 0; i < n; i++) { + r[i] = e.getPointerId(i); + } + return r; + } + + private static PointerCoords[] getPointerCoords(MotionEvent e) { + int n = e.getPointerCount(); + PointerCoords[] r = new PointerCoords[n]; + for (int i = 0; i < n; i++) { + r[i] = new PointerCoords(); + e.getPointerCoords(i, r[i]); + } + return r; + } + + private static float transformAngle(Matrix m, float angleRadians) { + // Construct and transform a vector oriented at the specified clockwise + // angle from vertical. Coordinate system: down is increasing Y, right is + // increasing X. + float[] v = new float[2]; + v[0] = (float)Math.sin(angleRadians); + v[1] = -(float)Math.cos(angleRadians); + m.mapVectors(v); + + // Derive the transformed vector's clockwise angle from vertical. + float result = (float) Math.atan2(v[0], -v[1]); + if (result < -Math.PI / 2) { + result += Math.PI; + } else if (result > Math.PI / 2) { + result -= Math.PI; + } + return result; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/network/HttpHelper.java b/app/src/main/java/com/hippo/ehviewer/network/HttpHelper.java new file mode 100644 index 000000000..c41fcdbbf --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/network/HttpHelper.java @@ -0,0 +1,1104 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.network; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; +import java.net.UnknownHostException; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; + +import org.apache.http.conn.ConnectTimeoutException; +import org.json.JSONArray; +import org.json.JSONObject; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Message; +import android.webkit.MimeTypeMap; + +import com.hippo.ehviewer.AppHandler; +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.ehclient.EhClient; +import com.hippo.ehviewer.ehclient.EhInfo; +import com.hippo.ehviewer.exception.StopRequestException; +import com.hippo.ehviewer.util.BgThread; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.Constants; +import com.hippo.ehviewer.util.EhUtils; +import com.hippo.ehviewer.util.FastByteArrayOutputStream; +import com.hippo.ehviewer.util.Log; +import com.hippo.ehviewer.util.MathUtils; +import com.hippo.ehviewer.util.Ui; +import com.hippo.ehviewer.util.Utils; + +/** + * It is not thread safe + * + * @author Hippo + * + */ +public class HttpHelper { + private static final String TAG = HttpHelper.class.getSimpleName(); + + public static final String SAD_PANDA_ERROR = "Sad Panda"; + public static final String HAPPY_PANDA_BODY = "Happy Panda"; + + public static final String DOWNLOAD_OK_STR = "OK"; + + public static final int DOWNLOAD_OK_CODE = 0x0; + public static final int DOWNLOAD_FAIL_CODE = 0x1; + public static final int DOWNLOAD_STOP_CODE = 0x2; + + private static final String DEFAULT_CHARSET = "utf-8"; + private static final String CHARSET_KEY = "charset="; + + // TODO Get proxy list from server + private static String[] sProxyUrls = Config.DEFAULT_PROXY_URLS; + private static volatile int sProxyIndex = MathUtils.random(0, sProxyUrls.length); + private static Object sProxyIndexLock = new Object(); + + public class Package { + public Object obj; + public OnRespondListener listener; + public Package(Object obj, OnRespondListener listener) { + this.obj = obj; + this.listener = listener; + } + } + + private final Context mContext; + private Exception mException; + private String mLastUrl; + private OnRespondListener mListener; + private String mPreviewMode; + + private volatile long mLastEhConnectTime = 0; + private static final Object ehLock = new Object(); + + public interface OnRespondListener { + void onSuccess(Object body); + void onFailure(String eMsg); + } + + public static void updateProxyUrls(final Context context) { + new BgThread() { + @Override + public void run() { + try { + HttpHelper hh = new HttpHelper(context); + JSONObject json = new JSONObject(); + json.put("method", "proxy_urls"); + String body = hh.postJson(EhClient.API_EHVIEWER, json); + JSONObject jo = new JSONObject(body); + JSONArray ja = jo.getJSONObject("proxy_urls").getJSONArray("urls"); + String[] newUrls = new String[ja.length()]; + if (newUrls.length < 1) + throw new Throwable(); + for (int i = 0; i < ja.length(); i++) + newUrls[i] = ja.getString(i); + synchronized(sProxyIndexLock) { + sProxyUrls = newUrls; + sProxyIndex = MathUtils.random(0, sProxyUrls.length); + } + // Put proxy into config + Config.setProxyUrls(sProxyUrls); + } catch (Throwable e) { + synchronized(sProxyIndexLock) { + sProxyUrls = Config.getProxyUrls(); + sProxyIndex = MathUtils.random(0, sProxyUrls.length); + } + } + } + }.start(); + } + + public String getProxyUrl() { + synchronized(sProxyIndexLock) { + if (sProxyIndex < 0 || sProxyIndex >= sProxyUrls.length) + sProxyIndex = 0; + return sProxyUrls[sProxyIndex++]; + } + } + + public void reset() { + mListener = null; + mException = null; + mLastUrl = null; + mPreviewMode = null; + } + + public void setOnRespondListener(OnRespondListener l) { + mListener = l; + } + + public HttpHelper(Context context) { + mContext = context; + mContext.getApplicationContext(); + } + + /** + * Get last error message + * @return + */ + public String getEMsg() { + return getEMsg(mContext, mException); + } + + public Exception getException() { + return mException; + } + + public static String getEMsg(Context c, Exception e) { + if (e == null) + return c.getString(R.string.em_unknown_error); + + else if (e instanceof MalformedURLException) + return c.getString(R.string.em_url_format_error) + ": " + e.getMessage(); + + else if (e instanceof ConnectTimeoutException || + e instanceof SocketTimeoutException) + return c.getString(R.string.em_timeout) + ": " + e.getMessage(); + + else if (e instanceof UnknownHostException) + return c.getString(R.string.em_unknown_host) + ": " + e.getMessage(); + + else if (e instanceof ResponseCodeException) + return String.format(c.getString(R.string.em_unexpected_response_code), + ((ResponseCodeException)e).getResponseCode()) + ": " + e.getMessage(); + + else if (e instanceof RedirectionException) + return c.getString(R.string.em_redirection_error) + ": " + e.getMessage(); + + else if (e instanceof SocketException) + return "SocketException: " + e.getMessage(); + + else if (e instanceof SadPandaException) + return SAD_PANDA_ERROR; + + else if (e instanceof GetBodyException) + return c.getString(R.string.em_requst_null); + + else + return e.getClass().getSimpleName() + ": " +e.getMessage(); + } + + public String getLastUrl() { + return mLastUrl; + } + + private boolean isUrlCookiable(URL url) { + String host = url.getHost(); + for (String h : EhInfo.COOKIABLE_HOSTS) { + if (h.equals(host)) + return true; + } + return false; + } + + public void setPreviewMode(String previewMode) { + mPreviewMode = previewMode; + } + + private Object requst(RequestHelper rh) { + URL url = null; + HttpURLConnection conn = null; + boolean isCookiable = false; + + for (int times = 0; times < Config.getHttpRetry(); times++) { + mException = null; + int redirectionCount = 0; + boolean firstTime = true; + try { + url = (mLastUrl == null ? rh.getUrl() : new URL(mLastUrl)); + + isCookiable = isUrlCookiable(url); + + while (isCookiable) { + synchronized(ehLock) { + long cur = System.currentTimeMillis(); + long left = mLastEhConnectTime + Config.getEhMinInterval() - cur; + if (left <= 0) { + mLastEhConnectTime = cur; + break; + } else { + Thread.sleep(left); + } + } + } + + Log.d(TAG, "Requst " + url.toString()); + while (redirectionCount++ < Constants.MAX_REDIRECTS) { + conn = (HttpURLConnection) url.openConnection(); + conn.setInstanceFollowRedirects(false); + conn.setRequestProperty("User-Agent", Constants.userAgent); + conn.setConnectTimeout(Config.getHttpConnectTimeout()); + conn.setReadTimeout(Config.getHttpReadTimeout()); + // Set cookie if necessary + if (isCookiable) + EhInfo.getInstance(mContext).setCookie(conn, mPreviewMode); + // Do custom staff + if (firstTime) { + rh.onBeforeConnect(conn); + firstTime = false; + } + + conn.connect(); + final int responseCode = conn.getResponseCode(); + switch (responseCode) { + case HttpURLConnection.HTTP_OK: + case HttpURLConnection.HTTP_PARTIAL: + // Test sad panda + if (url.getHost().equals(EhInfo.EX_HOST)) { + String contentType = conn.getContentType(); + if (contentType != null && contentType.equals("image/gif") + && conn.getContentLength() == 9615) + throw new SadPandaException(); + } + // Store cookie if necessary + if (isCookiable) + EhInfo.getInstance(mContext).storeCookie(conn); + // Get object connection + Object obj = rh.onAfterConnect(conn); + // Send to UI thread if necessary + if (mListener != null) + AppHandler.getInstance().sendMessage( + Message.obtain(null, AppHandler.HTTP_HELPER_TAG, + Constants.TRUE, 0, new Package(obj, mListener))); + + return obj; + // redirect + case HttpURLConnection.HTTP_MOVED_PERM: + case HttpURLConnection.HTTP_MOVED_TEMP: + case HttpURLConnection.HTTP_SEE_OTHER: + case Constants.HTTP_TEMP_REDIRECT: + final String location = conn.getHeaderField("Location"); + Log.d(TAG, "New location " + location); + mLastUrl = location; + conn.disconnect(); + url = new URL(url, location); + continue; + + default: + String body; + // Get pure text error + if (rh instanceof GetStringHelper + && (body = (String)rh.onAfterConnect(conn)) != null + && !body.contains("<")) + throw new Exception(body); + else + throw new ResponseCodeException(responseCode); + } + } + throw new RedirectionException(); + } catch (Exception e) { + mException = e; + e.printStackTrace(); + // For download, if need stop, just stop + if (mException instanceof StopRequestException) + break; + } finally { + if (conn != null) + conn.disconnect(); + } + } + + // Send to UI thread if necessary + if (mListener != null) + AppHandler.getInstance().sendMessage( + Message.obtain(null, AppHandler.HTTP_HELPER_TAG, + Constants.FALSE, 0, new Package(getEMsg(), mListener))); + // Send exception to helper + if (mException != null) + rh.onGetException(mException); + + return null; + } + + private interface RequestHelper { + + /** + * Get the URL to connect + * @return + */ + public URL getUrl() throws MalformedURLException; + + /** + * Add header or do something else for HttpURLConnection before connect + * @param conn + * @throws Exception + */ + + public void onBeforeConnect(HttpURLConnection conn) throws Exception; + + /** + * Get what do you need from HttpURLConnection after connect + * Return null means get error + * @param conn + * @return + * @throws Exception + */ + public Object onAfterConnect(HttpURLConnection conn) throws Exception; + + /** + * Handle exception + * @param e + */ + public void onGetException(Exception e); + } + + /** + * RequstHelper for check sad panda, use HEAD method + */ + private class CheckSpHelper implements RequestHelper { + @Override + public URL getUrl() throws MalformedURLException { + return new URL(EhClient.HEADER_EX); + } + + @Override + public void onBeforeConnect(HttpURLConnection conn) + throws Exception { + conn.setRequestMethod("HEAD"); + } + + @Override + public Object onAfterConnect(HttpURLConnection conn) + throws Exception { + return HAPPY_PANDA_BODY; + } + + @Override + public void onGetException(Exception e) {} + } + + private abstract class GetStringHelper implements RequestHelper { + private final String mUrl; + + public GetStringHelper(String url) { + mUrl = url; + } + + @Override + public URL getUrl() throws MalformedURLException { + return new URL(mUrl); + } + + @Override + public void onBeforeConnect(HttpURLConnection conn) + throws Exception { + conn.addRequestProperty("Accept-Encoding", "gzip"); + } + + private String getBody(HttpURLConnection conn) + throws Exception { + String body = null; + InputStream is = null; + ByteArrayOutputStream baos = null; + try { + is = conn.getInputStream(); + String encoding = conn.getContentEncoding(); + if (encoding != null && encoding.equals("gzip")) + is = new GZIPInputStream(is); + + int length = conn.getContentLength(); + if (length >= 0) + baos = new ByteArrayOutputStream(length); + else + baos = new ByteArrayOutputStream(); + + Utils.copy(is, baos, Constants.BUFFER_SIZE); + + // Get charset + String charset = null; + String contentType = conn.getContentType(); + int index = -1; + if (contentType != null + && (index = contentType.indexOf(CHARSET_KEY)) != -1) { + charset = contentType.substring(index + CHARSET_KEY.length()); + } else + charset = DEFAULT_CHARSET; + + body = baos.toString(charset); + if (body == null) + throw new GetBodyException(); + } catch (Exception e) { + throw e; + } finally { + Utils.closeQuietly(is); + Utils.closeQuietly(baos); + } + return body; + } + + @Override + public Object onAfterConnect(HttpURLConnection conn) + throws Exception { + return getBody(conn); + } + + @Override + public void onGetException(Exception e) {} + } + + /** + * RequstHelper for GET method + */ + private class GetHelper extends GetStringHelper { + + public GetHelper(String url) { + super(url); + } + + @Override + public void onBeforeConnect(HttpURLConnection conn) + throws Exception { + super.onBeforeConnect(conn); + conn.setRequestMethod("GET"); + } + } + + /** + * RequstHelper for post form data, use POST method + */ + private class PostFormHelper extends GetStringHelper { + private final String[][] mArgs; + + public PostFormHelper(String url, String[][] args) { + super(url); + mArgs = args; + } + + @Override + public void onBeforeConnect(HttpURLConnection conn) + throws Exception { + super.onBeforeConnect(conn); + conn.setDoOutput(true); + conn.setDoInput(true); + conn.setUseCaches(false); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", + "application/x-www-form-urlencoded"); + + DataOutputStream out = new DataOutputStream(conn.getOutputStream()); + StringBuilder sb = new StringBuilder(); + int i = 0; + for (String[] arg : mArgs) { + if (i != 0) + sb.append("&"); + sb.append(URLEncoder.encode(arg[0], "UTF-8")); + sb.append("="); + sb.append(URLEncoder.encode(arg[1], "UTF-8")); + i++; + } + out.writeBytes(sb.toString()); + out.flush(); + out.close(); + } + } + + /** + * RequstHelper for post json, use POST method + */ + private class PostJsonHelper extends GetStringHelper { + private final JSONObject mJo; + + public PostJsonHelper(String url, JSONObject jo) { + super(url); + mJo = jo; + } + + @Override + public void onBeforeConnect(HttpURLConnection conn) throws Exception { + super.onBeforeConnect(conn); + conn.setDoOutput(true); + conn.setDoInput(true); + conn.setUseCaches(false); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/json"); + + DataOutputStream out = new DataOutputStream(conn.getOutputStream()); + out.writeBytes(mJo.toString()); + out.flush(); + out.close(); + } + } + + public abstract static class FormData { + private final Map mProperties; + + public FormData() { + mProperties = new LinkedHashMap(); + } + + public void setProperty(String key, String value) { + mProperties.put(key, value); + } + + public void clearProperty(String key) { + mProperties.remove(key); + } + + public void clearAllProperties() { + mProperties.clear(); + } + + /** + * Put information to target OutputStream. + * + * @param os Target OutputStream + * @throws IOException + */ + public abstract void output(OutputStream os) throws IOException; + + public void doOutPut(OutputStream os) throws IOException { + StringBuilder sb = new StringBuilder(); + for (String key : mProperties.keySet()) + sb.append(key).append(": ").append(mProperties.get(key)).append("\r\n"); + sb.append("\r\n"); + os.write(sb.toString().getBytes()); + output(os); + } + } + + public static class StringData extends FormData { + private final String mStr; + + public StringData(String str) { + mStr = str; + } + + @Override + public void output(OutputStream os) throws IOException { + os.write(mStr.getBytes()); + os.write("\r\n".getBytes()); + } + } + + public static class BitmapData extends FormData { + private final Bitmap mBitmap; + + public BitmapData(Bitmap bmp) { + mBitmap = bmp; + setProperty("Content-Type", "image/jpeg"); + } + + @Override + public void output(OutputStream os) throws IOException { + mBitmap.compress(Bitmap.CompressFormat.JPEG, 98, os); //TODO I need a better quality + os.write("\r\n".getBytes()); + } + } + + public static class FileData extends FormData { + private final File mFile; + + public FileData(File file) { + mFile = file; + setProperty("Content-Type", + URLConnection.guessContentTypeFromName(file.getName())); + } + + @Override + public void output(OutputStream os) throws IOException { + InputStream is = new FileInputStream(mFile); + byte[] buffer = new byte[1024]; + int bytesRead; + while((bytesRead = is.read(buffer)) !=-1) + os.write(buffer, 0, bytesRead); + is.close(); + + os.write("\r\n".getBytes()); + } + } + + private class PostFormDataHelper extends GetStringHelper { + private static final String BOUNDARY = "------WebKitFormBoundary7eDB0hDQ91s22Tkf"; + + private final List mDataList; + + + public PostFormDataHelper(String url, List dataList) { + super(url); + mDataList = dataList; + } + + @Override + public void onBeforeConnect(HttpURLConnection conn) throws Exception { + super.onBeforeConnect(conn); + conn.setDoOutput(true); + conn.setDoInput(true); + conn.setUseCaches(false); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundary7eDB0hDQ91s22Tkf"); + + DataOutputStream out = new DataOutputStream(conn.getOutputStream()); + //OutputStream out = new FileOutputStream(new File("/sdcard/a.txt")); + + for (FormData data : mDataList) { + out.write(BOUNDARY.getBytes()); + out.write("\r\n".getBytes()); + data.doOutPut(out); + } + out.write(BOUNDARY.getBytes()); + out.write("--".getBytes()); + + out.flush(); + out.close(); + } + } + + private class GetImageHelper implements RequestHelper { + private final String mUrl; + + public GetImageHelper(String url) { + mUrl = url; + } + + @Override + public URL getUrl() throws MalformedURLException { + return new URL(mUrl); + } + + @Override + public void onBeforeConnect(HttpURLConnection conn) + throws Exception { + conn.setRequestMethod("GET"); + } + + @Override + public Object onAfterConnect(HttpURLConnection conn) + throws Exception { + // If just + // Bitmap bmp = BitmapFactory.decodeStream(conn.getInputStream(), null, Ui.getBitmapOpt()); + // bitmap might be incomplete. + int size = conn.getContentLength(); + FastByteArrayOutputStream fbaos = new FastByteArrayOutputStream(size == -1 ? 24 * 1024 : (size + 100)); + Utils.copy(conn.getInputStream(), fbaos); + Bitmap bmp = BitmapFactory.decodeByteArray(fbaos.getBuffer(), 0, fbaos.size(), Ui.getBitmapOpt()); + + if (bmp == null) + throw new GetBodyException(); + return bmp; + } + + @Override + public void onGetException(Exception e) {} + } + + public static interface OnDownloadListener { + public void onDownloadStartConnect(); + /** + * If totalSize -1 for can't get length info + * @param totalSize + */ + public void onDownloadStartDownload(int totalSize); + /** + * If totalSize -1 for can't get length info + * @param totalSize + */ + public void onDownloadStatusUpdate(int downloadSize, int totalSize); + public void onDownloadOver(int status, String eMsg); + } + + public static class DownloadControlor { + private boolean mStop = false; + + public void stop() { + mStop = true; + } + + public void reset() { + mStop = false; + } + + public boolean isStop() { + return mStop; + } + } + + private class DownloadHelper implements RequestHelper { + + private static final String DOWNLOAD_EXTENSION = ".download"; + + private final String mUrl; + private final File mDir; + private String mFileName; + private File mFile; + private File mTempFile; + private final boolean mIsProxy; + private final DownloadControlor mControlor; + private final OnDownloadListener mListener; + private int mContentLength; + private int mReceivedSize; + + public DownloadHelper(String url, File dir, String fileName, boolean isProxy, + DownloadControlor controlor, OnDownloadListener listener) { + mUrl = url; + mDir = dir; + mFileName = fileName; + mIsProxy = isProxy; + mControlor = controlor; + mListener = listener; + } + + @Override + public URL getUrl() throws MalformedURLException { + return new URL(mIsProxy? getProxyUrl() : mUrl); + } + + @Override + public void onBeforeConnect(HttpURLConnection conn) throws Exception { + if (mControlor != null && mControlor.isStop()) + throw new StopRequestException(); + + if (mIsProxy) { + conn.setDoOutput(true); + conn.setDoInput(true); + conn.setUseCaches(false); + conn.setRequestMethod("POST"); + + DataOutputStream out = new DataOutputStream(conn.getOutputStream()); + out.writeBytes("{\"url\":\"" + mUrl + "\"}"); + out.flush(); + out.close(); + } else { + conn.setRequestMethod("GET"); + conn.setRequestProperty("Range", "bytes=0-"); + } + if (mListener != null) + mListener.onDownloadStartConnect(); + } + + private int getContentLength(HttpURLConnection conn) { + int contentLength = conn.getContentLength(); + String range; + if (contentLength == -1 && + (range = conn.getHeaderField("Content-Range")) != null) { + // Content-Range looks like bytes 500-999/1234 + contentLength = 0; + int step = 0; + boolean isNum = false; + char ch; + for (int i = 0; i < range.length(); i++) { + ch = range.charAt(i); + if (ch >= '0' && ch <= '9') { + if (!isNum) { + isNum = true; + step++; + } + } else { + isNum = false; + } + if (isNum && step == 3) + contentLength = contentLength * 10 + ch - '0'; + } + } + return contentLength; + } + + protected String fixNewExtension(String ext) { + return null; + } + + @Override + public Object onAfterConnect(HttpURLConnection conn) throws Exception { + // Check stop + if (mControlor != null && mControlor.isStop()) + throw new StopRequestException(); + + String contentType = conn.getContentType(); + int index = contentType.indexOf(';'); + if (index != -1) + contentType = contentType.substring(0, index); + + mContentLength = getContentLength(conn); + if (mListener != null) + mListener.onDownloadStartDownload(mContentLength); + + // Fix extension + MimeTypeMap mime = MimeTypeMap.getSingleton(); + String newExtension = mime.getExtensionFromMimeType(contentType); + if (newExtension == null) { + index = contentType.lastIndexOf('/'); + if (index != -1) + newExtension = contentType.substring(index + 1); + } + newExtension = fixNewExtension(newExtension); + if (newExtension != null) { + index = mFileName.lastIndexOf('.'); + if (index == -1) { + mFileName = mFileName + '.' + newExtension; + } else { + mFileName = mFileName.substring(0, index) + '.' + newExtension; + } + } + + // Make sure parent exist + mDir.mkdirs(); + mFile = new File(mDir, mFileName); + mTempFile = new File(mDir, mFileName + DOWNLOAD_EXTENSION); + // Transfer + transferData(conn.getInputStream(), new FileOutputStream(mTempFile)); + // Get ok, rename + mTempFile.renameTo(mFile); + // Callback + if (mListener != null) + mListener.onDownloadOver(DOWNLOAD_OK_CODE, null); + return DOWNLOAD_OK_STR; + } + + @Override + public void onGetException(Exception e) { + // Delete unfinished file + if (mTempFile != null) + mTempFile.delete(); + if (mFile != null) + mFile.delete(); + // Callback + if (mListener != null) { + if (e instanceof StopRequestException) + mListener.onDownloadOver(DOWNLOAD_STOP_CODE, getEMsg()); + else + mListener.onDownloadOver(DOWNLOAD_FAIL_CODE, getEMsg()); + } + } + + private void transferData(InputStream in, OutputStream out) + throws Exception { + final byte data[] = new byte[Constants.BUFFER_SIZE]; + mReceivedSize = 0; + + while (true) { + // Check stop first + if (mControlor != null && mControlor.isStop()) + throw new StopRequestException(); + + int bytesRead = in.read(data); + if (bytesRead == -1) + break; + out.write(data, 0, bytesRead); + mReceivedSize += bytesRead; + if (mListener != null) + mListener.onDownloadStatusUpdate(mReceivedSize, mContentLength); + } + + if (mContentLength != -1 && mReceivedSize != mContentLength) + throw new UncompletedException("Received size is " + mReceivedSize + + ", but ContentLength is " + mContentLength); + } + + } + + + private class DownloadEhImageHelper extends DownloadHelper { + + public DownloadEhImageHelper(String url, File dir, String fileName, + boolean isProxy, DownloadControlor controlor, + OnDownloadListener listener) { + super(url, dir, fileName, isProxy, controlor, listener); + } + + @Override + protected String fixNewExtension(String ext) { + int length = EhUtils.POSSIBLE_IMAGE_EXTENSION_ARRAY.length; + int i; + for (i = 0; i < length; i++) { + if (EhUtils.POSSIBLE_IMAGE_EXTENSION_ARRAY[i].equals(ext)) + break; + } + if (i == length) + ext = EhUtils.DEFAULT_IMAGE_EXTENSION; + return ext; + } + } + + private class DownloadOriginEhImageHelper extends DownloadEhImageHelper { + + public DownloadOriginEhImageHelper(String url, File dir, String fileName, + boolean isProxy, DownloadControlor controlor, + OnDownloadListener listener) { + super(url, dir, fileName, isProxy, controlor, listener); + } + + @Override + public Object onAfterConnect(HttpURLConnection conn) throws Exception { + if (mLastUrl == null) + throw new BandwidthExceededException(); + + return super.onAfterConnect(conn); + } + } + + + /** + * Check Sad Panda. + * If get Sad Panda, return null, + * else return HttpHelper.HAPPY_PANDA_BODY + * @return + */ + public String checkSadPanda() { + return (String)requst(new CheckSpHelper()); + } + + /** + * Http GET method + * @param url + * @return + */ + public String get(String url) { + return (String)requst(new GetHelper(url)); + } + + /** + * Post form data + * @param url + * @param args + * @return + */ + public String postForm(String url, String[][] args) { + return (String)requst(new PostFormHelper(url, args)); + } + + /** + * Post json data + * @param url + * @param json + * @return + */ + public String postJson(String url, JSONObject json) { + return (String)requst(new PostJsonHelper(url, json)); + } + + /** + * Post form data, multipart/form-data + * @param url + * @param datas + * @return + */ + public String postFormData(String url, List dataList) { + return (String)requst(new PostFormDataHelper(url, dataList)); + } + + /** + * Get image + * @param url + * @return + */ + public Bitmap getImage(String url) { + return (Bitmap) requst(new GetImageHelper(url)); + } + + public String download(String url, File dir, String file, boolean isProxy, + DownloadControlor controlor, OnDownloadListener listener) { + return (String) requst(new DownloadHelper(url, dir, file, isProxy, + controlor, listener)); + } + + public void downloadInThread(final String url, final File dir, + final String file, final boolean isProxy, + final DownloadControlor controlor, final OnDownloadListener listener) { + new BgThread() { + @Override + public void run() { + download(url, dir, file, isProxy, controlor, listener); + } + }.start(); + } + + public String downloadEhImage(String url, File dir, String file, boolean isProxy, + DownloadControlor controlor, OnDownloadListener listener) { + return (String) requst(new DownloadEhImageHelper(url, dir, file, isProxy, + controlor, listener)); + } + + public String downloadOriginEhImage(String url, File dir, String file, boolean isProxy, + DownloadControlor controlor, OnDownloadListener listener) { + return (String) requst(new DownloadOriginEhImageHelper(url, dir, file, isProxy, + controlor, listener)); + } + + /** Exceptions **/ + + public class SadPandaException extends Exception { + private static final long serialVersionUID = 1L; + } + + public class GetBodyException extends Exception { + private static final long serialVersionUID = 1L; + } + + public class RedirectionException extends Exception { + private static final long serialVersionUID = 1L; + } + + public class UncompletedException extends Exception { + + public UncompletedException(String string) { + super(string); + } + + private static final long serialVersionUID = 1L; + } + + public class ResponseCodeException extends Exception { + + private static final long serialVersionUID = 1L; + private static final String eMsg = "Error response code"; + private final int mResponseCode; + + public ResponseCodeException(int responseCode) { + this(responseCode, eMsg); + } + + public ResponseCodeException(int responseCode, String message) { + super(message); + mResponseCode = responseCode; + } + + @Override + public String getMessage() { + return eMsg + ": " + mResponseCode; + } + + public int getResponseCode() { + return mResponseCode; + } + } + + public class BandwidthExceededException extends Exception { + + private static final long serialVersionUID = 1L; + + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/network/Network.java b/app/src/main/java/com/hippo/ehviewer/network/Network.java new file mode 100644 index 000000000..1a5d03674 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/network/Network.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.network; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; + +public final class Network { + + public static final int NETWORK_STATE_NONE = 0x0; + public static final int NETWORK_STATE_MOBILE = 0x1; + public static final int NETWORK_STATE_WIFI = 0x2; + + public static int getNetworkState(Context context) { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo mWifi = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI); + NetworkInfo mMobile = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); + + if (mWifi != null && mWifi.isConnected()) + return NETWORK_STATE_WIFI; + else if (mMobile != null && mMobile.isConnected()) + return NETWORK_STATE_MOBILE; + else + return NETWORK_STATE_NONE; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/network/UrlBuilder.java b/app/src/main/java/com/hippo/ehviewer/network/UrlBuilder.java new file mode 100644 index 000000000..0951a3407 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/network/UrlBuilder.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.network; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +public class UrlBuilder { + + public String mRootUrl; + public Map mQueryMap = new HashMap(); + + public UrlBuilder(String rootUrl) { + mRootUrl = rootUrl; + } + + public void addQuery(String key, Object value) { + mQueryMap.put(key, value); + } + + public String build() { + if (mQueryMap.size() == 0) { + return mRootUrl; + } else { + StringBuilder sb = new StringBuilder(mRootUrl); + sb.append("?"); + + Iterator iter = mQueryMap.keySet().iterator(); + if (iter.hasNext()) { + String key = iter.next(); + Object value = mQueryMap.get(key); + sb.append(key).append("=").append(value); + } + while (iter.hasNext()) { + String key = iter.next(); + Object value = mQueryMap.get(key); + sb.append("&").append(key).append("=").append(value); + } + return sb.toString(); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/preference/ColorPreference.java b/app/src/main/java/com/hippo/ehviewer/preference/ColorPreference.java new file mode 100644 index 000000000..465da5d73 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/preference/ColorPreference.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.preference; + +import java.util.Locale; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.app.MaterialAlertDialog; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.Ui; +import com.hippo.ehviewer.util.ViewUtils; +import com.hippo.ehviewer.widget.ColorPickerView; + +public class ColorPreference extends DialogPreference implements ColorPickerView.OnColorChangedListener, View.OnClickListener { + + private View mColorBrick; + + public ColorPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ColorPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setColor(int color) { + if (mColorBrick != null) + mColorBrick.setBackgroundColor(color); + } + + @Override + protected void onBindView(View view) { + super.onBindView(view); + + View ll = view.findViewById(android.R.id.widget_frame); + if (ll != null && ll instanceof LinearLayout) { + LinearLayout widgetFrame = (LinearLayout)ll; + widgetFrame.setVisibility(View.VISIBLE); + + mColorBrick = new View(getContext()); + if (isEnabled()) + setColor(Config.getThemeColor()); + else + setColor(getContext().getResources().getColor(R.color.disabled_mask)); + widgetFrame.addView(mColorBrick, new LinearLayout.LayoutParams(Ui.dp2pix(24), Ui.dp2pix(24))); + } + } + + @Override + protected boolean inScrollView() { + return true; + } + + @Override + @SuppressLint("InflateParams") + protected View onCreateDialogView() { + return ViewUtils.inflateDialogView(R.layout.color_scheme_dialog, false); + } + + private void setUpPresets(View view, final int which) { + final Button preset = (Button) view.findViewById(which); + if (preset != null) + preset.setOnClickListener(this); + } + + @Override + protected void onBindDialogView(View view) { + int curColor = Config.getThemeColor(); + final ColorPickerView colorPicker = (ColorPickerView) view.findViewById(R.id.color_picker_view); + ViewUtils.removeHardwareAccelerationSupport(colorPicker); + Button oldColor = (Button) view.findViewById(R.id.color_scheme_dialog_old_color); + oldColor.setOnClickListener(this); + final Button newColor = (Button) view.findViewById(R.id.color_scheme_dialog_new_color); + setUpPresets(view, R.id.color_scheme_dialog_preset_one); + setUpPresets(view, R.id.color_scheme_dialog_preset_two); + setUpPresets(view, R.id.color_scheme_dialog_preset_three); + setUpPresets(view, R.id.color_scheme_dialog_preset_four); + setUpPresets(view, R.id.color_scheme_dialog_preset_five); + setUpPresets(view, R.id.color_scheme_dialog_preset_six); + setUpPresets(view, R.id.color_scheme_dialog_preset_seven); + setUpPresets(view, R.id.color_scheme_dialog_preset_eight); + final EditText hexValue = (EditText) view.findViewById(R.id.color_scheme_dialog_hex_value); + hexValue.addTextChangedListener(new TextWatcher() { + @Override + public void onTextChanged(final CharSequence s, final int start, final int before, + final int count) { + try { + int color = Color.parseColor("#" + + hexValue.getText().toString().toUpperCase(Locale.getDefault())); + colorPicker.setColor(color); + newColor.setBackgroundColor(color); + } catch (final Exception ignored) { + } + } + + @Override + public void beforeTextChanged(final CharSequence s, final int start, final int count, + final int after) { + /* Nothing to do */ + } + + @Override + public void afterTextChanged(final Editable s) { + /* Nothing to do */ + } + }); + + colorPicker.setOnColorChangedListener(this); + oldColor.setBackgroundColor(curColor); + colorPicker.setColor(curColor, true); + } + + @Override + protected void onPrepareDialogBuilder(MaterialAlertDialog.Builder builder) { + // Empty + } + + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + super.onClick(dialog, which); + if (which == MaterialAlertDialog.POSITIVE) { + ColorPickerView colorPicker = (ColorPickerView) dialog.findViewById(R.id.color_picker_view); + if (colorPicker == null) + return true; + int color = colorPicker.getColor() | 0xff000000; + if (callChangeListener(color)) { + Config.setThemeColor(color); + setColor(color); + return true; + } else { + return false; + } + } else { + return true; + } + } + + private String padLeft(final String string, final char padChar, final int size) { + if (string.length() >= size) { + return string; + } + final StringBuilder result = new StringBuilder(); + for (int i = string.length(); i < size; i++) { + result.append(padChar); + } + result.append(string); + return result.toString(); + } + + @Override + public void onColorChanged(View view, int color) { + try { + View p = (View) view.getParent(); + EditText hexValue = (EditText) p.findViewById(R.id.color_scheme_dialog_hex_value); + Button newColor = (Button) view.findViewById(R.id.color_scheme_dialog_new_color); + + if (hexValue != null) + hexValue.setText(padLeft(Integer.toHexString(color).toUpperCase(Locale.getDefault()), + '0', 8)); + if (newColor != null) + newColor.setBackgroundColor(color); + } catch (Throwable e) { + } + } + + private int getColor(final int color) { + return getContext().getResources().getColor(color); + } + + @Override + public void onClick(View v) { + + View view = ViewUtils.getAncestor(v, R.id.main); + if (view == null) + return; + ColorPickerView colorPicker = (ColorPickerView) view.findViewById(R.id.color_picker_view); + if (colorPicker == null) + return; + + switch (v.getId()) { + case R.id.color_scheme_dialog_preset_one: + colorPicker.setColor(getColor(R.color.theme_red)); + break; + case R.id.color_scheme_dialog_preset_two: + colorPicker.setColor(getColor(R.color.theme_orange)); + break; + case R.id.color_scheme_dialog_preset_three: + colorPicker.setColor(getColor(R.color.theme_lime)); + break; + case R.id.color_scheme_dialog_preset_four: + colorPicker.setColor(getColor(R.color.theme_green)); + break; + case R.id.color_scheme_dialog_preset_five: + colorPicker.setColor(getColor(R.color.theme_teal)); + break; + case R.id.color_scheme_dialog_preset_six: + colorPicker.setColor(getColor(R.color.theme_blue)); + break; + case R.id.color_scheme_dialog_preset_seven: + colorPicker.setColor(getColor(R.color.theme_purple)); + break; + case R.id.color_scheme_dialog_preset_eight: + colorPicker.setColor(getColor(R.color.theme_brown)); + break; + case R.id.color_scheme_dialog_old_color: + colorPicker.setColor(Config.getThemeColor()); + break; + default: + break; + } + + onColorChanged(colorPicker, colorPicker.getColor()); + } + + @Override + protected void onDialogCreate(Dialog dialog) { + dialog.getWindow().setFormat(PixelFormat.RGBA_8888); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/preference/DialogPreference.java b/app/src/main/java/com/hippo/ehviewer/preference/DialogPreference.java new file mode 100644 index 000000000..394249e96 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/preference/DialogPreference.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.preference; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.util.AttributeSet; +import android.view.View; + +import com.hippo.ehviewer.app.MaterialAlertDialog; + +public abstract class DialogPreference extends android.preference.DialogPreference implements MaterialAlertDialog.OnClickListener { + + private MaterialAlertDialog.Builder mBuilder; + + /** The dialog, if it is showing. */ + private Dialog mDialog; + /** Which button was clicked. */ + private int mWhichButtonClicked; + + public DialogPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public DialogPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onClick() { + if (mDialog != null && mDialog.isShowing()) return; + + showDialog(null); + } + + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + mWhichButtonClicked = which; + return true; + } + + protected abstract void onPrepareDialogBuilder(MaterialAlertDialog.Builder builder); + + protected abstract boolean inScrollView(); + + @Override + protected void showDialog(Bundle state) { + Context context = getContext(); + + mWhichButtonClicked = MaterialAlertDialog.POSITIVE; + + mBuilder = new MaterialAlertDialog.Builder(context) + .setTitle(getDialogTitle()) + //.setIcon(mDialogIcon) // TODO Not support now + .setPositiveButton(getPositiveButtonText()) + .setNegativeButton(getNegativeButtonText()) + .setButtonListener(this); + + + View contentView = onCreateDialogView(); + if (contentView != null) { + onBindDialogView(contentView); + mBuilder.setView(contentView, inScrollView()); + } else { + mBuilder.setMessage(getDialogMessage()); + } + + onPrepareDialogBuilder(mBuilder); + + // Create the dialog + final Dialog dialog = mDialog = mBuilder.create(); + onDialogCreate(dialog); + if (state != null) { + dialog.onRestoreInstanceState(state); + } + dialog.setOnDismissListener(this); + dialog.show(); + } + + protected void onDialogCreate(Dialog dialog) { + + } + + @Override + protected abstract void onBindDialogView(View view); + + @Override + public void onDismiss(DialogInterface dialog) { + mDialog = null; + onDialogClosed(mWhichButtonClicked == MaterialAlertDialog.POSITIVE); + } + + @Override + public Dialog getDialog() { + return mDialog; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/preference/EditTextPreference.java b/app/src/main/java/com/hippo/ehviewer/preference/EditTextPreference.java new file mode 100644 index 000000000..140cb5bd8 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/preference/EditTextPreference.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.preference; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.EditText; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.app.MaterialAlertDialog; +import com.hippo.ehviewer.widget.MaterialToast; + +public class EditTextPreference extends DialogPreference { + + private EditText mEditText; + + private int mMax; + private int mMin; + + public EditTextPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0); + } + + public EditTextPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs, defStyle); + } + + private void init(Context context, AttributeSet attrs, int defStyle) { + mEditText = new EditText(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.EditTextPreference, defStyle, 0); + mMax = a.getInt(R.styleable.EditTextPreference_etpMax, Integer.MAX_VALUE); + mMin = a.getInt(R.styleable.EditTextPreference_etpMin, Integer.MIN_VALUE); + a.recycle(); + } + + @Override + protected boolean inScrollView() { + return true; + } + + @Override + protected View onCreateDialogView() { + // Make sure mEditText is an orphan + ViewParent oldParent = mEditText.getParent(); + if (oldParent != null) + ((ViewGroup) oldParent).removeView(mEditText); + + mEditText.setTextColor(getContext().getResources().getColor( + R.color.secondary_text_dark)); + return mEditText; + } + + @Override + protected void onBindDialogView(View view) { + mEditText.setText(getPersistedString(null)); + } + + @Override + protected void onPrepareDialogBuilder(MaterialAlertDialog.Builder builder) { + // Empty + } + + @Override + protected Object onGetDefaultValue(TypedArray a, int index) { + return a.getString(index); + } + + @Override + protected void onSetInitialValue(boolean restorePersistedValue, + Object defaultValue) { + setSummary(restorePersistedValue ? getPersistedString((String) defaultValue) + : (String) defaultValue); + if (!restorePersistedValue) + persistString((String) defaultValue); + } + + // Return true for close and save + private boolean checkBorder(String value) { + boolean re; + if (mMax == Integer.MAX_VALUE && mMin == Integer.MIN_VALUE) { + return true; + } else { + try { + int intValue = Integer.parseInt(value); + if (intValue > mMax || intValue < mMin) + re = false; + else + re = true; + } catch (Throwable e) { + re = false; + } + } + + // Show Toast + if (!re) + MaterialToast.showToast(R.string.invalid_input); + + return re; + } + + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + super.onClick(dialog, which); + if (which == MaterialAlertDialog.POSITIVE) { + String value = mEditText.getText().toString(); + if (checkBorder(value) && callChangeListener(value)) { + persistString(value); + setSummary(value); + return true; + } else { + return false; + } + } else { + return true; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/preference/ListPreference.java b/app/src/main/java/com/hippo/ehviewer/preference/ListPreference.java new file mode 100644 index 000000000..4f5b41c8f --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/preference/ListPreference.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.preference; + +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; + +import com.hippo.ehviewer.app.MaterialAlertDialog; +import com.hippo.ehviewer.app.MaterialAlertDialog.Builder; + +public class ListPreference extends DialogPreference { + + private CharSequence[] mEntries; + private CharSequence[] mEntryValues; + private String mValue; + private String mSummary; + private int mClickedDialogEntryIndex; + private boolean mValueSet; + + public ListPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0); + } + + public ListPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs, defStyle); + } + + private void init(Context context, AttributeSet attrs, int defStyle) { + TypedArray a = context.obtainStyledAttributes(attrs, + new int[]{ 16842930, 16843256 }, 0, 0); + mEntries = a.getTextArray(0); + mEntryValues = a.getTextArray(1); + a.recycle(); + + a = context.obtainStyledAttributes(attrs, + new int[]{ 16842754, 16842765, 16842766, + 16842994, 16843233, 16843238, 16843240, + 16843241, 16843242, 16843243, 16843244, + 16843245, 16843246, 16843491 }, 0, 0); + mSummary = a.getString(7); + a.recycle(); + } + + public void setEntries(CharSequence[] entries) { + mEntries = entries; + } + + public void setEntries(int entriesResId) { + setEntries(getContext().getResources().getTextArray(entriesResId)); + } + + public CharSequence[] getEntries() { + return mEntries; + } + + public void setEntryValues(CharSequence[] entryValues) { + mEntryValues = entryValues; + } + + public void setEntryValues(int entryValuesResId) { + setEntryValues(getContext().getResources().getTextArray(entryValuesResId)); + } + + public CharSequence[] getEntryValues() { + return mEntryValues; + } + + public void setValue(String value) { + // Always persist/notify the first time. + final boolean changed = !TextUtils.equals(mValue, value); + if (changed || !mValueSet) { + mValue = value; + mValueSet = true; + persistString(value); + if (changed) { + notifyChanged(); + } + } + } + + @Override + public CharSequence getSummary() { + final CharSequence entry = getEntry(); + if (entry == null) { + return super.getSummary(); + } else if (mSummary == null) { + return entry; + } else { + return String.format(mSummary, entry); + } + } + + @Override + public void setSummary(CharSequence summary) { + super.setSummary(summary); + if (summary == null && mSummary != null) { + mSummary = null; + } else if (summary != null && !summary.equals(mSummary)) { + mSummary = summary.toString(); + } + } + + public void setValueIndex(int index) { + if (mEntryValues != null) { + setValue(mEntryValues[index].toString()); + } + } + + public String getValue() { + return mValue; + } + + public CharSequence getEntry() { + int index = getValueIndex(); + return index >= 0 && mEntries != null ? mEntries[index] : null; + } + + public int findIndexOfValue(String value) { + if (value != null && mEntryValues != null) { + for (int i = mEntryValues.length - 1; i >= 0; i--) { + if (mEntryValues[i].equals(value)) { + return i; + } + } + } + return -1; + } + + private int getValueIndex() { + return findIndexOfValue(mValue); + } + + @Override + protected boolean inScrollView() { + return false; + } + + @Override + protected void onBindDialogView(View view) { + // Empty + } + + @Override + protected void onPrepareDialogBuilder(Builder builder) { + + mClickedDialogEntryIndex = getValueIndex(); + + builder.setSingleChoiceItems(mEntries, mClickedDialogEntryIndex, + new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + mClickedDialogEntryIndex = which; + ListPreference.this.onClick(dialog, MaterialAlertDialog.POSITIVE); + return true; + } + }); + } + + + @Override + protected void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + + if (positiveResult && mClickedDialogEntryIndex >= 0 && mEntryValues != null) { + String value = mEntryValues[mClickedDialogEntryIndex].toString(); + if (callChangeListener(value)) { + setValue(value); + } + } + } + + @Override + protected Object onGetDefaultValue(TypedArray a, int index) { + return a.getString(index); + } + + @Override + protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { + setValue(restoreValue ? getPersistedString(mValue) : (String) defaultValue); + } + + @Override + protected Parcelable onSaveInstanceState() { + final Parcelable superState = super.onSaveInstanceState(); + if (isPersistent()) { + // No need to save instance state since it's persistent + return superState; + } + + final SavedState myState = new SavedState(superState); + myState.value = getValue(); + return myState; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (state == null || !state.getClass().equals(SavedState.class)) { + // Didn't save state for us in onSaveInstanceState + super.onRestoreInstanceState(state); + return; + } + + SavedState myState = (SavedState) state; + super.onRestoreInstanceState(myState.getSuperState()); + setValue(myState.value); + } + + private static class SavedState extends BaseSavedState { + String value; + + public SavedState(Parcel source) { + super(source); + value = source.readString(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeString(value); + } + + public SavedState(Parcelable superState) { + super(superState); + } + + @SuppressWarnings("unused") + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/preference/NumberOfColumnsPreference.java b/app/src/main/java/com/hippo/ehviewer/preference/NumberOfColumnsPreference.java new file mode 100644 index 000000000..96dc4502e --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/preference/NumberOfColumnsPreference.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.preference; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.View; +import android.widget.EditText; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.app.MaterialAlertDialog; +import com.hippo.ehviewer.app.MaterialAlertDialog.Builder; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.ViewUtils; +import com.hippo.ehviewer.widget.MaterialToast; + +public class NumberOfColumnsPreference extends DialogPreference { + + private Context mContext; + private String keyPortrait; + private String keyLandscape; + private int defValuePortrait; + private int defValueLandscape; + + public NumberOfColumnsPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0); + } + + public NumberOfColumnsPreference(Context context, AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + init(context, attrs, defStyle); + } + + private void updateSummary(int portrait, int landscape) { + setSummary(String.format("%s: %d, %s: %d", mContext.getString(R.string.portrait), + portrait, mContext.getString(R.string.landscape), landscape)); + } + + private void init(Context context, AttributeSet attrs, + int defStyle) { + mContext = context; + TypedArray a = mContext.obtainStyledAttributes(attrs, + R.styleable.NumberOfColumnsPreference, defStyle, 0); + + keyPortrait = a.getString(R.styleable.NumberOfColumnsPreference_keyPortrait); + keyLandscape = a.getString(R.styleable.NumberOfColumnsPreference_keyLandscape); + defValuePortrait = a.getInt(R.styleable.NumberOfColumnsPreference_defValuePortrait, 0); + defValueLandscape = a.getInt(R.styleable.NumberOfColumnsPreference_defValueLandscape, 0); + + if (defValuePortrait <= 0 || defValuePortrait >= 100) + defValuePortrait = 1; + if (defValueLandscape <= 0 || defValueLandscape >= 100) + defValueLandscape = 1; + + a.recycle(); + + updateSummary(Config.getInt(keyPortrait, defValuePortrait), + Config.getInt(keyLandscape, defValueLandscape)); + } + + @Override + protected boolean inScrollView() { + return true; + } + + @Override + @SuppressLint("InflateParams") + protected View onCreateDialogView() { + return ViewUtils.inflateDialogView(R.layout.set_column_number, false); + } + + @Override + protected void onBindDialogView(View view) { + final EditText editTextPortrait = (EditText) view.findViewById(R.id.portrait); + editTextPortrait.setText(String.valueOf(Config.getInt(keyPortrait, defValuePortrait))); + final EditText editTextLandscape = (EditText) view.findViewById(R.id.landscape); + editTextLandscape.setText(String.valueOf(Config.getInt(keyLandscape, defValueLandscape))); + } + + @Override + protected void onPrepareDialogBuilder(Builder builder) { + // Empty + } + + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + super.onClick(dialog, which); + if (which == MaterialAlertDialog.POSITIVE) { + try { + EditText editTextPortrait = (EditText) dialog.findViewById(R.id.portrait); + EditText editTextLandscape = (EditText) dialog.findViewById(R.id.landscape); + int newPortrait = Integer.valueOf(editTextPortrait.getText().toString()); + int newLandscape = Integer.valueOf(editTextLandscape.getText().toString()); + if (newPortrait > 0 && newPortrait < 25 && + newLandscape > 0 && newLandscape < 25) { // TODO Need a better range + Config.setInt(keyPortrait, newPortrait); + Config.setInt(keyLandscape, newLandscape); + updateSummary(newPortrait, newLandscape); + return true; + } else { + throw new Exception(); + } + } catch (Exception e) { + MaterialToast.showToast(R.string.invalid_input); + return false; + } + } else { + return true; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/preference/SeekBarDialogPreference.java b/app/src/main/java/com/hippo/ehviewer/preference/SeekBarDialogPreference.java new file mode 100644 index 000000000..709526a1f --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/preference/SeekBarDialogPreference.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.preference; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.SeekBar; + +import com.hippo.ehviewer.app.MaterialAlertDialog; +import com.hippo.ehviewer.app.MaterialAlertDialog.Builder; +import com.hippo.ehviewer.util.Utils; + +public class SeekBarDialogPreference extends DialogPreference { + + private SeekBar mSeekBar; + + public SeekBarDialogPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0); + } + + public SeekBarDialogPreference(Context context, AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + init(context, attrs, defStyle); + } + + private void init(Context context, AttributeSet attrs, int defStyle) { + mSeekBar = new SeekBar(context, attrs); + } + + @Override + protected boolean inScrollView() { + return true; + } + + @Override + protected View onCreateDialogView() { + // Make sure mEditText is an orphan + ViewParent oldParent = mSeekBar.getParent(); + if (oldParent != null) + ((ViewGroup) oldParent).removeView(mSeekBar); + + return mSeekBar; + } + + @Override + protected void onBindDialogView(View view) { + mSeekBar.setProgress(Utils.parseIntSafely(getPersistedString(null), 120)); + } + + @Override + protected void onPrepareDialogBuilder(Builder builder) { + // Empty + } + + @Override + protected Object onGetDefaultValue(TypedArray a, int index) { + return a.getString(index); + } + + @Override + protected void onSetInitialValue(boolean restorePersistedValue, + Object defaultValue) { + setSummary(restorePersistedValue ? getPersistedString((String) defaultValue) + : (String) defaultValue); + if (!restorePersistedValue) + persistString((String) defaultValue); + } + + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + super.onClick(dialog, which); + if (which == MaterialAlertDialog.POSITIVE) { + String value = Integer.toString(mSeekBar.getProgress()); + if (callChangeListener(value)) { + persistString(value); + setSummary(value); + return true; + } else { + return false; + } + } else { + return true; + } + } + +} diff --git a/app/src/main/java/com/hippo/ehviewer/receiver/NetworkChangeReceiver.java b/app/src/main/java/com/hippo/ehviewer/receiver/NetworkChangeReceiver.java new file mode 100644 index 000000000..be963880a --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/receiver/NetworkChangeReceiver.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.receiver; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; + +import com.hippo.ehviewer.network.Network; +import com.hippo.ehviewer.util.Secret; + +public class NetworkChangeReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { + + // Update secret image here + int state = Network.getNetworkState(context); + if (state == Network.NETWORK_STATE_WIFI) { + Secret.updateSecretImage(context); + } + + // TODO stop download when mobile network + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/service/DownloadService.java b/app/src/main/java/com/hippo/ehviewer/service/DownloadService.java new file mode 100644 index 000000000..41f2e3ed2 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/service/DownloadService.java @@ -0,0 +1,479 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.service; + +import java.util.ArrayList; +import java.util.List; + +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; +import android.support.v4.app.NotificationCompat; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.data.Data; +import com.hippo.ehviewer.data.DownloadInfo; +import com.hippo.ehviewer.data.GalleryInfo; +import com.hippo.ehviewer.ehclient.ExDownloader; +import com.hippo.ehviewer.ehclient.ExDownloaderManager; +import com.hippo.ehviewer.ui.DownloadActivity; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.Utils; + +public class DownloadService extends Service + implements ExDownloader.ListenerForDownload { + + @SuppressWarnings("unused") + private static final String TAG = DownloadService.class.getSimpleName(); + + private static final int DOWNLOADING_NOTIFY_ID = -1; + private static final int DOWNLOAD_NOTIFY_ID = -2; + + public static final String ACTION_UPDATE = "com.hippo.ehviewer.service.DownloadService.UPDATE"; + + public static final String ACTION_ADD = "com.hippo.ehviewer.service.DownloadService.ADD"; + public static final String ACTION_START = "com.hippo.ehviewer.service.DownloadService.START"; + public static final String ACTION_START_ALL = "com.hippo.ehviewer.service.DownloadService.START_ALL"; + public static final String ACTION_STOP = "com.hippo.ehviewer.service.DownloadService.STOP"; + public static final String ACTION_STOP_CURRENT = "com.hippo.ehviewer.service.DownloadService.STOP_CURRENT"; + public static final String ACTION_STOP_ALL = "com.hippo.ehviewer.service.DownloadService.STOP_ALL"; + public static final String ACTION_DELETE = "com.hippo.ehviewer.service.DownloadService.DELETE"; + public static final String ACTION_CLEAR = "com.hippo.ehviewer.service.DownloadService.ACTION_CLEAR"; + + public static final String KEY_GID = "gid"; + public static final String KEY_GALLERY_INFO = "gallery_info"; + + private Context mContext; + private Data mData; + private ExDownloaderManager mEdManager; + private volatile DownloadInfo mCurDownloadInfo = null; + private volatile ExDownloader mCurExDownloader = null; + private Binder mBinder = null; + private NotificationManager mNotifyManager; + private NotificationCompat.Builder mBuilder; + private String mSpeedStr = null; + + private final List mDownloadOk = new ArrayList(); + private final List mDownloadFailed = new ArrayList(); + + @Override + public void onCreate() { + super.onCreate(); + + mContext = getApplication(); + mData = Data.getInstance(); + mEdManager = ExDownloaderManager.getInstance(); + + mBinder = new Binder(); + mNotifyManager = (NotificationManager) + getSystemService(Context.NOTIFICATION_SERVICE); + } + + private synchronized void handleIntent(Intent intent) { + boolean startDownload = false; + + if (intent == null) { + if (!Config.getKeepDownloadService()) { + stopForeground(true); + stopSelf(); + return; + } else { + startDownload = true; + } + } else { + String action = intent.getAction(); + if (ACTION_ADD.equals(action)) { + GalleryInfo gi = intent.getParcelableExtra(KEY_GALLERY_INFO); + if (gi != null) + add(gi); + else + startDownload = true; + + } else if (ACTION_START.equals(action)) { + int gid = intent.getIntExtra(KEY_GID, -1); + if (gid != -1) + start(gid); + else + startDownload = true; + + } else if (ACTION_START_ALL.equals(action)) { + startAll(); + + } else if (ACTION_STOP.equals(action)) { + int gid = intent.getIntExtra(KEY_GID, -1); + if (gid != -1) + stop(gid); + else + startDownload = true; + + } else if (ACTION_STOP_CURRENT.equals(action)) { + stopCurrentTask(); + + } else if (ACTION_STOP_ALL.equals(action)) { + stopAll(); + + } else if (ACTION_DELETE.equals(action)) { + int gid = intent.getIntExtra(KEY_GID, -1); + if (gid != -1) + delete(gid); + else + startDownload = true; + + } else if (ACTION_CLEAR.equals(action)) { + mDownloadOk.clear(); + mDownloadFailed.clear(); + startDownload = true; + + } else { + startDownload = true; + } + } + + if (startDownload) + tryToStartDownload(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + handleIntent(intent); + return START_STICKY; + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + @Override + public void onDestroy() { + super.onDestroy(); + + stopForeground(true); + + mBinder = null; + mNotifyManager = null; + } + + /** + * Try to start download, if there is no item to start, stop itself + */ + private synchronized void tryToStartDownload() { + if (mCurDownloadInfo != null || mCurExDownloader != null) + return; + + mCurDownloadInfo = mData.getFirstWaitDownloadInfo(); + if (mCurDownloadInfo == null) { + stopSelf(); + return; + } + + mCurDownloadInfo.state = DownloadInfo.STATE_DOWNLOAD; + mCurDownloadInfo.download = -1; + mCurDownloadInfo.total = -1; + // To update download state + mData.addDownload(mCurDownloadInfo); + + GalleryInfo gi = mCurDownloadInfo.galleryInfo; + mCurExDownloader = mEdManager.getExDownloader(gi.gid, + gi.token, gi.title, mCurDownloadInfo.mode); + mCurExDownloader.setListenerDownload(this); + // setDownloadMode will start download thread + mCurExDownloader.setDownloadMode(true); + } + + /** + * notify download info has changed + */ + private void notifyUpdate() { + Intent it = new Intent(ACTION_UPDATE); + sendBroadcast(it); + } + + /** + * If task is in list, reture false + * @param galleryInfo + * @return + */ + private boolean add(GalleryInfo galleryInfo) { + int gid = galleryInfo.gid; + DownloadInfo di; + if ((di = mData.getDownload(gid)) != null) { + if (di.state != DownloadInfo.STATE_DOWNLOAD) { + mData.setDownloadState(gid, DownloadInfo.STATE_WAIT); + tryToStartDownload(); + notifyUpdate(); + } + return false; + } else { + di = new DownloadInfo(galleryInfo, Config.getMode()); + di.state = DownloadInfo.STATE_WAIT; + mData.addDownload(di); + tryToStartDownload(); + notifyUpdate(); + return true; + } + } + + private synchronized void start(int gid) { + DownloadInfo di = mData.getDownload(gid); + if (di != null) + start(di); + } + + private synchronized void start(DownloadInfo di) { + if (mCurDownloadInfo != di && di.state != DownloadInfo.STATE_WAIT) { + mData.setDownloadState(di, DownloadInfo.STATE_WAIT); + notifyUpdate(); + } + tryToStartDownload(); + } + + public void startAll() { + mData.startAllDownload(); + tryToStartDownload(); + notifyUpdate(); + } + + private synchronized void stop(int gid) { + DownloadInfo di = mData.getDownload(gid); + if (di != null) + stop(di); + } + + private synchronized void stop(DownloadInfo di) { + if (mCurDownloadInfo == di) { + // Cancel download notification + stopForeground(true); + + // Target downloadinfo is downloading + mData.setDownloadState(mCurDownloadInfo, DownloadInfo.STATE_NONE); + mCurDownloadInfo = null; + + mCurExDownloader.setListenerDownload(null); + mCurExDownloader.setDownloadMode(false); + mEdManager.freeExDownloader(mCurExDownloader); + mCurExDownloader = null; + + tryToStartDownload(); + notifyUpdate(); + } else { + di.state = DownloadInfo.STATE_NONE; + tryToStartDownload(); + notifyUpdate(); + } + } + + public void stopCurrentTask() { + if (mCurDownloadInfo != null) + stop(mCurDownloadInfo); + else + tryToStartDownload(); + } + + public synchronized void stopAll() { + mData.stopAllDownload(); + stopCurrentTask(); + } + + private synchronized void delete(int gid) { + DownloadInfo di = mData.getDownload(gid); + if (di != null) + delete(di); + } + + public void delete(DownloadInfo di) { + stop(di); + mData.deleteDownload(di.galleryInfo.gid); + notifyUpdate(); + } + + private void ensureNotification() { + if (mBuilder != null) + return; + + mBuilder = new NotificationCompat.Builder(mContext); + mBuilder.setSmallIcon(android.R.drawable.stat_sys_download); + Intent intent = new Intent(DownloadService.this,DownloadActivity.class); + PendingIntent pendingIntent = PendingIntent.getActivity(DownloadService.this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + mBuilder.setContentIntent(pendingIntent); + mBuilder.setOngoing(true).setAutoCancel(false); + + // Add action + Intent stopIntent = new Intent(this, DownloadService.class); + stopIntent.setAction(ACTION_STOP_CURRENT); + PendingIntent piStop = PendingIntent.getService(this, 0, stopIntent, 0); + mBuilder.addAction(R.drawable.ic_action_stop, getString(R.string.stop), piStop); + + Intent stopAllIntent = new Intent(this, DownloadService.class); + stopAllIntent.setAction(ACTION_STOP_ALL); + PendingIntent piStopAll = PendingIntent.getService(this, 0, stopAllIntent, 0); + mBuilder.addAction(R.drawable.ic_action_close, getString(R.string.stop_all), piStopAll); + } + + @Override + public void onStart(int gid) { + if (mCurDownloadInfo == null) + return; + + mCurDownloadInfo.download = -1; + mCurDownloadInfo.total = -1; + + ensureNotification(); + + mBuilder.setContentTitle(getString(R.string.start_download) + " " + gid) + .setContentText(null) + .setProgress(0, 0, true); + startForeground(DOWNLOADING_NOTIFY_ID, mBuilder.build()); + + notifyUpdate(); + } + + @Override + public void onGetSum(int gid, int sum) { + if (mCurDownloadInfo == null) + return; + + mCurDownloadInfo.download = 0; + mCurDownloadInfo.total = sum; + + ensureNotification(); + + mBuilder.setContentTitle(getString(R.string.downloading) + " " + gid) + .setContentText(null) + .setProgress(mCurDownloadInfo.total, mCurDownloadInfo.download, false); + startForeground(DOWNLOADING_NOTIFY_ID, mBuilder.build()); + + notifyUpdate(); + } + + private long mLastStartForegroundTime = 0; + + @Override + public void onDownload(int gid, int downloadSize, int totalSize) { + if (mCurDownloadInfo == null) + return; + + mCurDownloadInfo.download = downloadSize; + mCurDownloadInfo.total = totalSize; + + ensureNotification(); + + mBuilder.setContentTitle(getString(R.string.downloading) + " " + gid) + .setContentText(mSpeedStr) + .setProgress(totalSize, downloadSize, false); + + long now = System.currentTimeMillis(); + if (now - mLastStartForegroundTime > 500) { + startForeground(DOWNLOADING_NOTIFY_ID, mBuilder.build()); + mLastStartForegroundTime = now; + } + + notifyUpdate(); + } + + @Override + public void onUpdateSpeed(int gid, int speed) { + if (mCurDownloadInfo == null) + return; + + mCurDownloadInfo.speed = speed; + mSpeedStr = Utils.sizeToString(speed) + "/S"; + + ensureNotification(); + + mBuilder.setContentTitle(getString(R.string.downloading) + " " + gid) + .setContentText(mSpeedStr) + .setProgress(mCurDownloadInfo.total, mCurDownloadInfo.download, false); + startForeground(DOWNLOADING_NOTIFY_ID, mBuilder.build()); + + notifyUpdate(); + } + + private synchronized String getDownloadNotificationText() { + + int ok = mDownloadOk.size(); + int failed = mDownloadFailed.size(); + + if (ok == 0 && failed == 0) { + return "null"; + } else if (ok == 1 && failed == 0) { + return String.format(getString(R.string.download_1), mDownloadOk.get(0)); + } else if (failed == 0) { + return String.format(getString(R.string.download_2), mDownloadOk.size()); + } else if (ok == 0 && failed == 1) { + return String.format(getString(R.string.download_3), mDownloadFailed.get(0)); + } else if (ok == 0) { + return String.format(getString(R.string.download_4), mDownloadFailed.size()); + } else if (ok == 1 && failed == 1){ + return String.format(getString(R.string.download_5), mDownloadOk.get(0), mDownloadFailed.get(0)); + } else { + return String.format(getString(R.string.download_6), mDownloadOk.size(), mDownloadFailed.size()); + } + } + + @Override + public synchronized void onDownloadOver(int gid, int legacy) { + if (mCurDownloadInfo == null) + return; + + mCurDownloadInfo.legacy = legacy; + if (mCurDownloadInfo.legacy == 0) { + // Download ok + if (!mDownloadOk.contains(gid)) + mDownloadOk.add(gid); + mDownloadFailed.remove((Integer) gid); + } else { + // Download failed + if (!mDownloadFailed.contains(gid)) + mDownloadFailed.add(gid); + mDownloadOk.remove((Integer) gid); + } + + Intent clearIntent = new Intent(this, DownloadService.class); + clearIntent.setAction(ACTION_CLEAR); + PendingIntent piClear = PendingIntent.getService(this, 0, clearIntent, 0); + + NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext); + builder.setSmallIcon(android.R.drawable.stat_sys_download_done); + Intent intent = new Intent(DownloadService.this,DownloadActivity.class); + PendingIntent pendingIntent = PendingIntent.getActivity(DownloadService.this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + builder.setContentIntent(pendingIntent); + builder.setContentTitle(getString(R.string.download_over)) + .setContentText(getDownloadNotificationText()) + .setDeleteIntent(piClear) + .setOngoing(false).setAutoCancel(true); + mNotifyManager.notify(DOWNLOAD_NOTIFY_ID, builder.build()); + stopForeground(true); + + mCurDownloadInfo.legacy = legacy; + mCurDownloadInfo.state = DownloadInfo.STATE_FINISH; + mData.addDownload(mCurDownloadInfo); + mCurDownloadInfo = null; + + mCurExDownloader.setListenerDownload(null); + mCurExDownloader.setDownloadMode(false); + mEdManager.freeExDownloader(mCurExDownloader); + mCurExDownloader = null; + + tryToStartDownload(); + notifyUpdate(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/tile/RectDrawable.java b/app/src/main/java/com/hippo/ehviewer/tile/RectDrawable.java new file mode 100644 index 000000000..84f3060b8 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/tile/RectDrawable.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.tile; + +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.drawable.Drawable; +import android.util.StateSet; + +public class RectDrawable extends Drawable { + + private final int[][] mStateSets; + private final int[] mColors; + private int mColor; + private int mLastIndex = -1; + + public RectDrawable(int color) { + mStateSets = null; + mColors = null; + mColor = color; + } + + public RectDrawable(int[][] stateSets, int[] colors) { + mStateSets = stateSets; + mColors = colors; + } + + @Override + public boolean isStateful() { + return mStateSets != null; + } + + @Override + protected boolean onStateChange(int[] stateSet) { + final int N = mStateSets.length; + for (int i = 0; i < N; i++) { + if (StateSet.stateSetMatches(mStateSets[i], stateSet)) { + if (mLastIndex == i) + return false; + mLastIndex = i; + + mColor = mColors[i]; + invalidateSelf(); + return true; + } + } + return false; + } + + @Override + public void draw(Canvas canvas) { + canvas.drawColor(mColor); + } + + @Override + public void setAlpha(int alpha) { + // Empty + } + + @Override + public void setColorFilter(ColorFilter cf) { + // Empty + } + + @Override + public int getOpacity() { + // Empty + return 0; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/tile/TileSalon.java b/app/src/main/java/com/hippo/ehviewer/tile/TileSalon.java new file mode 100644 index 000000000..0a54ac948 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/tile/TileSalon.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.tile; + +import android.view.View; + +public class TileSalon { + + @SuppressWarnings("deprecation") + public static void reform(View view, int bgColor) { + view.setBackgroundDrawable(new RectDrawable(bgColor)); + } + + @SuppressWarnings("deprecation") + public static void reform(View view, int[][] stateSets, int[] bgColors) { + view.setBackgroundDrawable(new RectDrawable(stateSets, bgColors)); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/AbsActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/AbsActivity.java new file mode 100644 index 000000000..12ac4463c --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/AbsActivity.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ui; + +import android.app.Activity; + +import com.google.analytics.tracking.android.EasyTracker; +import com.hippo.ehviewer.cache.ImageCache; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.Ui; + +public abstract class AbsActivity extends Activity { + + @Override + protected void onResume() { + super.onResume(); + + Ui.adjustOrientation(this); + } + + @Override + protected void onStart() { + super.onStart(); + + if (Config.getAllowAnalyics()) + EasyTracker.getInstance(this).activityStart(this); + } + + @Override + protected void onStop() { + super.onStop(); + + if (Config.getAllowAnalyics()) + EasyTracker.getInstance(this).activityStop(this); + + ImageCache.getInstance(this).flush(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/AbsPreferenceActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/AbsPreferenceActivity.java new file mode 100644 index 000000000..89299dce6 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/AbsPreferenceActivity.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ui; + +import android.preference.PreferenceActivity; + +import com.google.analytics.tracking.android.EasyTracker; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.Ui; + +public abstract class AbsPreferenceActivity extends PreferenceActivity { + + private static final String TAG = AbsPreferenceActivity.class.getSimpleName(); + + @Override + protected void onResume() { + super.onResume(); + + Ui.adjustOrientation(this); + } + + @Override + public void onStart() { + super.onStart(); + + if (Config.getAllowAnalyics()) + EasyTracker.getInstance(this).activityStart(this); + } + + @Override + public void onStop() { + super.onStop(); + + if (Config.getAllowAnalyics()) + EasyTracker.getInstance(this).activityStop(this); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/AbsTranslucentActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/AbsTranslucentActivity.java new file mode 100644 index 000000000..60b6d0eea --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/AbsTranslucentActivity.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ui; + +import android.content.res.Configuration; + +import com.hippo.ehviewer.util.Ui; + +public class AbsTranslucentActivity extends AbsActivity { + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + Ui.updateTranslucent(this); + } + + @Override + protected void onResume() { + super.onResume(); + + Ui.updateTranslucent(this); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/DownloadActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/DownloadActivity.java new file mode 100644 index 000000000..15412b567 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/DownloadActivity.java @@ -0,0 +1,397 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ui; + +import java.util.List; + +import android.annotation.SuppressLint; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.CheckBox; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.hippo.ehviewer.AppContext; +import com.hippo.ehviewer.ImageLoader; +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.app.MaterialAlertDialog; +import com.hippo.ehviewer.cardview.CardViewSalon; +import com.hippo.ehviewer.data.Data; +import com.hippo.ehviewer.data.DownloadInfo; +import com.hippo.ehviewer.data.GalleryInfo; +import com.hippo.ehviewer.drawable.MaterialIndicatorDrawable; +import com.hippo.ehviewer.drawable.MaterialIndicatorDrawable.Stroke; +import com.hippo.ehviewer.service.DownloadService; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.EhUtils; +import com.hippo.ehviewer.util.Ui; +import com.hippo.ehviewer.util.Utils; +import com.hippo.ehviewer.util.ViewUtils; +import com.hippo.ehviewer.widget.ExpandingListView; +import com.hippo.ehviewer.widget.FitWindowView; +import com.hippo.ehviewer.widget.LoadImageView; +import com.hippo.ehviewer.windowsanimate.WindowsAnimate; + +public class DownloadActivity extends AbsTranslucentActivity implements FitWindowView.OnFitSystemWindowsListener { + + private AppContext mAppContext; + private WindowsAnimate mWindowsAnimate; + private int mThemeColor; + private List mDownloads; + + private FitWindowView mStandard; + private ExpandingListView mList; + private ListAdapter mAdapter; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver(){ + @Override + public void onReceive(Context context, Intent intent) { + if(intent.getAction().equals(DownloadService.ACTION_UPDATE)) + mAdapter.notifyDataSetChanged(); + } + }; + + @Override + public void onFitSystemWindows(int l, int t, int r, int b) { + mList.setPadding(mList.getPaddingLeft(), t, mList.getPaddingRight(), b); + + Ui.colorStatusBarKK(this, mThemeColor, t - Ui.ACTION_BAR_HEIGHT); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.download); + + mAppContext = (AppContext) getApplication(); + + // Init windows animate + mWindowsAnimate = new WindowsAnimate(); + mWindowsAnimate.init(this); + + // Receiver + IntentFilter filter = new IntentFilter(); + filter.addAction(DownloadService.ACTION_UPDATE); + registerReceiver(mReceiver, filter); + + // Menu + MaterialIndicatorDrawable materialIndicator = new MaterialIndicatorDrawable(this, Color.WHITE, Stroke.THIN); + materialIndicator.setIconState(MaterialIndicatorDrawable.IconState.ARROW); + Ui.setMaterialIndicator(getActionBar(), materialIndicator); + + mDownloads = Data.getInstance().getAllDownloads(); + for (DownloadInfo di : mDownloads) + di.setExpanded(false); + + mStandard = (FitWindowView) findViewById(R.id.standard); + mList = (ExpandingListView)findViewById(R.id.download); + + mStandard.addOnFitSystemWindowsListener(this); + mAdapter = new ListAdapter(); + mList.setAdapter(mAdapter); + mList.setSelector(new ColorDrawable(Color.TRANSPARENT)); + mList.setExpandingId(R.id.buttons); + + mThemeColor = Config.getCustomThemeColor() ? Config.getThemeColor() : Ui.THEME_COLOR; + getActionBar().setBackgroundDrawable(new ColorDrawable(mThemeColor)); + Ui.colorStatusBarL(this, mThemeColor); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + unregisterReceiver(mReceiver); + mWindowsAnimate.free(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.download, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle item selection + Intent it; + switch (item.getItemId()) { + case android.R.id.home: + finish(); + return true; + + case R.id.action_start: + it = new Intent(DownloadActivity.this, DownloadService.class); + it.setAction(DownloadService.ACTION_START_ALL); + startService(it); + return true; + + case R.id.action_stop: + it = new Intent(DownloadActivity.this, DownloadService.class); + it.setAction(DownloadService.ACTION_STOP_ALL); + startService(it); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + public class ListAdapter extends BaseAdapter { + @Override + public int getCount() { + return mDownloads.size(); + } + + @Override + public Object getItem(int position) { + return mDownloads.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + final DownloadInfo di = mDownloads.get(position); + final GalleryInfo gi = di.galleryInfo; + + if (convertView == null) { + convertView = LayoutInflater.from(DownloadActivity.this) + .inflate(R.layout.download_list_item, parent, false); + CardViewSalon.reformWithShadow(((ViewGroup)convertView).getChildAt(0), + 0xFFFAFAFA, 0, false); // TODO + ((TextView)convertView.findViewById(R.id.action)).setTextColor(mThemeColor); + } + LoadImageView thumb = (LoadImageView)convertView.findViewById(R.id.thumb); + if (!String.valueOf(gi.gid).equals(thumb.getKey())) { + // Set margin top 8dp if position is 0, otherwise 4dp + LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) + convertView.findViewById(R.id.card_view).getLayoutParams(); + if (position == 0) + lp.topMargin = Ui.dp2pix(8); + else + lp.topMargin = Ui.dp2pix(4); + + // Set new thumb + thumb.setImageDrawable(null); + thumb.setLoadInfo(gi.thumb, String.valueOf(gi.gid)); + ImageLoader.getInstance(DownloadActivity.this).add(gi.thumb, String.valueOf(gi.gid), + new LoadImageView.SimpleImageGetListener(thumb).setFixScaleType(true)); + } + TextView title = (TextView)convertView.findViewById(R.id.title); + title.setText(gi.title); + View Buttons = convertView.findViewById(R.id.buttons); + if (di.getExpanded()) + Buttons.setVisibility(View.VISIBLE); + else + Buttons.setVisibility(View.GONE); + + ProgressBar pb = (ProgressBar)convertView.findViewById(R.id.progress_bar); + TextView leftText = (TextView)convertView.findViewById(R.id.text_left); + TextView rightText = (TextView)convertView.findViewById(R.id.text_right); + + View read = convertView.findViewById(R.id.read); + TextView action = (TextView)convertView.findViewById(R.id.action); + View detail = convertView.findViewById(R.id.detail); + View delete = convertView.findViewById(R.id.delete); + + read.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(DownloadActivity.this, + GalleryActivity.class); + intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, gi); + intent.putExtra(GalleryActivity.KEY_START_INDEX, -1); + startActivity(intent); + } + }); + delete.setOnClickListener(new DeleteAction(di)); + detail.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(DownloadActivity.this, + GalleryDetailActivity.class); + intent.putExtra(GalleryDetailActivity.KEY_G_INFO, gi); + startActivity(intent); + } + }); + + switch (di.state) { + case DownloadInfo.STATE_NONE: + pb.setVisibility(View.INVISIBLE); + leftText.setText(R.string.not_started); + rightText.setVisibility(View.GONE); + + action.setText(getString(R.string.start)); + action.setOnClickListener(new StartAction(di)); + break; + + case DownloadInfo.STATE_WAIT: + pb.setVisibility(View.INVISIBLE); + leftText.setText(R.string.waiting); + rightText.setVisibility(View.GONE); + + action.setText(getString(R.string.stop)); + action.setOnClickListener(new StopAction(di)); + break; + + case DownloadInfo.STATE_DOWNLOAD: + leftText.setText(Utils.sizeToString(di.speed) + "/S"); + if (di.total == -1) { + pb.setVisibility(View.VISIBLE); + pb.setIndeterminate(true); + rightText.setVisibility(View.GONE); + } else { + pb.setVisibility(View.VISIBLE); + pb.setIndeterminate(false); + pb.setMax(di.total); + pb.setProgress(di.download); + rightText.setVisibility(View.VISIBLE); + rightText.setText(di.download + "/" + di.total); + } + + action.setText(getString(R.string.stop)); + action.setOnClickListener(new StopAction(di)); + break; + + case DownloadInfo.STATE_FINISH: + pb.setVisibility(View.INVISIBLE); + if (di.legacy == 0) + leftText.setText(R.string.done); + else + leftText.setText(String.format(getApplication().getString(R.string.legacy_pages), di.legacy)); + rightText.setVisibility(View.GONE); + + action.setText(getString(R.string.start)); + action.setOnClickListener(new StartAction(di)); + break; + } + return convertView; + } + + @Override + public void notifyDataSetChanged() { + if (!mList.isAnimating()) + super.notifyDataSetChanged(); + } + + @Override + public void notifyDataSetInvalidated() { + if (!mList.isAnimating()) + super.notifyDataSetInvalidated(); + } + } + + private class DeleteAction implements View.OnClickListener { + + private static final String KEY_INCLUDE_PIC = "include_pic"; + + public DownloadInfo mDownloadInfo; + + public DeleteAction(DownloadInfo di) { + mDownloadInfo = di; + } + + @Override + @SuppressLint("InflateParams") + public void onClick(View v) { + final View view = ViewUtils.inflateDialogView(R.layout.dialog_message_checkbox, false); + final TextView message = (TextView) view.findViewById(R.id.message); + final CheckBox cb = (CheckBox) view.findViewById(R.id.checkbox); + message.setText(R.string.delete_item); + cb.setText(R.string.pic_included); + cb.setChecked(Config.getBoolean(KEY_INCLUDE_PIC, false)); + + new MaterialAlertDialog.Builder(DownloadActivity.this).setTitle(R.string.attention) + .setView(view, true) + .setDefaultButton(MaterialAlertDialog.POSITIVE | MaterialAlertDialog.NEGATIVE) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + if (which == MaterialAlertDialog.POSITIVE) { + Intent it = new Intent(DownloadActivity.this, DownloadService.class); + it.setAction(DownloadService.ACTION_DELETE); + it.putExtra(DownloadService.KEY_GID, mDownloadInfo.galleryInfo.gid); + startService(it); + // Delete dir + if (cb.isChecked()) { + GalleryInfo gi = mDownloadInfo.galleryInfo; + Utils.deleteDirInThread(EhUtils.getGalleryDir(gi.gid, gi.title)); + } + // Remember delete choice + Config.setBoolean(KEY_INCLUDE_PIC, cb.isChecked()); + } + return true; + } + }).show(); + } + } + + private class StartAction implements View.OnClickListener { + public DownloadInfo mDownloadInfo; + + public StartAction(DownloadInfo di) { + mDownloadInfo = di; + } + + @Override + public void onClick(View v) { + if (mDownloadInfo.state == DownloadInfo.STATE_NONE || + mDownloadInfo.state == DownloadInfo.STATE_FINISH) { + + Intent it = new Intent(DownloadActivity.this, DownloadService.class); + it.setAction(DownloadService.ACTION_START); + it.putExtra(DownloadService.KEY_GID, mDownloadInfo.galleryInfo.gid); + startService(it); + mAdapter.notifyDataSetChanged(); + } + } + } + + + private class StopAction implements View.OnClickListener { + + public DownloadInfo mDownloadInfo; + + public StopAction(DownloadInfo di) { + mDownloadInfo = di; + } + + @Override + public void onClick(View v) { + Intent it = new Intent(DownloadActivity.this, DownloadService.class); + it.setAction(DownloadService.ACTION_STOP); + it.putExtra(DownloadService.KEY_GID, mDownloadInfo.galleryInfo.gid); + startService(it); + mAdapter.notifyDataSetChanged(); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/FavouriteActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/FavouriteActivity.java new file mode 100644 index 000000000..e3a3b71ca --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/FavouriteActivity.java @@ -0,0 +1,594 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ui; + +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import android.annotation.SuppressLint; +import android.app.ActionBar; +import android.app.AlertDialog; +import android.content.Intent; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.Path; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.PathShape; +import android.os.Bundle; +import android.view.ActionMode; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.FrameLayout; +import android.widget.ListView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.app.MaterialAlertDialog; +import com.hippo.ehviewer.app.MaterialProgressDialog; +import com.hippo.ehviewer.data.Data; +import com.hippo.ehviewer.data.GalleryInfo; +import com.hippo.ehviewer.drawable.MaterialIndicatorDrawable; +import com.hippo.ehviewer.drawable.MaterialIndicatorDrawable.Stroke; +import com.hippo.ehviewer.ehclient.EhClient; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.Favorite; +import com.hippo.ehviewer.util.Ui; +import com.hippo.ehviewer.widget.ActionableToastBar; +import com.hippo.ehviewer.widget.ActionableToastBar.ActionClickedListener; +import com.hippo.ehviewer.widget.FitWindowView; +import com.hippo.ehviewer.widget.GalleryListView; +import com.hippo.ehviewer.widget.GalleryListView.OnGetListListener; +import com.hippo.ehviewer.widget.recyclerview.EasyRecyclerView; +import com.hippo.ehviewer.widget.MaterialToast; +import com.hippo.ehviewer.widget.SlidingDrawerLayout; + +public class FavouriteActivity extends AbsTranslucentActivity + implements EasyRecyclerView.MultiChoiceModeListener, + View.OnTouchListener, GalleryListView.GalleryListViewHelper, + FitWindowView.OnFitSystemWindowsListener, EasyRecyclerView.OnItemClickListener { + + private static final String TAG = FavouriteActivity.class.getSimpleName(); + + public static final int LIST_MODE_DETAIL = 0; + public static final int LIST_MODE_THUMB = 1; + + private static final String LOCAL_FAVORITE_URL = "ehviewer://local_favorite"; + + private Data mData; + private Resources mResources; + private EhClient mClient; + private ActionBar mActionBar; + private int mThemeColor; + + private SlidingDrawerLayout mDrawerLayout; + private ListView mMenu; + private FrameLayout mContentView; + private FitWindowView mStandard; + private GalleryListView mGalleryListView; + + private ActionableToastBar mActionableToastBar; + + private Set mChoiceGiSet; + private Set mChoiceGiSetCopy; // Store selected GalleryInfo + private Iterator mSetIter; // For move from local to cloud + private GalleryInfo mTargetGi; + private int mMenuIndex; + private int mTargetCat; + + private AlertDialog mMoveDialog; + private MaterialProgressDialog mProgressDialog; + + private List mLastModifyGiList = null; + private int mLastModifyPageNum = 0; + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + } + + @Override + public void onBackPressed() { + if (mActionableToastBar.isShown()) + mActionableToastBar.hide(true); + else if (mDrawerLayout.isDrawerOpen(mMenu)) + mDrawerLayout.closeDrawer(mMenu); + else + super.onBackPressed(); + } + + private void initLocalFavorite() { + mGalleryListView.setEnabledHeader(false); + mGalleryListView.setEnabledFooter(false); + setTitle(Favorite.FAVORITE_TITLES[mMenuIndex]); + mGalleryListView.refresh(); + } + + private void initFavorite() { + mGalleryListView.setEnabledHeader(true); + mGalleryListView.setEnabledFooter(true); + setTitle(Favorite.FAVORITE_TITLES[mMenuIndex]); + mGalleryListView.refresh(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.favorite); + + mData = Data.getInstance(); + mResources =getResources(); + mClient = EhClient.getInstance(); + + mActionBar = getActionBar(); + mActionBar.setDisplayHomeAsUpEnabled(true); + + // Menu + MaterialIndicatorDrawable materialIndicator = new MaterialIndicatorDrawable(this, Color.WHITE, Stroke.THIN); + materialIndicator.setIconState(MaterialIndicatorDrawable.IconState.ARROW); + Ui.setMaterialIndicator(getActionBar(), materialIndicator); + + // Get View + mDrawerLayout = (SlidingDrawerLayout) findViewById(R.id.drawerlayout); + mMenu = (ListView) mDrawerLayout.findViewById(R.id.favorite_menu_list); + mContentView = (FrameLayout) mDrawerLayout.findViewById(R.id.content); + mStandard = (FitWindowView) mDrawerLayout.findViewById(R.id.standard); + mGalleryListView = (GalleryListView) mDrawerLayout.findViewById(R.id.gallery_list); + + mActionableToastBar = new ActionableToastBar(this); + mActionableToastBar.setBackgroundColor(mResources.getColor(android.R.color.holo_purple)); + mContentView.addView(mActionableToastBar); + mContentView.setOnTouchListener(this); + + mGalleryListView.setGalleryListViewHelper(this); + mStandard.addOnFitSystemWindowsListener(this); + //mPullViewGroup.setAgainstToChildPadding(true); + + mChoiceGiSet = new LinkedHashSet(); + mChoiceGiSetCopy = new LinkedHashSet(); + + //mList.setOnTouchListener(this); + mGalleryListView.setOnItemClickListener(this); + mGalleryListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); + mGalleryListView.setMultiChoiceModeListener(this); + + mMenu.setClipToPadding(false); + mMenu.setAdapter(new BaseAdapter() { + private ShapeDrawable d; + private ShapeDrawable createDrawable() { + Path path = new Path(); + path.moveTo(50, 10); + path.lineTo(10, 50); + path.lineTo(50, 90); + path.lineTo(90, 50); + path.close(); + ShapeDrawable d = new ShapeDrawable(new PathShape(path, 100, 100)); + d.getPaint().setColor(0xcdffffff); + d.setBounds(0, 0, Ui.dp2pix(36), Ui.dp2pix(36)); + return d; + } + @Override + public int getCount() { + return EhClient.FAVORITE_SLOT_NUM + 1; + } + @Override + public Object getItem(int position) { + return null; + } + @Override + public long getItemId(int position) { + return position; + } + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) + convertView = LayoutInflater.from(FavouriteActivity.this).inflate(R.layout.menu_item, parent, false); + TextView tv = (TextView)convertView; + tv.setText(Favorite.FAVORITE_TITLES[position]); + if (position == 0) { + Drawable dr = mResources.getDrawable(R.drawable.ic_setting_panda_light); + dr.setBounds(0, 0, Ui.dp2pix(36), Ui.dp2pix(36)); + tv.setCompoundDrawables(dr, null, null, null); + tv.setCompoundDrawablePadding(Ui.dp2pix(8)); + } else { + if (d == null) + d = createDrawable(); + tv.setCompoundDrawables(d, null, null, null); + tv.setCompoundDrawablePadding(Ui.dp2pix(8)); + } + return convertView; + } + }); + mMenu.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, + int position, long id) { + mDrawerLayout.closeDrawers(); + // If same index, do nothing + if (mMenuIndex == position) + return; + + mMenuIndex = position; + setTitle(Favorite.FAVORITE_TITLES[mMenuIndex]); + if (mMenuIndex == 0) + initLocalFavorite(); + else + initFavorite(); + } + }); + + // Set random color + mThemeColor = Config.getCustomThemeColor() ? Config.getThemeColor() : Ui.THEME_COLOR; + mActionBar.setBackgroundDrawable(new ColorDrawable(mThemeColor)); + mMenu.setBackgroundColor(mThemeColor); + Ui.colorStatusBarL(this, mThemeColor); + + // Check login + if (!mClient.isLogin()) { + MaterialToast.showToast(R.string.favorite_warning); + } + + // TODO Should show default favourite + mMenuIndex = 0; + initLocalFavorite(); + } + + @Override + public void onFitSystemWindows(int l, int t, int r, int b) { + int magicSpacing = Ui.dp2pix(20); + FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( + RelativeLayout.LayoutParams.MATCH_PARENT, Ui.dp2pix(60)); + lp.gravity = Gravity.BOTTOM; + // Make sure actionable is above navigation bar + lp.bottomMargin = b + magicSpacing; + lp.leftMargin = magicSpacing; + lp.rightMargin = magicSpacing; + mActionableToastBar.setLayoutParams(lp); + mGalleryListView.setPaddingTopAndBottom(t, b); + ((SlidingDrawerLayout.LayoutParams) mMenu.getLayoutParams()).topMargin = t; + mMenu.setPadding(mMenu.getPaddingLeft(), mMenu.getPaddingTop(), mMenu.getPaddingRight(), b); + + Ui.colorStatusBarKK(this, mThemeColor, t - Ui.ACTION_BAR_HEIGHT); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.favorite, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle item selection + switch (item.getItemId()) { + case android.R.id.home: + finish(); + return true; + case R.id.action_list: + if (mDrawerLayout.isDrawerOpen(mMenu)) + mDrawerLayout.closeDrawer(mMenu); + else + mDrawerLayout.openDrawer(mMenu); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + } + + private void showToastBar(final ActionClickedListener listener, int descriptionIconResourceId, + CharSequence descriptionText, int actionIconResource, CharSequence actionText, + boolean replaceVisibleToast) { + mActionableToastBar.show(listener, descriptionIconResourceId, + descriptionText, actionIconResource, actionText, replaceVisibleToast); + } + + private void hideToastBar(MotionEvent event) { + if (mActionableToastBar != null) { + if (event != null && mActionableToastBar.isEventInToastBar(event)) { + // Avoid touches inside the undo bar. + return; + } + mActionableToastBar.hide(false); + } + } + + @Override + public boolean onItemClick(EasyRecyclerView parent, View view, + int position, long id) { + Intent intent = new Intent(this, GalleryDetailActivity.class); + GalleryInfo gi = mGalleryListView.getGalleryInfo(position); + intent.putExtra(GalleryDetailActivity.KEY_G_INFO, gi); + startActivity(intent); + return true; + } + + @Override + @SuppressLint("ClickableViewAccessibility") + public boolean onTouch(View v, MotionEvent event) { + hideToastBar(event); + return super.onTouchEvent(event); + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.favorite_choice, menu); + mode.setTitle(R.string.select_item); + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return true; + } + + private int[] getGids(Set giSet) { + int[] gids = new int[giSet.size()]; + int i = 0; + for (GalleryInfo gi : giSet) + gids[i++] = gi.gid; + return gids; + } + + private void startMoveFromLocal2Cloud() { + mProgressDialog = MaterialProgressDialog.create(this, getString(R.string.moving), false); + mProgressDialog.setMax(mChoiceGiSetCopy.size()); + mProgressDialog.setProgress(0); + mProgressDialog.show(); + moveFromLocal2Cloud(); + } + + private void moveFromLocal2Cloud() { + if (mSetIter == null) + mSetIter = mChoiceGiSetCopy.iterator(); + + if (mSetIter.hasNext() || mTargetGi != null) { + if (mTargetGi == null) + mTargetGi = mSetIter.next(); + mClient.addToFavorite(mTargetGi.gid, mTargetGi.token, + mTargetCat, null, new EhClient.OnAddToFavoriteListener() { + @Override + public void onSuccess() { + // remove from set iterator, favorite data + mSetIter.remove(); + mData.deleteLocalFavourite(mTargetGi.gid); + mGalleryListView.getGalleryList().remove(mTargetGi); + mGalleryListView.notifyDataSetChanged(); + // set mGiLocal2Cloud null for above + mTargetGi = null; + mProgressDialog.setProgress(mProgressDialog.getProgress() + 1); + moveFromLocal2Cloud(); + } + @Override + public void onFailure(String eMsg) { + mGalleryListView.notifyDataSetChanged(); + mProgressDialog.dismiss(); + mProgressDialog = null; + showToastBar(new ActionClickedListener() { + @Override + public void onActionClicked() { + startMoveFromLocal2Cloud(); + } + }, 0, mResources.getString(R.string.failed_to_move), + R.drawable.ic_warning, mResources.getString(R.string.retry), true); + } + }); + } else { + if (mGalleryListView.getGalleryList().size() == 0) + mGalleryListView.onlyShowNone(); + mSetIter = null; + mProgressDialog.dismiss(); + mProgressDialog = null; + MaterialToast.showToast(R.string.move_successful); + } + } + + private AlertDialog createMoveDialog(final ActionMode mode) { + return new MaterialAlertDialog.Builder(this).setTitle(R.string.where_to_move) + .setItems(Favorite.FAVORITE_TITLES, new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int position) { + if (mMenuIndex == position) { + MaterialToast.showToast(R.string.dst_src_same); + return true; + } + + mTargetCat = position - 1; + if (mMenuIndex == 0) { // From local favorite to cloud + mMoveDialog.dismiss(); + mMoveDialog = null; + + startMoveFromLocal2Cloud(); + } else { + if (position == 0) { // From cloud to local favorite + mMoveDialog.dismiss(); + mMoveDialog = null; + + mClient.modifyFavorite(getGids(mChoiceGiSetCopy), mTargetCat, + mMenuIndex -1, new Modify(mResources.getString(R.string.move_successful), + mResources.getString(R.string.failed_to_move), true)); + mGalleryListView.setHeaderRefreshing(true); + } else { // change cloud dir + mMoveDialog.dismiss(); + mMoveDialog = null; + + mClient.modifyFavorite(getGids(mChoiceGiSetCopy), mTargetCat, + mMenuIndex -1, new Modify(mResources.getString(R.string.move_successful), + mResources.getString(R.string.failed_to_move), false)); + mGalleryListView.setHeaderRefreshing(true); + } + } + return true; + } + }).setNegativeButton(android.R.string.cancel).create(); + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + // get a copy of mChoiceGiSet + mSetIter = null; + mChoiceGiSetCopy.clear(); + for (GalleryInfo gi : mChoiceGiSet) + mChoiceGiSetCopy.add(gi); + + switch (item.getItemId()) { + case R.id.action_delete: + if (mMenuIndex == 0) { + for (GalleryInfo gi : mChoiceGiSet) + mData.deleteLocalFavourite(gi.gid); + mGalleryListView.refresh(); + } else { + mTargetCat = -1; + mClient.modifyFavorite(getGids(mChoiceGiSet), mTargetCat, + mMenuIndex -1, new Modify(mResources.getString(R.string.delete_successfully), + mResources.getString(R.string.failed_to_delete), false)); + mGalleryListView.setHeaderRefreshing(true); + } + mode.finish(); + return true; + case R.id.action_move: + mMoveDialog = createMoveDialog(mode); + mMoveDialog.show(); + mode.finish(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + mChoiceGiSet.clear(); + } + + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, + long id, boolean checked) { + if (checked) + mChoiceGiSet.add(mGalleryListView.getGalleryInfo(position)); + else + mChoiceGiSet.remove(mGalleryListView.getGalleryInfo(position)); + } + + @Override + public String getTargetUrl(int targetPage) { + if (mMenuIndex == 0) + return LOCAL_FAVORITE_URL; + else + return mClient.getFavoriteUrlWithCat(mMenuIndex - 1, targetPage); + } + + @Override + public void doGetGallerys(String url, final long taskStamp, + final OnGetListListener listener) { + // If get local favorite + if (mLastModifyGiList != null) { + listener.onSuccess(taskStamp, mLastModifyGiList, mLastModifyPageNum); + mLastModifyGiList = null; + mLastModifyPageNum = 0; + } else if (LOCAL_FAVORITE_URL.equals(url)) { + List giList = mData.getAllLocalFavourites(); + if (giList == null || giList.size() == 0) + listener.onSuccess(taskStamp, giList, 0); + else + listener.onSuccess(taskStamp, giList, 1); + } else { // If get cloud favorite + mClient.getGList(url, Config.getApiMode(), null, new EhClient.OnGetGListListener() { + @Override + public void onSuccess(Object checkFlag, List giList, + int maxPage) { + listener.onSuccess(taskStamp, giList, maxPage); + } + @Override + public void onFailure(Object checkFlag, String eMsg) { + listener.onFailure(taskStamp, eMsg); + } + }); + } + } + + private class Modify implements EhClient.OnModifyFavoriteListener { + private final String mSuccStr; + private final String mFailStr; + private final boolean mToLocal; + + public Modify(String succStr, String failStr, boolean toLocal) { + mSuccStr = succStr; + mFailStr = failStr; + mToLocal = toLocal; + } + + @Override + public void onSuccess(List gis, int pageNum) { + mLastModifyGiList = gis; + mLastModifyPageNum = pageNum; + mGalleryListView.refresh(); + MaterialToast.showToast(mSuccStr); + + // add to local + if (mToLocal) + for (GalleryInfo gi : mChoiceGiSetCopy) + mData.addLocalFavourite(gi); + } + @Override + public void onFailure(String eMsg) { + mGalleryListView.setHeaderRefreshing(false); + showToastBar(new Remodify(mSuccStr, mFailStr, mToLocal), 0, mFailStr + ": " + eMsg, + R.drawable.ic_action_redo, mResources.getString(R.string.retry), true); + } + } + + /** + * Redelete when delete error + * @author Hippo + * + */ + private class Remodify implements ActionableToastBar.ActionClickedListener { + private final String mSuccStr; + private final String mFailStr; + private final boolean mToLocal; + + public Remodify(String succStr, String failStr, boolean toLocal) { + mSuccStr = succStr; + mFailStr = failStr; + mToLocal = toLocal; + } + + @Override + public void onActionClicked() { + mClient.modifyFavorite(getGids(mChoiceGiSetCopy), -1, mMenuIndex -1, new Modify(mSuccStr, mFailStr, mToLocal)); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java new file mode 100644 index 000000000..5d2eaf3b1 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java @@ -0,0 +1,551 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ui; + +import java.io.File; + +import android.annotation.TargetApi; +import android.app.Dialog; +import android.content.Intent; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.AdapterView; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.SeekBar; +import android.widget.Spinner; +import android.widget.TextView; + +import com.hippo.ehviewer.AppHandler; +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.app.DirSelectDialog; +import com.hippo.ehviewer.app.MaterialAlertDialog; +import com.hippo.ehviewer.data.Data; +import com.hippo.ehviewer.data.GalleryInfo; +import com.hippo.ehviewer.ehclient.ExDownloader; +import com.hippo.ehviewer.gallery.GalleryView; +import com.hippo.ehviewer.gallery.data.ImageSet; +import com.hippo.ehviewer.gallery.ui.GLRootView; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.FullScreenHelper; +import com.hippo.ehviewer.util.MathUtils; +import com.hippo.ehviewer.util.Utils; +import com.hippo.ehviewer.widget.ColorView; +import com.hippo.ehviewer.widget.MaterialToast; +import com.hippo.ehviewer.widget.SlidingLayout; + +public class GalleryActivity extends AbsActivity + implements GalleryView.GalleryViewListener, SeekBar.OnSeekBarChangeListener, + FullScreenHelper.OnFullScreenBrokenListener, CompoundButton.OnCheckedChangeListener, + SlidingLayout.OnChildHideListener, AdapterView.OnItemSelectedListener, + View.OnClickListener, Utils.OnCopyOverListener { + + @SuppressWarnings("unused") + private final static String TAG = GalleryActivity.class.getSimpleName(); + + public final static String KEY_GALLERY_INFO = "gallery_info"; + public final static String KEY_START_INDEX = "start_index"; + + public final static String KEY_LIGHTNESS = "lightness"; + public final static float DEFAULT_LIGHTNESS = 0.5f; + + public final static String KEY_FIRST_READ_CONFIG_V1 = "first_read_config_v1"; + public final static boolean DEFAULT_FIRST_READ_CONFIG_V1 = true; + + public final static String KEY_LAST_SAVE_IMAGE_FILE_PATH = "last_save_image_file_path"; + public final static String DEFAULT_LAST_SAVE_IMAGE_FILE_PATH = null; + + private View mClock; + private View mBattery; + + private SlidingLayout mConfigSliding; + /** It store index, start from 0, so max is samller than size **/ + private TextView mCurrentPage; + private TextView mPageSum; + private SeekBar mPageSeeker; + private TextView mRefresh; + private TextView mSend; + private TextView mMoreSettings; + private Spinner mReadingDirection; + private Spinner mPageScaling; + private Spinner mStartPosition; + private CheckBox mKeepScreenOn; + private CheckBox mShowClock; + private CheckBox mShowBattery; + private CheckBox mCustomLightness; + private SeekBar mCustomLightnessValue; + private CheckBox mCustomCodec; + private Spinner mDecodeFormat; + + private ImageSet mImageSet; + + private GalleryView mGalleryView; + + private ColorView mMaskView; + + private GalleryInfo mGi; + + private FullScreenHelper mFullScreenHelper; + + private File mCurImageFile; + + private Dialog mSendDialog; + + private final Runnable mFullScreenTask = new Runnable() { + @Override public void run() { + mFullScreenHelper.setFullScreen(true); + } + }; + + private void startFullScreenTask() { + AppHandler.getInstance().postDelayed(mFullScreenTask, 2000); + } + + private void cancelFullScreenTask() { + AppHandler.getInstance().removeCallbacks(mFullScreenTask); + } + + @Override + public void onCopyOver(boolean success, File src, File dst) { + MaterialToast.showToast(success ? String.format(getString(R.string.save_successful), dst.getPath()) + : getString(R.string.save_failed)); + if (success) + Config.setString(KEY_LAST_SAVE_IMAGE_FILE_PATH, dst.getParent()); + } + + private Dialog createSaveImageDialog() { + return DirSelectDialog.create( + new MaterialAlertDialog.Builder(GalleryActivity.this) + .setDrakTheme(true).setTitle(R.string.select_save_dir) + .setDefaultButton(MaterialAlertDialog.POSITIVE | MaterialAlertDialog.NEGATIVE) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + if (which == MaterialAlertDialog.POSITIVE) { + String targetPath = ((DirSelectDialog) dialog).getCurrentPath(); + StringBuilder sb = new StringBuilder(); + sb.append(mImageSet.getTitle()).append("-").append(mGalleryView.getCurIndex() + 1) + .append(".").append(Utils.getExtension(mCurImageFile.getName(), "jpg")); + Utils.copyInNewThread(mCurImageFile, new File(targetPath, Utils.standardizeFilename(sb.toString())), + GalleryActivity.this); + } + return true; + } + }), + Config.getString(KEY_LAST_SAVE_IMAGE_FILE_PATH, null)); + } + + private Dialog createSendDialog() { + return new MaterialAlertDialog.Builder(this).setDrakTheme(true).setTitle(R.string.send) + .setItems(R.array.send_entries, new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + // Check current image file again + if (mCurImageFile == null || !mCurImageFile.exists()) { + MaterialToast.showToast(R.string.current_image_not_downloaded); + return false; + } + + switch (which) { + case 0: + createSaveImageDialog().show(); + break; + case 1: + Uri uri = Uri.fromFile(mCurImageFile); + Intent shareIntent = new Intent(); + shareIntent.setAction(Intent.ACTION_SEND); + shareIntent.putExtra(Intent.EXTRA_STREAM, uri); + shareIntent.setType(Utils.getMimeType(uri.toString())); + startActivity(Intent.createChooser(shareIntent, getString(R.string.share_image))); + break; + } + return true; + } + }).setNegativeButton(android.R.string.cancel).create(); + } + + + @Override + public void onFullScreenBroken(boolean fullScreen) { + // TODO For some MIUI system, onSystemUiVisibilityChange() do not work fine + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + } + + @Override + protected void onResume() { + super.onResume(); + + updateState(); + mFullScreenHelper.setFullScreen(!mConfigSliding.isShowing()); + } + + private void setScreenLightness(boolean enable, int lightness) { + Window w = getWindow(); + WindowManager.LayoutParams lp = w.getAttributes(); + if (enable) { + lightness = MathUtils.clamp(lightness, 0, 200); + if (lightness > 100) { + mMaskView.setColor(0); + // Avoid BRIGHTNESS_OVERRIDE_OFF, + // screen may be off when lp.screenBrightness is 0.0f + lp.screenBrightness = Math.max((lightness - 100) / 100.0f, 0.05f); + } else { + mMaskView.setColor(MathUtils.lerp(0xde, 0x00, lightness / 100.0f) << 24); + lp.screenBrightness = 0.05f; + } + } else { + mMaskView.setColor(0); + lp.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE; + } + w.setAttributes(lp); + } + + private void updateState() { + mReadingDirection.setSelection(Config.getReadingDirection()); + mPageScaling.setSelection(Config.getPageScaling()); + mStartPosition.setSelection(Config.getStartPosition()); + mGalleryView.setReadingDirection(Config.getReadingDirection()); + mGalleryView.setPageScaling(Config.getPageScaling()); + mGalleryView.setStartPosition(Config.getStartPosition()); + + mKeepScreenOn.setChecked(Config.getKeepSreenOn()); + if (Config.getKeepSreenOn()) + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + else + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + mShowClock.setChecked(Config.getShowClock()); + mShowBattery.setChecked(Config.getShowBattery()); + mClock.setVisibility(Config.getShowClock() ? View.VISIBLE : View.GONE); + mBattery.setVisibility(Config.getShowBattery() ? View.VISIBLE : View.GONE); + + mCustomLightness.setChecked(Config.getCustomLightness()); + if (mCustomLightness.isChecked()) + mCustomLightnessValue.setEnabled(true); + else + mCustomLightnessValue.setEnabled(false); + mCustomLightnessValue.setProgress(Config.getCustomLightnessValue()); + setScreenLightness(mCustomLightness.isChecked(), mCustomLightnessValue.getProgress()); + + if (Utils.SUPPORT_IMAGE) + mCustomCodec.setEnabled(true); + else + mCustomCodec.setEnabled(false); + mCustomCodec.setChecked(Config.getCustomCodec()); + if (Utils.SUPPORT_IMAGE && mCustomCodec.isChecked()) + mDecodeFormat.setEnabled(true); + else + mDecodeFormat.setEnabled(false); + mDecodeFormat.setSelection(Config.getDecodeFormatIndex()); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.gl_root_group); + + mFullScreenHelper = new FullScreenHelper(this); + mFullScreenHelper.setOnFullScreenBrokenListener(this); + mFullScreenHelper.setFullScreen(true); + + mMaskView = (ColorView)findViewById(R.id.mask); + mClock = findViewById(R.id.clock); + mBattery = findViewById(R.id.battery); + + mConfigSliding = (SlidingLayout) findViewById(R.id.config_sliding); + mCurrentPage = (TextView) mConfigSliding.findViewById(R.id.current_page); + mPageSum = (TextView) mConfigSliding.findViewById(R.id.page_sum); + mPageSeeker = (SeekBar) mConfigSliding.findViewById(R.id.page_seeker); + mRefresh = (TextView) mConfigSliding.findViewById(R.id.refresh); + mSend = (TextView) mConfigSliding.findViewById(R.id.send); + mMoreSettings = (TextView) mConfigSliding.findViewById(R.id.more_settings); + mReadingDirection = (Spinner) mConfigSliding.findViewById(R.id.reading_direction); + mPageScaling = (Spinner) mConfigSliding.findViewById(R.id.page_scaling); + mStartPosition = (Spinner) mConfigSliding.findViewById(R.id.start_position); + mKeepScreenOn = (CheckBox) mConfigSliding.findViewById(R.id.keep_screen_on); + mShowClock = (CheckBox) mConfigSliding.findViewById(R.id.gallery_show_clock); + mShowBattery = (CheckBox) mConfigSliding.findViewById(R.id.gallery_show_battery); + mCustomLightness = (CheckBox) mConfigSliding.findViewById(R.id.custom_lightness); + mCustomLightnessValue = (SeekBar) mConfigSliding.findViewById(R.id.custom_lightness_value); + mCustomCodec = (CheckBox) mConfigSliding.findViewById(R.id.custom_codec); + mDecodeFormat = (Spinner) mConfigSliding.findViewById(R.id.decode_format); + + mConfigSliding.hide(); + + mSendDialog = createSendDialog(); + + Intent intent = getIntent(); + mGi = (GalleryInfo) intent.getParcelableExtra(KEY_GALLERY_INFO); + + if (mGi == null) { + // Get error force finish + new MaterialAlertDialog.Builder(this).setTitle(R.string.error) + .setMessage(R.string.ga_data_error) + .setPositiveButton(R.string.close) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + finish(); + return true; + } + }).show(); + } else { + + Data.getInstance().addHistory(mGi, Data.READ); + + int startIndex = intent.getIntExtra(KEY_START_INDEX, 0); + if (startIndex == -1) + startIndex = ExDownloader.readCurReadIndex(mGi.gid, mGi.title); + + mImageSet = new ImageSet(mGi.gid, mGi.token, mGi.title, startIndex); + mGalleryView = new GalleryView(getApplicationContext(), mImageSet, startIndex); + + GLRootView glrv= (GLRootView)findViewById(R.id.gl_root_view); + glrv.setContentPane(mGalleryView); + + // Set listener + mConfigSliding.setOnChildHideListener(this); + mGalleryView.setGalleryViewListener(this); + mPageSeeker.setOnSeekBarChangeListener(this); + mRefresh.setOnClickListener(this); + mSend.setOnClickListener(this); + mMoreSettings.setOnClickListener(this); + mReadingDirection.setOnItemSelectedListener(this); + mPageScaling.setOnItemSelectedListener(this); + mStartPosition.setOnItemSelectedListener(this); + mDecodeFormat.setOnItemSelectedListener(this); + mKeepScreenOn.setOnCheckedChangeListener(this); + mShowClock.setOnCheckedChangeListener(this); + mShowBattery.setOnCheckedChangeListener(this); + mCustomLightness.setOnCheckedChangeListener(this); + mCustomLightnessValue.setOnSeekBarChangeListener(this); + mCustomCodec.setOnCheckedChangeListener(this); + + mKeepScreenOn = (CheckBox) mConfigSliding.findViewById(R.id.keep_screen_on); + mShowClock = (CheckBox) mConfigSliding.findViewById(R.id.gallery_show_clock); + mShowBattery = (CheckBox) mConfigSliding.findViewById(R.id.gallery_show_battery); + mCustomCodec = (CheckBox) mConfigSliding.findViewById(R.id.custom_codec); + + mPageSeeker.setMax(mGalleryView.getSize() - 1); + mPageSeeker.setProgress(startIndex); + + mCurrentPage.setText(Integer.toString(startIndex + 1)); + mPageSum.setText(Integer.toString(mGalleryView.getSize())); + + onRightToLeftChanged(mGalleryView.isRightToLeft()); + + // Show first tip + String keyGalleryFirst = "gallery_first"; + if (Config.getBoolean(keyGalleryFirst, true)) { + Config.setBoolean(keyGalleryFirst, false); + new MaterialAlertDialog.Builder(this).setDrakTheme(true).setTitle(R.string.tip) + .setMessage(R.string.gallery_tip) + .setNegativeButton(android.R.string.cancel) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + mFullScreenHelper.setFullScreen(true); + return true; + } + }).show(); + } + + // Update state + updateState(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + if (mImageSet != null) + mImageSet.free(); + if (mGalleryView != null) + mGalleryView.free(); + } + + @Override + public void onBackPressed() { + if (mConfigSliding.isInHideAnimate()) + return; + + if (mConfigSliding.isShowing()) + mConfigSliding.toBaseLevel(); + else + finish(); + } + + @Override + public void onClick(View v) { + if (v == mRefresh) { + mImageSet.redownload(mGalleryView.getCurIndex()); + } else if (v == mSend) { + mCurImageFile = mImageSet.getImageFile(mGalleryView.getCurIndex()); + if (mCurImageFile == null || !mCurImageFile.exists()) + MaterialToast.showToast(R.string.current_image_not_downloaded); + else + mSendDialog.show(); + + } else if (v == mMoreSettings) { + Intent intent = new Intent(GalleryActivity.this, SettingsActivity.class); + startActivity(intent); + } + } + + @Override + public void onTapCenter() { + if (!mConfigSliding.isShowing()) { + if (mFullScreenHelper.willHideNavBar()) + cancelFullScreenTask(); + mFullScreenHelper.setFullScreen(false); + + boolean firstTime = Config.getBoolean(KEY_FIRST_READ_CONFIG_V1, DEFAULT_FIRST_READ_CONFIG_V1); + mConfigSliding.toReservedLevel(firstTime); + if (firstTime) + Config.setBoolean(KEY_FIRST_READ_CONFIG_V1, false); + } + } + + @Override + public void onPageChanged(int index) { + mPageSeeker.setProgress(index); + mCurrentPage.setText(Integer.toString(index + 1)); + } + + @Override + public void onSizeUpdate(int size) { + mPageSeeker.setMax(size - 1); + mPageSeeker.setProgress(mGalleryView.getCurIndex() + 1); + mCurrentPage.setText(Integer.toString(mGalleryView.getCurIndex() + 1)); + mPageSum.setText(Integer.toString(size)); + } + + @Override + public void onSlideBottom(float dx) { + // Empty + } + + @Override + public void onSlideBottomOver() { + // Empty + } + + @Override + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + public void onRightToLeftChanged(boolean value) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + int nevLd = value ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR; + if (mPageSeeker.getLayoutDirection() != nevLd) { + mPageSeeker.setLayoutDirection(nevLd); + } + } + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, + boolean fromUser) { + if (seekBar == mPageSeeker) { + mCurrentPage.setText(Integer.toString(progress + 1)); + } else if (seekBar == mCustomLightnessValue) { + setScreenLightness(mCustomLightness.isChecked(), progress); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Empty + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + if (seekBar == mPageSeeker) { + mGalleryView.goToPage(seekBar.getProgress()); + } else if (seekBar == mCustomLightnessValue) { + Config.setCustomLightnessValue(seekBar.getProgress()); + } + } + + @Override + public void onChildHide() { + if (mFullScreenHelper.willHideNavBar()) + startFullScreenTask(); + else + mFullScreenHelper.setFullScreen(true); + } + + @Override + public void onItemSelected(AdapterView parent, View view, int position, + long id) { + if (parent == mReadingDirection) { + Config.setReadingDirection(mReadingDirection.getSelectedItemPosition()); + mGalleryView.setReadingDirection(position); + } else if (parent == mPageScaling) { + Config.setPageScaling(mPageScaling.getSelectedItemPosition()); + mGalleryView.setPageScaling(position); + } else if (parent == mStartPosition) { + Config.setStartPosition(mStartPosition.getSelectedItemPosition()); + mGalleryView.setStartPosition(position); + } else if (parent == mDecodeFormat) { + Config.setDecodeFormatFromIndex(mDecodeFormat.getSelectedItemPosition()); + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + // Empty + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (buttonView == mShowClock) { + Config.setShowClock(isChecked); + mClock.setVisibility(isChecked ? View.VISIBLE : View.GONE); + } else if (buttonView == mShowBattery) { + Config.setShowBattery(isChecked); + mBattery.setVisibility(isChecked ? View.VISIBLE : View.GONE); + } else if (buttonView == mKeepScreenOn) { + Config.setKeepSreenOn(isChecked); + if (isChecked) + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + else + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else if (buttonView == mCustomLightness) { + Config.setCustomLightness(isChecked); + if (isChecked) + mCustomLightnessValue.setEnabled(true); + else + mCustomLightnessValue.setEnabled(false); + setScreenLightness(isChecked, mCustomLightnessValue.getProgress()); + } else if (buttonView == mCustomCodec) { + Config.setCustomCodec(isChecked); + if (Utils.SUPPORT_IMAGE && mCustomCodec.isChecked()) + mDecodeFormat.setEnabled(true); + else + mDecodeFormat.setEnabled(false); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/GalleryDetailActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/GalleryDetailActivity.java new file mode 100644 index 000000000..2c484e7e6 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/GalleryDetailActivity.java @@ -0,0 +1,1310 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ui; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.support.v7.widget.LinearLayoutManager; +import android.text.Html; +import android.view.ContextThemeWrapper; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextSwitcher; +import android.widget.TextView; +import android.widget.ViewSwitcher; + +import com.hippo.ehviewer.AppContext; +import com.hippo.ehviewer.ImageLoader; +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.app.MaterialAlertDialog; +import com.hippo.ehviewer.cache.ImageCache; +import com.hippo.ehviewer.data.ApiGalleryDetail; +import com.hippo.ehviewer.data.Comment; +import com.hippo.ehviewer.data.Data; +import com.hippo.ehviewer.data.DownloadInfo; +import com.hippo.ehviewer.data.GalleryDetail; +import com.hippo.ehviewer.data.GalleryInfo; +import com.hippo.ehviewer.data.ListUrls; +import com.hippo.ehviewer.data.LofiDetailImpl; +import com.hippo.ehviewer.data.LofiGalleryDetail; +import com.hippo.ehviewer.data.LofiGalleryInfo; +import com.hippo.ehviewer.data.PreviewImpl; +import com.hippo.ehviewer.data.PreviewList; +import com.hippo.ehviewer.drawable.MaterialIndicatorDrawable; +import com.hippo.ehviewer.drawable.MaterialIndicatorDrawable.Stroke; +import com.hippo.ehviewer.drawable.OvalDrawable; +import com.hippo.ehviewer.effect.ripple.RippleSalon; +import com.hippo.ehviewer.ehclient.DetailUrlParser; +import com.hippo.ehviewer.ehclient.EhClient; +import com.hippo.ehviewer.service.DownloadService; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.Constants; +import com.hippo.ehviewer.util.Favorite; +import com.hippo.ehviewer.util.Ui; +import com.hippo.ehviewer.util.ViewUtils; +import com.hippo.ehviewer.widget.AutoWrapLayout; +import com.hippo.ehviewer.widget.FitWindowView; +import com.hippo.ehviewer.widget.FloatingActionButton; +import com.hippo.ehviewer.widget.LinkifyTextView; +import com.hippo.ehviewer.widget.LoadImageView; +import com.hippo.ehviewer.widget.MaterialProgress; +import com.hippo.ehviewer.widget.MaterialToast; +import com.hippo.ehviewer.widget.ProgressiveRatingBar; +import com.hippo.ehviewer.widget.RefreshTextView; +import com.hippo.ehviewer.widget.ResponedScrollView; +import com.hippo.ehviewer.widget.SimpleGridLayout; +import com.hippo.ehviewer.widget.SuperButton; +import com.hippo.ehviewer.widget.recyclerview.EasyRecyclerView; +import com.hippo.ehviewer.widget.recyclerview.LinearDividerItemDecoration; +import com.hippo.ehviewer.windowsanimate.WindowsAnimate; + +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map.Entry; + +public class GalleryDetailActivity extends AbsTranslucentActivity + implements View.OnClickListener, + ResponedScrollView.OnScrollStateChangedListener, + View.OnTouchListener , ViewSwitcher.ViewFactory, + ProgressiveRatingBar.OnUserRateListener, PreviewList.PreviewHolder, + View.OnLayoutChangeListener, EasyRecyclerView.OnItemClickListener, + EasyRecyclerView.OnItemLongClickListener, RefreshTextView.OnRefreshListener, + FitWindowView.OnFitSystemWindowsListener { + + @SuppressWarnings("unused") + private static final String TAG = GalleryDetailActivity.class.getSimpleName(); + + public static final String KEY_G_INFO = "gallery_info"; + + private AppContext mAppContext; + private Resources mResources; + private EhClient mClient; + private WindowsAnimate mWindowsAnimate; + private GalleryInfo mGalleryInfo; + + private int mRunningAnimateNum = 0; + private boolean isCheckmark = false; + private boolean isSetDrawable = false; + + private FitWindowView mStandard; + private RefreshTextView mRefreshText; + private LoadImageView mThumb; + private TextView mTitle; + private TextView mUploader; + private SuperButton mDownloadButton; + private SuperButton mReadButton; + private TextView mCategory; + private TextView mMoreOfUploader; + private TextView mSimilar; + private TextSwitcher mRatingText; + private ProgressiveRatingBar mRating; + private TextView mFavorite; + private TextView mRate; + private TextView mCommentMoreText; + private SimpleGridLayout mPreview; + private SuperButton mPreviewPage; + private SuperButton mPreviewBack; + private SuperButton mPreviewFront; + private MaterialProgress mPreviewWait; + private SuperButton mPreviewRefresh; + + private ResponedScrollView mDetailScroll; + private ScrollView mMoreDetailScroll; + private View mMoreComment; + + private View mDetailHeader; + private View mDetailButtons; + private TextView mWarning; + private View mDetailActions; + private View mDividerAM; + private View mDetailMore; + private View mDividerMR; + private View mDetailRate; + private View mDividerRT; + private LinearLayout mDetailTag; + private View mDividerTC; + private LinearLayout mDetailComment; + private View mDividerCP; + private LinearLayout mDetailPreview; + + private EasyRecyclerView mCommentList; + private FloatingActionButton mReply; + + private OvalDrawable mCategoryDrawable; + private Drawable mRateDrawable; + private Drawable mCheckmarkDrawable; + + private CommentAdapter mCommentAdapter; + + private int mPaddingTop = -1; + private int mThemeColor; + private int mCurPreviewPage = 0; + private boolean mShowPreview = false; + private boolean mGetPreview = false; + + private String mOldReply; + private boolean mReplyPositive; + + private AlertDialog createGoToDialog() { + CharSequence[] items = new CharSequence[((PreviewImpl)mGalleryInfo).getPreviewPageNum()]; + for (int i = 0; i < items.length; i++) + items[i] = Integer.toString(i + 1); + + // TODO too slow + + return new MaterialAlertDialog.Builder(this).setTitle(R.string.jump) + .setNegativeButton(android.R.string.cancel) + .setItems(items, new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int position) { + if (position != mCurPreviewPage) { + mCurPreviewPage = position; + refreshPreview(); + } + return true; + } + }).create(); + } + + private GalleryInfo handleIntent(Intent intent) { + GalleryInfo gi = null; + if (intent.getAction() == "android.intent.action.VIEW") { + // Start from url + DetailUrlParser parser = new DetailUrlParser(); + if (parser.parser(intent.getData().getPath())) { + if (Config.getMode() == EhClient.MODE_LOFI) { + // We can't get info from lofi detail, we need get info from api + gi = new ApiGalleryDetail(); + gi.gid = parser.gid; + gi.token = parser.token; + } else { + gi = new GalleryDetail(); + gi.gid = parser.gid; + gi.token = parser.token; + } + } else { + // If can not parser url, just keep it null + } + } else { + // + gi = (GalleryInfo)(intent.getParcelableExtra(KEY_G_INFO)); + if (gi != null) { + // Add history + Data.getInstance().addHistory(gi, Data.BROWSE); + + if (gi instanceof LofiGalleryInfo) { + if (Config.getMode() == EhClient.MODE_LOFI) { + gi = new LofiGalleryDetail((LofiGalleryInfo)gi); + } else { + gi = new GalleryDetail(gi); + } + } else { + if (Config.getMode() == EhClient.MODE_LOFI) { + gi = new LofiGalleryDetail(gi); // TAG is null + } else { + gi = new GalleryDetail(gi); + } + } + } + } + return gi; + } + + /* + @Override + protected void onNewIntent(Intent intent) { + if (mGalleryInfo != null) + mGiStack.push(mGalleryInfo); + setIntent(intent); + mGalleryInfo = handleIntent(intent); + + if (mGalleryInfo == null) { + // TODO need a toast ? + if (mGiStack.isEmpty()) + finish(); + else + mGalleryInfo = mGiStack.pop(); + } else { + doPreLayout(); + } + }*/ + + @Override + protected void onDestroy() { + super.onDestroy(); + mWindowsAnimate.free(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mGalleryInfo = handleIntent(getIntent()); + if (mGalleryInfo == null) + // If no GalleryInfo pass, just finish + finish(); + + setContentView(R.layout.gallery_detail); + setTitle(String.valueOf(mGalleryInfo.gid)); + + mAppContext = (AppContext) getApplication(); + mClient = EhClient.getInstance(); + mWindowsAnimate = new WindowsAnimate(); + mWindowsAnimate.init(this); + + mResources = getResources(); + + // Menu + MaterialIndicatorDrawable materialIndicator = new MaterialIndicatorDrawable(this, Color.WHITE, Stroke.THIN); + materialIndicator.setIconState(MaterialIndicatorDrawable.IconState.ARROW); + Ui.setMaterialIndicator(getActionBar(), materialIndicator); + + // Get view + mStandard = (FitWindowView) findViewById(R.id.standard); + + mDetailScroll = (ResponedScrollView) findViewById(R.id.detail_scroll); + mMoreDetailScroll = (ScrollView) findViewById(R.id.more_detail_scroll); + mMoreComment = findViewById(R.id.more_comment); + mRefreshText = (RefreshTextView) findViewById(R.id.refresh_text); + + mDetailHeader = findViewById(R.id.detail_header); + mDetailButtons = findViewById(R.id.detail_buttons); + mWarning = (TextView) findViewById(R.id.warning); + mDetailActions = findViewById(R.id.detail_actions); + mDividerAM = findViewById(R.id.detail_divider_a_m); + mDetailMore = findViewById(R.id.detail_more); + mDividerMR = findViewById(R.id.detail_divider_m_r); + mDetailRate = findViewById(R.id.detail_rate); + mDividerRT = findViewById(R.id.detail_divider_r_t); + mDetailTag = (LinearLayout) findViewById(R.id.detail_tag); + mDividerTC = findViewById(R.id.detail_divider_t_c); + mDetailComment = (LinearLayout) findViewById(R.id.detail_comment); + mDividerCP = findViewById(R.id.detail_divider_c_p); + mDetailPreview = (LinearLayout) findViewById(R.id.detail_preview); + + mThumb = (LoadImageView) findViewById(R.id.thumb); + mTitle = (TextView) findViewById(R.id.title); + mUploader = (TextView) findViewById(R.id.uploader); + mDownloadButton = (SuperButton) findViewById(R.id.download); + mReadButton = (SuperButton) findViewById(R.id.read); + mCategory = (TextView) findViewById(R.id.category); + mMoreOfUploader = (TextView) findViewById(R.id.more_of_uploader); + mSimilar = (TextView) findViewById(R.id.similar); + mRatingText = (TextSwitcher) mDetailRate.findViewById(R.id.rating_text); + mRating = (ProgressiveRatingBar) mDetailRate.findViewById(R.id.rating); + mFavorite = (TextView) findViewById(R.id.favorite); + mRate = (TextView) findViewById(R.id.rate); + mCommentMoreText = (TextView) findViewById(R.id.comment_more_text); + mPreview = (SimpleGridLayout) findViewById(R.id.preview); + mPreviewPage = (SuperButton) findViewById(R.id.preview_num); + mPreviewBack = (SuperButton) findViewById(R.id.back); + mPreviewFront = (SuperButton) findViewById(R.id.front); + mPreviewWait = (MaterialProgress) findViewById(R.id.preview_wait); + mPreviewRefresh = (SuperButton) findViewById(R.id.preview_refresh); + + mCommentList = (EasyRecyclerView) findViewById(R.id.comment_list); + mReply = (FloatingActionButton) findViewById(R.id.reply); + + // Set Drawable + Rect rect = new Rect(0, 0, Ui.dp2pix(48), Ui.dp2pix(48)); + mCategoryDrawable = new OvalDrawable(0); + mCategoryDrawable.setBounds(0, 0, Ui.dp2pix(48), Ui.dp2pix(48)); + mCategory.setCompoundDrawables(null, mCategoryDrawable, null, null); + Drawable moreOfUploaderDrawable = mResources.getDrawable(R.drawable.ic_more_of_uploader); + moreOfUploaderDrawable.setBounds(rect); + mMoreOfUploader.setCompoundDrawables(null, moreOfUploaderDrawable, null, null); + Drawable similarDrawable = mResources.getDrawable(R.drawable.ic_similar); + similarDrawable.setBounds(rect); + mSimilar.setCompoundDrawables(null, similarDrawable, null, null); + Drawable favoriteDrawable = mResources.getDrawable(R.drawable.ic_favorite); + favoriteDrawable.setBounds(rect); + mFavorite.setCompoundDrawables(null, favoriteDrawable, null, null); + mRateDrawable = mResources.getDrawable(R.drawable.ic_rate); + mRateDrawable.setBounds(rect); + mCheckmarkDrawable = mResources.getDrawable(R.drawable.ic_checkmark); + mCheckmarkDrawable.setBounds(rect); + mRate.setCompoundDrawables(null, mRateDrawable, null, null); + + // Init + mRatingText.setFactory(this); + mPreview.setColumnCountPortrait( + Config.getPreviewColumnsPortrait()); + mPreview.setColumnCountLandscape( + Config.getPreviewColumnsLandscape()); + + // Comment + mCommentList.setLayoutManager(new LinearLayoutManager(this)); + + // Set theme color + setThemeColor(mThemeColor = Config.getCustomThemeColor() ? Config.getThemeColor() : + (mGalleryInfo.title == null ? Ui.THEME_COLOR : Ui.getCategoryColor(mGalleryInfo.category))); + + // Set ripple + /* + mWindowsAnimate.addRippleEffect(mDownloadButton, true); + mWindowsAnimate.addRippleEffect(mFavorite, false); + mWindowsAnimate.addRippleEffect(mRate, false); + mWindowsAnimate.addRippleEffect(mPreviewPage, true); + mWindowsAnimate.addRippleEffect(mPreviewBack, true); + mWindowsAnimate.addRippleEffect(mPreviewFront, true); + */ + + // Set listener + mStandard.addOnFitSystemWindowsListener(this); + mDownloadButton.setOnClickListener(this); + mReadButton.setOnClickListener(this); + mCategory.setOnClickListener(this); + mMoreOfUploader.setOnClickListener(this); + mSimilar.setOnClickListener(this); + mRating.setOnUserRateListener(this); + mFavorite.setOnClickListener(this); + mRate.setOnClickListener(this); + mDetailMore.setOnTouchListener(this); + mDetailComment.setOnTouchListener(this); + mMoreDetailScroll.addOnLayoutChangeListener(this); + mDetailScroll.addOnLayoutChangeListener(this); + mDetailScroll.setOnScrollStateChangedListener(this); + mCommentList.addOnLayoutChangeListener(this); + mPreviewPage.setOnClickListener(this); + mPreviewBack.setOnClickListener(this); + mPreviewFront.setOnClickListener(this); + mPreviewRefresh.setOnClickListener(this); + mReply.setOnClickListener(this); + mRefreshText.setDefaultRefresh(R.string.click_retry, this); + + doPreLayout(); + } + + private void setThemeColor(int themeColor) { + mThemeColor = themeColor; + getActionBar().setBackgroundDrawable(new ColorDrawable(mThemeColor)); + mDownloadButton.setRoundBackground(true, false, mResources.getColor(R.color.background_light), mThemeColor); + mDownloadButton.setTextColor(mThemeColor); + mReadButton.setRoundBackground(true, mThemeColor, 0); + ((TextView)findViewById(R.id.detail_more_text)).setTextColor(mThemeColor); + mCommentMoreText.setTextColor(mThemeColor); + mPreviewPage.setRoundBackground(true, false, mResources.getColor(R.color.background_light), mThemeColor); + mPreviewPage.setTextColor(mThemeColor); + mPreviewBack.setRoundBackground(true, false, mResources.getColor(R.color.background_light), mThemeColor); + mPreviewBack.setTextColor(mThemeColor); + mPreviewFront.setRoundBackground(true, false, mResources.getColor(R.color.background_light), mThemeColor); + mPreviewFront.setTextColor(mThemeColor); + mPreviewWait.setColor(mThemeColor); + mPreviewRefresh.setRoundBackground(true, mThemeColor, 0); + mReply.setColor(mThemeColor); + Ui.colorStatusBarL(this, mThemeColor); + if (mPaddingTop != -1) + Ui.colorStatusBarKK(this, mThemeColor, mPaddingTop - Ui.ACTION_BAR_HEIGHT); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle item selection + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void getDetailInfo() { + String detailUrl = mClient.getDetailUrl( + mGalleryInfo.gid, mGalleryInfo.token); + if (mGalleryInfo instanceof GalleryDetail) + mClient.getGDetail(detailUrl, (GalleryDetail)mGalleryInfo, new GDetailGetListener()); + else if (mGalleryInfo instanceof LofiGalleryDetail) + mClient.getLGDetail(detailUrl, (LofiGalleryDetail)mGalleryInfo, new GLDetailGetListener()); + else if (mGalleryInfo instanceof ApiGalleryDetail) + ; // TODO + } + + private void doPreLayout() { + mRefreshText.setRefreshing(true); + + if (mGalleryInfo.title != null) { + // Not from url + mDetailScroll.setVisibility(View.VISIBLE); + mDetailHeader.setVisibility(View.VISIBLE); + + mThumb.setLoadInfo(mGalleryInfo.thumb, String.valueOf(mGalleryInfo.gid)); + ImageLoader.getInstance(this).add(mGalleryInfo.thumb, String.valueOf(mGalleryInfo.gid), + new LoadImageView.SimpleImageGetListener(mThumb).setTransitabled(false)); + mTitle.setText(mGalleryInfo.title); + mUploader.setText(mGalleryInfo.uploader); + } + + getDetailInfo(); + } + + private String getRatingText(float rating) { + if (rating == Float.NaN) + return "(´_ゝ`)"; + + int resId = 0; + switch (Math.round(rating * 2)) { + case 0: + resId = R.string.rating0; break; + case 1: + resId = R.string.rating1; break; + case 2: + resId = R.string.rating2; break; + case 3: + resId = R.string.rating3; break; + case 4: + resId = R.string.rating4; break; + case 5: + resId = R.string.rating5; break; + case 6: + resId = R.string.rating6; break; + case 7: + resId = R.string.rating7; break; + case 8: + resId = R.string.rating8; break; + case 9: + resId = R.string.rating9; break; + case 10: + resId = R.string.rating10; break; + } + + if (resId == 0) + return "(´_ゝ`)"; + else + return getString(resId); + } + + private String getAllRatingText(float rating, int people) { + StringBuilder sb = new StringBuilder() + .append(String.format(getString(R.string.we_feel), getRatingText(rating))) + .append(" (").append(rating); + if (people != -1) + sb.append(", ").append(people).append(")").toString(); + else + sb.append(")"); + return sb.toString(); + } + + private void addTag(LinkedHashMap> tags) { + int x = Ui.dp2pix(2); + int y = Ui.dp2pix(4); + ContextThemeWrapper ctw = new ContextThemeWrapper(this, R.style.TextTag); + for (Entry> tagGroup : tags.entrySet()) { + + LinearLayout tagGroupLayout = new LinearLayout(this); + tagGroupLayout.setOrientation(LinearLayout.HORIZONTAL); + AutoWrapLayout tagLayout = new AutoWrapLayout(this); + + // Group name + final String groupName = tagGroup.getKey(); + TextView groupNameView = new TextView(ctw); + groupNameView.setText(groupName); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + lp.setMargins(x, y, x, y); + tagGroupLayout.addView(groupNameView, lp); + + for (final String tag : tagGroup.getValue()) { + TextView tagView = new TextView(ctw); + tagView.setText(tag); + tagView.setBackgroundColor(mThemeColor); + tagView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(GalleryDetailActivity.this, + GalleryListActivity.class); + intent.setAction(GalleryListActivity.ACTION_GALLERY_LIST); + intent.putExtra(GalleryListActivity.KEY_MODE, + ListUrls.MODE_TAG); + intent.putExtra(GalleryListActivity.KEY_TAG, groupName + ":" + tag); + intent.setAction(GalleryListActivity.ACTION_GALLERY_LIST); + startActivity(intent); + } + }); + AutoWrapLayout.LayoutParams alp = new AutoWrapLayout.LayoutParams(); + alp.setMargins(x, y, x, y); + tagLayout.addView(tagView, alp); + } + tagGroupLayout.addView(tagLayout); + mDetailTag.addView(tagGroupLayout); + } + } + + private void addLofiTag(String[] tags) { + int x = Ui.dp2pix(2); + int y = Ui.dp2pix(4); + ContextThemeWrapper ctw = new ContextThemeWrapper(this, R.style.TextTag); + AutoWrapLayout tagLayout = new AutoWrapLayout(this); + for (String tag : tags) { + TextView tagView = new TextView(ctw); + tagView.setText(tag); + tagView.setBackgroundColor(mThemeColor); + AutoWrapLayout.LayoutParams alp = new AutoWrapLayout.LayoutParams(); + alp.setMargins(x, y, x, y); + tagLayout.addView(tagView, alp); + } + mDetailTag.addView(tagLayout); + } + + private String getPreviewPageNum(int pageNum) { + return pageNum == Integer.MAX_VALUE ? "??" : String.valueOf(pageNum); + } + + private void updateDownloadButton() { + int gid = mGalleryInfo.gid; + DownloadInfo di = Data.getInstance().getDownload(gid); + if (di == null) { + mDownloadButton.setText(R.string.download); + } else { + mDownloadButton.setText(R.string.downloaded); + } + } + + /** + * GalleryDetail + * LofiGalleryDetail + * ApiGalleryDetail + */ + private void doLayout() { + if (mDetailScroll.getVisibility() == View.GONE) { + // If not set header in doPreLayout + // Set theme + setThemeColor(Ui.getCategoryColor(mGalleryInfo.category)); + // Add history first + Data.getInstance().addHistory(mGalleryInfo, Data.BROWSE); + mDetailScroll.setVisibility(View.VISIBLE); + mDetailHeader.setVisibility(View.VISIBLE); + mThumb.setLoadInfo(mGalleryInfo.thumb, String.valueOf(mGalleryInfo.gid)); + ImageLoader.getInstance(this).add(mGalleryInfo.thumb, String.valueOf(mGalleryInfo.gid), + new LoadImageView.SimpleImageGetListener(mThumb)); + mTitle.setText(mGalleryInfo.title); + mUploader.setText(mGalleryInfo.uploader); + } + + // Button + mDetailButtons.setVisibility(View.VISIBLE); + updateDownloadButton(); + + // waring + mWarning.setVisibility(View.VISIBLE); + + // Action + mDetailActions.setVisibility(View.VISIBLE); + mCategory.setText(Ui.getCategoryText(mGalleryInfo.category)); + mCategoryDrawable.setColor(Ui.getCategoryColor(mGalleryInfo.category)); + + // Detail + mDividerAM.setVisibility(View.VISIBLE); + mDetailMore.setVisibility(View.VISIBLE); + if (mGalleryInfo instanceof GalleryDetail) { + GalleryDetail galleryDetail = (GalleryDetail)mGalleryInfo; + ((TextView)mDetailMore.findViewById(R.id.language)).setText( + getString(R.string.language) + ": " + galleryDetail.language); + ((TextView)mDetailMore.findViewById(R.id.posted)).setText(galleryDetail.posted); + ((TextView)mDetailMore.findViewById(R.id.pages)).setText( + getString(R.string.pages) + ": " + String.valueOf(galleryDetail.pages)); + ((TextView)mDetailMore.findViewById(R.id.size)).setText( + getString(R.string.size) + ": " + galleryDetail.size); + StringBuilder sb = new StringBuilder(); + sb.append("gid: ").append(galleryDetail.gid).append("\n\n") + .append("token: ").append(galleryDetail.token).append("\n\n") + .append("title: ").append(galleryDetail.title).append("\n\n") + .append("title_jpn: ").append(galleryDetail.title_jpn).append("\n\n") + .append("thumb: ").append(galleryDetail.thumb).append("\n\n") + .append("category: ").append(Ui.getCategoryText(galleryDetail.category)).append("\n\n") + .append("uploader: ").append(galleryDetail.uploader).append("\n\n") + .append("posted: ").append(galleryDetail.posted).append("\n\n") + .append("pages: ").append(galleryDetail.pages).append("\n\n") + .append("size: ").append(galleryDetail.size).append("\n\n") + .append("resized: ").append(galleryDetail.resized).append("\n\n") + .append("parent: ").append(galleryDetail.parent).append("\n\n") + .append("visible: ").append(galleryDetail.visible).append("\n\n") + .append("language: ").append(galleryDetail.language).append("\n\n") + .append("people: ").append(galleryDetail.people).append("\n\n") + .append("rating: ").append(galleryDetail.rating); + ((TextView)mMoreDetailScroll.findViewById(R.id.more_detail)).setText(sb.toString()); + } else if (mGalleryInfo instanceof LofiGalleryDetail) { + LofiGalleryDetail lofiGalleryDetail = (LofiGalleryDetail)mGalleryInfo; + ((TextView)mDetailMore.findViewById(R.id.language)).setVisibility(View.GONE); + ((TextView)mDetailMore.findViewById(R.id.posted)).setVisibility(View.GONE); + ((TextView)mDetailMore.findViewById(R.id.pages)).setVisibility(View.GONE); + ((TextView)mDetailMore.findViewById(R.id.size)).setText(lofiGalleryDetail.posted); + StringBuilder sb = new StringBuilder(); + sb.append("gid: ").append(lofiGalleryDetail.gid).append("\n\n") + .append("token: ").append(lofiGalleryDetail.token).append("\n\n") + .append("title: ").append(lofiGalleryDetail.title).append("\n\n") + .append("thumb: ").append(lofiGalleryDetail.thumb).append("\n\n") + .append("category: ").append(Ui.getCategoryText(lofiGalleryDetail.category)).append("\n\n") + .append("uploader: ").append(lofiGalleryDetail.uploader).append("\n\n") + .append("posted: ").append(lofiGalleryDetail.posted).append("\n\n") + .append("simpleLanguage: ").append(lofiGalleryDetail.simpleLanguage).append("\n\n") + .append("rating: ").append(lofiGalleryDetail.rating).append("\n\n"); + ((TextView)mMoreDetailScroll.findViewById(R.id.more_detail)).setText(sb.toString()); + } else if (mGalleryInfo instanceof ApiGalleryDetail) { + // TODO + } + + // Rate + mDividerMR.setVisibility(View.VISIBLE); + mDetailRate.setVisibility(View.VISIBLE); + int people = mGalleryInfo instanceof GalleryDetail ? ((GalleryDetail)mGalleryInfo).people : -1; + mRatingText.setCurrentText(getAllRatingText(mGalleryInfo.rating, people)); + mRating.setRating(mGalleryInfo.rating); + mRating.setEnableRate(false); + + // Tag + if (mGalleryInfo instanceof GalleryDetail) { + GalleryDetail galleryDetail = (GalleryDetail)mGalleryInfo; + if (galleryDetail.tags != null && galleryDetail.tags.size() > 0) { + mDividerRT.setVisibility(View.VISIBLE); + mDetailTag.setVisibility(View.VISIBLE); + addTag(galleryDetail.tags); + } + } else if (mGalleryInfo instanceof LofiDetailImpl) { + LofiDetailImpl lofiDetailImpl = (LofiDetailImpl)mGalleryInfo; + String[] tags = lofiDetailImpl.getTags(); + if (tags != null && tags.length > 0) { + mDividerRT.setVisibility(View.VISIBLE); + mDetailTag.setVisibility(View.VISIBLE); + addLofiTag(tags); + } + } + + // Comment + GalleryDetail galleryDetail; + if (mGalleryInfo instanceof GalleryDetail && + (galleryDetail = (GalleryDetail)mGalleryInfo) != null && + galleryDetail.comments != null) { + mDividerTC.setVisibility(View.VISIBLE); + mDetailComment.setVisibility(View.VISIBLE); + + int commentNum = galleryDetail.comments.size(); + if (commentNum == 0) + mCommentMoreText.setText(R.string.no_comment); + else if (commentNum <= 2) + mCommentMoreText.setText(R.string.no_more_comment); + else + mCommentMoreText.setText(R.string.click_more); + int maxShown = Math.min(2, commentNum); + for (int i = 0; i < maxShown; i++) { + mDetailComment.addView(getCommentView(null, galleryDetail.comments.get(i), true), + i, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + } + + LinearDividerItemDecoration itemDecorationnew = new LinearDividerItemDecoration( + LinearDividerItemDecoration.VERTICAL, 0x22000000, Ui.dp2pix(1)); // TODO + itemDecorationnew.setShowLastDivider(true); + itemDecorationnew.setPadding(Ui.dp2pix(16)); + mCommentAdapter = new CommentAdapter(); + mCommentList.setHasFixedSize(true); + mCommentList.setAdapter(mCommentAdapter); + mCommentList.addItemDecoration(itemDecorationnew); + mCommentList.setOnItemClickListener(this); + mCommentList.setOnItemLongClickListener(this); + } + + // Add preview + if (mGalleryInfo instanceof PreviewImpl) { + PreviewImpl previewImpl = (PreviewImpl)mGalleryInfo; + PreviewList pl = previewImpl.getPreview(0); + if (pl != null) { + mDividerCP.setVisibility(View.VISIBLE); + mDetailPreview.setVisibility(View.VISIBLE); + + mPreviewPage.setText((mCurPreviewPage + 1) + "/" + + getPreviewPageNum(previewImpl.getPreviewPageNum())); + pl.setData(this, this, previewImpl, mCurPreviewPage); + pl.addPreview(mPreview); + } + } + } + + private void refreshPreview() { + if (!(mGalleryInfo instanceof PreviewImpl)) + return; + + PreviewImpl previewImpl = (PreviewImpl)mGalleryInfo; + mPreview.removeAllViews(); + mPreviewPage.setText((mCurPreviewPage + 1) + "/" + getPreviewPageNum(previewImpl.getPreviewPageNum())); + + PreviewList previewList = previewImpl.getPreview(mCurPreviewPage); + if (previewList == null) { + mPreviewWait.setVisibility(View.VISIBLE); + mPreviewRefresh.setVisibility(View.GONE); + + String url = mClient.getDetailUrl( + mGalleryInfo.gid, mGalleryInfo.token, mCurPreviewPage); + mClient.getPreviewList(url, Config.getMode(), mCurPreviewPage, + new PListGetListener()); + + mGetPreview = true; + } else { + mPreviewWait.setVisibility(View.GONE); + mPreviewRefresh.setVisibility(View.GONE); + previewList.addPreview(mPreview); + + mShowPreview = true; + } + } + + @SuppressLint("InflateParams") + private View getCommentView(View contentView, Comment comment, boolean restrict) { + if (contentView == null) { + contentView = LayoutInflater.from(this).inflate(R.layout.comments_item, null); + if (!restrict) { + RippleSalon.addRipple(contentView, ColorStateList.valueOf(0x40000000)); + //mWindowsAnimate.addRippleEffect(contentView, true); + } + } + ((TextView)contentView.findViewById(R.id.user)).setText(comment.user); + ((TextView)contentView.findViewById(R.id.time)).setText(comment.time); + TextView commentText = (TextView)contentView.findViewById(R.id.comment); + if (restrict) { + commentText.setMaxLines(3); + // Cancel auto link + commentText.setAutoLinkMask(0); + } + commentText.setText(Html.fromHtml(comment.comment)); + return contentView; + } + + @Override + public void onBackPressed() { + if (mRunningAnimateNum != 0 || mWindowsAnimate.isRunningAnimate()) + return; + + if (mMoreDetailScroll.getVisibility() == View.VISIBLE) { + mDetailScroll.setVisibility(View.VISIBLE); + mWindowsAnimate.addMoveExitTransitions(mMoreDetailScroll, null); + } else if (mMoreComment.getVisibility() == View.VISIBLE) { + mDetailScroll.setVisibility(View.VISIBLE); + mWindowsAnimate.addMoveExitTransitions(mMoreComment, null); + } else { + super.onBackPressed(); + } + } + + @Override + public void onRefresh() { + getDetailInfo(); + } + + @Override + @SuppressWarnings("deprecation") + public void onClick(View v) { + if (mRunningAnimateNum != 0 || mWindowsAnimate.isRunningAnimate()) + return; + + if (v == mDownloadButton) { + Intent it = new Intent(GalleryDetailActivity.this, DownloadService.class); + it.setAction(DownloadService.ACTION_ADD); + it.putExtra(DownloadService.KEY_GALLERY_INFO, mGalleryInfo); + startService(it); + mDownloadButton.setText(R.string.downloaded); + MaterialToast.showToast(R.string.toast_add_download); + } else if (v == mReadButton) { + Intent intent = new Intent(this, + GalleryActivity.class); + intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, mGalleryInfo); + intent.putExtra(GalleryActivity.KEY_START_INDEX, -1); + startActivity(intent); + } else if (v == mCategory) { + Intent intent = new Intent(this, GalleryListActivity.class); + intent.setAction(GalleryListActivity.ACTION_GALLERY_LIST); + intent.putExtra(GalleryListActivity.KEY_MODE, ListUrls.MODE_NORMAL); + intent.putExtra(GalleryListActivity.KEY_CATEGORY, mGalleryInfo.category); + startActivity(intent); + } else if (v == mMoreOfUploader) { + Intent intent = new Intent(this, GalleryListActivity.class); + intent.setAction(GalleryListActivity.ACTION_GALLERY_LIST); + intent.putExtra(GalleryListActivity.KEY_MODE, ListUrls.MODE_UPLOADER); + intent.putExtra(GalleryListActivity.KEY_UPLOADER, mGalleryInfo.uploader); + startActivity(intent); + } else if (v == mSimilar) { + Intent intent = new Intent(this, GalleryListActivity.class); + intent.setAction(GalleryListActivity.ACTION_GALLERY_LIST); + intent.putExtra(GalleryListActivity.KEY_MODE, ListUrls.MODE_IMAGE_SEARCH); + intent.putExtra(GalleryListActivity.KEY_IMAGE_KEY, String.valueOf(mGalleryInfo.gid)); + intent.putExtra(GalleryListActivity.KEY_IMAGE_URL, mGalleryInfo.thumb); + startActivity(intent); + } else if (v == mFavorite) { + Favorite.addToFavorite(this, mGalleryInfo); + } else if (v == mRate) { + if (isCheckmark) { + int rating = (int)(mRating.getRating() * 2 + 0.5); + if (rating <= 0 || rating > 10) { + MaterialToast.showToast(R.string.invalid_rating); + return; + } + mClient.rate(mGalleryInfo.gid, mGalleryInfo.token, rating, new EhClient.OnRateListener() { + @Override + public void onSuccess(float ratingAvg, int ratingCnt) { + mGalleryInfo.rating = ratingAvg; + mRatingText.setText(getAllRatingText(mGalleryInfo.rating, ratingCnt)); + mRating.setRating(mGalleryInfo.rating); + } + @Override + public void onFailure(String eMsg) { + mRatingText.setText(getString(R.string.rate_failed)); + MaterialToast.showToast(eMsg); + } + }); + mRatingText.setText(getString(R.string.rate_succeeded)); + mRating.setEnableRate(false); + } else { + mRatingText.setText(getString(R.string.rate_sensibly)); + mRating.setEnableRate(true); + } + + // Animate to change + ValueAnimator animation = ValueAnimator.ofFloat(0.0f, 90.0f, 180.0f); + animation.setDuration(Constants.ANIMATE_TIME); + animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float rotationY = (Float)animation.getAnimatedValue(); + if (rotationY > 90) { + rotationY = 180 - rotationY; + if (!isSetDrawable) { + mRate.setCompoundDrawables(null, isCheckmark ? mRateDrawable : mCheckmarkDrawable, null, null); + mRate.setText(isCheckmark ? R.string.rate : android.R.string.ok); + isSetDrawable = true; + isCheckmark = !isCheckmark; + } + } + mRate.setRotationY(rotationY); + } + }); + animation.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) {} + @Override + public void onAnimationRepeat(Animator animation) {} + @Override + public void onAnimationEnd(Animator animation) { + mRunningAnimateNum--; + isSetDrawable = false; + } + @Override + public void onAnimationCancel(Animator animation) {} + }); + animation.start(); + mRunningAnimateNum++; + } else if (v == mReply) { + mReplyPositive = false; + final EditText et = new EditText(this); + et.setGravity(Gravity.TOP); + et.setHint("评论内容"); // TODO + et.setTextColor(getResources().getColor(R.color.secondary_text_dark)); + et.setBackgroundDrawable(null); + et.setText(mOldReply); + if (mOldReply != null) + et.setSelection(mOldReply.length()); + + new MaterialAlertDialog.Builder(this).setTitle(R.string.reply) + .setView(et, true, new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)) + .setNegativeButton(android.R.string.cancel) + .setPositiveButton(android.R.string.ok) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + if (which == MaterialAlertDialog.POSITIVE) { + mReplyPositive = true; + mClient.comment(EhClient.getDetailUrl(mGalleryInfo.gid, + mGalleryInfo.token, 0, Config.getApiMode()), et.getText().toString(), + new EhClient.OnCommentListener() { + @Override + public void onSuccess(List comments) { + ((GalleryDetail)mGalleryInfo).comments = comments; + mCommentAdapter.notifyDataSetChanged(); + mOldReply = null; + } + @Override + public void onFailure(String eMsg) { + MaterialToast.showToast(eMsg); + mOldReply = et.getText().toString(); + } + }); + } + return true; + } + }).setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + if (mReplyPositive) + mOldReply = null; + else + mOldReply = et.getText().toString(); + } + }).show(); + + } else if (v == mPreviewPage) { + if (mGalleryInfo instanceof PreviewImpl && + ((PreviewImpl)mGalleryInfo).getPreviewPageNum() != Integer.MAX_VALUE && + ((PreviewImpl)mGalleryInfo).getPreviewPageNum() > 1) + createGoToDialog().show(); + } else if (v == mPreviewBack) { + if (!(mGalleryInfo instanceof PreviewImpl) || + (mGalleryInfo instanceof LofiGalleryDetail && mGetPreview)) + return; + + if (mCurPreviewPage <= 0) + return; + mCurPreviewPage--; + refreshPreview(); + } else if (v == mPreviewFront) { + if (!(mGalleryInfo instanceof PreviewImpl) || + (mGalleryInfo instanceof LofiGalleryDetail && mGetPreview)) + return; + + if (mCurPreviewPage >= ((PreviewImpl)mGalleryInfo).getPreviewPageNum() - 1) + return; + mCurPreviewPage++; + refreshPreview(); + } else if (v == mPreviewRefresh) { + refreshPreview(); + } + } + + @Override + public void onLayoutChange(View v, int left, int top, int right, + int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + if (v == mMoreDetailScroll) { + mMoreDetailScroll.scrollTo(0, 0); + } else if (v == mCommentList) { + //mCommentList.setSelection(0); + } else if (v == mDetailScroll) { + if (mShowPreview) { + mShowPreview = false; + mDetailScroll.smoothScrollTo(0, mDividerCP.getTop()); + } + } + } + + @Override + public void onFitSystemWindows(int l, int t, int r, int b) { + mDetailScroll.setPadding(mDetailScroll.getPaddingLeft(), t, + mDetailScroll.getPaddingRight(), b); + mMoreDetailScroll.setPadding(mMoreDetailScroll.getPaddingLeft(), t, + mMoreDetailScroll.getPaddingRight(), b); + mCommentList.setPadding(mCommentList.getPaddingLeft(), t, + mCommentList.getPaddingRight(), b + Ui.dp2pix(16 + 56 + 16)); + ((FrameLayout.LayoutParams)mReply.getLayoutParams()).bottomMargin = Ui.dp2pix(16) + b; + + mPaddingTop = t; + Ui.colorStatusBarKK(this, mThemeColor, mPaddingTop - Ui.ACTION_BAR_HEIGHT); + } + + @Override + @SuppressLint("ClickableViewAccessibility") + public boolean onTouch(View v, MotionEvent event) { + if (v == mDetailMore) { + if (ViewUtils.isClickAction(event)) { + mWindowsAnimate.addCircleTransitions(mDetailMore, (int)event.getX(), + (int)event.getY(), mResources.getColor(R.color.background_light), + new WindowsAnimate.OnAnimationEndListener() { + @Override + public void onAnimationEnd() { + mDetailScroll.setVisibility(View.GONE); + mMoreDetailScroll.setVisibility(View.VISIBLE); + AlphaAnimation aa = new AlphaAnimation(0.0f,1.0f); + aa.setDuration(Constants.ANIMATE_TIME); + mMoreDetailScroll.startAnimation(aa); + } + }); + } + } else if (v == mDetailComment) { + if (ViewUtils.isClickAction(event)) { + mWindowsAnimate.addCircleTransitions(mDetailComment, (int)event.getX(), + (int)event.getY(), mResources.getColor(R.color.background_light), + new WindowsAnimate.OnAnimationEndListener() { + @Override + public void onAnimationEnd() { + mDetailScroll.setVisibility(View.GONE); + mMoreComment.setVisibility(View.VISIBLE); + // Add reply icon animation + mReply.setVisibility(View.INVISIBLE); + AlphaAnimation aa = new AlphaAnimation(0.0f,1.0f); + aa.setDuration(Constants.ANIMATE_TIME); + aa.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) {} + @Override + public void onAnimationRepeat(Animation animation) {} + @Override + public void onAnimationEnd(Animation animation) { + mWindowsAnimate.addOvershootEnterTransitions(mReply, null); + } + }); + mMoreComment.startAnimation(aa); + } + }); + } + } + return true; + } + + @Override + public void onUserRate(float rating) { + mRatingText.setText(getRatingText(rating)); + } + + @Override + public boolean onItemClick(EasyRecyclerView parent, View view, int position, + long id) { + // Handler url here + LinkifyTextView comment = ((LinkifyTextView) view.findViewById(R.id.comment)); + String url = comment.getTouchedUrl(); + if (url != null) { + comment.clearTouchedUrl(); + Uri uri = Uri.parse(url); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + startActivity(intent); + return true; + } + return false; + } + + @Override + public boolean onItemLongClick(EasyRecyclerView parent, View view, + int position, long id) { + final Comment c = ((GalleryDetail) mGalleryInfo).comments.get(position); + + new MaterialAlertDialog.Builder(this).setTitle(R.string.what_to_do) + .setNegativeButton(android.R.string.cancel) + .setItems(R.array.comment_long_click, new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int position) { + switch (position) { + case 0: + ClipboardManager cm = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE); + cm.setPrimaryClip(ClipData.newPlainText(null, c.comment)); + MaterialToast.showToast(R.string.copyed); + break; + + case 1: + Intent intent = new Intent(GalleryDetailActivity.this, GalleryListActivity.class); + intent.setAction(GalleryListActivity.ACTION_GALLERY_LIST); + intent.putExtra(GalleryListActivity.KEY_MODE, ListUrls.MODE_UPLOADER); + intent.putExtra(GalleryListActivity.KEY_UPLOADER, c.user); + startActivity(intent); + break; + } + return true; + } + }).show(); + return true; + } + + @Override + public View makeView() { + TextView tv = new TextView(this); + tv.setGravity(Gravity.CENTER); + return tv; + } + + @Override + public int getCurPreviewPage() { + return mCurPreviewPage; + } + + @Override + public void onGetPreviewImageFailure() { + mPreviewWait.setVisibility(View.GONE); + mPreviewRefresh.setVisibility(View.VISIBLE); + } + + @Override + public void onScrollStateChanged(ResponedScrollView view, int state) { + if (view == mDetailScroll) { + ImageCache imageCache = ImageCache.getInstance(this); + if (state == ResponedScrollView.SCROLL_START) + imageCache.setPauseDiskCache(true); + else + imageCache.setPauseDiskCache(false); + } + } + + private class GDetailGetListener + implements EhClient.OnGetGDetailListener { + @Override + public void onSuccess(GalleryDetail md) { + mRefreshText.setRefreshing(false); + doLayout(); + } + + @Override + public void onFailure(String eMsg) { + mRefreshText.setEmesg(eMsg, true); + } + } + + private class GLDetailGetListener + implements EhClient.OnGetLGDetailListener { + @Override + public void onSuccess(LofiGalleryDetail md, boolean isLastPage) { + if (isLastPage) + ((LofiDetailImpl)mGalleryInfo).setPreviewPageNum(mCurPreviewPage + 1); + mRefreshText.setRefreshing(false); + doLayout(); + } + + @Override + public void onFailure(String eMsg) { + mRefreshText.setEmesg(eMsg, true); + } + } + + private class PListGetListener + implements EhClient.OnGetPreviewListListener { + @Override + public void onSuccess(Object checkFlag, PreviewList pageList, boolean isLastPage) { + if (isFinishing()) + return; + if (!(mGalleryInfo instanceof PreviewImpl)) + return; + + mPreviewWait.setVisibility(View.GONE); + mPreviewRefresh.setVisibility(View.GONE); + + int page = (Integer)checkFlag; + PreviewImpl previewImpl = (PreviewImpl)mGalleryInfo; + previewImpl.setPreview(page, pageList); + pageList.setData(GalleryDetailActivity.this, + GalleryDetailActivity.this, previewImpl, page); + pageList.addPreview(mPreview); + + if (isLastPage && mGalleryInfo instanceof LofiDetailImpl) { + ((LofiDetailImpl)mGalleryInfo).setPreviewPageNum(mCurPreviewPage + 1); + mPreviewPage.setText((mCurPreviewPage + 1) + "/" + + getPreviewPageNum(previewImpl.getPreviewPageNum())); + } + + mGetPreview = false; + mShowPreview = true; + } + + @Override + public void onFailure(Object checkFlag, String eMsg) { + if (isFinishing()) + return; + if (!(mGalleryInfo instanceof PreviewImpl)) + return; + + int page = (Integer)checkFlag; + if (page == mCurPreviewPage) { + MaterialToast.showToast(eMsg); + mPreviewWait.setVisibility(View.GONE); + mPreviewRefresh.setVisibility(View.VISIBLE); + } + + // TODO what to do with mGetPreview + } + } + + + private class CommentHolder extends EasyRecyclerView.ViewHolder { + private final TextView userView; + private final TextView timeView; + private final TextView commentView; + + public CommentHolder(View itemView) { + super(itemView); + + userView = (TextView) itemView.findViewById(R.id.user); + timeView = (TextView) itemView.findViewById(R.id.time); + commentView = (TextView) itemView.findViewById(R.id.comment); + } + + } + + private class CommentAdapter extends EasyRecyclerView.FooterAdapter { + + private final LayoutInflater mInflater; + + public CommentAdapter() { + mInflater = LayoutInflater.from(GalleryDetailActivity.this); + } + + @Override + public CommentHolder onCreateAndBindFooterViewHolder(ViewGroup parent, + View footerView) { + // No footer + return null; + } + + @Override + public CommentHolder onCreateViewHolderActual(ViewGroup parent, + int viewType) { + View view = mInflater.inflate(R.layout.comments_item, parent, false); + RippleSalon.addRipple(view, ColorStateList.valueOf(0x40000000)); // TODO + return new CommentHolder(view); + } + + @Override + public void onBindViewHolderActual(CommentHolder holder, int position) { + final Comment comment = ((GalleryDetail) mGalleryInfo).comments.get(position); + + holder.userView.setText(comment.user); + holder.timeView.setText(comment.time); + holder.commentView.setText(Html.fromHtml(comment.comment)); + } + + @Override + public int getItemCountActual() { + if (mGalleryInfo instanceof GalleryDetail) { + GalleryDetail galleryDetail = (GalleryDetail)mGalleryInfo; + if (galleryDetail.comments == null) + return 0; + else + return galleryDetail.comments.size(); + } else { + return 0; + } + } + + } + +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/GalleryListActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/GalleryListActivity.java new file mode 100644 index 000000000..09168ae68 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/GalleryListActivity.java @@ -0,0 +1,1624 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ui; + +import java.io.File; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.ActionBar; +import android.app.AlertDialog; +import android.app.SearchManager; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Message; +import android.provider.MediaStore; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.style.ImageSpan; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AutoCompleteTextView; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.Spinner; +import android.widget.TextView; + +import com.hippo.ehviewer.AppContext; +import com.hippo.ehviewer.AppHandler; +import com.hippo.ehviewer.ImageLoader; +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.SimpleSuggestionProvider; +import com.hippo.ehviewer.UpdateHelper; +import com.hippo.ehviewer.app.MaterialAlertDialog; +import com.hippo.ehviewer.data.Data; +import com.hippo.ehviewer.data.GalleryInfo; +import com.hippo.ehviewer.data.ListUrls; +import com.hippo.ehviewer.data.Tag; +import com.hippo.ehviewer.drawable.FreeMaterialDrawable; +import com.hippo.ehviewer.drawable.MaterialIndicatorDrawable; +import com.hippo.ehviewer.drawable.MaterialIndicatorDrawable.Stroke; +import com.hippo.ehviewer.ehclient.EhClient; +import com.hippo.ehviewer.network.HttpHelper; +import com.hippo.ehviewer.service.DownloadService; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.DialogUtils; +import com.hippo.ehviewer.util.Favorite; +import com.hippo.ehviewer.util.Ui; +import com.hippo.ehviewer.util.Utils; +import com.hippo.ehviewer.util.ViewUtils; +import com.hippo.ehviewer.widget.CategoryTable; +import com.hippo.ehviewer.widget.DrawerListView; +import com.hippo.ehviewer.widget.FitWindowView; +import com.hippo.ehviewer.widget.GalleryListView; +import com.hippo.ehviewer.widget.GalleryListView.OnGetListListener; +import com.hippo.ehviewer.widget.MaterialToast; +import com.hippo.ehviewer.widget.SearchView; +import com.hippo.ehviewer.widget.SlidingDrawerLayout; +import com.hippo.ehviewer.widget.SuggestionHelper; +import com.hippo.ehviewer.widget.SuggestionTextView; +import com.hippo.ehviewer.widget.TagListView; +import com.hippo.ehviewer.widget.TagsAdapter; +import com.hippo.ehviewer.widget.recyclerview.EasyRecyclerView; +import com.hippo.ehviewer.windowsanimate.WindowsAnimate; + +/* + * + * Footer 显示产生的 refresh 手动 pull 显示产生的 refresh 按键或其他产生的 refresh + * | | | + * | | | + * ∨ ∨ ∨ + * 触发 onFootRefresh 触发 onRefreshStart setHeaderRefresh(true) + * | | | + * |<---------------------------------|-----------------------------| + * ∨ + * set listUrl + * set getMode + * | + * | + * ∨ + * getList + */ + +/** + * Make sure when getting list pulllistview is refreshing + * + * @author Hippo + */ +public class GalleryListActivity extends AbsTranslucentActivity implements View.OnClickListener, + SearchView.OnQueryTextListener, SearchView.OnFocusChangeListener, + SlidingDrawerLayout.DrawerListener, EhClient.OnLoginListener, + GalleryListView.GalleryListViewHelper, FitWindowView.OnFitSystemWindowsListener, + SearchView.OnActionViewExpandedListener, EasyRecyclerView.OnItemClickListener, + EasyRecyclerView.OnItemLongClickListener { + + @SuppressWarnings("unused") + private static final String TAG = GalleryListActivity.class.getSimpleName(); + + private static int RESULT_LOAD_SEARCH_IMAGE = 1; + + public static final String ACTION_GALLERY_LIST = "com.hippo.ehviewer.intent.action.GALLERY_LIST"; + + public static final String KEY_MODE = "mode"; + public static final String KEY_CATEGORY = "category"; + public static final String KEY_TAG = "tag"; + public static final String KEY_UPLOADER = "uploader"; + public static final String KEY_IMAGE_KEY = "image_key"; + public static final String KEY_IMAGE_URL = "image_url"; + + private AppContext mAppContext; + private EhClient mClient; + private Resources mResources; + private WindowsAnimate mWindowsAnimate; + private SuggestionHelper mSuggestions; + private ActionBar mActionBar; + private int mThemeColor; + + private MaterialIndicatorDrawable mMaterialIndicator; + private boolean mDirection; + + private SlidingDrawerLayout mDrawerLayout; + private View mLeftMenu; + private View mRightMenu; + private FitWindowView mStandard; + private GalleryListView mGalleryListView; + + private View mUserPanel; + private DrawerListView mMenuList; + private ImageView mAvatar; + private TextView mUsernameView; + private Button mLoginButton; + private Button mRegisterButton; + private Button mLogoutButton; + private View mWaitLogView; + + private View mRightDrawerBg; + private TagListView mQuickSearchList; + private View mQuickSearchTip; + + private TextView mSearchImageText; + + private SearchView mSearchView; + private MenuItem mSearchItem; + + private TagsAdapter tagsAdapter; + + private ListUrls lus; + private final ArrayList listMenuTag = new ArrayList(); + + private Data mData; + + private int longClickItemIndex; + private boolean mShowDrawer; + + private AlertDialog loginDialog; + private AlertDialog mSearchDialog; + private AlertDialog longClickDialog; + + // Modify tag + private String newTagName = null; + + // Double click back exit + private long curBackTime = 0; + private static final int BACK_PRESSED_INTERVAL = 2000; + + private String mTitle; + + // private int mListModeThumbHeight; + + private void toRegister() { + Uri uri = Uri.parse("http://forums.e-hentai.org/index.php?act=Reg"); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + startActivity(intent); + } + + @Override + public void onSuccess() { + setUserPanel(); + } + + @Override + public void onGetAvatar(final int code) { + GalleryListActivity.this.runOnUiThread(new Runnable() { + @Override + public void run() { + switch (code) { + case EhClient.GET_AVATAR_OK: + setUserPanel(); + break; + case EhClient.NO_AVATAR: + MaterialToast.showToast(R.string.no_avatar); + break; + case EhClient.GET_AVATAR_ERROR: + default: + MaterialToast.showToast(R.string.get_avatar_error); + break; + } + } + }); + } + + @Override + public void onFailure(String eMesg) { + setUserPanel(); + MaterialToast.showToast(eMesg); + if (!GalleryListActivity.this.isFinishing()) + loginDialog.show(); + } + + @SuppressLint("InflateParams") + private AlertDialog createLoginDialog() { + View view = ViewUtils.inflateDialogView(R.layout.login, false); + + return new MaterialAlertDialog.Builder(this).setCancelable(false).setTitle(R.string.login).setView(view, true) + .setPositiveButton(android.R.string.ok).setNegativeButton(android.R.string.cancel) + .setNeutralButton(R.string.register).setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + switch (which) { + case MaterialAlertDialog.POSITIVE: + setUserPanel(WAIT); + String username = ((EditText) loginDialog.findViewById(R.id.username)).getText().toString(); + String password = ((EditText) loginDialog.findViewById(R.id.password)).getText().toString(); + mClient.login(username, password, GalleryListActivity.this); + return true; + case MaterialAlertDialog.NEGATIVE: + setUserPanel(); + return true; + case MaterialAlertDialog.NEUTRAL: + toRegister(); + return false; + default: + return false; + } + } + }).create(); + } + + @SuppressLint("InflateParams") + private AlertDialog createModeDialog() { + View cv = ViewUtils.inflateDialogView(R.layout.select_mode, false); + + final Spinner modeSpinner = (Spinner) cv.findViewById(R.id.mode_list); + modeSpinner.setSelection(Config.getMode()); + final Spinner apiModeSpinner = (Spinner) cv.findViewById(R.id.api_mode_list); + apiModeSpinner.setSelection(Config.getApiMode()); + final Spinner lofiResolutionSpinner = (Spinner) cv.findViewById(R.id.lofi_resolution_list); + lofiResolutionSpinner.setSelection(Config.getLofiResolution() - 1); + + return new MaterialAlertDialog.Builder(this).setTitle(R.string.mode).setView(cv, true) + .setPositiveButton(android.R.string.ok).setNegativeButton(android.R.string.cancel) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + if (which == MaterialAlertDialog.POSITIVE) { + Config.setMode(modeSpinner.getSelectedItemPosition()); + Config.setApiMode(apiModeSpinner.getSelectedItemPosition()); + Config.setLofiResolution(lofiResolutionSpinner.getSelectedItemPosition() + 1); + } + return true; + } + }).create(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == RESULT_LOAD_SEARCH_IMAGE && resultCode == RESULT_OK && null != data) { + Uri selectedImage = data.getData(); + String[] filePathColumn = { MediaStore.Images.Media.DATA }; + + Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null); + cursor.moveToFirst(); + + int columnIndex = cursor.getColumnIndex(filePathColumn[0]); + String ImagePath = cursor.getString(columnIndex); + cursor.close(); + + mSearchImageText.setVisibility(View.VISIBLE); + mSearchImageText.setText(ImagePath); + } + + } + + private void handleSearchView(View view) { + final SuggestionTextView pet = (SuggestionTextView) view.findViewById(R.id.search_text); + pet.setSuggestionHelper(mSuggestions); + CheckBox uploaderCb = (CheckBox) view.findViewById(R.id.checkbox_uploader); + uploaderCb.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + pet.setPrefix("uploader:"); + pet.setSuggestionHelper(null); + } else { + pet.setPrefix(null); + pet.setSuggestionHelper(mSuggestions); + } + } + }); + + final View advance = view.findViewById(R.id.filter_advance); + CheckBox cb = (CheckBox) view.findViewById(R.id.checkbox_advance); + cb.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) + advance.setVisibility(View.VISIBLE); + else + advance.setVisibility(View.GONE); + } + }); + + final View selectImage = view.findViewById(R.id.select_image); + selectImage.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent i = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); + startActivityForResult(i, RESULT_LOAD_SEARCH_IMAGE); + } + }); + } + + @SuppressLint("InflateParams") + private AlertDialog createSearchDialog() { + final View view = ViewUtils.inflateDialogView(R.layout.search, false); + final View searchNormal = view.findViewById(R.id.search_normal); + final View searchTag = view.findViewById(R.id.search_tag); + final View searchImage = view.findViewById(R.id.search_image); + mSearchImageText = (TextView) view.findViewById(R.id.target_image); + handleSearchView(view); + + return new MaterialAlertDialog.Builder(this).setTitle(android.R.string.search_go).setView(view, true) + .setActionButton(R.string.mode).setPositiveButton(android.R.string.ok) + .setNegativeButton(android.R.string.cancel).setNeutralButton(R.string.add) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(final MaterialAlertDialog dialog, int which) { + switch (which) { + case MaterialAlertDialog.ACTION: + if (searchNormal.getVisibility() == View.VISIBLE) { + searchNormal.setVisibility(View.GONE); + searchTag.setVisibility(View.VISIBLE); + } else if (searchTag.getVisibility() == View.VISIBLE) { + searchTag.setVisibility(View.GONE); + searchImage.setVisibility(View.VISIBLE); + } else if (searchImage.getVisibility() == View.VISIBLE) { + searchImage.setVisibility(View.GONE); + searchNormal.setVisibility(View.VISIBLE); + } + return false; + case MaterialAlertDialog.POSITIVE: + ListUrls listUrls = getLus(mSearchDialog); + if (listUrls == null) + return true; + + lus = listUrls; + mGalleryListView.refresh(); + mDrawerLayout.closeDrawers(); + + // Get title + String search = lus.getSearch(); + switch (lus.getMode()) { + case ListUrls.MODE_IMAGE_SEARCH: + mTitle = getString(R.string.image_search); + break; + case ListUrls.MODE_UPLOADER: + mTitle = search; + break; + case ListUrls.MODE_TAG: + mTitle = lus.getTag(); + break; + case ListUrls.MODE_POPULAR: + mTitle = getString(R.string.whatshot); + case ListUrls.MODE_NORMAL: + default: + if (search == null || search.isEmpty()) + mTitle = getString(android.R.string.search_go); + else + mTitle = search; + break; + } + setTitle(mTitle); + return true; + case MaterialAlertDialog.NEUTRAL: + if (searchImage.getVisibility() == View.VISIBLE) + return false; + createSetNameDialog(null, null, new OnSetNameListener() { + @Override + public void onSetVaildName(String newName) { + mData.addTag(new Tag(newName, getLus(mSearchDialog))); + listMenuTag.add(newName); + tagsAdapter.addId(newName); + tagsAdapter.notifyDataSetChanged(); + + dialog.dismiss(); + } + }).show(); + return false; + default: + return true; + } + } + }).create(); + } + + private ListUrls getLus(AlertDialog dialog) { + return getLus(dialog.findViewById(R.id.custom)); + } + + private ListUrls getLus(View view) { + ListUrls lus = null; + + View searchNormal = view.findViewById(R.id.search_normal); + View searchTag = view.findViewById(R.id.search_tag); + View searchImage = view.findViewById(R.id.search_image); + + if (searchNormal.getVisibility() == View.VISIBLE) { + int type; + CategoryTable ct = (CategoryTable) view.findViewById(R.id.category_table); + type = ct.getCategory(); + EditText et = (EditText) view.findViewById(R.id.search_text); + lus = new ListUrls(type, et.getText().toString()); + + CheckBox uploaderCb = (CheckBox) view.findViewById(R.id.checkbox_uploader); + if (uploaderCb.isChecked()) { + lus.setMode(ListUrls.MODE_UPLOADER); + } + + CheckBox cb = (CheckBox) view.findViewById(R.id.checkbox_advance); + if (cb.isChecked()) { + CheckBox checkImageSname = (CheckBox) view.findViewById(R.id.checkbox_sname); + CheckBox checkImageStags = (CheckBox) view.findViewById(R.id.checkbox_stags); + CheckBox checkImageSdesc = (CheckBox) view.findViewById(R.id.checkbox_sdesc); + CheckBox checkImageStorr = (CheckBox) view.findViewById(R.id.checkbox_storr); + CheckBox checkImageSto = (CheckBox) view.findViewById(R.id.checkbox_sto); + CheckBox checkImageSdt1 = (CheckBox) view.findViewById(R.id.checkbox_sdt1); + CheckBox checkImageSdt2 = (CheckBox) view.findViewById(R.id.checkbox_sdt2); + CheckBox checkImageSh = (CheckBox) view.findViewById(R.id.checkbox_sh); + + int advType = 0; + if (checkImageSname.isChecked()) + advType |= ListUrls.SNAME; + if (checkImageStags.isChecked()) + advType |= ListUrls.STAGS; + if (checkImageSdesc.isChecked()) + advType |= ListUrls.SDESC; + if (checkImageStorr.isChecked()) + advType |= ListUrls.STORR; + if (checkImageSto.isChecked()) + advType |= ListUrls.STO; + if (checkImageSdt1.isChecked()) + advType |= ListUrls.STD1; + if (checkImageSdt2.isChecked()) + advType |= ListUrls.STD2; + if (checkImageSh.isChecked()) + advType |= ListUrls.SH; + CheckBox checkImageSr = (CheckBox) view.findViewById(R.id.checkbox_sr); + if (checkImageSr.isChecked()) { + Spinner spinnerMinRating = (Spinner) view.findViewById(R.id.spinner_min_rating); + lus.setAdvance(advType, spinnerMinRating.getSelectedItemPosition() + 2); + } else + lus.setAdvance(advType); + } + + // For tag search + } else if (searchTag.getVisibility() == View.VISIBLE) { + EditText et = (EditText) view.findViewById(R.id.search_tag_text); + lus = new ListUrls(); + lus.setTag(et.getText().toString()); + + // For image search + } else if (searchImage.getVisibility() == View.VISIBLE) { + String filePath = (String) mSearchImageText.getText(); + File file = new File(filePath); + if (!file.exists()) { + MaterialToast.showToast(R.string.image_not_exist); + } else { + if (!file.canRead()) { + MaterialToast.showToast(R.string.image_not_readable); + } else { + + CheckBox similar = (CheckBox) view.findViewById(R.id.checkboxSimilar); + CheckBox covers = (CheckBox) view.findViewById(R.id.checkboxCovers); + CheckBox exp = (CheckBox) view.findViewById(R.id.checkboxExp); + + lus = new ListUrls(); + lus.setSearchFile(file, (similar.isChecked() ? EhClient.IMAGE_SEARCH_USE_SIMILARITY_SCAN : 0) + | (covers.isChecked() ? EhClient.IMAGE_SEARCH_ONLY_SEARCH_COVERS : 0) + | (exp.isChecked() ? EhClient.IMAGE_SEARCH_SHOW_EXPUNGED : 0)); + } + } + } + + return lus; + } + + private AlertDialog createLongClickDialog() { + return new MaterialAlertDialog.Builder(this).setTitle(R.string.what_to_do) + .setItems(R.array.list_item_long_click, new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int position) { + final GalleryInfo gi = mGalleryListView.getGalleryInfo(longClickItemIndex); + switch (position) { + case 0: // Add favourite item + Favorite.addToFavorite(GalleryListActivity.this, gi); + break; + case 1: + Intent it = new Intent(GalleryListActivity.this, DownloadService.class); + it.setAction(DownloadService.ACTION_ADD); + it.putExtra(DownloadService.KEY_GALLERY_INFO, gi); + startService(it); + MaterialToast.showToast(R.string.toast_add_download); + break; + } + return true; + } + }).setNegativeButton(android.R.string.cancel).create(); + } + + @SuppressLint("InflateParams") + private AlertDialog createJumpDialog() { + View view = ViewUtils.inflateDialogView(R.layout.jump, false); + TextView tv = (TextView) view.findViewById(R.id.list_jump_sum); + // For lofi, can not get page num, so use Integer.MAX_VALUE + tv.setText(String.format(getString(R.string.jump_summary), mGalleryListView.getCurPage() + 1, + mGalleryListView.getPageNum() == Integer.MAX_VALUE ? getString(R.string._unknown) : + String.valueOf(mGalleryListView.getPageNum()))); + tv = (TextView) view.findViewById(R.id.list_jump_to); + tv.setText(R.string.jump_to); + final EditText et = (EditText) view.findViewById(R.id.list_jump_edit); + + return new MaterialAlertDialog.Builder(this).setTitle(R.string.jump).setView(view, true) + .setPositiveButton(android.R.string.ok).setNegativeButton(android.R.string.cancel) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + if (which == MaterialAlertDialog.POSITIVE) { + int targetPage; + try { + targetPage = Integer.parseInt(et.getText().toString()) - 1; + if (targetPage < 0 || targetPage >= mGalleryListView.getPageNum()) + throw new Exception(); + } catch (Exception e) { + MaterialToast.showToast(R.string.toast_invalid_page); + return false; + } + mGalleryListView.jumpTo(targetPage); + return true; + } else { + return true; + } + } + }).create(); + } + + private interface OnSetNameListener { + public void onSetVaildName(String newName); + } + + /** + * Create a set name dialog + * + * @param hint + * Text to set in edittext first + * @param oldStr + * string can be oldstr, even it is in listMenuTitle + * @param listener + * what to do when set right text + */ + @SuppressLint("InflateParams") + private AlertDialog createSetNameDialog(final String hint, final String oldStr, final OnSetNameListener listener) { + View view = ViewUtils.inflateDialogView(R.layout.set_name, false); + final EditText et = (EditText) view.findViewById(R.id.set_name_edit); + if (hint != null) + et.setText(hint); + + return new MaterialAlertDialog.Builder(this).setTitle(R.string.add_tag).setView(view, true) + .setPositiveButton(android.R.string.ok).setNegativeButton(android.R.string.cancel) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + if (which == MaterialAlertDialog.POSITIVE) { + String key = et.getText().toString(); + if (key.length() == 0) { + MaterialToast.showToast(R.string.invalid_input); + return false; + } else if (listMenuTag.contains(key) && !key.equals(oldStr)) { + MaterialToast.showToast("该名称已存在"); // TODO + return false; + } else { + if (listener != null) { + listener.onSetVaildName(key); + } + return true; + } + } else { + return true; + } + } + }).create(); + } + + @SuppressLint("InflateParams") + private AlertDialog createModifyTagDialog(final int position) { + final View view = ViewUtils.inflateDialogView(R.layout.search, false); + final View searchNormal = view.findViewById(R.id.search_normal); + final View searchTag = view.findViewById(R.id.search_tag); + final View searchImage = view.findViewById(R.id.search_image); + handleSearchView(view); + + ListUrls listUrls = mData.getTag(position); + setFilterView(view, listUrls); + + return new MaterialAlertDialog.Builder(this).setTitle(listMenuTag.get(position)).setView(view, true) + .setActionButton(R.string.mode).setPositiveButton(android.R.string.ok) + .setNegativeButton(android.R.string.cancel).setNeutralButton(R.string.tag_change_name) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(final MaterialAlertDialog dialog, int which) { + switch (which) { + case MaterialAlertDialog.ACTION: + if (searchNormal.getVisibility() == View.GONE) { + searchNormal.setVisibility(View.VISIBLE); + searchTag.setVisibility(View.GONE); + searchImage.setVisibility(View.GONE); + } else { + searchNormal.setVisibility(View.GONE); + searchTag.setVisibility(View.VISIBLE); + searchImage.setVisibility(View.GONE); + } + return false; + case MaterialAlertDialog.POSITIVE: + ListUrls listUrls = getLus(view); + if (newTagName != null) { + mData.setTag(position, new Tag(newTagName, listUrls)); + tagsAdapter.set(listMenuTag.get(position), newTagName); + listMenuTag.set(position, newTagName); + tagsAdapter.notifyDataSetChanged(); + newTagName = null; + } else { + mData.setTag(position, new Tag(listMenuTag.get(position), listUrls)); + } + return true; + case MaterialAlertDialog.NEUTRAL: + String hint = newTagName == null ? listMenuTag.get(position) : newTagName; + createSetNameDialog(hint, listMenuTag.get(position), new OnSetNameListener() { + @Override + public void onSetVaildName(String newName) { + if (newName.equals(listMenuTag.get(position))) { // If + // new + // is + // old + // name + dialog.setTitle(listMenuTag.get(position)); + } else { + newTagName = newName; + dialog.setTitle(String.format(getString(R.string.new_tag_name), newTagName)); + } + } + }).show(); + return false; + case MaterialAlertDialog.NEGATIVE: + default: + return true; + } + } + }).create(); + } + + private void setFilterView(View view, ListUrls listUrls) { + + View searchNormal = view.findViewById(R.id.search_normal); + View searchTag = view.findViewById(R.id.search_tag); + + if (listUrls.getMode() == ListUrls.MODE_TAG) { + searchNormal.setVisibility(View.GONE); + searchTag.setVisibility(View.VISIBLE); + + EditText et = (EditText) view.findViewById(R.id.search_tag_text); + et.setText(listUrls.getTag()); + } else { + searchNormal.setVisibility(View.VISIBLE); + searchTag.setVisibility(View.GONE); + + int category = listUrls.getCategory(); + CategoryTable ct = (CategoryTable) view.findViewById(R.id.category_table); + ct.setCategory(category); + + EditText et = (EditText) view.findViewById(R.id.search_text); + et.setText(listUrls.getSearch()); + + // Advance + CheckBox cb = (CheckBox) view.findViewById(R.id.checkbox_advance); + cb.setChecked(listUrls.isAdvance()); + + CheckBox checkImageSname = (CheckBox) view.findViewById(R.id.checkbox_sname); + CheckBox checkImageStags = (CheckBox) view.findViewById(R.id.checkbox_stags); + CheckBox checkImageSdesc = (CheckBox) view.findViewById(R.id.checkbox_sdesc); + CheckBox checkImageStorr = (CheckBox) view.findViewById(R.id.checkbox_storr); + CheckBox checkImageSto = (CheckBox) view.findViewById(R.id.checkbox_sto); + CheckBox checkImageSdt1 = (CheckBox) view.findViewById(R.id.checkbox_sdt1); + CheckBox checkImageSdt2 = (CheckBox) view.findViewById(R.id.checkbox_sdt2); + CheckBox checkImageSh = (CheckBox) view.findViewById(R.id.checkbox_sh); + + int advType = listUrls.getAdvanceType(); + if (advType != -1) { + if ((advType & ListUrls.SNAME) == 0) + checkImageSname.setChecked(false); + else + checkImageSname.setChecked(true); + if ((advType & ListUrls.STAGS) == 0) + checkImageStags.setChecked(false); + else + checkImageStags.setChecked(true); + if ((advType & ListUrls.SDESC) == 0) + checkImageSdesc.setChecked(false); + else + checkImageSdesc.setChecked(true); + if ((advType & ListUrls.STORR) == 0) + checkImageStorr.setChecked(false); + else + checkImageStorr.setChecked(true); + if ((advType & ListUrls.STO) == 0) + checkImageSto.setChecked(false); + else + checkImageSto.setChecked(true); + if ((advType & ListUrls.STD1) == 0) + checkImageSdt1.setChecked(false); + else + checkImageSdt1.setChecked(true); + if ((advType & ListUrls.STD2) == 0) + checkImageSdt2.setChecked(false); + else + checkImageSdt2.setChecked(true); + if ((advType & ListUrls.SH) == 0) + checkImageSh.setChecked(false); + else + checkImageSh.setChecked(true); + + // MinRating + CheckBox checkImageSr = (CheckBox) view.findViewById(R.id.checkbox_sr); + if (listUrls.isMinRating()) + checkImageSr.setChecked(true); + else + checkImageSr.setChecked(false); + Spinner spinnerMinRating = (Spinner) view.findViewById(R.id.spinner_min_rating); + int index; + if (listUrls.getMinRating() == -1) + index = 0; + else + index = listUrls.getMinRating() - 2; + spinnerMinRating.setSelection(index); + } + + // Show advance if need + final View advance = view.findViewById(R.id.filter_advance); + if (listUrls.isAdvance()) + advance.setVisibility(View.VISIBLE); + } + } + + @Override + protected void onResume() { + super.onResume(); + mGalleryListView.setListMode(Config.getListMode()); + mGalleryListView.updateSpanCount(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mGalleryListView.updateSpanCount(); + } + + /** + * Get + * + * @param intent + */ + private void handleIntent(Intent intent) { + String action = intent != null ? intent.getAction() : null; + if (Intent.ACTION_SEARCH.equals(action)) { + String query = intent.getStringExtra(SearchManager.QUERY); + if (mSearchView != null && query != null) + mSearchView.setQuery(query, true); + } else if (ACTION_GALLERY_LIST.equals(action)) { + int mode = intent.getIntExtra(GalleryListActivity.KEY_MODE, -1); + switch (mode) { + case ListUrls.MODE_TAG: + lus = new ListUrls(); + String tag = intent.getStringExtra(KEY_TAG); + lus.setTag(tag); + mTitle = tag; + setTitle(mTitle); + break; + + case ListUrls.MODE_UPLOADER: + String uploader = "uploader:" + intent.getStringExtra(KEY_UPLOADER); + lus = new ListUrls(ListUrls.ALL_CATEGORT, uploader); + lus.setMode(ListUrls.MODE_UPLOADER); + mTitle = uploader; + setTitle(mTitle); + break; + + case ListUrls.MODE_IMAGE_SEARCH: + lus = new ListUrls(); + lus.setSearchImage(intent.getStringExtra(KEY_IMAGE_KEY), intent.getStringExtra(KEY_IMAGE_URL), + EhClient.IMAGE_SEARCH_USE_SIMILARITY_SCAN); + mTitle = getString(R.string.similar_content); + setTitle(mTitle); + break; + + case ListUrls.MODE_NORMAL: + // Target is category + int category = intent.getIntExtra(KEY_CATEGORY, ListUrls.ALL_CATEGORT); + lus = new ListUrls(category); + mTitle = Ui.getCategoryText(category); + setTitle(mTitle); + break; + + default: + // Get error, go to home page + lus = new ListUrls(); + mTitle = mResources.getString(R.string.homepage); + setTitle(mTitle); + break; + } + } else { + lus = new ListUrls(); + mTitle = mResources.getString(R.string.homepage); + setTitle(mTitle); + } + } + + @Override + protected void onNewIntent(Intent intent) { + setIntent(intent); + handleIntent(intent); + mGalleryListView.refresh(); + } + + @Override + public void onClick(View v) { + if (v == mLoginButton) { + loginDialog.show(); + } else if (v == mRegisterButton) { + toRegister(); + } else if (v == mLogoutButton) { + mClient.logout(); + setUserPanel(); + } + } + + @Override + public void onDrawerClosed(View view) { + setTitle(mTitle); + invalidateOptionsMenu(); + + if (view == mLeftMenu) + mDirection = false; + } + + @Override + public void onDrawerOpened(View view) { + if (view == mLeftMenu) + setTitle(R.string.app_name); + invalidateOptionsMenu(); + + if (view == mLeftMenu) + mDirection = true; + } + + @Override + public void onDrawerSlide(View view, float f) { + if (view == mLeftMenu) { + mMaterialIndicator.setTransformationOffset( + MaterialIndicatorDrawable.AnimationState.BURGER_ARROW, + mDirection ? 2 - f : f); + } + } + + @Override + public void onDrawerStateChanged(View drawerView, int newState) { + // Empty + } + + @Override + public void onFitSystemWindows(int l, int t, int r, int b) { + mGalleryListView.setPaddingTopAndBottom(t, b); + ((SlidingDrawerLayout.LayoutParams) mLeftMenu.getLayoutParams()).topMargin = t; + ((SlidingDrawerLayout.LayoutParams) mRightMenu.getLayoutParams()).topMargin = t; + mMenuList.setPadding(mMenuList.getPaddingLeft(), mMenuList.getPaddingTop(), + mMenuList.getPaddingRight(), b); + mQuickSearchList.setPadding(mQuickSearchList.getPaddingLeft(), mQuickSearchList.getPaddingTop(), + mQuickSearchList.getPaddingRight(), b); + + Ui.colorStatusBarKK(this, mThemeColor, t - Ui.ACTION_BAR_HEIGHT); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + mMaterialIndicator.syncState(savedInstanceState); + } + + @Override protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mMaterialIndicator.onSaveInstanceState(outState); + } + + @Override + public boolean onItemClick(EasyRecyclerView parent, View view, + int position, long id) { + Intent intent = new Intent(GalleryListActivity.this, GalleryDetailActivity.class); + GalleryInfo gi = mGalleryListView.getGalleryInfo(position); + intent.putExtra(GalleryDetailActivity.KEY_G_INFO, gi); + startActivity(intent); + return true; + } + + @Override + public boolean onItemLongClick(EasyRecyclerView parent, View view, + int position, long id) { + longClickItemIndex = position; + longClickDialog.show(); + return true; + } + + @SuppressWarnings("deprecation") + @Override + @SuppressLint("InflateParams") + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.gallery_list); + + mAppContext = (AppContext) getApplication(); + mData = Data.getInstance(); + mClient = EhClient.getInstance(); + mResources = getResources(); + mWindowsAnimate = new WindowsAnimate(); + mWindowsAnimate.init(this); + mSuggestions = SuggestionHelper.getInstance(GalleryListActivity.this, SimpleSuggestionProvider.AUTHORITY, + SimpleSuggestionProvider.MODE); + + handleIntent(getIntent()); + // Check show drawer or not + if (ACTION_GALLERY_LIST.equals(getIntent().getAction())) + mShowDrawer = false; + else + mShowDrawer = true; + + // Init dialog + loginDialog = createLoginDialog(); + mSearchDialog = createSearchDialog(); + longClickDialog = createLongClickDialog(); + + ColorDrawable cd = new ColorDrawable(Color.WHITE); + cd.setBounds(0, 0, Ui.dp2pix(32), Ui.dp2pix(32)); + this. + + // Menu + mMaterialIndicator = new MaterialIndicatorDrawable(this, Color.WHITE, Stroke.THIN); + if (mShowDrawer) + mMaterialIndicator.setIconState(MaterialIndicatorDrawable.IconState.BURGER); + else + mMaterialIndicator.setIconState(MaterialIndicatorDrawable.IconState.ARROW); + mActionBar = getActionBar(); + Ui.setMaterialIndicator(mActionBar, mMaterialIndicator); + + // Get View + mDrawerLayout = (SlidingDrawerLayout) findViewById(R.id.drawerlayout); + + mLeftMenu = mDrawerLayout.findViewById(R.id.list_menu_left); + mRightMenu = mDrawerLayout.findViewById(R.id.list_menu_right); + mStandard = (FitWindowView) mDrawerLayout.findViewById(R.id.standard); + mGalleryListView = (GalleryListView) mDrawerLayout.findViewById(R.id.gallery_list); + + mUserPanel = mLeftMenu.findViewById(R.id.user_panel); + mMenuList = (DrawerListView) mLeftMenu.findViewById(R.id.list_menu_item_list); + mAvatar = (ImageView) mUserPanel.findViewById(R.id.avatar); + mUsernameView = (TextView) mUserPanel.findViewById(R.id.user); + mLoginButton = (Button) mUserPanel.findViewById(R.id.login); + mRegisterButton = (Button) mUserPanel.findViewById(R.id.register); + mLogoutButton = (Button) mUserPanel.findViewById(R.id.logout); + mWaitLogView = mUserPanel.findViewById(R.id.wait); + + mRightDrawerBg = mRightMenu.findViewById(R.id.background); + mQuickSearchList = (TagListView) mRightMenu.findViewById(R.id.list_menu_tag_list); + mQuickSearchTip = mRightMenu.findViewById(R.id.tip_text); + + mLoginButton.setOnClickListener(this); + mRegisterButton.setOnClickListener(this); + mLogoutButton.setOnClickListener(this); + + // Drawer + mDrawerLayout.setDrawerListener(this); + if (!mShowDrawer) + mDrawerLayout.setDrawerLockMode(SlidingDrawerLayout.LOCK_MODE_LOCKED_CLOSED, mLeftMenu); + + // Content + mGalleryListView.setGalleryListViewHelper(this); + mStandard.addOnFitSystemWindowsListener(this); + + // leftDrawer + mUserPanel.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + mUserPanel.setBackgroundDrawable(new FreeMaterialDrawable()); + + final Drawable[] drawableArray = new Drawable[] { + mResources.getDrawable(R.drawable.ic_drawer_home), + mResources.getDrawable(R.drawable.ic_drawer_panda), + mResources.getDrawable(R.drawable.ic_drawer_search), + mResources.getDrawable(R.drawable.ic_drawer_favorite), + mResources.getDrawable(R.drawable.ic_drawer_whatshot), + mResources.getDrawable(R.drawable.ic_drawer_history), + mResources.getDrawable(R.drawable.ic_drawer_download), + mResources.getDrawable(R.drawable.ic_drawer_settings), + }; + final CharSequence[] titleArray = new CharSequence[] { + mResources.getString(R.string.homepage), + mResources.getString(R.string.mode), + mResources.getString(android.R.string.search_go), + mResources.getString(R.string.favourite), + mResources.getString(R.string.whatshot), + mResources.getString(R.string.history), + mResources.getString(R.string.download), + mResources.getString(R.string.action_settings) + }; + + mMenuList.setClipToPadding(false); + mMenuList.setData(drawableArray, titleArray); + mMenuList.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + Intent intent; + switch (position) { + case 0: // Home page + lus = new ListUrls(ListUrls.NONE, null, 0); + mGalleryListView.refresh(); + mDrawerLayout.closeDrawers(); + mTitle = mResources.getString(R.string.homepage); + setTitle(mTitle); + break; + + case 1: // Mode + createModeDialog().show(); + break; + + case 2: // Search + mSearchDialog.show(); + break; + + case 3: // Favourite + intent = new Intent(GalleryListActivity.this, FavouriteActivity.class); + startActivity(intent); + break; + + case 4: // Popular + lus = new ListUrls(); + lus.setMode(ListUrls.MODE_POPULAR); + mTitle = mResources.getString(R.string.whatshot); + setTitle(mTitle); + mGalleryListView.refresh(); + mDrawerLayout.closeDrawers(); + break; + + case 5: // HistoryActivity + intent = new Intent(GalleryListActivity.this, HistoryActivity.class); + startActivity(intent); + break; + + case 6: // Download + intent = new Intent(GalleryListActivity.this, DownloadActivity.class); + startActivity(intent); + break; + + case 7: // Settings + intent = new Intent(GalleryListActivity.this, SettingsActivity.class); + startActivity(intent); + break; + } + } + }); + + mRightDrawerBg.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + mRightDrawerBg.setBackgroundDrawable(new FreeMaterialDrawable().setBgIndex(2).setKeepTop(true)); + + List keys = mData.getAllTagNames(); + for (int i = 0; i < keys.size(); i++) + listMenuTag.add(keys.get(i)); + tagsAdapter = new TagsAdapter(this, R.layout.menu_tag, listMenuTag); + tagsAdapter.setOnDataSetChangedListener(new TagsAdapter.OnDataSetChangedListener() { + @Override + public void OnDataSetChanged() { + if (listMenuTag.size() == 0) + mQuickSearchTip.setVisibility(View.VISIBLE); + else + mQuickSearchTip.setVisibility(View.GONE); + } + }); + if (listMenuTag.size() == 0) + mQuickSearchTip.setVisibility(View.VISIBLE); + else + mQuickSearchTip.setVisibility(View.GONE); + + mQuickSearchList.setClipToPadding(false); + mQuickSearchList.setAdapter(tagsAdapter); + mQuickSearchList.setChoiceMode(ListView.CHOICE_MODE_SINGLE); + mQuickSearchList.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView arg0, View arg1, int position, long arg3) { + lus = mData.getTag(position); + mGalleryListView.refresh(); + mDrawerLayout.closeDrawers(); + mTitle = listMenuTag.get(position); + setTitle(mTitle); + } + }); + mQuickSearchList.setOnModifyListener(new TagListView.OnModifyListener() { + @Override + public void onModify(int position) { + createModifyTagDialog(position).show(); + } + }); + mQuickSearchList.setOnSwapListener(new TagListView.OnSwapListener() { + @Override + public void onSwap(int positionOne, int positionTwo) { + String temp = listMenuTag.get(positionOne); + mData.swapTag(positionOne, positionTwo); + listMenuTag.set(positionOne, listMenuTag.get(positionTwo)); + listMenuTag.set(positionTwo, temp); + tagsAdapter.notifyDataSetChanged(); + } + }); + mQuickSearchList.setOnDeleteListener(new TagListView.OnDeleteListener() { + @Override + public void onDelete(int position) { + String removeItem = listMenuTag.remove(position); + mData.deleteTag(position); + tagsAdapter.remove(removeItem); + tagsAdapter.notifyDataSetChanged(); + } + }); + + // Listview + mGalleryListView.setOnItemClickListener(this); + mGalleryListView.setOnItemLongClickListener(this); + + // Set random color + mThemeColor = Config.getCustomThemeColor() ? Config.getThemeColor() : Ui.THEME_COLOR; + mActionBar.setBackgroundDrawable(new ColorDrawable(mThemeColor)); + Ui.colorStatusBarL(this, mThemeColor); + + // Update user panel + setUserPanel(); + + // get MangeList + mGalleryListView.firstTimeRefresh(); + + // If not show drawer, just return + if (!mShowDrawer) { + return; + } + + // Check update + if (Config.isAutoCheckForUpdate()) + checkUpdate(); + + String keyFirstTime = "first_time"; + if (Config.getBoolean(keyFirstTime, true)) { + Config.setBoolean(keyFirstTime, false); + + // Show left menu, Can't invoke showMenu() immediately + Message m = Message.obtain(null, new Runnable() { + @Override + public void run() { + mDrawerLayout.openDrawer(mLeftMenu); + } + }); + AppHandler.getInstance().sendMessageDelayed(m, 500L); + } + + String keyTcWarning = "traditional_chinese_warning"; + String country = Locale.getDefault().getCountry(); + if (Config.getBoolean(keyTcWarning, true) && (country.equals("HK") || country.equals("TW"))) { + Config.setBoolean(keyTcWarning, false); + new MaterialAlertDialog.Builder(this).setTitle("注意") + .setMessage("正體中文的翻譯由 OpenCC 自動完成,若有任何錯誤或不妥之處歡迎指出。") + .show(); + } + } + + @Override + public boolean onQueryTextSubmit(String query) { + // Store suggestion + mSuggestions.saveRecentQuery(query, null); + // collapse search view + if (mSearchItem != null) + mSearchItem.collapseActionView(); + + String t = null; + if (query == null || query.isEmpty()) + t = getString(android.R.string.search_go); + else + t = query; + + lus = new ListUrls(ListUrls.NONE, query); + mGalleryListView.refresh(); + mTitle = t; + setTitle(mTitle); + return true; + } + + @Override + public boolean onQueryTextChange(String newText) { + return false; + } + + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus && mSearchItem != null) + mSearchItem.collapseActionView(); + } + + @Override + public void onActionViewExpanded() { + if (mSearchView != null && lus != null) + mSearchView.setQuery(lus.toString(), false); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + if (mDrawerLayout.isDrawerOpen(mRightMenu)) + getMenuInflater().inflate(R.menu.quick_search, menu); + else if (mDrawerLayout.isDrawerOpen(mLeftMenu)) + return true; + else + getMenuInflater().inflate(R.menu.gallery_list, menu); + + SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); + mSearchItem = menu.findItem(R.id.action_search); + mSearchView = (SearchView) mSearchItem.getActionView(); + mSearchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); + mSearchView.setOnQueryTextListener(this); + mSearchView.setOnQueryTextFocusChangeListener(this); + mSearchView.setOnActionViewExpandedListener(this); + + // Make search view custom look + int searchTextID = mResources.getIdentifier("android:id/search_src_text", null, null); + AutoCompleteTextView searchText = null; + if (searchTextID > 0 && (searchText = (AutoCompleteTextView) mSearchView.findViewById(searchTextID)) != null) { + + int searchCursorID = mResources.getIdentifier("android:drawable/text_cursor_holo_dark", null, null); + if (searchCursorID > 0) { + try { + Field f = Class.forName("android.widget.TextView").getDeclaredField("mCursorDrawableRes"); + f.setAccessible(true); + f.setInt(searchText, searchCursorID); + } catch (Exception e) { + } + } + + searchText.setTextColor(Color.WHITE); + searchText.setHintTextColor(Color.WHITE); + + Drawable searchImage = mResources.getDrawable(R.drawable.ic_action_search); + if (searchImage != null) { + SpannableStringBuilder ssb = new SpannableStringBuilder(" "); + ssb.append(getString(R.string.advance_search_left)); + int textSize = (int) (searchText.getTextSize() * 1.25); + searchImage.setBounds(0, 0, textSize, textSize); + ssb.setSpan(new ImageSpan(searchImage), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + searchText.setHint(ssb); + } + } + + int plateViewID = mResources.getIdentifier("android:id/search_plate", null, null); + View plateView = null; + if (plateViewID > 0 && (plateView = mSearchView.findViewById(plateViewID)) != null) { + plateView.setBackgroundResource(R.drawable.textfield_searchview); + } + + int plateRightViewID = mResources.getIdentifier("android:id/submit_area", null, null); + View plateRightView = null; + if (plateRightViewID > 0 && (plateRightView = mSearchView.findViewById(plateRightViewID)) != null) { + plateRightView.setBackgroundResource(R.drawable.textfield_searchview_right); + } + + int closeViewID = mResources.getIdentifier("android:id/search_close_btn", null, null); + ImageView closeView = null; + Drawable closeImage = mResources.getDrawable(R.drawable.ic_action_close); + if (closeViewID > 0 && (closeView = (ImageView) mSearchView.findViewById(closeViewID)) != null + && closeImage != null) { + closeView.setImageDrawable(closeImage); + } + + int voiceViewID = mResources.getIdentifier("android:id/search_voice_btn", null, null); + ImageView voiceView = null; + Drawable voiceImage = mResources.getDrawable(R.drawable.ic_action_voice_search); + if (voiceViewID > 0 && (voiceView = (ImageView) mSearchView.findViewById(voiceViewID)) != null + && voiceImage != null) { + voiceView.setImageDrawable(voiceImage); + } + + // Hide some menu item + if (mGalleryListView.getListMode() == GalleryListView.LIST_MODE_DETAIL) + menu.removeItem(R.id.action_detail); + else if (mGalleryListView.getListMode() == GalleryListView.LIST_MODE_THUMB) + menu.removeItem(R.id.action_thumb); + + return true; + } + + // Double click back exit + @Override + public void onBackPressed() { + if (mDrawerLayout.isDrawerOpen(mLeftMenu) || mDrawerLayout.isDrawerOpen(mRightMenu)) { + mDrawerLayout.closeDrawers(); + } else { + if (!mShowDrawer) { + finish(); + } else { + if (System.currentTimeMillis() - curBackTime > BACK_PRESSED_INTERVAL) { + curBackTime = System.currentTimeMillis(); + MaterialToast.showToast(R.string.exit_tip); + } else + finish(); + } + } + } + + private void jump() { + createJumpDialog().show(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle item selection + switch (item.getItemId()) { + case android.R.id.home: + if (mShowDrawer) { + if (mDrawerLayout.isDrawerOpen(mLeftMenu)) + mDrawerLayout.closeDrawers(); + else + mDrawerLayout.openDrawer(mLeftMenu); + } else { + finish(); + } + return true; + case R.id.action_refresh: + mGalleryListView.refresh(); + return true; + case R.id.action_jump: + if (!mGalleryListView.isRefreshing() && mGalleryListView.isGetGalleryOk()) + jump(); + return true; + case R.id.action_detail: + mGalleryListView.setListMode(GalleryListView.LIST_MODE_DETAIL); + Config.setListMode(GalleryListView.LIST_MODE_DETAIL); + invalidateOptionsMenu(); + return true; + case R.id.action_thumb: + mGalleryListView.setListMode(GalleryListView.LIST_MODE_THUMB); + Config.setListMode(GalleryListView.LIST_MODE_THUMB); + invalidateOptionsMenu(); + return true; + case R.id.action_add: + // TODO + if (lus != null) { + View view = ViewUtils.inflateDialogView(R.layout.set_name, false); + final EditText et = (EditText) view.findViewById(R.id.set_name_edit); + new MaterialAlertDialog.Builder(this).setTitle("添加当前状态至快速搜索") // TODO + .setView(view, true) + .setDefaultButton(MaterialAlertDialog.POSITIVE | MaterialAlertDialog.NEGATIVE) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + if (which == MaterialAlertDialog.POSITIVE) { + String name = et.getText().toString(); + if (name == null || name.isEmpty()) { + MaterialToast.showToast(R.string.invalid_input); + return false; + } + if (listMenuTag.contains(name)) { + MaterialToast.showToast("该名称已存在"); // TODO + return false; + } + mData.addTag(new Tag(name, lus)); + listMenuTag.add(name); + tagsAdapter.addId(name); + tagsAdapter.notifyDataSetChanged(); + } + return true; + } + }).show(); + } + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + mWindowsAnimate.free(); + } + + private void checkUpdate() { + new UpdateHelper((AppContext) getApplication()).SetOnCheckUpdateListener( + new UpdateHelper.OnCheckUpdateListener() { + @Override + public void onSuccess(String version, long size, final String url, final String fileName, + String info) { + String sizeStr = Utils.sizeToString(size); + AlertDialog dialog = DialogUtils.createUpdateDialog(GalleryListActivity.this, version, sizeStr, + info, new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + if (which == MaterialAlertDialog.POSITIVE) { + HttpHelper hh = new HttpHelper(GalleryListActivity.this); + hh.downloadInThread(url, new File(Config.getDownloadPath()), fileName, + false, null, new UpdateHelper.UpdateListener( + GalleryListActivity.this, fileName)); + } + return true; + } + }).create(); + if (!GalleryListActivity.this.isFinishing()) + dialog.show(); + } + + @Override + public void onNoUpdate() { + UpdateHelper.setEnabled(true); + } + + @Override + public void onFailure(String eMsg) { + UpdateHelper.setEnabled(true); + } + }).autoCheckUpdate(); + } + + private static final int LOGIN = 0x0; + private static final int LOGOUT = 0x1; + private static final int WAIT = 0x2; + + private void setUserPanel() { + if (mClient.isLogin()) + setUserPanel(LOGOUT); + else + setUserPanel(LOGIN); + } + + private void setUserPanel(int state) { + + switch (state) { + case LOGIN: + mAvatar.setImageDrawable(mClient.getAvatar()); + mUsernameView.setVisibility(View.GONE); + mLoginButton.setVisibility(View.VISIBLE); + mRegisterButton.setVisibility(View.VISIBLE); + mLogoutButton.setVisibility(View.GONE); + mWaitLogView.setVisibility(View.GONE); + break; + case LOGOUT: + mAvatar.setImageDrawable(mClient.getAvatar()); + mUsernameView.setText(mClient.getDisplayname()); + mUsernameView.setVisibility(View.VISIBLE); + mLoginButton.setVisibility(View.GONE); + mRegisterButton.setVisibility(View.GONE); + mLogoutButton.setVisibility(View.VISIBLE); + mWaitLogView.setVisibility(View.GONE); + break; + case WAIT: + mAvatar.setImageDrawable(mClient.getAvatar()); + mUsernameView.setVisibility(View.GONE); + mLoginButton.setVisibility(View.GONE); + mRegisterButton.setVisibility(View.GONE); + mLogoutButton.setVisibility(View.GONE); + mWaitLogView.setVisibility(View.VISIBLE); + break; + } + } + + @Override + public String getTargetUrl(int targetPage) { + lus.setPage(targetPage); + return lus.getUrl(); + } + + @Override + public void doGetGallerys(String url, final long taskStamp, final OnGetListListener listener) { + if (lus.getMode() == ListUrls.MODE_IMAGE_SEARCH) { + if (url == null) { + // No result url + if (lus.getSearchFile() == null) { + ImageLoader.getInstance(GalleryListActivity.this).add(lus.getSearchImageUrl(), + lus.getSearchImageKey(), new ImageLoader.OnGetImageListener() { + @Override + public void onGetImage(String key, Bitmap bmp, int state) { + if (bmp != null) { + mClient.getGListFromImageSearch(bmp, lus.getImageSearchMode(), taskStamp, + new EhClient.OnGetGListFromImageSearchListener() { + @Override + public void onSuccess(Object checkFlag, List giList, + int maxPage, String newUrl) { + + System.out.println(giList.size()); + + lus.setSearchResult(newUrl); + listener.onSuccess(taskStamp, giList, maxPage); + } + + @Override + public void onFailure(Object checkFlag, String eMsg) { + listener.onFailure(taskStamp, eMsg); // TODO + } + }); + } else { + listener.onFailure(taskStamp, "~~~~~~~~~~"); // TODO + } + } + }); + } else { + // File search + mClient.getGListFromImageSearch(lus.getSearchFile(), lus.getImageSearchMode(), taskStamp, + new EhClient.OnGetGListFromImageSearchListener() { + @Override + public void onSuccess(Object checkFlag, List giList, int maxPage, + String newUrl) { + lus.setSearchResult(newUrl); + listener.onSuccess(taskStamp, giList, maxPage); + } + + @Override + public void onFailure(Object checkFlag, String eMsg) { + listener.onFailure(taskStamp, eMsg); + } + }); + } + } else { + // Get result url + mClient.getGList(url, null, new EhClient.OnGetGListListener() { + @Override + public void onSuccess(Object checkFlag, List lmdArray, int pageNum) { + listener.onSuccess(taskStamp, lmdArray, pageNum); + } + + @Override + public void onFailure(Object checkFlag, String eMsg) { + listener.onFailure(taskStamp, eMsg); + } + }); + } + } else if (lus.getMode() == ListUrls.MODE_POPULAR) { + mClient.getPopular(new EhClient.OnGetPopularListener() { + @Override + public void onSuccess(List gis, long timeStamp) { + listener.onSuccess(taskStamp, gis, gis.size() == 0 ? 0 : 1); + // Show update time + if (timeStamp != -1 && Config.getShowPopularUpdateTime()) + MaterialToast.showToast(String.format(getString(R.string.popular_update_time), + Utils.sDate.format(timeStamp))); + } + + @Override + public void onFailure(String eMsg) { + listener.onFailure(taskStamp, eMsg); + } + }); + } else { + mClient.getGList(url, null, new EhClient.OnGetGListListener() { + @Override + public void onSuccess(Object checkFlag, List lmdArray, int pageNum) { + listener.onSuccess(taskStamp, lmdArray, pageNum); + } + + @Override + public void onFailure(Object checkFlag, String eMsg) { + listener.onFailure(taskStamp, eMsg); + } + }); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/HistoryActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/HistoryActivity.java new file mode 100644 index 000000000..2a175e960 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/HistoryActivity.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ui; + +import java.util.ArrayList; +import java.util.List; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.CheckBox; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.app.MaterialAlertDialog; +import com.hippo.ehviewer.data.Data; +import com.hippo.ehviewer.data.GalleryInfo; +import com.hippo.ehviewer.drawable.MaterialIndicatorDrawable; +import com.hippo.ehviewer.drawable.MaterialIndicatorDrawable.Stroke; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.Ui; +import com.hippo.ehviewer.util.ViewUtils; +import com.hippo.ehviewer.widget.FitWindowView; +import com.hippo.ehviewer.widget.GalleryListView; +import com.hippo.ehviewer.widget.GalleryListView.OnGetListListener; +import com.hippo.ehviewer.widget.recyclerview.EasyRecyclerView; + +public class HistoryActivity extends AbsTranslucentActivity + implements EasyRecyclerView.OnItemClickListener, MaterialAlertDialog.OnClickListener, + GalleryListView.GalleryListViewHelper, FitWindowView.OnFitSystemWindowsListener { + + private static final String TAG = HistoryActivity.class.getSimpleName(); + + public static final int LIST_MODE_DETAIL = 0; + public static final int LIST_MODE_THUMB = 1; + + private Data mData; + private int mThemeColor; + + private FitWindowView mStandard; + private GalleryListView mGalleryListView; + + private int mFilterMode; + + private Dialog mFilterDialog; + private CheckBox mJustBrowse; + private CheckBox mHaveRead; + + private Dialog mClearDialog; + + private static final String HISTORY_URL = "ehviewer://history"; + private static final String KEY_HISTORY_FILTER = "history_filter"; + + private void setFilterDialogMode(int filterMode) { + if ((filterMode & Data.BROWSE) == 0) + mJustBrowse.setChecked(false); + else + mJustBrowse.setChecked(true); + if ((filterMode & Data.READ) == 0) + mHaveRead.setChecked(false); + else + mHaveRead.setChecked(true); + } + + private int getFilterDialogMode() { + int filterMode = 0; + filterMode |= mJustBrowse.isChecked() ? Data.BROWSE : 0; + filterMode |= mHaveRead.isChecked() ? Data.READ : 0; + return filterMode; + } + + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + if (dialog == mFilterDialog) { + switch (which) { + case MaterialAlertDialog.POSITIVE: + int newFilterMode = getFilterDialogMode(); + if (newFilterMode != mFilterMode) { + mFilterMode = newFilterMode; + Config.setInt(KEY_HISTORY_FILTER, mFilterMode); + mGalleryListView.refresh(); + } + } + return true; + + } else if (dialog == mClearDialog) { + switch (which) { + case MaterialAlertDialog.POSITIVE: + Data.getInstance().clearHistory(); + mGalleryListView.refresh(); + } + return true; + + } + return true; + } + + @SuppressLint("InflateParams") + private void createFilterDialog() { + View view = ViewUtils.inflateDialogView(R.layout.history_filter, false); + mJustBrowse = (CheckBox) view.findViewById(R.id.just_browse); + mHaveRead = (CheckBox) view.findViewById(R.id.have_read); + + mFilterDialog = new MaterialAlertDialog.Builder(this).setTitle(R.string.filter) + .setView(view, true) + .setDefaultButton(MaterialAlertDialog.POSITIVE | MaterialAlertDialog.NEGATIVE) + .setButtonListener(this).create(); + } + + private void createClearDialog() { + mClearDialog = new MaterialAlertDialog.Builder(this).setTitle(R.string.attention) + .setMessage(R.string.clear_history_message) + .setDefaultButton(MaterialAlertDialog.POSITIVE | MaterialAlertDialog.NEGATIVE) + .setButtonListener(this).create(); + } + + @Override + public void onFitSystemWindows(int l, int t, int r, int b) { + mGalleryListView.setPaddingTopAndBottom(t, b); + Ui.colorStatusBarKK(this, mThemeColor, t - Ui.ACTION_BAR_HEIGHT); + } + + @Override + protected void onResume() { + super.onResume(); + + mGalleryListView.refresh(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.history); + + mData = Data.getInstance(); + mFilterMode = Config.getInt(KEY_HISTORY_FILTER, Data.BROWSE | Data.READ); + + // Menu + MaterialIndicatorDrawable materialIndicator = new MaterialIndicatorDrawable(this, Color.WHITE, Stroke.THIN); + materialIndicator.setIconState(MaterialIndicatorDrawable.IconState.ARROW); + Ui.setMaterialIndicator(getActionBar(), materialIndicator); + + // Theme + mThemeColor = Config.getCustomThemeColor() ? Config.getThemeColor() : Ui.THEME_COLOR; + getActionBar().setBackgroundDrawable(new ColorDrawable(mThemeColor)); + Ui.colorStatusBarL(this, mThemeColor); + + mStandard = (FitWindowView) findViewById(R.id.standard); + mGalleryListView = (GalleryListView) findViewById(R.id.gallery_list); + + mGalleryListView.setGalleryListViewHelper(this); + mStandard.addOnFitSystemWindowsListener(this); + + // Do not need header an footer + mGalleryListView.setEnabledHeader(false); + mGalleryListView.setEnabledFooter(false); + mGalleryListView.setOnItemClickListener(this); + + createFilterDialog(); + createClearDialog(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.history, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + return true; + + case R.id.action_filter: + if (!mFilterDialog.isShowing()) { + setFilterDialogMode(mFilterMode); + mFilterDialog.show(); + } + return true; + + case R.id.action_clear: + if (!mClearDialog.isShowing()) + mClearDialog.show(); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public String getTargetUrl(int targetPage) { + return HISTORY_URL; + } + + @Override + public void doGetGallerys(String url, long taskStamp, + OnGetListListener listener) { + if (url.equals(HISTORY_URL)) { + List giList = new ArrayList(mData.getHistory(mFilterMode, true)); + if (giList == null || giList.size() == 0) + listener.onSuccess(taskStamp, giList, 0); + else + listener.onSuccess(taskStamp, giList, 1); + } + } + + @Override + public boolean onItemClick(EasyRecyclerView parent, View view, + int position, long id) { + Intent intent = new Intent(this, GalleryDetailActivity.class); + GalleryInfo gi = mGalleryListView.getGalleryInfo(position); + intent.putExtra(GalleryDetailActivity.KEY_G_INFO, gi); + startActivity(intent); + return true; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/SecretActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/SecretActivity.java new file mode 100644 index 000000000..9f3d82c7e --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/SecretActivity.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ui; + + +import java.util.Arrays; + +import android.graphics.Bitmap; +import android.os.Bundle; +import android.os.SystemClock; +import android.view.View; +import android.widget.ImageView; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.effect.DrawableTransition; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.Secret; +import com.hippo.ehviewer.util.Ui; +import com.hippo.ehviewer.widget.MaterialToast; + +public class SecretActivity extends AbsActivity implements View.OnClickListener { + + private final long[] mHits = new long[3]; + private Bitmap mBmp; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mBmp = Secret.getSecretImage(this); + if (mBmp == null) { + finish(); + return; + } + + int themeColor = Config.getCustomThemeColor() ? Config.getThemeColor() : Ui.THEME_COLOR; + Ui.colorStatusBarL(this, themeColor); + + ImageView iv = new ImageView(this); + iv.setScaleType(ImageView.ScaleType.CENTER_CROP); + iv.setImageBitmap(mBmp); + iv.setOnClickListener(this); + iv.setSoundEffectsEnabled(false); + setContentView(iv); + DrawableTransition.transit(iv.getDrawable(), false, 3000); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + if (mBmp != null) { + mBmp.recycle(); + mBmp = null; + } + } + + @Override + public void onClick(View v) { + System.arraycopy(mHits, 1, mHits, 0, mHits.length-1); + mHits[mHits.length-1] = SystemClock.uptimeMillis(); + if (mHits[0] >= (SystemClock.uptimeMillis() - 500)) { + Arrays.fill(mHits, 0); + MaterialToast.showToast(R.string.photo_footnotes); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/SettingsActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/SettingsActivity.java new file mode 100644 index 000000000..3cff5379f --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/SettingsActivity.java @@ -0,0 +1,921 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ui; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.SystemClock; +import android.preference.CheckBoxPreference; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import android.provider.SearchRecentSuggestions; +import android.text.util.Linkify; +import android.view.MenuItem; +import android.webkit.WebView; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TableLayout; + +import com.hippo.ehviewer.AppContext; +import com.hippo.ehviewer.AppHandler; +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.SimpleSuggestionProvider; +import com.hippo.ehviewer.UpdateHelper; +import com.hippo.ehviewer.app.DirSelectDialog; +import com.hippo.ehviewer.app.MaterialAlertDialog; +import com.hippo.ehviewer.app.MaterialProgressDialog; +import com.hippo.ehviewer.data.ApiGalleryInfo; +import com.hippo.ehviewer.data.Data; +import com.hippo.ehviewer.data.DownloadInfo; +import com.hippo.ehviewer.drawable.MaterialIndicatorDrawable; +import com.hippo.ehviewer.drawable.MaterialIndicatorDrawable.Stroke; +import com.hippo.ehviewer.ehclient.EhClient; +import com.hippo.ehviewer.ehclient.EhInfo; +import com.hippo.ehviewer.network.HttpHelper; +import com.hippo.ehviewer.preference.ListPreference; +import com.hippo.ehviewer.util.BgThread; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.DialogUtils; +import com.hippo.ehviewer.util.EhUtils; +import com.hippo.ehviewer.util.Favorite; +import com.hippo.ehviewer.util.Secret; +import com.hippo.ehviewer.util.Ui; +import com.hippo.ehviewer.util.Utils; +import com.hippo.ehviewer.util.ViewUtils; +import com.hippo.ehviewer.widget.CategoryTable; +import com.hippo.ehviewer.widget.MaterialToast; +import com.hippo.ehviewer.widget.SuggestionHelper; + +public class SettingsActivity extends AbsPreferenceActivity { + @SuppressWarnings("unused") + private static String TAG = SettingsActivity.class.getSimpleName(); + + private int mThemeColor; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Set random color + mThemeColor = Config.getCustomThemeColor() ? Config.getThemeColor() : Ui.THEME_COLOR; + getActionBar().setBackgroundDrawable(new ColorDrawable(mThemeColor)); + Ui.colorStatusBarL(this, mThemeColor); + + // Menu + MaterialIndicatorDrawable materialIndicator = new MaterialIndicatorDrawable(this, Color.WHITE, Stroke.THIN); + materialIndicator.setIconState(MaterialIndicatorDrawable.IconState.ARROW); + Ui.setMaterialIndicator(getActionBar(), materialIndicator); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + @Override + public void onBuildHeaders(List
    target) { + loadHeadersFromResource(R.xml.settings_headers, target); + } + + private static final String[] ENTRY_FRAGMENTS = { DisplayFragment.class.getName(), EhFragment.class.getName(), + ReadFragment.class.getName(), DownloadFragment.class.getName(), AdvancedFragment.class.getName(), + AboutFragment.class.getName() }; + + private static final int[] FRAGMENT_ICONS = { R.drawable.ic_setting_display, R.drawable.ic_setting_panda_light, + R.drawable.ic_setting_read, R.drawable.ic_setting_download, R.drawable.ic_setting_advanced, + R.drawable.ic_setting_about }; + + @Override + protected boolean isValidFragment(String fragmentName) { + for (int i = 0; i < ENTRY_FRAGMENTS.length; i++) { + if (ENTRY_FRAGMENTS[i].equals(fragmentName)) + return true; + } + return false; + } + + public static class DisplayFragment extends PreferenceFragment implements + Preference.OnPreferenceChangeListener { + + private static final String KEY_SCREEN_ORIENTATION = "screen_orientation"; + + private ListPreference mScreenOrientation; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.display_settings); + + mScreenOrientation = (ListPreference) findPreference(KEY_SCREEN_ORIENTATION); + mScreenOrientation.setOnPreferenceChangeListener(this); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final String key = preference.getKey(); + if (KEY_SCREEN_ORIENTATION.equals(key)) { + getActivity().setRequestedOrientation(Config.screenOriPre2Value(Integer.parseInt((String) newValue))); + + } + + return true; + } + } + + public static class EhFragment extends PreferenceFragment implements + Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener { + + private static final int[] EXCULDE_TAG_GROUP_RESID = { R.id.tag_group_reclass, R.id.tag_group_language, + R.id.tag_group_parody, R.id.tag_group_character, R.id.tag_group_group, R.id.tag_group_artist, + R.id.tag_group_male, R.id.tag_group_female }; + + private static final int[] EXCULDE_TAG_GROUP_ID = { 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80 }; + + private static final int[] EXCULDE_LANGUAGE_RESID = { R.id.el_japanese_translated, R.id.el_japanese_rewrite, + R.id.el_english_original, R.id.el_english_translated, R.id.el_english_rewrite, + R.id.el_chinese_original, R.id.el_chinese_translated, R.id.el_chinese_rewrite, R.id.el_dutch_original, + R.id.el_dutch_translated, R.id.el_dutch_rewrite, R.id.el_french_original, R.id.el_french_translated, + R.id.el_french_rewrite, R.id.el_german_original, R.id.el_german_translated, R.id.el_german_rewrite, + R.id.el_hungarian_original, R.id.el_hungarian_translated, R.id.el_hungarian_rewrite, + R.id.el_italian_original, R.id.el_italian_translated, R.id.el_italian_rewrite, R.id.el_korean_original, + R.id.el_korean_translated, R.id.el_korean_rewrite, R.id.el_polish_original, R.id.el_polish_translated, + R.id.el_polish_rewrite, R.id.el_portuguese_original, R.id.el_portuguese_translated, + R.id.el_portuguese_rewrite, R.id.el_russian_original, R.id.el_russian_translated, + R.id.el_russian_rewrite, R.id.el_spanish_original, R.id.el_spanish_translated, R.id.el_spanish_rewrite, + R.id.el_thai_original, R.id.el_thai_translated, R.id.el_thai_rewrite, R.id.el_vietnamese_original, + R.id.el_vietnamese_translated, R.id.el_vietnamese_rewrite, R.id.el_other_original, + R.id.el_other_translated, R.id.el_other_rewrite, }; + + private static final String[] EXCULDE_LANGUAGE_ID = { "1024", "2048", "1", "1025", "2049", "10", "1034", + "2058", "20", "1044", "2068", "30", "1054", "2078", "40", "1064", "2088", "50", "1074", "2098", "60", + "1084", "2108", "70", "1094", "2118", "80", "1104", "2128", "90", "1114", "2138", "100", "1124", + "2148", "110", "1134", "2158", "120", "1144", "2168", "130", "1154", "2178", "255", "1279", "2303" }; + + private static final String KEY_LIST_DEFAULT_CATEGORY = "list_default_category"; + private static final String KEY_EXCULDE_TAG_GROUP = "exculde_tag_group"; + private static final String KEY_EXCULDE_LANGUAGE = "exculde_language"; + private static final String KEY_CLEAR_SUGGESTIONS = "clear_suggestions"; + private static final String KEY_PREVIEW_MODE = "preview_mode"; + private static final String KEY_DEFAULT_FAVORITE = "default_favorite"; + + private Preference mListDefaultCategory; + private Preference mExculdeTagGroup; + private Preference mExculdeLanguage; + private Preference mClearSuggestions; + private ListPreference mPreviewMode; + private ListPreference mDefaultFavorite; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.eh_settings); + + mListDefaultCategory = findPreference(KEY_LIST_DEFAULT_CATEGORY); + mListDefaultCategory.setOnPreferenceClickListener(this); + mExculdeTagGroup = findPreference(KEY_EXCULDE_TAG_GROUP); + mExculdeTagGroup.setOnPreferenceClickListener(this); + mExculdeLanguage = findPreference(KEY_EXCULDE_LANGUAGE); + mExculdeLanguage.setOnPreferenceClickListener(this); + mClearSuggestions = findPreference(KEY_CLEAR_SUGGESTIONS); + mClearSuggestions.setOnPreferenceClickListener(this); + mPreviewMode = (ListPreference) findPreference(KEY_PREVIEW_MODE); + mPreviewMode.setOnPreferenceChangeListener(this); + mDefaultFavorite = (ListPreference) findPreference(KEY_DEFAULT_FAVORITE); + + int i = 0; + String[] entrise = new String[Favorite.FAVORITE_TITLES.length + 1]; + entrise[i++] = getString(R.string.none); + for (String str : Favorite.FAVORITE_TITLES) + entrise[i++] = str; + mDefaultFavorite.setEntries(entrise); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final String key = preference.getKey(); + if (KEY_PREVIEW_MODE.equals(key)) { + String newPreviewMode = (String) newValue; + EhInfo.getInstance(getActivity()).setPreviewMode(newPreviewMode); + } + return true; + } + + @SuppressLint("InflateParams") + @Override + public boolean onPreferenceClick(Preference preference) { + final String key = preference.getKey(); + if (KEY_LIST_DEFAULT_CATEGORY.equals(key)) { + int defaultCat = Config.getDefaultCat(); + final CategoryTable ct = new CategoryTable(getActivity()); + ct.setCategory(defaultCat); + + new MaterialAlertDialog.Builder(getActivity()).setTitle(R.string.list_default_category_title) + .setView(ct, true).setPositiveButton(android.R.string.ok) + .setNegativeButton(android.R.string.cancel) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + if (which == MaterialAlertDialog.POSITIVE) { + int defaultCat = ct.getCategory(); + Config.setDefaultCat(defaultCat); + EhInfo.getInstance(getActivity()).setDefaultCat(defaultCat); + } + return true; + } + }).show(); + + } else if (KEY_EXCULDE_TAG_GROUP.equals(key)) { + final TableLayout tl = (TableLayout) ViewUtils.inflateDialogView(R.layout.exculde_tag_group, false); + setExculdeTagGroup(tl, Config.getExculdeTagGroup()); + + new MaterialAlertDialog.Builder(getActivity()).setTitle(R.string.exculde_tag_group_title).setView(tl, true) + .setPositiveButton(android.R.string.ok).setNegativeButton(android.R.string.cancel) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + int newValue = getExculdeTagGroup(tl); + Config.setExculdeTagGroup(newValue); + EhInfo.getInstance(getActivity()).setExculdeTagGroup(newValue); + return true; + } + }).show(); + + } else if (KEY_EXCULDE_LANGUAGE.equals(key)) { + final TableLayout tl = (TableLayout) ViewUtils.inflateDialogView(R.layout.exculde_language, false); + setExculdeLanguage(tl, Config.getExculdeLanguage()); + + new MaterialAlertDialog.Builder(getActivity()).setTitle(R.string.exculde_language_title).setView(tl, true) + .setPositiveButton(android.R.string.ok).setNegativeButton(android.R.string.cancel) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + String newValue = getExculdeLanguage(tl); + Config.setExculdeLanguage(newValue); + EhInfo.getInstance(getActivity()).setExculdeLanguage(newValue); + return true; + } + }).show(); + + } else if (KEY_CLEAR_SUGGESTIONS.equals(key)) { + SearchRecentSuggestions suggestions = SuggestionHelper.getInstance(getActivity(), + SimpleSuggestionProvider.AUTHORITY, SimpleSuggestionProvider.MODE); + suggestions.clearHistory(); + } + return true; + } + + private static void setExculdeTagGroup(TableLayout tl, int value) { + for (int i = 0; i < EXCULDE_TAG_GROUP_RESID.length; i++) { + CheckBox cb = (CheckBox) tl.findViewById(EXCULDE_TAG_GROUP_RESID[i]); + cb.setChecked(Utils.int2boolean(value & EXCULDE_TAG_GROUP_ID[i])); + } + } + + private static int getExculdeTagGroup(TableLayout tl) { + int newValue = 0; + for (int i = 0; i < EXCULDE_TAG_GROUP_RESID.length; i++) { + CheckBox cb = (CheckBox) tl.findViewById(EXCULDE_TAG_GROUP_RESID[i]); + if (cb.isChecked()) + newValue |= EXCULDE_TAG_GROUP_ID[i]; + } + return newValue; + } + + private static int getLanguage(String id) { + for (int i = 0; i < EXCULDE_LANGUAGE_ID.length; i++) { + if (EXCULDE_LANGUAGE_ID[i].equals(id)) + return EXCULDE_LANGUAGE_RESID[i]; + } + return 0; + } + + private static void setExculdeLanguage(TableLayout tl, String value) { + String[] items = value.split("x"); + for (String item : items) { + int resId = getLanguage(item); + if (resId != 0) + ((CheckBox) tl.findViewById(resId)).setChecked(true); + } + } + + private static String getExculdeLanguage(TableLayout tl) { + StringBuilder sb = new StringBuilder(); + boolean isFirst = true; + for (int i = 0; i < EXCULDE_LANGUAGE_RESID.length; i++) { + if (((CheckBox) tl.findViewById(EXCULDE_LANGUAGE_RESID[i])).isChecked()) { + if (isFirst) + isFirst = false; + else + sb.append('x'); + sb.append(EXCULDE_LANGUAGE_ID[i]); + } + } + return sb.toString(); + } + } + + public static class ReadFragment extends PreferenceFragment implements + Preference.OnPreferenceClickListener { + + private static final String KEY_CUSTOM_CODEC = "custom_codec"; + private static final String KEY_CLEAN_REDUNDANCY = "clean_redundancy"; + + private CheckBoxPreference mCustomCodec; + private Preference mCleanRedundancy; + + private MaterialProgressDialog mMaterialProgressDialog; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.read_settings); + + mCustomCodec = (CheckBoxPreference) findPreference(KEY_CUSTOM_CODEC); + mCleanRedundancy = findPreference(KEY_CLEAN_REDUNDANCY); + mCleanRedundancy.setOnPreferenceClickListener(this); + + if (!Utils.SUPPORT_IMAGE) { + mCustomCodec.setEnabled(false); + mCustomCodec.setChecked(Config.getCustomCodec()); + mCustomCodec.setSummary(R.string.custom_codec_summary_not); + } else { + mCustomCodec.setChecked(Config.getCustomCodec()); + } + } + + private boolean isInDownloadList(List diList, String filename) { + for (DownloadInfo di : diList) + if (filename.startsWith(String.valueOf(di.galleryInfo.gid))) + return true; + return false; + } + + private class CleanResponder implements Runnable { + + public static final int STATE_NONE = 0x0; + public static final int STATE_START = 0x1; + public static final int STATE_DOING = 0x2; + public static final int STATE_DONE = 0x3; + + private final int mState; + private final int mMax; + private final int mProgress; + + public CleanResponder(int state, int max, int progress) { + mState = state; + mMax = max; + mProgress = progress; + } + + @Override + public void run() { + + switch (mState) { + case STATE_NONE: + MaterialToast.showToast(R.string.no_redundancy); + break; + case STATE_START: + if (!getActivity().isFinishing()) { + mMaterialProgressDialog = MaterialProgressDialog.create(getActivity(), getActivity().getString(R.string.clean_redundancy_title), false); + mMaterialProgressDialog.show(); + } + break; + case STATE_DOING: + if (mMaterialProgressDialog != null) { + mMaterialProgressDialog.setMax(mMax); + mMaterialProgressDialog.setProgress(mProgress); + } + break; + case STATE_DONE: + if (mMaterialProgressDialog != null) { + mMaterialProgressDialog.dismiss(); + MaterialToast.showToast(String.format(getString(R.string.clean_redundancy), mMax)); + } + mMaterialProgressDialog = null; + break; + } + } + } + + @Override + public boolean onPreferenceClick(Preference preference) { + final String key = preference.getKey(); + if (KEY_CLEAN_REDUNDANCY.equals(key)) { + + // Create a thread to do clean task + new BgThread() { + @Override + public void run() { + Data data = Data.getInstance(); + List diList = data.getAllDownloads(); + File downloadDir = new File(Config.getDownloadPath()); + String[] files = downloadDir.list(); + Handler handler = AppHandler.getInstance(); + List targetDirList = new ArrayList(); + + if (files == null) { + // Check files null + handler.post(new CleanResponder(CleanResponder.STATE_NONE, 0, 0)); + return; + } + + for (String filename : files) { + // If in download list, just continue + if (isInDownloadList(diList, filename)) + continue; + File dir = new File(downloadDir, filename); + // If there is no tag file, just continue + if (!new File(dir, EhUtils.EH_DOWNLOAD_FILENAME).exists()) + continue; + // Add to list + targetDirList.add(dir); + } + + if (targetDirList.isEmpty()) { + handler.post(new CleanResponder(CleanResponder.STATE_NONE, 0, 0)); + return; + } else { + handler.post(new CleanResponder(CleanResponder.STATE_START, 0, 0)); + } + // Do delete + for (int i = 0; i < targetDirList.size(); i++) { + File dir = targetDirList.get(i); + Utils.deleteFile(dir); + handler.post(new CleanResponder(CleanResponder.STATE_DOING, targetDirList.size(), i + 1)); + } + // Close windows + handler.post(new CleanResponder(CleanResponder.STATE_DONE, targetDirList.size(), targetDirList + .size())); + } + }.start(); + } + return true; + } + } + + public static class DownloadFragment extends PreferenceFragment implements + Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener { + + private static final String KEY_DOWNLOAD_PATH = "download_path"; + private static final String KEY_MEDIA_SCAN = "media_scan"; + + private DirSelectDialog mDirSelectDialog; + + private Preference mDownloadPath; + private CheckBoxPreference mMediaScan; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.download_settings); + + mDownloadPath = findPreference(KEY_DOWNLOAD_PATH); + mDownloadPath.setOnPreferenceClickListener(this); + mMediaScan = (CheckBoxPreference) findPreference(KEY_MEDIA_SCAN); + mMediaScan.setOnPreferenceChangeListener(this); + + mDownloadPath.setSummary(Config.getDownloadPath()); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + final String key = preference.getKey(); + if (KEY_MEDIA_SCAN.equals(key)) { + boolean value = (Boolean) newValue; + File nomedia = new File(Config.getDownloadPath(), ".nomedia"); + if (value) { + nomedia.delete(); + } else { + try { + nomedia.createNewFile(); + } catch (IOException e) { + } + } + } + return true; + } + + @Override + @SuppressLint("InflateParams") + public boolean onPreferenceClick(Preference preference) { + final String key = preference.getKey(); + if (KEY_DOWNLOAD_PATH.equals(key)) { + mDirSelectDialog = DirSelectDialog.create(new MaterialAlertDialog.Builder(getActivity()) + .setTitle(R.string.download_path_title) + .setDefaultButton(MaterialAlertDialog.POSITIVE | MaterialAlertDialog.NEGATIVE) + .setActionButton(R.string._new) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, + int which) { + switch (which) { + case MaterialAlertDialog.ACTION: + final EditText et = new EditText(getActivity()); + et.setText(R.string.new_folder); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT); + int x = Ui.dp2pix(8); + lp.leftMargin = x; + lp.rightMargin = x; + lp.topMargin = x; + lp.bottomMargin = x; + new MaterialAlertDialog.Builder(getActivity()).setTitle(R.string.new_folder) + .setView(et, true, lp).setPositiveButton(R.string._new) + .setNegativeButton(android.R.string.cancel) + .setButtonListener(new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + if (which == MaterialAlertDialog.POSITIVE) { + File dir = new File(mDirSelectDialog.getCurrentPath(), et.getText() + .toString()); + dir.mkdirs(); + mDirSelectDialog.refresh(); + } + return true; + } + }).show(); + return false; + case MaterialAlertDialog.POSITIVE: + if (!mDirSelectDialog.canWrite()) { + MaterialToast.showToast(R.string.cur_dir_not_writable); + return false; + } + String downloadPath = mDirSelectDialog.getCurrentPath(); + // Update .nomedia file + // TODO Should I delete .nomedia in old + // download dir ? + if (!Config.getMediaScan()) { + try { + new File(Config.getDownloadPath(), ".nomedia").createNewFile(); + } catch (IOException e) { + } + } + Config.setDownloadPath(downloadPath); + mDownloadPath.setSummary(downloadPath); + return true; + case MaterialAlertDialog.NEGATIVE: + default: + return true; + } + } + }) , Config.getDownloadPath()); + mDirSelectDialog.show(); + } + + return true; + } + } + + public static class AdvancedFragment extends PreferenceFragment implements + Preference.OnPreferenceClickListener { + + private static final String KEY_FIX_DIRNAME = "fix_dirname"; + + private Preference mFixDirname; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.advanced_settings); + + mFixDirname = findPreference(KEY_FIX_DIRNAME); + mFixDirname.setOnPreferenceClickListener(this); + } + + @Override + public boolean onPreferenceClick(Preference preference) { + final String key = preference.getKey(); + if (KEY_FIX_DIRNAME.equals(key)) { + mFixDirname.setEnabled(false); + new BgThread() { + @Override + public void run() { + File downloadDir = new File(Config.getDownloadPath()); + List targetDirList = new ArrayList(); + + String[] list = downloadDir.list(); + if (list != null) { + for (String str : list) { + if (str.contains("-")) + continue; + File dir = new File(downloadDir, str); + if (new File(dir, EhUtils.EH_DOWNLOAD_FILENAME).exists()) + targetDirList.add(dir); + } + } + + if (targetDirList.size() == 0) { + AppHandler.getInstance().post(new Runnable() { + @Override + public void run() { + mFixDirname.setEnabled(true); + MaterialToast.showToast(R.string.no_need_fixing); + } + }); + return; + } else { + AppHandler.getInstance().post(new Runnable() { + @Override + public void run() { + MaterialToast.showToast(R.string.start_fixing); + } + }); + } + + int handleNum = 0; + List dirs = new ArrayList(); + List gids = new ArrayList(); + List tokens = new ArrayList(); + // Do get title from api + for (int i = 0; i < targetDirList.size(); i++) { + File dir = targetDirList.get(i); + File info = new File(dir, EhUtils.EH_DOWNLOAD_FILENAME); + InputStream is = null; + int gid; + String token; + try { + is = new BufferedInputStream(new FileInputStream(info), 128); + // skip read index + Utils.readAsciiLine(is); + gid = Integer.parseInt(Utils.readAsciiLine(is)); + token = Utils.readAsciiLine(is); + Utils.closeQuietly(is); + dirs.add(dir); + gids.add(gid); + tokens.add(token); + // Post api when to 25 or the end + if (gids.size() == 25 || i == targetDirList.size() - 1) { + ApiGalleryInfo[] agiArray = EhClient.getInstance().getApiGalleryInfo( + Utils.toIntArray(gids), tokens.toArray(new String[tokens.size()])); + // Change dirname + if (agiArray != null) { + for (int j = 0; j < agiArray.length; j++) { + ApiGalleryInfo agi = agiArray[j]; + if (agi != null) { + handleNum++; + dirs.get(j).renameTo(EhUtils.generateGalleryDir(agi.gid, agi.title)); + } + } + } + dirs.clear(); + gids.clear(); + tokens.clear(); + // Wait for CD, if not last + if (i != targetDirList.size() - 1) + Thread.sleep(3000); + } + } catch (Throwable e) { + continue; + } + } + final int _handleNum = handleNum; + AppHandler.getInstance().post(new Runnable() { + @Override + public void run() { + mFixDirname.setEnabled(true); + MaterialToast.showToast(String.format(getString(R.string.fixing_over), _handleNum)); + } + }); + } + }.start(); + } + return true; + } + } + + public static class AboutFragment extends PreferenceFragment implements + Preference.OnPreferenceClickListener { + + private static final String KEY_AUTHOR = "author"; + private static final String KEY_TWITTER = "twitter"; + private static final String KEY_GOOGLE_PLUS = "google_plus"; + private static final String KEY_CHANGELOG = "changelog"; + private static final String KEY_THANKS = "thanks"; + private static final String KEY_WEBSITE = "website"; + private static final String KEY_SOURCE = "source"; + private static final String KEY_VERSION = "version"; + private static final String KEY_CHECK_UPDATE = "check_for_update"; + private static final String KEY_CLOUD_DRIVE = "cloud_drive"; + private static final String KEY_ABOUT_ANALYICS = "about_analyics"; + + private Preference mAuthor; + private Preference mTwitter; + private Preference mGooglePlus; + private Preference mChangelog; + private Preference mThanks; + private Preference mWebsite; + private Preference mSource; + private Preference mVersion; + private Preference mCheckUpdate; + private Preference mCloudDrive; + private Preference mAboutAnalyics; + + private final long[] mHits = new long[3]; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.about_settings); + + mAuthor = findPreference(KEY_AUTHOR); + mAuthor.setOnPreferenceClickListener(this); + mTwitter = findPreference(KEY_TWITTER); + mTwitter.setOnPreferenceClickListener(this); + mGooglePlus = findPreference(KEY_GOOGLE_PLUS); + mGooglePlus.setOnPreferenceClickListener(this); + mChangelog = findPreference(KEY_CHANGELOG); + mChangelog.setOnPreferenceClickListener(this); + mThanks = findPreference(KEY_THANKS); + mThanks.setOnPreferenceClickListener(this); + mWebsite = findPreference(KEY_WEBSITE); + mWebsite.setOnPreferenceClickListener(this); + mSource = findPreference(KEY_SOURCE); + mSource.setOnPreferenceClickListener(this); + mVersion = findPreference(KEY_VERSION); + mVersion.setOnPreferenceClickListener(this); + mCheckUpdate = findPreference(KEY_CHECK_UPDATE); + mCheckUpdate.setOnPreferenceClickListener(this); + mCloudDrive = findPreference(KEY_CLOUD_DRIVE); + mCloudDrive.setOnPreferenceClickListener(this); + mAboutAnalyics = findPreference(KEY_ABOUT_ANALYICS); + mAboutAnalyics.setOnPreferenceClickListener(this); + + try { + Context c = getActivity().getApplicationContext(); + PackageInfo pi= c.getPackageManager().getPackageInfo(c.getPackageName(), 0); + mVersion.setSummary(pi.versionName); + } catch (NameNotFoundException e) {} + } + + @Override + public boolean onPreferenceClick(Preference preference) { + final String key = preference.getKey(); + if (KEY_VERSION.equals(key)) { + System.arraycopy(mHits, 1, mHits, 0, mHits.length-1); + mHits[mHits.length-1] = SystemClock.uptimeMillis(); + if (mHits[0] >= (SystemClock.uptimeMillis() - 500)) { + Arrays.fill(mHits, 0); + Activity a = getActivity(); + if (Secret.hasSecretImage(a)) { + Intent intent = new Intent(a, SecretActivity.class); + a.startActivity(intent); + getActivity().overridePendingTransition(0, 0); + } + } + + } else if (KEY_AUTHOR.equals(key)) { + Intent i = new Intent(Intent.ACTION_SENDTO); + i.setData(Uri.parse("mailto:ehviewersu@gmail.com")); + i.putExtra(Intent.EXTRA_SUBJECT, "About EhViewer"); + startActivity(i); + + } else if (KEY_TWITTER.equals(key)) { + new MaterialAlertDialog.Builder(getActivity()) + .setTitle(R.string.twitter_title) + .setItems(new CharSequence[] { "@EhViewer", "@jkjvinn" }, + new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + Uri uri = null; + switch (which) { + case 0: + uri = Uri.parse("https://twitter.com/EhViewer"); + break; + case 1: + uri = Uri.parse("https://twitter.com/jkjvinn"); + break; + } + if (uri != null) { + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + startActivity(intent); + } + return true; + } + }).setNegativeButton(android.R.string.cancel).show(); + + } else if (KEY_GOOGLE_PLUS.equals(key)) { + Uri uri = Uri.parse("https://plus.google.com/communities/103823982034655188459"); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + startActivity(intent); + + } else if (KEY_CHANGELOG.equals(key)) { + InputStream is = getActivity().getResources().openRawResource(R.raw.change_log); + new MaterialAlertDialog.Builder(getActivity()).setTitle(R.string.changelog) + .setMessage(Utils.InputStream2String(is, "utf-8")).setNegativeButton(android.R.string.cancel) + .show(); + + } else if (KEY_THANKS.equals(key)) { + InputStream is = getActivity().getResources().openRawResource(R.raw.thanks); + final WebView webView = new WebView(getActivity()); + webView.loadData(Utils.InputStream2String(is, "utf-8"), "text/html; charset=UTF-8", null); + new MaterialAlertDialog.Builder(getActivity())// .setTitle(R.string.thanks) + .setView(webView, true).setNegativeButton(android.R.string.cancel).show(); + + } else if (KEY_WEBSITE.equals(key)) { + Uri uri = Uri.parse("http://www.ehviewer.com"); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + startActivity(intent); + + } else if (KEY_SOURCE.equals(key)) { + Uri uri = Uri.parse("https://github.com/seven332/EhViewer"); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + startActivity(intent); + + } else if (KEY_CHECK_UPDATE.equals(key)) { + mCheckUpdate.setSummary(R.string.checking_update); + mCheckUpdate.setEnabled(false); + new UpdateHelper((AppContext) getActivity().getApplication()).SetOnCheckUpdateListener( + new UpdateHelper.OnCheckUpdateListener() { + @Override + public void onSuccess(String version, long size, final String url, final String fileName, + String info) { + mCheckUpdate.setSummary(R.string.found_update); + String sizeStr = Utils.sizeToString(size); + AlertDialog dialog = DialogUtils.createUpdateDialog(getActivity(), version, sizeStr, info, + new MaterialAlertDialog.OnClickListener() { + @Override + public boolean onClick(MaterialAlertDialog dialog, int which) { + if (which == MaterialAlertDialog.POSITIVE) { + HttpHelper hh = new HttpHelper(getActivity()); + hh.downloadInThread(url, new File(Config.getDownloadPath()), + fileName, false, null, new UpdateHelper.UpdateListener( + getActivity(), fileName)); + } + return true; + } + }).create(); + if (!getActivity().isFinishing()) + dialog.show(); + } + + @Override + public void onNoUpdate() { + mCheckUpdate.setSummary(R.string.up_to_date); + mCheckUpdate.setEnabled(true); + UpdateHelper.setEnabled(true); + } + + @Override + public void onFailure(String eMsg) { + mCheckUpdate.setSummary(eMsg); + mCheckUpdate.setEnabled(true); + UpdateHelper.setEnabled(true); + } + }).checkUpdate(); + + } else if (KEY_CLOUD_DRIVE.equals(key)) { + Uri uri = Uri.parse("https://mega.co.nz/#F!xkMChYrI!Q85i3d7kNkhVkwvDePbahw"); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + startActivity(intent); + + } else if (KEY_ABOUT_ANALYICS.equals(key)) { + new MaterialAlertDialog.Builder(getActivity()).setTitle(R.string.about_analyics_title) + .setMessageAutoLink(Linkify.WEB_URLS).setMessage(R.string.about_analyics_comment) + .setNegativeButton(android.R.string.cancel).show(); + + } + return true; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/AnimatorUtils.java b/app/src/main/java/com/hippo/ehviewer/util/AnimatorUtils.java new file mode 100644 index 000000000..728b64452 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/AnimatorUtils.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +import android.animation.Animator; +import android.animation.ValueAnimator; + +public final class AnimatorUtils { + + public static Animator createDelayAnimator(long delay) { + Animator anim = ValueAnimator.ofFloat(0f, 1f); + anim.setDuration(delay);; + return anim; + } + +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/AutoExpandArray.java b/app/src/main/java/com/hippo/ehviewer/util/AutoExpandArray.java new file mode 100644 index 000000000..796798445 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/AutoExpandArray.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +public class AutoExpandArray { + + @SuppressWarnings("unused") + private static final String TAG = AutoExpandArray.class.getSimpleName(); + + private static final int MIN_CAPACITY_INCREMENT = 12; + + private int maxValidIndex = 0; + private transient Object[] array; + + public AutoExpandArray(int capacity) { + if (capacity < 0) { + throw new IllegalArgumentException("capacity < 0: " + capacity); + } + array = new Object[capacity]; + } + + public AutoExpandArray() { + array = new Object[MIN_CAPACITY_INCREMENT]; + } + + private synchronized void ensureCapacity(int index) { + if (index < array.length) + return; + + Object[] newArray = new Object[(index + 1) << 1]; + System.arraycopy(array, 0, newArray, 0, array.length); + array = newArray; + } + + public synchronized void setCapacity(int newCapacity) { + if (newCapacity <= array.length) + return; + + Object[] newArray = new Object[newCapacity]; + System.arraycopy(array, 0, newArray, 0, array.length); + array = newArray; + } + + public synchronized E set(int index, E object) { + if (index > maxValidIndex) + maxValidIndex = index; + + ensureCapacity(index); + @SuppressWarnings("unchecked") + E result = (E)array[index]; + array[index] = object; + return result; + } + + @SuppressWarnings("unchecked") + public synchronized E get(int index) { + if (index >= array.length || index < 0) { + return null; + } + return (E)array[index]; + } + + public synchronized int length() { + return array.length; + } + + public synchronized int maxValidIndex() { + return maxValidIndex; + } + } diff --git a/app/src/main/java/com/hippo/ehviewer/util/BgThread.java b/app/src/main/java/com/hippo/ehviewer/util/BgThread.java new file mode 100644 index 000000000..cdefd3187 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/BgThread.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +import android.os.Process; + +public class BgThread extends Thread { + + public BgThread() { + super(); + setPriority(Process.THREAD_PRIORITY_BACKGROUND); + } + + public BgThread(Runnable runnable) { + super(runnable); + setPriority(Process.THREAD_PRIORITY_BACKGROUND); + } + + public BgThread(Runnable runnable, String threadName) { + super(runnable, threadName); + setPriority(Process.THREAD_PRIORITY_BACKGROUND); + } + + public BgThread(String threadName) { + super(threadName); + setPriority(Process.THREAD_PRIORITY_BACKGROUND); + } + + public BgThread(ThreadGroup group, Runnable runnable) { + super(group, runnable); + setPriority(Process.THREAD_PRIORITY_BACKGROUND); + } + + public BgThread(ThreadGroup group, Runnable runnable, String threadName) { + super(group, runnable, threadName); + setPriority(Process.THREAD_PRIORITY_BACKGROUND); + } + + public BgThread(ThreadGroup group, String threadName) { + super(group, threadName); + setPriority(Process.THREAD_PRIORITY_BACKGROUND); + } + + public BgThread(ThreadGroup group, Runnable runnable, String threadName, long stackSize) { + super(group, runnable, threadName, stackSize); + setPriority(Process.THREAD_PRIORITY_BACKGROUND); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/Config.java b/app/src/main/java/com/hippo/ehviewer/util/Config.java new file mode 100644 index 000000000..839055ed7 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/Config.java @@ -0,0 +1,643 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; +import android.os.Environment; +import android.preference.PreferenceManager; + +import com.hippo.ehviewer.data.ListUrls; +import com.hippo.ehviewer.ehclient.EhClient; + +import java.io.File; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public final class Config { + @SuppressWarnings("unused") + private static final String TAG = Config.class.getSimpleName(); + + public static final String EXTERANL_DIR_NAME = "EhViewer"; + public static File sExternalDir; + + private static final String KEY_UPDATE_DATE = "update_date"; + + private static boolean mInit = false; + + private static Context mContext; + private static SharedPreferences mConfigPre; + + /** + * Init Config + * + * @param context Application context + */ + public static void init(Context context) { + if (mInit) + return; + mInit = true; + + mContext = context; + mConfigPre = PreferenceManager.getDefaultSharedPreferences(mContext); + + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + sExternalDir = new File(Environment.getExternalStorageDirectory(), EXTERANL_DIR_NAME); + Utils.ensureDir(sExternalDir, true); + } else { + sExternalDir = null; + } + } + + /** + * Is init + * @return True if init + */ + public static boolean isInit() { + return mInit; + } + + public static boolean getBoolean(String key, boolean defValue) { + return mConfigPre.getBoolean(key, defValue); + } + + public static void setBoolean(String key, boolean value) { + mConfigPre.edit().putBoolean(key, value).apply(); + } + + public static int getInt(String key, int defValue) { + return mConfigPre.getInt(key, defValue); + } + + public static void setInt(String key, int value) { + mConfigPre.edit().putInt(key, value).apply(); + } + + public static String getString(String key, String defValue) { + return mConfigPre.getString(key, defValue); + } + + public static void setString(String key, String value) { + mConfigPre.edit().putString(key, value).apply(); + } + + public static int getIntFromStr(String key, int defValue) { + return Utils.parseIntSafely(mConfigPre.getString(key, Integer.toString(defValue)), defValue); + } + + public static void setIntToStr(String key, int value) { + mConfigPre.edit().putString(key, Integer.toString(value)).apply(); + } + + public static float getFloat(String key, float defValue) { + return mConfigPre.getFloat(key, defValue); + } + + public static void setFloat(String key, float value) { + mConfigPre.edit().putFloat(key, value).apply(); + } + + + /****** Display ******/ + + private static final String KEY_CUSTOM_THEME_COLOR = "custom_theme_color"; + private static final boolean DEFAULT_CUSTOM_THEME_COLOR = false; + + private static final String KEY_THEME_COLOR = "theme_color"; + private static final int DEFAULT_THEME_COLOR = Ui.THEME_COLOR; + + private static final String KEY_SCREEN_ORIENTATION = "screen_orientation"; + private static final int DEFAULT_SCREEN_ORIENTATION = 0; + + private static final String KEY_LIST_MODE = "list_mode"; + private static final int DEFAULT_LIST_MODE = 0; + + private static final String KEY_LIST_THUMB_COLUMNS_PORTRAIT = "list_thumb_columns_portrait"; + private static final int DEFAULT_LIST_THUMB_COLUMNS_PORTRAIT = 3; + + private static final String KEY_LIST_THUMB_COLUMNS_LANDSCAPE = "list_thumb_columns_landscape"; + private static final int DEFAULT_LIST_THUMB_COLUMNS_LANDSCAPE = 5; + + private static final String KEY_PREVIEW_COLUMNS_PORTRAIT = "preview_columns_portrait"; + private static final int DEFAULT_PREVIEW_COLUMNS_PORTRAIT = 3; + + private static final String KEY_PREVIEW_COLUMNS_LANDSCAPE = "preview_columns_landscape"; + private static final int DEFAULT_PREVIEW_COLUMNS_LANDSCAPE = 5; + + private static final String KEY_SHOW_POPULAR_UPDATE_TIME = "show_popular_update_time"; + private static final boolean DEFAULT_SHOW_POPULAR_UPDATE_TIME = false; + + public static boolean getCustomThemeColor() { + return getBoolean(KEY_CUSTOM_THEME_COLOR, DEFAULT_CUSTOM_THEME_COLOR); + } + + public static int getThemeColor() { + return getInt(KEY_THEME_COLOR, DEFAULT_THEME_COLOR); + } + + public static void setThemeColor(int color) { + setInt(KEY_THEME_COLOR, color); + } + + public static int getScreenOriMode() { + return screenOriPre2Value(getIntFromStr(KEY_SCREEN_ORIENTATION, DEFAULT_SCREEN_ORIENTATION)); + } + + public static int screenOriPre2Value(int screenOriModePre) { + switch (screenOriModePre) { + case 0: + return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + case 1: + return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; + case 2: + return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + default: + return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + } + } + + public static int getListMode() { + return getIntFromStr(KEY_LIST_MODE, DEFAULT_LIST_MODE); + } + + public static void setListMode(int listMode) { + setIntToStr(KEY_LIST_MODE, listMode); + } + + public static int getListThumbColumnsPortrait() { + return getInt(KEY_LIST_THUMB_COLUMNS_PORTRAIT, DEFAULT_LIST_THUMB_COLUMNS_PORTRAIT); + } + + public static int getListThumbColumnsLandscape() { + return getInt(KEY_LIST_THUMB_COLUMNS_LANDSCAPE, DEFAULT_LIST_THUMB_COLUMNS_LANDSCAPE); + } + + public static int getPreviewColumnsPortrait() { + return getInt(KEY_PREVIEW_COLUMNS_PORTRAIT, DEFAULT_PREVIEW_COLUMNS_PORTRAIT); + } + + public static int getPreviewColumnsLandscape() { + return getInt(KEY_PREVIEW_COLUMNS_LANDSCAPE, DEFAULT_PREVIEW_COLUMNS_LANDSCAPE); + } + + public static boolean getShowPopularUpdateTime() { + return getBoolean(KEY_SHOW_POPULAR_UPDATE_TIME, DEFAULT_SHOW_POPULAR_UPDATE_TIME); + } + + + /****** Eh Config ******/ + + private static final String KEY_DEFAULT_CAT = "default_cat"; + private static final int DEFAULT_DEFAULT_CAT = ListUrls.ALL_CATEGORT; + + // m for normal, l for large + public static final String KEY_PREVIEW_MODE = "preview_mode"; + public static final String PREVIEW_MODE_NORMAL = "m"; + public static final String PREVIEW_MODE_LARGE = "l"; + private static final String DEFAULT_PREVIEW_MODE = PREVIEW_MODE_LARGE; + + /** + * reclass 0x1 + * language 0x2 + * parody 0x4 + * character 0x8 + * group 0x10 + * artist 0x20 + * male 0x40 + * female 0x80 + */ + private static final String KEY_EXCLUDE_TAG_GROUP = "exculde_tag_group"; + private static final int DEFAULT_EXCLUDE_TAG_GROUP = 0; + + /** + * Original Translated Rewrite All + * Japanese 1024 2048 + * English 1 1025 2049 + * Chinese 10 1034 2058 + * Dutch 20 1044 2068 + * French 30 1054 2078 + * German 40 1064 2088 + * Hungarian 50 1074 2098 + * Italian 60 1084 2108 + * Korean 70 1094 2118 + * Polish 80 1104 2128 + * Portuguese 90 1114 2138 + * Russian 100 1124 2148 + * Spanish 110 1134 2158 + * Thai 120 1144 2168 + * Vietnamese 130 1154 2178 + * Other 255 1279 2303 + */ + private static final String KEY_EXCLUDE_LANGUAGE = "exculde_language"; + private static final String DEFAULT_EXCLUDE_LANGUAGE = ""; + + private static final String KEY_DEFAULT_FAVORITE = "default_favorite"; + private static final int DEFAULT_DEFAULT_FAVORITE = -2; + + private static final String KEY_MAX_HISTORY_COUNT = "max_history_count"; + private static final int DEFAULT_MAX_HISTORY_COUNT = 100; + + public static int getDefaultCat() { + return getInt(KEY_DEFAULT_CAT, DEFAULT_DEFAULT_CAT); + } + + public static void setDefaultCat(int defaultCat) { + setInt(KEY_DEFAULT_CAT, defaultCat); + } + + public static String getPreviewMode() { + return getString(KEY_PREVIEW_MODE, DEFAULT_PREVIEW_MODE); + } + + public static int getExculdeTagGroup() { + return getInt(KEY_EXCLUDE_TAG_GROUP, DEFAULT_EXCLUDE_TAG_GROUP); + } + + public static void setExculdeTagGroup(int exculdeTagGroup) { + setInt(KEY_EXCLUDE_TAG_GROUP, exculdeTagGroup); + } + + public static String getExculdeLanguage() { + return getString(KEY_EXCLUDE_LANGUAGE, DEFAULT_EXCLUDE_LANGUAGE); + } + + public static void setExculdeLanguage(String exculdeLanguage) { + setString(KEY_EXCLUDE_LANGUAGE, exculdeLanguage); + } + + public static int getDefaultFavorite() { + return getIntFromStr(KEY_DEFAULT_FAVORITE, DEFAULT_DEFAULT_FAVORITE); + } + + public static void setDefaultFavorite(int defaultFavorite) { + setIntToStr(KEY_DEFAULT_FAVORITE, defaultFavorite); + } + + public static int getMaxHistoryCount() { + return getIntFromStr(KEY_MAX_HISTORY_COUNT, DEFAULT_MAX_HISTORY_COUNT); + } + + + /****** Read ******/ + + private static final String KEY_READING_DIRECTION = "reading_direction"; + private static final int DEFAULT_READING_DIRECTION = 0; + + private static final String KEY_PAGE_SCALING = "page_scaling"; + private static final int DEFAULT_PAGE_SCALING = 3; + + private static final String KEY_START_POSITION = "start_position"; + private static final int DEFAULT_START_POSITION = 1; + + private static final String KEY_GALLERY_SHOW_CLOCK = "gallery_show_clock"; + private static final boolean DEFAULT_GALLERY_SHOW_CLOCK = true; + + private static final String KEY_KEEP_SCREEN_ON = "keep_screen_on"; + private static final boolean DEFAULT_KEEP_SCREEN_ON = false; + + private static final String KEY_GALLERY_SHOW_BATTERY = "gallery_show_battery"; + private static final boolean DEFAULT_GALLERY_SHOW_BATTERY = true; + + private static final String KEY_CUSTOM_LIGHTNESS = "custom_lightness"; + private static final boolean DEFAULT_CUSTOM_LIGHTNESS = false; + + private static final String KEY_CUSTOM_LIGHTNESS_VALUE = "custom_lightness_value"; + private static final int DEFAULT_CUSTOM_LIGHTNESS_VALUE = 120; + + private static final String KEY_CUSTOM_CODEC = "custom_codec"; + + private static final String KEY_DECODE_FORMAT = "decode_format"; + private static final int DEFAULT_DECODE_FORMAT = 0; + + public static int getReadingDirection() { + return getIntFromStr(KEY_READING_DIRECTION, DEFAULT_READING_DIRECTION); + } + + public static void setReadingDirection(int value) { + setIntToStr(KEY_READING_DIRECTION, value); + } + + public static int getPageScaling() { + return getIntFromStr(KEY_PAGE_SCALING, DEFAULT_PAGE_SCALING); + } + + public static void setPageScaling(int value) { + setIntToStr(KEY_PAGE_SCALING, value); + } + + public static int getStartPosition() { + return getIntFromStr(KEY_START_POSITION, DEFAULT_START_POSITION); + } + + public static void setStartPosition(int value) { + setIntToStr(KEY_START_POSITION, value); + } + + public static boolean getKeepSreenOn() { + return getBoolean(KEY_KEEP_SCREEN_ON, DEFAULT_KEEP_SCREEN_ON); + } + + public static void setKeepSreenOn(boolean value) { + setBoolean(KEY_KEEP_SCREEN_ON, value); + } + + public static boolean getShowClock() { + return getBoolean(KEY_GALLERY_SHOW_CLOCK, DEFAULT_GALLERY_SHOW_CLOCK); + } + + public static void setShowClock(boolean value) { + setBoolean(KEY_GALLERY_SHOW_CLOCK, value); + } + + public static boolean getShowBattery() { + return getBoolean(KEY_GALLERY_SHOW_BATTERY, DEFAULT_GALLERY_SHOW_BATTERY); + } + + public static void setShowBattery(boolean value) { + setBoolean(KEY_GALLERY_SHOW_BATTERY, value); + } + + public static boolean getCustomLightness() { + return getBoolean(KEY_CUSTOM_LIGHTNESS, DEFAULT_CUSTOM_LIGHTNESS); + } + + public static void setCustomLightness(boolean value) { + setBoolean(KEY_CUSTOM_LIGHTNESS, value); + } + + public static int getCustomLightnessValue() { + return getIntFromStr(KEY_CUSTOM_LIGHTNESS_VALUE, DEFAULT_CUSTOM_LIGHTNESS_VALUE); + } + + public static void setCustomLightnessValue(int value) { + setIntToStr(KEY_CUSTOM_LIGHTNESS_VALUE, value); + } + + public static boolean getCustomCodec() { + return getBoolean(KEY_CUSTOM_CODEC, Utils.SUPPORT_IMAGE); + } + + public static void setCustomCodec(boolean value) { + setBoolean(KEY_CUSTOM_CODEC, value); + } + + public static int getDecodeFormat() { + return getIntFromStr(KEY_DECODE_FORMAT, DEFAULT_DECODE_FORMAT); + } + + public static int getDecodeFormatIndex() { + int index = getIntFromStr(KEY_DECODE_FORMAT, DEFAULT_DECODE_FORMAT); + switch (index) { + case 6409: + index = 1; + break; + case 6410: + index = 2; + break; + case 6407: + index = 3; + break; + case 6408: + index = 4; + break; + case 0: + default: + index = 0; + break; + } + return index; + } + + public static void setDecodeFormatFromIndex(int index) { + switch (index) { + case 1: + index = 6409; + break; + case 2: + index = 6410; + break; + case 3: + index = 6407; + break; + case 4: + index = 6408; + break; + case 0: + default: + index = 0; + break; + } + setIntToStr(KEY_DECODE_FORMAT, index); + } + + + /****** Download ******/ + + private static final String KEY_DOWNLOAD_PATH = "download_path"; + private static final String DEFAULT_DOWNLOAD_PATH = + Environment.getExternalStorageDirectory() + "/EhViewer/download/"; + + private static final String KEY_MEDIA_SCAN = "media_scan"; + private static final boolean DEFAULT_MEDIA_SCAN = false; + + private static final String KEY_DOWNLOAD_THREAD = "download_thread"; + private static final int DEFAULT_DOWNLOAD_THREAD = 3; + + private static final String KEY_DOWNLOAD_ORIGIN_IMAGE = "download_origin_image"; + private static final boolean DEFAULT_DOWNLOAD_ORIGIN_IMAGE = false; + + private static final String KEY_KEEP_DOWNLOAD_SERVICE = "keep_download_service"; + private static final boolean DEFAULT_KEEP_DOWNLOAD_SERVICE = true; + + public static String getDownloadPath() { + return getString(KEY_DOWNLOAD_PATH, DEFAULT_DOWNLOAD_PATH); + } + + public static void setDownloadPath(String path) { + setString(KEY_DOWNLOAD_PATH, path); + } + + public static boolean getMediaScan() { + return getBoolean(KEY_MEDIA_SCAN, DEFAULT_MEDIA_SCAN); + } + + public static int getDownloadThread() { + return getIntFromStr(KEY_DOWNLOAD_THREAD, DEFAULT_DOWNLOAD_THREAD); + } + + public static boolean getDownloadOriginImage() { + return getBoolean(KEY_DOWNLOAD_ORIGIN_IMAGE, DEFAULT_DOWNLOAD_ORIGIN_IMAGE); + } + + public static boolean getKeepDownloadService() { + return getBoolean(KEY_KEEP_DOWNLOAD_SERVICE, DEFAULT_KEEP_DOWNLOAD_SERVICE); + } + + + /****** Advanced ******/ + + private static final String KEY_HTTP_RETRY = "http_retry"; + private static final int DEFAULT_HTTP_RETRY = 3; + + private static final String KEY_HTTP_CONNECT_TIMEOUT = "http_connect_timeout"; + private static final int DEFAULT_HTTP_CONNECT_TIMEOUT = 5000; + + private static final String KEY_HTTP_READ_TIMEOUT = "http_read_timeout"; + private static final int DEFAULT_HTTP_READ_TIMEOUT = 5000; + + private static final String KEY_EH_MIN_INTERVAL = "eh_min_interval"; + private static final int DEFAULT_EH_MIN_INTERVAL = 0; + + public static int getHttpRetry() { + return getIntFromStr(KEY_HTTP_RETRY, DEFAULT_HTTP_RETRY); + } + + public static int getHttpConnectTimeout() { + return getIntFromStr(KEY_HTTP_CONNECT_TIMEOUT, DEFAULT_HTTP_CONNECT_TIMEOUT); + } + + public static int getHttpReadTimeout() { + return getIntFromStr(KEY_HTTP_READ_TIMEOUT, DEFAULT_HTTP_READ_TIMEOUT); + } + + public static int getEhMinInterval() { + return getIntFromStr(KEY_EH_MIN_INTERVAL, DEFAULT_EH_MIN_INTERVAL); + } + + + /****** About ******/ + + private static final String KEY_AUTO_CHECK_FOR_UPDATE = "auto_check_for_update"; + private static final boolean DEFAULT_AUTO_CHECK_FOR_UPDATE = true; + + private static final String KEY_UPDATE_SERVER = "update_server"; + private static final int DEFAULT_UPDATE_SERVER = 1; + + private static final String KEY_ALLOW_ANALYTICS = "allow_analyics"; + private static final boolean DEFAULT_ALLOW_ANALYTICS = false; + + public static boolean isAutoCheckForUpdate() { + return getBoolean(KEY_AUTO_CHECK_FOR_UPDATE, DEFAULT_AUTO_CHECK_FOR_UPDATE); + } + + public static void setAutoCheckForUpdate(boolean autoCheckForUpdate) { + setBoolean(KEY_AUTO_CHECK_FOR_UPDATE, autoCheckForUpdate); + } + + public static int getUpdateDate() { + return getInt(KEY_UPDATE_DATE, 0); + } + + public static void setUpdateDate() { + setUpdateDate(Utils.getDate()); + } + + public static void setUpdateDate(int date) { + setInt(KEY_UPDATE_DATE, date); + } + + public static String getUpdateServer() { + int value = getIntFromStr(KEY_UPDATE_SERVER, DEFAULT_UPDATE_SERVER); + switch (value) { + case 1: + return "qiniu"; + case 0: + default: + return "google"; + } + } + + public static boolean getAllowAnalyics() { + return getBoolean(KEY_ALLOW_ANALYTICS, DEFAULT_ALLOW_ANALYTICS); + } + + public static void setAllowAnalyics(boolean setAnalyics) { + setBoolean(KEY_ALLOW_ANALYTICS, setAnalyics); + } + + + /****** Mode an API Mode ******/ + + private static final String KEY_MODE = "mode"; + private static final int DEFAULT_MODE = EhClient.MODE_G; + + private static final String KEY_API_MODE = "api_mode"; + private static final int DEFAULT_API_MODE = EhClient.MODE_G; + + private static final String KEY_LOFI_RESOLUTION = "lofi_resolution"; + private static final int DEFAULT_LOFI_RESOLUTION = 1; + + public static int getMode() { + return getInt(KEY_MODE, DEFAULT_MODE); + } + + public static void setMode(int mode) { + setInt(KEY_MODE, mode); + } + + public static int getApiMode() { + return getInt(KEY_API_MODE, DEFAULT_API_MODE); + } + + public static void setApiMode(int apiMode) { + setInt(KEY_API_MODE, apiMode); + } + + public static int getLofiResolution() { + return getInt(KEY_LOFI_RESOLUTION, DEFAULT_LOFI_RESOLUTION); + } + + public static void setLofiResolution(int lofiResolution) { + setInt(KEY_LOFI_RESOLUTION, lofiResolution); + } + + + // Proxy urls + private static final String PROXY_URLS_KEY = "proxy_urls"; + public static final String[] DEFAULT_PROXY_URLS = { + "http://proxyy0000.appsp0t.com/proxy", + "http://proxyy0001.appsp0t.com/proxy", + "http://proxyy0002.appsp0t.com/proxy", + "http://proxyy0003.appsp0t.com/proxy", + "http://proxyy0004.appsp0t.com/proxy", + "http://proxyy0005.appsp0t.com/proxy", + "http://proxyy0006.appsp0t.com/proxy", + "http://proxyy0007.appsp0t.com/proxy", + "http://proxyy0008.appsp0t.com/proxy", + "http://proxyy0009.appsp0t.com/proxy", + "http://proxyy000a.appsp0t.com/proxy", + "http://proxyy000b.appsp0t.com/proxy", + "http://proxyy000c.appsp0t.com/proxy", + "http://proxyy000d.appsp0t.com/proxy", + "http://proxyy000e.appsp0t.com/proxy", + "http://proxyy000f.appsp0t.com/proxy" + }; + + public static String[] getProxyUrls() { + Set re = mConfigPre.getStringSet(PROXY_URLS_KEY, null); + if (re != null && re.size() > 0) + return re.toArray(new String[re.size()]); + else + return DEFAULT_PROXY_URLS; + } + + public static void setProxyUrls(String[] urls) { + mConfigPre.edit().putStringSet(PROXY_URLS_KEY, new HashSet(Arrays.asList(urls))).apply(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/Constants.java b/app/src/main/java/com/hippo/ehviewer/util/Constants.java new file mode 100644 index 000000000..407ef371a --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/Constants.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +public final class Constants { + + public static final int FALSE = 0; + public static final int TRUE = 1; + + public static final int ANIMATE_TIME = 300; + + public static final int HTTP_TEMP_REDIRECT = 307; + public static String defaultUserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.76 Safari/537.36"; + public static String userAgent = System.getProperty("http.agent", defaultUserAgent); + public static final int DEFAULT_TIMEOUT = 5 * 1000; + public static final int MAX_REDIRECTS = 5; + + public static final int BUFFER_SIZE = 4096; +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/Crash.java b/app/src/main/java/com/hippo/ehviewer/util/Crash.java new file mode 100644 index 000000000..f0e322fe2 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/Crash.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Build; +import android.os.Environment; + +public class Crash { + private static final String TAG = "Crash"; + + private static Context mContext; + + private static String externalCrashFilePath = "/EhViewer/crash"; + private static String internalCrashFilePath = "/crash"; + + private static final String NEW_CRAHS = "new_crash"; + private static final String LAST_CRASH_POSITION = "last_crash_position"; + private static final String LAST_CRASH_NAME = "last_crash_name"; + + private static boolean mInit = false; + + /** + * Init Crash + * + * @param context Application context + */ + public static void init(Context context) { + if (mInit) + return; + mInit = true; + + mContext = context; + } + + /** + * Is init + * @return True if init + */ + public static boolean isInit() { + return mInit; + } + + public static final String getThrowableInfo(Throwable t) { + Writer writer = new StringWriter(); + PrintWriter printWriter = new PrintWriter(writer); + t.printStackTrace(printWriter); + Throwable cause = t.getCause(); + while (cause != null) { + cause.printStackTrace(printWriter); + cause = cause.getCause(); + } + printWriter.close(); + return writer.toString(); + } + + /** + * Save throwable infomation to file + * + * @param ex The throwable to store + */ + public static void saveCrashInfo2File(Throwable ex) { + StringBuffer sb = new StringBuffer(); + collectDeviceInfo(sb); + + String result = getThrowableInfo(ex); + Log.e(TAG, result); + sb.append("======== CrashInfo ========\n").append(result).append("\n"); + try { + long timestamp = System.currentTimeMillis(); + String time = Utils.sDate.format(System.currentTimeMillis()); + String fileName = "crash-" + time + "-" + timestamp + ".log"; + String path = null; + boolean position = false; + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + path = Environment.getExternalStorageDirectory() + externalCrashFilePath; + position = true; + } + else { + path = mContext.getFilesDir() + internalCrashFilePath; + position = false; + } + File dir = new File(path); + if (!dir.exists()) { + dir.mkdirs(); + } + FileOutputStream fos = new FileOutputStream(new File(path, fileName)); + fos.write(sb.toString().getBytes()); + fos.close(); + // Record info for next time upload + SharedPreferences configPre = mContext.getSharedPreferences("config", 0); + SharedPreferences.Editor editor = configPre.edit(); + editor.putBoolean(NEW_CRAHS, true); + editor.putBoolean(LAST_CRASH_POSITION, position); + editor.putString(LAST_CRASH_NAME, fileName); + editor.commit(); + } catch (Exception e) { + Log.e(TAG, "An error occured while writing crash file...", e); + } + } + + private static void collectDeviceInfo(StringBuffer sb) { + try { + PackageManager pm = mContext.getPackageManager(); + PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES); + if (pi != null) { + String versionName = pi.versionName == null ? "null" : pi.versionName; + String versionCode = String.valueOf(pi.versionCode); + sb.append("======== PackageInfo ========\n"); + sb.append("PackageName=" + pi.packageName + "\n"); + sb.append("VersionName=" + versionName + "\n"); + sb.append("VersionCode=" + versionCode + "\n"); + sb.append("\n"); + } + } catch (NameNotFoundException e) { + Log.e(TAG, "an error occured when collect package info", e); + } + sb.append("======== DeviceInfo ========\n"); + sb.append("BOARD=" + Build.BOARD + "\n"); + sb.append("BOOTLOADER=" + Build.BOOTLOADER + "\n"); + sb.append("CPU_ABI=" + Build.CPU_ABI + "\n"); + sb.append("CPU_ABI2=" + Build.CPU_ABI2 + "\n"); + sb.append("DEVICE=" + Build.DEVICE + "\n"); + sb.append("DISPLAY=" + Build.DISPLAY + "\n"); + sb.append("FINGERPRINT=" + Build.FINGERPRINT + "\n"); + sb.append("HARDWARE=" + Build.HARDWARE + "\n"); + sb.append("HOST=" + Build.HOST + "\n"); + sb.append("ID=" + Build.ID + "\n"); + sb.append("MANUFACTURER=" + Build.MANUFACTURER + "\n"); + sb.append("MODEL=" + Build.MODEL + "\n"); + sb.append("PRODUCT=" + Build.PRODUCT + "\n"); + sb.append("RADIO=" + Build.getRadioVersion() + "\n"); + sb.append("SERIAL=" + Build.SERIAL + "\n"); + sb.append("TAGS=" + Build.TAGS + "\n"); + sb.append("TYPE=" + Build.TYPE + "\n"); + sb.append("USER=" + Build.USER + "\n"); + sb.append("CODENAME=" + Build.VERSION.CODENAME + "\n"); + sb.append("INCREMENTAL=" + Build.VERSION.INCREMENTAL + "\n"); + sb.append("RELEASE=" + Build.VERSION.RELEASE + "\n"); + sb.append("SDK=" + Build.VERSION.SDK_INT + "\n"); + sb.append("\n"); + } + + /** + * Check is there last crash + * + * @return Return the crash String or null if there is no + */ + public static String getLastCrash() { + SharedPreferences configPre = mContext.getSharedPreferences("config", 0); + if (!configPre.getBoolean(NEW_CRAHS, false)) + return null; + + boolean position = configPre.getBoolean(LAST_CRASH_POSITION, false); + String lastCrashName; + if ((lastCrashName = configPre.getString(LAST_CRASH_NAME, null)) == null) + return null; + + File lastCrashFile; + if (position) { + if(!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) + return null; + lastCrashFile = new File(Environment.getExternalStorageDirectory() + externalCrashFilePath, lastCrashName); + } + else { + lastCrashFile = new File(mContext.getFilesDir() + internalCrashFilePath, lastCrashName); + } + if (!lastCrashFile.isFile()) + return null; + + try { + byte[] buffer = new byte[(int) lastCrashFile.length()]; + DataInputStream din = new DataInputStream(new FileInputStream(lastCrashFile)); + din.readFully(buffer); + din.close(); + configPre.edit().putBoolean(NEW_CRAHS, false).commit(); + return new String(buffer); + } catch (Exception e) { + Log.e(TAG, "An error occured while reading crash file...", e); + } + return null; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/DateUtils.java b/app/src/main/java/com/hippo/ehviewer/util/DateUtils.java new file mode 100644 index 000000000..a6f8b22cd --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/DateUtils.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +import android.text.format.DateFormat; + +public final class DateUtils { + + @SuppressWarnings("deprecation") + public static boolean hasSeconds(CharSequence inFormat) { + return hasDesignator(inFormat, DateFormat.SECONDS); + } + + + @SuppressWarnings("deprecation") + public static boolean hasDesignator(CharSequence inFormat, char designator) { + if (inFormat == null) return false; + + final int length = inFormat.length(); + + int c; + int count; + + for (int i = 0; i < length; i += count) { + count = 1; + c = inFormat.charAt(i); + + if (c == DateFormat.QUOTE) { + count = skipQuotedText(inFormat, i, length); + } else if (c == designator) { + return true; + } + } + + return false; + } + + @SuppressWarnings("deprecation") + private static int skipQuotedText(CharSequence s, int i, int len) { + if (i + 1 < len && s.charAt(i + 1) == DateFormat.QUOTE) { + return 2; + } + + int count = 1; + // skip leading quote + i++; + + while (i < len) { + char c = s.charAt(i); + + if (c == DateFormat.QUOTE) { + count++; + // QUOTEQUOTE -> QUOTE + if (i + 1 < len && s.charAt(i + 1) == DateFormat.QUOTE) { + i++; + } else { + break; + } + } else { + i++; + count++; + } + } + + return count; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/DialogUtils.java b/app/src/main/java/com/hippo/ehviewer/util/DialogUtils.java new file mode 100644 index 000000000..d3f06805f --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/DialogUtils.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +import android.content.Context; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.app.MaterialAlertDialog; + +public class DialogUtils { + + public static MaterialAlertDialog.Builder createUpdateDialog(Context context, String version, String size, + String info, MaterialAlertDialog.OnClickListener l) { + + return new MaterialAlertDialog.Builder(context).setTitle(R.string.update).setCancelable(false) + .setMessage(String.format(context.getString(R.string.update_message), version, size, info)) + .setPositiveButton(android.R.string.ok).setNegativeButton(android.R.string.cancel).setButtonListener(l); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/EhUtils.java b/app/src/main/java/com/hippo/ehviewer/util/EhUtils.java new file mode 100644 index 000000000..1544845e0 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/EhUtils.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +import java.io.File; + +import com.hippo.ehviewer.data.ListUrls; + +public final class EhUtils { + + public static final String EH_DOWNLOAD_FILENAME = ".ehviewer"; + + private static final int[] CATEGORY_VALUES = { ListUrls.MISC, ListUrls.DOUJINSHI, ListUrls.MANGA, + ListUrls.ARTIST_CG, ListUrls.GAME_CG, ListUrls.IMAGE_SET, ListUrls.COSPLAY, ListUrls.ASIAN_PORN, + ListUrls.NON_H, ListUrls.WESTERN, ListUrls.UNKNOWN }; + + // TODO How about "Private" + private static final String[][] CATEGORY_STRINGS = { new String[] { "misc" }, new String[] { "doujinshi" }, + new String[] { "manga" }, new String[] { "artistcg", "Artist CG Sets" }, + new String[] { "gamecg", "Game CG Sets" }, new String[] { "imageset", "Image Sets" }, + new String[] { "cosplay" }, new String[] { "asianporn", "Asian Porn" }, new String[] { "non-h" }, + new String[] { "western" }, new String[] { "unknown" } }; + + public static int getCategory(String type) { + int i; + for (i = 0; i < CATEGORY_STRINGS.length - 1; i++) { + for (String str : CATEGORY_STRINGS[i]) + if (str.equalsIgnoreCase(type)) + return CATEGORY_VALUES[i]; + } + + return CATEGORY_VALUES[i]; + } + + public static String getCategory(int type) { + int i; + for (i = 0; i < CATEGORY_VALUES.length - 1; i++) { + if (CATEGORY_VALUES[i] == type) + break; + } + return CATEGORY_STRINGS[i][0]; + } + + /** + * Get gallery for read and download Gid is constant, but title may be + * changed + * + * @param gid + * @param title + * @return + */ + public static File getGalleryDir(int gid, String title) { + + File downloadDir = new File(Config.getDownloadPath()); + File gDir = new File(downloadDir, Utils.standardizeFilename(Integer.toString(gid))); + File defaultDir = generateGalleryDir(gid, title); + + if (gDir.exists()) { + if (gDir.renameTo(defaultDir)) + return defaultDir; + else + return gDir; + } else { + // TODO I need a better way or only call it in non-UI thread + String[] fileList = downloadDir.list(); + if (fileList != null) { + for (String str : fileList) { + // For title changed + if (str.startsWith(gid + "-") && (gDir = new File(downloadDir, str)) != null && gDir.isDirectory()) + return gDir; + } + } + + return defaultDir; + } + } + + public static File generateGalleryDir(int gid, String title) { + return new File(Config.getDownloadPath(), Utils.standardizeFilename(gid + "-" + title)); + } + + /** + * Index start from 0 + * + * @param index + * @param extension + * @return + */ + public static String getImageFilename(int index, String extension) { + return String.format("%08d.%s", index + 1, extension); + } + + public static String[] POSSIBLE_IMAGE_EXTENSION_ARRAY = new String[] { + "jpg", "jpeg", "png", "gif" + }; + + public static String DEFAULT_IMAGE_EXTENSION = POSSIBLE_IMAGE_EXTENSION_ARRAY[0]; + + public static String[] getPossibleImageFilenames(int index) { + String prefix = String.format("%08d.", index + 1); + + int length = POSSIBLE_IMAGE_EXTENSION_ARRAY.length; + String[] strs = new String[length]; + for (int i = 0; i < length; i++) + strs[i] = prefix + POSSIBLE_IMAGE_EXTENSION_ARRAY[i]; + + return strs; + } + +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/FastByteArrayOutputStream.java b/app/src/main/java/com/hippo/ehviewer/util/FastByteArrayOutputStream.java new file mode 100644 index 000000000..d629a11c0 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/FastByteArrayOutputStream.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +public class FastByteArrayOutputStream extends OutputStream { + /** + * The byte array containing the bytes written. + */ + protected byte[] buf; + + /** + * The number of bytes written. + */ + protected int count; + + /** + * Constructs a new ByteArrayOutputStream with a default size of 32 bytes. + * If more than 32 bytes are written to this instance, the underlying byte + * array will expand. + */ + public FastByteArrayOutputStream() { + buf = new byte[32]; + } + + /** + * Constructs a new {@code ByteArrayOutputStream} with a default size of + * {@code size} bytes. If more than {@code size} bytes are written to this + * instance, the underlying byte array will expand. + * + * @param size + * initial size for the underlying byte array, must be + * non-negative. + * @throws IllegalArgumentException + * if {@code size} < 0. + */ + public FastByteArrayOutputStream(int size) { + if (size >= 0) { + buf = new byte[size]; + } else { + throw new IllegalArgumentException("size < 0"); + } + } + + /** + * Closes this stream. This releases system resources used for this stream. + * + * @throws IOException + * if an error occurs while attempting to close this stream. + */ + @Override + public void close() throws IOException { + /** + * Although the spec claims "A closed stream cannot perform output + * operations and cannot be reopened.", this implementation must do + * nothing. + */ + super.close(); + } + + private void expand(int i) { + /* Can the buffer handle @i more bytes, if not expand it */ + if (count + i <= buf.length) { + return; + } + + byte[] newbuf = new byte[(count + i) * 2]; + System.arraycopy(buf, 0, newbuf, 0, count); + buf = newbuf; + } + + /** + * Resets this stream to the beginning of the underlying byte array. All + * subsequent writes will overwrite any bytes previously stored in this + * stream. + */ + public synchronized void reset() { + count = 0; + } + + /** + * Returns the total number of bytes written to this stream so far. + * + * @return the number of bytes written to this stream. + */ + public int size() { + return count; + } + + /** + * Returns the contents of this ByteArrayOutputStream as a byte array. Any + * changes made to the receiver after returning will not be reflected in the + * byte array returned to the caller. + * + * @return this stream's current contents as a byte array. + */ + public synchronized byte[] getBuffer() { + return buf; + } + + /** + * Returns the contents of this ByteArrayOutputStream as a string. Any + * changes made to the receiver after returning will not be reflected in the + * string returned to the caller. + * + * @return this stream's current contents as a string. + */ + + @Override + public String toString() { + return new String(buf, 0, count); + } + + /** + * Returns the contents of this ByteArrayOutputStream as a string. Each byte + * {@code b} in this stream is converted to a character {@code c} using the + * following function: + * {@code c == (char)(((hibyte & 0xff) << 8) | (b & 0xff))}. This method is + * deprecated and either {@link #toString()} or {@link #toString(String)} + * should be used. + * + * @param hibyte + * the high byte of each resulting Unicode character. + * @return this stream's current contents as a string with the high byte set + * to {@code hibyte}. + * @deprecated Use {@link #toString()} instead. + */ + @Deprecated + public String toString(int hibyte) { + char[] newBuf = new char[size()]; + for (int i = 0; i < newBuf.length; i++) { + newBuf[i] = (char) (((hibyte & 0xff) << 8) | (buf[i] & 0xff)); + } + return new String(newBuf); + } + + /** + * Returns the contents of this ByteArrayOutputStream as a string converted + * according to the encoding declared in {@code charsetName}. + * + * @param charsetName + * a string representing the encoding to use when translating + * this stream to a string. + * @return this stream's current contents as an encoded string. + * @throws UnsupportedEncodingException + * if the provided encoding is not supported. + */ + public String toString(String charsetName) throws UnsupportedEncodingException { + return new String(buf, 0, count, charsetName); + } + + /** + * Writes {@code count} bytes from the byte array {@code buffer} starting at + * offset {@code index} to this stream. + * + * @param buffer + * the buffer to be written. + * @param offset + * the initial position in {@code buffer} to retrieve bytes. + * @param len + * the number of bytes of {@code buffer} to write. + * @throws NullPointerException + * if {@code buffer} is {@code null}. + * @throws IndexOutOfBoundsException + * if {@code offset < 0} or {@code len < 0}, or if + * {@code offset + len} is greater than the length of + * {@code buffer}. + */ + @Override + public synchronized void write(byte[] buffer, int offset, int len) { + if ((offset | count) < 0 || offset > buffer.length || buffer.length - offset < count) { + throw new ArrayIndexOutOfBoundsException("length=" + buffer.length + "; regionStart=" + offset + + "; regionLength=" + count); + } + if (len == 0) { + return; + } + expand(len); + System.arraycopy(buffer, offset, buf, this.count, len); + this.count += len; + } + + /** + * Writes the specified byte {@code oneByte} to the OutputStream. Only the + * low order byte of {@code oneByte} is written. + * + * @param oneByte + * the byte to be written. + */ + @Override + public synchronized void write(int oneByte) { + if (count == buf.length) { + expand(1); + } + buf[count++] = (byte) oneByte; + } + + /** + * Takes the contents of this stream and writes it to the output stream + * {@code out}. + * + * @param out + * an OutputStream on which to write the contents of this stream. + * @throws IOException + * if an error occurs while writing to {@code out}. + */ + public synchronized void writeTo(OutputStream out) throws IOException { + out.write(buf, 0, count); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/Favorite.java b/app/src/main/java/com/hippo/ehviewer/util/Favorite.java new file mode 100644 index 000000000..a75afa76f --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/Favorite.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.content.Context; +import android.content.res.Resources; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.ListView; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.app.MaterialAlertDialog; +import com.hippo.ehviewer.data.Data; +import com.hippo.ehviewer.data.GalleryInfo; +import com.hippo.ehviewer.ehclient.EhClient; +import com.hippo.ehviewer.widget.MaterialToast; + +public class Favorite { + + private static Context mContext; + private static Resources mResources; + + private static AlertDialog lastAddToFavoriteDialog; + + public static String[] FAVORITE_TITLES; + + public static void init(Context context) { + mContext = context; + mResources = mContext.getResources(); + + FAVORITE_TITLES = new String[EhClient.FAVORITE_SLOT_NUM + 1]; + FAVORITE_TITLES[0] = mResources.getString(R.string.local_favorite); + for (int i = 1; i < EhClient.FAVORITE_SLOT_NUM + 1; i++) { + FAVORITE_TITLES[i] = mResources.getString(R.string.favourite) + " "+ (i - 1); + } + } + + public static void addToFavorite(final Context context, GalleryInfo gi) { + int defaultFavorite = Config.getDefaultFavorite(); + switch (defaultFavorite) { + case -2: + Favorite.getAddToFavoriteDialog(context, gi).show(); + break; + case -1: + Data.getInstance().addLocalFavourite(gi); + MaterialToast.showToast(R.string.toast_add_favourite); + break; + default: + EhClient.getInstance().addToFavorite(gi.gid, + gi.token, defaultFavorite, null, new EhClient.OnAddToFavoriteListener() { + @Override + public void onSuccess() { + MaterialToast.showToast(R.string.toast_add_favourite); + } + @Override + public void onFailure(String eMsg) { + MaterialToast.showToast(R.string.failed_to_add); + } + }); + } + } + + @SuppressLint("InflateParams") + public static AlertDialog getAddToFavoriteDialog(final Context context, final GalleryInfo gi) { + + // TODO List do not work final + + View view = ViewUtils.inflateDialogView(R.layout.add_to_favorite, false); + final CheckBox cb = (CheckBox) view.findViewById(R.id.set_default); + ListView lv = (ListView) view.findViewById(R.id.list); + lv.setAdapter(new ArrayAdapter(mContext, + R.layout.select_dialog_item, FAVORITE_TITLES)); + lv.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, + int position, long id) { + lastAddToFavoriteDialog.dismiss(); + lastAddToFavoriteDialog = null; + + if (cb.isChecked()) + Config.setDefaultFavorite(position - 1); + + switch (position) { + case 0: + Data.getInstance().addLocalFavourite(gi); + MaterialToast.showToast(R.string.toast_add_favourite); + break; + default: + EhClient.getInstance().addToFavorite(gi.gid, + gi.token, position - 1, null, new EhClient.OnAddToFavoriteListener() { + @Override + public void onSuccess() { + MaterialToast.showToast(R.string.toast_add_favourite); + } + @Override + public void onFailure(String eMsg) { + MaterialToast.showToast(R.string.failed_to_add); + } + }); + } + } + }); + + return lastAddToFavoriteDialog = new MaterialAlertDialog.Builder(context) + .setTitle(R.string.where_to_add) + .setView(view, false).setNegativeButton(android.R.string.cancel) + .setPositiveButton(android.R.string.ok).create(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/FullScreenHelper.java b/app/src/main/java/com/hippo/ehviewer/util/FullScreenHelper.java new file mode 100644 index 000000000..e5aa371dd --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/FullScreenHelper.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.os.Build; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.Window; +import android.view.WindowManager; + +@SuppressLint("InlinedApi") +public final class FullScreenHelper implements View.OnSystemUiVisibilityChangeListener { + + private static final String TAG = FullScreenHelper.class.getSimpleName(); + + private final int NOT_FULL_SCREEN_JELLY_BEAN = + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | + View.SYSTEM_UI_FLAG_LAYOUT_STABLE; + + private final int FULL_SCREEN_JELLY_BEAN = + NOT_FULL_SCREEN_JELLY_BEAN | + View.SYSTEM_UI_FLAG_LOW_PROFILE | + View.SYSTEM_UI_FLAG_FULLSCREEN; + + private final int FULL_SCREEN_FLAG_JELLY_BEAN = + View.SYSTEM_UI_FLAG_LOW_PROFILE | + View.SYSTEM_UI_FLAG_FULLSCREEN; + + private final int NOT_FULL_SCREEN_KITKAT = + NOT_FULL_SCREEN_JELLY_BEAN | + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + + private final int FULL_SCREEN_KITKAT = + NOT_FULL_SCREEN_KITKAT | + View.SYSTEM_UI_FLAG_LOW_PROFILE | + View.SYSTEM_UI_FLAG_FULLSCREEN | + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + + private boolean mFullScreen; + private final Activity mActivity; + private final Window mWindow; + private final View mDecorView; + + private boolean mHasNavBar; + + private OnFullScreenBrokenListener mListener; + + private final int TEST_SDK = Build.VERSION.SDK_INT; + + public interface OnFullScreenBrokenListener { + /** + * FullScreen state should be fullScreen or not, + * but user or system broke it + * + * @param fullScreen support to be + */ + public void onFullScreenBroken(boolean fullScreen); + } + + public FullScreenHelper(Activity activity) { + mFullScreen = false; + mActivity = activity; + mWindow = mActivity.getWindow(); + mDecorView = mWindow.getDecorView(); + mDecorView.setOnSystemUiVisibilityChangeListener(this); + + String mainKey = Utils.getSystemProperties("qemu.hw.mainkeys"); + int resourceId = activity.getResources().getIdentifier("config_showNavigationBar", "bool", "android"); + if (resourceId != 0) { + mHasNavBar = activity.getResources().getBoolean(resourceId); + // check override flag (see static block) + if ("1".equals(mainKey)) { + mHasNavBar = false; + } else if ("0".equals(mainKey)) { + mHasNavBar = true; + } + } else { + mHasNavBar = !ViewConfiguration.get(activity).hasPermanentMenuKey(); + } + } + + public boolean willHideNavBar() { + if (TEST_SDK >= Build.VERSION_CODES.KITKAT && mHasNavBar) + return true; + else + return false; + } + + @Override + public void onSystemUiVisibilityChange(int visibility) { + if (TEST_SDK >= Build.VERSION_CODES.JELLY_BEAN && + TEST_SDK < Build.VERSION_CODES.KITKAT) { + if ((mFullScreen && visibility != FULL_SCREEN_FLAG_JELLY_BEAN) || + (!mFullScreen && visibility != 0)) { + // User or system change visibility + if (mListener != null) + mListener.onFullScreenBroken(mFullScreen); + } + } + } + + public void setOnFullScreenBrokenListener(OnFullScreenBrokenListener l) { + mListener = l; + } + + public boolean getFullScreen() { + return mFullScreen; + } + + public void setFullScreen(boolean fullScreen) { + mFullScreen = fullScreen; + if (fullScreen) { + if (TEST_SDK < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + // Empty + } else if (TEST_SDK < Build.VERSION_CODES.JELLY_BEAN) { + mWindow.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + + } else if (TEST_SDK < Build.VERSION_CODES.KITKAT) { + mDecorView.setSystemUiVisibility(FULL_SCREEN_JELLY_BEAN); + + } else { + mDecorView.setSystemUiVisibility(FULL_SCREEN_KITKAT); + } + } else { + if (TEST_SDK < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + // Empty + } else if (TEST_SDK < Build.VERSION_CODES.JELLY_BEAN) { + mWindow.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + + } else if (TEST_SDK < Build.VERSION_CODES.KITKAT) { + mDecorView.setSystemUiVisibility(NOT_FULL_SCREEN_JELLY_BEAN); + + } else { + mDecorView.setSystemUiVisibility(NOT_FULL_SCREEN_KITKAT); + } + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/IntArray.java b/app/src/main/java/com/hippo/ehviewer/util/IntArray.java new file mode 100644 index 000000000..b107cbff6 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/IntArray.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +public class IntArray { + private static final int INIT_CAPACITY = 8; + + private int mData[] = new int[INIT_CAPACITY]; + private int mSize = 0; + + public void add(int value) { + if (mData.length == mSize) { + int temp[] = new int[mSize + mSize]; + System.arraycopy(mData, 0, temp, 0, mSize); + mData = temp; + } + mData[mSize++] = value; + } + + public int removeLast() { + mSize--; + return mData[mSize]; + } + + public int size() { + return mSize; + } + + // For testing only + public int[] toArray(int[] result) { + if (result == null || result.length < mSize) { + result = new int[mSize]; + } + System.arraycopy(mData, 0, result, 0, mSize); + return result; + } + + public int[] getInternalArray() { + return mData; + } + + public void clear() { + mSize = 0; + if (mData.length != INIT_CAPACITY) mData = new int[INIT_CAPACITY]; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/Log.java b/app/src/main/java/com/hippo/ehviewer/util/Log.java new file mode 100644 index 000000000..90b1b25d6 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/Log.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +import android.content.Context; + +import com.hippo.ehviewer.BuildConfig; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +public final class Log { + private static final String NULL = "null"; + private static final String LOG_FILENAME = "log.log"; + + private static final Object sLock = new Object(); + + private static File sLogFile; + + public static void init(Context context) { + if (Config.sExternalDir != null) + sLogFile = new File(Config.sExternalDir, LOG_FILENAME); + else + sLogFile = new File(context.getFilesDir(), LOG_FILENAME); + } + + public static void f(final String msg) { + new BgThread() { + @Override + public void run() { + synchronized(sLock) { + try { + FileWriter fw = new FileWriter(sLogFile, true); + fw.append(Utils.sDate.format(System.currentTimeMillis())) + .append(": ").append(msg).append("\n"); + fw.flush(); + fw.close(); + } catch (IOException e) {} + } + } + }.start(); + } + + public static void f(final Throwable tr) { + new BgThread() { + @Override + public void run() { + synchronized(sLock) { + try { + FileWriter fw = new FileWriter(sLogFile, true); + fw.append(Utils.sDate.format(System.currentTimeMillis())) + .append(": ").append(Crash.getThrowableInfo(tr)).append("\n"); + fw.flush(); + fw.close(); + } catch (IOException e) {} + } + } + }.start(); + } + + public static int v(String tag, String msg) { + if (BuildConfig.DEBUG) + return android.util.Log.v(tag, msg == null ? NULL : msg); + else + return 0; + } + + public static int v(String tag, String msg, Throwable tr) { + if (BuildConfig.DEBUG) + return android.util.Log.v(tag, msg == null ? NULL : msg, tr); + else + return 0; + } + + public static int d(String tag, String msg) { + if (BuildConfig.DEBUG) + return android.util.Log.d(tag, msg == null ? NULL : msg); + else + return 0; + } + + public static int d(String tag, String msg, Throwable tr) { + if (BuildConfig.DEBUG) + return android.util.Log.d(tag, msg == null ? NULL : msg, tr); + else + return 0; + } + + public static int i(String tag, String msg) { + if (BuildConfig.DEBUG) + return android.util.Log.i(tag, msg == null ? NULL : msg); + else + return 0; + } + + public static int i(String tag, String msg, Throwable tr) { + if (BuildConfig.DEBUG) + return android.util.Log.i(tag, msg == null ? NULL : msg, tr); + else + return 0; + } + + public static int w(String tag, String msg) { + if (BuildConfig.DEBUG) + return android.util.Log.w(tag, msg == null ? NULL : msg); + else + return 0; + } + + public static int w(String tag, String msg, Throwable tr) { + if (BuildConfig.DEBUG) + return android.util.Log.w(tag, msg == null ? NULL : msg, tr); + else + return 0; + } + + public static int w(String tag, Throwable tr) { + return android.util.Log.w(tag, tr); + } + + public static int e(String tag, String msg) { + if (BuildConfig.DEBUG) + return android.util.Log.e(tag, msg == null ? NULL : msg); + else + return 0; + } + + public static int e(String tag, String msg, Throwable tr) { + if (BuildConfig.DEBUG) + return android.util.Log.e(tag, msg == null ? NULL : msg, tr); + else + return 0; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/MathUtils.java b/app/src/main/java/com/hippo/ehviewer/util/MathUtils.java new file mode 100644 index 000000000..634380195 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/MathUtils.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +import java.util.Random; + +// Get code from android.util.MathUtils +public final class MathUtils { + private static final Random sRandom = new Random(); + private static final float DEG_TO_RAD = 3.1415926f / 180.0f; + private static final float RAD_TO_DEG = 180.0f / 3.1415926f; + + public static float abs(float v) { + return v > 0 ? v : -v; + } + + public static float log(float a) { + return (float) Math.log(a); + } + + public static float exp(float a) { + return (float) Math.exp(a); + } + + public static float pow(float a, float b) { + return (float) Math.pow(a, b); + } + + public static float max(float a, float b) { + return a > b ? a : b; + } + + public static float max(int a, int b) { + return a > b ? a : b; + } + + public static float max(float a, float b, float c) { + return a > b ? (a > c ? a : c) : (b > c ? b : c); + } + + public static float max(int a, int b, int c) { + return a > b ? (a > c ? a : c) : (b > c ? b : c); + } + + public static float min(float a, float b) { + return a < b ? a : b; + } + + public static float min(int a, int b) { + return a < b ? a : b; + } + + public static float min(float a, float b, float c) { + return a < b ? (a < c ? a : c) : (b < c ? b : c); + } + + public static float min(int a, int b, int c) { + return a < b ? (a < c ? a : c) : (b < c ? b : c); + } + + public static float dist(float x1, float y1, float x2, float y2) { + final float x = (x2 - x1); + final float y = (y2 - y1); + return (float) Math.hypot(x, y); + } + + public static float dist(float x1, float y1, float z1, float x2, float y2, float z2) { + final float x = (x2 - x1); + final float y = (y2 - y1); + final float z = (z2 - z1); + return (float) Math.sqrt(x * x + y * y + z * z); + } + + public static float mag(float a, float b) { + return (float) Math.hypot(a, b); + } + + public static float mag(float a, float b, float c) { + return (float) Math.sqrt(a * a + b * b + c * c); + } + + public static float sq(float v) { + return v * v; + } + + public static float radians(float degrees) { + return degrees * DEG_TO_RAD; + } + + public static float degrees(float radians) { + return radians * RAD_TO_DEG; + } + + public static float acos(float value) { + return (float) Math.acos(value); + } + + public static float asin(float value) { + return (float) Math.asin(value); + } + + public static float atan(float value) { + return (float) Math.atan(value); + } + + public static float atan2(float a, float b) { + return (float) Math.atan2(a, b); + } + + public static float tan(float angle) { + return (float) Math.tan(angle); + } + + public static int lerp(int start, int stop, float amount) { + return start + (int) ((stop - start) * amount); + } + + public static float lerp(float start, float stop, float amount) { + return start + (stop - start) * amount; + } + + public static float norm(float start, float stop, float value) { + return (value - start) / (stop - start); + } + + public static float map(float minStart, float minStop, float maxStart, float maxStop, float value) { + return maxStart + (maxStart - maxStop) * ((value - minStart) / (minStop - minStart)); + } + + /** + * Returns the input value x clamped to the range [min, max]. + * + * @param x + * @param min + * @param max + * @return + */ + public static int clamp(int x, int min, int max) { + if (x > max) return max; + if (x < min) return min; + return x; + } + + /** + * Returns the input value x clamped to the range [min, max]. + * + * @param x + * @param min + * @param max + * @return + */ + public static float clamp(float x, float min, float max) { + if (x > max) return max; + if (x < min) return min; + return x; + } + + /** + * Returns the input value x clamped to the range [min, max]. + * @param x + * @param min + * @param max + * @return + */ + public static long clamp(long x, long min, long max) { + if (x > max) return max; + if (x < min) return min; + return x; + } + + /** + * Returns the next power of two. + * Returns the input if it is already power of 2. + * Throws IllegalArgumentException if the input is <= 0 or + * the answer overflows. + * + * @param n + * @return + */ + public static int nextPowerOf2(int n) { + if (n <= 0 || n > (1 << 30)) throw new IllegalArgumentException("n is invalid: " + n); + n -= 1; + n |= n >> 16; + n |= n >> 8; + n |= n >> 4; + n |= n >> 2; + n |= n >> 1; + return n + 1; + } + + public static boolean isSameSide(float a, float b, float standard) { + float offset1 = standard - a; + float offset2 = standard - b; + return (offset1 >= 0 && offset2 >= 0) || (offset1 >= 0 && offset2 >= 0); + } + + public static boolean closer(float a, float b, float standard) { + float offset1 = standard - a; + float offset2 = standard - b; + if ((offset1 >= 0 && offset2 >= 0) || + (offset1 >= 0 && offset2 >= 0)) { + return Math.abs(offset1) < Math.abs(offset2); + } else { + float aa = a / standard; + float bb = b / standard; + aa = aa > 1.0f ? 1 / aa : aa; + bb = bb > 1.0f ? 1 / bb : bb; + return aa > bb; + } + } + + public static int random(int howbig) { + return (int) (sRandom.nextFloat() * howbig); + } + + public static int random(int howsmall, int howbig) { + if (howsmall >= howbig) + return howsmall; + return (int) (sRandom.nextFloat() * (howbig - howsmall) + howsmall); + } + + public static float random(float howbig) { + return sRandom.nextFloat() * howbig; + } + + public static float random(float howsmall, float howbig) { + if (howsmall >= howbig) + return howsmall; + return sRandom.nextFloat() * (howbig - howsmall) + howsmall; + } + + public static void randomSeed(long seed) { + sRandom.setSeed(seed); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/Secret.java b/app/src/main/java/com/hippo/ehviewer/util/Secret.java new file mode 100644 index 000000000..26bd27e0c --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/Secret.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +import java.io.File; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; + +import com.hippo.ehviewer.network.HttpHelper; + +public final class Secret { + + private static final String DIR_NAME = "images"; + private static final String IMAGE_NAME = "sercet.jpg"; + private static final String SERCET_IMAGE_URL = "http://www.ehviewer.com/static/images/pandas.jpg"; + + private static volatile boolean sIsGetSecretImage = false; + + public static void updateSecretImage(Context context) { + if (sIsGetSecretImage) + return; + sIsGetSecretImage = true; + + new UpdateSercetImageThread(context).start(); + } + + public static boolean hasSecretImage(Context context) { + if (sIsGetSecretImage) + return false; + + File dir = context.getExternalFilesDir(DIR_NAME); + File image = new File(dir, IMAGE_NAME); + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inJustDecodeBounds = true; + BitmapFactory.decodeFile(image.getAbsolutePath(), opts); + return opts.outWidth > 0 && opts.outHeight > 0; + } + + public static Bitmap getSecretImage(Context context) { + if (sIsGetSecretImage) + return null; + + File dir = context.getExternalFilesDir(DIR_NAME); + File image = new File(dir, IMAGE_NAME); + return BitmapFactory.decodeFile(image.getAbsolutePath(), null); + } + + private static class UpdateSercetImageThread extends BgThread { + + private final Context mContext; + + public UpdateSercetImageThread(Context context) { + mContext = context; + } + + @Override + public void run() { + // TODO check usable space + File dir = mContext.getExternalFilesDir(DIR_NAME); + File image = new File(dir, IMAGE_NAME); + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inJustDecodeBounds = true; + BitmapFactory.decodeFile(image.getAbsolutePath(), opts); + if (opts.outWidth <= 0 || opts.outHeight <= 0) { + // Get image error, try to get image from internet + HttpHelper hh = new HttpHelper(mContext); + hh.download(SERCET_IMAGE_URL, mContext.getExternalFilesDir(DIR_NAME), + IMAGE_NAME, false, null, null); + } + sIsGetSecretImage = false; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/SqlUtils.java b/app/src/main/java/com/hippo/ehviewer/util/SqlUtils.java new file mode 100644 index 000000000..80b20cd9f --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/SqlUtils.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +import java.util.ArrayList; +import java.util.List; + +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; + +public final class SqlUtils { + + public static void exeSQLSafely(SQLiteDatabase db, String sql) { + try { + db.execSQL(sql); + } catch (SQLException e) {} + } + + public static void dropTable(SQLiteDatabase db, String tableName) { + exeSQLSafely(db, "DROP TABLE IF EXISTS " + tableName); + } + + public static void dropAllTable(SQLiteDatabase db) { + List tables = new ArrayList(); + Cursor cursor = db.rawQuery("SELECT * FROM sqlite_master WHERE type='table';", null); + cursor.moveToFirst(); + while (!cursor.isAfterLast()) { + String tableName = cursor.getString(1); + if (!tableName.equals("android_metadata") && + !tableName.equals("sqlite_sequence")) + tables.add(tableName); + cursor.moveToNext(); + } + cursor.close(); + + for(String tableName : tables) { + dropTable(db, tableName); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/Theme.java b/app/src/main/java/com/hippo/ehviewer/util/Theme.java new file mode 100644 index 000000000..3e4035b63 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/Theme.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.StateListDrawable; + +public final class Theme { + + public static final int GREY_COLOR = 0xffaaaaaa; + public static final ColorDrawable GREY_DRAWABLE = + new ColorDrawable(0xffaaaaaa); + + private static final int[] DARK_COLOR_TABLE = { + 0xffe91e63, 0xff9c27b0, 0xff673ab7, + 0xff3f51b5, 0xff5677fc, 0xff009688, + 0xff259b24 + }; + + /** + * get darker color of target color, alpha not change + * @param color + * @return + */ + public static int getDarkerColor(int color) { + float[] hsv = new float[3]; + Color.colorToHSV(color, hsv); + hsv[2] *= 0.9f; + return Color.HSVToColor(hsv); + } + + public static StateListDrawable getClickDrawable(Context context, int color) { + StateListDrawable sld = new StateListDrawable(); + ColorDrawable normal = new ColorDrawable(color); + ColorDrawable dark = new ColorDrawable(getDarkerColor(color)); + + sld.addState(new int[]{-android.R.attr.state_enabled}, GREY_DRAWABLE); + sld.addState(new int[]{android.R.attr.state_enabled, android.R.attr.state_selected}, dark); + sld.addState(new int[]{android.R.attr.state_enabled, android.R.attr.state_focused}, dark); + sld.addState(new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}, dark); + sld.addState(new int[]{}, normal); + return sld; + } + + public static StateListDrawable getRadiiClickDrawable(Context context, int color) { + int corner = Ui.dp2pix(2); + float[] cornerRadii = new float[]{ + corner, corner, corner, corner, corner, corner, corner, corner}; + + StateListDrawable sld = new StateListDrawable(); + GradientDrawable normal = new GradientDrawable(); + normal.setShape(GradientDrawable.RECTANGLE); + normal.setCornerRadii(cornerRadii); + normal.setColor(color); + GradientDrawable dark = new GradientDrawable(); + dark.setShape(GradientDrawable.RECTANGLE); + dark.setCornerRadii(cornerRadii); + dark.setColor(getDarkerColor(color)); + + sld.addState(new int[]{-android.R.attr.state_enabled}, GREY_DRAWABLE); + sld.addState(new int[]{android.R.attr.state_enabled, android.R.attr.state_selected}, dark); + sld.addState(new int[]{android.R.attr.state_enabled, android.R.attr.state_focused}, dark); + sld.addState(new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}, dark); + sld.addState(new int[]{}, normal); + return sld; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/Ui.java b/app/src/main/java/com/hippo/ehviewer/util/Ui.java new file mode 100644 index 000000000..5d328e3de --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/Ui.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +import android.annotation.TargetApi; +import android.app.ActionBar; +import android.app.Activity; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.View; +import android.view.View.MeasureSpec; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.FrameLayout.LayoutParams; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.data.ListUrls; +import com.hippo.ehviewer.drawable.MaterialIndicatorDrawable; + +public class Ui { + @SuppressWarnings("unused") + private static final String TAG = Ui.class.getSimpleName(); + private static Context mContext; + private static Resources mResources; + private static final BitmapFactory.Options opt = new BitmapFactory.Options(); + + public static float mDensity; + public static float mScaledDensity; + + public static int DOUJINSHI_BG_COLOR; + public static int MANGA_BG_COLOR; + public static int ARTIST_CG_BG_COLOR; + public static int GAME_CG_BG_COLOR; + public static int WESTERN_BG_COLOR; + public static int NON_H_BG_COLOR; + public static int IMAGE_SET_BG_COLOR; + public static int COSPLAY_BG_COLOR; + public static int ASIAN_PORN_BG_COLOR; + public static int MISC_BG_COLOR; + public static int UNKNOWN_BG_COLOR; + + public static int THEME_COLOR; + + public static Drawable transparentDrawable; + + public static int ACTION_BAR_HEIGHT; + + /** + * Init Crash + * + * @param context Application context + */ + public static void init(Context context) { + mContext = context; + + opt.inPreferredConfig = Bitmap.Config.RGB_565; + opt.inPurgeable = true; + opt.inInputShareable = true; + + mResources = mContext.getResources(); + + mDensity = mResources.getDisplayMetrics().density; + mScaledDensity = mResources.getDisplayMetrics().scaledDensity; + + // init color + + DOUJINSHI_BG_COLOR = mResources.getColor(R.color.doujinshi_bg); + MANGA_BG_COLOR = mResources.getColor(R.color.manga_bg); + ARTIST_CG_BG_COLOR = mResources.getColor(R.color.artist_cg_bg); + GAME_CG_BG_COLOR = mResources.getColor(R.color.game_cg_bg); + WESTERN_BG_COLOR = mResources.getColor(R.color.western_bg); + NON_H_BG_COLOR = mResources.getColor(R.color.non_h_bg); + IMAGE_SET_BG_COLOR = mResources.getColor(R.color.image_set_bg); + COSPLAY_BG_COLOR = mResources.getColor(R.color.cosplay_bg); + ASIAN_PORN_BG_COLOR = mResources.getColor(R.color.asian_porn_bg); + MISC_BG_COLOR = mResources.getColor(R.color.misc_bg); + UNKNOWN_BG_COLOR = mResources.getColor(R.color.unknown_bg); + + THEME_COLOR = mResources.getColor(R.color.theme); + + // init drawable + transparentDrawable = new ColorDrawable(Color.TRANSPARENT); + + ACTION_BAR_HEIGHT = mResources.getDimensionPixelSize(R.dimen.action_bar_height); + } + + public static int getInternalDimensionSize(Resources res, String key) { + int result = 0; + int resourceId = res.getIdentifier(key, "dimen", "android"); + if (resourceId > 0) { + result = res.getDimensionPixelSize(resourceId); + } + return result; + } + + public static int getThemeDimensionPixelSize(android.content.res.Resources.Theme theme, int attrId) { + TypedValue tv = new TypedValue(); + theme.resolveAttribute(attrId, tv, true); + return TypedValue.complexToDimensionPixelSize(tv.data, mContext.getResources().getDisplayMetrics()); + } + + public static String getCategoryText(int category) { + switch (category) { + case ListUrls.DOUJINSHI: + return mResources.getString(R.string.doujinshi); + case ListUrls.MANGA: + return mResources.getString(R.string.manga); + case ListUrls.ARTIST_CG: + return mResources.getString(R.string.artist_cg); + case ListUrls.GAME_CG: + return mResources.getString(R.string.game_cg); + case ListUrls.WESTERN: + return mResources.getString(R.string.western); + case ListUrls.NON_H: + return mResources.getString(R.string.non_h); + case ListUrls.IMAGE_SET: + return mResources.getString(R.string.image_set); + case ListUrls.COSPLAY: + return mResources.getString(R.string.cosplay); + case ListUrls.ASIAN_PORN: + return mResources.getString(R.string.asian_porn); + case ListUrls.MISC: + return mResources.getString(R.string.misc); + default: + return mResources.getString(R.string.unknown); + } + } + + public static int getCategoryColor(int category) { + switch (category) { + case ListUrls.DOUJINSHI: + return DOUJINSHI_BG_COLOR; + case ListUrls.MANGA: + return MANGA_BG_COLOR; + case ListUrls.ARTIST_CG: + return ARTIST_CG_BG_COLOR; + case ListUrls.GAME_CG: + return GAME_CG_BG_COLOR; + case ListUrls.WESTERN: + return WESTERN_BG_COLOR; + case ListUrls.NON_H: + return NON_H_BG_COLOR; + case ListUrls.IMAGE_SET: + return IMAGE_SET_BG_COLOR; + case ListUrls.COSPLAY: + return COSPLAY_BG_COLOR; + case ListUrls.ASIAN_PORN: + return ASIAN_PORN_BG_COLOR; + case ListUrls.MISC: + return MISC_BG_COLOR; + default: + return UNKNOWN_BG_COLOR; + } + } + + /** + * dp conversion to pix + * + * @param dp The value you want to conversion + * @return value in pix + */ + public static int dp2pix(float dp) { + return (int) (mDensity * dp + 0.5f); + } + + public static float pix2dp(int pix) { + return pix/mDensity; + } + + public static int sp2pix(float sp) { + return (int) (sp * mScaledDensity + 0.5f); + } + + public static float pix2sp(float pix) { + return pix / mScaledDensity; + } + + /** + * Get default BitmapFactory.Options + * + * @return + */ + public static BitmapFactory.Options getBitmapOpt() { + return opt; + } + + /** + * Check has navigation bar or not + * @return + */ + public static boolean hasNavigationBar() { + boolean hasNavigationBar = false; + boolean hasMenuKey = ViewConfiguration.get(mContext).hasPermanentMenuKey(); + boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK); + + if(!hasMenuKey && !hasBackKey) + hasNavigationBar = true; + return hasNavigationBar; + } + + /** + * Get height of status bar + * @return + */ + public static int getStatusBarHeight() { + Resources resources = mContext.getResources(); + int sbhResourceId = resources.getIdentifier("status_bar_height", + "dimen", "android"); + if (sbhResourceId > 0) + return resources.getDimensionPixelSize(sbhResourceId); + else return 0; + } + + /** + * Get height of navigation bar + * @param force If false, alway return 0 if the device has no navigation bar + * @return + */ + public static int getNavigationBarHeight(boolean force) { + if (!force && !hasNavigationBar()) + return 0; + int nbhResourceId = mResources.getIdentifier( + "navigation_bar_height", "dimen", "android"); + if (nbhResourceId > 0) + return mResources.getDimensionPixelSize(nbhResourceId); + else + return 0; + } + + public static void measureView(View child) { + ViewGroup.LayoutParams p = child.getLayoutParams(); + if (p == null) { + p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + } + int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); + int lpHeight = p.height; + int childHeightSpec; + if (lpHeight > 0) { + childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, + MeasureSpec.EXACTLY); + } else { + childHeightSpec = MeasureSpec.makeMeasureSpec(0, + MeasureSpec.UNSPECIFIED); + } + child.measure(childWidthSpec, childHeightSpec); + } + + /** + * Update translucent state for API >= 19, KK or more powerful + * + * @param activity + */ + @TargetApi(Build.VERSION_CODES.KITKAT) + public static void updateTranslucent(Activity activity) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + Configuration c = activity.getResources().getConfiguration(); + Window w = activity.getWindow(); + + if (c.orientation == Configuration.ORIENTATION_PORTRAIT || + activity.getResources().getBoolean(R.bool.is_table)) + w.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + else + w.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + + // For KK set a color view at top to make color status bar + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) + w.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + } + } + + /** + * Return Configuration.ORIENTATION_PORTRAIT only + * if orientation != Configuration.ORIENTATION_LANDSCAPE + * + * @param orientation + * @return + */ + public static int filterOrientation(int orientation) { + if (orientation != Configuration.ORIENTATION_LANDSCAPE) + orientation = Configuration.ORIENTATION_PORTRAIT; + return orientation; + } + + /** + * Set config Orientation + * + * @param activity + */ + public static void adjustOrientation(Activity activity) { + int screenOri = Config.getScreenOriMode(); + if (screenOri != activity.getRequestedOrientation()) + activity.setRequestedOrientation(screenOri); + } + + public static void colorStatusBarKK(Activity activity, int color, int height) { + int darkColor = Theme.getDarkerColor(color); + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { + View statusBarBgView; + FrameLayout.LayoutParams lp; + ViewGroup decorViewGroup = (ViewGroup) activity.getWindow().getDecorView(); + + // Try to find old translucent view + if (decorViewGroup.getChildCount() > 1 && + (statusBarBgView = decorViewGroup.getChildAt(1)) != null && + statusBarBgView.getId() == R.id.translucent_view) { + lp = (LayoutParams) statusBarBgView.getLayoutParams(); + lp.height = height; + lp.gravity = Gravity.TOP; + statusBarBgView.setBackgroundColor(darkColor); + statusBarBgView.setLayoutParams(lp); + } else { + statusBarBgView = new View(activity); + statusBarBgView.setId(R.id.translucent_view); + statusBarBgView.setBackgroundColor(darkColor); + lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, height); + lp.gravity = Gravity.TOP; + decorViewGroup.addView(statusBarBgView, lp); + } + } + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public static void colorStatusBarL(Activity activity, int color) { + int darkColor = Theme.getDarkerColor(color); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Window w = activity.getWindow(); + w.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + w.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + w.setStatusBarColor(Theme.getDarkerColor(darkColor)); + } + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) + public static void setMaterialIndicator(ActionBar actionBar, MaterialIndicatorDrawable d) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + actionBar.setDisplayShowHomeEnabled(true); + actionBar.setHomeButtonEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(false); + actionBar.setIcon(d); + } else { + actionBar.setHomeButtonEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setHomeAsUpIndicator(d); + actionBar.setIcon(null); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/Utils.java b/app/src/main/java/com/hippo/ehviewer/util/Utils.java new file mode 100644 index 000000000..dd6d7a903 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/Utils.java @@ -0,0 +1,743 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Method; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Vibrator; +import android.text.TextUtils; +import android.webkit.MimeTypeMap; + +import com.hippo.ehviewer.AppHandler; + +public final class Utils { + @SuppressWarnings("unused") + private static String TAG = Utils.class.getSimpleName(); + + public static final boolean SUPPORT_IMAGE; + @SuppressLint("SimpleDateFormat") + public static final DateFormat sDate = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + @SuppressLint("SimpleDateFormat") + public static final DateFormat sDate2 = new SimpleDateFormat("yyyyMMdd"); + + + static { + String cpu = Build.CPU_ABI; + SUPPORT_IMAGE = cpu.equals("armeabi") || cpu.equals("armeabi-v7a") || + cpu.equals("arm64-v8a") || cpu.equals("x86") || cpu.equals("x86_64"); + } + + /** + * Put InputStream to File, default bufferSize is 512 * 1024 + * + * @param is + * @param file + * @throws IOException + */ + public static void inputStream2File(InputStream is, File file) throws IOException { + OutputStream os = new FileOutputStream(file); + copy(is, os); + } + + public static void copy(InputStream is, OutputStream os) throws IOException { + copy(is, os, 512 * 1024); + } + + public static void copy(InputStream is, OutputStream os, int size) throws IOException { + byte[] buffer = new byte[size]; + int bytesRead; + while((bytesRead = is.read(buffer)) !=-1) + os.write(buffer, 0, bytesRead); + is.close(); + os.flush(); + os.close(); + buffer = null; + } + + public static boolean copy(File src, File dst) { + try { + copy(new FileInputStream(src), new FileOutputStream(dst)); + } catch (Exception e) { + return false; + } + return true; + } + + public static interface OnCopyOverListener { + public void onCopyOver(boolean success, File src, File dst); + } + + public static void copyInNewThread(final File src, final File dst, final OnCopyOverListener l) { + new BgThread(new Runnable() { + @Override + public void run() { + final boolean ok = copy(src, dst); + if (l != null) { + AppHandler.getInstance().post(new Runnable() { + @Override + public void run() { + l.onCopyOver(ok, src, dst); + } + }); + } + } + }).start(); + } + + public static boolean isNumber(String str) { + for (int i = 0; i < str.length(); i++) { + char ch = str.charAt(i); + if (ch < '0' || ch > '9') + return false; + } + return true; + } + + public static String byteArrayToHexString(byte[] b) { + StringBuilder sb = new StringBuilder(b.length * 2); + for (byte element : b) { + int v = element & 0xFF; + if (v < 16) { + sb.append('0'); + } + sb.append(Integer.toHexString(v)); + } + return sb.toString().toUpperCase(Locale.getDefault()); + } + + public static byte[] hexStringToByteArray(String s) { + int len = s.length()/2*2; + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[(i / 2)] = ((byte) ((Character.digit(s.charAt(i), 16) << 4) + Character + .digit(s.charAt(i + 1), 16))); + } + return data; + } + + /** + * Convernt context in stream to string + * + * @param is + * @param charset + * @return + */ + public static String InputStream2String(InputStream is, String charset) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + String str = null; + try { + Utils.copy(is, baos, 1024); + str = baos.toString(charset); + } catch (IOException e) { + e.printStackTrace(); + } finally { + Utils.closeQuietly(baos); + } + return str; + } + + public static String[] getStrings(SharedPreferences shaper, String key) { + String str = shaper.getString(key, null); + if (str == null || str.length() == 0) + return null; + return new String(Utils.hexStringToByteArray(str)).split("\n"); + } + + public static void putStrings(SharedPreferences shaper, String key, List strs) { + StringBuffer sb = new StringBuffer(); + for (String item : strs) { + sb.append(item); + sb.append('\n'); + } + int length = sb.length(); + if (length != 0) + sb.delete(length - 1, length); + shaper.edit().putString(key, byteArrayToHexString(sb.toString().getBytes())).apply(); + } + + public static void closeQuietly (Closeable is) { + try { + if (is != null) + is.close(); + } catch (IOException e) { + } + } + + /** + * Try to delete file, dir and it's children + * + * @param dir + * The dir to deleted + */ + public static void deleteFile(File file) { + File[] files = file.listFiles(); + if (files != null) { + for (File f : files) + deleteFile(f); + } + final File to = new File(file.getAbsolutePath() + + System.currentTimeMillis()); + if (file.renameTo(to)) + to.delete(); + else + file.delete(); + } + + /** + * Start a new thread to delete dir + */ + public static void deleteDirInThread(final File file) { + new BgThread() { + @Override + public void run() { + deleteFile(file); + } + }.start(); + } + + static { + + } + + public static int getDate() { + int time = 0; + try { + time = Integer.parseInt(sDate2.format(new Date())); + } catch (NumberFormatException e) {} + return time; + } + + + private static String[] SIZE_UNIT = {"%.2f B", "%.2f KB", "%.2f MB", "%.2f GB"}; + + public static String sizeToString(long size) { + int length = SIZE_UNIT.length; + + float sizeFloat = size; + for (int i = 0; i < length; i++) { + if (sizeFloat < 1024 || i == length - 1) { + return String.format(SIZE_UNIT[i], sizeFloat); + } + sizeFloat /= 1024; + } + return null; + } + + /** + * Execute an {@link AsyncTask} on a thread pool + * + * @param forceSerial True to force the task to run in serial order + * @param task Task to execute + * @param args Optional arguments to pass to + * {@link AsyncTask#execute(Object[])} + * @param Task argument type + */ + @SuppressLint("NewApi") + public static void execute(final boolean forceSerial, final AsyncTask task, + final T... args) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.DONUT) { + throw new UnsupportedOperationException( + "This class can only be used on API 4 and newer."); + } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB || forceSerial) { + task.execute(args); + } else { + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, args); + } + } + + private static final String[] ESCAPE_CHARATER_LIST = { + "&", + "<", + ">", + """, + "'", + "×" + }; + + private static final String[] UNESCAPE_CHARATER_LIST = { + "&", + "<", + ">", + "\"", + "'", + "×" + }; + + /** + * Mostly title and url need it + * @param str + * @return + */ + public static String unescapeXml(String str) { + return replaceEach(str, ESCAPE_CHARATER_LIST, UNESCAPE_CHARATER_LIST); + } + + /** + * Make file name valid by removing invalid character and set max length + * @param name + * @return + */ + public static String standardizeFilename(String name) { + name = name.replaceAll("[\\\\/:*?\"<>\\|]", ""); + name = name.length() > 255 ? name.substring(0, 255) : name; + return name.replaceAll("^[\\s]+", "").replace("[\\s]+$", ""); + } + + /** + * Joins the elements of the provided array into a single String + * containing the provided list of elements. + * + * @param array + * @param separator + * @return + */ + public static String join(final Object[] array, + final Object separator) { + final StringBuilder sb = new StringBuilder(array.length * 16); + for (int i = 0; i < array.length; i++) { + if (i != 0) + sb.append(separator); + sb.append(array[i]); + } + return sb.toString(); + } + + /** + *

    + * Replaces all occurrences of Strings within another String. + *

    + * + *

    + * A {@code null} reference passed to this method is a no-op, or if + * any "search string" or "string to replace" is null, that replace will be + * ignored. This will not repeat. For repeating replaces, call the + * overloaded method. + *

    + * + *
    +     *  StringUtils.replaceEach(null, *, *)        = null
    +     *  StringUtils.replaceEach("", *, *)          = ""
    +     *  StringUtils.replaceEach("aba", null, null) = "aba"
    +     *  StringUtils.replaceEach("aba", new String[0], null) = "aba"
    +     *  StringUtils.replaceEach("aba", null, new String[0]) = "aba"
    +     *  StringUtils.replaceEach("aba", new String[]{"a"}, null)  = "aba"
    +     *  StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""})  = "b"
    +     *  StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"})  = "aba"
    +     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"})  = "wcte"
    +     *  (example of how it does not repeat)
    +     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"})  = "dcte"
    +     * 
    + * + * @param text + * text to search and replace in, no-op if null + * @param searchList + * the Strings to search for, no-op if null + * @param replacementList + * the Strings to replace them with, no-op if null + * @return the text with any replacements processed, {@code null} if + * null String input + * @throws IllegalArgumentException + * if the lengths of the arrays are not the same (null is ok, + * and/or size 0) + */ + // Get from org.apache.commons.lang3.StringUtils + public static String replaceEach(final String text, final String[] searchList, final String[] replacementList) { + return replaceEach(text, searchList, replacementList, false, 0); + } + + /** + *

    + * Replaces all occurrences of Strings within another String. + *

    + * + *

    + * A {@code null} reference passed to this method is a no-op, or if + * any "search string" or "string to replace" is null, that replace will be + * ignored. + *

    + * + *
    +     *  StringUtils.replaceEach(null, *, *, *) = null
    +     *  StringUtils.replaceEach("", *, *, *) = ""
    +     *  StringUtils.replaceEach("aba", null, null, *) = "aba"
    +     *  StringUtils.replaceEach("aba", new String[0], null, *) = "aba"
    +     *  StringUtils.replaceEach("aba", null, new String[0], *) = "aba"
    +     *  StringUtils.replaceEach("aba", new String[]{"a"}, null, *) = "aba"
    +     *  StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}, *) = "b"
    +     *  StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}, *) = "aba"
    +     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}, *) = "wcte"
    +     *  (example of how it repeats)
    +     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, false) = "dcte"
    +     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, true) = "tcte"
    +     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}, *) = IllegalStateException
    +     * 
    + * + * @param text + * text to search and replace in, no-op if null + * @param searchList + * the Strings to search for, no-op if null + * @param replacementList + * the Strings to replace them with, no-op if null + * @param repeat if true, then replace repeatedly + * until there are no more possible replacements or timeToLive < 0 + * @param timeToLive + * if less than 0 then there is a circular reference and endless + * loop + * @return the text with any replacements processed, {@code null} if + * null String input + * @throws IllegalStateException + * if the search is repeating and there is an endless loop due + * to outputs of one being inputs to another + * @throws IllegalArgumentException + * if the lengths of the arrays are not the same (null is ok, + * and/or size 0) + */ + // Get from org.apache.commons.lang3.StringUtils + private static String replaceEach( + final String text, final String[] searchList, final String[] replacementList, final boolean repeat, final int timeToLive) { + + // mchyzer Performance note: This creates very few new objects (one major goal) + // let me know if there are performance requests, we can create a harness to measure + + if (text == null || text.isEmpty() || searchList == null || + searchList.length == 0 || replacementList == null || replacementList.length == 0) { + return text; + } + + // if recursing, this shouldn't be less than 0 + if (timeToLive < 0) { + throw new IllegalStateException("Aborting to protect against StackOverflowError - " + + "output of one loop is the input of another"); + } + + final int searchLength = searchList.length; + final int replacementLength = replacementList.length; + + // make sure lengths are ok, these need to be equal + if (searchLength != replacementLength) { + throw new IllegalArgumentException("Search and Replace array lengths don't match: " + + searchLength + + " vs " + + replacementLength); + } + + // keep track of which still have matches + final boolean[] noMoreMatchesForReplIndex = new boolean[searchLength]; + + // index on index that the match was found + int textIndex = -1; + int replaceIndex = -1; + int tempIndex = -1; + + // index of replace array that will replace the search string found + // NOTE: logic duplicated below START + for (int i = 0; i < searchLength; i++) { + if (noMoreMatchesForReplIndex[i] || searchList[i] == null || + searchList[i].isEmpty() || replacementList[i] == null) { + continue; + } + tempIndex = text.indexOf(searchList[i]); + + // see if we need to keep searching for this + if (tempIndex == -1) { + noMoreMatchesForReplIndex[i] = true; + } else { + if (textIndex == -1 || tempIndex < textIndex) { + textIndex = tempIndex; + replaceIndex = i; + } + } + } + // NOTE: logic mostly below END + + // no search strings found, we are done + if (textIndex == -1) { + return text; + } + + int start = 0; + + // get a good guess on the size of the result buffer so it doesn't have to double if it goes over a bit + int increase = 0; + + // count the replacement text elements that are larger than their corresponding text being replaced + for (int i = 0; i < searchList.length; i++) { + if (searchList[i] == null || replacementList[i] == null) { + continue; + } + final int greater = replacementList[i].length() - searchList[i].length(); + if (greater > 0) { + increase += 3 * greater; // assume 3 matches + } + } + // have upper-bound at 20% increase, then let Java take over + increase = Math.min(increase, text.length() / 5); + + final StringBuilder buf = new StringBuilder(text.length() + increase); + + while (textIndex != -1) { + + for (int i = start; i < textIndex; i++) { + buf.append(text.charAt(i)); + } + buf.append(replacementList[replaceIndex]); + + start = textIndex + searchList[replaceIndex].length(); + + textIndex = -1; + replaceIndex = -1; + tempIndex = -1; + // find the next earliest match + // NOTE: logic mostly duplicated above START + for (int i = 0; i < searchLength; i++) { + if (noMoreMatchesForReplIndex[i] || searchList[i] == null || + searchList[i].isEmpty() || replacementList[i] == null) { + continue; + } + tempIndex = text.indexOf(searchList[i], start); + + // see if we need to keep searching for this + if (tempIndex == -1) { + noMoreMatchesForReplIndex[i] = true; + } else { + if (textIndex == -1 || tempIndex < textIndex) { + textIndex = tempIndex; + replaceIndex = i; + } + } + } + // NOTE: logic duplicated above END + + } + final int textLength = text.length(); + for (int i = start; i < textLength; i++) { + buf.append(text.charAt(i)); + } + final String result = buf.toString(); + if (!repeat) { + return result; + } + + return replaceEach(result, searchList, replacementList, repeat, timeToLive - 1); + } + + /** + * 0 for false, Non 0 for true + * + * @param integer + * @return + */ + public static boolean int2boolean(int integer) { + return integer != 0; + } + + /** + * false for 0, true for 1 + * + * @param bool + * @return + */ + public static int boolean2int(boolean bool) { + return bool ? 1 : 0; + } + + /** + * Returns the ASCII characters up to but not including the next "\r\n", or + * "\n". + * + * @throws java.io.EOFException if the stream is exhausted before the next + * newline character. + */ + public static String readAsciiLine(final InputStream in) throws IOException { + final StringBuilder result = new StringBuilder(80); + while (true) { + final int c = in.read(); + if (c == -1) { + throw new EOFException(); + } else if (c == '\n') { + break; + } + + result.append((char)c); + } + final int length = result.length(); + if (length > 0 && result.charAt(length - 1) == '\r') { + result.setLength(length - 1); + } + return result.toString(); + } + + /** + * Try to make dir exists + * + * @param dir + * @param force + */ + public static void ensureDir(File dir, boolean force) { + if (!dir.exists()) { + dir.mkdirs(); + } else if (force && dir.isFile()) { + dir.delete(); + dir.mkdirs(); + } + } + + /** + * Return true if point is in the rect + * + * @param area + * @param x + * @param y + * @return + */ + public static boolean isInArea(int[] area, int x, int y) { + if (area == null) + throw new NullPointerException("area is null in " + + Utils.class.getName() + ".isInArea"); + if (area.length != 4) + throw new IllegalArgumentException( + "area's length should be 4, but it's length is " + area.length); + if (x >= area[0] && x < area[2] && y >= area[1] && y < area[3]) + return true; + else + return false; + } + + public static boolean isOpaque(int color) { + return color >>> 24 == 0xFF; + } + + /** + * Throws AssertionError if the input is false. + * @param cond + */ + public static void assertTrue(boolean cond) { + if (!cond) { + throw new AssertionError(); + } + } + + /** + * vibrator + */ + public static void vibrator(Context context, long milliseconds) { + Vibrator v = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE); + v.vibrate(milliseconds); + } + + public static String getPathName(String parent, String name) { + StringBuilder sb = new StringBuilder(parent); + if (parent.charAt(parent.length() - 1) != File.separatorChar) + sb.append(File.separatorChar); + sb.append(name); + return sb.toString(); + } + + public static int parseIntSafely(String str, int defaultValue) { + try { + return Integer.parseInt(str); + } catch (Throwable e) { + return defaultValue; + } + } + + public static int[] toIntArray(Collection collection) { + Object[] boxedArray = collection.toArray(); + int len = boxedArray.length; + int[] array = new int[len]; + for (int i = 0; i < len; i++) { + // checkNotNull for GWT (do not optimize) + array[i] = ((Number) (boxedArray[i])).intValue(); + } + return array; + } + + // Throws NullPointerException if the input is null. + public static T checkNotNull(T object) { + if (object == null) throw new NullPointerException(); + return object; + } + + /** + * Get extension from target string, int lower case + * + * @param name + * @param defautl + * @return + */ + public static String getExtension(String name, String defautl) { + String ext = MimeTypeMap.getFileExtensionFromUrl(name); + if (TextUtils.isEmpty(ext)) + ext = defautl; + return ext; + } + + /** + * It will return null if can't find mime + * + * @param url + * @return + */ + public static String getMimeType(String url) { + String type = null; + String extension = MimeTypeMap.getFileExtensionFromUrl(url); + if (extension != null) { + MimeTypeMap mime = MimeTypeMap.getSingleton(); + type = mime.getMimeTypeFromExtension(extension); + } + return type; + } + + + /** + * If get error return ""; + * + * @param key + * @return + */ + public static String getSystemProperties(String key) { + try { + Class c = Class.forName("android.os.SystemProperties"); + Method m = c.getDeclaredMethod("get", String.class); + m.setAccessible(true); + return (String) m.invoke(null, key); + } catch (Throwable e) { + return ""; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/ViewUtils.java b/app/src/main/java/com/hippo/ehviewer/util/ViewUtils.java new file mode 100644 index 000000000..cabdddd73 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/ViewUtils.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.MeasureSpec; +import android.view.ViewGroup; +import android.view.ViewParent; + +import com.hippo.ehviewer.R; + +public final class ViewUtils { + + public static ContextThemeWrapper sContextThemeWrapper; + public static ContextThemeWrapper sDarkContextThemeWrapper; + private static LayoutInflater sDialogViewInflater; + private static LayoutInflater sDarkDialogViewInflater; + + public static final void initDialogViewInflater(Context context) { + sContextThemeWrapper = new ContextThemeWrapper(context, R.style.AppTheme_Dialog); + sDarkContextThemeWrapper = new ContextThemeWrapper(context, R.style.AppTheme_Dark_Dialog); + sDialogViewInflater = LayoutInflater.from(sContextThemeWrapper); + sDarkDialogViewInflater = LayoutInflater.from(sDarkContextThemeWrapper); + } + + public static final View inflateDialogView(int resId, boolean dark) { + LayoutInflater inflater = dark ? sDarkDialogViewInflater : sDialogViewInflater; + return inflater.inflate(resId, null); + } + + /** + * Get view center location in window + * + * @param view + * @param location + */ + public static void getCenterInWindows(View view, int[] location) { + getLocationInWindow(view, location); + location[0] += view.getWidth() / 2; + location[1] += view.getHeight() / 2; + } + + /** + * Get view location in window + * + * @param view + * @param location + */ + public static void getLocationInWindow(View view, int[] location) { + if (location == null || location.length < 2) { + throw new IllegalArgumentException( + "location must be an array of two integers"); + } + + float[] position = new float[2]; + + position[0] = view.getLeft(); + position[1] = view.getTop(); + + ViewParent viewParent = view.getParent(); + while (viewParent instanceof View) { + view = (View) viewParent; + if (view.getId() == android.R.id.content) { + break; + } + + position[0] -= view.getScrollX(); + position[1] -= view.getScrollY(); + + position[0] += view.getLeft(); + position[1] += view.getTop(); + + viewParent = view.getParent(); + } + + location[0] = (int) (position[0] + 0.5f); + location[1] = (int) (position[1] + 0.5f); + } + + public static View getAncestor(View view, int id) { + ViewParent viewParent = view.getParent(); + while (viewParent instanceof View) { + view = (View) viewParent; + if (view.getId() == id) + return view; + viewParent = view.getParent(); + } + return null; + } + + /** + * Returns a bitmap showing a screenshot of the view passed in. + * + * @param v + * @return + */ + public static Bitmap getBitmapFromView(View v) { + int width = v.getWidth(); + int height = v.getHeight(); + if (width == 0 && height == 0) { + width = v.getMeasuredWidth(); + height = v.getMeasuredHeight(); + } + Bitmap bitmap = Bitmap.createBitmap(width, height, + Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + // TODO I need to know why I need it, when ScrollView + canvas.translate(-v.getScrollX(), -v.getScrollY()); + v.draw(canvas); + return bitmap; + } + + public static boolean isClickAction(MotionEvent event) { + // TODO bad idea to check click + return event.getAction() == MotionEvent.ACTION_UP + && System.nanoTime() / 1000000 - event.getDownTime() < 200; + } + + public static void removeFromParent(View view) { + ViewParent vp = view.getParent(); + if (vp instanceof ViewGroup) + ((ViewGroup) vp).removeView(view); + } + + /** + * Method that removes the support for HardwareAcceleration from a + * {@link View}.
    + *
    + * Check AOSP notice:
    + * + *
    +     * 'ComposeShader can only contain shaders of different types (a BitmapShader and a
    +     * LinearGradient for instance, but not two instances of BitmapShader)'. But, 'If your
    +     * application is affected by any of these missing features or limitations, you can turn
    +     * off hardware acceleration for just the affected portion of your application by calling
    +     * setLayerType(View.LAYER_TYPE_SOFTWARE, null).'
    +     * 
    + * + * @param v + * The view + */ + public static void removeHardwareAccelerationSupport(View v) { + if (v.getLayerType() != View.LAYER_TYPE_SOFTWARE) { + v.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + } + } + + public static void measureView(View v, int width, int height) { + int widthMeasureSpec; + int heightMeasureSpec; + if (width == ViewGroup.LayoutParams.WRAP_CONTENT) + widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, + MeasureSpec.UNSPECIFIED); + else + widthMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(width, 0), + MeasureSpec.EXACTLY); + if (height == ViewGroup.LayoutParams.WRAP_CONTENT) + heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, + MeasureSpec.UNSPECIFIED); + else + heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(height, 0), + MeasureSpec.EXACTLY); + + v.measure(widthMeasureSpec, heightMeasureSpec); + } + + /** + * Determine if the supplied view is under the given point in the + * parent view's coordinate system. + * + * @param view Child view of the parent to hit test + * @param x X position to test in the parent's coordinate system + * @param y Y position to test in the parent's coordinate system + * @return true if the supplied view is under the given point, false otherwise + */ + public static boolean isViewUnder(View view, int x, int y) { + if (view == null || view.getVisibility() != View.VISIBLE) { + return false; + } + return x >= view.getLeft() && + x < view.getRight() && + y >= view.getTop() && + y < view.getBottom(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/util/ZInterpolator.java b/app/src/main/java/com/hippo/ehviewer/util/ZInterpolator.java new file mode 100644 index 000000000..4792c2afe --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/util/ZInterpolator.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.util; + +import android.animation.TimeInterpolator; + +public class ZInterpolator implements TimeInterpolator { + + private final float focalLength; + + public ZInterpolator() { + this(0.5f); + } + + public ZInterpolator(float foc) { + focalLength = foc; + } + + @Override + public float getInterpolation(float input) { + return (1.0f - focalLength / (focalLength + input)) / + (1.0f - focalLength / (focalLength + 1.0f)); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/ActionableToastBar.java b/app/src/main/java/com/hippo/ehviewer/widget/ActionableToastBar.java new file mode 100644 index 000000000..c2297f9cf --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/ActionableToastBar.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.hippo.ehviewer.widget; + +import com.hippo.ehviewer.R; + +import android.animation.Animator; +import android.animation.AnimatorInflater; +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +/** + * A custom {@link View} that exposes an action to the user. + *

    + * This is a copy of packages/apps/UnifiedEmail/src/com/android/mail/ui/ActionableToastBar.java + * with minor modifications. + */ +public class ActionableToastBar extends LinearLayout { + private boolean mHidden = true; + private Animator mShowAnimation; + private Animator mHideAnimation; + + /** Icon for the description. */ + private ImageView mActionDescriptionIcon; + /** The clickable view */ + private View mActionButton; + /** Icon for the action button. */ + private ImageView mActionIcon; + /** The view that contains the description. */ + private TextView mActionDescriptionView; + /** The view that contains the text for the action button. */ + private TextView mActionText; + //private ToastBarOperation mOperation; + + public ActionableToastBar(Context context) { + this(context, null); + } + + public ActionableToastBar(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ActionableToastBar(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + LayoutInflater.from(context).inflate(R.layout.actionable_toast_row, this, true); + setVisibility(View.GONE); + + mActionDescriptionIcon = (ImageView)findViewById(R.id.description_icon); + mActionDescriptionView = (TextView)findViewById(R.id.description_text); + mActionButton = findViewById(R.id.action_button); + mActionIcon = (ImageView)findViewById(R.id.action_icon); + mActionText = (TextView)findViewById(R.id.action_text); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + mActionDescriptionIcon = (ImageView)findViewById(R.id.description_icon); + mActionDescriptionView = (TextView)findViewById(R.id.description_text); + mActionButton = findViewById(R.id.action_button); + mActionIcon = (ImageView)findViewById(R.id.action_icon); + mActionText = (TextView)findViewById(R.id.action_text); + } + + /** + * Displays the toast bar and makes it visible. Allows the setting of + * parameters to customize the display. + * @param listener performs some action when the action button is clicked + * @param descriptionIconResourceId resource ID for the description icon or + * 0 if no icon should be shown + * @param descriptionText a description text to show in the toast bar + * @param showActionIcon if true, the action button icon should be shown + * @param actionTextResource resource ID for the text to show in the action button + * @param replaceVisibleToast if true, this toast should replace any currently visible toast. + * Otherwise, skip showing this toast. + */ + public void show(final ActionClickedListener listener, int descriptionIconResourceId, + CharSequence descriptionText, int actionIconResource, CharSequence actionText, + boolean replaceVisibleToast) { + + if (!mHidden && !replaceVisibleToast) { + return; + } + + mActionButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View widget) { + if (listener != null) { + listener.onActionClicked(); + } + hide(true); + } + }); + + // Set description icon. + if (descriptionIconResourceId == 0) { + mActionDescriptionIcon.setVisibility(GONE); + } else { + mActionDescriptionIcon.setVisibility(VISIBLE); + mActionDescriptionIcon.setImageResource(descriptionIconResourceId); + } + + mActionDescriptionView.setText(descriptionText); + if (actionIconResource == 0) { + mActionIcon.setVisibility(GONE); + } else { + mActionIcon.setVisibility(VISIBLE); + mActionIcon.setImageResource(actionIconResource); + } + mActionText.setText(actionText); + + mHidden = false; + getShowAnimation().start(); + } + + /** + * Hides the view and resets the state. + */ + public void hide(boolean animate) { + // Prevent multiple call to hide. + // Also prevent hiding if show animation is going on. + if (!mHidden && !getShowAnimation().isRunning()) { + mHidden = true; + if (getVisibility() == View.VISIBLE) { + mActionDescriptionView.setText(""); + mActionButton.setOnClickListener(null); + // Hide view once it's clicked. + if (animate) { + getHideAnimation().start(); + } else { + setAlpha(0); + setVisibility(View.GONE); + } + } + } + } + + private Animator getShowAnimation() { + if (mShowAnimation == null) { + mShowAnimation = AnimatorInflater.loadAnimator(getContext(), + R.anim.fade_in); + mShowAnimation.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + setVisibility(View.VISIBLE); + } + @Override + public void onAnimationEnd(Animator animation) { + // There is a tiny change that and hide animation could have finished right + // before the show animation finished. In that case, the hide will mark the + // view as GONE. We need to make sure the last one wins. + setVisibility(View.VISIBLE); + } + @Override + public void onAnimationCancel(Animator animation) { + } + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + mShowAnimation.setTarget(this); + } + return mShowAnimation; + } + + private Animator getHideAnimation() { + if (mHideAnimation == null) { + mHideAnimation = AnimatorInflater.loadAnimator(getContext(), + R.anim.fade_out); + mHideAnimation.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + @Override + public void onAnimationRepeat(Animator animation) { + } + @Override + public void onAnimationEnd(Animator animation) { + setVisibility(View.GONE); + } + @Override + public void onAnimationCancel(Animator animation) { + } + }); + mHideAnimation.setTarget(this); + } + return mHideAnimation; + } + + public boolean isEventInToastBar(MotionEvent event) { + if (!isShown()) { + return false; + } + int[] xy = new int[2]; + float x = event.getX(); + float y = event.getY(); + getLocationOnScreen(xy); + return (x > xy[0] && x < (xy[0] + getWidth()) && y > xy[1] && y < xy[1] + getHeight()); + } + + /** + * Classes that wish to perform some action when the action button is clicked + * should implement this interface. + */ + public interface ActionClickedListener { + public void onActionClicked(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/AlphaPatternDrawable.java b/app/src/main/java/com/hippo/ehviewer/widget/AlphaPatternDrawable.java new file mode 100644 index 000000000..61752fc1d --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/AlphaPatternDrawable.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2010 Daniel Nilsson Copyright (C) 2012 THe CyanogenMod Project + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; + +/** + * This drawable that draws a simple white and gray chess board pattern. It's + * pattern you will often see as a background behind a partly transparent image + * in many applications. + * + * @author Daniel Nilsson + */ +public class AlphaPatternDrawable extends Drawable { + + private final Paint mPaint = new Paint(); + + private final Paint mPaintWhite = new Paint(); + + private final Paint mPaintGray = new Paint(); + + private int mRectangleSize = 10; + + private int numRectanglesHorizontal; + + private int numRectanglesVertical; + + /* Bitmap in which the pattern will be cached. */ + private Bitmap mBitmap; + + /**/ + public AlphaPatternDrawable(final int rectangleSize) { + mRectangleSize = rectangleSize; + mPaintWhite.setColor(0xffffffff); + mPaintGray.setColor(0xffcbcbcb); + } + + /** + * {@inheritDoc} + */ + @Override + public void draw(final Canvas canvas) { + canvas.drawBitmap(mBitmap, null, getBounds(), mPaint); + } + + /** + * {@inheritDoc} + */ + @Override + public int getOpacity() { + return 0; + } + + /** + * {@inheritDoc} + */ + @Override + public void setAlpha(final int alpha) { + throw new UnsupportedOperationException("Alpha is not supported by this drawable."); + } + + /** + * {@inheritDoc} + */ + @Override + public void setColorFilter(final ColorFilter cf) { + throw new UnsupportedOperationException("ColorFilter is not supported by this drawable."); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onBoundsChange(final Rect bounds) { + super.onBoundsChange(bounds); + + final int mHeight = bounds.height(); + final int mWidth = bounds.width(); + + numRectanglesHorizontal = (int)Math.ceil((mWidth / mRectangleSize)); + numRectanglesVertical = (int)Math.ceil(mHeight / mRectangleSize); + + generatePatternBitmap(); + } + + /** + * This will generate a bitmap with the pattern as big as the rectangle we + * were allow to draw on. We do this to cache the bitmap so we don't need to + * recreate it each time draw() is called since it takes a few milliseconds. + */ + private void generatePatternBitmap() { + + if (getBounds().width() <= 0 || getBounds().height() <= 0) { + return; + } + + mBitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Config.ARGB_8888); + final Canvas mCanvas = new Canvas(mBitmap); + + final Rect mRect = new Rect(); + boolean mVerticalStartWhite = true; + for (int i = 0; i <= numRectanglesVertical; i++) { + boolean mIsWhite = mVerticalStartWhite; + for (int j = 0; j <= numRectanglesHorizontal; j++) { + mRect.top = i * mRectangleSize; + mRect.left = j * mRectangleSize; + mRect.bottom = mRect.top + mRectangleSize; + mRect.right = mRect.left + mRectangleSize; + + mCanvas.drawRect(mRect, mIsWhite ? mPaintWhite : mPaintGray); + + mIsWhite = !mIsWhite; + } + mVerticalStartWhite = !mVerticalStartWhite; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/AutoWrapLayout.java b/app/src/main/java/com/hippo/ehviewer/widget/AutoWrapLayout.java new file mode 100644 index 000000000..d2e3fd208 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/AutoWrapLayout.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import java.util.ArrayList; +import java.util.List; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import com.hippo.ehviewer.R; + +/** + * A ViewGroup that can layout views in line and auto wrap + * + * @author Hippo + * + */ +public class AutoWrapLayout extends ViewGroup { + @SuppressWarnings("unused") + private final static String TAG = "AutoWrapLayout"; + private final List rectList = new ArrayList(); + + private Alignment mAlignment; + + private static final Alignment[] sBaseLineArray = { Alignment.TOP, + Alignment.CENTER, Alignment.BOTTOM }; + + + public enum Alignment { + TOP(0), + CENTER(1), + BOTTOM(2); + + Alignment(int ni) { + nativeInt = ni; + } + + final int nativeInt; + } + + public AutoWrapLayout(Context context) { + super(context); + } + public AutoWrapLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + public AutoWrapLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.AutoWrapLayout, defStyle, 0); + + int index = a.getInt(R.styleable.AutoWrapLayout_alignment, -1); + if (index >= 0) + setAlignment(sBaseLineArray[index]); + + a.recycle(); + } + + public void setAlignment(Alignment baseLine) { + if (baseLine == null) + return; + + if (mAlignment != baseLine) { + mAlignment = baseLine; + + requestLayout(); + invalidate(); + } + } + + public Alignment getScaleType() { + return mAlignment; + } + + private void adjustBaseLine(int lineHeight, int startIndex, int endIndex) { + if (mAlignment == Alignment.TOP) + return; + + for (int index = startIndex; index < endIndex; index++) { + final View child = getChildAt(index); + final AutoWrapLayout.LayoutParams lp = + (AutoWrapLayout.LayoutParams)child.getLayoutParams(); + Rect rect = rectList.get(index); + int offsetRaw = lineHeight - rect.height() - lp.topMargin - lp.bottomMargin; + if (mAlignment == Alignment.CENTER) + rect.offset(0, offsetRaw/2); + else if (mAlignment == Alignment.BOTTOM) + rect.offset(0, offsetRaw); + } + } + + + // TODO Take vertical mode + /** + * each row or line at least show one child + * + * horizontal only show child can show or partly show in parent + */ + @SuppressLint("DrawAllocation") + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + + int maxWidth = MeasureSpec.getSize(widthMeasureSpec); + int maxHeight = MeasureSpec.getSize(heightMeasureSpec); + + if (widthMode == MeasureSpec.UNSPECIFIED) + maxWidth = Integer.MAX_VALUE; + if (heightMode == MeasureSpec.UNSPECIFIED) + maxHeight = Integer.MAX_VALUE; + + int paddingLeft = getPaddingLeft(); + int paddingTop = getPaddingTop(); + int paddingRight = getPaddingRight(); + int paddingBottom = getPaddingBottom(); + + int maxRightBound = maxWidth - paddingRight; + int maxBottomBound = maxHeight - paddingBottom; + + int left; + int top; + int right; + int bottom; + int rightBound = paddingLeft; + int maxRightNoPadding = rightBound; + int bottomBound; + int lastMaxBottom = paddingTop; + int maxBottom = lastMaxBottom; + int childWidth; + int childHeight; + + int lineStartIndex = 0; + int lineEndIndex = 0; // endIndex + 1 + + rectList.clear(); + int childCount = getChildCount(); + for (int index = 0; index < childCount; index++) { + final View child = getChildAt(index); + child.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + if (child.getVisibility() == View.GONE) + continue; + final AutoWrapLayout.LayoutParams lp = + (AutoWrapLayout.LayoutParams)child.getLayoutParams(); + childWidth = child.getMeasuredWidth(); + childHeight = child.getMeasuredHeight(); + + left = rightBound + lp.leftMargin; + right = left + childWidth; + rightBound = right + lp.rightMargin; + if (rightBound > maxRightBound) { // Go to next row + lineEndIndex = index; + // Adjust child position base on baseline + adjustBaseLine(maxBottom - lastMaxBottom, lineStartIndex, lineEndIndex); + + // If child can't show in parent begin this line + if (maxBottom >= maxBottomBound) + break; + + // If it is first item in line, try to show it all + if (lineEndIndex == lineStartIndex) { + child.measure(MeasureSpec.makeMeasureSpec( + maxWidth - paddingLeft - paddingRight + - lp.leftMargin - lp.rightMargin, MeasureSpec.AT_MOST), + MeasureSpec.UNSPECIFIED); + childWidth = child.getMeasuredWidth(); + childHeight = child.getMeasuredHeight(); + } + left = paddingLeft + lp.leftMargin; + right = left + childWidth; + rightBound = right + lp.rightMargin; + + lastMaxBottom = maxBottom; + top = lastMaxBottom + lp.topMargin; + bottom = top + childHeight; + bottomBound = bottom + lp.bottomMargin; + + lineStartIndex = index; + } else { + top = lastMaxBottom + lp.topMargin; + bottom = top + childHeight; + bottomBound = bottom + lp.bottomMargin; + } + // Update max + if (rightBound > maxRightNoPadding) + maxRightNoPadding = rightBound; + if (bottomBound > maxBottom) + maxBottom = bottomBound; + Rect rect = new Rect(); + rect.left = left; + rect.top = top; + rect.right = right; + rect.bottom = bottom; + rectList.add(rect); + } + + // Handle last line baseline + adjustBaseLine(maxBottom - lastMaxBottom, lineStartIndex, rectList.size()); + + int measuredWidth; + int measuredHeight; + + if (widthMode == MeasureSpec.EXACTLY) + measuredWidth = maxWidth; + else + measuredWidth = maxRightNoPadding + paddingRight; + if (heightMode == MeasureSpec.EXACTLY) + measuredHeight = maxHeight; + else { + measuredHeight = maxBottom + paddingBottom; + if (heightMode == MeasureSpec.AT_MOST) + measuredHeight = measuredHeight > maxHeight ? maxHeight : measuredHeight; + } + + setMeasuredDimension(measuredWidth, measuredHeight); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + final int count = rectList.size(); + for(int i = 0; i < count; i++){ + final View child = this.getChildAt(i); + if (child.getVisibility() == View.GONE) + continue; + Rect rect = rectList.get(i); + child.layout(rect.left, rect.top, rect.right, rect.bottom); + } + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new AutoWrapLayout.LayoutParams(getContext(), attrs); + } + + @Override + protected LayoutParams generateDefaultLayoutParams() { + return new AutoWrapLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + } + + @Override + protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + return new LayoutParams(p); + } + + public static class LayoutParams extends ViewGroup.MarginLayoutParams { + public LayoutParams() { + this(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + } + public LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + } + public LayoutParams(int width, int height) { + super(width, height); + } + public LayoutParams(android.view.ViewGroup.LayoutParams source) { + super(source); + } + public LayoutParams(MarginLayoutParams source) { + super(source); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/BatteryView.java b/app/src/main/java/com/hippo/ehviewer/widget/BatteryView.java new file mode 100644 index 000000000..ad62a80eb --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/BatteryView.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.os.BatteryManager; +import android.util.AttributeSet; +import android.widget.TextView; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.drawable.BatteryDrawable; +import com.hippo.ehviewer.util.Ui; + +public class BatteryView extends TextView { + @SuppressWarnings("unused") + private static final String TAG = BatteryView.class.getSimpleName(); + + private int mLevel = 0; + private boolean mCharging = false; + + private BatteryDrawable mDrawable; + + private boolean mAttached = false; + private boolean mIsChargerWorking = false; + + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + + int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0) + * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0); + boolean charging = (intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1) + == BatteryManager.BATTERY_STATUS_CHARGING); + + if (mLevel != level || mCharging != charging) { + mLevel = level; + mCharging = charging; + if (mCharging && mLevel != 100) { + startCharger(); + } else { + stopCharger(); + mDrawable.setElect(mLevel); + } + setText(String.valueOf(mLevel) + "%"); + } + } + }; + + private final Runnable mCharger = new Runnable() { + + private int level = 0; + + @Override + public void run() { + level += 2; + if (level > mLevel) + level = 0; + mDrawable.setElect(level, mLevel <= BatteryDrawable.WARN_LIMIT); + getHandler().postDelayed(mCharger, 200); + } + }; + + public BatteryView(Context context) { + super(context); + init(); + } + + public BatteryView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BatteryView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + init(); + + TypedArray typedArray = context.obtainStyledAttributes( + attrs, R.styleable.BatteryView, defStyleAttr, 0); + int color = typedArray.getColor(R.styleable.BatteryView_bvColor, Color.WHITE); + typedArray.recycle(); + + mDrawable.setColor(color); + } + + private void init() { + mDrawable = new BatteryDrawable(); + // TODO how to get size ? + int height = Ui.dp2pix(12); + mDrawable.setBounds(0, 0, (int) (height / 0.618f), height); + setCompoundDrawables(mDrawable, null, null, null); + setCompoundDrawablePadding(Ui.dp2pix(5)); + } + + private void startCharger() { + if (!mIsChargerWorking) { + getHandler().post(mCharger); + mIsChargerWorking = true; + } + } + + private void stopCharger() { + if (mIsChargerWorking) { + getHandler().removeCallbacks(mCharger); + mIsChargerWorking = false; + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (!mAttached) { + mAttached = true; + + registerReceiver(); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (mAttached) { + unregisterReceiver(); + stopCharger(); + mAttached = false; + } + } + + private void registerReceiver() { + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + getContext().registerReceiver(mIntentReceiver, filter, null, getHandler()); + } + + private void unregisterReceiver() { + getContext().unregisterReceiver(mIntentReceiver); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/CategoryTable.java b/app/src/main/java/com/hippo/ehviewer/widget/CategoryTable.java new file mode 100644 index 000000000..d36e6d0e7 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/CategoryTable.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.widget.TableLayout; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.data.ListUrls; +import com.hippo.ehviewer.util.Utils; + +public class CategoryTable extends TableLayout { + + private CheckTextView mDoujinshi; + private CheckTextView mManga; + private CheckTextView mArtistCG; + private CheckTextView mGameCG; + private CheckTextView mWestern; + private CheckTextView mNonH; + private CheckTextView mImageSets; + private CheckTextView mCosplay; + private CheckTextView mAsianPorn; + private CheckTextView mMisc; + + + public CategoryTable(Context context) { + super(context); + init(); + } + + public CategoryTable(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public void init() { + LayoutInflater inflater = LayoutInflater.from(getContext()); + inflater.inflate(R.layout.category_table, this); + + setStretchAllColumns(true); + + mDoujinshi = (CheckTextView)findViewById(R.id.button_doujinshi); + mManga = (CheckTextView)findViewById(R.id.button_manga); + mArtistCG = (CheckTextView)findViewById(R.id.button_artistcg); + mGameCG = (CheckTextView)findViewById(R.id.button_gamecg); + mWestern = (CheckTextView)findViewById(R.id.button_western); + mNonH = (CheckTextView)findViewById(R.id.button_non_h); + mImageSets = (CheckTextView)findViewById(R.id.button_imageset); + mCosplay = (CheckTextView)findViewById(R.id.button_cosplay); + mAsianPorn = (CheckTextView)findViewById(R.id.button_asianporn); + mMisc = (CheckTextView)findViewById(R.id.button_misc); + } + + /** + * Set each button checked or not according to category. + * + * @param category + */ + public void setCategory(int category) { + mDoujinshi.setChecked(!Utils.int2boolean(category & ListUrls.DOUJINSHI)); + mManga.setChecked(!Utils.int2boolean(category & ListUrls.MANGA)); + mArtistCG.setChecked(!Utils.int2boolean(category & ListUrls.ARTIST_CG)); + mGameCG.setChecked(!Utils.int2boolean(category & ListUrls.GAME_CG)); + mWestern.setChecked(!Utils.int2boolean(category & ListUrls.WESTERN)); + mNonH.setChecked(!Utils.int2boolean(category & ListUrls.NON_H)); + mImageSets.setChecked(!Utils.int2boolean(category & ListUrls.IMAGE_SET)); + mCosplay.setChecked(!Utils.int2boolean(category & ListUrls.COSPLAY)); + mAsianPorn.setChecked(!Utils.int2boolean(category & ListUrls.ASIAN_PORN)); + mMisc.setChecked(!Utils.int2boolean(category & ListUrls.MISC)); + } + + /** + * Get category according to button. + * @return + */ + public int getCategory() { + int category = 0; + if (!mDoujinshi.isChecked()) category |= ListUrls.DOUJINSHI; + if (!mManga.isChecked()) category |= ListUrls.MANGA; + if (!mArtistCG.isChecked()) category |= ListUrls.ARTIST_CG; + if (!mGameCG.isChecked()) category |= ListUrls.GAME_CG; + if (!mWestern.isChecked()) category |= ListUrls.WESTERN; + if (!mNonH.isChecked()) category |= ListUrls.NON_H; + if (!mImageSets.isChecked()) category |= ListUrls.IMAGE_SET; + if (!mCosplay.isChecked()) category |= ListUrls.COSPLAY; + if (!mAsianPorn.isChecked()) category |= ListUrls.ASIAN_PORN; + if (!mMisc.isChecked()) category |= ListUrls.MISC; + return category; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/CheckTextView.java b/app/src/main/java/com/hippo/ehviewer/widget/CheckTextView.java new file mode 100644 index 000000000..0ffe23935 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/CheckTextView.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.TextView; + +public class CheckTextView extends TextView implements OnClickListener{ + @SuppressWarnings("unused") + private static String TAG = "CheckImage"; + + private static int MASK = 0xbb000000; + + private Paint mPaint; + + private boolean mChecked = false; + + public CheckTextView(Context context) { + super(context); + init(); + } + + public CheckTextView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CheckTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + mPaint = new Paint(); + mPaint.setColor(MASK); + setOnClickListener(this); + } + + @Override + public void onClick(View v) { + mChecked = !mChecked; + invalidate(); + } + + @SuppressLint("DrawAllocation") + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (mChecked) + canvas.drawRect(canvas.getClipBounds(), mPaint); + } + + public void setChecked(boolean checked) { + mChecked = checked; + invalidate(); + } + + public boolean isChecked() { + return mChecked; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/ColorPickerView.java b/app/src/main/java/com/hippo/ehviewer/widget/ColorPickerView.java new file mode 100644 index 000000000..da6f657c7 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/ColorPickerView.java @@ -0,0 +1,947 @@ +/* + * Copyright (C) 2010 Daniel Nilsson Copyright (C) 2012 THe CyanogenMod Project + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ComposeShader; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.Paint.Style; +import android.graphics.Point; +import android.graphics.PorterDuff; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.Shader.TileMode; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +/** + * Displays a color picker to the user and allow them to select a color. A + * slider for the alpha channel is also available. Enable it by setting + * setAlphaSliderVisible(boolean) to true. + * + * @author Daniel Nilsson + */ +public class ColorPickerView extends View { + + public interface OnColorChangedListener { + public void onColorChanged(View view, int color); + } + + private final static int PANEL_SAT_VAL = 0; + + private final static int PANEL_HUE = 1; + + private final static int PANEL_ALPHA = 2; + + /** + * The width in pixels of the border surrounding all color panels. + */ + private final static float BORDER_WIDTH_PX = 1; + + /** + * The width in dp of the hue panel. + */ + private float HUE_PANEL_WIDTH = 30f; + + /** + * The height in dp of the alpha panel + */ + private float ALPHA_PANEL_HEIGHT = 20f; + + /** + * The distance in dp between the different color panels. + */ + private float PANEL_SPACING = 10f; + + /** + * The radius in dp of the color palette tracker circle. + */ + private float PALETTE_CIRCLE_TRACKER_RADIUS = 5f; + + /** + * The dp which the tracker of the hue or alpha panel will extend outside of + * its bounds. + */ + private float RECTANGLE_TRACKER_OFFSET = 2f; + + private static float mDensity = 1f; + + private OnColorChangedListener mListener; + + private Paint mSatValPaint; + + private Paint mSatValTrackerPaint; + + private Paint mHuePaint; + + private Paint mHueTrackerPaint; + + private Paint mAlphaPaint; + + private Paint mAlphaTextPaint; + + private Paint mBorderPaint; + + private Shader mValShader; + + private Shader mSatShader; + + private Shader mHueShader; + + private Shader mAlphaShader; + + private int mAlpha = 0xff; + + private float mHue = 360f; + + private float mSat = 0f; + + private float mVal = 0f; + + private String mAlphaSliderText = "Alpha"; + + private int mSliderTrackerColor = 0xff1c1c1c; + + private int mBorderColor = 0xff6E6E6E; + + private boolean mShowAlphaPanel = false; + + /* + * To remember which panel that has the "focus" when processing hardware + * button data. + */ + private int mLastTouchedPanel = PANEL_SAT_VAL; + + /** + * Offset from the edge we must have or else the finger tracker will get + * clipped when it is drawn outside of the view. + */ + private float mDrawingOffset; + + /* + * Distance form the edges of the view of where we are allowed to draw. + */ + private RectF mDrawingRect; + + private RectF mSatValRect; + + private RectF mHueRect; + + private RectF mAlphaRect; + + private AlphaPatternDrawable mAlphaPattern; + + private Point mStartTouchPoint = null; + + public ColorPickerView(final Context context) { + this(context, null); + } + + public ColorPickerView(final Context context, final AttributeSet attrs) { + this(context, attrs, 0); + } + + public ColorPickerView(final Context context, final AttributeSet attrs, final int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + mDensity = getContext().getResources().getDisplayMetrics().density; + PALETTE_CIRCLE_TRACKER_RADIUS *= mDensity; + RECTANGLE_TRACKER_OFFSET *= mDensity; + HUE_PANEL_WIDTH *= mDensity; + ALPHA_PANEL_HEIGHT *= mDensity; + PANEL_SPACING = PANEL_SPACING * mDensity; + + mDrawingOffset = calculateRequiredOffset(); + + initPaintTools(); + + // Needed for receiving track ball motion events. + setFocusable(true); + setFocusableInTouchMode(true); + } + + private void initPaintTools() { + + mSatValPaint = new Paint(); + mSatValTrackerPaint = new Paint(); + mHuePaint = new Paint(); + mHueTrackerPaint = new Paint(); + mAlphaPaint = new Paint(); + mAlphaTextPaint = new Paint(); + mBorderPaint = new Paint(); + + mSatValTrackerPaint.setStyle(Style.STROKE); + mSatValTrackerPaint.setStrokeWidth(2f * mDensity); + mSatValTrackerPaint.setAntiAlias(true); + + mHueTrackerPaint.setColor(mSliderTrackerColor); + mHueTrackerPaint.setStyle(Style.STROKE); + mHueTrackerPaint.setStrokeWidth(2f * mDensity); + mHueTrackerPaint.setAntiAlias(true); + + mAlphaTextPaint.setColor(0xff1c1c1c); + mAlphaTextPaint.setTextSize(14f * mDensity); + mAlphaTextPaint.setAntiAlias(true); + mAlphaTextPaint.setTextAlign(Align.CENTER); + mAlphaTextPaint.setFakeBoldText(true); + + } + + private float calculateRequiredOffset() { + float offset = Math.max(PALETTE_CIRCLE_TRACKER_RADIUS, RECTANGLE_TRACKER_OFFSET); + offset = Math.max(offset, BORDER_WIDTH_PX * mDensity); + + return offset * 1.5f; + } + + private int[] buildHueColorArray() { + + final int[] hue = new int[361]; + + int count = 0; + for (int i = hue.length - 1; i >= 0; i--, count++) { + hue[count] = Color.HSVToColor(new float[] { + i, 1f, 1f + }); + } + + return hue; + } + + @Override + protected void onDraw(final Canvas canvas) { + + if (mDrawingRect.width() <= 0 || mDrawingRect.height() <= 0) { + return; + } + + drawSatValPanel(canvas); + drawHuePanel(canvas); + drawAlphaPanel(canvas); + + } + + private void drawSatValPanel(final Canvas canvas) { + + final RectF rect = mSatValRect; + + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(mDrawingRect.left, mDrawingRect.top, rect.right + BORDER_WIDTH_PX, + rect.bottom + BORDER_WIDTH_PX, mBorderPaint); + } + + if (mValShader == null) { + mValShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, + 0xffffffff, 0xff000000, TileMode.CLAMP); + } + + final int rgb = Color.HSVToColor(new float[] { + mHue, 1f, 1f + }); + + mSatShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, 0xffffffff, rgb, + TileMode.CLAMP); + final ComposeShader mShader = new ComposeShader(mValShader, mSatShader, + PorterDuff.Mode.MULTIPLY); + mSatValPaint.setShader(mShader); + + canvas.drawRect(rect, mSatValPaint); + + final Point p = satValToPoint(mSat, mVal); + + mSatValTrackerPaint.setColor(0xff000000); + canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS - 1f * mDensity, + mSatValTrackerPaint); + + mSatValTrackerPaint.setColor(0xffdddddd); + canvas.drawCircle(p.x, p.y, PALETTE_CIRCLE_TRACKER_RADIUS, mSatValTrackerPaint); + + } + + private void drawHuePanel(final Canvas canvas) { + + final RectF rect = mHueRect; + + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(rect.left - BORDER_WIDTH_PX, rect.top - BORDER_WIDTH_PX, rect.right + + BORDER_WIDTH_PX, rect.bottom + BORDER_WIDTH_PX, mBorderPaint); + } + + if (mHueShader == null) { + mHueShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, + buildHueColorArray(), null, TileMode.CLAMP); + mHuePaint.setShader(mHueShader); + } + + canvas.drawRect(rect, mHuePaint); + + final float rectHeight = 4 * mDensity / 2; + + final Point p = hueToPoint(mHue); + + final RectF r = new RectF(); + r.left = rect.left - RECTANGLE_TRACKER_OFFSET; + r.right = rect.right + RECTANGLE_TRACKER_OFFSET; + r.top = p.y - rectHeight; + r.bottom = p.y + rectHeight; + + canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint); + + } + + private void drawAlphaPanel(final Canvas canvas) { + + if (!mShowAlphaPanel || mAlphaRect == null || mAlphaPattern == null) { + return; + } + + final RectF rect = mAlphaRect; + + if (BORDER_WIDTH_PX > 0) { + mBorderPaint.setColor(mBorderColor); + canvas.drawRect(rect.left - BORDER_WIDTH_PX, rect.top - BORDER_WIDTH_PX, rect.right + + BORDER_WIDTH_PX, rect.bottom + BORDER_WIDTH_PX, mBorderPaint); + } + + mAlphaPattern.draw(canvas); + + final float[] hsv = new float[] { + mHue, mSat, mVal + }; + final int color = Color.HSVToColor(hsv); + final int acolor = Color.HSVToColor(0, hsv); + + mAlphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, color, acolor, + TileMode.CLAMP); + + mAlphaPaint.setShader(mAlphaShader); + + canvas.drawRect(rect, mAlphaPaint); + + if (mAlphaSliderText != null && mAlphaSliderText != "") { + canvas.drawText(mAlphaSliderText, rect.centerX(), rect.centerY() + 4 * mDensity, + mAlphaTextPaint); + } + + final float rectWidth = 4 * mDensity / 2; + + final Point p = alphaToPoint(mAlpha); + + final RectF r = new RectF(); + r.left = p.x - rectWidth; + r.right = p.x + rectWidth; + r.top = rect.top - RECTANGLE_TRACKER_OFFSET; + r.bottom = rect.bottom + RECTANGLE_TRACKER_OFFSET; + + canvas.drawRoundRect(r, 2, 2, mHueTrackerPaint); + + } + + private Point hueToPoint(final float hue) { + + final RectF rect = mHueRect; + final float height = rect.height(); + + final Point p = new Point(); + + p.y = (int)(height - hue * height / 360f + rect.top); + p.x = (int)rect.left; + + return p; + } + + private Point satValToPoint(final float sat, final float val) { + + final RectF rect = mSatValRect; + final float height = rect.height(); + final float width = rect.width(); + + final Point p = new Point(); + + p.x = (int)(sat * width + rect.left); + p.y = (int)((1f - val) * height + rect.top); + + return p; + } + + private Point alphaToPoint(final int alpha) { + + final RectF rect = mAlphaRect; + final float width = rect.width(); + + final Point p = new Point(); + + p.x = (int)(width - alpha * width / 0xff + rect.left); + p.y = (int)rect.top; + + return p; + + } + + private float[] pointToSatVal(float x, float y) { + + final RectF rect = mSatValRect; + final float[] result = new float[2]; + + final float width = rect.width(); + final float height = rect.height(); + + if (x < rect.left) { + x = 0f; + } else if (x > rect.right) { + x = width; + } else { + x = x - rect.left; + } + + if (y < rect.top) { + y = 0f; + } else if (y > rect.bottom) { + y = height; + } else { + y = y - rect.top; + } + + result[0] = 1.f / width * x; + result[1] = 1.f - 1.f / height * y; + + return result; + } + + private float pointToHue(float y) { + + final RectF rect = mHueRect; + + final float height = rect.height(); + + if (y < rect.top) { + y = 0f; + } else if (y > rect.bottom) { + y = height; + } else { + y = y - rect.top; + } + + return 360f - y * 360f / height; + } + + private int pointToAlpha(int x) { + + final RectF rect = mAlphaRect; + final int width = (int)rect.width(); + + if (x < rect.left) { + x = 0; + } else if (x > rect.right) { + x = width; + } else { + x = x - (int)rect.left; + } + + return 0xff - x * 0xff / width; + + } + + @Override + public boolean onTrackballEvent(final MotionEvent event) { + + final float x = event.getX(); + final float y = event.getY(); + + boolean update = false; + + if (event.getAction() == MotionEvent.ACTION_MOVE) { + + switch (mLastTouchedPanel) { + + case PANEL_SAT_VAL: + + float sat, + val; + + sat = mSat + x / 50f; + val = mVal - y / 50f; + + if (sat < 0f) { + sat = 0f; + } else if (sat > 1f) { + sat = 1f; + } + + if (val < 0f) { + val = 0f; + } else if (val > 1f) { + val = 1f; + } + + mSat = sat; + mVal = val; + + update = true; + + break; + + case PANEL_HUE: + + float hue = mHue - y * 10f; + + if (hue < 0f) { + hue = 0f; + } else if (hue > 360f) { + hue = 360f; + } + + mHue = hue; + + update = true; + + break; + + case PANEL_ALPHA: + + if (!mShowAlphaPanel || mAlphaRect == null) { + update = false; + } else { + + int alpha = (int)(mAlpha - x * 10); + + if (alpha < 0) { + alpha = 0; + } else if (alpha > 0xff) { + alpha = 0xff; + } + + mAlpha = alpha; + + update = true; + } + + break; + } + + } + + if (update) { + + if (mListener != null) { + mListener.onColorChanged(this, Color.HSVToColor(mAlpha, new float[] { + mHue, mSat, mVal + })); + } + + invalidate(); + return true; + } + + return super.onTrackballEvent(event); + } + + @Override + public boolean onTouchEvent(final MotionEvent event) { + + boolean update = false; + + switch (event.getAction()) { + + case MotionEvent.ACTION_DOWN: + + mStartTouchPoint = new Point((int)event.getX(), (int)event.getY()); + + update = moveTrackersIfNeeded(event); + + break; + + case MotionEvent.ACTION_MOVE: + + update = moveTrackersIfNeeded(event); + + break; + + case MotionEvent.ACTION_UP: + + mStartTouchPoint = null; + + update = moveTrackersIfNeeded(event); + + break; + + } + + if (update) { + + if (mListener != null) { + mListener.onColorChanged(this, Color.HSVToColor(mAlpha, new float[] { + mHue, mSat, mVal + })); + } + + invalidate(); + return true; + } + + return super.onTouchEvent(event); + } + + private boolean moveTrackersIfNeeded(final MotionEvent event) { + + if (mStartTouchPoint == null) { + return false; + } + + boolean update = false; + + final int startX = mStartTouchPoint.x; + final int startY = mStartTouchPoint.y; + + if (mHueRect.contains(startX, startY)) { + mLastTouchedPanel = PANEL_HUE; + + mHue = pointToHue(event.getY()); + + update = true; + } else if (mSatValRect.contains(startX, startY)) { + + mLastTouchedPanel = PANEL_SAT_VAL; + + final float[] result = pointToSatVal(event.getX(), event.getY()); + + mSat = result[0]; + mVal = result[1]; + + update = true; + } else if (mAlphaRect != null && mAlphaRect.contains(startX, startY)) { + + mLastTouchedPanel = PANEL_ALPHA; + + mAlpha = pointToAlpha((int)event.getX()); + + update = true; + } + + return update; + } + + @Override + protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { + + int width = 0; + int height = 0; + + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int heightMode = MeasureSpec.getMode(heightMeasureSpec); + + int widthAllowed = MeasureSpec.getSize(widthMeasureSpec); + int heightAllowed = MeasureSpec.getSize(heightMeasureSpec); + + widthAllowed = chooseWidth(widthMode, widthAllowed); + heightAllowed = chooseHeight(heightMode, heightAllowed); + + if (!mShowAlphaPanel) { + height = (int)(widthAllowed - PANEL_SPACING - HUE_PANEL_WIDTH); + + // If calculated height (based on the width) is more than the + // allowed height. + if (height > heightAllowed) { + height = heightAllowed; + width = (int)(height + PANEL_SPACING + HUE_PANEL_WIDTH); + } else { + width = widthAllowed; + } + } else { + + width = (int)(heightAllowed - ALPHA_PANEL_HEIGHT + HUE_PANEL_WIDTH); + + if (width > widthAllowed) { + width = widthAllowed; + height = (int)(widthAllowed - HUE_PANEL_WIDTH + ALPHA_PANEL_HEIGHT); + } else { + height = heightAllowed; + } + + } + + setMeasuredDimension(width, height); + } + + private int chooseWidth(final int mode, final int size) { + if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) { + return size; + } else { // (mode == MeasureSpec.UNSPECIFIED) + return getPrefferedWidth(); + } + } + + private int chooseHeight(final int mode, final int size) { + if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) { + return size; + } else { // (mode == MeasureSpec.UNSPECIFIED) + return getPrefferedHeight(); + } + } + + private int getPrefferedWidth() { + + int width = getPrefferedHeight(); + + if (mShowAlphaPanel) { + width -= PANEL_SPACING + ALPHA_PANEL_HEIGHT; + } + + return (int)(width + HUE_PANEL_WIDTH + PANEL_SPACING); + + } + + private int getPrefferedHeight() { + + int height = (int)(200 * mDensity); + + if (mShowAlphaPanel) { + height += PANEL_SPACING + ALPHA_PANEL_HEIGHT; + } + + return height; + } + + @Override + protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + mDrawingRect = new RectF(); + mDrawingRect.left = mDrawingOffset + getPaddingLeft(); + mDrawingRect.right = w - mDrawingOffset - getPaddingRight(); + mDrawingRect.top = mDrawingOffset + getPaddingTop(); + mDrawingRect.bottom = h - mDrawingOffset - getPaddingBottom(); + + setUpSatValRect(); + setUpHueRect(); + setUpAlphaRect(); + } + + private void setUpSatValRect() { + + final RectF dRect = mDrawingRect; + float panelSide = dRect.height() - BORDER_WIDTH_PX * 2; + + if (mShowAlphaPanel) { + panelSide -= PANEL_SPACING + ALPHA_PANEL_HEIGHT; + } + + final float left = dRect.left + BORDER_WIDTH_PX; + final float top = dRect.top + BORDER_WIDTH_PX; + final float bottom = top + panelSide; + final float right = left + panelSide; + + mSatValRect = new RectF(left, top, right, bottom); + } + + private void setUpHueRect() { + final RectF dRect = mDrawingRect; + + final float left = dRect.right - HUE_PANEL_WIDTH + BORDER_WIDTH_PX; + final float top = dRect.top + BORDER_WIDTH_PX; + final float bottom = dRect.bottom - BORDER_WIDTH_PX + - (mShowAlphaPanel ? PANEL_SPACING + ALPHA_PANEL_HEIGHT : 0); + final float right = dRect.right - BORDER_WIDTH_PX; + + mHueRect = new RectF(left, top, right, bottom); + } + + private void setUpAlphaRect() { + + if (!mShowAlphaPanel) { + return; + } + + final RectF dRect = mDrawingRect; + + final float left = dRect.left + BORDER_WIDTH_PX; + final float top = dRect.bottom - ALPHA_PANEL_HEIGHT + BORDER_WIDTH_PX; + final float bottom = dRect.bottom - BORDER_WIDTH_PX; + final float right = dRect.right - BORDER_WIDTH_PX; + + mAlphaRect = new RectF(left, top, right, bottom); + + mAlphaPattern = new AlphaPatternDrawable((int)(5 * mDensity)); + mAlphaPattern.setBounds(Math.round(mAlphaRect.left), Math.round(mAlphaRect.top), + Math.round(mAlphaRect.right), Math.round(mAlphaRect.bottom)); + + } + + /** + * Set a OnColorChangedListener to get notified when the color selected by + * the user has changed. + * + * @param listener + */ + public void setOnColorChangedListener(final OnColorChangedListener listener) { + mListener = listener; + } + + /** + * Set the color of the border surrounding all panels. + * + * @param color + */ + public void setBorderColor(final int color) { + mBorderColor = color; + invalidate(); + } + + /** + * Get the color of the border surrounding all panels. + */ + public int getBorderColor() { + return mBorderColor; + } + + /** + * Get the current color this view is showing. + * + * @return the current color. + */ + public int getColor() { + return Color.HSVToColor(mAlpha, new float[] { + mHue, mSat, mVal + }); + } + + /** + * Set the color the view should show. + * + * @param color The color that should be selected. + */ + public void setColor(final int color) { + setColor(color, false); + } + + /** + * Set the color this view should show. + * + * @param color The color that should be selected. + * @param callback If you want to get a callback to your + * OnColorChangedListener. + */ + public void setColor(final int color, final boolean callback) { + + final int alpha = Color.alpha(color); + final int red = Color.red(color); + final int blue = Color.blue(color); + final int green = Color.green(color); + + final float[] hsv = new float[3]; + + Color.RGBToHSV(red, green, blue, hsv); + + mAlpha = alpha; + mHue = hsv[0]; + mSat = hsv[1]; + mVal = hsv[2]; + + if (callback && mListener != null) { + mListener.onColorChanged(this, Color.HSVToColor(mAlpha, new float[] { + mHue, mSat, mVal + })); + } + + invalidate(); + } + + /** + * Get the drawing offset of the color picker view. The drawing offset is + * the distance from the side of a panel to the side of the view minus the + * padding. Useful if you want to have your own panel below showing the + * currently selected color and want to align it perfectly. + * + * @return The offset in pixels. + */ + public float getDrawingOffset() { + return mDrawingOffset; + } + + /** + * Set if the user is allowed to adjust the alpha panel. Default is false. + * If it is set to false no alpha will be set. + * + * @param visible + */ + public void setAlphaSliderVisible(final boolean visible) { + + if (mShowAlphaPanel != visible) { + mShowAlphaPanel = visible; + + /* + * Reset all shader to force a recreation. Otherwise they will not + * look right after the size of the view has changed. + */ + mValShader = null; + mSatShader = null; + mHueShader = null; + mAlphaShader = null; + + requestLayout(); + } + + } + + public void setSliderTrackerColor(final int color) { + mSliderTrackerColor = color; + + mHueTrackerPaint.setColor(mSliderTrackerColor); + + invalidate(); + } + + public int getSliderTrackerColor() { + return mSliderTrackerColor; + } + + /** + * Set the text that should be shown in the alpha slider. Set to null to + * disable text. + * + * @param res string resource id. + */ + public void setAlphaSliderText(final int res) { + final String text = getContext().getString(res); + setAlphaSliderText(text); + } + + /** + * Set the text that should be shown in the alpha slider. Set to null to + * disable text. + * + * @param text Text that should be shown. + */ + public void setAlphaSliderText(final String text) { + mAlphaSliderText = text; + invalidate(); + } + + /** + * Get the current value of the text that will be shown in the alpha slider. + * + * @return + */ + public String getAlphaSliderText() { + return mAlphaSliderText; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/ColorView.java b/app/src/main/java/com/hippo/ehviewer/widget/ColorView.java new file mode 100644 index 000000000..5bfc07c13 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/ColorView.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Canvas; +import android.os.Build; +import android.util.AttributeSet; +import android.view.View; + +public class ColorView extends View { + + private int mColor; + + public ColorView(Context context) { + super(context); + } + + public ColorView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ColorView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public ColorView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public void setColor(int color) { + if (mColor != color) { + mColor = color; + invalidate(); + } + } + + @Override + protected void onDraw(Canvas canvas) { + canvas.drawColor(mColor); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/DrawerListView.java b/app/src/main/java/com/hippo/ehviewer/widget/DrawerListView.java new file mode 100644 index 000000000..f1517b051 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/DrawerListView.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.util.Ui; + +public class DrawerListView extends ListView { + + private Context mContext; + private DrawerListAdapter mAdapter; + + private static final int DRAWABLE_SIDE = Ui.dp2pix(24); + + public DrawerListView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public DrawerListView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public DrawerListView(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(context); + } + + private void init(Context context) { + mContext = context; + mAdapter = new DrawerListAdapter(); + setAdapter(mAdapter); + } + + public void setData(Drawable[] drawableArray, CharSequence[] titleArray) { + mAdapter.setData(drawableArray, titleArray); + } + + private class DrawerListAdapter extends BaseAdapter { + + private Drawable[] mDrawableArray; + private CharSequence[] mTitleArray; + + private void setData(Drawable[] drawableArray, CharSequence[] titleArray) { + if (drawableArray != null && titleArray != null) { + if (drawableArray.length != titleArray.length) + throw new java.lang.IllegalStateException("drawableArray.length != titleArray.length"); + } else if (drawableArray == null && titleArray == null) { + throw new java.lang.IllegalStateException("drawableArray and titleArray should be all null or not null"); + } + mDrawableArray = drawableArray; + mTitleArray = titleArray; + notifyDataSetChanged(); + } + + @Override + public int getCount() { + return mTitleArray == null ? 0 : mTitleArray.length; + } + + @Override + public Object getItem(int position) { + return position; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) + convertView = LayoutInflater.from(mContext).inflate(R.layout.drawer_list_item, parent, false); + + TextView tv = (TextView) convertView; + Drawable d = mDrawableArray[position]; + CharSequence t = mTitleArray[position]; + tv.setText(t); + d.setBounds(0, 0, DRAWABLE_SIDE, DRAWABLE_SIDE); + tv.setCompoundDrawables(d, null, null, null); + + return convertView; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/ExpandableListItem.java b/app/src/main/java/com/hippo/ehviewer/widget/ExpandableListItem.java new file mode 100644 index 000000000..76d2af7d8 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/ExpandableListItem.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +public interface ExpandableListItem { + + public void setExpanded(boolean expanding); + + public boolean getExpanded(); +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/ExpandingListView.java b/app/src/main/java/com/hippo/ehviewer/widget/ExpandingListView.java new file mode 100644 index 000000000..ef58a707c --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/ExpandingListView.java @@ -0,0 +1,563 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.content.Context; +import android.graphics.Canvas; +import android.support.v4.view.ViewCompat; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewTreeObserver; +import android.widget.AbsListView; +import android.widget.AdapterView; +import android.widget.ListView; + +/** + * A custom listview which supports the preview of extra content corresponding to each cell + * by clicking on the cell to hide and show the extra content. + */ +public class ExpandingListView extends ListView { + + @SuppressWarnings("unused") + private static final String TAG = ExpandingListView.class.getSimpleName(); + + private int mExpandingId; + private boolean mShouldRemoveObserver = false; + private boolean mAnimating = false; + + private final List mViewsToDraw = new ArrayList(); + + private int[] mTranslate; + + public ExpandingListView(Context context) { + super(context); + init(); + } + + public ExpandingListView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public ExpandingListView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + setOnItemClickListener(mItemClickListener); + } + + public void setExpandingId(int id) { + mExpandingId = id; + } + + public boolean isAnimating() { + return mAnimating; + } + + /** + * Listens for item clicks and expands or collapses the selected view depending on + * its current state. + */ + private final AdapterView.OnItemClickListener mItemClickListener = new AdapterView + .OnItemClickListener() { + @Override + public void onItemClick (AdapterView parent, View view, int position, long id) { + if (view.findViewById(mExpandingId).getVisibility() == View.GONE) { + expandView(view); + } else { + collapseView(view); + } + } + }; + + private int[] getTopAndBottomTranslationsE(int top, int bottom, int yDelta) { + int yTranslateTop = 0; + int yTranslateBottom = yDelta; + + int height = bottom - top; + int bottomLine = getHeight() - getPaddingBottom(); + + boolean isOverTop = top < getPaddingTop(); + boolean isBelowBottom = (top + height + yDelta) > bottomLine; + if (isOverTop) { + yTranslateTop = top - getPaddingTop(); + yTranslateBottom = yDelta - yTranslateTop; + } else if (isBelowBottom){ + int deltaBelow = top + height + yDelta - bottomLine; + yTranslateTop = top - deltaBelow < getPaddingTop() ? (top - getPaddingTop()) : deltaBelow; + yTranslateBottom = yDelta - yTranslateTop; + } + + return new int[] {yTranslateTop, yTranslateBottom}; + } + + private int[] getTopAndBottomTranslationsC(int top, int bottom, int yDelta, int firstTop, View view) { + int yTranslateTop = 0; + int yTranslateBottom = yDelta; + + int bottomLine = getHeight() - getPaddingBottom(); + + int index = 0; + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + if (getChildAt(i).equals(view)) { + index = i; + break; + } + } + + int maxBottom = bottom; + for (int i = index + 1; i < getChildCount(); i++) { + View v = getChildAt(i); + maxBottom += v.getVisibility() != View.GONE ? v.getHeight() + getDividerHeight() : 0; + } + + int newMaxBottom = maxBottom - yDelta; + int maxYTranslateTop = getPaddingTop() - firstTop; + + boolean isCollapsingAboveBottom = maxBottom >= bottomLine && newMaxBottom < bottomLine; + + if (isCollapsingAboveBottom) { + yTranslateTop = bottomLine - newMaxBottom; + if (yTranslateTop > maxYTranslateTop) + yTranslateTop = maxYTranslateTop; + yTranslateBottom = yDelta - yTranslateTop; + } + + return new int[] {yTranslateTop, yTranslateBottom}; + } + + + /** + * This method expands the view that was clicked and animates all the views + * around it to make room for the expanding view. There are several steps required + * to do this which are outlined below. + * + * 1. Store the current top and bottom bounds of each visible item in the listview. + * 2. Update the layout parameters of the selected view. In the context of this + * method, the view should be originally collapsed and set to some custom height. + * The layout parameters are updated so as to wrap the content of the additional + * text that is to be displayed. + * + * After invoking a layout to take place, the listview will order all the items + * such that there is space for each view. This layout will be independent of what + * the bounds of the items were prior to the layout so two pre-draw passes will + * be made. This is necessary because after the layout takes place, some views that + * were visible before the layout may now be off bounds but a reference to these + * views is required so the animation completes as intended. + * + * 3. The first predraw pass will set the bounds of all the visible items to + * their original location before the layout took place and then force another + * layout. Since the bounds of the cells cannot be set directly, the method + * setSelectionFromTop can be used to achieve a very similar effect. + * 4. The expanding view's bounds are animated to what the final values should be + * from the original bounds. + * 5. The bounds above the expanding view are animated upwards while the bounds + * below the expanding view are animated downwards. + * 6. The extra text is faded in as its contents become visible throughout the + * animation process. + * + * It is important to note that the listview is disabled during the animation + * because the scrolling behaviour is unpredictable if the bounds of the items + * within the listview are not constant during the scroll. + */ + + private void expandView(final View view) { + mAnimating = true; + + final ExpandableListItem viewObject = (ExpandableListItem)getItemAtPosition + (getPositionForView(view)); + + /* Store the original top and bottom bounds of all the cells.*/ + final int oldTop = view.getTop(); + final int oldBottom = view.getBottom(); + + final HashMap oldCoordinates = new HashMap(); + + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View v = getChildAt(i); + ViewCompat.setHasTransientState(v, true); + oldCoordinates.put(v, new int[] {v.getTop(), v.getBottom()}); + } + + /* Update the layout so the extra content becomes visible.*/ + final View expandingLayout = view.findViewById(mExpandingId); + expandingLayout.setVisibility(View.VISIBLE); + + /* Add an onPreDraw Listener to the listview. onPreDraw will get invoked after onLayout + * and onMeasure have run but before anything has been drawn. This + * means that the final post layout properties for all the items have already been + * determined, but still have not been rendered onto the screen.*/ + final ViewTreeObserver observer = getViewTreeObserver(); + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + + @Override + public boolean onPreDraw() { + /* Determine if this is the first or second pass.*/ + if (!mShouldRemoveObserver) { + mShouldRemoveObserver = true; + + /* Calculate what the parameters should be for setSelectionFromTop. + * The ListView must be offset in a way, such that after the animation + * takes place, all the cells that remain visible are rendered completely + * by the ListView.*/ + int newTop = view.getTop(); + int newBottom = view.getBottom(); + + int newHeight = newBottom - newTop; + int oldHeight = oldBottom - oldTop; + int delta = newHeight - oldHeight; + + mTranslate = getTopAndBottomTranslationsE(oldTop, oldBottom, delta); + + int currentTop = view.getTop(); + int futureTop = oldTop - mTranslate[0]; + + int firstChildStartTop = getChildAt(0).getTop(); + int firstVisiblePosition = getFirstVisiblePosition(); + int deltaTop = currentTop - futureTop; + + int i; + int childCount = getChildCount(); + for (i = 0; i < childCount; i++) { + View v = getChildAt(i); + int height = v.getBottom() - Math.max(0, v.getTop()); + if (deltaTop - height > 0) { + firstVisiblePosition++; + deltaTop -= height; + } else { + break; + } + } + + if (i > 0) { + firstChildStartTop = 0; + } + + setSelectionFromTop(firstVisiblePosition, firstChildStartTop - deltaTop - getPaddingTop()); + + /* Request another layout to update the layout parameters of the cells.*/ + requestLayout(); + + /* Return false such that the ListView does not redraw its contents on + * this layout but only updates all the parameters associated with its + * children.*/ + return false; + } + + /* Remove the predraw listener so this method does not keep getting called. */ + mShouldRemoveObserver = false; + observer.removeOnPreDrawListener(this); + + int yTranslateTop = mTranslate[0]; + int yTranslateBottom = mTranslate[1]; + + ArrayList animations = new ArrayList(); + + int index = indexOfChild(view); + + /* Loop through all the views that were on the screen before the cell was + * expanded. Some cells will still be children of the ListView while + * others will not. The cells that remain children of the ListView + * simply have their bounds animated appropriately. The cells that are no + * longer children of the ListView also have their bounds animated, but + * must also be added to a list of views which will be drawn in dispatchDraw.*/ + for (View v: oldCoordinates.keySet()) { + int[] old = oldCoordinates.get(v); + v.setTop(old[0]); + v.setBottom(old[1]); + if (v.getParent() == null) { + mViewsToDraw.add(v); + int delta = old[0] < oldTop ? -yTranslateTop : yTranslateBottom; + animations.add(getAnimation(v, delta, delta)); + } else { + int i = indexOfChild(v); + if (v != view) { + int delta = i > index ? yTranslateBottom : -yTranslateTop; + animations.add(getAnimation(v, delta, delta)); + } + ViewCompat.setHasTransientState(v, false); + } + } + + /* Adds animation for expanding the cell that was clicked. */ + animations.add(getAnimation(view, -yTranslateTop, yTranslateBottom)); + + /* Adds an animation for fading in the extra content. */ + animations.add(ObjectAnimator.ofFloat(view.findViewById(mExpandingId), + View.ALPHA, 0, 1)); + + /* Disabled the ListView for the duration of the animation.*/ + setEnabled(false); + setClickable(false); + + /* Play all the animations created above together at the same time. */ + AnimatorSet s = new AnimatorSet(); + s.playTogether(animations); + s.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mAnimating = false; + viewObject.setExpanded(true); + setEnabled(true); + setClickable(true); + if (mViewsToDraw.size() > 0) { + for (View v : mViewsToDraw) { + ViewCompat.setHasTransientState(v, false); + } + } + mViewsToDraw.clear(); + } + }); + s.start(); + return true; + } + }); + } + + /** + * By overriding dispatchDraw, we can draw the cells that disappear during the + * expansion process. When the cell expands, some items below or above the expanding + * cell may be moved off screen and are thus no longer children of the ListView's + * layout. By storing a reference to these views prior to the layout, and + * guaranteeing that these cells do not get recycled, the cells can be drawn + * directly onto the canvas during the animation process. After the animation + * completes, the references to the extra views can then be discarded. + */ + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + + if (mViewsToDraw.size() == 0) { + return; + } + + for (View v: mViewsToDraw) { + canvas.translate(0, v.getTop()); + v.draw(canvas); + canvas.translate(0, -v.getTop()); + } + } + + /** + * This method collapses the view that was clicked and animates all the views + * around it to close around the collapsing view. There are several steps required + * to do this which are outlined below. + * + * 1. Update the layout parameters of the view clicked so as to minimize its height + * to the original collapsed (default) state. + * 2. After invoking a layout, the listview will shift all the cells so as to display + * them most efficiently. Therefore, during the first predraw pass, the listview + * must be offset by some amount such that given the custom bound change upon + * collapse, all the cells that need to be on the screen after the layout + * are rendered by the listview. + * 3. On the second predraw pass, all the items are first returned to their original + * location (before the first layout). + * 4. The collapsing view's bounds are animated to what the final values should be. + * 5. The bounds above the collapsing view are animated downwards while the bounds + * below the collapsing view are animated upwards. + * 6. The extra text is faded out as its contents become visible throughout the + * animation process. + */ + + private void collapseView(final View view) { + mAnimating = true; + + final ExpandableListItem viewObject = (ExpandableListItem)getItemAtPosition + (getPositionForView(view)); + + /* Store the original top and bottom bounds of all the cells.*/ + final int oldTop = view.getTop(); + final int oldBottom = view.getBottom(); + final int firstTop = getChildAt(0).getTop(); + + final HashMap oldCoordinates = new HashMap(); + + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View v = getChildAt(i); + ViewCompat.setHasTransientState(v, true); + oldCoordinates.put(v, new int [] {v.getTop(), v.getBottom()}); + } + + /* Update the layout so the extra content becomes gone.*/ + final View expandingLayout = view.findViewById(mExpandingId); + expandingLayout.setVisibility(View.GONE); + + /* Add an onPreDraw listener. */ + final ViewTreeObserver observer = getViewTreeObserver(); + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + + @Override + public boolean onPreDraw() { + + if (!mShouldRemoveObserver) { + /*Same as for expandingView, the parameters for setSelectionFromTop must + * be determined such that the necessary cells of the ListView are rendered + * and added to it.*/ + mShouldRemoveObserver = true; + + expandingLayout.setVisibility(View.VISIBLE); + + int newTop = view.getTop(); + int newBottom = view.getBottom(); + + int newHeight = newBottom - newTop; + int oldHeight = oldBottom - oldTop; + int deltaHeight = oldHeight - newHeight; + + mTranslate = getTopAndBottomTranslationsC(oldTop, oldBottom, deltaHeight, firstTop, view); + + int currentTop = view.getTop(); + int futureTop = oldTop + mTranslate[0]; + + int firstChildStartTop = getChildAt(0).getTop(); + int firstVisiblePosition = getFirstVisiblePosition(); + int deltaTop = currentTop - futureTop; + + int i; + int childCount = getChildCount(); + for (i = 0; i < childCount; i++) { + View v = getChildAt(i); + int height = v.getBottom() - Math.max(0, v.getTop()); + if (deltaTop - height > 0) { + firstVisiblePosition++; + deltaTop -= height; + } else { + break; + } + } + + if (i > 0) { + firstChildStartTop = 0; + } + + setSelectionFromTop(firstVisiblePosition, firstChildStartTop - deltaTop - getPaddingTop()); + + requestLayout(); + + return false; + } + + mShouldRemoveObserver = false; + observer.removeOnPreDrawListener(this); + + int yTranslateTop = mTranslate[0]; + int yTranslateBottom = mTranslate[1]; + + int index = indexOfChild(view); + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View v = getChildAt(i); + int [] old = oldCoordinates.get(v); + if (old != null) { + /* If the cell was present in the ListView before the collapse and + * after the collapse then the bounds are reset to their old values.*/ + v.setTop(old[0]); + v.setBottom(old[1]); + ViewCompat.setHasTransientState(v, false); + } else { + /* If the cell is present in the ListView after the collapse but + * not before the collapse then the bounds are calculated using + * the bottom and top translation of the collapsing cell.*/ + int delta = i > index ? yTranslateBottom : -yTranslateTop; + v.setTop(v.getTop() + delta); + v.setBottom(v.getBottom() + delta); + } + } + + final View expandingLayout = view.findViewById(mExpandingId); + + /* Animates all the cells present on the screen after the collapse. */ + ArrayList animations = new ArrayList(); + for (int i = 0; i < childCount; i++) { + View v = getChildAt(i); + if (v != view) { + float diff = i > index ? -yTranslateBottom : yTranslateTop; + animations.add(getAnimation(v, diff, diff)); + } + } + + /* Adds animation for collapsing the cell that was clicked. */ + animations.add(getAnimation(view, yTranslateTop, -yTranslateBottom)); + + /* Adds an animation for fading out the extra content. */ + animations.add(ObjectAnimator.ofFloat(expandingLayout, View.ALPHA, 1, 0)); + + /* Disabled the ListView for the duration of the animation.*/ + setEnabled(false); + setClickable(false); + + /* Play all the animations created above together at the same time. */ + AnimatorSet s = new AnimatorSet(); + s.playTogether(animations); + s.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mAnimating = false; + expandingLayout.setVisibility(View.GONE); + view.setLayoutParams(new AbsListView.LayoutParams(AbsListView + .LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.WRAP_CONTENT)); + viewObject.setExpanded(false); + setEnabled(true); + setClickable(true); + /* Note that alpha must be set back to 1 in case this view is reused + * by a cell that was expanded, but not yet collapsed, so its state + * should persist in an expanded state with the extra content visible.*/ + expandingLayout.setAlpha(1); + } + }); + s.start(); + + return true; + } + }); + } + + /** + * This method takes some view and the values by which its top and bottom bounds + * should be changed by. Given these params, an animation which will animate + * these bound changes is created and returned. + */ + private Animator getAnimation(final View view, float translateTop, float translateBottom) { + + int top = view.getTop(); + int bottom = view.getBottom(); + + int endTop = (int)(top + translateTop); + int endBottom = (int)(bottom + translateBottom); + + PropertyValuesHolder translationTop = PropertyValuesHolder.ofInt("top", top, endTop); + PropertyValuesHolder translationBottom = PropertyValuesHolder.ofInt("bottom", bottom, + endBottom); + + return ObjectAnimator.ofPropertyValuesHolder(view, translationTop, translationBottom); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/FileExplorerView.java b/app/src/main/java/com/hippo/ehviewer/widget/FileExplorerView.java new file mode 100644 index 000000000..c595a78a0 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/FileExplorerView.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import java.io.File; +import java.io.FileFilter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Environment; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import com.hippo.ehviewer.R; + +public class FileExplorerView extends ListView + implements AdapterView.OnItemClickListener { + + @SuppressWarnings("unused") + private static final String TAG = "FileExplorerView"; + + private static final File PARENT_DIR = null; + private static final String PARENT_DIR_NAME = ".."; + + private static final DirFileFilter dff = new DirFileFilter(); + private static final FileSort fs = new FileSort(); + + private Context mContext; + + private Filter mFilter; + private static final Filter[] sFilterArray = { Filter.ALL, + Filter.DIR}; + + private File mCurDir; + + private BaseAdapter mAdapter; + private List mFileList; + + public enum Filter { + ALL(0), + DIR(1); + + Filter(int ni) { + nativeInt = ni; + } + + final int nativeInt; + } + + public FileExplorerView(Context context) { + super(context); + init(context); + } + + public FileExplorerView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0); + } + + public FileExplorerView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs, defStyle); + } + + private void init(Context context, AttributeSet attrs, int defStyle) { + mContext = context; + + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.FileExplorerView, defStyle, 0); + + int modeIndex = a.getInt(R.styleable.FileExplorerView_filter, 0); + if (modeIndex >=0 && modeIndex < sFilterArray.length) + mFilter = sFilterArray[modeIndex]; + else + mFilter = Filter.ALL; + + String path = a.getString(R.styleable.FileExplorerView_path); + if (path != null) { + mCurDir = new File(path); + if (!mCurDir.isDirectory()) + mCurDir = null; + } + if (mCurDir == null) + mCurDir = getSafeDir(); + + a.recycle(); + + init(); + } + + private void init(Context context) { + mContext = context; + + mFilter = Filter.ALL; + mCurDir = getSafeDir(); + + init(); + } + + private void init() { + mFileList = new ArrayList(); + getDirList(); + mAdapter = new FileAdapter(); + setAdapter(mAdapter); + setOnItemClickListener(this); + } + + private void getDirList() { + + File[] files = null; + if (mFilter == Filter.DIR) + files = mCurDir.listFiles(dff); + else + files = mCurDir.listFiles(); + + mFileList.clear(); + if (mCurDir.getParent() != null) + mFileList.add(PARENT_DIR); + if (files != null) { + for (File file : files) + mFileList.add(file); + } + // sort + Collections.sort(mFileList, fs); + } + + /** + * Reget dir list and go to top in the list + */ + public void refresh() { + getDirList(); + mAdapter.notifyDataSetChanged(); + // Go to top + setSelection(0); + } + + /** + * Set mode + * @param mode + */ + public void setMode(Filter mode) { + mFilter = mode; + refresh(); + } + + /** + * @param path The path you want to set for current path + * @return True if the path is refer to a directory + */ + public boolean setPath(String path) { + File dir = new File(path); + if (!dir.isDirectory()) + return false; + + mCurDir = dir; + refresh(); + return true; + } + + /** + * True if current dir can write + * @return + */ + public boolean canWrite() { + return mCurDir.canWrite(); + } + + /** + * Get current path + * @return + */ + public String getCurPath() { + return mCurDir.getPath(); + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, + long id) { + File newFile; + File file = mFileList.get(position); + if (file == PARENT_DIR) + newFile = mCurDir.getParentFile(); + else + newFile = file; + mCurDir = newFile; + refresh(); + } + + class FileAdapter extends BaseAdapter { + LayoutInflater mInflater = LayoutInflater.from(mContext); + @Override + public int getCount() { + return mFileList.size(); + } + + @Override + public Object getItem(int position) { + return mFileList.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) + convertView = mInflater.inflate(R.layout.file_explorer_item, parent, false); + + TextView tv = (TextView) convertView; + File file = mFileList.get(position); + String name; + if (file == PARENT_DIR) + name = PARENT_DIR_NAME; + else + name = file.getName(); + tv.setText(name); + + return convertView; + } + } + + private static File getSafeDir() { + File mDir; + mDir = Environment.getExternalStorageDirectory(); + if (mDir == null || !mDir.isDirectory()) + mDir = new File("/"); + return mDir; + } + + static class DirFileFilter implements FileFilter { + @Override + public boolean accept(File pathname) { + return pathname.isDirectory(); + } + } + + static class FileSort implements Comparator { + @Override + public int compare(File lhs, File rhs) { + if (lhs == null) + return Integer.MIN_VALUE; + else if (rhs == null) + return Integer.MAX_VALUE; + else + return lhs.getName().compareToIgnoreCase(rhs.getName()); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/FitWindowView.java b/app/src/main/java/com/hippo/ehviewer/widget/FitWindowView.java new file mode 100644 index 000000000..7333e240f --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/FitWindowView.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import java.util.LinkedList; +import java.util.List; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Rect; +import android.os.Build; +import android.util.AttributeSet; +import android.view.View; + +public class FitWindowView extends View { + + private List mListeners; + + public FitWindowView(Context context) { + super(context); + init(); + } + + public FitWindowView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public FitWindowView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public FitWindowView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + private void init() { + mListeners = new LinkedList(); + setFitsSystemWindows(true); + } + + public void addOnFitSystemWindowsListener(OnFitSystemWindowsListener l) { + mListeners.add(l); + } + + public void removeOnFitSystemWindowsListener(OnFitSystemWindowsListener l) { + mListeners.remove(l); + } + + @Override + @SuppressWarnings("deprecation") + protected boolean fitSystemWindows(Rect insets) { + int l = insets.left; + int t = insets.top; + int r = insets.right; + int b = insets.bottom; + for (OnFitSystemWindowsListener listener : mListeners) + listener.onFitSystemWindows(l, t, r, b); + return super.fitSystemWindows(insets); + } + + public static interface OnFitSystemWindowsListener { + public void onFitSystemWindows(int l, int t, int r, int b); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/FixedAspectImageView.java b/app/src/main/java/com/hippo/ehviewer/widget/FixedAspectImageView.java new file mode 100644 index 000000000..a83d6709d --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/FixedAspectImageView.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.AttributeSet; +import android.widget.ImageView; + +import com.hippo.ehviewer.R; + +/** + * aspect is width / height + * + * @author Hippo + * + */ + +public class FixedAspectImageView extends ImageView { + + private static final String TAG = FixedAspectImageView.class.getSimpleName(); + + private int mMaxWidth = Integer.MAX_VALUE; + private int mMaxHeight = Integer.MAX_VALUE; + private boolean mAdjustViewBounds = false; + + private boolean mAdjustViewBoundsCompat = false; + private float mAspect = -1f; + + public FixedAspectImageView(Context context) { + super(context); + } + + public FixedAspectImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public FixedAspectImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray typedArray = context.obtainStyledAttributes( + attrs, R.styleable.FixedAspectImageView, defStyle, 0); + setAspect(typedArray.getFloat(R.styleable.FixedAspectImageView_aspect, -1f)); + typedArray.recycle(); + } + + @Override + public void setMaxWidth(int maxWidth) { + super.setMaxWidth(maxWidth); + mMaxWidth = maxWidth; + } + + @Override + public void setMaxHeight(int maxHeight) { + super.setMaxHeight(maxHeight); + mMaxHeight = maxHeight; + } + + @Override + public void setAdjustViewBounds(boolean adjustViewBounds) { + super.setAdjustViewBounds(adjustViewBounds); + mAdjustViewBounds = adjustViewBounds; + } + + public void init() { + mAdjustViewBoundsCompat = getContext().getApplicationInfo().targetSdkVersion <= + Build.VERSION_CODES.JELLY_BEAN_MR1; + } + + /** + * Enable aspect will set AdjustViewBounds true. + * Any negative float to disable it, + * disable Aspect will not disable AdjustViewBounds. + * + * @param ratio + */ + public void setAspect(float ratio) { + if (ratio > 0) { + mAspect = ratio; + setAdjustViewBounds(true); + } else { + mAspect = -1f; + } + requestLayout(); + } + + private int resolveAdjustedSize(int desiredSize, int maxSize, + int measureSpec) { + int result = desiredSize; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + switch (specMode) { + case MeasureSpec.UNSPECIFIED: + /* Parent says we can be as big as we want. Just don't be larger + * than max size imposed on ourselves. + */ + result = Math.min(desiredSize, maxSize); + break; + case MeasureSpec.AT_MOST: + // Parent says we can be as big as we want, up to specSize. + // Don't be larger than specSize, and don't be larger than + // the max size imposed on ourselves. + result = Math.min(Math.min(desiredSize, specSize), maxSize); + break; + case MeasureSpec.EXACTLY: + // No choice. Do what we are told. + result = specSize; + break; + } + return result; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int w = 0; + int h = 0; + + // Desired aspect ratio of the view's contents (not including padding) + float desiredAspect = 0.0f; + + // We are allowed to change the view's width + boolean resizeWidth = false; + + // We are allowed to change the view's height + boolean resizeHeight = false; + + final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); + final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); + + Drawable drawable = getDrawable(); + if (drawable == null) { + // If no drawable, its intrinsic size is 0. + w = h = 0; + + if (mAdjustViewBounds && mAspect > 0.0f) { + resizeWidth = widthSpecMode != MeasureSpec.EXACTLY; + resizeHeight = heightSpecMode != MeasureSpec.EXACTLY; + desiredAspect = mAspect; + } + } else { + w = drawable.getIntrinsicWidth(); + h = drawable.getIntrinsicHeight(); + if (w <= 0) w = 1; + if (h <= 0) h = 1; + + // We are supposed to adjust view bounds to match the aspect + // ratio of our drawable. See if that is possible. + if (mAdjustViewBounds) { + resizeWidth = widthSpecMode != MeasureSpec.EXACTLY; + resizeHeight = heightSpecMode != MeasureSpec.EXACTLY; + + desiredAspect = mAspect <= 0.0f ? ((float) w / (float) h) : mAspect; + } + } + + int pleft = getPaddingLeft(); + int pright = getPaddingRight(); + int ptop = getPaddingTop(); + int pbottom = getPaddingBottom(); + + int widthSize; + int heightSize; + + if (resizeWidth || resizeHeight) { + // If we get here, it means we want to resize to match the + // drawables aspect ratio, and we have the freedom to change at + // least one dimension. + + // Get the max possible width given our constraints + widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec); + + // Get the max possible height given our constraints + heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec); + + if (desiredAspect != 0.0f) { + // See what our actual aspect ratio is + float actualAspect = (float)(widthSize - pleft - pright) / + (heightSize - ptop - pbottom); + + if (Math.abs(actualAspect - desiredAspect) > 0.0000001) { + boolean done = false; + + // Try adjusting width to be proportional to height + if (resizeWidth) { + int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) + + pleft + pright; + + // Allow the width to outgrow its original estimate if height is fixed. + if (!resizeHeight && !mAdjustViewBoundsCompat) { + widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec); + } + + if (newWidth <= widthSize) { + widthSize = newWidth; + done = true; + } + } + + // Try adjusting height to be proportional to width + if (!done && resizeHeight) { + int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) + + ptop + pbottom; + + // Allow the height to outgrow its original estimate if width is fixed. + if (!resizeWidth && !mAdjustViewBoundsCompat) { + heightSize = resolveAdjustedSize(newHeight, mMaxHeight, + heightMeasureSpec); + } + + if (newHeight <= heightSize) { + heightSize = newHeight; + } + } + } + } + } else { + // We are either don't want to preserve the drawables aspect ratio, + // or we are not allowed to change view dimensions. Just measure in + // the normal way. + w += pleft + pright; + h += ptop + pbottom; + + w = Math.max(w, getSuggestedMinimumWidth()); + h = Math.max(h, getSuggestedMinimumHeight()); + + widthSize = resolveSizeAndState(w, widthMeasureSpec, 0); + heightSize = resolveSizeAndState(h, heightMeasureSpec, 0); + } + + setMeasuredDimension(widthSize, heightSize); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/FloatingActionButton.java b/app/src/main/java/com/hippo/ehviewer/widget/FloatingActionButton.java new file mode 100644 index 000000000..a427628a4 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/FloatingActionButton.java @@ -0,0 +1,151 @@ +package com.hippo.ehviewer.widget; + +import android.annotation.SuppressLint; +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.Rect; +import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +import com.hippo.ehviewer.R; + +public class FloatingActionButton extends View { + + @SuppressWarnings("unused") + private static final String TAG = FloatingActionButton.class.getSimpleName(); + + private final Paint mButtonPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint mDrawablePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private Bitmap mBitmap; + private int mColor; + private final int mDrawableSide; + private final float mShadowRadius; + private final float mShadowDx; + private final float mShadowDy; + + private int mCircleX; + private int mCircleY; + private int mCircleRadius; + private final Rect mSrc = new Rect(); + private final RectF mDst = new RectF(); + + + public FloatingActionButton(Context context) { + this(context, null); + } + + public FloatingActionButton(Context context, AttributeSet attributeSet) { + this(context, attributeSet, 0); + } + + public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FloatingActionButton); + mDrawableSide = a.getDimensionPixelSize(R.styleable.FloatingActionButton_fabDrawableSide, 72); + mColor = a.getColor(R.styleable.FloatingActionButton_fabColor, Color.WHITE); + mButtonPaint.setStyle(Paint.Style.FILL); + mButtonPaint.setColor(mColor); + mShadowRadius = a.getDimension(R.styleable.FloatingActionButton_fabShadowRadius, 10.0f); + mShadowDx = a.getDimension(R.styleable.FloatingActionButton_fabShadowDx, 0.0f); + mShadowDy = a.getDimension(R.styleable.FloatingActionButton_fabShadowDy, 3.0f); + int shadowColor = a.getColor(R.styleable.FloatingActionButton_fabShadowColor, 0x8a000000); + mButtonPaint.setShadowLayer(mShadowRadius, mShadowDx, mShadowDy, shadowColor); + Drawable drawable = a.getDrawable(R.styleable.FloatingActionButton_fabDrawable); + if (drawable != null && drawable instanceof BitmapDrawable) + mBitmap = ((BitmapDrawable) drawable).getBitmap(); + a.recycle(); + + setWillNotDraw(false); + setLayerType(View.LAYER_TYPE_SOFTWARE, null); + } + + public void setColor(int color) { + mColor = color; + mButtonPaint.setColor(mColor); + invalidate(); + } + + public void setDrawable(Drawable drawable) { + mBitmap = ((BitmapDrawable) drawable).getBitmap(); + updateValue(); + invalidate(); + } + + public static int getDefaultSize(int size, int measureSpec) { + int result = size; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + switch (specMode) { + case MeasureSpec.UNSPECIFIED: + case MeasureSpec.AT_MOST: + result = size; + break; + case MeasureSpec.EXACTLY: + result = specSize; + break; + } + return result; + } + + private void updateValue() { + float half = Math.min(getWidth(), getHeight()) / 2; + mCircleX = (int) (half - mShadowDx); + mCircleY = (int) (half - mShadowDy); + mCircleRadius = (int) (half - mShadowRadius); + float halfDrawableSide = mDrawableSide / 2.0f; + if (mBitmap != null) { + mSrc.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight()); + mDst.set(mCircleX - halfDrawableSide, mCircleY - halfDrawableSide, mCircleX + halfDrawableSide, mCircleY + halfDrawableSide); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), + getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + updateValue(); + } + + @Override + protected void onDraw(Canvas canvas) { + canvas.drawCircle(mCircleX, mCircleY, mCircleRadius, mButtonPaint); + if (null != mBitmap) + canvas.drawBitmap(mBitmap, mSrc, mDst, mDrawablePaint); + } + + @Override + @SuppressLint("ClickableViewAccessibility") + public boolean onTouchEvent(MotionEvent event) { + int color; + if (event.getAction() == MotionEvent.ACTION_UP) { + color = mColor; + } else { + color = darkenColor(mColor); + } + mButtonPaint.setColor(color); + invalidate(); + return super.onTouchEvent(event); + } + + public static int darkenColor(int color) { + float[] hsv = new float[3]; + Color.colorToHSV(color, hsv); + hsv[2] *= 0.8f; + return Color.HSVToColor(hsv); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hippo/ehviewer/widget/GalleryListView.java b/app/src/main/java/com/hippo/ehviewer/widget/GalleryListView.java new file mode 100644 index 000000000..c5fd47df6 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/GalleryListView.java @@ -0,0 +1,795 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import java.util.ArrayList; +import java.util.List; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Build; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.StaggeredGridLayoutManager; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.TextView; + +import com.hippo.ehviewer.AppHandler; +import com.hippo.ehviewer.ImageLoader; +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.cache.ImageCache; +import com.hippo.ehviewer.cardview.CardViewSalon; +import com.hippo.ehviewer.data.GalleryInfo; +import com.hippo.ehviewer.ehclient.ListParser; +import com.hippo.ehviewer.util.Config; +import com.hippo.ehviewer.util.Ui; +import com.hippo.ehviewer.util.ViewUtils; +import com.hippo.ehviewer.widget.recyclerview.EasyRecyclerView; +import com.hippo.ehviewer.widget.recyclerview.MarginItemDecoration; +import com.hippo.ehviewer.widget.recyclerview.EasyRecyclerView.FooterAdapter; + +public class GalleryListView extends FrameLayout implements RefreshLayout.OnFooterRefreshListener, + RefreshLayout.OnRefreshListener { + + private static final String TAG = GalleryListView.class.getSimpleName(); + + public static final int LIST_MODE_DETAIL = 0; + public static final int LIST_MODE_THUMB = 1; + + private static final int MODE_REFRESH = 0; + private static final int MODE_NEXT_PAGE = 1; + private static final int MODE_PRE_PAGE = 2; + private static final int MODE_SOMEWHERE = 3; + + private Context mContext; + private GalleryListViewHelper mHelper; + + private List mGiList; + + private RefreshLayout mRefreshLayout; + private EasyRecyclerView mEasyRecyclerView; + private RefreshTextView mRefreshTextView; + + private int mListMode = LIST_MODE_DETAIL; + + private GalleryAdapter mAdapter; + private StaggeredGridLayoutManager mLayoutManager; + private MarginItemDecoration mItemDecoration; + private final int[] mFirstPositionTemp = new int[50]; // TODO + + private long mTaskStamp; + + /** + * First index of current page + */ + private int mFirstIndex; + /** + * Last index of current page + */ + private int mLastIndex; + private int mCurPage; + private int mFirstPage = 0; + private int mLastPage = 0; + /** + * The number of page in sum + */ + private int mPageNum; + private int mItemPerPage; + private int mGetMode; + + private OnGetListListener mListener; + + /** + * If true, list will make showed item not changed after get + */ + private boolean mIsKeepPosition; + + private int mTargetPage; + private String mTargetUrl; + private boolean isFootRefresh = false; + + private LayoutInflater mInflater; + + private int mListDetailThumbWidth; + private int mListDetailThumbHeight; + + public GalleryListView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0, 0); + } + + public GalleryListView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs, defStyleAttr, 0); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public GalleryListView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(context, attrs, defStyleAttr, defStyleRes); + } + + private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + mContext = context; + mGiList = new ArrayList(); + mListener = new OnGetGalleryListListener(); + + LayoutInflater.from(mContext).inflate(R.layout.gallery_list_view, this); + + mRefreshLayout = (RefreshLayout) findViewById(R.id.refresh_layout); + mEasyRecyclerView = mRefreshLayout.getEasyRecyclerView(); + mRefreshTextView = (RefreshTextView) findViewById(R.id.refresh_text); + + mAdapter = new GalleryAdapter(mContext, mGiList); + mLayoutManager = new StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL); + mItemDecoration = new MarginItemDecoration(); + mEasyRecyclerView.setOnScrollListener(new OnScrollListener()); + mEasyRecyclerView.setClipToPadding(false); + mEasyRecyclerView.setAdapter(mAdapter); + mEasyRecyclerView.setLayoutManager(mLayoutManager); + mEasyRecyclerView.addItemDecoration(mItemDecoration); + mEasyRecyclerView.setItemAnimator(new DefaultItemAnimator()); + mEasyRecyclerView.setHasFixedSize(true); + setItemMargin(Ui.dp2pix(8)); // TODO for tablet margin should be greater + + mRefreshTextView.setDefaultRefresh(R.string.click_retry, new RefreshTextView.OnRefreshListener() { + @Override + public void onRefresh() { + retry(); + } + }); + + mRefreshLayout.setColorSchemeResources( + R.color.refresh_color_1, + R.color.refresh_color_2, + R.color.refresh_color_3, + R.color.refresh_color_4); + mRefreshLayout.setOnHeaderRefreshListener(this); + mRefreshLayout.setOnFooterRefreshListener(this); + mRefreshLayout.setFooterString( + mContext.getString(R.string.footer_loading), + mContext.getString(R.string.footer_loaded), + mContext.getString(R.string.footer_fail)); + mRefreshLayout.addFooterView(); + + // Caculate gallery detail height + mInflater = LayoutInflater.from(mContext); + View v = mInflater.inflate(R.layout.test_gallery_list_detail_thumb, null); + ((TextView) v.findViewById(R.id.title)).setText("haha\nhaha"); + ((TextView) v.findViewById(R.id.uploader)).setText("haha"); + ((RatingView) v.findViewById(R.id.rate)).setRating(2.3f); + ((TextView) v.findViewById(R.id.category)).setText("haha"); + ViewUtils.measureView(v, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + mListDetailThumbHeight = Math.max(v.getMeasuredHeight(), Ui.dp2pix(120)); + mListDetailThumbWidth = mListDetailThumbHeight * 2 / 3; + } + + public int getListMode() { + return mListMode; + } + + public void setListMode(int listMode) { + if (mListMode != listMode) { + mListMode = listMode; + // Update span + updateSpanCount(); + mAdapter.notifyDataSetChanged(); + } + } + + private boolean isLandscape() { + return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; + } + + private static final String[] KEY_SPAN_COUNT_ARRAY = { + "list_detail_columns_portrait", "list_detail_columns_landscape", + "list_thumb_columns_portrait", "list_thumb_columns_landscape" + }; + + private static final int[] DEFAULT_SPAN_COUNT_ARRAY = { + 1, 2, 3, 5 + }; + + public void updateSpanCount() { + int index = mListMode * 2 + (isLandscape() ? 1 : 0); + try { + mLayoutManager.setSpanCount(Config.getInt(KEY_SPAN_COUNT_ARRAY[index], + DEFAULT_SPAN_COUNT_ARRAY[index])); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + public void setGalleryListViewHelper(GalleryListViewHelper h) { + mHelper = h; + } + + public void setPaddingTopAndBottom(int top, int bottom) { + setPadding(getPaddingLeft(), top, getPaddingRight(), getPaddingBottom()); + mEasyRecyclerView.setPadding(mEasyRecyclerView.getPaddingLeft(), mEasyRecyclerView.getPaddingTop(), + mEasyRecyclerView.getPaddingRight(), mEasyRecyclerView.getPaddingBottom() + bottom); + } + + private void setItemMargin(int margin) { + int halfMargin = margin / 2; + mItemDecoration.setMargin(margin); + mEasyRecyclerView.setPadding(halfMargin, halfMargin, + halfMargin, halfMargin); + } + + public void setEnabledHeader(boolean enabled) { + mRefreshLayout.setEnabledHeader(enabled); + } + + public void setEnabledFooter(boolean enabled) { + mRefreshLayout.setEnabledFooter(enabled); + } + + public void setHeaderRefreshing(boolean refreshing) { + mRefreshLayout.setRefreshing(refreshing); + } + + public void setOnItemClickListener(EasyRecyclerView.OnItemClickListener l) { + mEasyRecyclerView.setOnItemClickListener(l); + } + + public void setOnItemLongClickListener(EasyRecyclerView.OnItemLongClickListener l) { + mEasyRecyclerView.setOnItemLongClickListener(l); + } + + public void setChoiceMode(int choiceMode) { + mEasyRecyclerView.setChoiceMode(choiceMode); + } + + public void setMultiChoiceModeListener(EasyRecyclerView.MultiChoiceModeListener listener) { + mEasyRecyclerView.setMultiChoiceModeListener(listener); + } + + public void notifyDataSetChanged() { + mAdapter.notifyDataSetChanged(); + } + + + public void setNoneText(CharSequence text) { + mRefreshTextView.setVisibility(View.VISIBLE); + mRefreshTextView.setEmesg(text, false); + } + + public void onlyShowList() { + mRefreshLayout.setVisibility(View.VISIBLE); + mRefreshTextView.setRefreshing(false); + mRefreshTextView.setVisibility(View.GONE); + } + + public void onlyShowNone() { + mRefreshLayout.setVisibility(View.GONE); + mRefreshTextView.setVisibility(View.VISIBLE); + mRefreshTextView.setEmesg(R.string.none, false); + } + + private void getGallerys() { + setGallerysLayout(); + + mTaskStamp = System.currentTimeMillis(); + mHelper.doGetGallerys(mTargetUrl, mTaskStamp, mListener); + } + + @Override + public void onRefresh() { + // It is invokened by user pull, so no need to + if (mFirstPage > 0) { + getPrePage(true); + } else { + refresh(); + } + } + + @Override + public boolean onFooterRefresh() { + if (!isRefreshing() && mLastPage < mPageNum - 1) { + isFootRefresh = true; + getNextPage(true); + isFootRefresh = false; + return true; + } else { + return false; + } + } + + public void firstTimeRefresh() { + // set mPullViewGroup gone, make wait view show + mRefreshLayout.setVisibility(View.GONE); + mRefreshTextView.setVisibility(View.VISIBLE); + mRefreshTextView.setRefreshing(true); + + refresh(); + } + + private void setGallerysLayout() { + if (mRefreshLayout.getVisibility() == View.VISIBLE) { + if (!isFootRefresh) + mRefreshLayout.setRefreshing(true); + mRefreshTextView.setVisibility(View.GONE); + } else { + mRefreshLayout.setVisibility(View.GONE); + mRefreshTextView.setVisibility(View.VISIBLE); + mRefreshTextView.setRefreshing(true); + } + } + + protected boolean retry() { + // Need to update url, because mode may be changed + mTargetUrl = mHelper.getTargetUrl(mTargetPage); + getGallerys(); + return true; + } + + /** + * Go to page 0.
    + * You should know you can refresh or not. + */ + public void refresh() { + mGetMode = MODE_REFRESH; + mTargetPage = 0; + mTargetUrl = mHelper.getTargetUrl(mTargetPage); + mIsKeepPosition = false; + getGallerys(); + } + + /** + * Get previous page.
    + * You should know you can get previous page or not + * + * @param isKeepPosition + */ + protected void getPrePage(boolean isKeepPosition) { + mGetMode = MODE_PRE_PAGE; + mTargetPage = mFirstPage - 1; + mTargetUrl = mHelper.getTargetUrl(mTargetPage); + mIsKeepPosition = isKeepPosition; + getGallerys(); + } + + /** + * Get next page.
    + * You should know you can get next page or not + * + * @param isKeepPosition + */ + protected void getNextPage(boolean isKeepPosition) { + mGetMode = MODE_NEXT_PAGE; + mTargetPage = mLastPage + 1; + mTargetUrl = mHelper.getTargetUrl(mTargetPage); + mIsKeepPosition = isKeepPosition; + getGallerys(); + } + + /** + * Get some page.
    + * You should know you can get that page or not + * + * @param page + */ + protected void getSomewhere(int page) { + mGetMode = MODE_SOMEWHERE; + mTargetPage = page; + mTargetUrl = mHelper.getTargetUrl(mTargetPage); + mIsKeepPosition = false; + getGallerys(); + } + + public void jumpTo(int page) { + if (page >= mFirstPage && page <= mLastPage) { + int position = (page - mFirstPage) * mItemPerPage; + mLayoutManager.scrollToPosition(position); + } else if (page == mFirstPage - 1) { + getPrePage(false); + } else if (page == mLastPage + 1) { + getNextPage(false); + } else { + getSomewhere(page); + } + } + + /** + * @return True if actionbar or footer is refreshing + */ + public boolean isRefreshing() { + return mRefreshLayout.isRefreshing(); + } + + public boolean isGetGalleryOk() { + return mRefreshLayout.getVisibility() == View.VISIBLE; + } + + public int getPageNum() { + return mPageNum; + } + + public int getCurPage() { + return mCurPage; + } + + public List getGalleryList() { + return mGiList; + } + + public GalleryInfo getGalleryInfo(int position) { + return mGiList.get(position); + } + + public static interface OnGetListListener { + public void onSuccess(long taskStamp, List gis, int maxPage); + public void onFailure(long taskStamp, String eMsg); + } + + private class OnGetGalleryListListener implements OnGetListListener, Runnable { + + @Override + public void run() { + int preIndex = mLayoutManager.findFirstVisibleItemPositions(mFirstPositionTemp)[0] - 1; + if (preIndex >= 0) + mEasyRecyclerView.smoothScrollToPosition(preIndex); + } + + public void smoothScrollToPrePosition() { + AppHandler.getInstance().post(this); + } + + @Override + public void onSuccess(long taskStamp, List gis, int pageNum) { + if (mTaskStamp != taskStamp) + return; + + if (pageNum == ListParser.TARGET_PAGE_IS_LAST) + mPageNum = mTargetPage + 1; + else + mPageNum = pageNum; + + int itemPerPage = gis == null ? 0 : gis.size(); + if (mItemPerPage < itemPerPage) + mItemPerPage = itemPerPage; + + if (mPageNum == 0) { // Get none + onlyShowNone(); + + mFirstPage = 0; + mLastPage = 0; + mGiList.clear(); + mAdapter.notifyDataSetChanged(); + } else { + onlyShowList(); + + int start; + switch (mGetMode) { + case MODE_REFRESH: + mFirstPage = 0; + mLastPage = 0; + mGiList.clear(); + mGiList.addAll(gis); + mAdapter.notifyDataSetChanged(); + // For current page + mFirstIndex = 0; + mLastIndex = gis.size() - 1; + mCurPage = 0; + + mLayoutManager.scrollToPosition(0); + break; + + case MODE_PRE_PAGE: + mFirstPage--; + mGiList.addAll(0, gis); + mAdapter.notifyItemRangeInserted(0, gis.size()); + + if (mIsKeepPosition) { + mFirstIndex += gis.size(); + mLastIndex += gis.size(); + smoothScrollToPrePosition(); + } else { + mFirstIndex = 0; + mLastIndex = gis.size()-1; + mCurPage = mTargetPage; + mLayoutManager.scrollToPosition(0); + } + break; + + case MODE_NEXT_PAGE: + mLastPage++; + start = mGiList.size(); + mGiList.addAll(gis); + mAdapter.notifyItemRangeInserted(start, gis.size()); + + if (!mIsKeepPosition) { + mFirstIndex = mGiList.size() - gis.size(); + mLastIndex = mGiList.size() - 1; + mCurPage = mTargetPage; + mLayoutManager.scrollToPosition(0); + } + break; + + case MODE_SOMEWHERE: + mFirstPage = mTargetPage; + mLastPage = mTargetPage; + mGiList.clear(); + mGiList.addAll(gis); + mAdapter.notifyDataSetChanged(); + // For current page + mFirstIndex = 0; + mLastIndex = gis.size() - 1; + mCurPage = mTargetPage; + + mLayoutManager.scrollToPosition(0); + } + } + + mRefreshLayout.setAnyRefreshComplete(true); + } + + @Override + public void onFailure(long taskStamp, String eMsg) { + if (mTaskStamp != taskStamp) + return; + + switch (mGetMode) { + case MODE_REFRESH: + case MODE_SOMEWHERE: + mRefreshLayout.setVisibility(View.GONE); + mRefreshTextView.setVisibility(View.VISIBLE); + if (eMsg.equals(mContext.getString(R.string.em_index_error))) { + mRefreshTextView.setEmesg(eMsg, mContext.getString(R.string.click_first_page), + new RefreshTextView.OnRefreshListener() { + @Override + public void onRefresh() { + mRefreshTextView.setRefreshing(true); + refresh(); + } + }); + } else { + mRefreshTextView.setEmesg(eMsg, true); + } + break; + default: + mRefreshTextView.setRefreshing(false); + mRefreshTextView.setVisibility(View.GONE); + MaterialToast.showToast(eMsg); + } + mRefreshLayout.setAnyRefreshComplete(false); + } + } + + public static interface GalleryListViewHelper { + /** + * Get url string for very page + * + * @param targetPage + * @return + */ + public String getTargetUrl(int targetPage); + + /** + * Do get gallarys here, you shuold invoke the onSuccess or onFailure of + * listener when over + * + * @param url + * @param taskStamp + * @param listener + */ + public void doGetGallerys(String url, long taskStamp, OnGetListListener listener); + } + + public class OnScrollListener extends RecyclerView.OnScrollListener { + + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + switch (newState) { + case RecyclerView.SCROLL_STATE_DRAGGING: + ImageCache.getInstance(mContext).setPauseDiskCache(true); + break; + case RecyclerView.SCROLL_STATE_IDLE: + ImageCache.getInstance(mContext).setPauseDiskCache(false); + break; + } + } + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + mRefreshLayout.onScrolled(recyclerView, dx, dy); + + int itemCount = mLayoutManager.getChildCount(); + int firstVisibleItem = mLayoutManager.findFirstVisibleItemPositions(mFirstPositionTemp)[0]; + + // itemCount might include some view can't be seen + // Just check top line child and bottom line child + int visibleItemCount = itemCount; + int height = mEasyRecyclerView.getHeight(); + int span = mLayoutManager.getSpanCount(); + // Top line + int start = 0; + int end = Math.min(span, itemCount); + for (int i = start; i < end; i++) { + View view = mLayoutManager.getChildAt(i); + if (view.getBottom() <= 0 || view.getTop() > height) + visibleItemCount--; + } + // Bottom line + start = Math.max(itemCount - 1 - span, 0); + end = itemCount; + for (int i = start; i < end; i++) { + View view = mLayoutManager.getChildAt(i); + if (view.getBottom() <= 0 || view.getTop() > height) + visibleItemCount--; + } + + if (mItemPerPage == 0) + return; + if (mLastIndex == 0) + mLastIndex = mItemPerPage - 1; + int pageChanged = (firstVisibleItem - mFirstIndex) / mItemPerPage; + if (pageChanged == 0) + pageChanged = (firstVisibleItem + visibleItemCount - mLastIndex - 1) / mItemPerPage; + + if (pageChanged != 0) { + mCurPage = mCurPage + pageChanged; + mFirstIndex += pageChanged * mItemPerPage; + mLastIndex += pageChanged * mItemPerPage; + } + } + } + + private class GalleryViewHolder extends RecyclerView.ViewHolder { + + public int viewType; + public LoadImageView thumb; + public TextView title; + public TextView uploader; + public TextView category; + public RatingView rate; + public TextView posted; + public TextView simpleLanguage; + + public GalleryViewHolder(View itemView, int viewType) { + super(itemView); + + this.viewType = viewType; + switch (viewType) { + case LIST_MODE_DETAIL: + thumb = (LoadImageView) itemView.findViewById(R.id.thumb); + title = (TextView) itemView.findViewById(R.id.title); + category = (TextView) itemView.findViewById(R.id.category); + uploader = (TextView) itemView.findViewById(R.id.uploader); + rate = (RatingView) itemView.findViewById(R.id.rate); + posted = (TextView) itemView.findViewById(R.id.posted); + simpleLanguage = (TextView) itemView.findViewById(R.id.simple_language); + + ViewGroup.LayoutParams lp = thumb.getLayoutParams(); + lp.width = mListDetailThumbWidth; + lp.height = mListDetailThumbHeight; + break; + case LIST_MODE_THUMB: + thumb = (LoadImageView) itemView.findViewById(R.id.thumb); + category = (TextView) itemView.findViewById(R.id.category); + simpleLanguage = (TextView) itemView.findViewById(R.id.simple_language); + } + } + } + + public class GalleryAdapter extends EasyRecyclerView.FooterAdapter { + + private final Context mContext; + private final List mGiList; + private final ImageLoader mImageLoader; + + + public GalleryAdapter(Context context, List gilist) { + mContext = context; + mGiList = gilist; + mImageLoader = ImageLoader.getInstance(mContext); + } + + @Override + public GalleryViewHolder onCreateAndBindFooterViewHolder( + ViewGroup parent, View footerView) { + return new GalleryViewHolder(footerView, FooterAdapter.TYPE_FOOTER); + } + + @Override + public GalleryViewHolder onCreateViewHolderActual(ViewGroup parent, + int viewType) { + int resId; + if (viewType == LIST_MODE_DETAIL) + resId = R.layout.gallery_list_detail_item; + else + resId = R.layout.gallery_list_thumb_item; + View view = mInflater.inflate(resId, parent, false); + CardViewSalon.reformWithShadow(view, new int[][]{ + new int[]{android.R.attr.state_pressed}, + new int[]{android.R.attr.state_activated}, + new int[]{}}, + new int[]{0xff84cae4, 0xff33b5e5, 0xFFFAFAFA}, null, false); + return new GalleryViewHolder(view, viewType); + } + + @Override + public void onBindViewHolderActual(GalleryViewHolder holder, + int position) { + GalleryInfo gi = mGiList.get(position); + final LoadImageView thumb = holder.thumb; + if (!String.valueOf(gi.gid).equals(thumb.getKey())) { + // Set new thumb + thumb.setImageDrawable(null); + thumb.setLoadInfo(gi.thumb, String.valueOf(gi.gid)); + mImageLoader.add(gi.thumb, String.valueOf(gi.gid), + new LoadImageView.SimpleImageGetListener(thumb).setFixScaleType(true)); + } + // Set simple language + TextView simpleLanguage = holder.simpleLanguage; + if (gi.simpleLanguage == null) { + simpleLanguage.setVisibility(View.GONE); + } else { + simpleLanguage.setVisibility(View.VISIBLE); + simpleLanguage.setText(gi.simpleLanguage); + } + + // For detail mode + if (holder.viewType == LIST_MODE_DETAIL) { + // Set category + TextView category = holder.category; + String newText = Ui.getCategoryText(gi.category); + if (!newText.equals(category.getText())) { + category.setText(newText); + category.setBackgroundColor(Ui.getCategoryColor(gi.category)); + } + // Set manga title + TextView title = holder.title; + title.setText(gi.title); + // Set uploder + TextView uploader = holder.uploader; + uploader.setText(gi.uploader); + // Set star + RatingView rate = holder.rate; + rate.setRating(gi.rating); + // set posted + TextView posted = holder.posted; + posted.setText(gi.posted); + } else { + // Set category + TextView category = holder.category; + String newText = Ui.getCategoryText(gi.category); + if (!newText.equals(category.getText())) { + category.setText(newText); + category.setBackgroundColor(Ui.getCategoryColor(gi.category) + & 0x00ffffff | 0xca000000); + } + } + } + + @Override + public int getItemViewTypeActual(int position) { + return mListMode; + } + + @Override + public int getItemCountActual() { + return mGiList.size(); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/HotspotTouchHelper.java b/app/src/main/java/com/hippo/ehviewer/widget/HotspotTouchHelper.java new file mode 100644 index 000000000..2051de64f --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/HotspotTouchHelper.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.annotation.TargetApi; +import android.os.Build; +import android.view.MotionEvent; +import android.view.TouchDelegate; +import android.view.View; +import android.view.ViewConfiguration; + +public class HotspotTouchHelper implements View.OnTouchListener { + + private final Hotspotable mOwner; + + private boolean mPrePressed = false; + private boolean mHasPerformedLongPress = false; + + private CheckForLongPress mPendingCheckForLongPress = null; + private CheckForTap mPendingCheckForTap = null; + private PerformClick mPerformClick = null; + private UnsetPressedState mUnsetPressedState = null; + + /** + * Cache the touch slop from the context that created the view. + */ + private int mTouchSlop = -1; + + public HotspotTouchHelper(Hotspotable owner){ + mOwner = owner; + } + + @Override + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public boolean onTouch(View v, MotionEvent event) { + final float x = event.getX(); + final float y = event.getY(); + + if (event.getAction() == MotionEvent.ACTION_DOWN || + event.getAction() == MotionEvent.ACTION_MOVE) + mOwner.setHotspot(x, y); + + if (!v.isEnabled()) { + if (event.getAction() == MotionEvent.ACTION_UP && v.isPressed()) { + v.setPressed(false); + } + // A disabled view that is clickable still consumes the touch + // events, it just doesn't respond to them. + return (v.isClickable() || v.isLongClickable()); + } + + TouchDelegate touchDelegate = v.getTouchDelegate(); + if (touchDelegate != null) { + if (touchDelegate.onTouchEvent(event)) { + return true; + } + } + + if (v.isClickable() || v.isLongClickable()) { + switch (event.getAction()) { + case MotionEvent.ACTION_UP: + if (v.isPressed() || mPrePressed) { + // take focus if we don't have it already and we should in + // touch mode. + boolean focusTaken = false; + if (v.isFocusable() && v.isFocusableInTouchMode() && !v.isFocused()) { + focusTaken = v.requestFocus(); + } + + if (mPrePressed) { + // The button is being released before we actually + // showed it as pressed. Make it show the pressed + // state now (before scheduling the click) to ensure + // the user sees it. + setPressed(v, true, x, y); + } + + if (!mHasPerformedLongPress) { + // This is a tap, so remove the longpress check + removeLongPressCallback(v); + + // Only perform take click actions if we were in the pressed state + if (!focusTaken) { + // Use a Runnable and post this rather than calling + // performClick directly. This lets other visual state + // of the view update before click actions start. + if (mPerformClick == null) { + mPerformClick = new PerformClick(v); + } + if (!v.post(mPerformClick)) { + v.performClick(); + } + } + } + + if (mUnsetPressedState == null) { + mUnsetPressedState = new UnsetPressedState(v); + } + + if (mPrePressed) { + v.postDelayed(mUnsetPressedState, + ViewConfiguration.getPressedStateDuration()); + } else if (!v.post(mUnsetPressedState)) { + // If the post failed, unpress right now + mUnsetPressedState.run(); + } + + removeTapCallback(v); + } + break; + + case MotionEvent.ACTION_DOWN: + mHasPerformedLongPress = false; + + if (performButtonActionOnTouchDown(v, event)) { + break; + } + + mPrePressed = true; + if (mPendingCheckForTap == null) { + mPendingCheckForTap = new CheckForTap(v); + } + mPendingCheckForTap.x = event.getX(); + mPendingCheckForTap.y = event.getY(); + v.postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); + break; + + case MotionEvent.ACTION_CANCEL: + v.setPressed(false); + removeTapCallback(v); + removeLongPressCallback(v); + break; + + case MotionEvent.ACTION_MOVE: + mOwner.setHotspot(x, y); + + if (mTouchSlop == -1) + mTouchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop(); + + // Be lenient about moving outside of buttons + if (!pointInView(v, x, y, mTouchSlop)) { + // Outside button + removeTapCallback(v); + if (v.isPressed()) { + // Remove any future long press/tap checks + removeLongPressCallback(v); + + v.setPressed(false); + } + } + break; + } + + return true; + } + + return false; + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private void setPressed(View v, boolean pressed, float x, float y){ + v.setPressed(pressed); + mOwner.setHotspot(x, y); + } + + /** + * Utility method to determine whether the given point, in local coordinates, + * is inside the view, where the area of the view is expanded by the slop factor. + * This method is called while processing touch-move events to determine if the event + * is still within the view. + */ + public boolean pointInView(View v, float localX, float localY, float slop) { + return localX >= -slop && localY >= -slop && localX < ((v.getRight() - v.getLeft()) + slop) && + localY < ((v.getBottom() - v.getTop()) + slop); + } + + /** + * Performs button-related actions during a touch down event. + * + * @param event The event. + * @return True if the down was consumed. + */ + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + protected boolean performButtonActionOnTouchDown(View v, MotionEvent event) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + if ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) { + if (v.showContextMenu()) { + return true; + } + } + } + return false; + } + + /** + * Remove the longpress detection timer. + */ + private void removeLongPressCallback(View v) { + if (mPendingCheckForLongPress != null) { + v.removeCallbacks(mPendingCheckForLongPress); + } + } + + /** + * Remove the tap detection timer. + */ + private void removeTapCallback(View v) { + if (mPendingCheckForTap != null) { + mPrePressed = false; + v.removeCallbacks(mPendingCheckForTap); + } + } + + private void checkForLongClick(View v, int delayOffset) { + if (v.isLongClickable()) { + mHasPerformedLongPress = false; + + if (mPendingCheckForLongPress == null) { + mPendingCheckForLongPress = new CheckForLongPress(v); + } + v.postDelayed(mPendingCheckForLongPress, + ViewConfiguration.getLongPressTimeout() - delayOffset); + } + } + + private final class CheckForLongPress implements Runnable { + + private final View mView; + + public CheckForLongPress(View v) { + mView = v; + } + + @Override + public void run() { + if (mView.isPressed() && (mView.getParent() != null)) { + if (mView.performLongClick()) { + mHasPerformedLongPress = true; + } + } + } + } + + private final class CheckForTap implements Runnable { + public float x; + public float y; + private final View mView; + + public CheckForTap(View v) { + mView = v; + } + + @Override + public void run() { + mPrePressed = false; + setPressed(mView, true, x, y); + checkForLongClick(mView, ViewConfiguration.getTapTimeout()); + } + } + + private final class PerformClick implements Runnable { + private final View mView; + + public PerformClick(View v) { + mView = v; + } + + @Override + public void run() { + mView.performClick(); + } + } + + private final class UnsetPressedState implements Runnable { + private final View mView; + + public UnsetPressedState(View v) { + mView = v; + } + + @Override + public void run() { + mView.setPressed(false); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/Hotspotable.java b/app/src/main/java/com/hippo/ehviewer/widget/Hotspotable.java new file mode 100644 index 000000000..170c3bda5 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/Hotspotable.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +public interface Hotspotable { + + public void setHotspot(float x, float y); +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/LinkifyTextView.java b/app/src/main/java/com/hippo/ehviewer/widget/LinkifyTextView.java new file mode 100644 index 000000000..b635eda10 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/LinkifyTextView.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.text.Layout; +import android.text.Spannable; +import android.text.style.ClickableSpan; +import android.text.style.URLSpan; +import android.text.util.Linkify; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.TextView; + +public class LinkifyTextView extends TextView { + + private String mUrl; + + public LinkifyTextView(Context context) { + super(context); + init(); + } + + public LinkifyTextView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public LinkifyTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + public void init() { + // Just make urls striking + // LinkifyTextView itself should not handle touch url event + setAutoLinkMask(Linkify.WEB_URLS); + setLinksClickable(false); + } + + public String getTouchedUrl() { + return mUrl; + } + + /** + * Call it when you do not need the url any more + */ + public void clearTouchedUrl() { + mUrl = null; + } + + @Override + @SuppressLint("ClickableViewAccessibility") + public boolean onTouchEvent(MotionEvent event) { + if (getText() instanceof Spannable) { + // Get this code from android.text.method.LinkMovementMethod. + // Work fine ! + int x = (int) event.getX(); + int y = (int) event.getY(); + boolean isGetUrl = false; + + x -= getTotalPaddingLeft(); + y -= getTotalPaddingTop(); + + x += getScrollX(); + y += getScrollY(); + + Layout layout = getLayout(); + int line = layout.getLineForVertical(y); + int off = layout.getOffsetForHorizontal(line, x); + + ClickableSpan[] links = ((Spannable)getText()).getSpans(off, off, ClickableSpan.class); + + for (ClickableSpan link : links) { + if (link instanceof URLSpan) { + mUrl = ((URLSpan)link).getURL(); + isGetUrl = true; + break; + } + } + if (!isGetUrl) + mUrl = null; + } else { + mUrl = null; + } + + return super.onTouchEvent(event); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/LoadImageView.java b/app/src/main/java/com/hippo/ehviewer/widget/LoadImageView.java new file mode 100644 index 000000000..7554f947c --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/LoadImageView.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; + +import com.hippo.ehviewer.ImageLoader; +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.effect.DrawableTransition; + +public class LoadImageView extends FixedAspectImageView { + @SuppressWarnings("unused") + private static final String TAG = LoadImageView.class.getSimpleName(); + + public static final int NONE = 0x0; + public static final int LOADING = 0x1; + public static final int LOADED = 0x2; + public static final int FAIL = 0x3; + + private static final int TOUCH_IMAGE_ID = R.drawable.ic_touch; + + private int mState = NONE; + + private String mUrl; + private String mKey; + + public LoadImageView(Context context) { + super(context); + } + public LoadImageView(Context context, AttributeSet attrs) { + super(context, attrs); + } + public LoadImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setLoadInfo(String url, String key) { + mUrl = url; + mKey = key; + } + + public synchronized void setState(int state) { + mState = state; + if (mState != FAIL) + setClickable(false); + } + + public void setOnClick() { + setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + setClickable(false); + setImageDrawable(null); + ImageLoader.getInstance(getContext()).add(mUrl, mKey, + new SimpleImageGetListener(LoadImageView.this)); + } + }); + } + + public String getUrl() { + return mUrl; + } + + public String getKey() { + return mKey; + } + + public synchronized int getState() { + return mState; + } + + public void setTouchImage() { + setImageResource(TOUCH_IMAGE_ID); + } + + /** + * Load target bmp, set progressive animation + * @param bmp + */ + public void setContextImage(Bitmap bmp, int state) { + setImageBitmap(bmp); + if (state != ImageLoader.STATE_FROM_MEMORY) { + final Drawable drawable = getDrawable(); + DrawableTransition.transit(drawable, false, 1000); + } + } + + public static class SimpleImageGetListener + implements ImageLoader.OnGetImageListener { + private boolean mTransitabled = true; + private boolean mFixScaleType = false; + private final LoadImageView mLiv; + + public SimpleImageGetListener(LoadImageView liv) { + mLiv = liv; + mLiv.setState(LoadImageView.LOADING); + } + + public SimpleImageGetListener setTransitabled(boolean transitabled) { + mTransitabled = transitabled; + return this; + } + + public SimpleImageGetListener setFixScaleType(boolean fixScaleType) { + mFixScaleType = fixScaleType; + return this; + } + + public boolean isVaild(String key) { + return key.equals(mLiv.getKey()) + && mLiv.getParent() != null; + } + + @Override + public void onGetImage(String key, Bitmap bmp, int state) { + if (isVaild(key) && mLiv.getState() == LoadImageView.LOADING) { + if (bmp != null) { + if (mFixScaleType) { + float aspectRatio = (float)bmp.getHeight() / bmp.getWidth(); + if (aspectRatio < 1.7 && aspectRatio > 1.3) + mLiv.setScaleType(ImageView.ScaleType.CENTER_CROP); + else + mLiv.setScaleType(ImageView.ScaleType.FIT_CENTER); + } + + if (mTransitabled) + mLiv.setContextImage(bmp, state); + else + mLiv.setImageBitmap(bmp); + mLiv.setState(LoadImageView.LOADED); + } else { + if (mFixScaleType) + mLiv.setScaleType(ImageView.ScaleType.FIT_CENTER); + mLiv.setTouchImage(); + mLiv.setOnClick(); + mLiv.setState(LoadImageView.FAIL); + } + } + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/MaterialProgress.java b/app/src/main/java/com/hippo/ehviewer/widget/MaterialProgress.java new file mode 100644 index 000000000..4f8382958 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/MaterialProgress.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.View; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.drawable.MaterialProgressDrawable; + +public class MaterialProgress extends View { + + private final MaterialProgressDrawable mDrawable; + + @SuppressWarnings("deprecation") + public MaterialProgress(Context context) { + super(context); + + mDrawable = new MaterialProgressDrawable(); + setBackgroundDrawable(mDrawable); + if (getVisibility() == View.VISIBLE) + start(); + } + + public MaterialProgress(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + @SuppressWarnings("deprecation") + public MaterialProgress(Context context, AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + + TypedArray typedArray = context.obtainStyledAttributes( + attrs, R.styleable.MaterialProgress, defStyleAttr, 0); + + int width = typedArray.getDimensionPixelSize(R.styleable.MaterialProgress_mpWidth, -1); + int height = typedArray.getDimensionPixelSize(R.styleable.MaterialProgress_mpHeight, -1); + int color = typedArray.getColor(R.styleable.MaterialProgress_mpColor, 0); + int thickness = typedArray.getDimensionPixelSize(R.styleable.MaterialProgress_mpThickness, 5); + int innerRadius = typedArray.getDimensionPixelSize(R.styleable.MaterialProgress_mpInnerRadius, -1); + + typedArray.recycle(); + + mDrawable = new MaterialProgressDrawable(width, height, color, thickness, innerRadius); + setBackgroundDrawable(mDrawable); + if (getVisibility() == View.VISIBLE) + start(); + } + + public void setColor(int color) { + mDrawable.setColor(color); + } + + public boolean isRunning() { + return mDrawable.isRunning(); + } + + public void start() { + mDrawable.start(); + } + + public void stop() { + mDrawable.stop(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/MaterialToast.java b/app/src/main/java/com/hippo/ehviewer/widget/MaterialToast.java new file mode 100644 index 000000000..ae8803361 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/MaterialToast.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; + +import com.hippo.ehviewer.R; + +public class MaterialToast extends Toast { + + private static Context sContext; + + public static void setContext(Context context) { + sContext = context; + } + + @SuppressLint("InflateParams") + public MaterialToast(String mesg) { + super(sContext); + init(mesg); + } + + @SuppressLint("InflateParams") + public MaterialToast(int resId) { + this(sContext.getString(resId)); + } + + @SuppressLint("InflateParams") + private void init(String mesg) { + View v = LayoutInflater.from(sContext) + .inflate(R.layout.material_toast, null); + TextView tv = (TextView)v.findViewById(R.id.message); + tv.setText(mesg); + setView(v); + setDuration(LENGTH_SHORT); + } + + public static final void showToast(String mesg) { + new MaterialToast(mesg).show(); + } + + public static final void showToast(int resId) { + new MaterialToast(resId).show(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/PrefixEditText.java b/app/src/main/java/com/hippo/ehviewer/widget/PrefixEditText.java new file mode 100644 index 000000000..5513cb8c2 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/PrefixEditText.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.content.Context; +import android.text.Editable; +import android.text.Selection; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.widget.AutoCompleteTextView; + +public class PrefixEditText extends AutoCompleteTextView + implements TextWatcher { + + private Context mContext; + private String mPrefix; + + public PrefixEditText(Context context) { + super(context); + init(context); + } + public PrefixEditText(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + public PrefixEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context); + } + + private void init(Context context) { + mContext = context; + addTextChangedListener(this); + } + + public void setPrefix(int resId) { + setPrefix(mContext.getString(resId)); + } + + public void setPrefix(String prefix) { + mPrefix = prefix; + setText(mPrefix); + if (mPrefix != null) + Selection.setSelection(getText(), mPrefix.length()); + } + + @Override + protected void onSelectionChanged(int selStart, int selEnd) { + super.onSelectionChanged(selStart, selEnd); + if(mPrefix != null) { + int newSelStart = selStart; + int newSelEnd = selEnd; + int prefixLength = mPrefix.length(); + if (newSelStart < prefixLength) + newSelStart = prefixLength; + if (newSelEnd < prefixLength) + newSelEnd = prefixLength; + setSelection(newSelStart, newSelEnd); + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, + int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + if(mPrefix != null && + !s.toString().startsWith(mPrefix)){ + setText(mPrefix); + setSelection(mPrefix.length()); + } + } + +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/ProgressiveRatingBar.java b/app/src/main/java/com/hippo/ehviewer/widget/ProgressiveRatingBar.java new file mode 100644 index 000000000..aa28a92a9 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/ProgressiveRatingBar.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.RatingBar; + +@SuppressLint("ClickableViewAccessibility") +public class ProgressiveRatingBar extends RatingBar + implements View.OnTouchListener { + + private boolean mEnableRate = true; + private OnUserRateListener mListener; + + public ProgressiveRatingBar(Context context) { + super(context); + setOnTouchListener(this); + } + public ProgressiveRatingBar(Context context, AttributeSet attrs) { + super(context, attrs); + setOnTouchListener(this); + } + public ProgressiveRatingBar(Context context, AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + setOnTouchListener(this); + } + + public void setEnableRate(boolean enableRate) { + mEnableRate = enableRate; + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (mEnableRate) + return false; + else + return true; + } + + @Override + @SuppressLint("WrongCall") + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (isPressed() && mListener != null) { + mListener.onUserRate(getRating()); + } + } + + public void setOnUserRateListener(OnUserRateListener l) { + mListener = l; + } + + public interface OnUserRateListener { + void onUserRate(float rating); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/ProgressiveTextView.java b/app/src/main/java/com/hippo/ehviewer/widget/ProgressiveTextView.java new file mode 100644 index 000000000..5279cca0e --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/ProgressiveTextView.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.widget.TextView; + +/** + * + * A simple view to show new text replacing old text with animation + * + * @author Hippo + * + */ +public class ProgressiveTextView extends TextView { + @SuppressWarnings("unused") + private static final String TAG = "ProgressiveTextView"; + + private boolean isProgress = false; + private CharSequence mNewText; + private Bitmap mShowBmp; + private float xOffset; + private float yOffset; + + public ProgressiveTextView(Context context) { + super(context); + } + public ProgressiveTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + public ProgressiveTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public final void setNewText(int resid) { + setNewText(getContext().getResources().getText(resid)); + } + + public final void setNewText(CharSequence newText) { + mNewText = newText; + isProgress = true; + invalidate(); + } + + @SuppressLint("DrawAllocation") + @Override + protected void onDraw(Canvas canvas) { + if (isProgress && (mShowBmp != null || mNewText != null)) { + int width = canvas.getWidth(); + int height = canvas.getHeight(); + + if (mShowBmp == null) { + mShowBmp = Bitmap.createBitmap(width, 2 * height, + Bitmap.Config.ARGB_8888); + Canvas showCanvas = new Canvas(mShowBmp); + xOffset = 0.0f; + yOffset = 0.0f; + + super.onDraw(showCanvas); + setText(mNewText); + mNewText = null; + showCanvas.translate(0, height); + super.onDraw(showCanvas); + } + + if (yOffset > -height) { + canvas.drawBitmap(mShowBmp, xOffset, yOffset, null); + yOffset -= 1; + invalidate(); + } else { + yOffset = -height; + canvas.drawBitmap(mShowBmp, xOffset, yOffset, null); + mShowBmp.recycle(); + mShowBmp = null; + isProgress = false; + } + } else { + super.onDraw(canvas); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/RatingView.java b/app/src/main/java/com/hippo/ehviewer/widget/RatingView.java new file mode 100644 index 000000000..5ee367c05 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/RatingView.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.TextView; + +import com.hippo.ehviewer.AppContext; + +public class RatingView extends TextView { + + private static final char START_EMPTY = '\uFA73'; + private static final char START_HALF = '\uFA74'; + private static final char START_FULL = '\uFA75'; + + private int mRating = -1; + + public RatingView(Context context) { + super(context); + init(context); + } + public RatingView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + public RatingView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + private void init(Context context) { + setTypeface(((AppContext)context.getApplicationContext()).getFaceTypeface()); + } + + public void setRating(float rating) { + mRating = Math.min(10, Math.round(rating * 2)); + int fullNum = mRating / 2; + int halfNum = mRating % 2; + int emptyNum = 5 - fullNum - halfNum; + StringBuilder sb = new StringBuilder(5); + for (int i = 0; i < fullNum; i++) + sb.append(START_FULL); + if (halfNum == 1) + sb.append(START_HALF); + for (int i = 0; i < emptyNum; i++) + sb.append(START_EMPTY); + setText(sb.toString()); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/RefreshLayout.java b/app/src/main/java/com/hippo/ehviewer/widget/RefreshLayout.java new file mode 100644 index 000000000..5420a825b --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/RefreshLayout.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.content.Context; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.widget.recyclerview.EasyRecyclerView; + +public class RefreshLayout extends SwipeRefreshLayout { + + private final static int FOOTER_REFRESHING = 0; + private final static int FOOTER_SUCCESS = 1; + private final static int FOOTER_FAIL = 2; + + private EasyRecyclerView mEasyRecyclerView; + private View mFooterView; + + private TextView mFooterTipTextView; + private View mFooterProgressBar; + private int mFooterState = FOOTER_SUCCESS; + private OnFooterRefreshListener mFooterRefreshListener; + + // Footer String to show + private String mFooterRefreshStr; + private String mFooterSuccessStr; + private String mFooterFailStr; + + private boolean mIsEnabledHeader = true; + private boolean mIsEnabledFooter = true; + + public RefreshLayout(Context context) { + super(context); + init(context, null); + } + + public RefreshLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + if (attrs == null) + mEasyRecyclerView = new EasyRecyclerView(context); + else + mEasyRecyclerView = new EasyRecyclerView(context, attrs); + mEasyRecyclerView.setId(R.id.easy_recycler_view); + addView(mEasyRecyclerView); + + // Footer view + mFooterView = LayoutInflater.from(context).inflate(R.layout.pull_list_view_footer, null); + mFooterTipTextView = (TextView) mFooterView.findViewById(R.id.footer_tip_text); + mFooterProgressBar = mFooterView.findViewById(R.id.footer_progressBar); + mFooterView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!isRefreshing() && mFooterState == FOOTER_FAIL) + footerRefresh(); + } + }); + } + + public EasyRecyclerView getEasyRecyclerView() { + return mEasyRecyclerView; + } + + public void addFooterView() { + ((EasyRecyclerView.FooterAdapter) mEasyRecyclerView.getAdapter()).setFooterView(mFooterView); + } + + public void removeFooterView() { + ((EasyRecyclerView.FooterAdapter) mEasyRecyclerView.getAdapter()).setFooterView(null); + } + + public void setOnHeaderRefreshListener(OnRefreshListener l) { + super.setOnRefreshListener(l); + } + + public void setOnFooterRefreshListener(OnFooterRefreshListener l) { + mFooterRefreshListener = l; + } + + private void footerRefresh() { + if (mFooterRefreshListener.onFooterRefresh()) { + mFooterState = FOOTER_REFRESHING; + changeFooterViewByState(); + + // Disable header refresh + if (mIsEnabledHeader) + setEnabled(false); + } + } + + /** + * Set footer UI String + * + * @param refreshStr + * @param successStr + * @param failStr + */ + public void setFooterString(String refreshStr, String successStr, String failStr) { + mFooterRefreshStr = refreshStr; + mFooterSuccessStr = successStr; + mFooterFailStr = failStr; + + switch (mFooterState) { + case FOOTER_REFRESHING: + mFooterTipTextView.setText(mFooterRefreshStr); + break; + case FOOTER_SUCCESS: + mFooterTipTextView.setText(mFooterSuccessStr); + break; + case FOOTER_FAIL: + mFooterTipTextView.setText(mFooterFailStr); + break; + } + } + + /** + * Refresh footer UI + */ + private void changeFooterViewByState() { + switch (mFooterState) { + case FOOTER_REFRESHING: + mFooterTipTextView.setText(mFooterRefreshStr); + mFooterProgressBar.setVisibility(View.VISIBLE); + break; + case FOOTER_SUCCESS: + mFooterTipTextView.setText(mFooterSuccessStr); + mFooterProgressBar.setVisibility(View.GONE); + break; + case FOOTER_FAIL: + mFooterTipTextView.setText(mFooterFailStr); + mFooterProgressBar.setVisibility(View.GONE); + break; + } + } + + + + public void setHeaderRefreshComplete() { + super.setRefreshing(false); + } + + public void setFooterRefreshComplete(boolean isSuccess) { + if (isSuccess) + mFooterState = FOOTER_SUCCESS; + else + mFooterState = FOOTER_FAIL; + changeFooterViewByState(); + + // enable header refresh + if (mIsEnabledHeader) + setEnabled(true); + } + + public void setAnyRefreshComplete() { + setAnyRefreshComplete(true); + } + + public void setAnyRefreshComplete(boolean isSuccess) { + setHeaderRefreshComplete(); + setFooterRefreshComplete(isSuccess); + } + + /** + * @return True if actionbar is refreshing + */ + public boolean isHeaderRefreshing() { + return super.isRefreshing(); + } + + /** + * @return True if footer is refreshing + */ + public boolean isFooterRefreshing() { + return mFooterState == FOOTER_REFRESHING; + } + + /** + * @return True if actionbar or footer is refreshing + */ + @Override + public boolean isRefreshing() { + return (mIsEnabledHeader ? isHeaderRefreshing() : false) + | (mIsEnabledFooter ? isFooterRefreshing() : false); + } + + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + if (mIsEnabledFooter && mFooterView.isShown() && mFooterState != FOOTER_FAIL) + footerRefresh(); + } + + public void setEnabledHeader(boolean enabled) { + if (mIsEnabledHeader == enabled) + return; + + mIsEnabledHeader = enabled; + setEnabled(enabled); + } + + public void setEnabledFooter(boolean enabled) { + if (mIsEnabledFooter == enabled) + return; + + if (mIsEnabledFooter = enabled) + addFooterView(); + else + removeFooterView(); + } + + public interface OnFooterRefreshListener { + /** + * @return True if this refresh action is vaild + */ + boolean onFooterRefresh(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/RefreshTextView.java b/app/src/main/java/com/hippo/ehviewer/widget/RefreshTextView.java new file mode 100644 index 000000000..7c9e0f438 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/RefreshTextView.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; + +import com.hippo.ehviewer.AppContext; +import com.hippo.ehviewer.AppHandler; + +// TODO add animation +public class RefreshTextView extends TextView + implements View.OnClickListener{ + + private volatile boolean mIsRefreshing = false; + private String mDefaultTip; + private OnRefreshListener mDefaultListener; + private OnRefreshListener mListener; + + private static final String[] REFRESHING_STRINGS = { + "(\uFA70π\uFA71)", + "(\uFA71π\uFA70)" + }; + + private static final int mSize = REFRESHING_STRINGS.length; + + public RefreshTextView(Context context) { + super(context); + init(context); + } + + public RefreshTextView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public RefreshTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context); + } + + public void init(Context context) { + setTypeface(((AppContext)context.getApplicationContext()).getFaceTypeface()); + setOnClickListener(this); + } + + public void setDefaultRefresh(int tiprResId, OnRefreshListener listener) { + setDefaultRefresh(getContext().getString(tiprResId), listener); + } + + public void setDefaultRefresh(String tip, OnRefreshListener listener) { + mDefaultTip = tip; + mDefaultListener = listener; + } + + @Override + public void onClick(View v) { + if (mIsRefreshing) + return; + + if (mListener != null) + mListener.onRefresh(); + + setRefreshing(true); + } + + public void setRefreshing(boolean refreshing) { + if (mIsRefreshing == refreshing) { + return; + } + + mIsRefreshing = refreshing; + if (mIsRefreshing) + new RefreshingThread().start(); + else + setText(null); + } + + public void setEmesg(int resId, boolean canRefresh) { + setEmesg(getContext().getString(resId), canRefresh); + } + + public void setEmesg(CharSequence emseg, boolean canRefresh) { + if (canRefresh) + setEmesg(emseg, mDefaultTip, mDefaultListener); + else + setEmesg(emseg, null, null); + } + + public void setEmesg(CharSequence emseg, String tip, + OnRefreshListener listener) { + mIsRefreshing = false; + boolean canRefresh = listener == null ? false : true; + + StringBuilder sb = new StringBuilder("==█ \n(>π<)\n\n"); + sb.append(emseg); + if (canRefresh) { + sb.append("\n\n").append(tip); + mListener = listener; + } else { + mListener = null; + } + + setText(sb.toString()); + } + + /** + * The thread to control refreshing animate + * + * @author Hippo + * + */ + private class RefreshingThread extends Thread { + int mIndex; + @Override + public void run() { + mIndex = 0; + while (mIsRefreshing) { + AppHandler.getInstance().post(new Runnable() { + @Override + public void run() { + if (mIsRefreshing) + RefreshTextView.this.setText(REFRESHING_STRINGS[mIndex]); + } + }); + try { + Thread.sleep(500); + } catch (InterruptedException e) {} + + if (++mIndex >= mSize) + mIndex = 0; + } + } + } + + public interface OnRefreshListener { + void onRefresh(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/ResponedScrollView.java b/app/src/main/java/com/hippo/ehviewer/widget/ResponedScrollView.java new file mode 100644 index 000000000..03b8710ff --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/ResponedScrollView.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.ScrollView; + +public class ResponedScrollView extends ScrollView { + + public static final int SCROLL_START = 0x0; + public static final int SCROLL_END = 0x1; + + public static final long CHECK_DELAY = 100L; + + private boolean isPosted = false; + private int mLastScrollY; + private OnScrollStateChangedListener mListener; + + public ResponedScrollView(Context context) { + super(context); + } + + public ResponedScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ResponedScrollView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setOnScrollStateChangedListener(OnScrollStateChangedListener l) { + mListener = l; + } + + private void wakeScrollObserver() { + mLastScrollY = getScrollY(); + postDelayed(mScrollObserver, CHECK_DELAY); + isPosted = true; + } + + @Override + @SuppressLint("ClickableViewAccessibility") + public boolean onTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_UP && !isPosted) { + wakeScrollObserver(); + if (mListener != null) + mListener.onScrollStateChanged(this, SCROLL_START); + } + return super.onTouchEvent(ev); + } + + private final Runnable mScrollObserver = new Runnable() { + @Override + public void run() { + int newScrollY = getScrollY(); + if(newScrollY == mLastScrollY) { + isPosted = false; + if (mListener != null) + mListener.onScrollStateChanged(ResponedScrollView.this, SCROLL_END); + } else { + wakeScrollObserver(); + } + } + }; + + public interface OnScrollStateChangedListener { + public void onScrollStateChanged(ResponedScrollView view, int state); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/SearchView.java b/app/src/main/java/com/hippo/ehviewer/widget/SearchView.java new file mode 100644 index 000000000..8d2ec5b91 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/SearchView.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; + +public class SearchView extends android.widget.SearchView { + + private static final String TAG = SearchView.class.getSimpleName(); + + private OnActionViewExpandedListener mListener; + + public SearchView(Context context) { + super(context); + } + + public SearchView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public SearchView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public SearchView(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public void onActionViewExpanded() { + super.onActionViewExpanded(); + + if (mListener != null) + mListener.onActionViewExpanded(); + } + + public void setOnActionViewExpandedListener(OnActionViewExpandedListener l) { + mListener = l; + } + + public interface OnActionViewExpandedListener { + public void onActionViewExpanded(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/SimpleGridLayout.java b/app/src/main/java/com/hippo/ehviewer/widget/SimpleGridLayout.java new file mode 100644 index 000000000..f04868be3 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/SimpleGridLayout.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import com.hippo.ehviewer.R; + +/** + * not scrollable + * + * @author Hippo + * + */ +public class SimpleGridLayout extends ViewGroup { + + private static final int DEFAULT_COLUMNS_PORTRAIT = 2; + private static final int DEFAULT_COLUMNS_LANDSCAPE = 3; + + + private int mItemMargin; + private int mColumnCount; + + private int mColumnCountPortrait = 1; + private int mColumnCountLandscape = 1; + + private int[] mItemHeights; + private int mItemWidth; + + public SimpleGridLayout(Context context) { + super(context); + init(); + } + + public SimpleGridLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SimpleGridLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SimpleGridLayout, defStyle, 0); + + mColumnCount = typedArray.getInteger( + R.styleable.SimpleGridLayout_sgl_column_count, 0); + + if (mColumnCount > 0) { + mColumnCountPortrait = mColumnCount; + mColumnCountLandscape = mColumnCount; + } + else { + mColumnCountPortrait = typedArray.getInteger( + R.styleable.SimpleGridLayout_sgl_column_count_portrait, + DEFAULT_COLUMNS_PORTRAIT); + mColumnCountLandscape = typedArray.getInteger( + R.styleable.SimpleGridLayout_sgl_column_count_landscape, + DEFAULT_COLUMNS_LANDSCAPE); + } + + mItemMargin = typedArray.getDimensionPixelSize( + R.styleable.SimpleGridLayout_sgl_item_margin, 0); + + typedArray.recycle(); + } + + public void init() { + setWillNotDraw(true); + } + + public void setItemMargin(int itemMargin) { + if (mItemMargin != itemMargin) { + mItemMargin = itemMargin; + requestLayout(); + } + } + + public void setColumnCount(int columnCount) { + if (columnCount > 0) { + boolean needRequestLayout = + isLandscape() ? (mColumnCountLandscape != columnCount ? true : false) : + (mColumnCountPortrait != columnCount ? true : false); + + mColumnCountPortrait = columnCount; + mColumnCountLandscape = columnCount; + + if (needRequestLayout) + requestLayout(); + } + } + + public void setColumnCountPortrait(int columnCountPortrait) { + if (columnCountPortrait > 0 && + mColumnCountPortrait != columnCountPortrait) { + mColumnCountPortrait = columnCountPortrait; + if (!isLandscape()) + requestLayout(); + } + } + + public void setColumnCountLandscape(int columnCountLandscape) { + if (columnCountLandscape > 0 && + mColumnCountLandscape != columnCountLandscape) { + mColumnCountLandscape = columnCountLandscape; + + if (isLandscape()) + requestLayout(); + } + } + + private boolean isLandscape() { + return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; + } + + private int getMaxValue(int[] array) { + int max = array[0]; + for (int i = 1; i < array.length; i++) + if (array[i] > max) max = array[i]; + return max; + } + + private int getMaxValue(int[] array, int length) { + int max = array[0]; + for (int i = 1; i < length; i++) + if (array[i] > max) max = array[i]; + return max; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + + int maxWidth = MeasureSpec.getSize(widthMeasureSpec); + int maxHeight = MeasureSpec.getSize(heightMeasureSpec); + + if (widthMode == MeasureSpec.UNSPECIFIED) + maxWidth = Integer.MAX_VALUE; + if (heightMode == MeasureSpec.UNSPECIFIED) + maxHeight = Integer.MAX_VALUE; + + mColumnCount = isLandscape() ? mColumnCountLandscape : mColumnCountPortrait; + + // Get item width MeasureSpec + int itemWidthMeasureSpec; + if (widthMode == MeasureSpec.UNSPECIFIED) { + mItemWidth = Integer.MAX_VALUE; + itemWidthMeasureSpec = MeasureSpec.makeMeasureSpec(mItemWidth, MeasureSpec.UNSPECIFIED); + } else { + mItemWidth = (maxWidth - (mColumnCount + 1) * mItemMargin) / mColumnCount; + itemWidthMeasureSpec = MeasureSpec.makeMeasureSpec(mItemWidth, MeasureSpec.EXACTLY); + } + + int maxItemWidth = 0; + int indexInRow = 0; + int lastbottom = mItemMargin; + int[] heightsOneRow = new int[mColumnCount]; + int childCount = getChildCount(); + if (mItemHeights == null || mItemHeights.length != childCount) + mItemHeights = new int[childCount]; + + for (int index = 0; index < childCount; index++, indexInRow++) { + final View child = getChildAt(index); + + child.measure(itemWidthMeasureSpec, MeasureSpec.UNSPECIFIED); + int childwidth = child.getMeasuredWidth(); + int childHeight = child.getMeasuredHeight(); + + if (maxItemWidth < childwidth) + maxItemWidth = childwidth; + + // Get a new row + if (indexInRow == mColumnCount) + indexInRow = 0; + heightsOneRow[indexInRow] = childHeight; + + // Get row end + if (indexInRow == mColumnCount - 1) { + int maxItemHeight = getMaxValue(heightsOneRow); + for (int i = 0; i < mColumnCount; i++) + mItemHeights[index - mColumnCount + 1 + i] = lastbottom + maxItemHeight - heightsOneRow[i]; + // Add margin + lastbottom += maxItemHeight + mItemMargin; + } + } + + // Get some item left + if (indexInRow != mColumnCount) { + int maxItemHeight = getMaxValue(heightsOneRow, indexInRow); + for (int i = 0; i < indexInRow; i++) + mItemHeights[childCount - indexInRow + i] = lastbottom + maxItemHeight - heightsOneRow[i]; + // Add margin + lastbottom += maxItemHeight + mItemMargin; + } + + int measuredWidth; + int measuredHeight; + if (widthMode == MeasureSpec.UNSPECIFIED) + measuredWidth = maxItemWidth; + else + measuredWidth = maxWidth; + if (heightMode == MeasureSpec.EXACTLY) + measuredHeight = maxHeight; + else if (heightMode == MeasureSpec.AT_MOST) + measuredHeight = maxHeight < lastbottom ? maxHeight : lastbottom; + else + measuredHeight = lastbottom; + + setMeasuredDimension(measuredWidth, measuredHeight); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int lastRight = 0; + int childCount = getChildCount(); + for (int index = 0; index < childCount; index++) { + final View child = getChildAt(index); + + int indexInRow = index % mColumnCount; + if (indexInRow == 0) + lastRight = mItemMargin; + + int itemWidth = mItemWidth == Integer.MAX_VALUE ? child.getMeasuredWidth(): mItemWidth; + child.layout(lastRight, mItemHeights[index], lastRight + itemWidth, + mItemHeights[index] + child.getMeasuredHeight()); + + lastRight += itemWidth + mItemMargin; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/SlidingDrawerLayout.java b/app/src/main/java/com/hippo/ehviewer/widget/SlidingDrawerLayout.java new file mode 100644 index 000000000..03e90db6a --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/SlidingDrawerLayout.java @@ -0,0 +1,1184 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.v4.content.ContextCompat; +import android.support.v4.view.GravityCompat; +import android.support.v4.view.MotionEventCompat; +import android.support.v4.view.ViewCompat; +import android.support.v4.widget.ViewDragHelper; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowInsets; + +import com.hippo.ehviewer.util.Constants; +import com.hippo.ehviewer.util.MathUtils; +import com.hippo.ehviewer.util.Ui; +import com.hippo.ehviewer.util.ViewUtils; +import com.hippo.ehviewer.util.ZInterpolator; + +@SuppressLint("RtlHardcoded") +public class SlidingDrawerLayout extends ViewGroup implements ValueAnimator.AnimatorUpdateListener, + Animator.AnimatorListener { + + private static final String TAG = SlidingDrawerLayout.class.getSimpleName(); + + private static final int[] LAYOUT_ATTRS = new int[] { + android.R.attr.layout_gravity + }; + + private static final int STATE_CLOSED = 0; + private static final int STATE_SLIDING = 1; + private static final int STATE_OPEN = 2; + + /** + * The drawer is unlocked. + */ + public static final int LOCK_MODE_UNLOCKED = 0; + + /** + * The drawer is locked closed. The user may not open it, though the app may + * open it programmatically. + */ + public static final int LOCK_MODE_LOCKED_CLOSED = 1; + + /** + * The drawer is locked open. The user may not close it, though the app may + * close it programmatically. + */ + public static final int LOCK_MODE_LOCKED_OPEN = 2; + + private static final float OPEN_SENSITIVITY = 0.1f; + private static final float CLOSE_SENSITIVITY = 0.9f; + + private int mMinDrawerMargin; + private boolean mInLayout; + + private View mContentView; + private View mLeftDrawer; + private View mRightDrawer; + private ShadowView mShadow; + + private ViewDragHelper mDragHelper; + private float mInitialMotionX; + private float mInitialMotionY; + + private boolean mLeftOpened; + private boolean mRightOpened; + private int mLeftState; + private int mRightState; + private float mLeftPercent; + private float mRightPercent; + private int mLeftLockMode; + private int mRightLockMode; + + private ValueAnimator mAnimator; + private View mTargetView; + private int mStartLeft; + private int mEndLeft; + private boolean mToOpen; + private boolean mCancelAnimation; + private View mOpenTask; + + private DrawerListener mListener; + + private Drawable mStatusBarBackground; + + private boolean mIntercepted; + private int mInterceptPointNum; + + private Object mLastInsets; + private boolean mDrawStatusBarBackground; + + private static int sDefaultMinDrawerMargin = 56; + + private static final SlidingDrawerLayoutCompatImpl IMPL; + + static { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + IMPL = new SlidingDrawerLayoutCompatImplApi21(); + } else { + IMPL = new SlidingDrawerLayoutCompatImplBase(); + } + } + + interface SlidingDrawerLayoutCompatImpl { + void configureApplyInsets(View drawerLayout); + void dispatchChildInsets(View child, Object insets, int drawerGravity); + void applyMarginInsets(MarginLayoutParams lp, Object insets, int drawerGravity); + int getTopInset(Object lastInsets); + } + + static class SlidingDrawerLayoutCompatImplBase implements SlidingDrawerLayoutCompatImpl { + @Override + public void configureApplyInsets(View drawerLayout) { + // This space for rent + } + + @Override + public void dispatchChildInsets(View child, Object insets, int drawerGravity) { + // This space for rent + } + + @Override + public void applyMarginInsets(MarginLayoutParams lp, Object insets, int drawerGravity) { + // This space for rent + } + + @Override + public int getTopInset(Object insets) { + return 0; + } + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + static class SlidingDrawerLayoutCompatImplApi21 implements SlidingDrawerLayoutCompatImpl { + @Override + public void configureApplyInsets(View drawerLayout) { + if (drawerLayout instanceof SlidingDrawerLayout) { + drawerLayout.setOnApplyWindowInsetsListener(new InsetsListener()); + drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + } + } + + @Override + public void dispatchChildInsets(View child, Object insets, int gravity) { + WindowInsets wi = (WindowInsets) insets; + if (gravity == Gravity.LEFT) { + wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(), + wi.getSystemWindowInsetTop(), 0, wi.getSystemWindowInsetBottom()); + } else if (gravity == Gravity.RIGHT) { + wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(), + wi.getSystemWindowInsetRight(), wi.getSystemWindowInsetBottom()); + } + child.dispatchApplyWindowInsets(wi); + } + + @Override + public void applyMarginInsets(MarginLayoutParams lp, Object insets, int gravity) { + WindowInsets wi = (WindowInsets) insets; + if (gravity == Gravity.LEFT) { + wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(), + wi.getSystemWindowInsetTop(), 0, wi.getSystemWindowInsetBottom()); + } else if (gravity == Gravity.RIGHT) { + wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(), + wi.getSystemWindowInsetRight(), wi.getSystemWindowInsetBottom()); + } + lp.leftMargin = wi.getSystemWindowInsetLeft(); + lp.topMargin = wi.getSystemWindowInsetTop(); + lp.rightMargin = wi.getSystemWindowInsetRight(); + lp.bottomMargin = wi.getSystemWindowInsetBottom(); + } + + @Override + public int getTopInset(Object insets) { + return insets != null ? ((WindowInsets) insets).getSystemWindowInsetTop() : 0; + } + + static class InsetsListener implements View.OnApplyWindowInsetsListener { + @Override + public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { + final SlidingDrawerLayout drawerLayout = (SlidingDrawerLayout) v; + drawerLayout.setChildInsets(insets, insets.getSystemWindowInsetTop() > 0); + return insets.consumeSystemWindowInsets(); + } + } + } + + /** + * Listener for monitoring events about drawers. + */ + public interface DrawerListener { + /** + * Called when a drawer's position changes. + * @param drawerView The child view that was moved + * @param slideOffset The new offset of this drawer within its range, from 0-1 + */ + public void onDrawerSlide(View drawerView, float percent); + + /** + * Called when a drawer has settled in a completely open state. + * The drawer is interactive at this point. + * + * @param drawerView Drawer view that is now open + */ + public void onDrawerOpened(View drawerView); + + /** + * Called when a drawer has settled in a completely closed state. + * + * @param drawerView Drawer view that is now closed + */ + public void onDrawerClosed(View drawerView); + + /** + * Called when the drawer motion state changes. The new state will + * be one of {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}. + * + * @param newState The new drawer motion state + */ + public void onDrawerStateChanged(View drawerView, int newState); + } + + /** + * minMargin should be app bar height in Material design + * + * @param minMargin + */ + public static final void setDefaultMinDrawerMargin(int minMargin) { + sDefaultMinDrawerMargin = minMargin; + } + + public SlidingDrawerLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public SlidingDrawerLayout(Context context, AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public SlidingDrawerLayout(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + private void init() { + mLeftOpened = false; + mRightOpened = false; + mLeftState = STATE_CLOSED; + mRightState = STATE_CLOSED; + mLeftPercent = 0.0f; + mRightPercent = 0.0f; + mMinDrawerMargin = sDefaultMinDrawerMargin; + mDragHelper = ViewDragHelper.create(this, 0.5f, new DragHelperCallback()); + mAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); + mAnimator.addUpdateListener(this); + mAnimator.addListener(this); + mAnimator.setInterpolator(new ZInterpolator(0.5f)); + mCancelAnimation = false; + + setWillNotDraw(false); + } + + public void setDrawerListener(DrawerListener listener) { + mListener = listener; + } + + /** + * Close all currently open drawer views by animating them out of view. + */ + public void closeDrawers() { + closeDrawer(mLeftDrawer); + closeDrawer(mRightDrawer); + } + + /** + * Open the specified drawer view by animating it into view. + * + * @param drawerView + * Drawer view to open + */ + public void openDrawer(View drawerView) { + if (drawerView == null || (drawerView != mLeftDrawer && drawerView != mRightDrawer)) + return; + + boolean isLeft = drawerView == mLeftDrawer; + switch (isLeft ? mLeftState : mRightState) { + case STATE_CLOSED: + // Check other side first + switch (isLeft ? mRightState : mLeftState) { + case STATE_CLOSED: + startAnimation(isLeft, true, false); + break; + case STATE_SLIDING: + if (mAnimator.isRunning()) { + if (mToOpen) { + cancelAnimation(); + mOpenTask = isLeft ? mLeftDrawer : mRightDrawer; + startAnimation(!isLeft, false, false); + } else { + mOpenTask = isLeft ? mLeftDrawer : mRightDrawer; + } + } else { + // You finger is on Screen! + } + break; + case STATE_OPEN: + // Close other side first + mOpenTask = isLeft ? mLeftDrawer : mRightDrawer; + startAnimation(!isLeft, false, false); + break; + } + break; + + case STATE_SLIDING: + if (mAnimator.isRunning()) { + if (mToOpen) { + // Same purpose + } else { + cancelAnimation(); + startAnimation(isLeft, true, false); + } + } else { + // You finger is on Screen! + } + break; + + case STATE_OPEN: + // Ok it is opened + break; + } + } + + public void openDrawer(int gravity) { + if (gravity == Gravity.LEFT) + openDrawer(mLeftDrawer); + else if (gravity == Gravity.RIGHT) + openDrawer(mRightDrawer); + } + + /** + * Close the specified drawer view by animating it into view. + * + * @param drawerView + * Drawer view to close + */ + public void closeDrawer(View drawerView) { + if (drawerView == null || (drawerView != mLeftDrawer && drawerView != mRightDrawer)) + return; + + boolean isLeft = drawerView == mLeftDrawer; + switch (isLeft ? mLeftState : mRightState) { + case STATE_CLOSED: + // Ok it is closed + break; + case STATE_SLIDING: + if (mAnimator.isRunning()) { + if (mToOpen) { + cancelAnimation(); + startAnimation(isLeft, false, false); + } else { + // Same purpose + } + } else { + // You finger is on Screen! + } + break; + case STATE_OPEN: + startAnimation(isLeft, false, false); + break; + } + } + + public void closeDrawer(int gravity) { + if (gravity == Gravity.LEFT) + closeDrawer(mLeftDrawer); + else if (gravity == Gravity.RIGHT) + closeDrawer(mRightDrawer); + } + + public boolean isDrawerOpen(View drawer) { + if (drawer == mLeftDrawer) + return mLeftOpened; + else if (drawer == mRightDrawer) + return mRightOpened; + else + throw new IllegalArgumentException("The view is not drawer"); + } + + public boolean isDrawerOpen(int gravity) { + if (gravity == Gravity.LEFT) + return isDrawerOpen(mLeftDrawer); + else if (gravity == Gravity.RIGHT) + return isDrawerOpen(mRightDrawer); + else + throw new IllegalArgumentException("gravity must be Gravity.LEFT or Gravity.RIGHT"); + } + + public void setDrawerLockMode(int lockMode, int gravity) { + if (gravity == Gravity.LEFT) + setDrawerLockMode(gravity, mLeftDrawer); + else if (gravity == Gravity.RIGHT) + setDrawerLockMode(gravity, mRightDrawer); + else + throw new IllegalArgumentException("gravity must be Gravity.LEFT or Gravity.RIGHT"); + } + + public void setDrawerLockMode(int lockMode, View drawer) { + if (drawer != mLeftDrawer && drawer != mRightDrawer) + throw new IllegalArgumentException("The view is not drawer"); + + int oldLockMode; + int otherSideLockMode; + if (drawer == mLeftDrawer) { + oldLockMode = mLeftLockMode; + otherSideLockMode = mRightLockMode; + mLeftLockMode = lockMode; + } else { + oldLockMode = mRightLockMode; + otherSideLockMode = mLeftLockMode; + mRightLockMode = lockMode; + } + if (oldLockMode == lockMode) + return; + + if (otherSideLockMode == LOCK_MODE_LOCKED_OPEN && lockMode == LOCK_MODE_LOCKED_OPEN) + throw new IllegalArgumentException("Only on side could be LOCK_MODE_LOCKED_OPEN"); + + switch (lockMode) { + // TODO What if open or close fail ? + case LOCK_MODE_LOCKED_OPEN: + openDrawer(drawer); + break; + case LOCK_MODE_LOCKED_CLOSED: + closeDrawer(drawer); + break; + } + } + + public int getDrawerLockMode(int gravity) { + if (gravity == Gravity.LEFT) + return getDrawerLockMode(mLeftDrawer); + else if (gravity == Gravity.RIGHT) + return getDrawerLockMode(mRightDrawer); + else + throw new IllegalArgumentException("gravity must be Gravity.LEFT or Gravity.RIGHT"); + } + + public int getDrawerLockMode(View drawer) { + if (drawer == mLeftDrawer) + return mLeftLockMode; + else if (drawer == mRightDrawer) + return mRightLockMode; + else + throw new IllegalArgumentException("The view is not drawer"); + } + + @Override + protected void onFinishInflate() { + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + final int absGravity = GravityCompat.getAbsoluteGravity( + ((LayoutParams) child.getLayoutParams()).gravity, + ViewCompat.getLayoutDirection(child)); + + if (absGravity == Gravity.NO_GRAVITY) { + if (mContentView != null) + throw new IllegalStateException("There is more than one content view"); + mContentView = child; + } else if ((absGravity & Gravity.LEFT) == Gravity.LEFT) { + if (mLeftDrawer != null) + throw new IllegalStateException("There is more than one left menu"); + mLeftDrawer = child; + } else if ((absGravity & Gravity.RIGHT) == Gravity.RIGHT) { + if (mRightDrawer != null) + throw new IllegalStateException("There is more than one right menu"); + mRightDrawer = child; + } else { + throw new IllegalStateException("Child " + child + " at index " + i + + " does not have a valid layout_gravity - must be Gravity.LEFT, " + + "Gravity.RIGHT or Gravity.NO_GRAVITY"); + } + } + + if (mContentView == null) + throw new IllegalStateException("There is no content view"); + // Material is solid. + // Input events cannot pass through material. + if (mLeftDrawer != null) + mLeftDrawer.setClickable(true); + if (mRightDrawer != null) + mRightDrawer.setClickable(true); + + mShadow = new ShadowView(getContext()); + addView(mShadow, 1); + } + + /** + * @hide Internal use only; called to apply window insets when configured + * with fitsSystemWindows="true" + */ + public void setChildInsets(Object insets, boolean draw) { + mLastInsets = insets; + mDrawStatusBarBackground = draw; + setWillNotDraw(!draw && getBackground() == null); + requestLayout(); + } + + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) + throw new IllegalArgumentException( + "SlidingDrawerLayout must be measured with MeasureSpec.EXACTLY."); + + setMeasuredDimension(widthSize, heightSize); + + final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this); + final int layoutDirection = ViewCompat.getLayoutDirection(this); + + // Gravity value for each drawer we've seen. Only one of each permitted. + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + + if (child.getVisibility() == GONE) { + continue; + } + + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + if (applyInsets) { + final int cgrav = GravityCompat.getAbsoluteGravity(lp.gravity, layoutDirection); + if (ViewCompat.getFitsSystemWindows(child)) { + IMPL.dispatchChildInsets(child, mLastInsets, cgrav); + } else { + IMPL.applyMarginInsets(lp, mLastInsets, cgrav); + } + } + + if (child == mContentView) { + // Content views get measured at exactly the layout's size. + final int contentWidthSpec = MeasureSpec.makeMeasureSpec( + widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); + final int contentHeightSpec = MeasureSpec.makeMeasureSpec( + heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY); + child.measure(contentWidthSpec, contentHeightSpec); + } else if (child == mLeftDrawer || child == mRightDrawer) { + final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec, + lp.leftMargin + lp.rightMargin, + Math.min(widthSize - mMinDrawerMargin, lp.width)); + final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec, + lp.topMargin + lp.bottomMargin, + lp.height); + child.measure(drawerWidthSpec, drawerHeightSpec); + } else if (child == mShadow) { + child.measure(widthMeasureSpec, heightMeasureSpec); + } else { + throw new IllegalStateException("Don't call addView"); + } + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + mInLayout = true; + final int width = r - l; + final int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + + if (child.getVisibility() == GONE) { + continue; + } + + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + if (child == mContentView) { + child.layout(lp.leftMargin, lp.topMargin, + lp.leftMargin + child.getMeasuredWidth(), + lp.topMargin + child.getMeasuredHeight()); + } else if (child == mShadow) { + child.layout(l, t, r, b); + } else { // Drawer, if it wasn't onMeasure would have thrown an exception. + final int childWidth = child.getMeasuredWidth(); + final int childHeight = child.getMeasuredHeight(); + int childLeft; + float percent; + + if (child == mLeftDrawer) { + percent = mLeftPercent; + childLeft = -childWidth + (int) (childWidth * percent); + } else { // Right; onMeasure checked for us. + percent = mRightPercent; + childLeft = width - (int) (childWidth * percent); + } + + final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; + + switch (vgrav) { + default: + case Gravity.TOP: { + child.layout(childLeft, lp.topMargin, childLeft + childWidth, + lp.topMargin + childHeight); + break; + } + + case Gravity.BOTTOM: { + final int height = b - t; + child.layout(childLeft, + height - lp.bottomMargin - child.getMeasuredHeight(), + childLeft + childWidth, + height - lp.bottomMargin); + break; + } + + case Gravity.CENTER_VERTICAL: { + final int height = b - t; + int childTop = (height - childHeight) / 2; + + // Offset for margins. If things don't fit right because of + // bad measurement before, oh well. + if (childTop < lp.topMargin) { + childTop = lp.topMargin; + } else if (childTop + childHeight > height - lp.bottomMargin) { + childTop = height - lp.bottomMargin - childHeight; + } + child.layout(childLeft, childTop, childLeft + childWidth, + childTop + childHeight); + break; + } + } + + final int newVisibility = percent > 0 ? VISIBLE : INVISIBLE; + if (child.getVisibility() != newVisibility) { + child.setVisibility(newVisibility); + } + } + } + mInLayout = false; + } + + @Override + public void requestLayout() { + if (!mInLayout) { + super.requestLayout(); + } + } + + private boolean isDrawerUnder(int x, int y) { + return ViewUtils.isViewUnder(mLeftDrawer, x, y) || ViewUtils.isViewUnder(mRightDrawer, x, y); + } + + private boolean isDrawersTouchable() { + if ((mLeftDrawer != null && mLeftLockMode == LOCK_MODE_UNLOCKED) + || (mRightDrawer != null && mRightLockMode == LOCK_MODE_UNLOCKED)) + return true; + else + return false; + } + + private int getActualDxLeft(int dx) { + int leftWidth = mLeftDrawer.getWidth(); + int oldLeft = mLeftDrawer.getLeft(); + int newLeft = MathUtils.clamp(oldLeft + dx, -leftWidth, 0); + int newVisible = newLeft == -leftWidth ? View.INVISIBLE : View.VISIBLE; + if (mLeftDrawer.getVisibility() != newVisible) + mLeftDrawer.setVisibility(newVisible); + + updateDrawerSlide(mLeftDrawer, (newLeft + leftWidth) / (float) leftWidth); + + return newLeft - oldLeft; + } + + private int getActualDxRight(int dx) { + int width = getWidth(); + int rightWidth = mRightDrawer.getWidth(); + int oldLeft = mRightDrawer.getLeft(); + int newLeft = MathUtils.clamp(oldLeft + dx, width - rightWidth, width); + int newVisible = newLeft == width ? View.INVISIBLE : View.VISIBLE; + if (mRightDrawer.getVisibility() != newVisible) + mRightDrawer.setVisibility(newVisible); + + updateDrawerSlide(mRightDrawer, (width - newLeft) / (float) rightWidth); + + return newLeft - oldLeft; + } + + private void slideLeftDrawer(int dx) { + mLeftDrawer.offsetLeftAndRight(getActualDxLeft(dx)); + } + + private void slideRightDrawer(int dx) { + mRightDrawer.offsetLeftAndRight(getActualDxRight(dx)); + } + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float value = (Float) animation.getAnimatedValue(); + int oldLeft = mTargetView.getLeft(); + int newLeft = MathUtils.lerp(mStartLeft, mEndLeft, value); + if (mTargetView == mLeftDrawer) + slideLeftDrawer(newLeft - oldLeft); + else + slideRightDrawer(newLeft - oldLeft); + } + + @Override + public void onAnimationStart(Animator animation) { + updateDrawerState(mTargetView, STATE_SLIDING); + } + + @Override + public void onAnimationEnd(Animator animation) { + if (!mCancelAnimation) { + updateDrawerSlide(mTargetView, mToOpen ? 1.0f : 0.0f); + updateDrawerState(mTargetView, mToOpen ? STATE_OPEN : STATE_CLOSED); + if (mToOpen) + dispatchOnDrawerOpened(mTargetView); + else + dispatchOnDrawerClosed(mTargetView); + + if (mOpenTask != null && !mToOpen && + (mOpenTask == mLeftDrawer || mOpenTask == mRightDrawer)) { + // If call startAnimation directly, + // onAnimationStart and onAnimationEnd will not be called + final boolean isLeft = mOpenTask == mLeftDrawer; + getHandler().post(new Runnable() { + @Override + public void run() { + startAnimation(isLeft, true, false); + } + }); + } + } + + mCancelAnimation = false; + mOpenTask = null; + } + + @Override + public void onAnimationCancel(Animator animation) { + // Empty + } + + @Override + public void onAnimationRepeat(Animator animation) { + // Empty + } + + private void startAnimation(final boolean isLeft, final boolean isOpen, + boolean staticDuration) { + mToOpen = isOpen; + if (isLeft) { + mTargetView = mLeftDrawer; + mStartLeft = mLeftDrawer.getLeft(); + mEndLeft = mToOpen ? 0 : -mLeftDrawer.getWidth(); + } else { + mTargetView = mRightDrawer; + mStartLeft = mRightDrawer.getLeft(); + mEndLeft = mToOpen ? getWidth() - mRightDrawer.getWidth() : getWidth(); + } + + if (mStartLeft == mEndLeft) { + // No need to animate + updateDrawerSlide(mTargetView, mToOpen ? 1.0f : 0.0f); + updateDrawerState(mTargetView, mToOpen ? STATE_OPEN : STATE_CLOSED); + if (mToOpen) + dispatchOnDrawerOpened(mTargetView); + else + dispatchOnDrawerClosed(mTargetView); + return; + } + + mAnimator.setDuration(staticDuration ? Constants.ANIMATE_TIME : + (long) (Ui.pix2dp(Math.abs(mEndLeft - mStartLeft)) * 1.25)); + mAnimator.start(); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + + final int action = MotionEventCompat.getActionMasked(ev); + final float x = ev.getX(); + final float y = ev.getY(); + boolean interceptSlide = false; + + if (!isDrawersTouchable()) { + return false; + } + + if ((mLeftState == STATE_OPEN || mRightState == STATE_OPEN) && + !isDrawerUnder((int) x, (int) y)) + return true; + + switch (action) { + case MotionEvent.ACTION_DOWN: + mIntercepted = false; + mInterceptPointNum = 0; + mInitialMotionX = x; + mInitialMotionY = y; + break; + case MotionEvent.ACTION_MOVE: + final float adx = Math.abs(x - mInitialMotionX); + final float ady = Math.abs(y - mInitialMotionY); + final int slop = mDragHelper.getTouchSlop(); + if (adx > slop && adx > ady) + interceptSlide = true; + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mDragHelper.cancel(); + break; + } + + if (++mInterceptPointNum > 8) + return false; + + mIntercepted = mDragHelper.shouldInterceptTouchEvent(ev) || interceptSlide; + return mIntercepted; + } + + private void cancelAnimation() { + if (mAnimator.isRunning()) { + mOpenTask = null; + mCancelAnimation = true; + mAnimator.cancel(); + } + } + + @Override + @SuppressLint("ClickableViewAccessibility") + public boolean onTouchEvent(MotionEvent ev) { + + // Cancel animate + cancelAnimation(); + + try { + mDragHelper.processTouchEvent(ev); + } catch (Throwable e) { + // TODO Another finger touch screen may make trouble + } + + final int action = MotionEventCompat.getActionMasked(ev); + final float x = ev.getX(); + final float y = ev.getY(); + + if (action == MotionEvent.ACTION_DOWN) { + mInterceptPointNum = 1; + mIntercepted = true; + } + + if (!mIntercepted) + return false; + + if (!isDrawersTouchable()) + return false; + + switch (action) { + case MotionEvent.ACTION_DOWN: + mInitialMotionX = x; + mInitialMotionY = y; + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mInterceptPointNum = 0; + + if (mLeftState == STATE_SLIDING) { + if (mLeftOpened && mLeftPercent < CLOSE_SENSITIVITY) + startAnimation(true, false, false); + else if (!mLeftOpened && mLeftPercent < OPEN_SENSITIVITY) + startAnimation(true, false, true); + else if (mLeftOpened && mLeftPercent >= CLOSE_SENSITIVITY) + startAnimation(true, true, true); + else if (!mLeftOpened && mLeftPercent >= OPEN_SENSITIVITY) + startAnimation(true, true, false); + } else if (mRightState == STATE_SLIDING) { + if (mRightOpened && mRightPercent < CLOSE_SENSITIVITY) + startAnimation(false, false, false); + else if (!mRightOpened && mRightPercent < OPEN_SENSITIVITY) + startAnimation(false, false, true); + else if (mRightOpened && mRightPercent >= CLOSE_SENSITIVITY) + startAnimation(false, true, true); + else if (!mRightOpened && mRightPercent >= OPEN_SENSITIVITY) + startAnimation(false, true, false); + } + break; + } + + return true; + } + + @Override + protected int getChildDrawingOrder(int childCount, int i) { + return i; + } + + /** + * Set a drawable to draw in the insets area for the status bar. + * Note that this will only be activated if this DrawerLayout fitsSystemWindows. + * + * @param bg Background drawable to draw behind the status bar + */ + public void setStatusBarBackground(Drawable bg) { + mStatusBarBackground = bg; + } + + /** + * Set a drawable to draw in the insets area for the status bar. + * Note that this will only be activated if this DrawerLayout fitsSystemWindows. + * + * @param resId Resource id of a background drawable to draw behind the status bar + */ + public void setStatusBarBackground(int resId) { + mStatusBarBackground = resId != 0 ? ContextCompat.getDrawable(getContext(), resId) : null; + } + + /** + * Set a drawable to draw in the insets area for the status bar. + * Note that this will only be activated if this DrawerLayout fitsSystemWindows. + * + * @param color Color to use as a background drawable to draw behind the status bar + * in 0xAARRGGBB format. + */ + public void setStatusBarBackgroundColor(int color) { + mStatusBarBackground = new ColorDrawable(color); + } + + @Override + public void onDraw(Canvas c) { + super.onDraw(c); + if (mDrawStatusBarBackground && mStatusBarBackground != null) { + final int inset = IMPL.getTopInset(mLastInsets); + if (inset > 0) { + mStatusBarBackground.setBounds(0, 0, getWidth(), inset); + mStatusBarBackground.draw(c); + } + } + } + + private void updateDrawerSlide(View drawerView, float percent) { + boolean update = false; + // Update percent + if (drawerView == mLeftDrawer) { + update = mLeftPercent != percent; + mLeftPercent = percent; + } else if (drawerView == mRightDrawer) { + update = mRightPercent != percent; + mRightPercent = percent; + } + + if (update) + mShadow.setPercent(percent); + + // Callback + if (update && mListener != null) { + mListener.onDrawerSlide(drawerView, percent); + } + } + + private void updateDrawerState(View drawerView, int state) { + boolean update = false; + // Update state + if (drawerView == mLeftDrawer) { + update = mLeftState != state; + mLeftState = state; + } else if (drawerView == mRightDrawer) { + update = mRightState != state; + mRightState = state; + } + + // Callback + if (update && mListener != null) { + mListener.onDrawerStateChanged(drawerView, state); + } + } + + private void dispatchOnDrawerClosed(View drawerView) { + boolean update = false; + // Update + if (drawerView == mLeftDrawer) { + update = mLeftOpened; + mLeftOpened = false; + } else if (drawerView == mRightDrawer) { + update = mRightOpened; + mRightOpened = false; + } + + // Callback + if (update && mListener != null) { + mListener.onDrawerClosed(drawerView); + } + } + + private void dispatchOnDrawerOpened(View drawerView) { + boolean update = false; + // Update + if (drawerView == mLeftDrawer) { + update = !mLeftOpened; + mLeftOpened = true; + } else if (drawerView == mRightDrawer) { + update = !mRightOpened; + mRightOpened = true; + } + + // Callback + if (update && mListener != null) { + mListener.onDrawerOpened(drawerView); + } + } + + @Override + protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + } + + @Override + protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof LayoutParams + ? new LayoutParams((LayoutParams) p) + : p instanceof ViewGroup.MarginLayoutParams + ? new LayoutParams((MarginLayoutParams) p) + : new LayoutParams(p); + } + + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof LayoutParams && super.checkLayoutParams(p); + } + + @Override + public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); + } + + public static class LayoutParams extends ViewGroup.MarginLayoutParams { + + public int gravity = Gravity.NO_GRAVITY; + + public LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + + final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS); + this.gravity = a.getInt(0, Gravity.NO_GRAVITY); + a.recycle(); + } + + public LayoutParams(int width, int height) { + super(width, height); + } + + public LayoutParams(int width, int height, int gravity) { + this(width, height); + this.gravity = gravity; + } + + public LayoutParams(LayoutParams source) { + super(source); + this.gravity = source.gravity; + } + + public LayoutParams(ViewGroup.LayoutParams source) { + super(source); + } + + public LayoutParams(ViewGroup.MarginLayoutParams source) { + super(source); + } + } + + private class DragHelperCallback extends ViewDragHelper.Callback { + + @Override + public boolean tryCaptureView(View child, int pointerId) { + return true; + } + + @Override + public int clampViewPositionVertical(View child, int top, int dy) { + return child.getTop(); + } + + @Override + public int clampViewPositionHorizontal(View child, int left, int dx) { + + if (!mIntercepted) + return child.getLeft(); + + if (child == mContentView || child == mShadow) { + if (mLeftState == STATE_CLOSED && mRightState == STATE_CLOSED) { + if (dx > 0 && mLeftDrawer != null && mLeftLockMode == LOCK_MODE_UNLOCKED) { + slideLeftDrawer(dx); + updateDrawerState(mLeftDrawer, STATE_SLIDING); + } else if (dx < 0 && mRightDrawer != null && mRightLockMode == LOCK_MODE_UNLOCKED) { + slideRightDrawer(dx); + updateDrawerState(mRightDrawer, STATE_SLIDING); + } + } else if (mLeftState != STATE_CLOSED) { + slideLeftDrawer(dx); + updateDrawerState(mLeftDrawer, STATE_SLIDING); + } else if (mRightState != STATE_CLOSED) { + slideRightDrawer(dx); + updateDrawerState(mRightDrawer, STATE_SLIDING); + } + return child.getLeft(); + + } else if (child == mLeftDrawer) { + updateDrawerState(mLeftDrawer, STATE_SLIDING); + return child.getLeft() + getActualDxLeft(dx); + + } else if (child == mRightDrawer) { + updateDrawerState(mRightDrawer, STATE_SLIDING); + return child.getLeft() + getActualDxRight(dx); + + } else { + return child.getLeft(); + } + } + + @Override + public int getViewHorizontalDragRange(View child) { + return Integer.MAX_VALUE; + } + } + + private class ShadowView extends View { + + private final int mForm = 0x0; + private final int mTo = 0x61; + private float mPercent = 0; + + public ShadowView(Context context) { + super(context); + } + + public void setPercent(float percent) { + mPercent = percent; + + invalidate(); + } + + @Override + protected void onDraw(Canvas c) { + c.drawARGB(MathUtils.lerp(mForm, mTo, mPercent), 0, 0, 0); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/SlidingLayout.java b/app/src/main/java/com/hippo/ehviewer/widget/SlidingLayout.java new file mode 100644 index 000000000..df8f601b9 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/SlidingLayout.java @@ -0,0 +1,598 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Build; +import android.support.v4.view.MotionEventCompat; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.CycleInterpolator; + +import com.hippo.ehviewer.util.AnimatorUtils; +import com.hippo.ehviewer.util.MathUtils; +import com.hippo.ehviewer.util.Ui; +import com.hippo.ehviewer.util.ViewUtils; +import com.hippo.ehviewer.util.ZInterpolator; + +public class SlidingLayout extends ViewGroup { + + private static final String TAG = SlidingLayout.class.getSimpleName(); + + /* + * There are three level, first is base level, second is reserved level, third is full screen level. + */ + + private static final int LEVEL_BASE = 0; + private static final int LEVEL_RESERVED = 1; + private static final int LEVEL_FULL_SCREEN = 2; + + private int mLevel = LEVEL_BASE; + + private View mChild; + private int mMaxWidth; + private int mSlop; + private float mShowPercent = 0.0f; + private boolean isBottom = true; + + private int mPaddingLeft; + private int mPaddingTop; + private int mPaddingRight; + private int mPaddingBottom; + + private ValueAnimator mToBaseLevelAnimate; + private ValueAnimator mToReservedLevelAnimate; + private ValueAnimator mToFullScreenLevelAnimate; + private SetYListener mToBaseLevelListener; + private SetYListener mToReservedLevelListener; + private SetYListener mToFullScreenLevelListener; + + private float mInitialMotionX; + private float mInitialMotionY; + private float mLastMotionY; + + private int mMinTop; + private int mMaxTop; + + /* + * ---------------------- mFullScreenTop + * + * ---------------------- m5Top + * + * + * ---------------------- m4Top + * + * ---------------------- mReservedTop + * + * ---------------------- m2Top + * + * + * ---------------------- m1Top + * + * ---------------------- mBaseTop + */ + + private int mBaseTop; + private int m1Top; + private int m2Top; + private int mReservedTop; + private int m4Top; + private int m5Top; + private int mFullScreenTop; + + private boolean mInHideAnimate = false; + + private OnChildHideListener mOnChildHideListener; + + private static final ZInterpolator ZINTERPOLATOR = new ZInterpolator(); + + public interface OnChildHideListener { + public void onChildHide(); + } + + + public SlidingLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public SlidingLayout(Context context, AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public SlidingLayout(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + private void init() { + mMaxWidth = Ui.dp2pix(480); + mSlop = Ui.dp2pix(0); + + mToBaseLevelAnimate = new ValueAnimator(); + mToBaseLevelListener = new SetYListener(); + mToBaseLevelAnimate.addUpdateListener(mToBaseLevelListener); + mToBaseLevelAnimate.setDuration(300); + mToBaseLevelAnimate.setInterpolator(ZINTERPOLATOR); + mToBaseLevelAnimate.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + mInHideAnimate = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + mLevel = LEVEL_BASE; + if (mChild.getVisibility() != View.INVISIBLE) + mChild.setVisibility(View.INVISIBLE); + + if (mOnChildHideListener != null) + mOnChildHideListener.onChildHide(); + + mInHideAnimate = false; + } + + @Override + public void onAnimationCancel(Animator animation) { + mInHideAnimate = false; + } + + @Override + public void onAnimationRepeat(Animator animation) { + // Empty + } + }); + + mToReservedLevelAnimate = new ValueAnimator(); + mToReservedLevelListener = new SetYListener(); + mToReservedLevelAnimate.addUpdateListener(mToReservedLevelListener); + mToReservedLevelAnimate.setDuration(300); + mToReservedLevelAnimate.setInterpolator(ZINTERPOLATOR); + mToReservedLevelAnimate.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + // Empty + } + + @Override + public void onAnimationEnd(Animator animation) { + mLevel = LEVEL_RESERVED; + } + + @Override + public void onAnimationCancel(Animator animation) { + // Empty + } + + @Override + public void onAnimationRepeat(Animator animation) { + // Empty + } + }); + + mToFullScreenLevelAnimate = new ValueAnimator(); + mToFullScreenLevelListener = new SetYListener(); + mToFullScreenLevelAnimate.addUpdateListener(mToFullScreenLevelListener); + mToFullScreenLevelAnimate.setDuration(300); + mToFullScreenLevelAnimate.setInterpolator(ZINTERPOLATOR); + mToFullScreenLevelAnimate.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + // Empty + } + + @Override + public void onAnimationEnd(Animator animation) { + mLevel = LEVEL_FULL_SCREEN; + } + + @Override + public void onAnimationCancel(Animator animation) { + // Empty + } + + @Override + public void onAnimationRepeat(Animator animation) { + // Empty + } + }); + } + + private class SetYListener implements ValueAnimator.AnimatorUpdateListener { + + private boolean mFirstShow = false; + private int mStartTop; + + public void reset() { + mFirstShow = true; + mStartTop = mChild.getTop(); + } + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int offsetY = (int) (float) (Float) animation.getAnimatedValue() - mChild.getTop(); + mChild.offsetTopAndBottom(offsetY); + updateShowPercent(); + + if (mFirstShow && Math.abs(mStartTop - mChild.getTop()) > 5) { + mFirstShow = false; + invalidate(); + } + } + } + + public void setOnChildHideListener(OnChildHideListener l) { + mOnChildHideListener = l; + } + + @Override + protected void onFinishInflate() { + final int childCount = getChildCount(); + if (childCount != 1) + throw new IllegalStateException("There must be only one child"); + + mChild = getChildAt(0); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) + throw new IllegalArgumentException( + "SlidingDrawerLayout must be measured with MeasureSpec.EXACTLY."); + + setMeasuredDimension(widthSize, heightSize); + + final View child = mChild; + if (child.getVisibility() != GONE) { + final int childWidthSpec = MeasureSpec.makeMeasureSpec( + Math.min(widthSize - getPaddingLeft() - getPaddingRight(), mMaxWidth), MeasureSpec.EXACTLY); + final int childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + child.measure(childWidthSpec, childHeightSpec); + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + final View child = mChild; + final int width = r - l; + final int height = b - t; + final int childWidth = child.getMeasuredWidth(); + final int childHeight = child.getMeasuredHeight(); + mPaddingLeft = getPaddingLeft(); + mPaddingTop = getPaddingTop(); + mPaddingRight = getPaddingRight(); + mPaddingBottom = getPaddingBottom(); + + // Set mReservedLength and mFullScreenLength + int heightWithoutPadding = height - mPaddingTop - mPaddingBottom; + int defaultReservedLength = Math.min(heightWithoutPadding - Ui.dp2pix(48), Ui.dp2pix(248)); + int defaultFullScreenLength = Math.max(heightWithoutPadding, defaultReservedLength); + int reservedLength = Math.min(defaultReservedLength, childHeight); + int fullScreenLength = Math.min(defaultFullScreenLength, childHeight); + + int showHeight = MathUtils.lerp(0, childHeight, mShowPercent); + + int childLeft = mPaddingLeft + (width - mPaddingLeft - mPaddingRight - childWidth) / 2; + + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; + final int maxGap = Ui.dp2pix(32); + final int brGap = Math.min(Math.abs((mBaseTop - mReservedTop) / 3), maxGap); + final int rfGap = Math.min(Math.abs((mReservedTop - mFullScreenTop) / 3), maxGap); + switch (vgrav) { + case Gravity.TOP: { + isBottom = false; + mMinTop = mPaddingTop; + mMaxTop = mMinTop + childHeight; + mBaseTop = mMinTop; + mReservedTop = mMinTop + reservedLength; + mFullScreenTop = mMinTop + fullScreenLength; + + m1Top = mBaseTop + brGap; + m2Top = mReservedTop - brGap; + m4Top = mReservedTop + rfGap; + m5Top = mFullScreenTop - rfGap; + + int childBottom = mMinTop + showHeight; + child.layout(childLeft, childBottom - childHeight, childLeft + childWidth, childBottom); + break; + } + default: + case Gravity.BOTTOM: { + isBottom = true; + mMaxTop = height - mPaddingBottom; + mMinTop = mMaxTop - childHeight; + mBaseTop = mMaxTop; + mReservedTop = mMaxTop - reservedLength; + mFullScreenTop = mMaxTop - fullScreenLength; + + m1Top = mBaseTop - brGap; + m2Top = mReservedTop + brGap; + m4Top = mReservedTop - rfGap; + m5Top = mFullScreenTop + rfGap; + + int childTop = mMaxTop - showHeight; + child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); + break; + } + } + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + boolean getTouchEvent = false; + final int action = MotionEventCompat.getActionMasked(ev); + final float x = ev.getX(); + final float y = ev.getY(); + final boolean isChildUnder = ViewUtils.isViewUnder(mChild, (int) x, (int) y); + + switch (action) { + case MotionEvent.ACTION_DOWN: + mInitialMotionX = x; + mInitialMotionY = y; + mLastMotionY = mInitialMotionY; + break; + case MotionEvent.ACTION_MOVE: + final float adx = Math.abs(x - mInitialMotionX); + final float ady = Math.abs(y - mInitialMotionY); + if (isChildUnder && adx > mSlop && ady > adx) + getTouchEvent = true; + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + break; + } + + return getTouchEvent; + } + + private void updateShowPercent() { + int top = mChild.getTop(); + mShowPercent = (isBottom ? (mMaxTop - top) : (top - mMinTop)) / + (float) mChild.getHeight(); + } + + private void cancelAnimator() { + if (mToBaseLevelAnimate.isRunning()) + mToBaseLevelAnimate.cancel(); + if (mToReservedLevelAnimate.isRunning()) + mToReservedLevelAnimate.cancel(); + if (mToFullScreenLevelAnimate.isRunning()) + mToFullScreenLevelAnimate.cancel(); + } + + @Override + @SuppressLint("ClickableViewAccessibility") + public boolean onTouchEvent(MotionEvent ev) { + final int action = MotionEventCompat.getActionMasked(ev); + final float x = ev.getX(); + final float y = ev.getY(); + boolean getTouchEvent = false; + + cancelAnimator(); + + int top; + switch (action) { + case MotionEvent.ACTION_DOWN: + mInitialMotionX = x; + mInitialMotionY = y; + mLastMotionY = mInitialMotionY; + if (isShowing()) + getTouchEvent = true; + break; + case MotionEvent.ACTION_MOVE: + final float dy = y - mLastMotionY; + mLastMotionY = y; + getTouchEvent = true; + + top = mChild.getTop(); + int newTop = top + (int) dy; + newTop = MathUtils.clamp(newTop, mMinTop, mMaxTop); + mChild.offsetTopAndBottom(newTop - top); + updateShowPercent(); + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + getTouchEvent = true; + + top = mChild.getTop(); + if (isBottom) { + switch (mLevel) { + default: + case LEVEL_BASE: + if (top < mFullScreenTop) + ; + else if (top < m4Top) + toFullScreenLevel(); + else if (top < m1Top) + toReservedLevel(false); + else + toBaseLevel(); + break; + case LEVEL_RESERVED: + if (top < mFullScreenTop) + ; + else if (top < m4Top) + toFullScreenLevel(); + else if (top < m2Top) + toReservedLevel(false); + else + toBaseLevel(); + break; + case LEVEL_FULL_SCREEN: + if (top < mFullScreenTop) + ; + else if (top < m5Top) + toFullScreenLevel(); + else if (top < m2Top) + toReservedLevel(false); + else + toBaseLevel(); + break; + } + } else { + // TODO + } + break; + } + return getTouchEvent; + } + + public void hide() { + if (mChild.getVisibility() != View.INVISIBLE) + mChild.setVisibility(View.INVISIBLE); + } + + public boolean isShowing() { + return mChild.getVisibility() == View.VISIBLE; + } + + public boolean isInHideAnimate() { + return mInHideAnimate; + } + + public void toReservedLevel(boolean withShake) { + cancelAnimator(); + // Show child + if (mChild.getVisibility() != View.VISIBLE) + mChild.setVisibility(View.VISIBLE); + + mToReservedLevelListener.reset(); + mToReservedLevelAnimate.setFloatValues(mChild.getTop(), mReservedTop); + + if (withShake) { + AnimatorSet bouncer = new AnimatorSet(); + Animator delayAnimator = AnimatorUtils.createDelayAnimator(200); + ValueAnimator shakeAnimator = ValueAnimator.ofFloat(mReservedTop, + isBottom ? mReservedTop - Ui.dp2pix(8) : mReservedTop + Ui.dp2pix(8)); + shakeAnimator.setDuration(500); + shakeAnimator.setInterpolator(new CycleInterpolator(1)); + shakeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int offsetY = (int) (float) (Float) animation.getAnimatedValue() - mChild.getTop(); + mChild.offsetTopAndBottom(offsetY); + updateShowPercent(); + } + }); + bouncer.play(mToReservedLevelAnimate).before(delayAnimator); + bouncer.play(delayAnimator).before(shakeAnimator); + bouncer.start(); + + } else { + mToReservedLevelAnimate.start(); + } + } + + public void toBaseLevel() { + cancelAnimator(); + // Show child + if (mChild.getVisibility() != View.VISIBLE) + mChild.setVisibility(View.VISIBLE); + + mToBaseLevelListener.reset(); + mToBaseLevelAnimate.setFloatValues(mChild.getTop(), mBaseTop); + mToBaseLevelAnimate.start(); + } + + public void toFullScreenLevel() { + cancelAnimator(); + // Show child + if (mChild.getVisibility() != View.VISIBLE) + mChild.setVisibility(View.VISIBLE); + + mToFullScreenLevelListener.reset(); + mToFullScreenLevelAnimate.setFloatValues(mChild.getTop(), mFullScreenTop); + mToFullScreenLevelAnimate.start(); + } + + @Override + protected ViewGroup.LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + } + + @Override + protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof LayoutParams + ? new LayoutParams((LayoutParams) p) + : new LayoutParams(p); + } + + @Override + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { + return p instanceof LayoutParams && super.checkLayoutParams(p); + } + + @Override + public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { + return new LayoutParams(getContext(), attrs); + } + + public static class LayoutParams extends ViewGroup.LayoutParams { + + private static final int[] LAYOUT_ATTRS = new int[] { + android.R.attr.layout_gravity + }; + + public int gravity = Gravity.BOTTOM; + + public LayoutParams(Context c, AttributeSet attrs) { + super(c, attrs); + + final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS); + this.gravity = a.getInt(0, Gravity.BOTTOM); + a.recycle(); + } + + public LayoutParams(int width, int height) { + super(width, height); + } + + public LayoutParams(int width, int height, int gravity) { + this(width, height); + this.gravity = gravity; + } + + public LayoutParams(LayoutParams source) { + super(source); + gravity = source.gravity; + } + + public LayoutParams(ViewGroup.LayoutParams source) { + super(source); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/SuggestionHelper.java b/app/src/main/java/com/hippo/ehviewer/widget/SuggestionHelper.java new file mode 100644 index 000000000..0b4bac549 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/SuggestionHelper.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.SearchRecentSuggestions; + +public class SuggestionHelper extends SearchRecentSuggestions { + + private static final String QUERY = "query"; + private static final String ORDER_BY = "date DESC"; + + private static final int MAX_HISTORY_COUNT = 250; + + private final List mQueryList = new ArrayList(); + + private static Map sInstances = new HashMap(); + + public static SuggestionHelper getInstance(Context context, String authority, int mode) { + SuggestionHelper s = sInstances.get(authority); + if (s == null) { + s = new SuggestionHelper(context, authority, mode); + sInstances.put(authority, s); + } + return s; + } + + private SuggestionHelper(Context context, String authority, int mode) { + super(context, authority, mode); + + Uri suggestionsUri = Uri.parse("content://" + authority + "/suggestions"); + + // Get all suggestion + ContentResolver cr = context.getContentResolver(); + Cursor cursor = cr.query(suggestionsUri, new String[]{QUERY}, null, null, ORDER_BY); + while(cursor.moveToNext()) + mQueryList.add(cursor.getString(cursor.getColumnIndex(QUERY))); + cursor.close(); + } + + public List getQueries() { + return mQueryList; + } + + @Override + public void saveRecentQuery(final String queryString, final String line2) { + super.saveRecentQuery(queryString, line2); + if (!mQueryList.contains(queryString)) + mQueryList.add(0, queryString); + truncateHistory(MAX_HISTORY_COUNT); + } + + @Override + public void clearHistory() { + super.clearHistory(); + truncateHistory(0); + } + + private void truncateHistory(int maxEntries) { + if (maxEntries == 0) { + mQueryList.clear(); + } else { + for (int i = mQueryList.size() - 1; i >= maxEntries; i--) { + mQueryList.remove(i); + } + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/SuggestionTextView.java b/app/src/main/java/com/hippo/ehviewer/widget/SuggestionTextView.java new file mode 100644 index 000000000..38ebc01c6 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/SuggestionTextView.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import java.util.List; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.inputmethod.InputMethodManager; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; + +import com.hippo.ehviewer.util.ViewUtils; + +public class SuggestionTextView extends PrefixEditText { + + private static final String TAG = SuggestionTextView.class.getSimpleName(); + + private List mAllSuggestions; + private ArrayAdapter mAdapte; + + private boolean mKeyboardIsShowing = true; + + public SuggestionTextView(Context context) { + super(context); + } + + public SuggestionTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SuggestionTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * We override this method to avoid an extra onItemClick being called on + * the drop-down's OnItemClickListener by + * {@link AutoCompleteTextView#onKeyUp(int, KeyEvent)} when an item is + * clicked with the trackball. + */ + @Override + public void performCompletion() { + } + + /** + * We override this method so that we can allow a threshold of zero, + * which ACTV does not. + */ + @Override + public boolean enoughToFilter() { + return true; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!isPopupShowing()) + showDropDown(); + + return super.onTouchEvent(event); + } + + @Override + public boolean onKeyPreIme(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + // special case for the back key, we do not even try to send it + // to the drop down list but instead, consume it immediately + if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { + KeyEvent.DispatcherState state = getKeyDispatcherState(); + if (state != null) { + state.startTracking(event, this); + } + return true; + } else if (event.getAction() == KeyEvent.ACTION_UP) { + KeyEvent.DispatcherState state = getKeyDispatcherState(); + if (state != null) { + state.handleUpEvent(event); + } + if (event.isTracking() && !event.isCanceled()) { + InputMethodManager imm = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null && mKeyboardIsShowing) { + imm.hideSoftInputFromWindow(getWindowToken(), 0); + mKeyboardIsShowing = false; + } else { + clearFocus(); + mKeyboardIsShowing = true; + } + return true; + } + } + } + return super.onKeyPreIme(keyCode, event); + } + + public void setSuggestionHelper(SuggestionHelper helper) { + if (helper != null) { + mAllSuggestions = helper.getQueries(); + mAdapte = new ArrayAdapter(ViewUtils.sContextThemeWrapper, + android.R.layout.simple_dropdown_item_1line, mAllSuggestions); + setAdapter(mAdapte); + } else { + mAllSuggestions = null; + mAdapte = null; + setAdapter(null); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/SuperButton.java b/app/src/main/java/com/hippo/ehviewer/widget/SuperButton.java new file mode 100644 index 000000000..b88ce0463 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/SuperButton.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.widget.Button; + +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.cardview.CardViewSalon; +import com.hippo.ehviewer.tile.TileSalon; +import com.hippo.ehviewer.util.Theme; + +public class SuperButton extends Button { + + public static final int BACKGROUND_TYPE_ROUND = 0x0; + public static final int BACKGROUND_TYPE_TILE = 0x1; + + public SuperButton(Context context) { + this(context, null); + init(context, null, 0); + } + + public SuperButton(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0); + } + + public SuperButton(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs, defStyle); + } + + public void init(Context context, AttributeSet attrs, int defStyle) { + int backgroundType = BACKGROUND_TYPE_ROUND; + int bgColor = context.getResources().getColor(R.color.background_light); + int boundColor = 0; + boolean withShadow = true; + + if (attrs != null) { + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.SuperButton, defStyle, 0); + backgroundType = a.getInt(R.styleable.SuperButton_backgroundType, backgroundType); + bgColor = a.getColor(R.styleable.SuperButton_backgroundColor, bgColor); + boundColor = a.getColor(R.styleable.SuperButton_boundColor, boundColor); + withShadow = a.getBoolean(R.styleable.SuperButton_withShadow, withShadow); + a.recycle(); + } + + switch (backgroundType) { + case BACKGROUND_TYPE_ROUND: + setRoundBackground(withShadow, bgColor, boundColor); + break; + case BACKGROUND_TYPE_TILE: + setTileBackground(bgColor); + break; + } + + } + + private void setStatefulRoundBackground(boolean withShadow, + int[][] stateSets, int[] bgColors, int[] boundColors) { + if (withShadow) { + CardViewSalon.reformWithShadow(this, stateSets, bgColors, boundColors, true); + } else { + CardViewSalon.reform(this, stateSets, bgColors, boundColors); + } + } + + private void setNotStatefulRoundBackground(boolean withShadow, + int bgColors, int boundColors) { + if (withShadow) { + CardViewSalon.reformWithShadow(this, bgColors, boundColors, true); + } else { + CardViewSalon.reform(this, bgColors, boundColors); + } + } + + public void setRoundBackground(boolean withShadow, int bgColor, int boundColor) { + setRoundBackground(withShadow, true, bgColor, boundColor); + } + + public void setRoundBackground(boolean withShadow, boolean stateful, int bgColor, int boundColor) { + if (stateful) + setStatefulRoundBackground(withShadow, new int[][]{ + new int[]{-android.R.attr.state_enabled}, + new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}, + new int[]{}}, new int[]{0xffcccccc, Theme.getDarkerColor(bgColor), bgColor}, + boundColor == 0 ? null : new int[]{0xffcccccc, Theme.getDarkerColor(boundColor), boundColor}); // TODO + else + setNotStatefulRoundBackground(withShadow, bgColor, boundColor); + } + + private void setTileBackground(int[][] stateSets, int[] bgColors) { + TileSalon.reform(this, stateSets, bgColors); + } + + public void setTileBackground(int bgColor) { + setTileBackground(new int[][]{ + new int[]{-android.R.attr.state_enabled}, + new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed}, + new int[]{}}, new int[]{0xffcccccc, Theme.getDarkerColor(bgColor), bgColor}); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/SuperSwipeRefreshLayout.java b/app/src/main/java/com/hippo/ehviewer/widget/SuperSwipeRefreshLayout.java new file mode 100644 index 000000000..ba8966014 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/SuperSwipeRefreshLayout.java @@ -0,0 +1,501 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.Animation; +import android.view.animation.Animation.AnimationListener; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Transformation; +import android.widget.AbsListView; + +public class SuperSwipeRefreshLayout extends ViewGroup { + private static final long RETURN_TO_ORIGINAL_POSITION_TIMEOUT = 300; + private static final float ACCELERATE_INTERPOLATION_FACTOR = 1.5f; + private static final float DECELERATE_INTERPOLATION_FACTOR = 2f; + private static final float PROGRESS_BAR_HEIGHT = 4; + private static final float MAX_SWIPE_DISTANCE_FACTOR = .6f; + private static final int REFRESH_TRIGGER_DISTANCE = 120; + + private final SwipeProgressBar mProgressBar; //the thing that shows progress is going + private boolean mIsProgressBarVisible = true; + private boolean mIsAgainstToChildPadding = false; + private View mTarget; //the content that gets pulled down + private int mOriginalOffsetTop; + private OnRefreshListener mListener; + private MotionEvent mDownEvent; + private int mFrom; + private boolean mRefreshing = false; + private final int mTouchSlop; + private float mDistanceToTriggerSync = -1; + private float mPrevY; + private final int mMediumAnimationDuration; + private float mFromPercentage = 0; + private float mCurrPercentage = 0; + private final int mProgressBarHeight; + private int mCurrentTargetOffsetTop; + // Target is returning to its start offset because it was cancelled or a + // refresh was triggered. + private boolean mReturningToStart; + private final DecelerateInterpolator mDecelerateInterpolator; + private final AccelerateInterpolator mAccelerateInterpolator; + private static final int[] LAYOUT_ATTRS = new int[] { + android.R.attr.enabled + }; + + private final Animation mAnimateToStartPosition = new Animation() { + @Override + public void applyTransformation(float interpolatedTime, Transformation t) { + int targetTop = 0; + if (mFrom != mOriginalOffsetTop) { + targetTop = (mFrom + (int)((mOriginalOffsetTop - mFrom) * interpolatedTime)); + } + int offset = targetTop - mTarget.getTop(); + final int currentTop = mTarget.getTop(); + if (offset + currentTop < 0) { + offset = 0 - currentTop; + } + setTargetOffsetTopAndBottom(offset); + } + }; + + private final Animation mShrinkTrigger = new Animation() { + @Override + public void applyTransformation(float interpolatedTime, Transformation t) { + float percent = mFromPercentage + ((0 - mFromPercentage) * interpolatedTime); + mProgressBar.setTriggerPercentage(percent); + } + }; + + private final AnimationListener mReturnToStartPositionListener = new BaseAnimationListener() { + @Override + public void onAnimationEnd(Animation animation) { + // Once the target content has returned to its start position, reset + // the target offset to 0 + mCurrentTargetOffsetTop = 0; + } + }; + + private final AnimationListener mShrinkAnimationListener = new BaseAnimationListener() { + @Override + public void onAnimationEnd(Animation animation) { + mCurrPercentage = 0; + } + }; + + private final Runnable mReturnToStartPosition = new Runnable() { + + @Override + public void run() { + mReturningToStart = true; + animateOffsetToStartPosition(mCurrentTargetOffsetTop + getPaddingTop(), + mReturnToStartPositionListener); + } + + }; + + // Cancel the refresh gesture and animate everything back to its original state. + private final Runnable mCancel = new Runnable() { + + @Override + public void run() { + mReturningToStart = true; + // Timeout fired since the user last moved their finger; animate the + // trigger to 0 and put the target back at its original position + if (mProgressBar != null) { + mFromPercentage = mCurrPercentage; + mShrinkTrigger.setDuration(mMediumAnimationDuration); + mShrinkTrigger.setAnimationListener(mShrinkAnimationListener); + mShrinkTrigger.reset(); + mShrinkTrigger.setInterpolator(mDecelerateInterpolator); + startAnimation(mShrinkTrigger); + } + animateOffsetToStartPosition(mCurrentTargetOffsetTop + getPaddingTop(), + mReturnToStartPositionListener); + } + + }; + + /** + * Simple constructor to use when creating a SwipeRefreshLayout from code. + * @param context + */ + public SuperSwipeRefreshLayout(Context context) { + this(context, null); + } + + /** + * Constructor that is called when inflating SwipeRefreshLayout from XML. + * @param context + * @param attrs + */ + public SuperSwipeRefreshLayout(Context context, AttributeSet attrs) { + super(context, attrs); + + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + + mMediumAnimationDuration = getResources().getInteger( + android.R.integer.config_mediumAnimTime); + + setWillNotDraw(false); + mProgressBar = new SwipeProgressBar(this); + final DisplayMetrics metrics = getResources().getDisplayMetrics(); + mProgressBarHeight = (int) (metrics.density * PROGRESS_BAR_HEIGHT); + mDecelerateInterpolator = new DecelerateInterpolator(DECELERATE_INTERPOLATION_FACTOR); + mAccelerateInterpolator = new AccelerateInterpolator(ACCELERATE_INTERPOLATION_FACTOR); + + final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); + setEnabled(a.getBoolean(0, true)); + a.recycle(); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + removeCallbacks(mCancel); + removeCallbacks(mReturnToStartPosition); + } + + @Override + public void onDetachedFromWindow() { + super.onDetachedFromWindow(); + removeCallbacks(mReturnToStartPosition); + removeCallbacks(mCancel); + } + + private void animateOffsetToStartPosition(int from, AnimationListener listener) { + mFrom = from; + mAnimateToStartPosition.reset(); + mAnimateToStartPosition.setDuration(mMediumAnimationDuration); + mAnimateToStartPosition.setAnimationListener(listener); + mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator); + mTarget.startAnimation(mAnimateToStartPosition); + } + + /** + * Set the listener to be notified when a refresh is triggered via the swipe + * gesture. + */ + public void setOnRefreshListener(OnRefreshListener listener) { + mListener = listener; + } + + private void setTriggerPercentage(float percent) { + if (percent == 0f) { + // No-op. A null trigger means it's uninitialized, and setting it to zero-percent + // means we're trying to reset state, so there's nothing to reset in this case. + mCurrPercentage = 0; + return; + } + mCurrPercentage = percent; + mProgressBar.setTriggerPercentage(percent); + } + + /** + * Notify the widget that refresh state has changed. Do not call this when + * refresh is triggered by a swipe gesture. + * + * @param refreshing Whether or not the view should show refresh progress. + */ + public void setRefreshing(boolean refreshing) { + if (mRefreshing != refreshing) { + ensureTarget(); + mCurrPercentage = 0; + mRefreshing = refreshing; + if (mRefreshing) { + mProgressBar.start(); + } else { + mProgressBar.stop(); + } + } + } + + /** + * Set the four colors used in the progress animation. The first color will + * also be the color of the bar that grows in response to a user swipe + * gesture. + * + * @param colorRes1 Color resource. + * @param colorRes2 Color resource. + * @param colorRes3 Color resource. + * @param colorRes4 Color resource. + */ + public void setColorScheme(int colorRes1, int colorRes2, int colorRes3, int colorRes4) { + ensureTarget(); + final Resources res = getResources(); + final int color1 = res.getColor(colorRes1); + final int color2 = res.getColor(colorRes2); + final int color3 = res.getColor(colorRes3); + final int color4 = res.getColor(colorRes4); + mProgressBar.setColorScheme(color1, color2, color3,color4); + } + + /** + * @return Whether the SwipeRefreshWidget is actively showing refresh + * progress. + */ + public boolean isRefreshing() { + return mRefreshing; + } + + private void ensureTarget() { + // Don't bother getting the parent height if the parent hasn't been laid out yet. + if (mTarget == null) { + if (getChildCount() > 1 && !isInEditMode()) { + throw new IllegalStateException( + "SwipeRefreshLayout can host only one direct child"); + } + mTarget = getChildAt(0); + mOriginalOffsetTop = mTarget.getTop() + getPaddingTop(); + } + if (mDistanceToTriggerSync == -1) { + if (getParent() != null && ((View)getParent()).getHeight() > 0) { + final DisplayMetrics metrics = getResources().getDisplayMetrics(); + mDistanceToTriggerSync = (int) Math.min( + ((View) getParent()) .getHeight() * MAX_SWIPE_DISTANCE_FACTOR, + REFRESH_TRIGGER_DISTANCE * metrics.density); + } + } + } + + /** + * Show or hide progress bar at same time + */ + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + mIsProgressBarVisible = enabled; + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + if (mIsProgressBarVisible) + mProgressBar.draw(canvas); + } + + /** + * If set true, refresh action will be triggered when + * the first item reaches the paddingTop of AbsListView. + * + * @param againstToChildPadding + */ + public void setAgainstToChildPadding(boolean againstToChildPadding) { + if (mIsAgainstToChildPadding != againstToChildPadding) { + mIsAgainstToChildPadding = againstToChildPadding; + setProgressBarBounds(); + } + } + + private void setProgressBarBounds() { + final int width = getMeasuredWidth(); + + if (mIsAgainstToChildPadding && getChildCount() != 0) { + final View child = getChildAt(0); + mProgressBar.setBounds(0, child.getPaddingTop(), width, mProgressBarHeight + child.getPaddingTop()); + } else { + mProgressBar.setBounds(0, 0, width, mProgressBarHeight); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (getChildCount() != 0) { + final int width = getMeasuredWidth(); + final int height = getMeasuredHeight(); + + final View child = getChildAt(0); + final int childLeft = getPaddingLeft(); + final int childTop = mCurrentTargetOffsetTop + getPaddingTop(); + final int childWidth = width - getPaddingLeft() - getPaddingRight(); + final int childHeight = height - getPaddingTop() - getPaddingBottom(); + child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); + } + + setProgressBarBounds(); + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (getChildCount() > 1 && !isInEditMode()) { + throw new IllegalStateException("SwipeRefreshLayout can host only one direct child"); + } + if (getChildCount() > 0) { + getChildAt(0).measure( + MeasureSpec.makeMeasureSpec( + getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), + MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec( + getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), + MeasureSpec.EXACTLY)); + } + } + + /** + * @return Whether it is possible for the child view of this layout to + * scroll up. Override this if the child view is a custom view. + */ + public boolean canChildScrollUp() { + if (mTarget instanceof AbsListView) { + final AbsListView absListView = (AbsListView) mTarget; + return absListView.getChildCount() > 0 + && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) + .getTop() < absListView.getPaddingTop()); + } else { + return mTarget.getScrollY() > 0; + } + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + ensureTarget(); + boolean handled = false; + if (mReturningToStart && ev.getAction() == MotionEvent.ACTION_DOWN) { + mReturningToStart = false; + } + if (isEnabled() && !mReturningToStart && !canChildScrollUp()) { + handled = onTouchEvent(ev); + } + return !handled ? super.onInterceptTouchEvent(ev) : handled; + } + + @Override + public void requestDisallowInterceptTouchEvent(boolean b) { + // Nope. + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + final int action = event.getAction(); + boolean handled = false; + switch (action) { + case MotionEvent.ACTION_DOWN: + mCurrPercentage = 0; + mDownEvent = MotionEvent.obtain(event); + mPrevY = mDownEvent.getY(); + break; + case MotionEvent.ACTION_MOVE: + if (mDownEvent != null && !mReturningToStart) { + final float eventY = event.getY(); + float yDiff = eventY - mDownEvent.getY(); + if (yDiff > mTouchSlop) { + // User velocity passed min velocity; trigger a refresh + if (yDiff > mDistanceToTriggerSync) { + // User movement passed distance; trigger a refresh + startRefresh(); + handled = true; + break; + } else { + // Just track the user's movement + setTriggerPercentage( + mAccelerateInterpolator.getInterpolation( + yDiff / mDistanceToTriggerSync)); + float offsetTop = yDiff; + if (mPrevY > eventY) { + offsetTop = yDiff - mTouchSlop; + } + updateContentOffsetTop((int) (offsetTop)); + if (mPrevY > eventY && (mTarget.getTop() < mTouchSlop)) { + // If the user puts the view back at the top, we + // don't need to. This shouldn't be considered + // cancelling the gesture as the user can restart from the top. + removeCallbacks(mCancel); + } else { + updatePositionTimeout(); + } + mPrevY = event.getY(); + handled = true; + } + } + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + if (mDownEvent != null) { + mDownEvent.recycle(); + mDownEvent = null; + } + break; + } + return handled; + } + + private void startRefresh() { + removeCallbacks(mCancel); + mReturnToStartPosition.run(); + setRefreshing(true); + mListener.onHeaderRefresh(); + } + + private void updateContentOffsetTop(int targetTop) { + final int currentTop = mTarget.getTop(); + if (targetTop > mDistanceToTriggerSync) { + targetTop = (int) mDistanceToTriggerSync; + } else if (targetTop < 0) { + targetTop = 0; + } + setTargetOffsetTopAndBottom(targetTop - currentTop); + } + + private void setTargetOffsetTopAndBottom(int offset) { + mTarget.offsetTopAndBottom(offset); + mCurrentTargetOffsetTop = mTarget.getTop(); + } + + private void updatePositionTimeout() { + removeCallbacks(mCancel); + postDelayed(mCancel, RETURN_TO_ORIGINAL_POSITION_TIMEOUT); + } + + /** + * Classes that wish to be notified when the swipe gesture correctly + * triggers a refresh should implement this interface. + */ + public interface OnRefreshListener { + public void onHeaderRefresh(); + } + + /** + * Simple AnimationListener to avoid having to implement unneeded methods in + * AnimationListeners. + */ + private class BaseAnimationListener implements AnimationListener { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/SwipeProgressBar.java b/app/src/main/java/com/hippo/ehviewer/widget/SwipeProgressBar.java new file mode 100644 index 000000000..221f2e1e2 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/SwipeProgressBar.java @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.support.v4.view.ViewCompat; +import android.view.View; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; + + +/** + * Custom progress bar that shows a cycle of colors as widening circles that + * overdraw each other. When finished, the bar is cleared from the inside out as + * the main cycle continues. Before running, this can also indicate how close + * the user is to triggering something (e.g. how far they need to pull down to + * trigger a refresh). + */ +final class SwipeProgressBar { + + // Default progress animation colors are grays. + private final static int COLOR1 = 0xB3000000; + private final static int COLOR2 = 0x80000000; + private final static int COLOR3 = 0x4d000000; + private final static int COLOR4 = 0x1a000000; + + // The duration of the animation cycle. + private static final int ANIMATION_DURATION_MS = 2000; + + // The duration of the animation to clear the bar. + private static final int FINISH_ANIMATION_DURATION_MS = 1000; + + // Interpolator for varying the speed of the animation. + private static final Interpolator INTERPOLATOR = BakedBezierInterpolator.getInstance(); + + private final Paint mPaint = new Paint(); + private final RectF mClipRect = new RectF(); + private float mTriggerPercentage; + private long mStartTime; + private long mFinishTime; + private boolean mRunning; + + // Colors used when rendering the animation, + private int mColor1; + private int mColor2; + private int mColor3; + private int mColor4; + private final View mParent; + + private final Rect mBounds = new Rect(); + + public SwipeProgressBar(View parent) { + mParent = parent; + mColor1 = COLOR1; + mColor2 = COLOR2; + mColor3 = COLOR3; + mColor4 = COLOR4; + } + + /** + * Set the four colors used in the progress animation. The first color will + * also be the color of the bar that grows in response to a user swipe + * gesture. + * + * @param color1 Integer representation of a color. + * @param color2 Integer representation of a color. + * @param color3 Integer representation of a color. + * @param color4 Integer representation of a color. + */ + void setColorScheme(int color1, int color2, int color3, int color4) { + mColor1 = color1; + mColor2 = color2; + mColor3 = color3; + mColor4 = color4; + } + + /** + * Update the progress the user has made toward triggering the swipe + * gesture. and use this value to update the percentage of the trigger that + * is shown. + */ + void setTriggerPercentage(float triggerPercentage) { + mTriggerPercentage = triggerPercentage; + mStartTime = 0; + ViewCompat.postInvalidateOnAnimation(mParent); + } + + /** + * Start showing the progress animation. + */ + void start() { + if (!mRunning) { + mTriggerPercentage = 0; + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + mRunning = true; + mParent.postInvalidate(); + } + } + + /** + * Stop showing the progress animation. + */ + void stop() { + if (mRunning) { + mTriggerPercentage = 0; + mFinishTime = AnimationUtils.currentAnimationTimeMillis(); + mRunning = false; + mParent.postInvalidate(); + } + } + + /** + * @return Return whether the progress animation is currently running. + */ + boolean isRunning() { + return mRunning || mFinishTime > 0; + } + + void draw(Canvas canvas) { + final int width = mBounds.width(); + final int height = mBounds.height(); + final int cx = width / 2; + final int cy = mBounds.top + height / 2; + boolean drawTriggerWhileFinishing = false; + int restoreCount = canvas.save(); + canvas.clipRect(mBounds); + + if (mRunning || (mFinishTime > 0)) { + long now = AnimationUtils.currentAnimationTimeMillis(); + long elapsed = (now - mStartTime) % ANIMATION_DURATION_MS; + long iterations = (now - mStartTime) / ANIMATION_DURATION_MS; + float rawProgress = (elapsed / (ANIMATION_DURATION_MS / 100f)); + + // If we're not running anymore, that means we're running through + // the finish animation. + if (!mRunning) { + // If the finish animation is done, don't draw anything, and + // don't repost. + if ((now - mFinishTime) >= FINISH_ANIMATION_DURATION_MS) { + mFinishTime = 0; + return; + } + + // Otherwise, use a 0 opacity alpha layer to clear the animation + // from the inside out. This layer will prevent the circles from + // drawing within its bounds. + long finishElapsed = (now - mFinishTime) % FINISH_ANIMATION_DURATION_MS; + float finishProgress = (finishElapsed / (FINISH_ANIMATION_DURATION_MS / 100f)); + float pct = (finishProgress / 100f); + // Radius of the circle is half of the screen. + float clearRadius = width / 2 * INTERPOLATOR.getInterpolation(pct); + mClipRect.set(cx - clearRadius, 0, cx + clearRadius, height); + canvas.saveLayerAlpha(mClipRect, 0, 0); + // Only draw the trigger if there is a space in the center of + // this refreshing view that needs to be filled in by the + // trigger. If the progress view is just still animating, let it + // continue animating. + drawTriggerWhileFinishing = true; + } + + // First fill in with the last color that would have finished drawing. + if (iterations == 0) { + canvas.drawColor(mColor1); + } else { + if (rawProgress >= 0 && rawProgress < 25) { + canvas.drawColor(mColor4); + } else if (rawProgress >= 25 && rawProgress < 50) { + canvas.drawColor(mColor1); + } else if (rawProgress >= 50 && rawProgress < 75) { + canvas.drawColor(mColor2); + } else { + canvas.drawColor(mColor3); + } + } + + // Then draw up to 4 overlapping concentric circles of varying radii, based on how far + // along we are in the cycle. + // progress 0-50 draw mColor2 + // progress 25-75 draw mColor3 + // progress 50-100 draw mColor4 + // progress 75 (wrap to 25) draw mColor1 + if ((rawProgress >= 0 && rawProgress <= 25)) { + float pct = (((rawProgress + 25) * 2) / 100f); + drawCircle(canvas, cx, cy, mColor1, pct); + } + if (rawProgress >= 0 && rawProgress <= 50) { + float pct = ((rawProgress * 2) / 100f); + drawCircle(canvas, cx, cy, mColor2, pct); + } + if (rawProgress >= 25 && rawProgress <= 75) { + float pct = (((rawProgress - 25) * 2) / 100f); + drawCircle(canvas, cx, cy, mColor3, pct); + } + if (rawProgress >= 50 && rawProgress <= 100) { + float pct = (((rawProgress - 50) * 2) / 100f); + drawCircle(canvas, cx, cy, mColor4, pct); + } + if ((rawProgress >= 75 && rawProgress <= 100)) { + float pct = (((rawProgress - 75) * 2) / 100f); + drawCircle(canvas, cx, cy, mColor1, pct); + } + if (mTriggerPercentage > 0 && drawTriggerWhileFinishing) { + // There is some portion of trigger to draw. Restore the canvas, + // then draw the trigger. Otherwise, the trigger does not appear + // until after the bar has finished animating and appears to + // just jump in at a larger width than expected. + canvas.restoreToCount(restoreCount); + restoreCount = canvas.save(); + canvas.clipRect(mBounds); + drawTrigger(canvas, cx, cy); + } + // Keep running until we finish out the last cycle. + ViewCompat.postInvalidateOnAnimation(mParent); + } else { + // Otherwise if we're in the middle of a trigger, draw that. + if (mTriggerPercentage > 0 && mTriggerPercentage <= 1.0) { + drawTrigger(canvas, cx, cy); + } + } + canvas.restoreToCount(restoreCount); + } + + private void drawTrigger(Canvas canvas, int cx, int cy) { + mPaint.setColor(mColor1); + canvas.drawCircle(cx, cy, cx * mTriggerPercentage, mPaint); + } + + /** + * Draws a circle centered in the view. + * + * @param canvas the canvas to draw on + * @param cx the center x coordinate + * @param cy the center y coordinate + * @param color the color to draw + * @param pct the percentage of the view that the circle should cover + */ + private void drawCircle(Canvas canvas, float cx, float cy, int color, float pct) { + mPaint.setColor(color); + canvas.save(); + canvas.translate(cx, cy); + float radiusScale = INTERPOLATOR.getInterpolation(pct); + canvas.scale(radiusScale, radiusScale); + canvas.drawCircle(0, 0, cx, mPaint); + canvas.restore(); + } + + /** + * Set the drawing bounds of this SwipeProgressBar. + */ + void setBounds(int left, int top, int right, int bottom) { + mBounds.left = left; + mBounds.top = top; + mBounds.right = right; + mBounds.bottom = bottom; + } + + /** + * A pre-baked bezier-curved interpolator for indeterminate progress animations. + */ + final static class BakedBezierInterpolator implements Interpolator { + private static final BakedBezierInterpolator INSTANCE = new BakedBezierInterpolator(); + + public final static BakedBezierInterpolator getInstance() { + return INSTANCE; + } + + /** + * Use getInstance instead of instantiating. + */ + private BakedBezierInterpolator() { + super(); + } + + /** + * Lookup table values. + * Generated using a Bezier curve from (0,0) to (1,1) with control points: + * P0 (0,0) + * P1 (0.4, 0) + * P2 (0.2, 1.0) + * P3 (1.0, 1.0) + * + * Values sampled with x at regular intervals between 0 and 1. + */ + private static final float[] VALUES = new float[] { + 0.0f, 0.0002f, 0.0009f, 0.0019f, 0.0036f, 0.0059f, 0.0086f, 0.0119f, 0.0157f, 0.0209f, + 0.0257f, 0.0321f, 0.0392f, 0.0469f, 0.0566f, 0.0656f, 0.0768f, 0.0887f, 0.1033f, 0.1186f, + 0.1349f, 0.1519f, 0.1696f, 0.1928f, 0.2121f, 0.237f, 0.2627f, 0.2892f, 0.3109f, 0.3386f, + 0.3667f, 0.3952f, 0.4241f, 0.4474f, 0.4766f, 0.5f, 0.5234f, 0.5468f, 0.5701f, 0.5933f, + 0.6134f, 0.6333f, 0.6531f, 0.6698f, 0.6891f, 0.7054f, 0.7214f, 0.7346f, 0.7502f, 0.763f, + 0.7756f, 0.7879f, 0.8f, 0.8107f, 0.8212f, 0.8326f, 0.8415f, 0.8503f, 0.8588f, 0.8672f, + 0.8754f, 0.8833f, 0.8911f, 0.8977f, 0.9041f, 0.9113f, 0.9165f, 0.9232f, 0.9281f, 0.9328f, + 0.9382f, 0.9434f, 0.9476f, 0.9518f, 0.9557f, 0.9596f, 0.9632f, 0.9662f, 0.9695f, 0.9722f, + 0.9753f, 0.9777f, 0.9805f, 0.9826f, 0.9847f, 0.9866f, 0.9884f, 0.9901f, 0.9917f, 0.9931f, + 0.9944f, 0.9955f, 0.9964f, 0.9973f, 0.9981f, 0.9986f, 0.9992f, 0.9995f, 0.9998f, 1.0f, 1.0f + }; + + private static final float STEP_SIZE = 1.0f / (VALUES.length - 1); + + @Override + public float getInterpolation(float input) { + if (input >= 1.0f) { + return 1.0f; + } + + if (input <= 0f) { + return 0f; + } + + int position = Math.min( + (int)(input * (VALUES.length - 1)), + VALUES.length - 2); + + float quantized = position * STEP_SIZE; + float difference = input - quantized; + float weight = difference / STEP_SIZE; + + return VALUES[position] + weight * (VALUES[position + 1] - VALUES[position]); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hippo/ehviewer/widget/TagListView.java b/app/src/main/java/com/hippo/ehviewer/widget/TagListView.java new file mode 100644 index 000000000..faa272b12 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/TagListView.java @@ -0,0 +1,833 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import java.util.ArrayList; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.TypeEvaluator; +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.os.Build; +import android.support.v4.util.LongSparseArray; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewTreeObserver; +import android.widget.AbsListView; +import android.widget.AdapterView; +import android.widget.ListView; + +import com.hippo.ehviewer.util.ViewUtils; + +/** + * The dynamic listview is an extension of listview that supports cell dragging + * and swapping. + * + * This layout is in charge of positioning the hover cell in the correct location + * on the screen in response to user touch events. It uses the position of the + * hover cell to determine when two cells should be swapped. If two cells should + * be swapped, all the corresponding data set and layout changes are handled here. + * + * If no cell is selected, all the touch events are passed down to the listview + * and behave normally. If one of the items in the listview experiences a + * long press event, the contents of its current visible state are captured as + * a bitmap and its visibility is set to INVISIBLE. A hover cell is then created and + * added to this layout as an overlaying BitmapDrawable above the listview. Once the + * hover cell is translated some distance to signify an item swap, a data set change + * accompanied by animation takes place. When the user releases the hover cell, + * it animates into its corresponding position in the listview. + * + * When the hover cell is either above or below the bounds of the listview, this + * listview also scrolls on its own so as to reveal additional content. + */ +public class TagListView extends ListView { + + @SuppressWarnings("unused") + private static final String TAG = TagListView.class.getSimpleName(); + + private final int SMOOTH_SCROLL_AMOUNT_AT_EDGE = 15; + private final int MOVE_DURATION = 150; + private final int SENSITY = 5; + + //public ArrayList mItemList; + + private int mLastEventX = -1; + private int mLastEventY = -1; + + private int mDownY = -1; + private int mDownX = -1; + + private int mTotalOffset = 0; + + private boolean mCellIsMobile = false; + private boolean mIsMobileScrolling = false; + private int mSmoothScrollAmountAtEdge = 0; + + private final int INVALID_ID = -1; + private long mAboveItemId = INVALID_ID; + private long mMobileItemId = INVALID_ID; + private long mBelowItemId = INVALID_ID; + + private int mStableItemCount = 0; + + private BitmapDrawable mHoverCell; + private BitmapDrawable mDeleteCell; + private BitmapDrawable mModifyCell; + private Rect mHoverCellCurrentBounds; + private Rect mHoverCellOriginalBounds; + + private boolean mIsDelete = false; + private boolean mIsModify = false; + + private final int INVALID_POINTER_ID = -1; + private int mActivePointerId = INVALID_POINTER_ID; + + private boolean mIsWaitingForScrollFinish = false; + private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE; + + private OnModifyListener mModifylistener = null; + private OnMoveLister mMovelistener = null; + private OnDeleteListener mOnDeleteListener = null; + private OnSwapListener mOnSwapListener = null; + + public TagListView(Context context) { + super(context); + init(context); + } + + public TagListView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context); + } + + public TagListView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public void init(Context context) { + setOnItemLongClickListener(mOnItemLongClickListener); + setOnScrollListener(mScrollListener); + DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + mSmoothScrollAmountAtEdge = (int)(SMOOTH_SCROLL_AMOUNT_AT_EDGE / metrics.density); + } + + /** + * Listens for long clicks on any items in the listview. When a cell has + * been selected, the hover cell is created and set up. + */ + private final AdapterView.OnItemLongClickListener mOnItemLongClickListener = + new AdapterView.OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView arg0, View arg1, int pos, long id) { + if (pos < mStableItemCount) + return true; + + if (mMovelistener != null) + mMovelistener.onMoveStart(); + + mTotalOffset = 0; + + int position = pointToPosition(mDownX, mDownY); + int itemNum = position - getFirstVisiblePosition(); + + View selectedView = getChildAt(itemNum); + mMobileItemId = getAdapter().getItemId(position); + mHoverCell = getAndAddHoverView(selectedView); + mDeleteCell = getDeleteView(selectedView); + mModifyCell = getModifyView(selectedView); + selectedView.setVisibility(INVISIBLE); + + mCellIsMobile = true; + + updateNeighborViewsForID(mMobileItemId); + + return true; + } + }; + + /** + * Creates the hover cell with the appropriate bitmap and of appropriate + * size. The hover cell's BitmapDrawable is drawn on top of the bitmap every + * single time an invalidate call is made. + */ + private BitmapDrawable getAndAddHoverView(View v) { + + int w = v.getWidth(); + int h = v.getHeight(); + int top = v.getTop(); + int left = v.getLeft(); + + Bitmap b = getHoverBitmap(v); + + BitmapDrawable drawable = new BitmapDrawable(getResources(), b); + + mHoverCellOriginalBounds = new Rect(left, top, left + w, top + h); + mHoverCellCurrentBounds = new Rect(mHoverCellOriginalBounds); + + drawable.setBounds(mHoverCellCurrentBounds); + + return drawable; + } + + private BitmapDrawable getDeleteView(View v) { + Bitmap b = getDeleteBitmap(v); + BitmapDrawable drawable = new BitmapDrawable(getResources(), b); + drawable.setBounds(mHoverCellCurrentBounds); + + return drawable; + } + + private Bitmap getDeleteBitmap(View v) { + Bitmap bitmap = ViewUtils.getBitmapFromView(v); + Canvas can = new Canvas(bitmap); + + Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + Paint paint = new Paint(); + paint.setColor(0x8fff0000); + can.drawRect(rect, paint); + + return bitmap; + } + + private BitmapDrawable getModifyView(View v) { + Bitmap b = getModifyBitmap(v); + BitmapDrawable drawable = new BitmapDrawable(getResources(), b); + drawable.setBounds(mHoverCellCurrentBounds); + + return drawable; + } + + private Bitmap getModifyBitmap(View v) { + Bitmap bitmap = ViewUtils.getBitmapFromView(v); + Canvas can = new Canvas(bitmap); + + Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + Paint paint = new Paint(); + paint.setColor(0x8faa00ff); + can.drawRect(rect, paint); + + return bitmap; + } + + /** Draws a black border over the screenshot of the view passed in. */ + private Bitmap getHoverBitmap(View v) { + Bitmap bitmap = ViewUtils.getBitmapFromView(v); + Canvas can = new Canvas(bitmap); + + Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + Paint paint = new Paint(); + paint.setColor(0x8f00aadd); + can.drawRect(rect, paint); + + return bitmap; + } + + /** + * Stores a reference to the views above and below the item currently + * corresponding to the hover cell. It is important to note that if this + * item is either at the top or bottom of the list, mAboveItemId or mBelowItemId + * may be invalid. + */ + private void updateNeighborViewsForID(long itemID) { + int position = getPositionForID(itemID); + TagsAdapter adapter = ((TagsAdapter)getAdapter()); + mAboveItemId = adapter.getItemId(position - 1); + mBelowItemId = adapter.getItemId(position + 1); + } + + /** Retrieves the view in the list corresponding to itemID */ + public View getViewForID (long itemID) { + int firstVisiblePosition = getFirstVisiblePosition(); + TagsAdapter adapter = ((TagsAdapter)getAdapter()); + for(int i = 0; i < getChildCount(); i++) { + View v = getChildAt(i); + int position = firstVisiblePosition + i; + long id = adapter.getItemId(position); + if (id == itemID) { + return v; + } + } + return null; + } + + /** Retrieves the position in the list corresponding to itemID */ + public int getPositionForID (long itemID) { + View v = getViewForID(itemID); + if (v == null) { + return -1; + } else { + return getPositionForView(v); + } + } + + public void setStableItemCount(int stableItemCount) { + mStableItemCount = stableItemCount; + } + + public interface OnModifyListener { + public void onModify(int position); + } + + public void setOnModifyListener(OnModifyListener listener) { + this.mModifylistener = listener; + } + + public interface OnMoveLister { + public void onMoveStart(); + public void onMoveOver(); + } + + public void setOnMoveLister(OnMoveLister listener) { + this.mMovelistener = listener; + } + + public interface OnDeleteListener { + public void onDelete(int position); + } + + public void setOnDeleteListener(OnDeleteListener listener) { + mOnDeleteListener = listener; + } + + public interface OnSwapListener { + public void onSwap(int positionOne, int positionTwo); + } + + public void setOnSwapListener(OnSwapListener listener) { + mOnSwapListener = listener; + } + + /** + * dispatchDraw gets invoked when all the child views are about to be drawn. + * By overriding this method, the hover cell (BitmapDrawable) can be drawn + * over the listview's items whenever the listview is redrawn. + */ + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + if (mIsDelete && mDeleteCell != null) { + mDeleteCell.setBounds(mHoverCellCurrentBounds); + mDeleteCell.draw(canvas); + } else if (mIsModify && mModifyCell != null){ + mModifyCell.setBounds(mHoverCellCurrentBounds); + mModifyCell.draw(canvas); + } else if (mHoverCell != null) { + mHoverCell.setBounds(mHoverCellCurrentBounds); + mHoverCell.draw(canvas); + } + } + + @Override + @SuppressLint("ClickableViewAccessibility") + public boolean onTouchEvent (MotionEvent event) { + switch (event.getAction() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + mDownX = (int)event.getX(); + mDownY = (int)event.getY(); + mActivePointerId = event.getPointerId(0); + break; + case MotionEvent.ACTION_MOVE: + if (mActivePointerId == INVALID_POINTER_ID) { + break; + } + + int pointerIndex = event.findPointerIndex(mActivePointerId); + + mLastEventX = (int) event.getX(pointerIndex); + mLastEventY = (int) event.getY(pointerIndex); + int deltaX = mLastEventX - mDownX; + int deltaY = mLastEventY - mDownY; + + if (mCellIsMobile) { + if (deltaX > mHoverCellOriginalBounds.width() / SENSITY) + mIsDelete = true; + else + mIsDelete = false; + + if (deltaX < -mHoverCellOriginalBounds.width() / SENSITY) + mIsModify = true; + else + mIsModify = false; + + mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left + deltaX, + mHoverCellOriginalBounds.top + deltaY + mTotalOffset); + //mHoverCell.setBounds(mHoverCellCurrentBounds); + invalidate(); + + handleCellSwitch(); + + mIsMobileScrolling = false; + handleMobileCellScroll(); + + return false; + } + break; + case MotionEvent.ACTION_UP: + if (mMovelistener != null) + mMovelistener.onMoveOver(); + touchEventsEnded(); + break; + case MotionEvent.ACTION_CANCEL: + if (mMovelistener != null) + mMovelistener.onMoveOver(); + touchEventsCancelled(); + break; + case MotionEvent.ACTION_POINTER_UP: + /* If a multitouch event took place and the original touch dictating + * the movement of the hover cell has ended, then the dragging event + * ends and the hover cell is animated to its corresponding position + * in the listview. */ + pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> + MotionEvent.ACTION_POINTER_INDEX_SHIFT; + final int pointerId = event.getPointerId(pointerIndex); + if (pointerId == mActivePointerId) { + if (mMovelistener != null) + mMovelistener.onMoveOver(); + touchEventsEnded(); + } + break; + default: + break; + } + + return super.onTouchEvent(event) | mCellIsMobile; + } + + /** + * This method determines whether the hover cell has been shifted far enough + * to invoke a cell swap. If so, then the respective cell swap candidate is + * determined and the data set is changed. Upon posting a notification of the + * data set change, a layout is invoked to place the cells in the right place. + * Using a ViewTreeObserver and a corresponding OnPreDrawListener, we can + * offset the cell being swapped to where it previously was and then animate it to + * its new position. + */ + private void handleCellSwitch() { + final int deltaY = mLastEventY - mDownY; + int deltaYTotal = mHoverCellOriginalBounds.top + mTotalOffset + deltaY; + + View belowView = getViewForID(mBelowItemId); + View mobileView = getViewForID(mMobileItemId); + View aboveView = getPositionForID(mAboveItemId) < mStableItemCount ? null : getViewForID(mAboveItemId); + + boolean isBelow = (belowView != null) && (deltaYTotal > belowView.getTop()); + boolean isAbove = (aboveView != null) && (deltaYTotal < aboveView.getTop()); + + if (isBelow || isAbove) { + + final long switchItemID = isBelow ? mBelowItemId : mAboveItemId; + View switchView = isBelow ? belowView : aboveView; + final int originalItem = getPositionForView(mobileView); + + if (switchView == null) { + updateNeighborViewsForID(mMobileItemId); + return; + } + + if (mOnSwapListener != null) { + mOnSwapListener.onSwap(originalItem, getPositionForView(switchView)); + } + + mDownY = mLastEventY; + + final int switchViewStartTop = switchView.getTop(); + + // TODO where does the different come from + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + mobileView.setVisibility(View.VISIBLE); + switchView.setVisibility(View.INVISIBLE); + } else { + mobileView.setVisibility(View.INVISIBLE); + switchView.setVisibility(View.VISIBLE); + } + + updateNeighborViewsForID(mMobileItemId); + + final ViewTreeObserver observer = getViewTreeObserver(); + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + observer.removeOnPreDrawListener(this); + + View switchView = getViewForID(switchItemID); + + mTotalOffset += deltaY; + + int switchViewNewTop = switchView.getTop(); + int delta = switchViewStartTop - switchViewNewTop; + + switchView.setTranslationY(delta); + + ObjectAnimator animator = ObjectAnimator.ofFloat(switchView, + View.TRANSLATION_Y, 0); + animator.setDuration(MOVE_DURATION); + animator.start(); + + return true; + } + }); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked", "unused" }) + private void swapElements(ArrayList arrayList, int indexOne, int indexTwo) { + Object temp = arrayList.get(indexOne); + arrayList.set(indexOne, arrayList.get(indexTwo)); + arrayList.set(indexTwo, temp); + } + + /** + * Resets all the appropriate fields to a default state while also animating + * the hover cell back to its correct location. + */ + private void touchEventsEnded () { + final View mobileView = getViewForID(mMobileItemId); + if (mCellIsMobile|| mIsWaitingForScrollFinish) { + mCellIsMobile = false; + mIsWaitingForScrollFinish = false; + mIsMobileScrolling = false; + mActivePointerId = INVALID_POINTER_ID; + + if (mIsDelete) { + animateRemoval(mobileView); + mIsDelete = false; + mAboveItemId = INVALID_ID; + mMobileItemId = INVALID_ID; + mBelowItemId = INVALID_ID; + mobileView.setVisibility(VISIBLE); + mHoverCell = null; + mDeleteCell = null; + mModifyCell = null; + invalidate(); + return; + } + + if (mIsModify) { + mIsModify = false; + if (mModifylistener != null) { + mModifylistener.onModify(getPositionForID(mMobileItemId)); + } + } + + // If the autoscroller has not completed scrolling, we need to wait for it to + // finish in order to determine the final location of where the hover cell + // should be animated to. + if (mScrollState != OnScrollListener.SCROLL_STATE_IDLE) { + mIsWaitingForScrollFinish = true; + return; + } + + mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left, mobileView.getTop()); + + ObjectAnimator hoverViewAnimator = ObjectAnimator.ofObject(mHoverCell, "bounds", + sBoundEvaluator, mHoverCellCurrentBounds); + hoverViewAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + invalidate(); + } + }); + hoverViewAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + setEnabled(false); + } + + @Override + public void onAnimationEnd(Animator animation) { + mAboveItemId = INVALID_ID; + mMobileItemId = INVALID_ID; + mBelowItemId = INVALID_ID; + mobileView.setVisibility(VISIBLE); + mHoverCell = null; + mDeleteCell = null; + mModifyCell = null; + setEnabled(true); + invalidate(); + } + }); + hoverViewAnimator.start(); + } else { + touchEventsCancelled(); + } + } + + private void animateRemoval(View viewToRemove) { + int firstVisiblePosition = getFirstVisiblePosition(); + final TagsAdapter mAdapter = (TagsAdapter)getAdapter(); + final LongSparseArray mItemIdTopMap = new LongSparseArray(); + + for (int i = 0; i < getChildCount(); ++i) { + View child = getChildAt(i); + if (child != viewToRemove) { + int position = firstVisiblePosition + i; + long itemId = mAdapter.getItemId(position); + mItemIdTopMap.put(itemId, child.getTop()); + } + } + // Delete the item from the adapter and tag + int position = getPositionForView(viewToRemove); + if (mOnDeleteListener != null) + mOnDeleteListener.onDelete(position); + + final ViewTreeObserver observer = getViewTreeObserver(); + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + observer.removeOnPreDrawListener(this); + boolean firstAnimation = true; + int firstVisiblePosition = getFirstVisiblePosition(); + for (int i = 0; i < getChildCount(); ++i) { + final View child = getChildAt(i); + int position = firstVisiblePosition + i; + long itemId = mAdapter.getItemId(position); + Integer startTop = mItemIdTopMap.get(itemId); + int top = child.getTop(); + if (startTop != null) { + if (startTop != top) { + int delta = startTop - top; + child.setTranslationY(delta); + ObjectAnimator animator = ObjectAnimator.ofFloat(child, + View.TRANSLATION_Y, 0); + animator.setDuration(MOVE_DURATION); + animator.start(); + if (firstAnimation) { + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + setEnabled(false); + } + @Override + public void onAnimationEnd(Animator animation) { + setEnabled(true); + invalidate(); + } + }); + firstAnimation = false; + } + } + } else { + // Animate new views along with the others. The catch is that they did not + // exist in the start state, so we must calculate their starting position + // based on neighboring views. + int childHeight = child.getHeight() + getDividerHeight(); + startTop = top + (i > 0 ? childHeight : -childHeight); + int delta = startTop - top; + child.setTranslationY(delta); + + ObjectAnimator animator = ObjectAnimator.ofFloat(child, + View.TRANSLATION_Y, 0); + animator.setDuration(MOVE_DURATION); + animator.start(); + if (firstAnimation) { + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + setEnabled(false); + } + @Override + public void onAnimationEnd(Animator animation) { + setEnabled(true); + invalidate(); + } + }); + firstAnimation = false; + } + } + } + mItemIdTopMap.clear(); + return true; + } + }); + } + + + + /** + * Resets all the appropriate fields to a default state. + */ + private void touchEventsCancelled () { + View mobileView = getViewForID(mMobileItemId); + if (mCellIsMobile) { + mAboveItemId = INVALID_ID; + mMobileItemId = INVALID_ID; + mBelowItemId = INVALID_ID; + mobileView.setVisibility(VISIBLE); + mHoverCell = null; + mDeleteCell = null; + mModifyCell = null; + invalidate(); + } + mCellIsMobile = false; + mIsDelete = false; + mIsModify = false; + mIsMobileScrolling = false; + mActivePointerId = INVALID_POINTER_ID; + } + + /** + * This TypeEvaluator is used to animate the BitmapDrawable back to its + * final location when the user lifts his finger by modifying the + * BitmapDrawable's bounds. + */ + private final static TypeEvaluator sBoundEvaluator = new TypeEvaluator() { + @Override + public Rect evaluate(float fraction, Rect startValue, Rect endValue) { + return new Rect(interpolate(startValue.left, endValue.left, fraction), + interpolate(startValue.top, endValue.top, fraction), + interpolate(startValue.right, endValue.right, fraction), + interpolate(startValue.bottom, endValue.bottom, fraction)); + } + + public int interpolate(int start, int end, float fraction) { + return (int)(start + fraction * (end - start)); + } + }; + + /** + * Determines whether this listview is in a scrolling state invoked + * by the fact that the hover cell is out of the bounds of the listview; + */ + private void handleMobileCellScroll() { + mIsMobileScrolling = handleMobileCellScroll(mHoverCellCurrentBounds); + } + + /** + * This method is in charge of determining if the hover cell is above + * or below the bounds of the listview. If so, the listview does an appropriate + * upward or downward smooth scroll so as to reveal new items. + */ + public boolean handleMobileCellScroll(Rect r) { + int offset = computeVerticalScrollOffset(); + int height = getHeight(); + int extent = computeVerticalScrollExtent(); + int range = computeVerticalScrollRange(); + int hoverViewTop = r.top; + int hoverHeight = r.height(); + + if (hoverViewTop <= 0 && offset > 0) { + smoothScrollBy(-mSmoothScrollAmountAtEdge, 0); + return true; + } + + if (hoverViewTop + hoverHeight >= height && (offset + extent) < range) { + smoothScrollBy(mSmoothScrollAmountAtEdge, 0); + return true; + } + + return false; + } + + /** + * This scroll listener is added to the listview in order to handle cell swapping + * when the cell is either at the top or bottom edge of the listview. If the hover + * cell is at either edge of the listview, the listview will begin scrolling. As + * scrolling takes place, the listview continuously checks if new cells became visible + * and determines whether they are potential candidates for a cell swap. + */ + private final AbsListView.OnScrollListener mScrollListener = new AbsListView.OnScrollListener () { + + private int mPreviousFirstVisibleItem = -1; + private int mPreviousVisibleItemCount = -1; + private int mCurrentFirstVisibleItem; + private int mCurrentVisibleItemCount; + private int mCurrentScrollState; + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, + int totalItemCount) { + mCurrentFirstVisibleItem = firstVisibleItem; + mCurrentVisibleItemCount = visibleItemCount; + + mPreviousFirstVisibleItem = (mPreviousFirstVisibleItem == -1) ? mCurrentFirstVisibleItem + : mPreviousFirstVisibleItem; + mPreviousVisibleItemCount = (mPreviousVisibleItemCount == -1) ? mCurrentVisibleItemCount + : mPreviousVisibleItemCount; + + checkAndHandleFirstVisibleCellChange(); + checkAndHandleLastVisibleCellChange(); + + mPreviousFirstVisibleItem = mCurrentFirstVisibleItem; + mPreviousVisibleItemCount = mCurrentVisibleItemCount; + } + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + mCurrentScrollState = scrollState; + mScrollState = scrollState; + isScrollCompleted(); + } + + /** + * This method is in charge of invoking 1 of 2 actions. Firstly, if the listview + * is in a state of scrolling invoked by the hover cell being outside the bounds + * of the listview, then this scrolling event is continued. Secondly, if the hover + * cell has already been released, this invokes the animation for the hover cell + * to return to its correct position after the listview has entered an idle scroll + * state. + */ + private void isScrollCompleted() { + if (mCurrentVisibleItemCount > 0 && mCurrentScrollState == SCROLL_STATE_IDLE) { + if (mCellIsMobile && mIsMobileScrolling) { + handleMobileCellScroll(); + } else if (mIsWaitingForScrollFinish) { + touchEventsEnded(); + } + } + } + + /** + * Determines if the listview scrolled up enough to reveal a new cell at the + * top of the list. If so, then the appropriate parameters are updated. + */ + public void checkAndHandleFirstVisibleCellChange() { + if (mCurrentFirstVisibleItem != mPreviousFirstVisibleItem) { + if (mCellIsMobile && mMobileItemId != INVALID_ID) { + updateNeighborViewsForID(mMobileItemId); + handleCellSwitch(); + } + } + } + + /** + * Determines if the listview scrolled down enough to reveal a new cell at the + * bottom of the list. If so, then the appropriate parameters are updated. + */ + public void checkAndHandleLastVisibleCellChange() { + int currentLastVisibleItem = mCurrentFirstVisibleItem + mCurrentVisibleItemCount; + int previousLastVisibleItem = mPreviousFirstVisibleItem + mPreviousVisibleItemCount; + if (currentLastVisibleItem != previousLastVisibleItem) { + if (mCellIsMobile && mMobileItemId != INVALID_ID) { + updateNeighborViewsForID(mMobileItemId); + handleCellSwitch(); + } + } + } + }; +} \ No newline at end of file diff --git a/app/src/main/java/com/hippo/ehviewer/widget/TagsAdapter.java b/app/src/main/java/com/hippo/ehviewer/widget/TagsAdapter.java new file mode 100644 index 000000000..37fb33296 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/TagsAdapter.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.content.Context; +import android.widget.ArrayAdapter; + +import java.util.HashMap; +import java.util.List; + +public class TagsAdapter extends ArrayAdapter { + + final int INVALID_ID = -1; + int lastId = -1; + + HashMap mIdMap = new HashMap(); + + private OnDataSetChangedListener mListener; + + public TagsAdapter(Context context, int textViewResourceId, List objects) { + super(context, textViewResourceId, objects); + for (int i = 0; i < objects.size(); ++i) { + mIdMap.put(objects.get(i), ++lastId); + } + } + + @Override + public long getItemId(int position) { + if (position < 0 || position >= mIdMap.size()) { + return INVALID_ID; + } + String item = getItem(position); + return mIdMap.get(item); + } + + @Override + public boolean hasStableIds() { + return true; + } + + public void set(String oldKey, String newKey) { + mIdMap.put(newKey, mIdMap.remove(oldKey)); + } + + @Override + public void remove(String key) { + super.remove(key); + mIdMap.remove(key); + } + + public void addId(String item) { + mIdMap.put(item, ++lastId); + } + + @Override + public void notifyDataSetChanged() { + super.notifyDataSetChanged(); + if (mListener != null) + mListener.OnDataSetChanged(); + } + + public void setOnDataSetChangedListener(OnDataSetChangedListener l) { + mListener = l; + } + + public interface OnDataSetChangedListener { + public void OnDataSetChanged(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/TextClock.java b/app/src/main/java/com/hippo/ehviewer/widget/TextClock.java new file mode 100644 index 000000000..42b41f236 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/TextClock.java @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import java.util.Calendar; +import java.util.TimeZone; + +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.os.SystemClock; +import android.provider.Settings; +import android.text.format.DateFormat; +import android.util.AttributeSet; +import android.view.ViewDebug.ExportedProperty; +import android.widget.TextView; + +import com.hippo.ehviewer.util.DateUtils; + +public class TextClock extends TextView { + + public static final CharSequence DEFAULT_FORMAT_12_HOUR = "hh:mm a"; + public static final CharSequence DEFAULT_FORMAT_24_HOUR; + + static { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) + DEFAULT_FORMAT_24_HOUR = "kk:mm"; + else + DEFAULT_FORMAT_24_HOUR = "HH:mm"; + } + + private CharSequence mFormat12; + private CharSequence mFormat24; + + @ExportedProperty + private CharSequence mFormat; + @ExportedProperty + private boolean mHasSeconds; + + private boolean mAttached; + + private Calendar mTime; + private String mTimeZone; + + private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + chooseFormat(); + onTimeChanged(); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + chooseFormat(); + onTimeChanged(); + } + }; + + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) { + final String timeZone = intent.getStringExtra("time-zone"); + createTime(timeZone); + } + onTimeChanged(); + } + }; + + private final Runnable mTicker = new Runnable() { + @Override + public void run() { + onTimeChanged(); + + long now = SystemClock.uptimeMillis(); + long next = now + (1000 - now % 1000); + + getHandler().postAtTime(mTicker, next); + } + }; + + public TextClock(Context context) { + this(context, null); + } + + public TextClock(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TextClock(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + mFormat12 = DEFAULT_FORMAT_12_HOUR; + mFormat24 = DEFAULT_FORMAT_24_HOUR; + mTimeZone = TimeZone.getDefault().getID(); + + init(); + } + + private void init() { + createTime(mTimeZone); + // Wait until onAttachedToWindow() to handle the ticker + chooseFormat(false); + } + + private void createTime(String timeZone) { + if (timeZone != null) { + mTime = Calendar.getInstance(TimeZone.getTimeZone(timeZone)); + } else { + mTime = Calendar.getInstance(); + } + } + + public CharSequence getFormat12Hour() { + return mFormat12; + } + + public void setFormat12Hour(CharSequence format) { + mFormat12 = format; + + chooseFormat(); + onTimeChanged(); + } + + public CharSequence getFormat24Hour() { + return mFormat24; + } + + public void setFormat24Hour(CharSequence format) { + mFormat24 = format; + + chooseFormat(); + onTimeChanged(); + } + + public boolean is24HourModeEnabled() { + return DateFormat.is24HourFormat(getContext()); + } + + public String getTimeZone() { + return mTimeZone; + } + + public void setTimeZone(String timeZone) { + mTimeZone = timeZone; + + createTime(timeZone); + onTimeChanged(); + } + + /** + * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()} + * depending on whether the user has selected 24-hour format. + * + * Calling this method does not schedule or unschedule the time ticker. + */ + private void chooseFormat() { + chooseFormat(true); + } + + public CharSequence getFormat() { + return mFormat; + } + + /** + * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()} + * depending on whether the user has selected 24-hour format. + * + * @param handleTicker true if calling this method should schedule/unschedule the + * time ticker, false otherwise + */ + private void chooseFormat(boolean handleTicker) { + final boolean format24Requested = is24HourModeEnabled(); + + if (format24Requested) { + mFormat = mFormat24; + } else { + mFormat = mFormat12; + } + + boolean hadSeconds = mHasSeconds; + mHasSeconds = DateUtils.hasSeconds(mFormat); + + if (handleTicker && mAttached && hadSeconds != mHasSeconds) { + if (hadSeconds) getHandler().removeCallbacks(mTicker); + else mTicker.run(); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (!mAttached) { + mAttached = true; + + registerReceiver(); + registerObserver(); + + createTime(mTimeZone); + + if (mHasSeconds) { + mTicker.run(); + } else { + onTimeChanged(); + } + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (mAttached) { + unregisterReceiver(); + unregisterObserver(); + + getHandler().removeCallbacks(mTicker); + + mAttached = false; + } + } + + private void registerReceiver() { + final IntentFilter filter = new IntentFilter(); + + filter.addAction(Intent.ACTION_TIME_TICK); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + + getContext().registerReceiver(mIntentReceiver, filter, null, getHandler()); + } + + private void registerObserver() { + final ContentResolver resolver = getContext().getContentResolver(); + resolver.registerContentObserver(Settings.System.CONTENT_URI, true, mFormatChangeObserver); + } + + private void unregisterReceiver() { + getContext().unregisterReceiver(mIntentReceiver); + } + + private void unregisterObserver() { + final ContentResolver resolver = getContext().getContentResolver(); + resolver.unregisterContentObserver(mFormatChangeObserver); + } + + private void onTimeChanged() { + mTime.setTimeInMillis(System.currentTimeMillis()); + setText(DateFormat.format(mFormat, mTime)); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/hippo/ehviewer/widget/TransitionLayout.java b/app/src/main/java/com/hippo/ehviewer/widget/TransitionLayout.java new file mode 100644 index 000000000..b86131b8d --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/TransitionLayout.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.os.Build; +import android.util.AttributeSet; +import android.widget.LinearLayout; + +import com.hippo.ehviewer.effect.DrawableTransition; + +public class TransitionLayout extends LinearLayout { + + private Bitmap mBmp; + private int mOldWidth; + private int mOldHeight; + + private static final String TAG = TransitionLayout.class.getSimpleName(); + + public TransitionLayout(Context context) { + super(context); + } + + public TransitionLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public TransitionLayout(Context context, AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public TransitionLayout(Context context, AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + int width = getWidth(); + int height = getHeight(); + + if (mOldWidth != width || mOldHeight != height || mBmp == null) { + mOldWidth = width; + mOldHeight = height; + + if (mBmp != null && !mBmp.isRecycled()) + mBmp.recycle(); + + mBmp = Bitmap.createBitmap(getWidth(), getHeight(), + Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(mBmp); + c.translate(-getScrollX(), -getScrollY()); + super.dispatchDraw(c); + BitmapDrawable b = new BitmapDrawable(getResources(), mBmp); + DrawableTransition.transit(b, false, 3000); + setBackgroundDrawable(b); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + setBackgroundDrawable(null); + if (mBmp != null && !mBmp.isRecycled()) + mBmp.recycle(); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/recyclerview/EasyRecyclerView.java b/app/src/main/java/com/hippo/ehviewer/widget/recyclerview/EasyRecyclerView.java new file mode 100644 index 000000000..3698ddb25 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/recyclerview/EasyRecyclerView.java @@ -0,0 +1,889 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget.recyclerview; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.support.v4.util.LongSparseArray; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.StaggeredGridLayoutManager; +import android.util.AttributeSet; +import android.util.SparseBooleanArray; +import android.util.TypedValue; +import android.view.ActionMode; +import android.view.InputDevice; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AbsListView; +import android.widget.Checkable; +import android.widget.ListAdapter; + +/** + * + * Get some code from twoway-view and AbsListView + * + * @author Hippo + * + */ +public class EasyRecyclerView extends RecyclerView implements View.OnClickListener, + View.OnLongClickListener { + + private static final String TAG = EasyRecyclerView.class.getSimpleName(); + + private static final String STATE_KEY_CHOICE_MODE = "choiceMode"; + private static final String STATE_KEY_CHECKED_STATES = "checkedStates"; + private static final String STATE_KEY_CHECKED_ID_STATES = "checkedIdStates"; + private static final String STATE_KEY_CHECKED_COUNT = "checkedCount"; + + /** + * Represents an invalid position. All valid positions are in the range 0 to 1 less than the + * number of items in the current adapter. + */ + public static final int INVALID_POSITION = -1; + + /** + * Normal list that does not indicate choices + */ + public static final int CHOICE_MODE_NONE = 0; + + /** + * The list allows up to one choice + */ + public static final int CHOICE_MODE_SINGLE = 1; + + /** + * The list allows multiple choices + */ + public static final int CHOICE_MODE_MULTIPLE = 2; + + /** + * The list allows multiple choices in a modal selection mode + */ + public static final int CHOICE_MODE_MULTIPLE_MODAL = 3; + + /** + * Controls if/how the user may choose/check items in the list + */ + private int mChoiceMode = CHOICE_MODE_NONE; + + /** + * Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive. + */ + private ActionMode mChoiceActionMode; + + /** + * Wrapper for the multiple choice mode callback; AbsListView needs to perform + * a few extra actions around what application code does. + */ + MultiChoiceModeWrapper mMultiChoiceModeCallback; + + /** + * Running count of how many items are currently checked + */ + private int mCheckedItemCount; + + /** + * Running state of which positions are currently checked + */ + private SparseBooleanArray mCheckStates; + + /** + * Running state of which IDs are currently checked. + * If there is a value for a given key, the checked state for that ID is true + * and the value holds the last known position in the adapter for that id. + */ + private LongSparseArray mCheckedIdStates; + + private OnItemClickListener mOnItemClickListener; + private OnItemLongClickListener mOnItemLongClickListener; + private boolean mNeedOnClickListener = false; + private boolean mNeedOnLongClickListener = false; + + private FooterAdapter mAdapter; + + private float mVerticalScrollFactor = 0; + + public EasyRecyclerView(Context context) { + super(context); + } + + public EasyRecyclerView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public EasyRecyclerView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public View getFootView() { + if (mAdapter != null) + return mAdapter.getFooterView(); + else + return null; + } + + private boolean isNeedOnClickListener() { + if (mOnItemClickListener == null && mChoiceMode == CHOICE_MODE_NONE) + return false; + else + return true; + } + + private boolean isNeedOnLongClickListener() { + if (mOnItemLongClickListener != null || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) + return true; + else + return false; + } + + private void setAllOnClickListener() { + final int count = getChildCount(); + final View footView = getFootView(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + if (child != footView) { + child.setOnClickListener(this); + } + } + } + + private void resetAllOnClickListener() { + final int count = getChildCount(); + final View footView = getFootView(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + if (child != footView) { + child.setOnClickListener(null); + child.setClickable(false); + } + } + } + + private void setAllOnLongClickListener() { + final int count = getChildCount(); + final View footView = getFootView(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + if (child != footView) { + child.setOnLongClickListener(this); + } + } + } + + private void resetAllOnLongClickListener() { + final int count = getChildCount(); + final View footView = getFootView(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + if (child != footView) { + child.setOnLongClickListener(null); + child.setLongClickable(false); + } + } + } + + public void updateOnClickState() { + boolean newNeedOnClickListener = isNeedOnClickListener(); + if (mNeedOnClickListener != newNeedOnClickListener) { + mNeedOnClickListener = newNeedOnClickListener; + if (mNeedOnClickListener) + setAllOnClickListener(); + else + resetAllOnClickListener(); + } + } + + public void updateOnLongClickState() { + boolean newNeedOnLongClickListener = isNeedOnLongClickListener(); + if (mNeedOnLongClickListener != newNeedOnLongClickListener) { + mNeedOnLongClickListener = newNeedOnLongClickListener; + if (mNeedOnLongClickListener) + setAllOnLongClickListener(); + else + resetAllOnLongClickListener(); + } + } + + /** + * Register a callback to be invoked when an item in the + * RecyclerView has been clicked. + * + * @param listener The callback that will be invoked. + */ + public void setOnItemClickListener(OnItemClickListener listener) { + mOnItemClickListener = listener; + updateOnClickState(); + } + + /** + * Register a callback to be invoked when an item in the + * RecyclerView has been clicked and held. + * + * @param listener The callback that will be invoked. + */ + public void setOnItemLongClickListener(OnItemLongClickListener listener) { + mOnItemLongClickListener = listener; + updateOnLongClickState(); + } + + /** + * + * adapter must be {@link com.hippo.ehviewer.widget.recyclerview.EasyRecyclerView.FooterAdapter} + */ + @Override + public void setAdapter(RecyclerView.Adapter adapter) { + super.setAdapter(adapter); + + mAdapter = (FooterAdapter) adapter; + mAdapter.mHolder = this; + if (adapter != null) { + if (mChoiceMode != CHOICE_MODE_NONE && adapter.hasStableIds() && + mCheckedIdStates == null) { + mCheckedIdStates = new LongSparseArray(); + } + } + + if (mCheckStates != null) { + mCheckStates.clear(); + } + + if (mCheckedIdStates != null) { + mCheckedIdStates.clear(); + } + } + + /** + * Returns the number of items currently selected. This will only be valid + * if the choice mode is not {@link #CHOICE_MODE_NONE} (default). + * + *

    To determine the specific items that are currently selected, use one of + * the getChecked* methods. + * + * @return The number of items currently selected + * + * @see #getCheckedItemPosition() + * @see #getCheckedItemPositions() + * @see #getCheckedItemIds() + */ + public int getCheckedItemCount() { + return mCheckedItemCount; + } + + /** + * Returns the checked state of the specified position. The result is only + * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE} + * or {@link #CHOICE_MODE_MULTIPLE}. + * + * @param position The item whose checked state to return + * @return The item's checked state or false if choice mode + * is invalid + * + * @see #setChoiceMode(int) + */ + public boolean isItemChecked(int position) { + if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) { + return mCheckStates.get(position); + } + + return false; + } + + /** + * Returns the currently checked item. The result is only valid if the choice + * mode has been set to {@link #CHOICE_MODE_SINGLE}. + * + * @return The position of the currently checked item or + * {@link #INVALID_POSITION} if nothing is selected + * + * @see #setChoiceMode(int) + */ + public int getCheckedItemPosition() { + if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) { + return mCheckStates.keyAt(0); + } + + return INVALID_POSITION; + } + + /** + * Returns the set of checked items in the list. The result is only valid if + * the choice mode has not been set to {@link #CHOICE_MODE_NONE}. + * + * @return A SparseBooleanArray which will return true for each call to + * get(int position) where position is a checked position in the + * list and false otherwise, or null if the choice + * mode is set to {@link #CHOICE_MODE_NONE}. + */ + public SparseBooleanArray getCheckedItemPositions() { + if (mChoiceMode != CHOICE_MODE_NONE) { + return mCheckStates; + } + return null; + } + + /** + * Returns the set of checked items ids. The result is only valid if the + * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter + * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true}) + * + * @return A new array which contains the id of each checked item in the + * list. + */ + public long[] getCheckedItemIds() { + if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) { + return new long[0]; + } + + final LongSparseArray idStates = mCheckedIdStates; + final int count = idStates.size(); + final long[] ids = new long[count]; + + for (int i = 0; i < count; i++) { + ids[i] = idStates.keyAt(i); + } + + return ids; + } + + /** + * Clear any choices previously set + */ + public void clearChoices() { + if (mCheckStates != null) { + mCheckStates.clear(); + } + if (mCheckedIdStates != null) { + mCheckedIdStates.clear(); + } + mCheckedItemCount = 0; + updateOnScreenCheckedViews(); + } + + /** + * Sets the checked state of the specified position. The is only valid if + * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or + * {@link #CHOICE_MODE_MULTIPLE}. + * + * @param position The item whose checked state is to be checked + * @param value The new checked state for the item + */ + public void setItemChecked(int position, boolean value) { + if (mChoiceMode == CHOICE_MODE_NONE) { + return; + } + + // Start selection mode if needed. We don't need to if we're unchecking something. + if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) { + if (mMultiChoiceModeCallback == null || + !mMultiChoiceModeCallback.hasWrappedCallback()) { + throw new IllegalStateException("AbsListView: attempted to start selection mode " + + "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " + + "supplied. Call setMultiChoiceModeListener to set a callback."); + } + mChoiceActionMode = startActionMode(mMultiChoiceModeCallback); + } + + if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) { + boolean oldValue = mCheckStates.get(position); + mCheckStates.put(position, value); + if (mCheckedIdStates != null && mAdapter.hasStableIds()) { + if (value) { + mCheckedIdStates.put(mAdapter.getItemId(position), position); + } else { + mCheckedIdStates.delete(mAdapter.getItemId(position)); + } + } + if (oldValue != value) { + if (value) { + mCheckedItemCount++; + } else { + mCheckedItemCount--; + } + } + if (mChoiceActionMode != null) { + final long id = mAdapter.getItemId(position); + mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode, + position, id, value); + } + } else { + boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds(); + // Clear all values if we're checking something, or unchecking the currently + // selected item + if (value || isItemChecked(position)) { + mCheckStates.clear(); + if (updateIds) { + mCheckedIdStates.clear(); + } + } + // this may end up selecting the value we just cleared but this way + // we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on + if (value) { + mCheckStates.put(position, true); + if (updateIds) { + mCheckedIdStates.put(mAdapter.getItemId(position), position); + } + mCheckedItemCount = 1; + } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) { + mCheckedItemCount = 0; + } + } + + updateOnScreenCheckedViews(); + } + + /** + * @see #setChoiceMode(int) + * + * @return The current choice mode + */ + public int getChoiceMode() { + return mChoiceMode; + } + + /** + * Defines the choice behavior for the List. By default, Lists do not have any choice behavior + * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the + * List allows up to one item to be in a chosen state. By setting the choiceMode to + * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen. + * + * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or + * {@link #CHOICE_MODE_MULTIPLE} + */ + public void setChoiceMode(int choiceMode) { + mChoiceMode = choiceMode; + if (mChoiceActionMode != null) { + mChoiceActionMode.finish(); + mChoiceActionMode = null; + } + if (mChoiceMode != CHOICE_MODE_NONE) { + if (mCheckStates == null) { + mCheckStates = new SparseBooleanArray(0); + } + if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) { + mCheckedIdStates = new LongSparseArray(0); + } + // Modal multi-choice mode only has choices when the mode is active. Clear them. + if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) { + clearChoices(); + } + } + + updateOnClickState(); + updateOnLongClickState(); + } + + /** + * Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the + * selection {@link ActionMode}. Only used when the choice mode is set to + * {@link #CHOICE_MODE_MULTIPLE_MODAL}. + * + * @param listener Listener that will manage the selection mode + * + * @see #setChoiceMode(int) + */ + public void setMultiChoiceModeListener(MultiChoiceModeListener listener) { + if (mMultiChoiceModeCallback == null) { + mMultiChoiceModeCallback = new MultiChoiceModeWrapper(); + } + mMultiChoiceModeCallback.setWrapped(listener); + } + + /** + * Perform a quick, in-place update of the checked or activated state + * on all visible item views. This should only be called when a valid + * choice mode is active. + */ + private void updateOnScreenCheckedViews() { + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + final int position = getChildPosition(child); + setViewChecked(child, mCheckStates.get(position)); + } + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static void setViewChecked(View view, boolean checked) { + final boolean useActivated = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; + if (view instanceof Checkable) { + ((Checkable) view).setChecked(checked); + } else if (useActivated) { + view.setActivated(checked); + } + } + + @Override + public boolean onGenericMotionEvent(MotionEvent event) { + if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { + switch (event.getAction()) { + case MotionEvent.ACTION_SCROLL: { + if (getScrollState() != SCROLL_STATE_DRAGGING) { + final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); + if (vscroll != 0) { + final int delta = (int) (vscroll * getVerticalScrollFactor()); + scrollBy(0, -delta); + } + } + } + } + } + return super.onGenericMotionEvent(event); + } + + /** + * Gets a scale factor that determines the distance the view should scroll + * vertically in response to {@link MotionEvent#ACTION_SCROLL}. + * @return The vertical scroll scale factor. + */ + protected float getVerticalScrollFactor() { + if (mVerticalScrollFactor == 0) { + TypedValue outValue = new TypedValue(); + if (!getContext().getTheme().resolveAttribute( + 0x0101004d, outValue, true)) {//com.android.internal.R.attr.listPreferredItemHeight + throw new IllegalStateException( + "Expected theme to define listPreferredItemHeight."); + } + mVerticalScrollFactor = outValue.getDimension( + getContext().getResources().getDisplayMetrics()); + } + return mVerticalScrollFactor; + } + + /* + @Override + public Parcelable onSaveInstanceState() { + final Parcelable state = new Bundle(super.onSaveInstanceState()); + + state.putInt(STATE_KEY_CHOICE_MODE, mChoiceMode); + state.putParcelable(STATE_KEY_CHECKED_STATES, mCheckStates); + state.putParcelable(STATE_KEY_CHECKED_ID_STATES, mCheckedIdStates); + state.putInt(STATE_KEY_CHECKED_COUNT, mCheckedItemCount); + + return state; + } + + public void onRestoreInstanceState(Bundle state) { + mChoiceMode = ChoiceMode.values()[state.getInt(STATE_KEY_CHOICE_MODE)]; + mCheckedStates = state.getParcelable(STATE_KEY_CHECKED_STATES); + mCheckedIdStates = state.getParcelable(STATE_KEY_CHECKED_ID_STATES); + mCheckedCount = state.getInt(STATE_KEY_CHECKED_COUNT); + + // TODO confirm ids here + }*/ + + /** + * Interface definition for a callback to be invoked when an item in the + * EasyRecyclerView has been clicked. + */ + public interface OnItemClickListener { + /** + * Callback method to be invoked when an item in the EasyRecyclerView + * has been clicked. + * + * @param parent The EasyRecyclerView where the click happened. + * @param view The view within the EasyRecyclerView that was clicked + * @param position The position of the view in the adapter. + * @param id The row id of the item that was clicked. + * + * @return true if the callback consumed the click, false otherwise + */ + boolean onItemClick(EasyRecyclerView parent, View view, int position, long id); + } + + /** + * Interface definition for a callback to be invoked when an item in the + * EasyRecyclerView has been clicked and held. + */ + public interface OnItemLongClickListener { + /** + * Callback method to be invoked when an item in the EasyRecyclerView + * has been clicked and held. + * + * @param parent The EasyRecyclerView where the click happened + * @param view The view within the EasyRecyclerView that was clicked + * @param position The position of the view in the list + * @param id The row id of the item that was clicked + * + * @return true if the callback consumed the long click, false otherwise + */ + boolean onItemLongClick(EasyRecyclerView parent, View view, int position, long id); + } + + + /* + * EasyRecyclerView.OnItemTouchListener will not work fine + * when item view's child need click action, because they make + * touch event deliver different + */ + + @Override + public void onClick(View view) { + final int position = getChildPosition(view); + final long id = mAdapter.getItemId(position); + + boolean dispatchItemClick = true; + + if (mChoiceMode != CHOICE_MODE_NONE) { + boolean checkedStateChanged = false; + + if (mChoiceMode == CHOICE_MODE_MULTIPLE || + (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null)) { + boolean checked = !mCheckStates.get(position, false); + mCheckStates.put(position, checked); + if (mCheckedIdStates != null && mAdapter.hasStableIds()) { + if (checked) { + mCheckedIdStates.put(mAdapter.getItemId(position), position); + } else { + mCheckedIdStates.delete(mAdapter.getItemId(position)); + } + } + if (checked) { + mCheckedItemCount++; + } else { + mCheckedItemCount--; + } + if (mChoiceActionMode != null) { + mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode, + position, id, checked); + dispatchItemClick = false; + } + checkedStateChanged = true; + } else if (mChoiceMode == CHOICE_MODE_SINGLE) { + boolean checked = !mCheckStates.get(position, false); + if (checked) { + mCheckStates.clear(); + mCheckStates.put(position, true); + if (mCheckedIdStates != null && mAdapter.hasStableIds()) { + mCheckedIdStates.clear(); + mCheckedIdStates.put(mAdapter.getItemId(position), position); + } + mCheckedItemCount = 1; + } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) { + mCheckedItemCount = 0; + } + checkedStateChanged = true; + } + + if (checkedStateChanged) { + updateOnScreenCheckedViews(); + } + } + + if (dispatchItemClick && mOnItemClickListener != null) { + mOnItemClickListener.onItemClick(this, view, position, id); + } + } + + + @Override + public boolean onLongClick(View view) { + final int position = getChildPosition(view); + final long id = mAdapter.getItemId(position); + + // CHOICE_MODE_MULTIPLE_MODAL takes over long press. + if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) { + if (mChoiceActionMode == null && + (mChoiceActionMode = startActionMode(mMultiChoiceModeCallback)) != null) { + setItemChecked(position, true); + } + return true; + } + + boolean handled = false; + if (mOnItemLongClickListener != null) { + handled = mOnItemLongClickListener.onItemLongClick(this, view, + position, id); + } + return handled; + } + + /** + * A MultiChoiceModeListener receives events for {@link AbsListView#CHOICE_MODE_MULTIPLE_MODAL}. + * It acts as the {@link ActionMode.Callback} for the selection mode and also receives + * {@link #onItemCheckedStateChanged(ActionMode, int, long, boolean)} events when the user + * selects and deselects list items. + */ + public interface MultiChoiceModeListener extends ActionMode.Callback { + /** + * Called when an item is checked or unchecked during selection mode. + * + * @param mode The {@link ActionMode} providing the selection mode + * @param position Adapter position of the item that was checked or unchecked + * @param id Adapter ID of the item that was checked or unchecked + * @param checked true if the item is now checked, false + * if the item is now unchecked. + */ + public void onItemCheckedStateChanged(ActionMode mode, + int position, long id, boolean checked); + } + + class MultiChoiceModeWrapper implements MultiChoiceModeListener { + private MultiChoiceModeListener mWrapped; + + public void setWrapped(MultiChoiceModeListener wrapped) { + mWrapped = wrapped; + } + + public boolean hasWrappedCallback() { + return mWrapped != null; + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + if (mWrapped.onCreateActionMode(mode, menu)) { + // Initialize checked graphic state? + return true; + } + return false; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return mWrapped.onPrepareActionMode(mode, menu); + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return mWrapped.onActionItemClicked(mode, item); + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + mWrapped.onDestroyActionMode(mode); + mChoiceActionMode = null; + + // Ending selection mode means deselecting everything. + clearChoices(); + + requestLayout(); + } + + @Override + public void onItemCheckedStateChanged(ActionMode mode, + int position, long id, boolean checked) { + mWrapped.onItemCheckedStateChanged(mode, position, id, checked); + + // If there are no items selected we no longer need the selection mode. + if (getCheckedItemCount() == 0) { + mode.finish(); + } + } + } + + public static abstract class FooterAdapter extends RecyclerView.Adapter { + + public static final int TYPE_FOOTER = Integer.MAX_VALUE; + + private EasyRecyclerView mHolder; + private View mFooterView; + + public boolean hasFootView() { + return mFooterView != null; + } + + /** + * footerView == null to remove foot view + * @param footerView + */ + public void setFooterView(View footerView) { + if (mFooterView == footerView) + return; + + int itemCountActual = getItemCountActual(); + View originView = mFooterView; + mFooterView = footerView; + if (originView != null) { + if (mFooterView != null) + notifyItemChanged(itemCountActual); + else + notifyItemRemoved(itemCountActual); + } else { + notifyItemInserted(itemCountActual); + } + } + + public View getFooterView() { + return mFooterView; + } + + @Override + public final VH onCreateViewHolder(ViewGroup parent, int viewType) { + if (mFooterView != null && viewType == TYPE_FOOTER) { + StaggeredGridLayoutManager.LayoutParams lp = new StaggeredGridLayoutManager.LayoutParams( + StaggeredGridLayoutManager.LayoutParams.WRAP_CONTENT, + StaggeredGridLayoutManager.LayoutParams.WRAP_CONTENT); + lp.setFullSpan(true); + mFooterView.setLayoutParams(lp); + return onCreateAndBindFooterViewHolder(parent, mFooterView); + } else { + return onCreateViewHolderActual(parent, viewType); + } + } + + @Override + public final void onBindViewHolder(VH holder, int position) { + if (mFooterView == null || position != getItemCountActual()) { + onBindViewHolderActual(holder, position); + if (mHolder.mNeedOnClickListener) + holder.itemView.setOnClickListener(mHolder); + if (mHolder.mNeedOnLongClickListener) + holder.itemView.setOnLongClickListener(mHolder); + EasyRecyclerView.setViewChecked(holder.itemView, + mHolder.isItemChecked(position)); + } + } + + @Override + public final int getItemViewType(int position) { + if (mFooterView != null && position == getItemCountActual()) + return TYPE_FOOTER; + else + return getItemViewTypeActual(position); + } + + @Override + public final int getItemCount() { + int itemCount = getItemCountActual(); + if (mFooterView != null) + itemCount++; + return itemCount; + } + + public abstract VH onCreateAndBindFooterViewHolder(ViewGroup parent, View footerView); + + public abstract VH onCreateViewHolderActual(ViewGroup parent, int viewType); + + public abstract void onBindViewHolderActual(VH holder, int position); + + /** + * Foot view type is Integer.MAX_VALUE, do not be the same + * + * @param position + * @return + */ + public int getItemViewTypeActual(int position) { + return 0; + } + + public abstract int getItemCountActual(); + + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/recyclerview/LinearDividerItemDecoration.java b/app/src/main/java/com/hippo/ehviewer/widget/recyclerview/LinearDividerItemDecoration.java new file mode 100644 index 000000000..a8dac20e0 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/recyclerview/LinearDividerItemDecoration.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget.recyclerview; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +public class LinearDividerItemDecoration extends RecyclerView.ItemDecoration { + public static final int HORIZONTAL = LinearLayoutManager.HORIZONTAL; + public static final int VERTICAL = LinearLayoutManager.VERTICAL; + + private boolean mShowFirstDivider = false; + private boolean mShowLastDivider = false; + + private final Rect mRect; + private final Paint mPaint; + + private int mOrientation; + private int mThickness; + private int mPaddingStart = 0; + private int mPaddingEnd = 0; + + public LinearDividerItemDecoration(int orientation, int color, int thickness) { + mRect = new Rect(); + mPaint = new Paint(); + mPaint.setStyle(Paint.Style.FILL); + setOrientation(orientation); + setColor(color); + setThickness(thickness); + } + + public void setOrientation(int orientation) { + if (orientation != HORIZONTAL && orientation != VERTICAL) { + throw new IllegalArgumentException("invalid orientation"); + } + mOrientation = orientation; + } + + public void setColor(int color) { + mPaint.setColor(color); + } + + public void setThickness(int thickness) { + mThickness = thickness; + } + + public void setShowFirstDivider(boolean showFirstDivider) { + mShowFirstDivider = showFirstDivider; + } + + public void setShowLastDivider(boolean showLastDivider) { + mShowLastDivider = showLastDivider; + } + + public void setPadding(int padding) { + setPaddingStart(padding); + setPaddingEnd(padding); + } + + public void setPaddingStart(int paddingStart) { + mPaddingStart = paddingStart; + } + + public void setPaddingEnd(int paddingEnd) { + mPaddingEnd = paddingEnd; + } + + @Override + public void getItemOffsets(Rect outRect, View view, + RecyclerView parent, RecyclerView.State state) { + final int position = parent.getChildPosition(view); + final int itemCount = parent.getAdapter().getItemCount(); + if (mOrientation == VERTICAL) { + if (position == 0 && mShowFirstDivider) + outRect.top = mThickness; + outRect.bottom = mThickness; + if (position == itemCount - 1 && !mShowLastDivider) + outRect.bottom = 0; + } else { + if (position == 0 && mShowFirstDivider) + outRect.left = mThickness; + outRect.right = mThickness; + if (position == itemCount - 1 && !mShowLastDivider) + outRect.left = 0; + } + } + + @Override + public void onDrawOver(Canvas c, RecyclerView parent, + RecyclerView.State state) { + int itemCount = parent.getAdapter().getItemCount(); + + if (mOrientation == VERTICAL) { + final int left = parent.getPaddingLeft() + mPaddingStart; + final int right = parent.getWidth() - parent.getPaddingRight() - mPaddingEnd; + final int childCount = parent.getChildCount(); + + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams(); + + if (i != childCount - 1 || mShowLastDivider + || parent.getChildPosition(child) != itemCount - 1) { + final int top = child.getBottom() + lp.bottomMargin; + final int bottom = top + mThickness; + mRect.set(left, top, right, bottom); + c.drawRect(mRect, mPaint); + } + + if (i == 0 && mShowFirstDivider && parent.getChildPosition(child) == 0) { + final int bottom = child.getTop() + lp.topMargin; + final int top = bottom - mThickness; + mRect.set(left, top, right, bottom); + c.drawRect(mRect, mPaint); + } + } + } else { + final int top = parent.getPaddingTop() + mPaddingStart; + final int bottom = parent.getHeight() - parent.getPaddingBottom() - mPaddingEnd; + final int childCount = parent.getChildCount(); + + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams(); + + if (i != childCount - 1 || mShowLastDivider + || parent.getChildPosition(child) != itemCount - 1) { + final int left = child.getRight() + lp.rightMargin; + final int right = left + mThickness; + mRect.set(left, top, right, bottom); + c.drawRect(mRect, mPaint); + } + + if (i == 0 && mShowFirstDivider && parent.getChildPosition(child) == 0) { + final int right = child.getLeft() + lp.leftMargin; + final int left = right - mThickness; + mRect.set(left, top, right, bottom); + c.drawRect(mRect, mPaint); + } + } + } + } + +} diff --git a/app/src/main/java/com/hippo/ehviewer/widget/recyclerview/MarginItemDecoration.java b/app/src/main/java/com/hippo/ehviewer/widget/recyclerview/MarginItemDecoration.java new file mode 100644 index 000000000..388ac4aa6 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/widget/recyclerview/MarginItemDecoration.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.widget.recyclerview; + +import android.graphics.Rect; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +/** + * Only work for StaggeredGridLayoutManager, FooterAdapter + * + * @author Hippo + * + */ +public class MarginItemDecoration extends RecyclerView.ItemDecoration { + + private int mHalfMargin; + + @Override + public void getItemOffsets(Rect outRect, View view, + RecyclerView parent, RecyclerView.State state) { + outRect.set(mHalfMargin, mHalfMargin, mHalfMargin, mHalfMargin); + } + + public void setMargin(int margin) { + mHalfMargin = margin / 2; + } + + public int getMargin() { + return mHalfMargin * 2; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/windowsanimate/AnimateCanvas.java b/app/src/main/java/com/hippo/ehviewer/windowsanimate/AnimateCanvas.java new file mode 100644 index 000000000..4d7f657f8 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/windowsanimate/AnimateCanvas.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.windowsanimate; + +import java.util.LinkedList; +import java.util.List; + +import android.content.Context; +import android.graphics.Canvas; +import android.view.View; + +public class AnimateCanvas extends View { + + private final List mSpriteList = new LinkedList(); + + public AnimateCanvas(Context context) { + super(context); + } + + void addSprite(Sprite sprite) { + mSpriteList.add(sprite); + } + + void removeSprite(Sprite sprite) { + mSpriteList.remove(sprite); + } + + @Override + public void onDraw(Canvas canvas) { + for (Sprite sprite : mSpriteList) { + sprite.draw(canvas); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/windowsanimate/BitmapSprite.java b/app/src/main/java/com/hippo/ehviewer/windowsanimate/BitmapSprite.java new file mode 100644 index 000000000..5d49e93eb --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/windowsanimate/BitmapSprite.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.windowsanimate; + +import android.graphics.Bitmap; +import android.graphics.Canvas; + +public class BitmapSprite extends Sprite { + + private int x; + private int y; + private Bitmap mBitmap; + + public BitmapSprite(WindowsAnimate holder, Bitmap bmp, int startX, int startY) { + super(holder); + mBitmap = bmp; + x = startX; + y = startY; + } + + public void setX(int x) { + this.x = x; + updateCanvas(); + } + + public void setY(int y) { + this.y = y; + updateCanvas(); + } + + @Override + public void draw(Canvas canvas) { + canvas.drawBitmap(mBitmap, x, y, null); + } + + public void free() { + mBitmap.recycle(); + mBitmap = null; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/windowsanimate/RippleHelpDrawable.java b/app/src/main/java/com/hippo/ehviewer/windowsanimate/RippleHelpDrawable.java new file mode 100644 index 000000000..16cfe356e --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/windowsanimate/RippleHelpDrawable.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.windowsanimate; + +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.view.View; + +import com.hippo.ehviewer.cardview.RoundRectDrawableWithShadow; + +/** + * @author Hippo + * + * Get lots of code from android.graphics.drawable.RippleDrawable + */ +public class RippleHelpDrawable extends Drawable { + + private final RippleSprite mRipple; + private final Drawable mActualDrawable; + + private boolean mActive = false; + + @SuppressWarnings("deprecation") + public RippleHelpDrawable(WindowsAnimate windowsAnimate, View view, boolean keepBound) { + mActualDrawable = view.getBackground(); + Rect padding = mActualDrawable == null || !(mActualDrawable instanceof RoundRectDrawableWithShadow) ? + null : ((RoundRectDrawableWithShadow)mActualDrawable).getSuggestionPadding(); + mRipple = new RippleSprite(windowsAnimate, view, getBounds(), padding, keepBound); + view.setBackgroundDrawable(this); + } + + @Override + public boolean isStateful() { + return true; + } + + @Override + protected boolean onStateChange(int[] stateSet) { + boolean active = false; + boolean enabled = false; + int N = stateSet.length; + for (int i = 0; i < N; i++) { + if (stateSet[i] == android.R.attr.state_enabled) { + enabled = true; + } + if ((stateSet[i] == android.R.attr.state_focused) || + (stateSet[i] == android.R.attr.state_pressed)) { + active = true; + } + } + + setActive((active) && (enabled)); + + if (mActualDrawable != null && mActualDrawable.isStateful()) + return mActualDrawable.setState(stateSet); + else + return false; + } + + private void setActive(boolean active) { + if (mActive != active) { + mActive = active; + if (active) { + mRipple.enter(); + } else { + mRipple.exit(); + } + } + } + + @Override + protected void onBoundsChange(Rect bounds) { + mRipple.onBoundsChanged(bounds); + if (mActualDrawable != null) + mActualDrawable.setBounds(bounds); + } + + @Override + public void draw(Canvas canvas) { + if (mActualDrawable != null) + mActualDrawable.draw(canvas); + } + + @Override + public void setAlpha(int alpha) { + if (mActualDrawable != null) + mActualDrawable.setAlpha(alpha); + } + + @Override + public void setColorFilter(ColorFilter cf) { + if (mActualDrawable != null) + mActualDrawable.setColorFilter(cf); + } + + @Override + public int getOpacity() { + if (mActualDrawable != null) + return mActualDrawable.getOpacity(); + else + return PixelFormat.TRANSPARENT; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/windowsanimate/RippleSprite.java b/app/src/main/java/com/hippo/ehviewer/windowsanimate/RippleSprite.java new file mode 100644 index 000000000..a38ac0def --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/windowsanimate/RippleSprite.java @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.windowsanimate; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.annotation.TargetApi; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PointF; +import android.graphics.Rect; +import android.os.Build; +import android.view.View; +import android.view.animation.LinearInterpolator; + +import com.hippo.ehviewer.util.MathUtils; +import com.hippo.ehviewer.util.Ui; +import com.hippo.ehviewer.util.ViewUtils; + +/** + * @author Hippo + * + * Get lots of code from android.graphics.drawable.Ripple + */ +public class RippleSprite extends Sprite { + private static final String TAG = RippleSprite.class.getSimpleName(); + + private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); + private static final TimeInterpolator DECEL_INTERPOLATOR = new LogInterpolator(); + + private static final float GLOBAL_SPEED = 1.0F; + private static final float WAVE_TOUCH_DOWN_ACCELERATION = 1024.0F; + private static final float WAVE_TOUCH_UP_ACCELERATION = 3400.0F; + private static final float WAVE_OPACITY_DECAY_VELOCITY = 3.0F; + private static final float WAVE_OUTER_OPACITY_VELOCITY_MAX = 4.5F; + private static final float WAVE_OUTER_OPACITY_VELOCITY_MIN = 1.5F; + private static final float WAVE_OUTER_SIZE_INFLUENCE_MAX = 200.0F; + private static final float WAVE_OUTER_SIZE_INFLUENCE_MIN = 40.0F; + private static final long RIPPLE_ENTER_DELAY = 80L; + + private final WindowsAnimate mHolder; + private final View mView; + private final Rect mBounds; + private final Rect mPadding; + private final boolean mKeepBound; + private float mOuterRadius; + private final int[] mViewPosition = new int[2]; + + private float mStartingX; + private float mStartingY; + private float mClampedStartingX; + private float mClampedStartingY; + + private ObjectAnimator mAnimRadius; + private ObjectAnimator mAnimOpacity; + private ObjectAnimator mAnimOuterOpacity; + private ObjectAnimator mAnimX; + private ObjectAnimator mAnimY; + private float mOuterOpacity = 0.0F; + private float mOpacity = 1.0F; + private float mTweenRadius = 0.0F; + private float mTweenX = 0.0F; + private float mTweenY = 0.0F; + + private boolean isRunning = false; + + private static Paint mPaint; + + static { + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mPaint.setColor(0x20444444); + } + + public RippleSprite(WindowsAnimate holder, View view, Rect bounds, Rect padding, boolean keepBound) { + super(holder); + mHolder = holder; + mView = view; + mBounds = new Rect(bounds); + mPadding = padding; + mKeepBound = keepBound; + + float halfWidth = mBounds.width() / 2.0F; + float halfHeight = mBounds.height() / 2.0F; + mOuterRadius = ((float) Math.sqrt(halfWidth * halfWidth + + halfHeight * halfHeight)); + + mStartingX = mBounds.exactCenterX(); + mStartingY = mBounds.exactCenterY(); + clampStartingPosition(); + } + + public void onBoundsChanged(Rect bounds) { + mBounds.set(bounds); + float halfWidth = mBounds.width() / 2.0F; + float halfHeight = mBounds.height() / 2.0F; + mOuterRadius = ((float) Math.sqrt(halfWidth * halfWidth + + halfHeight * halfHeight)); + + if (!mKeepBound) { + mStartingX = mBounds.exactCenterX(); + mStartingY = mBounds.exactCenterY(); + } + clampStartingPosition(); + } + + private void clampStartingPosition() { + float dX = mStartingX - mBounds.exactCenterX(); + float dY = mStartingY - mBounds.exactCenterY(); + float r = mOuterRadius; + if (dX * dX + dY * dY > r * r) { + double angle = Math.atan2(dY, dX); + mClampedStartingX = ((float)(Math.cos(angle) * r)); + mClampedStartingY = ((float)(Math.sin(angle) * r)); + } else { + mClampedStartingX = mStartingX; + mClampedStartingY = mStartingY; + } + } + + public void setOpacity(float a) { + mOpacity = a; + updateCanvas(); + } + + public float getOpacity() { + return mOpacity; + } + + public void setOuterOpacity(float a) { + mOuterOpacity = a; + updateCanvas(); + } + + public float getOuterOpacity() { + return mOuterOpacity; + } + + public void setRadiusGravity(float r) { + mTweenRadius = r; + updateCanvas(); + } + + public float getRadiusGravity() { + return mTweenRadius; + } + + public void setXGravity(float x) { + mTweenX = x; + updateCanvas(); + } + + public float getXGravity() { + return mTweenX; + } + + public void setYGravity(float y) { + mTweenY = y; + updateCanvas(); + } + + public float getYGravity() { + return mTweenY; + } + + @Override + public void draw(Canvas c) { + ViewUtils.getCenterInWindows(mView, mViewPosition); + + int saved = c.save(); + c.translate(mViewPosition[0], mViewPosition[1]); + if (mKeepBound) { + int halfWidth = mBounds.width() / 2; + int halfHeight = mBounds.height() / 2; + + if (mPadding != null) { + c.clipRect(-halfWidth + mPadding.left, -halfHeight + mPadding.top, + halfWidth - mPadding.right, halfHeight - mPadding.bottom); + } else + c.clipRect(-halfWidth, -halfHeight, + halfWidth, halfHeight); + } + + drawSoftware(c, mPaint); + + c.restoreToCount(saved); + } + + private void drawSoftware(Canvas c, Paint p) { + int paintAlpha = p.getAlpha(); + + int outerAlpha = (int) (paintAlpha * mOuterOpacity + 0.5F); + if ((outerAlpha > 0) && (mOuterRadius > 0.0F)) { + p.setAlpha(outerAlpha); + p.setStyle(Paint.Style.FILL); + c.drawCircle(0.0F, 0.0F, mOuterRadius, p); + } + + int alpha = (int) (paintAlpha * mOpacity + 0.5F); + float radius = MathUtils.lerp(0.0F, mOuterRadius, + mTweenRadius); + if ((alpha > 0) && (radius > 0.0F)) { + float x = MathUtils.lerp( + this.mClampedStartingX - this.mBounds.exactCenterX(), + 0.0F, this.mTweenX); + float y = MathUtils.lerp( + this.mClampedStartingY - this.mBounds.exactCenterY(), + 0.0F, this.mTweenY); + p.setAlpha(alpha); + p.setStyle(Paint.Style.FILL); + c.drawCircle(x, y, radius, p); + } + + p.setAlpha(paintAlpha); + } + + public void setStartPosition(float x, float y) { + mStartingX = x; + mStartingY = y; + clampStartingPosition(); + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) + public void enter() { + if (isRunning) + return; + isRunning = true; + + addSelf(); + + // If keepBound, set position + if (mKeepBound) { + Object obj = mView.getTag(); + if (obj != null && obj instanceof PointF) { + setStartPosition(((PointF)obj).x, ((PointF)obj).y); + } + } + + mOuterOpacity = 0.0F; + mOpacity = 1.0F; + mTweenRadius = 0.0F; + mTweenX = 0.0F; + mTweenY = 0.0F; + + int radiusDuration = (int) (1000.0D * Math.sqrt(mOuterRadius + / WAVE_TOUCH_DOWN_ACCELERATION * Ui.mDensity) + 0.5D); + + long outerDuration = 666L; + + ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radiusGravity", + new float[] { 1.0F }); + radius.setDuration(radiusDuration); + radius.setInterpolator(LINEAR_INTERPOLATOR); + radius.setStartDelay(RIPPLE_ENTER_DELAY); + + ObjectAnimator cX = ObjectAnimator.ofFloat(this, "xGravity", + new float[] { 1.0F }); + cX.setDuration(radiusDuration); + cX.setInterpolator(LINEAR_INTERPOLATOR); + cX.setStartDelay(RIPPLE_ENTER_DELAY); + + ObjectAnimator cY = ObjectAnimator.ofFloat(this, "yGravity", + new float[] { 1.0F }); + cY.setDuration(radiusDuration); + cY.setInterpolator(LINEAR_INTERPOLATOR); + cY.setStartDelay(RIPPLE_ENTER_DELAY); + + ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerOpacity", + new float[] { 0.0F, 1.0F }); + outer.setDuration(outerDuration); + outer.setInterpolator(LINEAR_INTERPOLATOR); + + if (Build.VERSION.SDK_INT >= 18) { + radius.setAutoCancel(true); + cX.setAutoCancel(true); + cY.setAutoCancel(true); + outer.setAutoCancel(true); + } + + mAnimRadius = radius; + mAnimOuterOpacity = outer; + mAnimX = cX; + mAnimY = cY; + + radius.start(); + outer.start(); + cX.start(); + cY.start(); + } + + public void exit() { + if (!isRunning) + return; + + cancelSoftwareAnimations(); + float radius = MathUtils.lerp(0.0F, mOuterRadius, + mTweenRadius); + float remaining; + if ((mAnimRadius != null) && (mAnimRadius.isRunning())) { + remaining = mOuterRadius - radius; + } else { + remaining = mOuterRadius; + } + + int radiusDuration = (int) (1000.0D * Math.sqrt(remaining / 4424.0F + * Ui.mDensity) + 0.5D); + + int opacityDuration = (int) (1000.0F * mOpacity / 3.0F + 0.5F); + + float outerSizeInfluence = MathUtils.clamp( + (mOuterRadius - WAVE_OUTER_SIZE_INFLUENCE_MIN * Ui.mDensity) + / (WAVE_OUTER_SIZE_INFLUENCE_MAX * Ui.mDensity), 0.0F, 1.0F); + + float outerOpacityVelocity = MathUtils.lerp(WAVE_OUTER_OPACITY_VELOCITY_MIN, + WAVE_OUTER_OPACITY_VELOCITY_MAX, outerSizeInfluence); + + int outerInflection = Math.max(0, (int) (1000.0F + * (mOpacity - mOuterOpacity) + / (3.0F + outerOpacityVelocity) + 0.5F)); + + int inflectionOpacity = (int) (255.0F * (mOuterOpacity + outerInflection + * outerOpacityVelocity * outerSizeInfluence / 1000.0F) + 0.5F); + + exitSoftware(radiusDuration, opacityDuration, outerInflection, + inflectionOpacity); + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) + private void exitSoftware(int radiusDuration, int opacityDuration, + int outerInflection, int inflectionOpacity) { + ObjectAnimator radiusAnim = ObjectAnimator.ofFloat(this, + "radiusGravity", new float[] { 1.0F }); + radiusAnim.setDuration(radiusDuration); + radiusAnim.setInterpolator(DECEL_INTERPOLATOR); + + ObjectAnimator xAnim = ObjectAnimator.ofFloat(this, "xGravity", + new float[] { 1.0F }); + xAnim.setDuration(radiusDuration); + xAnim.setInterpolator(DECEL_INTERPOLATOR); + + ObjectAnimator yAnim = ObjectAnimator.ofFloat(this, "yGravity", + new float[] { 1.0F }); + yAnim.setDuration(radiusDuration); + yAnim.setInterpolator(DECEL_INTERPOLATOR); + + ObjectAnimator opacityAnim = ObjectAnimator.ofFloat(this, "opacity", + new float[] { 0.0F }); + opacityAnim.setDuration(opacityDuration); + opacityAnim.setInterpolator(LINEAR_INTERPOLATOR); + + ObjectAnimator outerOpacityAnim; + if (outerInflection > 0) { + outerOpacityAnim = ObjectAnimator.ofFloat(this, + "outerOpacity", new float[] { inflectionOpacity / 255.0F }); + outerOpacityAnim.setDuration(outerInflection); + outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR); + + final int outerDuration = opacityDuration - outerInflection; + if (outerDuration > 0) { + outerOpacityAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + ObjectAnimator outerFadeOutAnim = ObjectAnimator + .ofFloat(RippleSprite.this, "outerOpacity", + new float[] { 0.0F }); + if (Build.VERSION.SDK_INT >= 18) + outerFadeOutAnim.setAutoCancel(true); + outerFadeOutAnim.setDuration(outerDuration); + outerFadeOutAnim + .setInterpolator(RippleSprite.LINEAR_INTERPOLATOR); + outerFadeOutAnim + .addListener(mAnimationListener); + + mAnimOuterOpacity = outerFadeOutAnim; + + outerFadeOutAnim.start(); + } + + @Override + public void onAnimationCancel(Animator animation) { + animation.removeListener(this); + } + }); + } else { + outerOpacityAnim.addListener(mAnimationListener); + } + } else { + outerOpacityAnim = ObjectAnimator.ofFloat(this, "outerOpacity", + new float[] { 0.0F }); + outerOpacityAnim.setDuration(opacityDuration); + outerOpacityAnim.addListener(mAnimationListener); + } + + if (Build.VERSION.SDK_INT >= 18) { + radiusAnim.setAutoCancel(true); + xAnim.setAutoCancel(true); + yAnim.setAutoCancel(true); + opacityAnim.setAutoCancel(true); + outerOpacityAnim.setAutoCancel(true); + } + + mAnimRadius = radiusAnim; + mAnimOpacity = opacityAnim; + mAnimOuterOpacity = outerOpacityAnim; + mAnimX = opacityAnim; + mAnimY = opacityAnim; + + radiusAnim.start(); + opacityAnim.start(); + outerOpacityAnim.start(); + xAnim.start(); + yAnim.start(); + } + + public void cancel() { + cancelSoftwareAnimations(); + } + + private void cancelSoftwareAnimations() { + if (mAnimRadius != null) { + mAnimRadius.cancel(); + } + + if (mAnimOpacity != null) { + mAnimOpacity.cancel(); + } + + if (mAnimOuterOpacity != null) { + mAnimOuterOpacity.cancel(); + } + + if (mAnimX != null) { + mAnimX.cancel(); + } + + if (mAnimY != null) { + mAnimY.cancel(); + } + } + + private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + removeSelf(); + isRunning = false; + } + }; + + private static class LogInterpolator implements TimeInterpolator { + @Override + public float getInterpolation(float input) { + return 1.0F - (float) Math.pow(400.0D, -input * 1.4D); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/windowsanimate/RoundSprite.java b/app/src/main/java/com/hippo/ehviewer/windowsanimate/RoundSprite.java new file mode 100644 index 000000000..7c377768e --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/windowsanimate/RoundSprite.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.windowsanimate; + +import android.graphics.Canvas; +import android.graphics.Paint; + +public class RoundSprite extends Sprite { + + private final int startX; + private final int startY; + private float radius; + private final Paint mPaint; + + public RoundSprite(WindowsAnimate holder, int startX, int startY, int color) { + super(holder); + this.startX = startX; + this.startY = startY; + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mPaint.setColor(color); + } + + public void setRadius(float radius) { + this.radius = radius; + updateCanvas(); + } + + @Override + void draw(Canvas canvas) { + canvas.drawCircle(startX, startY, radius, mPaint); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/windowsanimate/Sprite.java b/app/src/main/java/com/hippo/ehviewer/windowsanimate/Sprite.java new file mode 100644 index 000000000..2b101ceb6 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/windowsanimate/Sprite.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.windowsanimate; + +import android.graphics.Canvas; + +public abstract class Sprite { + + private final WindowsAnimate mHolder; + + public Sprite(WindowsAnimate holder) { + mHolder = holder; + } + + public void addSelf() { + mHolder.addRenderingSprite(this); + } + + public void removeSelf() { + mHolder.removeRenderingSprite(this); + } + + protected void updateCanvas() { + mHolder.updateCanvas(); + } + + abstract void draw(Canvas canvas); +} diff --git a/app/src/main/java/com/hippo/ehviewer/windowsanimate/WindowsAnimate.java b/app/src/main/java/com/hippo/ehviewer/windowsanimate/WindowsAnimate.java new file mode 100644 index 000000000..b68e736dd --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/windowsanimate/WindowsAnimate.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2014 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.windowsanimate; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.graphics.PointF; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.OvershootInterpolator; + +import com.hippo.ehviewer.util.Constants; +import com.hippo.ehviewer.util.ViewUtils; + +public final class WindowsAnimate + implements View.OnTouchListener { + + @SuppressWarnings("unused") + private static final String TAG = WindowsAnimate.class.getSimpleName(); + + private static final TimeInterpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator(); + private static final TimeInterpolator OVERSHOOT_INTERPOLATOR = new OvershootInterpolator(); + + private Context mContext; + private ViewGroup mContentViewGroup; + private AnimateCanvas mAnimateCanvas; + private int mRunningAnimateNum = 0; + + public boolean init(Activity activity) { + mContentViewGroup = (ViewGroup)activity.getWindow().getDecorView() + .findViewById(android.R.id.content); + if (mContentViewGroup == null) + return false; + + mContext = activity.getApplicationContext(); + mAnimateCanvas = new AnimateCanvas(mContext); + mContentViewGroup.addView(mAnimateCanvas, new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + return true; + } + + public void free() { + if (mContentViewGroup != null) { + mContentViewGroup.removeView(mAnimateCanvas); + mContentViewGroup = null; + } + } + + public boolean isRunningAnimate() { + return mRunningAnimateNum != 0; + } + + private void ensureCanvas() { + if (mContentViewGroup == null) + throw new RuntimeException("Call init(Activity) first, or call it after call free()"); + } + + @Override + @SuppressLint("ClickableViewAccessibility") + public boolean onTouch(View v, MotionEvent event) { + Object obj = v.getTag(); + if (obj == null) { + PointF p = new PointF(); + p.set(event.getX(), event.getY()); + v.setTag(p); + } else { + PointF p = (PointF)obj; + p.set(event.getX(), event.getY()); + } + return false; + } + + void updateCanvas() { + if (mAnimateCanvas != null) + mAnimateCanvas.invalidate(); + } + + /** + * Add ripple effecr when pressed, focus, just like in Android-L + * + * @param view + * @param keepBound If true, ripple will not out of view's area, + * and ripple start where you pressed. Attention, it will + * setOnTouchListener, to get position. + */ + /* + public void addRippleEffect(View view, boolean keepBound) { + ensureCanvas(); + + new RippleHelpDrawable(this, view, keepBound); + if (keepBound) { + view.setOnTouchListener(this); + } + }*/ + + void addRenderingSprite(Sprite sprite) { + if (mAnimateCanvas != null) + mAnimateCanvas.addSprite(sprite); + } + + void removeRenderingSprite(Sprite sprite) { + if (mAnimateCanvas != null) + mAnimateCanvas.removeSprite(sprite); + } + + /** + * Show a circle transitions at view center + * + * @param view + * @param color + * @param listener + */ + public void addCircleTransitions(View view, int color, OnAnimationEndListener listener) { + ensureCanvas(); + + int[] loaction = new int[2]; + ViewUtils.getCenterInWindows(view, loaction); + addCircleTransitions(loaction[0], loaction[1], color, listener); + } + + /** + * Show a circle transitions at specific position in view + * + * @param view + * @param x + * @param y + * @param color + * @param listener + */ + public void addCircleTransitions(View view, int x, int y, int color, OnAnimationEndListener listener) { + ensureCanvas(); + + int[] loaction = new int[2]; + ViewUtils.getLocationInWindow(view, loaction); + addCircleTransitions(loaction[0] + x, loaction[1] + y, color, listener); + } + + /** + * Show a circle transitions at specific position in window + * + * @param x + * @param y + * @param color + * @param listener + */ + public void addCircleTransitions(final int x, final int y, int color, final OnAnimationEndListener listener) { + ensureCanvas(); + + // Get max radius + int maxWidth = mAnimateCanvas.getWidth(); + int maxHeight = mAnimateCanvas.getHeight(); + float maxRadius = (float)Math.sqrt(maxWidth * maxWidth + maxHeight * maxHeight); + + final Sprite sprite = new RoundSprite(this, x, y, color); + ObjectAnimator oa = ObjectAnimator.ofFloat(sprite, "radius", + new float[] {0, maxRadius}); + oa.setDuration(Constants.ANIMATE_TIME); + oa.setInterpolator(ACCELERATE_INTERPOLATOR); + oa.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + sprite.addSelf(); + } + @Override + public void onAnimationRepeat(Animator animation) {} + @Override + public void onAnimationEnd(Animator animation) { + sprite.removeSelf(); + if (listener != null) + listener.onAnimationEnd(); + + mRunningAnimateNum--; + } + @Override + public void onAnimationCancel(Animator animation) {} + }); + oa.start(); + + mRunningAnimateNum++; + } + + /** + * Move view out of window, view will set View.GONE + * @param view + * @param listener + */ + // TODO not just from left to right + public void addMoveExitTransitions(final View view, final OnAnimationEndListener listener) { + ensureCanvas(); + + int[] location = new int[2]; + ViewUtils.getLocationInWindow(view, location); + final BitmapSprite bs = new BitmapSprite(this, ViewUtils.getBitmapFromView(view), location[0], location[1]); + + ObjectAnimator oa = ObjectAnimator.ofInt(bs, "x", + new int[] {location[0], mAnimateCanvas.getWidth()}); + oa.setDuration(Constants.ANIMATE_TIME); + oa.setInterpolator(ACCELERATE_INTERPOLATOR); + oa.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + bs.addSelf(); + view.setVisibility(View.GONE); + } + @Override + public void onAnimationRepeat(Animator animation) {} + @Override + public void onAnimationEnd(Animator animation) { + bs.removeSelf(); + bs.free(); + if (listener != null) + listener.onAnimationEnd(); + + mRunningAnimateNum--; + } + @Override + public void onAnimationCancel(Animator animation) {} + }); + oa.start(); + + mRunningAnimateNum++; + } + + public void addOvershootEnterTransitions(final View view, final OnAnimationEndListener listener) { + ensureCanvas(); + + int[] location = new int[2]; + ViewUtils.getLocationInWindow(view, location); + final BitmapSprite bs = new BitmapSprite(this, ViewUtils.getBitmapFromView(view), location[0], mAnimateCanvas.getHeight()); + + ObjectAnimator oa = ObjectAnimator.ofInt(bs, "y", + new int[] {mAnimateCanvas.getHeight(), location[1]}); + oa.setDuration(Constants.ANIMATE_TIME); + oa.setInterpolator(OVERSHOOT_INTERPOLATOR); + oa.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + bs.addSelf(); + } + @Override + public void onAnimationRepeat(Animator animation) {} + @Override + public void onAnimationEnd(Animator animation) { + bs.removeSelf(); + bs.free(); + view.setVisibility(View.VISIBLE); + if (listener != null) + listener.onAnimationEnd(); + + mRunningAnimateNum--; + } + @Override + public void onAnimationCancel(Animator animation) {} + }); + oa.start(); + + mRunningAnimateNum++; + } + + public interface OnAnimationEndListener { + public void onAnimationEnd(); + } +} diff --git a/app/src/main/jniLibs/armeabi-v7a/libimage.so b/app/src/main/jniLibs/armeabi-v7a/libimage.so new file mode 100644 index 0000000000000000000000000000000000000000..ae80f344d4ed7c3f3120caab94b6b6598190e3c3 GIT binary patch literal 279816 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%0A*H)pzabsfhQbsWXirRvnmqE8vz zy@3HS(LNhKbrM}Y>e*v$`FM?TwtW)%k3I@A(_3{~dytIqM0;)$eYC1$G3kcw?P9DRnJQ*=D9OQAD!D(hLk0@saiu>dJ z^-=_S9!AJUd>F!Bd{5#c@OZ>;k`PZS;<@0@1nmHN6~U&8l)pv5evoxSIEOer__{7T zgZOpO3E*pwO&8aA^j)srJRN;c-;3+Sg8mcy-!(PM1)MpQ@F0C@)P)j5TX%U zKJ{SaA#7+N*sQ}_5g(1T`w{T7Dy#|=KhMJo5Pz5JrGV1+U$-F`z+VJ974&21(X)_K ziYFl6UWbXNJ!weu17vkzOh6b89F5?EK+ihJ=zAaZ;3o;xAB6+yk2w84#5lxjK_>$r z1wt#;VxC;V3Jvg7@0^(Jm-61Ony^rfW3)+sy6QPrb_)uLsBXA8u0(hyQ_(?D2 zJ6$h<&O&$u{0Bj`r#-L(;T`bkyAt$daC!uB{G)2LL9;so`$}YvJl}d;LkXZbm_TYpBInMfX*<;zELz7JHTlZXg8kzxGtYd zh*MHK=mE~B-({JNK;JKo2QL`34QLE_-4Xr?+y{Z4aD>OeE9Ge|h|~A}wdXV<`QUXx zpr-+{YoNCgenfmcD863B@RK&|69js`h|B20zuB!nCUFP=_Q?h5&9;5iWbav6SlP5G_VMkozJ zSPuL?m(h31wC7#Un+HZ$#PO4HEE0j9Sl~qngSk!tct;TLiI5DP^}zUA9J|ith$_GT zf}iDLKO#=wUGK>Aq6s7;1R_l0ytRna_bu;&+BxqS(oF-_o>vh`hpaW17lV!lZ#8rV zBK`#8m0UlJ>-5reIsXvorwE4;<uX-VN!(5Wj`64{_}YhinCS+Yw$tm=3`}gk-Le z0UkYNDpCI8XTFr*87KrDfG`3f8+uky`o7yNgx(0+a}Bc2I;`>kUr_pnSKxnmIz8tT(_&XvqX#%X#K|$o^Xvep-XY@x-J47nuyz>AbFszov^* z^tl%JNF~z*{=T4jwNA-K*mt z)y4JER{(-^j0K2)sKfV)dUHQ@sf93>(!G z>Yt^$I7LH{Mg+~`{18qDgPuhA1)(+1cNv#Gz;Oh}`JDa?TFT>pMtqUh%+sIMDOK~h zi_>7}Ji_(b==8McT^=a`eO6@t;^>a;tzrM6{wTzCL{g~;(z6O0iYSczk(j*yp5n=g13p|OvF#{cqQV! z5I+Y>PblI!puZyALbw1tlIzw2Z$x;3V?^0-`h%wv!T^L*2s^ku9dsU#_XYncT{=MJ zNyT4(@LM4)Mfe-=dC)WjdY%A%1K~r22H+P#-&Q2*@8ACen4W(lJdF6G2%mGFBEX+4 z9sUbv_Rxf&;~;NkK>a~OLH`Fd8~k+$yAl5j!bb@73m`)9sw@;&czE`#3$o z=|N5pbNUshPEM;iJ&FFsJP} z4db*krzTFjaT>*GFHU1Q?ayffr$abRdm6_TscZr$abR z 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

    IsY@eB%gw-*~VX1}s)cMuZ4V?FXsrQ|UOS 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

    +e9CL&Vn4~Fd_fF*(u@2)?We#| zh8Qqz61*`MCRCwNNmqp^dSj2h3oCF%ce^K6*$;CMmb7IJ)bW|N=$Vy6Aa=vFc5M+N%adKqD3y zU_Zk95vKfrky#lq<`f#}{=XmL{RkgNSd!aI_w>Xt#!KfB=ZBrNj)^r)$4=*v&fgZC z-~Ok$!`}PZ|JQl=)lKt2c{2*jpQ1k$mOl~ap(pSX^3dNBq*K^Fi}?k71IH0RF=u~5 zUe@RxI6%KSzJaIlJ%scI(D^OWGRmVadJgN*G;5_F`$s1MZ%g+iN+18-eq;wOPRuQ( zAISGF`x2$gs5g_=kF|NDevD)3MEzgik8ZA))(_e$;sK=@BD}GGD-phPUgR>1y<6fS zC)N=7do(4bE`AWacI@q=Dj%M^~xgazO`B&{rN=$KZfll83Bolm%kcjRhK zhI|3nQ;rA`7eg<^{ZiaC|K$ukc)?aFIYBiC*bIdik{~`AY_drG^nb=L#s9=X`~)&kNxvaP zgTHwErI$QW>V4FH-C1sfNcWA7r`gGDC_#Bb8*a=ls=-S%UP9(7^Xs`|Hf> z0j(_MxfDj10DY{80XDg$K9zyysi40M-+goVZCpa&?>zn_4(c82oI>&8lI%_e3o8S! z4l{lx{N}LPQK680XyClr4D5lL97zi4hle3U;MYZepO#BM{N%=O$k;gu{G38oz^g5X zeepL{cqzj_(U&$n+SWmmHzH4i%tv^}u(__*&KPDwb2Q88UaZXc+^fj=0`?|ou1gU^ zDihv296m8;*=^kO*0&r@PH7vNF2!B3;xKQLwUhAo$K=nj&&a6-PLx<4p;nAlw@aRt z!{+zgx^LUVrYC>05EdBAaP3nMNn45A$eGd7FKZvFW?N)_$v%yFYT?mZ$+I=pvVHOw zm}kiz#vt*SI5Q{R_v&K!W`jM{DZViLL_B$f*0Owz-KT_}#O>Uv5W7z?SB~D&wx5nD z4l`k4SY;soY;FiLt$VFuy!)kJi$wUk`o-ZB@6}%Ijjg8EmYBoR9Nt92GsfuaFKN%< zG#==Iz)E9+ZzfkGP*^C^lykrEIv~NC8UZ;xpG><#!T;6+235i?%bf=Cq3Ie;?>e;Og-Z$*?R zP62CJNH705BK5yli<(f&-L&?_$`efvhJ)mT7Z!>aD@AW=(wSO#QXd=QuFE8e5(!}p zYP1I(PG3T%;_O!F!Lglo2la765U+W2>WLUma*P;Bdn`utr-;NYgAX{0c@s4aUHf-6 zv7W8G%5g|%zp18J_|1mw+ELP`=V)!`BebsY3H`D&wa;6%)pFRSyzOveDuwhT*D0i@ zv`;zi5GZaf1gvqlJ8cqdfvSx)fbt(Buy@Y&V#crdq+LpbQ!qjDOO__X3zsS*==%h= zG$TWGV;S5JZ_MqlAQuvbkDRA^B}PfRqGg~?$xB-#?y0F$IK4x4Ox5-+*Z`FJl2}z+ z`6J1XsQnTTJ5zN&QJo}(&l=O}NA!=x!UsY-AAyWKmEZqytpS?#5}tTJ^UK@w9ZWlY z&mI)ZNqvty?&8=s<$S3>zuDD%S1K&LwXj|L>zEmD6js2}c7jm`9kAsQ88lV4T4ZOL zt~UzL=-igBf(i@U$+#EsWkkU*Dr7QYX9YT!Y-WY`Ea=+YR5J4&crtn$HYg)Q%dY>p zN(PUGBpd>ii0?PR=LBOWcQ^cCz_%1+Ywm)7@LI?36Wkn?*)($HIZAk+i1lWqLT3o8 zOnHnE%m02%kv79i{I|ijXB_Nul8Ca)P|40^eFK$d?*F0g%j07FzQ>( zEdx1d2fzOcc1juGZ^bA!E3@ZT_JhCwI=$L7OktBVHAaQChU$efc@!&e)ZyQcj>5MDC9=(hvArwqTd84yeH?32*~AHA2ki+`VtY$v z$^e!T{EG*1+@B$4>35DYnvTv;w)u&EJKM^3a!RCRG~*zhK+0Lcq8-=MDwsTf0Uh85MNa6)md#lm-FwAD{^~U$q z&WN~9$y7Vg)uGN1weRYM)xbCQepH{Q?sVG8MXFVe6#HT&{5Eunc5f5lr%m&#F<9&3 zK^X-G>QS|a8;uXauT$3UZ`>I{8Q~bx9F^f0)jEWw!M9xcdb9c~^r4 z8nq3UbWyHk_qzzadsWD9r}}xrFTC!IXa_k<`^WH2o>b$EPn4scs5AUU75x-9`L*(C zEL(Vo?rxi~KsOg}1xt8Kbkj0;7mn)Jso1KUc5mvAwxPD&9ZDf{i5|tC3BOk$w}x6x zZ3=W-LQV8{_NF$$inpZT8%K2|OQ|h-_&ijh|1K`eT%MM;q`|vdBI%_Dd@k=^jk4Zs za0M+<<$`K$2Su3KaG;u79$q~hEY4b6^xT$8t+A!x>{)Ad4ORJ1jX|5$?=V+0Xd|K_ zTa2@%c(J%VpN4X*+IMno;WmKV5^g8Becv#kiY6VD}VV5eP{#U25r#ZqLW2G`}>cy z=KllQL4T*Ukd@*4ufNK`mZgnq;hQ5jQy%h9^%CcqS!{fZlw~(jI5DhkSDmoVffEAd z4XH|&onBaHp`0A034ien;x|Ox1Jqh|LyWjhzexPX-%|SwIYMoYmj0qF_}zMa`BYn| zzum0REz3-|M1C`63~x>r_kX5%81gTDYJ;i1Cgc@Qe&HtreJw*Hpl&IVApFp!QbK1fEJg3DcC4(;V7~0ZRWX{jaZa{v7+2sLv>R+UziTxEB{;~x5?5u z19Bz#HG8U8_t7oUfat3Mf2l-H-^vk2au_AVu`UK#DFc)OvAw@mR;TyTDrttl#HegI z&!rRmjcEMFf;>bZkzSvjuci&$~ZP_emoNOR6f^Ty=DsECO4sz3WOm|e(v(;BjRkm*aZqWR(damIb zYKfktMlluJcLE6yS06KNL-(oKET^C4LMg#mG~pLCjmFoOg@b{L%UvqPGTqhi9*P6oA7TeqV;ZT67k7z0&Z%$#4k_m+!f=xwjP#IwReBu zzr{c3XX^ReUuxAZuUwwr(9Faa%it+8xJU+1lED*X@K_lvEyy#qO`_`4IuX)j$Up_npVak&x(>?_b{F(JBGWCTr^^TRT z=>uix>tyIXW$N8!a90`JQ3kh{!HwbIlPRyMY`qN5mcjL9a6K7ZR|c1*KmM(C|LbJ% z-TwjC|K7U2qYQoS+1B%WTn0~*p|6y|^=0q|c$mS|mpasolpxE>I#5f&rIL-GsTA4I zVMn3-K&=SNAr?vLMGEw13t4)djSPD27J3U2maAmT(lh%N$+ggn`O~u1GTJg_X3{YE zJE${cQ28@Ykq)E01LHHc73)OAkpHjriZs18tskNjT4o15D3|Ds$%iQqvtKe7v_8dS zB%fJ-=Fc3vh{=;Nry_Z}Z8R+#gQk@}88Wxx`L}ZB95FsK=kb5~S;W*igQrN1mY>!q zlV>YCVd{`UFVbvj%e0vkuxFSSI3@>sW{#1~pl9%8_WQTbOg?|3`TwPyIhIzRnRdmn zCFZ*OD?LM>%sCb5GIZXT@tN5da~_$t!K7i%Bhv=-89ci&J~QNRWhYENOd3XC*NOeG zX!~Kv!(3CXbYjZrm$@F9HqYSC&G^i;XNC?PQ0Edp zs2|Da9*oZxjL*nlOe4pL1!|1Xx{S{v=6D(N%d~&dP~ZtnQHQO{iGH+c-5S$07WjDw z>O5LR>%0{`LvJF6pJdYf?Xzeg&7bteXv_2!t$aRX?pn>SS<6^Nof)5rEz$C~;>oZp zrfjWf7!t9G-|E+vDL$@MjT28X2 z$h;8yXV|>hHb^;hjEs2{$7nJ}rp++<{LQXNu0{Bh@siKXF*5pUWpB)}Fl77NJTl8+ zNvw= z>=FLf_WyM`mEDk>m6ai~0BHTCIXU2HL$WPE5kPx@>i}Z?L4+ABQ7MgQiHZTDaTSW| zABoG$0a7VSB9&vLoi0EM_+;b|`gDL#(^H5VF^VQq@|7Xhkdi?RTsTp0kdAH-oxCMp0(rO1h@5Z})dDO@7u z+5oZK3~?BzJ-{^JTO`KI0cHZa zCG9K#qMVNqLU~dl&>fHy)dEC3(7V4Sd`khMJYnxh|LXu^|H}bl zdBJ;9{)mQ|BL5E!$?Jd*`wOX$1&DH$eer60>D7k z#^fY`+HD$>MF4Fz8k181vb7tN7XvH>i24rHX{6=x2Z;1Z?FqeOCqkdnh0tf|HzqHK zdiCy&bics>aU21NXUd7{0n+`l37>j^*l&sf)Wdw&14Q~-#6kWBfPAR0GbDIDKyXVU zN2A8%Kq#k-8Y6krhXKq7i1cPX8tL(hObNdPfM~zzG|Uq1MwqE2GVO)XS`>y5 z>Jw;~B`N@ja^wND27bl8pK**0oY*&Q)MJdAw zpXC72F8FSZ$qK+vWkh4L3i9=COjZY&h zC7?069H2fxq(g}17UFVOaXCUP_owj;QM9<64-nU53c!4*PXky0@SwP!zzk6V;JHv< zPM2qhYQ*(b01rZWy|`R^a$|A^;Kl$^9yY+;fLn-hGa6=yTmhy*xxW|>6W0feas4T< z4zV3T)MpqCvqZrFQGW_+NVy}xFrf1U$Ojkz5bOQKOxz z71kBBs{|MeFnSxTXMlCvk^g=XKb`Ok28eNGH$oU!I=}#+OV|bL7GT3}D2Mf~y_b~N z?IY#XA=1tfAkwcpO!zGai1ucBq%qkW$`y_`Ci?+Q1LzMhDF^Do-m4IT9q~^RJoi*% zvMbatKG&G+4lwY1W3ngW7YHBY0>UTwGT{?&mGtXfNaV@7M&v(;5aN3QK$N4rh{#n6 z5c%&eCjBaukn#q=ZJ|8nZey}Nz|zvjWJiE`_XvITeIidlC20rWnxf|+4It91KPU8y ztFT{rQ93{@Z+J<{{i+GQCqOLMeNS+8fY@)qXM+0yvViQ=$) zpeP?;0??byXi823=n4?~Sq>1(w~EUT0?f*^6+IG{zY~|&0~7(BZ9MX~6^Q_%AE-wh z}0a^nz1GsQSOgH| zQ_*Us=SzgJRwUBZ1|CS)7Ge@Wz7Fg)09NTDeTGP(J;E%JE<(_ksTj8a2+xm`^Jsj( zs2Ct0aNCYR2lJBwFbw(Wk@BKWgr47}nI2aLKpc;^KCDroE9^?>{n?~k+klkonUHet zo*=J-lBmuU>lH=ry#bdyD$?x(IQXMSX5g!Uk83}e8-Ti&NGC7K1Bm4c{YiPAb#t;G z;AR7%J=jg!AfN-iDA+Y8yF>Y6fJpB>l+ddyIoTAT!bqYAeSoMhTZAA-F2Vx}B2Q0%pzkn%3PA5S z8v2cfyuu4|eZY;qNqIUzq{{<{<5}*5eBe9)AlgqIz?%1v2abUl0MK+SskaA+_2~d{ zJbvSV2I|wsL+k-q>QBn8ClLOw0E?lVnnZAQfY{yxmG@CB{p|xb|e|&jRdm3Zb(`2z0SDen50^Dk--QM0h~t&LRCL0YthifY@J#7_SW? z^=vNGL%a+`2z0do@jM|g82My~mIKrV`F8`<1(*jA$K}T(^ji_ayc`S#2=lZqjI`4a zN0=$%BLuz~2s0H#`qN0cA42etg$TiK*hXOaeo+z)Gep{az@eQPLXe|Mj4zHvJWI41 zA=IY>#Brqo6ukpoN5OgpSU4T(fv;l>DYuO!eEb0>L49Bx;IO{R0VV)mnn3716JZYy zcosnHw+0}N*M27A`$g{%0^hJ%0AaiZ2w@$WCJ{PUfXFukpe^vRokQfa0EqI&0z`W% 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 literal 0 HcmV?d00001 diff --git a/app/src/main/jniLibs/x86/libimage.so b/app/src/main/jniLibs/x86/libimage.so new file mode 100644 index 0000000000000000000000000000000000000000..a53c17bf5e3bc62946549e6cb4de9ae8ef8281b6 GIT binary patch literal 597104 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

    g&rZAf)+2wzBQLNmN?|86jC3P}*Gx0#C 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 JEg=5#`@am>nh^j1 literal 0 HcmV?d00001 diff --git a/app/src/main/res/anim/anim_fade_in.xml b/app/src/main/res/anim/anim_fade_in.xml new file mode 100644 index 000000000..d9353fdd4 --- /dev/null +++ b/app/src/main/res/anim/anim_fade_in.xml @@ -0,0 +1,20 @@ + + + diff --git a/app/src/main/res/anim/anim_fade_out.xml b/app/src/main/res/anim/anim_fade_out.xml new file mode 100644 index 000000000..fe4132b74 --- /dev/null +++ b/app/src/main/res/anim/anim_fade_out.xml @@ -0,0 +1,20 @@ + + + diff --git a/app/src/main/res/anim/fade_in.xml b/app/src/main/res/anim/fade_in.xml new file mode 100644 index 000000000..49dfa2b6e --- /dev/null +++ b/app/src/main/res/anim/fade_in.xml @@ -0,0 +1,22 @@ + + + diff --git a/app/src/main/res/anim/fade_out.xml b/app/src/main/res/anim/fade_out.xml new file mode 100644 index 000000000..7394c196c --- /dev/null +++ b/app/src/main/res/anim/fade_out.xml @@ -0,0 +1,22 @@ + + + diff --git a/app/src/main/res/drawable-hdpi/btn_check_off.png b/app/src/main/res/drawable-hdpi/btn_check_off.png new file mode 100644 index 0000000000000000000000000000000000000000..f14f60d7ffbc617aff3f72f80e83798da68fe829 GIT binary patch literal 292 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWw1G$2?seLn>~)nW4yc$U(q$Iulc)Lz7Op!!?BsbDBQ~cDgn_ z;fhGnt!KXRcFLB`ihHL2x#FYtd)Z<&76yhN>2Ak%%?;8tKE6G6N>pZ$YqIm=y_-Up zojV+>S^RXGmKIO%svhZ0nc6c-a(-0&zoi(dy>#mj>Bh;2pPXfK{ZT03z`)3YMtr*P zRc}iOAM^gqi`yM+*46dx`WS7+x8r4U>#H8=qFn6_G0WdSEDh?J{C!Wr{He)6A9#Ea aNni-e64>)a$8R%`&*16m=d#Wzp$P!-UvIAf literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/btn_check_off_dark.png b/app/src/main/res/drawable-hdpi/btn_check_off_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..a5ddb4586e6635cd3110e0324ab04c0dd3fbcd07 GIT binary patch literal 291 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWw1GM?GB}Ln>~)nW4yc*nr3N?W&xbnmyL8l|L)a8`;Ul+3n-* z6;JP*a?xbstdPt1ChG`mf6DIN$HBnx$9R_UlfC;RXRq<&Ew7HW42=zTu08gtIA)(| z@|*rE0nu&AQ2D`O Zz+mMf7x_;)SRTk{@O1TaS?83{1ORH^ZuI~F literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/btn_check_off_dark_disabled.png b/app/src/main/res/drawable-hdpi/btn_check_off_dark_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..791b8fea74e1a485596d45af6adffeb21c13ceff GIT binary patch literal 295 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWw1GCp}#pLn>~)nX!=Xkbyv3^=Sc#GhbMYJN7cZ|IaXkizm&7 zxuf%v+R?J>DY?!+_B|oTpa;G@1K-$(?A0meuzI%)cyr#Js+G&V<|w#naas%;NYJU+26!_L^UCBhbSN d2i+SO8B&~OAAOs#Mjq%X22WQ%mvv4FO#o78a?1b! literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/btn_check_off_disabled.png b/app/src/main/res/drawable-hdpi/btn_check_off_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..66d07f82148e630a50d4a1f41d2ce9d343fe35e5 GIT binary patch literal 285 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWw1G`#oJ8Ln>~)nX!?tDS*dyzDG~9nt_$T;{*IZ{?{Km@ZiCt z6AwPHt-pAXldEx`z#TK+KHQ!4bWO^+ z!?s%1PcMe2WUlhsp}2fu(?;!&f9h_jYEC^AW#hV_bjF@Y1#WG2Miv1F22?_UH~GC2 z`_o+o{=av!KJvf&JJN#B;@n0j-$T5THpx-zF30|uBjWwM{@JvO3I>J^OmR#+3=7Hx V8e`vtt^<07!PC{xWt~$(69E0GZ2tfN literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/btn_check_on.png b/app/src/main/res/drawable-hdpi/btn_check_on.png new file mode 100644 index 0000000000000000000000000000000000000000..91edae05d6c59d1e96c12bb6fd13134ee166ff94 GIT binary patch literal 427 zcmV;c0aX5pP)cR>~aB=ehjvT=w=n3>S644lGq41i<_a_e-3i&>PM+;*V3WY-X z=Qv8EqHqn{1ym1_ErD7x9$bqCbrj&Yu=Nf6xo-CB6%A@BoX9tLhq$r~5+H$R zM5jq@6%g=~tWw(qp71L0=2^H3S3OX z>j;Pm)}Tb&YXrWkfCnJSTiZ)b!B-U+z4qi1jlx$IZ~-D#kZ2rU=NXVmpup>PpV!vp6Mst}z}G#M$R&{A)jq3~mk`t8vCT_L zGk~BK35G&4K7p!d_pqafHd}ZfCNb39Z_j` ztO5dlBO8xR-~|u0p38bKB<`^Ybnv~;w$NrV2n^t1!W1>Xj^PpV1|LGeE4-_~Jv6+F zfLLJ7e<*vSz}pJs0Mhiz9uEa?E3oQ7^b(`O+X`d=Q5#5%4)5{~=p<0!U8~SHi3=9I z(ViG?{?P4R0XA2B-w7VBos2z^ef$$X;W208fdmG;@1;a1fdy}LtYRS{mc#3qx0Gn` zw5cZnB!M>YzS&ve5K!Rkyp)i2T^#W48Se4=36>EhqfjUm3Pq8p00RJ$S(Vkvf=o&P O0000KWez+h$&AcmMhSIkU|D=bT4u$8sFrs2M%X+j!!57uiB#wZjDh4RngI*Z!2-NCeh zu|#C6V4OJT&Df%a2K*kT)WesXbaOGbDAIVN)Z-J9Of85)-vcB-0?)`wBSICB@P}@Q z&;%avQ0qlk@1;Z?ia-yqytalBhafP3hXYgA{49h=$Qry40juz)0<~{=69IX_%6}+( zPT=PX6aZ2wWl#8mpDVEHn~7-&r|@$H5`f4wB%H&W?13o>6nN7r^iAT11+UdoYDh@< zLXkMWwWQAAZ5?DUzXT?{&69W_fdOy(D$z?|!Q0papCn@StO&8CM1!ZMo&=Bt8qa;R zvB1Tnz}2{n!u6n(*9FLy;r_i|!e^fug+ifFC?mNGFaU-EnT`#974iT8002ovPDHLk FV1l8juiF3s literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/btn_check_on_disabled.png b/app/src/main/res/drawable-hdpi/btn_check_on_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..f3e396cb05ab3ff89f5c0ccb6f60aac1e496f541 GIT binary patch literal 407 zcmV;I0cie-P)wfW;2y%g^migkZ5)EY?4(F_C}AP&9&o5ZVRYx}cHeGf1I1JqKLrl1O-!tb>UY5?*h`1^NB z1wgip`W!r@0MHhG0bWyBLTKSr0HB8V1t?R8_W@81*6xS07bLt=&#{$mhF4u;1-^o3 z;6cFfIra=pzl4i2@;);#(Y5-npG`A?6+XuXaDl)a`5*u`h_T)$Sj#3DfC=ITej={< zpa5+U0(rv*2QWckRhuXPO$f|VX$8Q(=TX5&jc*0HBW1Mke5hxDNB}bNx!Jp!t8|Z( zLMQOU_JZs6dZxbzF|XIpqP|{du~;k?%cz$C0{{`*TSLYeP%i)g002ovPDHLkV1oA0 BsrCQ> literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/btn_radio_off.png b/app/src/main/res/drawable-hdpi/btn_radio_off.png new file mode 100644 index 0000000000000000000000000000000000000000..68574b5e20889d24bdac46f60cccf8b90b1c602a GIT binary patch literal 627 zcmV-(0*w8MP)o&<}sS z^E=z;An@?;@bI{13>f>0ffL{unC$tF09L>fSYL{ODKJF=CXfPhU~w)15%Bduv~?j@ zufpM(` zOvvZl;ngNTGPad~F*&4ahXQJ{qm4NPw3D|C+@stx_DMBp-AHROCh)JDhw>3Z9BXKxuPFu|#@gY%2jZIen*& zRv{)g&|2rz*G*!2N0~Dxbf_p(bFL)b-9QD2){IfWjI65AEoe+CvI>`YND@w*P?gEZ zzYy!ltHg(kl2Uf~1AHVaA#2!gtZ_{}zrglwEU`cKqCP+Y4cV(@QT~Lh(P#*f6Mx3Y zj}(>O+V#88?y%|Y!&duH>+RDbeX;1} z(H-XdfX=Y<73|&F%g)}tbI(4xQe=f|91^amc~V3v#P*AAoLH%dh=_=YPe$TP(BtvQ z;Znjlz$g}8n_ygFj7gHrZ$SVIKfN=*c^(@E8|#ul|faztN!~(jg@8%sW; zEi3aFO7$8~;9QhpI8wIW>j0fmJN7p!KD*=u%{d#o6P=_-^jU2c)?V3XV~e#}_SsrA sgw+0;y9=?sd3!w~A|fKyT`L4AG7u8B8=c)aFY2vX@L|1*0qmioYn zcpzGks6GMIeagwEHn<C7g7|z%bA!E2RWu zZ?3?B``%bQ+B7S%wFt1}`n0xa$h2q?u+CD1z{(Ay;NbWkOb zImjuqNg#AUAT&*2U_R##2;>W~jSz@scE^oI?;zV7tNe{t*zU1XI&G;0 z9ZP)(TFS~d_5bo(g{#CqFSfXAmVI6=3M93^%iRTSJHKA{@bK{Pcx`+OFaY9A&NN=? R{R{vA002ovPDHLkV1i`_{pkPz literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/btn_radio_off_disabled.png b/app/src/main/res/drawable-hdpi/btn_radio_off_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..fce0f7166350faa520b7cf953802ba64198e906f GIT binary patch literal 621 zcmV-z0+RiSP)zgD2t$lJ>uyxs2Mw@1+0PTu>_2OvyRnP1zKeRtojsyuJR1@TmGxa zMQWOyM3HQp?)})QkNQNto%!hFE2ELG9VY^vGXpm z#FfzJUx7om>K~f^`wQFvuYu#n`&)Wnn-(w!-muA|$^Q-rU~KzIi?bCmq~A)eG!*!K z$lPXS@C;co7YujAh_--)Jug&EtcaTW>>5*BK)?bmHFb)%YzAq^xY`0D_Pn;3+N}4C zttDW>4yn?jfRgQK6B7cO!JB#Zk?k1!pdfArn{9~3kP0Yz5K!0@aB`3e2z!`QXj6dS zg8<*OfI_k_8$cw92NeN1%SZ!@lMh(IlbaBb>EtMyNcN1aC7@)dZ*0j50n0#3l~Z3e zhg!jYauAS))=Af)jrLK2ckKt+KIw zFSI>u&h}!fd8nQ3)gr&KILpi3Hs9|3dfman!NK9O@h`vt3N>?mtT2|r00000NkvXX Hu0mjfBQOlF literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/btn_radio_on.png b/app/src/main/res/drawable-hdpi/btn_radio_on.png new file mode 100644 index 0000000000000000000000000000000000000000..7ae368e9ad16b2af6627cbfecfbf446f8a4ab066 GIT binary patch literal 692 zcmV;l0!#ggP)`;JP16vRU5gI|H&43+Syv<4Yl>;3w#r9Yj2MNCGEGt6@X) zTw*)Kc~A(Qmq1LMYW?P`ux)B@$Y2X3=@g3iz#L|LqI^tMPKAi6TR-l*BLSYft5^el zc8W7W1s}*}knM>L0;Ad?)m;6^;2LpV;Rx7;QsLW}Ks6Yl{5Pd5Ra{yK z>SB!aVMu@j7#<|J*tJqd$tNT^dA;zLr*z8%FiI-P2aso!~MyB`3|-MTE9 zIU7SwFiFH@Gfwx|1%Q!Wq?p~$EUppF4rk0M91OOt0qDWV<>zUSM8vHv4YsWT=yGZS z*i}-Y4Lbl>XlN=t_uYk5&H&tkJP9hL@yKnj5Eis@b^?RGM=n@9a1Eep$;;~=0M;#a zr*Z*cr~QvW;00000NkvXXu0mjfs5U6f literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/btn_radio_on_dark_disabled.png b/app/src/main/res/drawable-hdpi/btn_radio_on_dark_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..09c161b8f7e91c7aef38acfe061b1475e14d1718 GIT binary patch literal 675 zcmV;U0$lxxP)PpCmw$H_uw5dwsffh(>I6tKx$tUDk|+)43QA51uM1mAMGlc4 z3tC>-PD&y&66Vg_CKHjR<|j-x7_B9ASUE}&eSNTOps$(foOrY7k86GnofNKv|BW8B zMIRK1Gh&TIMRYB6pE21iFMO+n)oD+pC5@x6_nMqC^kXs~OfxWKzZX36C^4|F3rj2h zm*Y1i$3K}spACJw5unMIGU=3>`C+n|;Wrb3M9HL-Qs1pJFkrt|!Z%~ho}fc4tY(t$ zfe!+8IUI!OD~B@T9APD7;h^c;3jt|;RxQd1sqNM`fjW*21Z|NS_Ti1dPS}WiV|Psg zcL!SnJz@R!jovj0bT##So3nsZbznoF-br6mM!Q04+qVghErEpZj#RI>oNr5B>yYon zH-U{ojWVY)*c27DX{xz4tqIWXSdXw0vVgni%rFzBRPU2d#WGa-n#8)rstr?VM{mjQF@y%31XH;QQP6GFmX5qm=SCyD_#E247KM z;ZXm4*D6nOvBi#&k9oByNb2z}cQx9+`Sp50KtMpiYvV_N0RThM5IquoI%)s_002ov JPDHLkV1nZFCei=^ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/btn_radio_on_disabled.png b/app/src/main/res/drawable-hdpi/btn_radio_on_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..17b66dcab16a02c9feda79a09a99f3222f153017 GIT binary patch literal 723 zcmV;^0xbQBP)s z95PB4#}dFMumP;6v;4Ue+j4pyK81!7iv`oz;K%alvO%F zpZQ;i7Kt!84@LUeugdgMZHL%z;{sY3JjE0(AvJQPjpI-ZO)XW*G4r>Kmj^kLQlJHC z?!1_+#F3%r?|@}8>#sTe`wN@_E5KUf`8ht1YzycCPf74<$yWsuSoQtH$%#dj@oz0F z`3AnP6Wff|v~4lFQ-DEnfsHU|kx2TMjtJcq=;Bhxcw%g$X0Z>vOxbCwm_v$R#3E`X zme@DO32>4XSXndK1hI}}g1gr|%mlWrGMmA4&3khTXp-OKl>G1wyzgTlFJtVc4FL^u zNJ+-?B1ZxX<}8rJnhhHQlF2wvN!lX!D2jQ?yK@VmNB%m2rl@Pa!HU!YU zzss95((07y!>=mVmLx5u5-3002ovPDHLk FV1mWtIbHw& literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_action_add.png b/app/src/main/res/drawable-hdpi/ic_action_add.png new file mode 100644 index 0000000000000000000000000000000000000000..f52b21f3374fcce7a642f6c9bd11b915b4a7a728 GIT binary patch literal 188 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBBuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrH1%#d*3ohE&{obHkDAfC3L|pt79Jyg%a8y8@iJqGnh+R7`L^ z64X%?S=OL3pKaF9%n4!vS!sK7QyUlI7B$d0zuu$Tw5iNVv=&t;ucLK6V^I6CA2 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_action_clear.png b/app/src/main/res/drawable-hdpi/ic_action_clear.png new file mode 100644 index 0000000000000000000000000000000000000000..1e4cc7a8f369d46b3fedd75b988e7f2916f96d7f GIT binary patch literal 175 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBBuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrH1%1$w$ThE&{obM2rIg8+wXprrgf{rYcV3t6Ad+ZyO*I+cmR zLjB~_!_RlW&h20O$Gk52qM1Ku+OtSbO-;q>b#J3T#4O)+Fl(db?g)DZhVC{7W`=j? W=Kk}`bes+}oWax8&t;ucLK6TI$vm+D literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_action_close.png b/app/src/main/res/drawable-hdpi/ic_action_close.png new file mode 100644 index 0000000000000000000000000000000000000000..68138ede1ac933143f55b1709336380d4296e90f GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBBuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrH1%t?+bl45_&FX8J+i1_KV)USUfw_k{o6?hd`ChR2w^7)lT6vdvf9nPv};c_4_|k1|%Oc%$NbBBuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrH1%?ecVS45_%4lq8YTka@+?`K+VP6sZsY|Nm!ESjzC!E+Hi) z#voxIqgC3!dLM)O1u~C6$eR~PbeKGGSa#=v*Oe(QGnwXT1WRbRt0olsFsY^)1t~AN zu<0*z+e4PNRL@+7B}@zM@E*|EZ7{)dEu;6MJPD2C5)V#(V`HA!#&lwew56)4@1mRw zs#(Vpr@nRfex(?`^U|W9hA*TtrYu>=V)WF_@BCMWnMKa47kl(hs`^l})V)2~O_qg) cfsu{Dv&PBMZ2y|k1|%Oc%$NbBBuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrH1%&Gd9}45_&F=K5*g1_c3@fN5Mh2ZJ;`trxI(FJg|`z+x3~ z{mAA8eQsxcl6G|beloeCn5S5`XxZddW!HG7URM*;-2VSy*GBbh+sbC=Zug_=@2-By zk9MASn`7hYtk3sid{h=Wa50^HeoerUMbHIIT$t}zcJ6hc!jabXHyR7w8r>LHyp%3V r*wJkIP(|(L3zd5-XQ*%DFksklWv=e^B*RFcBN;qh{an^LB{Ts57aU%! literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_action_filter.png b/app/src/main/res/drawable-hdpi/ic_action_filter.png new file mode 100644 index 0000000000000000000000000000000000000000..f8e86ea573f2a013699ea99dd0d8a4abbcc17fd3 GIT binary patch literal 176 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBBuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrH1%1$nwShE&{obIp;9L4m{NVh>-Q`uuOYi<}lc+P`%{N0%le z!!ebe=RVri?^~-lbLN5koNpp6D#o8zG>L>vu-*PG)o7j3-3P8W_r&gzXJ~NsZD3?r Yb#d;$$=2Q3K+_pKUHx3vIVCg!0N2kymH+?% literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_action_list.png b/app/src/main/res/drawable-hdpi/ic_action_list.png new file mode 100644 index 0000000000000000000000000000000000000000..a73215409c8d912eb6e8836db7694f34f0687a9a GIT binary patch literal 339 zcmV-Z0j&OsP)F6XU=)lnz=$sJ|Nnmh zAk`1V2|)Y+hyxiJ881NP9H?yn1EARt@Tve>TJRqg+((k5HfTV%p9zbb1km^j^o%lW z4nhyY0|TD0fI*o6#QTA`ADX5{!6+an9XSB80=@0URxpAbu^%bBCjtwv1yH#LD%*bm zX!Zd@A=7|Xc)cI!f{{=`NmIarEf|5g9*IAJhK1wVuwbNn#fq!N z2r3v2phBP);|8eQek$9)5LW0CYI-1p{R3Svejvc7Z@~zP9-zxXg<$~@gBtgZNYahe luFFPq>nIonBOU+*7y!E+D002ovPDHLkV1n`?fEoY* literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_action_move.png b/app/src/main/res/drawable-hdpi/ic_action_move.png new file mode 100644 index 0000000000000000000000000000000000000000..3a04194333c1c3325aec904dfb5e1d36bb038a13 GIT binary patch literal 412 zcmV;N0b~A&P)2^hf<9KjJB z0XJ|2H)uKmj?xVr!4b*^^o_Kk1mrEKG~ttvS+ax)*>XxLD|ob^RG9?m zNC*+9lLE{j?eYX=KgO&<8%?WF7+u<(Fw7ETz`R%)FHI^~4?1i1IS>Z-QEK83jNzkd z$==cgz*v{PwAiRNzJFD~n#`kSYJ4#8;1*X0u|_PA3(jT}F<~%Ew&V;oszjGM#xnVU z%sg4fH&Bk5ASw(Z=;lO+z-Yl+qs+Zf4j>l0V>hZ%_9}c1mnRu;h|wb~+qv2P)sL^e#m~3=p-{d) zBs@0iEZ?=dy5K%_^$|AxLw<7CpsO#6|8tejk_9O*0R{k4Is|5vkTswH0000D2&c5wkk|A7}p#0Y|@cyL8X4&ri^1cVS3@#;ZDh$1dGJ*$Wts4;rc zLxLbg1Tl!HxD%K0d7&GIrn{=Us>Yc>7rac;UG;tMQ&nA6-IbORUMeHpe@h9_=n*VO zpd10u9%$$ks#dGp@c*t-soZttXf(h+jPFG~=o}!wih?u)KwD(`3BO%H3>ATBCII=C z`EDQ{=Y^;n0P}k+CdMQEj?OT&hwIxD_cV>pq6>ihv4MC4V01o7R-h$l0h)=XqseFj zQjSpuBUv8q)8;r3?@bU+0GM7#(Di6FT5K_B>qOK2*WA=|@hq!SApnfeB~W{0ew>f9 z0&Ra%_cRcf)7s<)!2D(c97IzJBo9$lqi_;{UMB#I3sS~+NAo#^sk||6QSk)kKRHjK zXtz9^HzW1FqmSq_((r3lsb;j-0x&P-p4y#3S8OYhaSCBxtI8!b$pG+@dk@ibG>C>s zDg)L+eC@>XMi$q9%O84hpIQUhrUVJPsIxI6&S!Y~E}gY; zGDm>mv#EF$X)RBd>s&ZSq8~2C<2E$_nr7!w4>5liiD?4ZN}!IEVEtTc&pS;_W+H%1 z)Hy=Dz9Posx+Z|5RN9adw1Z?fU6Q3tp;cTPB;Fg>cqRZnyvq`SzX^(jUtJT1Zf63x zK{B0bYD+)ncZD|L6VC*2kHE?0^AP|Cd=iFkTNI!dfc>FO_{1{-XsMi$2tEM7PM?IK z+nN9_l5BY*q1OFYAijh);u_Co8SMl>4|r1)6TlW6cBZ%-;96UVjVweDm;f~Yuc3)4 ziSBSsM~Td1ZU}REV%ScjI!5c`Q(DpPsUz?0MvjIJU={|oBTqB*hE9%pThej*6%yD7 zpoa7Gpl==PDS(BFv#N8YQh-I?3vFRL_k7b5GYO{{I2+>EaM=i}p&0oIzuh071Lc oAy_#A4xJXXNjU;RyVD`qKh_5EF$rB9;{X5v07*qoM6N<$g5qG4=l}o! literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_action_search.png b/app/src/main/res/drawable-hdpi/ic_action_search.png new file mode 100644 index 0000000000000000000000000000000000000000..8731851a0d86411786b2ac6a5b934ff72728e908 GIT binary patch literal 436 zcmV;l0ZaagP)4T`HK-vFQjKi#PL(8VFlP>T$vw0eBDdFP5T? z8GK^Cv8J+Ew4(x=kY%`sn@vki6fhi}^5nU`L+hJZpdLMt1w2EB9%yIz%>=mWgf|DA z%S@7WEYRM#o(V#d#R5HuQ`#I0WU2{FaOPWiiv=3c_uls{!*Q4*4^= eR#qZC1sDJbZ#nvCmLvNB0000kbQ)IULB4=Qv5ze}VAf`QiPVd;nf z;ZvrB9!o|91S&tOsm^CaK){}$w64F72#C|k1|%Oc%$NbBBuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrH1%m3q23hE&{oGyNcMg8~o3%0dD6M3JyU;W;fkIA>q6a`2wg zZud^;&|=FX&-f)Q2JIEhp}J+cjnS@4tlzzzdf@G|YwkUx22#(+g z?Iw)i299Dk&<)xR>;||2#pY}-6l%aj)8u|~(SY1N(Qv;gSgdUlTNWE*q`h2hlrZ$P zaT7vJyT6HrDQ(=%x8||(2p}KK2WoGa%B-tO$$d&>s)?Q<1Cm6~kO5^A12GT-MQF+b zWKY~4LPZvPpd4z+L(wJ{^=Pb)X&nIb7G)FFsm*m>OV)I4k`@(GPJ|FKk&%L+5ZYY0T?)HBNbSB6#1nya^L-Z100I#em U+g+ygZvX%Q07*qoM6N<$f_l)DxBvhE literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_checkmark.png b/app/src/main/res/drawable-hdpi/ic_checkmark.png new file mode 100644 index 0000000000000000000000000000000000000000..42f0d98318ab0b5975f8e3733eecda5b99bb1619 GIT binary patch literal 1384 zcmV-u1(*7XP)8Tyic-`NH))#mhFTip*iD5%hzin#*iID(f*xs34$IYf|DT=*5 zXXbqK|1-0*#^@${;hT&XS-=}zaZ6!K;SDwdmZdkCp#Q_M6t)yz?;}76Mi>*?GEHmg zyMqfm1fCls1a$!ONZjjqhX&e|z(>M~W{KdlAgenMnL&s=7xOf~tp#m5U{^48JL1Tf z0M=0i2V;PrjQ2Y}t^{o=;KP&27Fon0Q1eh3Ci)Up~9GpMCGi55^BY~wx(I$QcMIWGtMf0*KdCS2n?X1f=+!;yl;$_9g zY_N7-erUR{<#$tUOL^EpiG8*bM--IxS5mf+ysi{>oxj|KBtfz9ozynQC@yCu9}O&% zrJdpQ0ZgPoL44L9>)Vyq>_8Qu3G9p{9^_;^4mn$j z8Xj$5cAOX+#u7`1#j(!76$fYIzlQ7k7}|*etdA8|)6$^qNl^Ap2;5Yv7`TP|aCQ0m zQ$G(MgwqQ9%N4$|8xcMNnR^+sV;Lg${N!!D%8>JKXjz5bp~Uk9;nNT)Xf*sTYD}Ms zx!qZo0=mvG?70nULe?Eh?2`~yY)rk+VvMFS5&TA#EihDE zrIlluU6ewqM=6@@Fz;WFdmJOhf%#Jptj4%qvyps&qAW&&uUfDbEnO(8dpxmC+JvT9 z_DCr7)k|}Z7tqUCR@!k#lKY8F;HH8e7IuI+Ig>s1{b1WQ+5w`8)7*!ZMsoMJk~{(P z?jTL0xfqO2ul#xKrMmhp`w`;C;0pYwdx4|A6GWRodc1LoE`vUb3iv;j&?LNXrEeZ7 zcR^d>uZ}iHQC1KI-&(*@Sz29ae($^h*P3Vn*y8Z4lv(YqwvE*_P%B(Wf~OE}4wC?E zLk>0=&FF8lHs1eZ?;XEtMj8lU23GoVMN;=6nFQhOtp@s5rQ+GFjq|NFEVHH6aL!=j z9uDKrVR5>}AaoSjS9)VTyRRE;hbmo{!iAQ*LJ2SYlJyCEZlLO|IEbKcvI0L;3uOyk z8v@R)-l5bGtgIigti<`wqcCR&Y6)w|(rU5X;q=3V$YrZ6h*A1!xH9#hyj}RFk4Rs6D9E$Lv4OGi{kSp&>wu1@im{v2cLmE7gp zcUIYr)+OGA+hS|=O}stGWuPuG!FXgKShSwRYQQt7V8_%zuxLGr)qrPE!Hz}Wz!R6} VGcgn8$L|0D002ovPDHLkV1mvAnce^Z literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_drawer_download.png b/app/src/main/res/drawable-hdpi/ic_drawer_download.png new file mode 100644 index 0000000000000000000000000000000000000000..1546c3a3fa1ef462222d105dc26414c44cd3f82b GIT binary patch literal 320 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBBuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrH1%J@RyM45_&F=DHzYvw{d)!dkh5oky`u-X084PlI7AywD3`~H9 z1?<4#nsX=Z7$eCWP#=JLa|A^&Yru$8A<{*01Z4zc1mpn97{(ZA0F)Js6_5h;f}dxw zBVB64Zlwu47)Z&!R}ECfU&ea(5bZKU6{r(7RaBeqcY)EKTuszE!*!muyo;b1Y}BaW b`W9dS=op_O&e*BH00000NkvXXu0mjfIeW&_ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_drawer_history.png b/app/src/main/res/drawable-hdpi/ic_drawer_history.png new file mode 100644 index 0000000000000000000000000000000000000000..8c0bc2b126b1adf86e20bd00131d830eb83516bb GIT binary patch literal 652 zcmV;70(1R|P)KQ4GiT0cHj<1X-9t z41r+Dg189`mf2|x0xXUo2?>JPnF%y@7z740vn9jMOpIih8N?+avk1rc4iEp^VnuKjJa)2S*Pqw8$+u_`( z;$9It#PgZ|E`HNyIr8VkvzNde;yPpURA`v|+<-PQeBiUr-`2WVmQ@E3p40}YNEl^L zP1!Yu&urUP;qn4XK=4^6!nX%)*}@pkuPypc}^PH4BZ=sMGy2k%&VOOrnU)j$f m!x2!8^FuL_MAlAz1Q-C7N)P4`AF6XU=&~iV`Jk45T=G% z%+zoUkg^0~St%(gk>}5!U!cT1MhYCl1*9^8SPf0;77$NAd-m)%x`hzbF-1VEghg5y zh_!*Cbp{w(ziI9upkrjAAtQ@N89&rf_koW3L^B6L9Rms(5h9d>9AyG@)HPBZ#Y}=@ zKxriph`C7c11r=~ppbe?H3tD51Ik5NPzy)}AU#$IuaTf;xs8WsCtaj$$UF zjPa#n8EQbNtYRjjz@s+6TYxf>P~cG;U@btINht8B4X_rV%t($AhGuJ7I@Zgu0`w}- zQQryHWk5$3(;(_@0i&rGmn73*Ysw5d2eDA185s2GIHG#yunD122aP&t)IlT8L8D+4 d3_Ac2U;rmMSrbXO=8*sZ002ovPDHLkV1oZuiXi|1 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_drawer_panda.png b/app/src/main/res/drawable-hdpi/ic_drawer_panda.png new file mode 100644 index 0000000000000000000000000000000000000000..19ed5921c4afcf2a44c8a1754228c37608118d4d GIT binary patch literal 730 zcmV<00ww*4P)RCwC#SD|hjK@hdwlpM7|WF?^F z3JPM$$bl)1sj;bP@*VCs*wiH8Os}Aoa>$Ymv!o!AOnmeT8jxA(U9_6c?#-PG3Ov%t z*6iNQ+u7N-v#r)iPI8>ICF5{7?9$JewCk83H=9k?BnbH%(a*JG%1IOQmsbM9$5W5* zXVP!vs89ll=;w~~(lcej7ujJB;sBTy9M5VbCj`dI6ftE$*rqCp$;m*Gok0Z{G3FT$ zrd;`m0@`2~q!Aml+xdbqjl>~9S{Xn?Aza9HC;S^mezctVT-<3rlVH>pD{~Q9e zBE1iMY=eI%O+827up|GK^hT&N95AAsRk#{*^3cmV;_F9KkN!yi~u-9ETJ1G!pq4^v+Lg>@$va>><-s%8UvSdm43* ztmzzWjH&%{t$2OLHG!Bquikcp0xj>QUI3f{Gww7-Q(2ZlinQ*0UmdF*~e(n&dS`aslod{$|Dx=>ix#ih<3-)HS*D>Xu}k)ny`K6+9R7bd({FioM- z8#Pxa<_Fq>>%-GBi`1DKsg!H#fDBamFv%e&!Tp!GTv(p6H5)~SfO^U_F{e zQ8s{4bJHUWdmXj{HKtet5!8z2oKNaUZh}W%!hsBX)Vg!FMhnWZs4>XB8wj;2Hhj4P*+AtEWC~T>*PKH+GfeA!djpDr3je9>H)Jv4 j^K}1W{mZYGF98MsLSE*wAUyiI00000NkvXXu0mjfc<>uB literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_drawer_settings.png b/app/src/main/res/drawable-hdpi/ic_drawer_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..4811d8b75ea296670f410b6df71feb8f9def14f8 GIT binary patch literal 663 zcmV;I0%-k-P)$SJ831lyM z>Gj_4-Myc?Ty6t@47N-K8;!;TJ|%(djYgwe6TRCjET&{gOWz^vPBjglGsY-fT8oDDy>p&VXf&?jmI0A^WY!mN0h;EMopMc2Kj?ds50%6X1Zy9c5kz zbD+)RCa|W=-y!hlL4ksxC}^~%C`$sQ<5HL>8(nP-aWGmLlpHH8@VABC$qG7=4#8u~ z@Muh-#x}(mY9>_Z0ACTDo}Wedv=bhAll3g8J<@TC&pvQBhN3PUc~eUQje%n-75J^k z(BT4KAoCUK5D}nk$TcRa&N$?ZM1-Cx^z306RjtRsXMcCa!bpV-IHcl*-vdF}le#lu z0C`${OCa~*NKY-AaaxBf5rV5S;2E3rvPDGoT^mF>B&C0mvq{(Oqj8e9lrZy1ryjD>$1$w&>>r!_VCzMo8210 zVOz0ojO6+8ny;{SqK6_~^0_$<)HV>aPWkQOSDoS7Lyoz@*jef23dOY28x7kdj3!PH x(|6e-C7yH~3JXRPp0wZgo*``m{|kNu7ywc}(uk)Enydf-002ovPDHLkV1f*aw zH2(X@vTO|&bJvFlz|?^~a6ugcPy-ersQGUI^o36eY6YO8V*}bLpJm$aj|!+POEH*A zAxgKC1Ck_laXc6~HFY5o%$^U(3(k=1@+l)EBO@#3f&e}>`Jhx?NZ4SmoGvBki!H6! zQ$*-)@O@Yq=W;$KqXeDAJ0Y>UyhJ8XAoRQ zu;QjH^^~Q~0Eo?@!~;wL_NtkWS%9#*B4$+HfF~Px3X{lkION)?Yp@TnPXN1NR^B3OmZJ;9QDPe z(KgP2uu_C)1?rSs^i{}7*_P4K`{*nUg|THfS9JVjh)96~*l7fHVQ4!oy#L58`=9a# zTWjX`R}BDAVF|}d*SGnac|}OM=PW%%l%!h$)te_Ms)*77DNS+Vpe;4>Oi=d-IL8HG s3Eqzr4dL6q(E^bA^!Ll3AKwBD0FCy4)u=*uX#fBK07*qoM6N<$f=W2`OaK4? literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_favorite.png b/app/src/main/res/drawable-hdpi/ic_favorite.png new file mode 100644 index 0000000000000000000000000000000000000000..bcb852abc9df3aef2dc0ebd35d8cce1f0383f422 GIT binary patch literal 1926 zcmV;12YL93P)EfSg*E#148M2Qt!1VRb~gJ>a0(Zt56Rf+te)G9&{ z#QH%L(~6pEDD;a!E2y9#kw`Q`;zx{V3sPIaw&U;Z?ZLIZ-p=06-d?Xa$z<=Y@4b2N zx9`p8n{pN}Aratld_p4-2gt-j;sB`xNK{lLu-4Xzkg1{GiFz~YIMho}1?nHDhnWz& zSzq7hyrt!Pr$ZQxa_04BqRo5IRu1X})XR-_|H1P&)E`j4Mr~p_Ifr;fMW3u*QXV9- zvQk)T>MX|j0z-*Ji3ZMsrV+K8rKi{NnwoPGJklg0H#eCjCEX8T7J=z*R2iLmK-UU8 z-eR4dwS4d1ZnFkW0TOw6lUYK-ItZF%CM?9?zl2h2SVKcC8YE8(yXYRHX%9l);}DV= zwjlDTzP_crx%sP5GKK_56c>+X9UWz0ehPJr4MqH{1WYa7_$uDi^j$deM1KCQFzOlb z7NMHHT!}V10rV=?-d@QM9QZF7eb7ckcJ^47oV*^)^CTLvN^%Myy$actys`0=4709J zO3HH3u0&0f;ju{b9_#KdNn8U=UTfVcaQMd9I+p8;bP-rkD?-=5J4mjRe^ zMImmkqY!VhjEp51PfNOQFzOMe%K}(0MKR7x0ot`Hmfj=qC&e*Bb*vfxHkU zlmgDNj5jpUprn`c0EubSZegQF?Z$5zN}94RGHs)&!Il#FB;2d$W?@?uvYB}u~uqJTf_0GYE_f<=e33;5) z<>-H2gE7lu!XWiVtR;Y`bDpMNc1%cYA%K2{Dc8n?IqHo#MgY+QO~qP`V*@9_i%@W))7?l1WgnVVxmYJ@$oVc0U-|2 zM6m`FMcTrQm%#|sP>4N=-&e=c35q~Dg;*9stPOuwu?ZeaI15BpHpkXxBKDy7!7@8X zD6s^L-65a+)F91l)3j{5B%e6s5GY5=Gf1Wp|BMl(hb84xQbhX+=P^8Vs?%S43y=3joeX!8{@4>>0D{*#K!X7*k`^jk+iS z3&S}bng%VV%L^=K=RpW%dnCjdNPT$u^U>#Q31yclK*Wp%?X!4L;!>=DL^+DjBdne? zd+BljQS#=VJud)pN=1?4F>BbxztbXgHJdv1X_;hBX=C-=p^gsP7NOigX+9T$2-Y;E z0lti+((=F@Alo747htUXU&8mtqRenvb*eBCd;+s%$|8{}mOVjaW+t(;v{w*rrBW?K z3y~1(*vXSChDz29dlU=mr@jnW%0ycnE%uw}u@PalESy|z%N`UEZnU^vkM9ek9HeR_ zoIk?1Z>Pj|%Oz}ps3jugQOMhd(Q5j@oab=lD*;FkH?MabyoeTj zQ^HY1AZ*dqpz4w9zbiw>3cfG?HOZZHvD=bQ)6p%dVUl!yYP zgXHznRc<;27;DkO)8H!sIMkm-tVA3j1EgXYOJ{BuOIrFbJZ~X}@)}8cWn``Zq=V$~ z%*A69s@a)a7u}%+I3Fmp+JYe005U)-!SfB&`L#AVJSBk3u=^$y^B6tX0 zq8*sbeimU^iav)z}RR5iE{vLIl_uA_qtl50w5#Y=+4?21C5mk zD>@W#y+jQ_M!3=c3SkJVs7WY|Du7&fmmmtn0Wx_jae(3g*$dqN0W)SES==ngyZ`_I M07*qoM6N<$f`x5-VE_OC literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_more_of_uploader.png b/app/src/main/res/drawable-hdpi/ic_more_of_uploader.png new file mode 100644 index 0000000000000000000000000000000000000000..f793e561299f3ce13b20a99f548d8248fb108980 GIT binary patch literal 2106 zcmV-A2*vk_P)!S}KvclJdiwxZ=FmBrpX zStT{N*pI_lqfH@G3OCuC!euK;yczi0=lFd9hrP3^OsAD>%&G6LEFNZWJfKmsO7oCs z3?4!be%^9(ezL74Ei1pQjHn#<(NToBoc?b?e6)a(N<693aCw*0>%!A>3fQZGy;mp!QV!phUenvT{`S5fIS+pkL$?WZ59CXwc%?8 zC`63pw&VG_x2zj-ev%Ql+PYnT;#T|q+(ajSG+dj7EtxfYFlgSfI0{p&MPidZaJFlo4hvS7ErK1 z^HtzS39q%rhxq3~K_r-$ZFPFGpSL6+OgvXZ;8GrOoywfOmK+$eX#L2) zjoOL(8sv6(r=y8{YE)R1{Npj29WZJq?g5pT9`B1z_D2Z2&8WDD{7V67jZqtM4~PkJ zJAC^vTt9A9Ttxmo05sL84YL4R4nQl7ii^m9zCBS%Ur;{cFC%tL0mxJ69}m~X*G7az z%S$Mk=JeP0pk*W&p>D0q8E~B0EK+#wR(khmk;ibnQ(u=sjn}Y2GExL z11W7uy}pC2bbZpI=fY_CF5d26)N8{ufI|IHIht9=0)bwUUGfph-_p;Xt9P}-RDj$L z{|q#d9b8~;6l%xU;pm5$mq0U_3Q$GK$yA%t#)Rov_K79l0YFunbdbSRfI`h|H=3E@ zSB3}}RRH9$Bp~>Qmf^Xs(jzWLo`#mROAnWojSFbmF3-dSU+^Hmblp0$pXx(%e8En4iFQBc!KR%ub8bRP6N=#0Q9Vu4YLAb zp*kD1#A#grt;f~G!E?7|UWt{@lz`y=u*3a<7y#{9rdZ-(V+^7{5Z%&!G9{q*7B(g) zU%S`^#D{?1*H6 z8swjfCbC-$kaP{@hhjd&qH4P&(*vq1IMN59zKu}TM-Wmz0DWLNKui!;UB5)*ctQ|R zmn{`xTc)o;ObF&fWtb1G5Cl};l4j42Tn@4XAk2p*Vm`zjEFJ=jEnaeFdQdH!!yF1&k1fXCu^JBf8J}m@DBHQ4GooiJW#U?!dgjxA3i2iun za)5&UkdN)_VEa3`6982M&@)yA#C8U;9YP}MTp*!G#{7z*mzfdEjAEf0gzfDT+c9ku zA%tDv6C73cZ!8BWvsR`fxUpQT#V0HDa1Cw4*2dzu|_(dOO$oL)ceF*Qd7jTnRwl z{SY*PR}k9ydc4>zx^Yv!ck#XBccXS;Z#(P>`E+jFi}xjH>u`R>xEBT2PUC+gpp(?% zHRCX%J$_~R30#$#a5J(EW;_9r4K_udTDU3emrj7#c^kX%;d!HOM&#*qM4qx&1>v-L zUfrGTiRwNWdMJu903a;F6~Parw$J&dhFLYU7^p7ky8kwKn2~~m*<_qtr)v6i*)f6W z0O8)GTcT}mWQPGHw#)3Gx4~F9*E}W=|Cb_PGmahfL_2m+gj6s@M#V;6igVB?Vxq|* k-2Z;XmQovbip8@3AErrSggYsua{vGU07*qoM6N<$g0%49L;wH) literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_rate.png b/app/src/main/res/drawable-hdpi/ic_rate.png new file mode 100644 index 0000000000000000000000000000000000000000..83991cc57d639bacc4a444da985ea2a8265da140 GIT binary patch literal 3971 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_reading_action_refresh.png b/app/src/main/res/drawable-hdpi/ic_reading_action_refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..0d186a2e847103d5bffe741ff71e166189ac069b GIT binary patch literal 683 zcmV;c0#yBpP)Xxk=TQ?@ynApn1RwlMGWn*JwV`G9B;0eqNqz^#vs;h*obK>7NNdBZ#Bnap4JD<-! zlOz!l5fT51)R&{%?N-8L2sQwFR=jowyMUe2G(Bkg&Ec_y_g}pW0Otif#$Y3`-0L&6S5P0Xws{n95Bl5Z9^+T2mk@Wb)nw5c#b} z)CJg8`m7IDXw1eG>|G9!vj7e9f^zzRQEXTf3_ka;`Q5?`+$u|n1vyB{mVOniMl5d-VA@f<)CGu?oRAsX>fz2#2w-9DmV~`cDMNxH>f+X}S#2&}0Vhill?ck_U&0fa1{w^dC)tTrCPZJ$4Gc zmKIMgrKRg;rmtN6ZFN6_o_-~#S)I@li^!38j9C+~kTMMOkIbQ|9S3;+!o0GgKN R_^1E?002ovPDHLkV1iESG6Vns literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_reading_action_send.png b/app/src/main/res/drawable-hdpi/ic_reading_action_send.png new file mode 100644 index 0000000000000000000000000000000000000000..ef2547a6960724becb81fc96e91f39dd60e7947e GIT binary patch literal 365 zcmV-z0h0cSP)SMH(I>!c}w!CO!6orqqj0DW5Stpj41FCsvn?&2HSl@<}82)$wfWYa4)fVy6( z0Mzu-07$BrCO{IsasWitYYO1O%aN-w4=vV6D56c->;LSS+i^Pk;dcre>|6A+bz?00000 LNkvXXu0mjfN0*ea literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_reading_action_settings.png b/app/src/main/res/drawable-hdpi/ic_reading_action_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..58d9beacf1539e5cbea8f14bff0b7ac390e0c6e3 GIT binary patch literal 657 zcmV;C0&e|@P)nBonNJ4kV*V^7Q8r0OzOIko6V- z7+nLGI^*yzgU5nlcY)vh2n=nky_OItRL+%yR~>K-{DzLJ&XX~dCsjuRZh&XNIq)z! zD=4+bIRMPm4tVm*E&d2RhajYD3FQHt7icqgLIAQq!R%)u3`s=cI2M5-pz{K47o!k> z?5iDf>*zp-PuI4&1Qc4`9}lKh=K%Xgb*O#;Vk1zUW<3&7%H0EKshNDaCuL9&K96XYG$`2h-@r5uBt2ZX1DTxFa#Wo4?HE-fWA@ey1~G*}Ym zk{KfzHrX6)x{cE}-9~XE+cP$ubFijT8YF~TB4-~=C%DuyMspWhZ}8iROZ~<}5ho$T z#4PT1m`e@4x+U(wHbh*VZ!S51{|6fUunxG{)h6fv$Dtk-RGP)PQUe@LmU3iyY*5Ea zGaJ#STjn&HA*`JSrGj#c(3ghzx}}zyy+`Sg)M?b)-6$I05yj`1PFP%um%EyU=R+n~ r03V)Yf(6iel8F>4QluN9PXPu1Odk3lYqx|N00000NkvXXu0mjfc3>5G literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_reply.png b/app/src/main/res/drawable-hdpi/ic_reply.png new file mode 100644 index 0000000000000000000000000000000000000000..ac9b644664f87f21e5711bba36c68107d31e32ad GIT binary patch literal 368 zcmV-$0gwKPP) z7vK3TM2#B%8x0dqhQnb84n43wSQpGqxmAMgz_wsv5Cr>51psj*JqGh~y4ZmEAS5hR z01=)| zEELcqZpsicRo~1s!$zcVdsBr_-{SJ$lV3nu%y-Wbu(Mcz(^Ux3Q6YQY$iT z7V<3=;Is1pWl)u4^P)`Fc3v0CRaD$0@MHn zm?l8~J?1iADl!0weUp^xCni0o&s@3wf1&4V zsUeg?=#V^RBZXO!AV3ap)({F+-_)LA!$!Q_Z6TDibakS910rF7W) zBr@-^IA9+5RT=lI)QoLhn*;cJM);R+Kqc(=bL#*%3V=));A86m(h7jgz-RsA_RkR z*|_SXoFvH|Tvi}?Q54&bx~PZxThPs2ZJ73a06!pSGz1J1iUfR~!(|3?P3wMfE&P5m zj6c&JVLYH2`vMYy>_PB*T(P_aK@zuQ{D&eHjDQhJUC=9Z6egZLuyjyel5Wr>#!z{q z@Nof#(K~pe4CR59Vf=~WTr}i>LgWy7eh9$RmvE8B?G#IDjap7?bV%B3^fJ1z#dSkC zVY1&;>~$QQfX$Gmq>L*{tPGI@k?@B&LU+QO5Q6q9_jmOQe%8J{!i4AXi8$)vV8i&qHBek{P!GS0!)AjFaajO z1egF57?6PN4Z~qTdk!saXiXqBCNG=^2Ajla@64F`(q02i&Ds6Sv++-3&%~eFKqw#ukT=p0ttJEm3+pJFkw!@S=l8M0tY+nb2FZs*l4)6WH`Y;;W31GzVBx&F2v&we#7Hx8jJp_6;E?`?#V`g zKf^AN*eyJOABAvfH4A=V@o5WnF@QFd@OU0M?FHfww!c014vewO?*PQ#;_*5`yb19H zh>uGF(3_8prix+uz5x;)8f_&2(y1D*EW}4bJXsF!i|YfAF)iY?oTt&Q{Jl97P6=nA zuJ+Pt2;r^0Tg3j4&_Wm`t^tZUPjvv?yAGk!Q=4Ju287Dm$ToXy_0dS7bB0jL>(WQQ zGE(erG60KTi{?iU)VjSW0CH8pGd a5?}yV1;N10Q>}*p0000B6Zf=ii$=-Cx2HN*=vPMimcQSA$8U?5q>N{ z-hOh*^xm}Vs`Iygd#3j&!&0TZCHj18e86wnCPsrJoeE`J-mEkOuh1~wfB?u0#;+nlBE ztUbV%aez&SfqO&4jx&3h#V4?LKGR4o8n!Lkrfa=)@^?Ge-~-1UEVFiO zx@NbSS)oSy7uS1c|7X&AjDhaV4i)Xs#1w19A2D^L<+Qs*D)dX-J8@&@vSu-bzXx}Q zT)lBtw1oBY*Z2zFSAUu6ekaMZ;T!+!+h>dAcFetVLTd5Wwfbt3JEk}p t9-2Pu!z{b2hyDNn literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_setting_download_dark.png b/app/src/main/res/drawable-hdpi/ic_setting_download_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..42292689a2157d35779aeb21b494cd6dd7a0919b GIT binary patch literal 355 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@jI5q6jv*Dd-rTh1YjO}^O(?K!x+>Q&tHmon<*ntKT-#o= zNfFVr6CTUXv1RyeY<&92hd3vN#@PGfOpKXQERp3=FIf&7lx(pwY@dI;u*S2l{`32E zpxscAaJIqh^?(28J$rB9PwlDoNB9a>N^*QOb87kY z{feVrx*D@f*w35;hdSPTa&2f8aDCd%I92fI)5r!dj<8BI2g!qa`b-*%E>Dj#C_2T& zu^w<)F(sT?MCr<@)l5c$tDatEsnsCqGWF9{=l8 vn{$Pm%eRyqYj|O9@@FF}LzBXyKP(aq`EJuvqWHDdLC*Gc^>bP0l+XkKLNbcP literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_setting_download_light.png b/app/src/main/res/drawable-hdpi/ic_setting_download_light.png new file mode 100644 index 0000000000000000000000000000000000000000..2e8ad83ca05d76012140ad86539dd3dca64c125d GIT binary patch literal 343 zcmV-d0jU0oP)`Y2EVG1+DHuaj=k!3LE%-p9O?ZacNU5J6z)NMuckJD9Tv&XP p(N`9T{zK!3EebFUW6Do}0RW9_c}bUKii`jN002ovPDHLkV1km2hXnut literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_setting_panda_dark.png b/app/src/main/res/drawable-hdpi/ic_setting_panda_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..56998b6d28dbce54779c07c37aee1de43b3dd4df GIT binary patch literal 983 zcmV;|11S87P){Yh5rUDCgEm$fl4$+c;?3xo?g1I#nAZq%idD~9*-kZN$ zNH5)&ObE=(`+3m3Zam7;RxBxKEP zuuxY~SrTA2D;dmn5ED@(FxP~dI8wCv*Gw1*PkbpmJ2#unZL`_Tm=YVn+#J^YG%ceY zCdtTSEQ^r$h%$gf2p;484}|xZAYjPk%X9pCqss}5`E&ewPdNnE^of%YNqk*Iqzwk6 z_{8RML5Qd#Zwl@G0)D^dusb^Eoy;F&e_W_yV@zsU4~A zWTIPxN!XC+%yRu5B2EesI*SID+H_p-aE1*nK*gp8ck|Sw6NlJr4hka-(eV2sY_AjE z;X$la-{raMW8EFPP97aeI$cj6q4!curC2VmO_{dll}h?xJRI&IG)sbbCj_y)aYavzAWghrajg2eg%0p`wnY4BQ+KOJT` za25moe`pV9H3N7uAZ4i zynsoBov}9nP#h}b4|?Xpw;L?d2*nw^g&Y815ckypOkLAigq_@r0p-a1r|$bg-1$oA zOUa1ny`NQ191QG?#ZK2ca)ddDQqR|$<%gs6L7p;Or){%$i;!K}rLhoOr+TjL!y;z^F~Ue9J1b0qNsdsqo!-C%DyuN9XF zH%UEh@d0s4>nP0{2U~lNZ`=SX>+gkQ5V__DbuB~QdKDW$O7whF?yrF1dyh@jw-K{D znB`M=vfM;oOF{RA%}_Gem26dx>s8-mkre=R91dGuxiSaWRvflz*$fKc-~tb{yE=Br zIfhkj5yFDw+YJF&JNhZ?XIfbTPs*XLcglDjGI3Rgk3d&4a7-dLea!?NWZJ3CN7D!< z8=l&!wsCsr@+n6&-%n9O>=)vo224O6_Ql?LM01&Ap)*4p{!n3K9)ED2# z0W?XJ(jSbNL#GGl3&34Yl#V3*?2wgqFs(W+cjpLoHrwW-bG9&F?ZU${YxuaYnco8i z=qo(Sf-?I52Z-;48Dm?~t_~YmuOzTe$&!DoN8j_3``IcYUimC?Z^T{E$dHm09cp(5 z6u4_gYCm3N43t*-zO76IF~25?gWZlW2g=(`4t_G;F9i;E z$|&ISg{ge)I+NVpxtqMqckO=VkRB^ruhX^op69gHtEc{{f4=+3&P4*kyYs6b%<)>u zwM@6CRLzRFXW8prpA{H7>)SS-(z$WpATFnab#uY&g+H21a&y$#H=MJ0I=_Y?EtN@u z0RLv|C01|#bzb(tyobrN%58rzU5UxB)8Ss(zBK)d`G&Q@GQU3DTOGo%$6=dp`A@kS z3QO4-eU35Aw`1^qC0MSK+^^cdZSnix^Ec_(uc%nHS(m{smdfJ5fPywy zG-R!6y~cQCzT#>t=V$Gf@6KN;d(ikOH7mj){5Zn}ixAOG_afAeH#06U7xot9tl0Zj z^+klzNoPh59(GM7$r;fH_Cy7z3S5Y_G=~WCg}15) zh9vG>cfIlhqXzH8hNPp?FIMtr-C$k+`2kmer~ho7>GRH)E@0z${`o;r)S8Ek`{)0N zXlSc{u}#Wj{iQ{(WbKwm&S#Hq@Cltg|8&f`!?!N|cE52=V7gt+w=^9FJ%Q8qrc?M^ zwq0X$aN#M~%&2iZt}xx`>DH8`m94VNe15BO%=RjkRaaI{exrJTVb_YdPj6ClS%Hzo N;OXk;vd$@?2>^=N!#)52 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_similar.png b/app/src/main/res/drawable-hdpi/ic_similar.png new file mode 100644 index 0000000000000000000000000000000000000000..fcbec34f623f643887590f8c575b539c3d7044c3 GIT binary patch literal 1974 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 RmX`nk002ovPDHLkV1i0yRg3@t literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_touch.png b/app/src/main/res/drawable-hdpi/ic_touch.png new file mode 100644 index 0000000000000000000000000000000000000000..e5c39e554f56e5213b955ddc9c44c32ffcf98056 GIT binary patch literal 2203 zcmV;M2xRw(P)lMN)7C?r%xw*b0MvMqDGc$8gDwQ^^t*w={wY9k~ zUc88LadEi{blQ3YLP7(iIB?*A#iB)vq5#e+6B855t{^ltG`xQK^5w~tl$0NWgM({2 zB}%qf2@R0)?%liJO`0@myQ!(EQr)TR>grk_K76=s-n@CS=+i1&bUJN>Q2R4jym+x? zY;0`Cm@#8q3=9m^&fC`3#uOG7X8HN~dFJKi)yY|kgp?rVsZ*zxxx2fc96WfiN)zfO zB_%aiu3TBZcJ10!Iol&40a5{=Fb@xpNE;g)P53b7<>d_Q(1w73fH-t+);K_-4nhG$ z^Oz(hB}Fe>xG=P*0A0LzF=)-2H778(QKIu}TvkYcOcN3kqP@JlLU;hwBv(5mBtT{W zXcrfNl9Q8z0|NtD38DZ}c8?w)9iKfHy0EXJofhXuGJNQk^-de<$}L%D=jTO2=HnkUB2M8 z?Lok)+S}V(>g($Z-@JKqIzB#rZ)9Yo=t-_D0V3c4Ml(P$#Glmu_dq?HLAagG$jAs? zy?S*SeiY4a$rd0@rUUkh0bP4&+O%mQ=-Z;HKi}VbEkML;LyB^iELk!i7XTmf+JfG_ z^;&>PBAT0wSBW*zHlK^#w5X|%E&r>#S+O!lQLjzw!-e&s41**BNqEFIL46D3&8o)M*?CQqJBk+jYN zMIzBHA3S(afC$xA&Jxs_td=5Kb=EaNRyS_k$Od6PTFy2IcWg{{c6J`-VL;!t?2aD+}S?#bv$B!RB3chz4B50<&s(R5;=w<G2EYn|70Lw>pFe04=k#vt#7u z&2T@)pi+dv_aMKlU%!4Q_N!CMmF$rDmMvR+rcRxD1RRhRK44)`Y|!I3BX&n}@dy%| z08sFb9Xk@RUOk%5NPChz%1{UF-n~0=&YU@`5LFvlSXjtL)oN;LnA^8+|Ai77ucD%& zm)I*+f^dHLsu(Rq;$n^hs3FnO(Mj;6u4BiJWmGDSUqW%-B-a6Svq~j?>FMd$P{H;k z##0n6$-NlaBFl{%H~P+)F=OA%nKK8F+n3rNk$R!fiUG?R6A}^<&DTKFi_u1c*zx4! z2Y^}Jirj1+qHALiXp(`%O3$rZx600(Ipc?mgdFTo(yM1uH8WI#4cfeUbCiRF!%|3- z34sKbk*juS-Jhao+=aE@xpOB0(z6*8Qi{R*gFS5}h&>RgnmL*cjP|aH6DNKFNiqVN zrc=*GuKm@kR}8qS!lOrzE{7BqVoy{%#5w+94<-~qG!KbOKX|f1;o;#wgJST)jiv$R zYPt#{DYXC%(~g3L17TrdKWZyILfD`WRf0ewN$Lj)`UJIuvB;v`CQO*X;1hAJRY%hV zD5heHi;J0?H*e-bdcH!@LNUfr?894nga(M6kK&{LK0ZFKD33b{_dk5($dQbg#2Qhn zWo2ay>`pzh_t2P_nBTDn%HFlET@*^ix=IoSL)K8WK&aZ**|TSxl06bD>qi|BbMM~0 zMzr{dh=}j8_TSoguC9mCB)xoo@3@L4qm8V87w*pd5dtX12qH;(P)SCgn5PB>1$~1x zUw0a2&XpV=JE#~E#IDm}v~e9hYXAQIzrh}j6WU-?%tMA5YE_MxXWik$htshp(HdH- z7JCRW0b=tt>tn}`{fe6f524*Yxpks6a`52$`_FKwN;F=TWa`vJOn_+3j(jaDDry~E zeK?vJclULWq=P$m?%dZYJ%Xr~E+mNcYGkm9xE_E;{$5BWCXgs+BfF&jzlGs4>%V;oAj;B-%Ah8w^yms8b{!TNA=R2Czz8VLWLQt$hmf1? d$$Gu6&p&CU^mfukbOita002ovPDHLkV1m~p27&+p literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_warning.png b/app/src/main/res/drawable-hdpi/ic_warning.png new file mode 100644 index 0000000000000000000000000000000000000000..28df816d8c59e1349399c8bf8e48d2b9ae64415e GIT binary patch literal 847 zcmV-V1F-ywP) zlWDF})JN=EEw)uFfZg&~#KeE$^K}QNjq91Qd;I`hQUa%%6$YUCiz2a8V_8NJ*AmZ) zopeZS8{-Eb+OQib0$~Ag3IQ7(5gGt-xevgRQ3If=Ye?S5bPJ&cke#Rth^KiWa(4jn zveyqVmn9_E0*LD?<_;j_HzIfx0oX#eqbV6f03^f-0PX~@5ud@#_eFUXegfUPF%EDz4?E<{SHfcCMY(4nC~6i`CLy_`kw{hs>W6YK=o%6>?Sk*GL{uT?;2>tlq}UrrEdXWB6_DTf7syjpYs@iGe{6CgS!UL?S3 zUnQLH-*Ua(xp#x>g@JLNVZR56)Z?cQpqT)t1CxrmBy?ZSq=Vj$C*(Q|z=JdmsR5&7 zqAc76IRGqO-bD*K@xOXm0PAr)XQ`w>to?N1QIdZafIP2Pm@^6_ws8GQkevX?y83VU z*1&W;MtE}5}I)dAyz28XLU5L_~2 Zhktps_Awn3M$7;J002ovPDHLkV1m_Abx;5R literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/toast_frame.9.png b/app/src/main/res/drawable-hdpi/toast_frame.9.png new file mode 100644 index 0000000000000000000000000000000000000000..a804a8a9456438adfa3fc1ebb234b2c19be684b5 GIT binary patch literal 1573 zcmV+=2HN?FP)OBUV$;j3oneum>9Xy#2CFe24lQYi9sQ# zDA4kD+Vbj4TWYsJ-O}CSwjD2?Gc&vG%+AZs?9ADnZ!#A~K{PsD||M~yV|9PZo zYKW=Kc?}(Dni^Yzi>$_l*o^D39b551TvB{Cj

    QUBN77a1@8}Z@h***oR3>XIa*_ ze?VUZOw)8s2(ukK@d;du4LBdG`*GcZSscTE@I0Qz-*Eu%XIa+Mi}g&vA{4B_CftNu zu>+UtuVAg1!G8P^zr_nU-YX${B4C=Pt8f`^!#%>=#(Ob#52@e?9>*{6e3oS=7CY#o z1T4I6JwAu~g~zR1tWkZYBI4z*@H6})%d*qm4%W4Rh1YGzmxSk?-|bif;xuyn0FQO+ zjU5YEc-=YpJidZ$-HbCtObJi?5sq{k8`pGk!3lg34~Utz>`aL7vK8OL8=d@d+n!T+ z-bQ>4so{@xol?QGcnB}Hn>)6ZUKHYO#y4@7&Xe9UFebj^$MI5@5OJrD?xC*xvJM1;i^uUd{Tg1Q-^BKpT zxP0!LH35rl_)p`8rM0V6Y{SikR+#UB7l@uF3;C-FCM+np*;K=Ha4AJZh8^HsS<+qga7d znI~W|nLHux^AYoT6Th!c9#tA0usn$G;H{;wsw5o6_eC}QT*l54uqd|MEpFx6zMD4u zoWdXQM598)W{1qD@l!lyNE<#ZA$#$N@WQibSmu!Dxg<%B;a}p&+e$t5F^O;Esb@#Ac_%$3G+V*$N zNYiw!sP4H!y#eDEQJZqH5OrL4w|un$iR8#&i?nNYVoyPG4GDFPNuOG{Uz{4`d$XM2lXg0)$QfklEL Ym5X;r|CAZUKo>E1y85}Sb4q9e0PY$}!2kdN literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/btn_check_off_dark.png b/app/src/main/res/drawable-mdpi/btn_check_off_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..1217af98fd56be5be5d43620a3384f4e0181a9a3 GIT binary patch literal 244 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWw1G(>z@qLn>}1B}lL?PH13M*6aNL|GztvW~|oD&q5u~4UCNH zT&E`-Os-TBwOz%$R9)yPgUbBon40-cr$rlQNOv6F=($PYLXEscPUw^yd^~5(mIxeR z@L`_I;Vx_Nsv(fQ$133p<6=IEQig@!8u&KY-8T_XENEgYi&e8?X;{0!*I|D146%?+ pf8;KldAhi^A<#gB(V>BnVZkq%oA$3#vVksS@O1TaS?83{1OPXSRW1Mk literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/btn_check_off_dark_disabled.png b/app/src/main/res/drawable-mdpi/btn_check_off_dark_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..99a3b6c1277ac710af3c52d632b03bfc8a1fca24 GIT binary patch literal 241 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWw1GlRaG=Ln>}1B}lL?PH13M*6aNL|Nm-c%|xx6pLsjh8yOk> zbDN%UFnOnxs4ds$6ReA58kR6VdE_q=b&mbW8izBjzLQ!Tp7|e0uy*Twd*H&BL*i3? znCEi1%No3D2xRZEO1Q$fm`|dVVd1w1gAI1yWjh2a9GQ3D6Ov^*5O%?K!Ffj?POZp) nf*H@wUbH))Au)wP;Q+%caha;`I&N}6=P`J?`njxgN@xNAJ%m^# literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/btn_check_off_disabled.png b/app/src/main/res/drawable-mdpi/btn_check_off_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..22b05c9ef0c3171059274d58659fea1b20fc80fe GIT binary patch literal 226 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWw1GEuJopAr-fh5+qm`Cp0iB%UQZAh%K39d%y9AnZSR?=?Mpu zcPfe6YB4NbFZ`Ne#(77*e@+XZ+5}WvZ1mhDaDivjADLD@11By!ytdU;{2<}g9dz>3d54<%E&K*tY^n?#1`K|S Wc#ADJsw@Gzhr!d;&t;ucLK6TJ(@Yis literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/btn_check_on.png b/app/src/main/res/drawable-mdpi/btn_check_on.png new file mode 100644 index 0000000000000000000000000000000000000000..02b6b098105066cd1c8276dd55e707e2530dff2b GIT binary patch literal 319 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWw1G4?SHRLn>}1B}lL?PH13M_M4e0%j@H4+;2v`)%@`#oBA;W~HeHTRR zC*I?8h_GABJmtl=%a<8%=!h?v#>{u&P*IZX1P+rMg8ofPw}EU1%_*C-gf?0t zbzr*i;b(-5}1B}lL?PH13M*6VD$>@L+X#dhkOoeW2Md6${V zUr>$s5E19^+Hd9X(y4?~O-AZXrob(>8!R)~tavY6Vw7jQ#JIp^ImZ!hwnK~xr)3vv z)+z0kW!Nq=W3oeMp1rMX!`ULnEXxNBx;pCD7&{%BWk0HDY|;g?A2@MlI4X&`2c%eT z@ib=z}4yUY$zRgC59&>D9Zm3||a%s_tqM}WTN-8T7ELSQvyy0NX z5Y=|be0b1l*}jH_0n&WA>@rIlW7*j^1y%{nD3x~m&9FEjfT@9jnZaH)q5ZYY(+NP| OGI+ZBxvX}1B}lL?PH13M*6aNL|Nm-c%|xx6p9MSe4ULTc zxlKiO>>GH72PQoIf^l8u6A$Em+E2$G?Qxj`3rhLY<8OA46mv&#Xu9 zFWzP>INfO3$$w(oqUFpRBF}m0vhP{i`t*p^1EvcPk}HHnZcY<^%}}tAWy=F5G53HJ z%Z+>!Sf%nBjtYuI_iffY@Q`BzvxCG5w#a`of=+uDGVD@*=cujQrK7GceQ0sB)U?S` zOegvhTGjl*ytw(rjJ|sEwB9PnuzsM+`=-izLhq&R2i8;}1B}lL?PH13Mma}wK5L+_Ic7EdzJAwa>(-RIR z?^F`C)nZthzu4R0;ROYc<_ph`s+};okl8Se?}zaOz8_3SjyL@H>0rT-^3yVd!)Dob z<_Wsb8IzQ&tQclL)wxo1fITY0Zw>Q0XWn|h+|!+<4Gfc<=LKIbN|IIJJTfan#&P3Q z9+iviPhNt;mI_m1uO784Orx{OJ87Z{Ne?rh{Neu&s5{)Zu x&bJxgXC_?#-Iz0FU%Q!?`#}wf8U}>}3^S4q7UkLd{04fB!PC{xWt~$(69APuae)8; literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/btn_radio_off.png b/app/src/main/res/drawable-mdpi/btn_radio_off.png new file mode 100644 index 0000000000000000000000000000000000000000..6c80b6f0d0f588d93fadd75a91b36f4e4d0dbaf6 GIT binary patch literal 467 zcmV;^0WAKBP)U4vdc<1V0rVD17CgE9i(_RCg_S(!EdM}Qop zy8vMD4+>ybSCVsW8He4}*W%L) zFlR?icL31jLs<%!cMqu0swVLfss8P$3qW!QfQba4qUVe%oYZ1gqN@-9@9qs~V9wJN zIW~?dB1>TwaL$Sqvt#HUC|D861OOQWFC@L-)Trn9zQ3U##MH!6mikiK4Kd&w>uL+o zl~P(;0xu}|EBln+F6gcok~2GMy32T7yvq&QaXF63aS7A002ov JPDHLkV1hx~#DM?+ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/btn_radio_off_dark.png b/app/src/main/res/drawable-mdpi/btn_radio_off_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..6a9ad95d2ad6169564b8c80a430d2063a1bd8d53 GIT binary patch literal 428 zcmV;d0aN~oP)NTw`H>NxT-5;g)ki(C{@PsSA4>yUn6IrASQHs_y+NCyse4J5ENjQ|Qxr@e5v zVFmVbTQUN;YWf^+lF0iqfqGF0VN;xe#HeOhR0B4x1ayXa1#0000<$Fc79B7z7ARc>&A} z2DA6i%nSy@3xGjv0yDv`%az<3XiI^B;7cwgq5b;ZPkZ&e3l|OsTe@Hvh6sifEKz=S zU=3sJy^8?Yn8R=b3k_3yunL52TzvGFB2mvJs`8W%}&F^3l@vau+ zDlyI#A~O;tk&cH>gh@a_Jh$>_B4R@9b8fzG$oU_MY4e{)Fb6l3HNcT;nE)&}8TQiQ zgq66Ka@h&MqbBoM+ju?|39J`^5IL~CD=WC$l z@V)&JtPv1zARu%RFy;*}7AN4^MB%g&(2#Gw!h1f$sTY7PRbC>QOB8U;c$v+$XV>vBhQbU00000NkvXXu0mjfpgyb# literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/btn_radio_off_disabled.png b/app/src/main/res/drawable-mdpi/btn_radio_off_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..8579198b34a3f53dd8a91d4466f8dc6d747f4dca GIT binary patch literal 467 zcmV;^0WAKBP)@1r8zqk>uc^7JOi0`SjE2baz>ana64Y`1b&j z$2T!4BkhdeFC7J&@KN?^DvAbR$)OnFK)3$pOH}52?X(0ssNP0stjRg#hUH zA8@A8FRZ{ z{By{i7z%Tx#f}refw{f(xGg2V<+erx#4OtDkvq3}qGo{^y8v*P$$&f~J8TXD?ATRP z0RRd%ge7r}O2y3t*|wRx7DOQc$pio!AAk$X?@{3HQ^V#cfWhc>C}6#}sSBWGNo4Qo zg~&)|t!oTaEQ#bE067D%e1cFK`Mhu_--S^}q;EMc5yg)#C~+vqkE86ZBQJps$(3ET z)Z)S5hJ=?_vn5L{i;8EoWM)jv)|B!q+Okr+{4rSog2tZ!0{{*bRUUaM9rXYJ002ov JPDHLkV1kR|%n$$o literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/btn_radio_on.png b/app/src/main/res/drawable-mdpi/btn_radio_on.png new file mode 100644 index 0000000000000000000000000000000000000000..5f467dd262727e4fb13fe56f28f736447b9aa20d GIT binary patch literal 513 zcmV+c0{;DpP)W4 z*}xistvh$_eCpJ>Q>$!XZD4tWY+#Py5dwxF4?^@I9qXJzxT8)dh`_@mJ``e+MV>4# z(L1U#h35v=nrSYu8nNAf%>$q+jngA6Iq|24wF4l{V*pfT0?!(wrqaT4(Hd4VrZ)f- z0J+@{z&byp0~cL^RNs33H6Ra!B?Itd48+?+{55et9+Cb22|yacSc>m?JOlR3Pu}@< zpEG?%Xm3H^GX04hTRxN%V+5R1rD*qN%w`aPq3l%w08~IV1$DwJ(r2Q6 zXVp;mP}^-pHu@{(fMI17+X;8rie;ytTL-JjR&4hq#MWKYkoNPS@!B)Kf6tMKX{eBa z)K9mnj2*{n&I^eqr8dKbFLX1$TFkDw7ULE;HAg1bSyfgK>XUb;a%LPjVBJOV8150%r-awi8fxsVS0z*9 zo`54Fx4Q`dyaoV!S3v4p$6QZxFI4gbH$x;|Mg%AY_@GDV{~#ijP%OoN9;^V){KVh{ zkgM@b$llO+qsC9-*z&%Z7*gOUWOoX}eyQBEC{F5gFp9Ywb3Kz*?NG^gb~VNR$eV+S5cJ5^2Pi1r)ffXOCb4 zQ&{{L0;#HeR%=vn1`{~5l&EkK(yKLqx15Sto2flb1p1=a83;H-Hkm5n74mtnUsX1g z9n$_NMDYHK81SmBLM!18r8trG9004aQj9gR*z2y_P}XzP_1bZL|GGy)ZbJtJ_!X2A zKM$ssv3@mZzgtl#G zP%WXPtV=J$FRJ0F(f7e;WXe z7f@!zyUHl%igB!gT#+c59B`ROM=(GiGW`T#PTEa?(S~i# zV1VbkVLC;aURq#Wvs6r(P7y#&+Vl=61puXIZY>7DE7f|tVo^AHBBAlzr{1qgRBr!u z6c2tG{ls-Fc#vq4YW+V*e&R{??MLY-qYN$&0R{lKX~!i`yv7&+0000?V7;3ppVJ150*i=FQug$tKAplZ-59 zJ#4N-3sK{IUx|K+ZifP(LGMJfj&oM@9bk?EAQdeEfMqSZ!Fnc|JNpWt60HvbpurzO zdz0NKoY$VS3i6g%((8C@1JMOwPMJq#1R}m0p;)#5`_<~%twAu~UTG8JW`~l8=)L(-Q4fqSt1@@GIoojU7LO)k? zBV|O_lmXkqcQ^#V(A_fiSp}&W)JcFwhKqDJk=X(sTJm6z*0LPH@5CAi0^2G|D zz3c;EMO3Er%|-;bPAle)&nN()>a8LGR1w|OIR|#==@i$nL|{PYzyO5ZXeh78lPLfI002ovPDHLk FV1ht*z-j;h literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_action_add.png b/app/src/main/res/drawable-mdpi/ic_action_add.png new file mode 100644 index 0000000000000000000000000000000000000000..1f50f9af940898703640d0ec123ef312e70e9101 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5XLOfj@Ln>}1B}lMdVfz37fBuht2HqpDk{4|~?4~QhC}yxo z-PI=RZQ~8bQ+5nT*bAjjow(6(y6JyNp{c+H-HwP0KYFilEV{TT=}MDg+CdEk4hAM3 Z26joUHr<9j2|)81JYD@<);T3K0RT}YJzf9+ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_action_clear.png b/app/src/main/res/drawable-mdpi/ic_action_clear.png new file mode 100644 index 0000000000000000000000000000000000000000..302763f35254b96434f06058f166885608d5bb29 GIT binary patch literal 148 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5X%sgEjLn>}1B}lL?ZU{Qy(X;5kgTthS4i4trZj3)C`m8NT tm+8H6Q1s*$j`Cv{J7ig#!>k literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_action_close.png b/app/src/main/res/drawable-mdpi/ic_action_close.png new file mode 100644 index 0000000000000000000000000000000000000000..e105a646adf4cade74deb5f0ef145322b0c72000 GIT binary patch literal 207 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5X3O!vMLn>}1B}lL?Y54#DKR3h2N`-m;ZTEf#>}C2P(kOD^ zK#dS@d*kOLS}Lc6CVic-s8NPpaP7pL!<~i=Nryj5x|kmR7~y1lm@~p@%0r)K#q!H~ z9T`$5O?;P`oXs%N)yjywadGjEWx1`!TpJi07?>H(IN10KicDMww4A}y)z4*}Q$iB} D(e6s_ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_action_core_overflow.png b/app/src/main/res/drawable-mdpi/ic_action_core_overflow.png new file mode 100644 index 0000000000000000000000000000000000000000..ed30fe90baefd28d8a887ea6adb0e32aa03e2721 GIT binary patch literal 233 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx;}1B}lMdVfz37|NQ^*jL(8^7qtJMaiCFRuaC>E zg$}PD1TsAO?y-)2W9OzR6Ir}@8bTPR>~5Sm@#CQd9x_XQI52ofI_wm0u>0X~)3t2!JDN{yCQ>Zw_Gu(_*T)}DDuZ*gEyh`{VF2Tvi(Al43DhR&@I4qjMS f;AY0aVZbo)+YZYr6=4UUvlu*G{an^LB{Ts5-2zpk literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_action_delete.png b/app/src/main/res/drawable-mdpi/ic_action_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..e268fc5a3f939ae85ffd553b34b9548e240700ba GIT binary patch literal 171 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xd_7$pLn>}1B}lMNY5M>FzcjlAJTU(%m9s`E~gYP+Y=iB)e R_kjj8c)I$ztaD0e0s!FSH%b5i literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_action_filter.png b/app/src/main/res/drawable-mdpi/ic_action_filter.png new file mode 100644 index 0000000000000000000000000000000000000000..f2bc9ca78bd9ec0ab26cbbb3a56f8f03c8ca7561 GIT binary patch literal 154 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5XY&=~YLn>}1B}lL?ZU{QiaposKONgNeM?%iBiw1Hr6MZ~e zl-+fVBzm7X%A7pnVzI76w)aV(!4U?B21W)UUmmW2D_4Ag<}i4=`njxgN@xNA!f!6e literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_action_list.png b/app/src/main/res/drawable-mdpi/ic_action_list.png new file mode 100644 index 0000000000000000000000000000000000000000..6204bb0f26a452d93d815067618d8c8b0e8f8a17 GIT binary patch literal 252 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5X=6Sj}hE&{2N|0b}=KAyhe>`J{Y(qptRGQJE$PaQJjXd=S zPMlc5@#H)6O*R$jQzu?@oMtazc>4aoKbwQuWY)8NoB`tbEuXj-+DWl~uwYoafK9<{ z!Bp1w3qO9D7d7!rOIkaFl&_(KziXqz9R4H1?!kiV7BNk!Vrr;hHrO5C@QL+=h8CZI ym+SM)saEXL&v*e{%HZkh=d#Wzp$Py8DqHFR literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_action_move.png b/app/src/main/res/drawable-mdpi/ic_action_move.png new file mode 100644 index 0000000000000000000000000000000000000000..e0b3275e1b86ec453ddd90a833e5245fd84e3084 GIT binary patch literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5XR(rZQhE&{2N|0b(()9oTe{+V9zyEU>$ak=ITzJlKxP|99 z+c#l@8oNzNHC%}c7*B>8BslauRIqd8zHsE5bE~X|k<3|kzJPs;igiL0i!_7enugO!L6cT725p+%e^e;q!rA3v_YS0WG8tAyp4ei{=%qgCbyAJv zm-%l#s2fdsJ~@}uc9P~FVdQ&MBb@064*9H2?qr literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_action_redo.png b/app/src/main/res/drawable-mdpi/ic_action_redo.png new file mode 100644 index 0000000000000000000000000000000000000000..626cf54fcc3efb8630a84b0bc717e847f8f1bf63 GIT binary patch literal 600 zcmV-e0;m0nP)deY&0x9O4*o& zh`h$L%oRRT!84XM7j zXwL!i>I5+Y5V#SeUeu1NOmphee?i{}5I5?WqyPk#uNy*LNWMO3iQ`}mfO#VT!OIx5 zfSQ6vwe6qyognzWrzrqnjDg*b!5_3w(K)(9k4Uze{I3>S6DvyAE~!VHsZSXEngW8$ zLR&|9iq}KxvWV_Y8Bw;OOxre^)-66afI&J39KnZi@@)z({WicJRoQ;KCL3J_RxwiN zK8zztqo)FZWUkJ10J&x0fX)hgzypptJw^68NSA>Vd==aNq5$;!^zF$^2XKU?Y(pux z0Q7kBwe2M{rz{*7ijfN`wUd0zxT_H|l{4j@FNStZ)L-QEO5j?Q*GNXrp1F`@a&%h70-BV>c7 z8?Z@6NH>8E=m_0_Za^K$mfR>naFj>Yo@7}vmd`Oh(sh4Gky0ALLi$7mo51=bg?liU z%2osk=Z}^}1B}lL?Y5M>Fzcs_hN(Hk8OIZyr@E>8?vW0== zUGtHQOh%qH%e(SFL@+SDZ?tHfCU)RJokEzxF&j0byNq4J0bbb)rt(iW#z|=QAZq^*VZr;E+Ig#7 gzi1p_XJF!C2>jIIVb*qc6VPc4p00i_>zopr0QtyMr2qf` literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_action_stop.png b/app/src/main/res/drawable-mdpi/ic_action_stop.png new file mode 100644 index 0000000000000000000000000000000000000000..74425f147623f1d29edd4bbbc4e14fb941b195b5 GIT binary patch literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5X%spKkLn>}1B`q*akg5~=^Z$SS|9YPb4X&}wVx_;`9Rzx# t46+WcU|lTM6J;Qwn;^lf^sYQYGPsF~+k8)uele_nb`%BHi;^l0FbfSDWCZ{z#t+%%Q< z$Q?!bkKZ;=oOx&3)idFXgXe4(_qEMlwKex~p8Bo>{e>I`3}-9~5|{2ce+}qt22WQ% Jmvv4FO#n7OXg2@= literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_checkmark.png b/app/src/main/res/drawable-mdpi/ic_checkmark.png new file mode 100644 index 0000000000000000000000000000000000000000..5f3bad3072d6eb312fd8bfed70fb6f7a36209198 GIT binary patch literal 790 zcmV+x1L^#UP)0qR%b-~UX6u{QgM$+p$GQui13*8B?77s)t}kjY(-gp9Y{?0LCo!P2GSmvg zGT{&By>r!&4F^<#Q<#=TV8Wjw-miZ{HWW~TLkx})fUrhy&ZndPy-fk^o65S{N+N;7 z!`c@IxOCJv(r~~ETv?1GjL%SR^=o@x9DR4CYptPx5}d76;sC*E3fwe-8>@zs@*CQN zfteMzW3|X(gih51M+FSMSlC`_ho=m}-&61Xx6wTiYZ#bX`Ne1C zAg^j|fDt?-khiLzALV~_6mcOzdgeH#pOS8;a8d7AsbeNEF=yZ6+WL}=g$tC89;BlF zJIZqju8ayvVtCL>IL%uu{nHTKEKeV-_)_Jv|FNnINd>gC_cZ<4z@QWIACPF{FlMN=a~HXw@B}LUG=0$_e(-PRXf^B z_eEHA(<}qoV9Q@8B~B|NXcYp6=XaItCy`Zr#oh7CI@1fUY21blYTk3A#imW zRKP>r!u~R1c#Oo)zhUi~)#gkYG<1_K3hB;4i}tB|nHFH(>g2+f!InX@2F%v~0a2EDn4#?y*WR zVcF2yU}BKi#t>H4YOkh$R6 z)R^CxbzXSoKhA)=rX^daST%U;et4>kDYAN^>BZZO_Zz2O=U>9?P&U06WV)L(gm7s; zP`}{)Y0g%Ljl4oZtCJ@DYIm@iI)S+(bJD~t%M;K28!J==7MnbFi$1zo(LjocLD^GD VQD}YE4xqCcJYD@<);T3K0RRzGQ;`4w literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_drawer_download.png b/app/src/main/res/drawable-mdpi/ic_drawer_download.png new file mode 100644 index 0000000000000000000000000000000000000000..8c8beb8ca5975bf66a50289a5e1810dda10cd116 GIT binary patch literal 274 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xws^WYhE&{2N|1QfFmVD?a`NLOg{5uIzpmZfoL>F$W%{D6 z{BF7ujA90hR6P?FLYa@mq|Exx+Y#O1`CZqiK{E2&&(F{AFI}=^9pg+jhcjMn4{IF` z$189fEndwO^{JD`JE6_qZF<77WwTto3#9rkGYRP&%(jT*?%2?>;313dfn?6WHsMr8 zG4DFzY>Sf&GyGE=D?Wr-T$q#Nx^U+43B^X-vs6wn$#d!M;5?H5GVBI#ErUP;!zH_j VmV>(=R0G}5;OXk;vd$@?2>{TGXI=mR literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_drawer_favorite.png b/app/src/main/res/drawable-mdpi/ic_drawer_favorite.png new file mode 100644 index 0000000000000000000000000000000000000000..83786475bcbaf624f5f1839b66e5dee009ff8c03 GIT binary patch literal 382 zcmV-^0fGLBP)NFv5toFqv9TJAefI3x zTjZ)?#G=U;NI3y97n1NdAl?VWd!c-1Txx(8ZN_N<(C`ExWr@efkD;_I9yP~-mMnmA zn2`(zxypk;kn%$@J|HV4CH4LJ^XK}ZgsEtOFg6Ql0_bYNV=9{a z7@Gxqsc6AojARN-R!@M*OPvaleG8ZmR^f_+1<)KxF8BsDg8{<_P+xop`T~?etjM%r z643Qea9RMhP)CND>?JqQA^mwdV1 zz1R2S?t5>Zx0MEIxf(`M6k<664uLLE04rdgB+2sE0pPfYWeD_Y%u--h6{ywVe1K)7 zj>N%&zdH;Xa$hjWt5X2O`H~-}mI}pgpGOmI=UbeM@g=a!^6&9cUS*qQ**XXU$+{2h z0Y|dO`9}0RmI8)+$Tbkdd7*;e01w)j`k2=w$qh=KEx<9?XYIUBU?<6x_iqnCI(E5T z{E+r@u3=pT9iwz-sUksJWj@xCc~;)_qe(yLMr2lP61svZ>1c}Sd`4!;J>MbFsX^K5 z0L;V;@-1`eR$wAJ;1f9GyLlWXp7!AzoZf)LZ{+lo>D;hgk^edD-q}0n?41|rKU`;# zWR=$4HEe`+*L*{v>uqW8JItaBsR-HT(zTXRaRnUkDArxyc4VMrCLQcD?9%@;uK(f> eXDf~JCBOj6(6@%n47+v!0000{s$akT z{QTU1MH=IUf_MedHWijlErJ;*b)BjtI}K!>WW^jT`t#=IX6_T47%~jRuUu_8^v6PA zJyX<|-VE*qZQ_g@btVRWk?8onA@Yz1>*0@4hdR=B=v*n#GYbA>zCyL%P-j8Y!wB8P zHYq)xB}|P!tV0;>XSOx-)UqGxI*?K$Kci=VayPT7La5I&*1ZnX7eC<@6Eazxu4S5J zBACl~A$4zt-CWNeJGChB35&%hh@QN^afw7ykf_gl4Fz5X772#s&hA%}ei!HiJ<8ze L>gTe~DWM4frC4gVIRrHot-$b2GE0WJ+q(uw z*krP^^WL4uI?i8TIZde7>lJJTz@p#pkC!o$rjrvwMQklVRq#W=J(T7a!?I3M6f170KfZnWVkt5y&U=m14-O7-U zIS2qpN{|dbBVhwel54C}QLG@zfe+XhQT1TBopyb8Jb| zf|RgL7Tm#k2>K#pRJ&}vBA&Aodm_HY8@6q(0&?PkR-)U%(MCaj$JbrM5%_(zgS-a3;+Q^ VyKL!kwQc|a002ovPDHLkV1i~M{Br;R literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_drawer_search.png b/app/src/main/res/drawable-mdpi/ic_drawer_search.png new file mode 100644 index 0000000000000000000000000000000000000000..fdbfd229b500ca09b81e091642fbaa929b668d2b GIT binary patch literal 414 zcmV;P0b%}$P)AhkyR5W76)eEnO@S61Mk4mCQZ50d+$oy!?OPJMFveT zf*=TCBB;mQ6*PsOvMj4Q5dheMNuYkqJ^{W1$)gLwhJg!!Gqj}j5*nJ2j1-?mf~VMg zsKw`qxlo28(-^vKBd}}hNS<5){t@_dQ3G1~AoNTKWbnCd6G-@b--JL)snH-gS*Htu zj?8s}Y(@LYZn0Ws#Ahdem=L67LjtiH<|LK}LOre~1Sgh70NFvkr+kB#>SaI$^cFH z!r8NDrxVi4M5IHwh!i8+0yQGV@COkvC_t`i1>)~Od>n`$1F;Vf%i_=vlHUTAw*+EN zAg%@m-8Yy57Bme$P_J`A*@OVd;7lxLtilK)pzDQ!lrt3)jx*FOj5r7*pb_NlTTuLs zfZ8x@LG%`n22fb{!NL|AS4DW@25uHJjPd;W^Q%%)Qg5-OcaWj2$Pour`x~hC43KFD z#H?uQ7Xa1n#F|Wj5}=f`7fr(xWW$lvg2bMn32%X#K~g^WMoEk_q2&WhEGbD18s-dG z@{A065`~(G`~(_g zY6Qw6VQk5C6_yl)Ubv|d8T6Pr85sL-v7{h!z+R|XSj(a<&_aun!fp#W70yg-;y{#9>MWh(n7JQ?21{^v7Aiw}s_HIx_`~*k<0000NFvnH){V#>Qb%Qc`@+ zpFe*>NG%hY4tos52|$YsXl21$C>;;9M3xp7z=DPgY6%y~7JxK?!Wx_0*|TTAA#p)I za3;qG@j$GG!*cXsal+*bCL9KU3yF98aJ@0|O$&#sMxvFMw!Z zFg!*IQoNe5Ss;T&Y%kEzRYW*Kn3!aWUVwcg(s1M=1g`~e2x&qJ9!mmxv088uJ<_oi zXwc*wMnErSeE=-ue*?>UMzkbn0u;LslzWTh@;qn`hJh`>pu9?KD%uOg`bbF@WKc1l z6!RFWhqxLI>Wec#rV_T4fd)WHcLrJsi@T`664nOL%w&MY;Nw7pFE9*i01#jRFnU`! T=-TjN00000NkvXXu0mjfu(6>W literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_favorite.png b/app/src/main/res/drawable-mdpi/ic_favorite.png new file mode 100644 index 0000000000000000000000000000000000000000..7aed1698d207388eb62f9fb5ebb3d9133afae399 GIT binary patch literal 1219 zcmV;!1U&nRP)qORz8$35)~PRP!I)+m15=P^!?+kt?N3@%+A><=)lj;&it3}JGc4IKb-yJjsN5L znJxk0cF$riR}M&eA@d>AApMXxOo)0stKc;?&#iWXV2JN_XTZ-U_{f5!KnT-k$O}j< z>+i4R_4Vx|#fV5itXw&n{rGX1ah?yuL>+*AAo2jukMp{^W-~;DqGUq7y&q@o;VLlH36s>L@L;rF(tgZ`mLTF{HH*-2%ST{9%mv(8*~eJ zZEa(y%@6{@>&;ey==Zu_%m5x9W|^6#yttUA+F(Y5Kxpu_&H{m_Ygn7aNP?TC&70i z)w{#k4G4qI`)o2!YI%k>&iCO8boZ1_$jpL2q_P#LTX%zAKC zpYt-L)!TyvC{ug}Np=*J@!$c>nnjeZ|2YqMJh@0IuENLPe$em7n%#&ateU?KJ&`HO zQQRvu-Vu&y9zz;k6pTESKyk5)J$`%w5BEC)ZS}7#D%{m%@*P^z;6u)KWq(zG85_P@hmCn5>)|W*w4d5 zQI({lBcHdl^hCm;Uzzjy#^Yr{t9_M2B<^Fo%*A#|31`GM>XxaUneV`QS&T_E;LN** zKY2oxxe?o_1jtfkWhJ5NM#ZG1F(T1~Qet+bQet>~Lj*Ju(?Q}Ut}`T)Xa@N80RPEI z9K!@u5`O+zsd;R>V(?+lV1RI@}tCE@j^BB`puMl@sgXLpS#z*ptL=zw8v$)3 z)?y^+6V><;z;&VL^U(9R?Er7<2n>#d*SiiNuj7Aw7|0%w+koBXmR;bb5Ku=#QMTac z9AvBp_9uRKVA_=130@ijjRcixN;SYc0iHhH+TLtk1L5c}66)C+;7{6)TyrRufGp6V he*Y7QMK9~c_zisXQ@7*gBF+E+002ovPDHLkV1jL3KF|OF literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_more_of_uploader.png b/app/src/main/res/drawable-mdpi/ic_more_of_uploader.png new file mode 100644 index 0000000000000000000000000000000000000000..edcaa37162ce04582942dc843bfeeb5f50bc7fd9 GIT binary patch literal 1345 zcmV-H1-|-;P)~gNeE^_d`r|1$f6JsWbJJ_hKkV-YD^88F}@+w z!Fo3pMie4y3>cN58v+R%yJezTqNs>(Q<8;OlTmuv@&B}!2p!k^@a6WLq`7_hKex}X z=RD^;=Q)*TY;TPafo3JewZf%IC5EJuD`OquE|R2D-NCBC%BO`;bMdm=#Vm(=ZXJK5 zqPc!^&FNxIOVf{`Q1*BP@7Lk}cR+g!t_N^!FwENT+E;qKnN~CZ?o%1FtXfusanPu_whkqK7&6Cji9}I+`e) z=}pJ`-U53g&rWWzgJY6Gd-zxA$8&6?QV+)4BOkG}egI@cxn-ej@J~22mg5wDoK=Py zUcu4&(gSd25qj-QI5wN(6ozL{B3(O!qxB5{aq~PJdx7H=G+8{Z!EpV{OYa*1hU!c> zv&+k&b3R`HfcL%hz5#qNZ=n3>xZz*m(3M_JIiGg`Am*j@4S*a;$1@)~i=UTY^!b5t zM-Sduzqy{KdDrGMfb}Fid6nZmGz#m^bgVo3wE<{v$V5j1Y7Y~D7*-f&WR*4mP8|FU zhpys5sI36FUmJiz99Zw+GM@(^j@ZG9>c%xS|G4RWjR6Os0oQgWfIu9~K^%Og69A>{ z-{TSFLC6$Va@PgW_a4aif?-)gi7*EMtM@jf6?bdX1BGFfc^xy%6C40&js@*gS1)~M z=E)1`OVR^vM&>@G%v+cM*c7RIkzRkU4FG1M%i+v#cvSKr)GL@yv4tJoO40*`VZ=|b zA@4Getk=ySXt5IJl!Cu5uWN3p(SJ%By(UDzXk!33Z=jpJ6(^hv7X*_8|^NtsNyBLJq)F0C<@RV4o>dHze0a4xXzo zDFD*zFw$#_34pvvH6+rJx3vK%lqfIz2@X|rAW-G&MAhyQZ2+8Mv=PJTAr1gEhw!)_ zv5-5fFX<3+08rsmsPyp~>Z&tPWbV-hpv~AfO{$S^ccFCDJP(Q)h*JP5f-D*$pD)rwCL zpbzi>#33C49s>ZK=yTct0oYKManxu3Y5<$n&@boNh;^9mmTS0VZ8m}*bLK;RoY%=bjm!wiEP&Jt8$J3jOY-rZ` z7QM3*GgUW|za3bKk5dnzUawME*{(w_;^xp0j9-Gm?*G2TO5*uEco=eVj{a`*>EBf+ zUrVLxsF&F-f2K^sNQntBqJPtLVe{XOiUjQ&&TIbyM*w0}r-ESk00000NkvXXu0mjf DXQpYL literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_rate.png b/app/src/main/res/drawable-mdpi/ic_rate.png new file mode 100644 index 0000000000000000000000000000000000000000..72c0f078409f54b85f7159b76e11934ffbcda679 GIT binary patch literal 2401 zcmV-n37+aU zR{sGAn-7BF+bpJAD51{h$xVCn4WeHFs7)n<$SC*#%$|w%1=^3HO7poY`j`I?03rXC z#JmQ~enyJ^3~P88&u>y?bRskU$s_g84us7|!Q)M6%c(8-Cn_{{gYChTq!OMK+m%QD zA)7pt+A#J9RM5T~)BX^qT+UTZZ>zBRES|5!YKu4+Tb9oy^A8|w-UN)>MMvo z0Z4gUbCh-7UytuXv=Ai48~TOhpxfYi>y zB0q-`oDNnCuxJ6kg-I_Zl@IDL48Q$o1!xJhRpd?lmMeBP+c9K$XgQ^ZTYbb&?7$k> z4Wpe@GLALc@E;m~j~1nE(Y3r3XIKWx0w93+MFp}*2f7TsSBYg@y(5~7BvtLm!PM@% z#o9R$LhrJruQ01!Z(_f zD#j@_Q17zdX=Uml5Fcm<$YWg0mo!4JrsY%UWV1ftm>kNGF#rJGDq}pr@IeD+PmJgwp z)@6{ss1}69FO!j&&y_nq?QNc{0MdiiMt?DRc?Asl68!gHEHDp}S>=RDyS{2e=_yp; zSq*!uM4!VD&_X(R&I)mY+2+p~fJ~G!zM{Y5*DO4?0?g%H9Sdb!q__7fY8KETdI5cY zhV~^X<65q6{CBQ%Is%|UEXDSYH&=rBnRwhq$+iGbdI!gyJHkkh7VS{W4aEbf-~>5(M?oMk;!zb-J*gBNC{B07M19Cia$au;DAG^SB4_`h{nc*Yg2D9HY4RY@X6^*y-X3EtcRg zSPCes5O&|-biDL;4M0mUY`&|vpVbBMlSEpE5*~ofPVu6Mh(e1xd#yO zmmq$LVaac(g^GDv>}c2Jp&lpbC>GX<|Gy*ekT1LD2U-O~=I_1K+xB{Tnr_8K}^u~bM8)<9w2`UR9X;%VBYC%G>BNFsU>L`I3GBtf@B=Px@ zsEzCjwH?`s=aD#0XTYCG^6!Y;0oYi20rXzfCk}Qh$WU{@+jV%BAgmtDEhoeC230nE zEp;IE!pK4zT3ABtK}M#e+%gnxAHXlb^LX^1hxQcgV5TGPEr~cCBf2{eQu`|g?njgC z>pZk$VB$W=`|j@c&~*V+D8zPF;6I#RBssGjzmnDM!;XxAowPV%$lVU4kH5~9b|MHv zNYS`6m&NstoLIC7(qD-FlA@-FlVu#-AxCr0-qL(WG zePKA?V{HMb+R>*`!ccA^#59wtcA}4351-MOdjS<0d#W}Mn@H6jK*;}y9$4KZIjSqd zi8C7~&MkBzjpaa0YCVog2_MV~swIg}56TEYC)meYJ{10`%{$yxsGN$G-ozZ`NPent zM;39)y;bQJK9W@A8XD-JZiGRA1)HcOQ?L%ur-)3s}E>5ebikNe)01!K-P@d-r z=r^DzA8HHUeS^NJq4FNa!1;KPWO`7J68>Id7!F1y9MA+~oeE=p0zXgAaSnwwS5b4z zZ9M(GxHX3u`~!~oAYQCh2(e8yq$(NFX;~Q956?LZ>Ep)k{jAH4C-J|O+j}FC++sR7 z?EdbBD+pq;C5W8)=&%7x=jyO>XKZpj8*OA56S=bSEj#X1WXwsZQp@|a{q*dr=q4DW z+!16Xgcmrql$0%{WS@!R;G-hV$8koCEDHKXVgs3I8;o#5HY3Cwa*6kQn>jwCL4_l@ ztp1oS09}jqZ^bT^+4R|h6YZzzcgF*W1YUzdPqP`g4g>ApbqwiD6En;rw=z|+^K%fw zI*gVptDeNX7WQ{}4*-_)lIu~)oPPHjXsr`AzvNR4UHbt45|oFeFd zvJ@fn`!+(NF3wAb5FojJ5n z@nu{cm5aKJl*)-ogy7ZrGPga49J!f1g-{2D(z(u)7h)%o$lJ`3=0hPJHIO;Cw5}mv zkwM7>ZvW{{j5@t**xeNXLRxhdCB3)vr0uv^=p?nd^e1pC_u~Dlc2_;pTWt!ALVAhV zC3$=I#6;CzIF#Bwun%zlsGdf5~HPGX^7w=w9U*Y}_`;AW~ TC^0op00000NkvXXu0mjfN#lEq literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_reading_action_refresh.png b/app/src/main/res/drawable-mdpi/ic_reading_action_refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..f4bc6a47c5f65b5ab88b6f9ef640f1e533a04342 GIT binary patch literal 469 zcmV;`0V@89P)ILOW0X@VUS0{==MK-LiY3IO>lhB;fPzyf85Xp{3~lYS%g z5kPyc82KZT4}vqJd<3M9HI@jHL{6o3lZ0!PBi zN5ICe2k!p@AP*qVDYsr{D@h((iDD5gg)k)<(EK=1H00000 LNkvXXu0mjfV`#)9 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_reading_action_send.png b/app/src/main/res/drawable-mdpi/ic_reading_action_send.png new file mode 100644 index 0000000000000000000000000000000000000000..afc3523bc04f3aaa5a09bb0d319d735f81572310 GIT binary patch literal 293 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWw1G$30yfLn>}1B}lL?PT0`+|NsAJo-NGp7(Xg1@UbP?McqE$ zahB2Jj=#emv5x1)Mn-m$9PA3scLUnzNNBJhV9-!eYm#9pIkUX6fFX2oyAXSTYSoIT z44W3S&t}$R(Dd3YfAQjB50}OizS0R|0-v2SSGn>l&upE_Gl3~FQ)4Y_=@PYh7nt4o zFDPbDWSz;j>xFzxpu>BY6HTr?{qt^4X!Q#8EOR{K)Ss|y8}o`N$&DA3XJ{5OoRwB# ozNaEx(|ak0}~H}g-mstuXfBUpqCgtUHx3vIVCg!06e~B@c;k- literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_reading_action_settings.png b/app/src/main/res/drawable-mdpi/ic_reading_action_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..848c33ad73ad5843cb885c858721d8b978066b8c GIT binary patch literal 469 zcmV;`0V@89P)F4>9Waur;s5{tGC=AO z4rx0^M#d+kn8!qhqh-jHqnZOW$&@3_0id7;;(Tb3!^HT2lm#C1ETC#|nn83L0BTwe zq|Bk*-9UT-i1UG%kASB>0r5nrLtUX_CxK}rl4u72EeC0wN#z8e4s^tBVjXaZoCHMx zJOMhuj$j%9#TgAO2j>u|*#r}z1rdf_1L7#4kCZ?d#Dh_Ti_7yCb}e~aU#Ufhb;uU@fgAYm0O1FTPS-O5&AGnR~9S^ zPaxF+gd9O?l%E**cv22 zJQGyd;J`CUZIF-*fQBXFaX=zH+Jx81l%tvho{%Xw3Pu4|01#jRuF&I9scO+B00000 LNkvXXu0mjf1v|A( literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_reply.png b/app/src/main/res/drawable-mdpi/ic_reply.png new file mode 100644 index 0000000000000000000000000000000000000000..561a015eb8e92dd058a8aab8dd7e6fe718052f78 GIT binary patch literal 258 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5XmUy~2hE&{oGvy#}g94B1%8#$4^d3lAOtxxZ-qFCfAo+pM z4K*2#rh`HG|CV@aKHNJm;MMu-8?SwfV9P$5wXbRRlqFgvB?osNjr!ww_U@bq3`l%VVeK| literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_setting_about_dark.png b/app/src/main/res/drawable-mdpi/ic_setting_about_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..15de3a7569ea0711f7401231bea6f05fc6e59fa0 GIT binary patch literal 468 zcmV;_0W1EAP)`kERtWHJ55P)ub1jffRpnzM`JR*e1}8;p zc0mv`KP3=q7xCQ9TKPq1We4tKjJFufzmGoZ@W}Ag!JQUN)0nrVPV-$H3Tf1G$b$$Etv;$GL}o9i;Sw!5s`Bt8)Yz_ zWpP3Bla?nW4`-(S?paanU zVGUS=HOd+=0Q}1W3_u590D6&()IvfCO(N74GJK2K3Vi z?HOa8H2`p$Vu_(w#nuMe0!(ED07Y9sL(Slx+)i~XOyNAkl6{%x5+F)#0C3KuP7HCa z28crRhrbbdVvy_!ov8*uInOM-aT%SxodRsCb`p-0Y-W18*9NIbr8hvY+FrH~oLoe( zVW&+fZnW002ovPDHLkV1i&Yr9%J! literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_setting_advanced_dark.png b/app/src/main/res/drawable-mdpi/ic_setting_advanced_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..ac8f928cc3b824eb98b990d4936a19c6edecec6f GIT binary patch literal 461 zcmV;;0W$uHP)p(hmpu#Ej3Il4ui97#m^^oi z>6Yp9IySg z)rTYv!oKL>A!yHnq|00000NkvXXu0mjf DLKD=g literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_setting_advanced_light.png b/app/src/main/res/drawable-mdpi/ic_setting_advanced_light.png new file mode 100644 index 0000000000000000000000000000000000000000..67bb5b2cf447b685891c1cd3e92eabfafa0664da GIT binary patch literal 419 zcmV;U0bKrxP)}PS05ZVK05AXplnXkfQdNo*S|OxZveLSaeMy{4nj}$#QrGnq znhE?e>pKL15_*9ajYK&$qphG%DUGWg2w;U~6o$&2mfCMwzFTVGHxC?$3fbo1D}1B}lw%m?+_T?AWnG4yPD8c6OM2mllY(sQ&gQ zx=WT}%N-#P)d?>TvO2iS9ADlrv*GC`2d(}l0ka8{Chhw1=jUht9U=yTMh#pSd#>|{ zDe*EMeOf4_%jP=QK+)C3nn7avY|o9d617=JEE+NwY<{?c*-h5qRYM?qk5$4I#>IRJ z+iVq>*WB;+G>LAT#mBI}1B}lw%m?+`;|NsB#OhMlq&Ui1`Ilu9Tg0k{s z<1Sf-EqBB{u1M&~2Of!@%^n+NC2Efyv1rI#u=(K%W;a=bR}F#eJyr=<7#H&?Y_na! zw&s5Nq>OD+rm_rKywdzB90}QF_YXZvj;pd{xZ>uLzesfQFVdQ&MBb@07E=%KL7v# literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_setting_download_dark.png b/app/src/main/res/drawable-mdpi/ic_setting_download_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..4471cd160e5c1ee6f9c38817685d854240049045 GIT binary patch literal 302 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWw1G7d%}YLn>}1B}lw%m?(1e*s){N9K2c@HkqfNpJ(g0W~Ppc z)eWwWs~jtKvL11C|IF;{56A?Mbm<(EV2%b${m~L zXs}Os$gxOQ{T8E&MU%>D&msoFK82e_lVlr?3#LT+O=I3*@kn^*v|yeFS2J-A;~pld yD{aP^iIN6$m-L%?bi9?!mGqtag^7WYjX~r?ZcR(ah902b7(8A5T-G@yGywoz7jN4D literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_setting_download_light.png b/app/src/main/res/drawable-mdpi/ic_setting_download_light.png new file mode 100644 index 0000000000000000000000000000000000000000..618e756f3d5ba6a53fbc88c1e6f1b77bc728a18b GIT binary patch literal 298 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWw1GXFOdTLn>}1B}lw%m?(1e|NsBrS%W6AOtDQ*Nx9>*W~Ppc z)eZKJUd|Pk+(%rj8!{IxPPoFjm`|egK!bIIpnKoQH`hfU5HjkzA>hHDxnSAT z0OQW}+zks4a4$&XAabO08G(6ynhG4&g? zb5}>ma2HyYsnPfiVD$OiWwG!8xwAn9S86R9T5UF#2D`CRncpLwi4;Uh9kF3QD}UFpBoED zK;HkBylO`|<6;+Tdl3mIdM^Cb-- zJu*m>Es?*t%TrkN6ik}m8}j0(CrK3J*|Q{IPnfn+zThFpvz$FS>u2w z>OjtoQ&{&%e<7Ws+&-eP-(i?>4(NLrL3SKy<3TG#81_2DOqlVAxPc@K45=8=7|!X? z8jmnIcj{zoAq{07Cye2t7^^A{NL@&4J>kF?TT7-w#4et|kQFYvKww!5k#;4Ur0=LJ z#NrN&3}KSqBR5hBL^>JjvEg_}S{7vT43Vt>`~W+MDQqLnICCO~xp(jrYsnbbGAvKC zB`aZvO6gqAYz7^xBJTb~L@+jS;98KW%a~mDN!Aray*5@ts8BPgR8z+M3cZA0c=d8L z9*}WPz)KlX-*TCBlm|zjovY1os1PsKE;GLX=t#NIa&ktJc9V>1^Dj(}`vtq1zXMw( zX^_x0o`37Rz~D%W)q0zxblD^>x2XREcCQL`=?|EH`BV82U;uvV{1kZ6`vL#}002ov JPDHLkV1iocKXCv6 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_setting_panda_light.png b/app/src/main/res/drawable-mdpi/ic_setting_panda_light.png new file mode 100644 index 0000000000000000000000000000000000000000..6ef4e2ff0d90922727d3e47b6c54828385586dbf GIT binary patch literal 647 zcmV;20(kw2P)^@RCwC#SFv@%Kn(Ty(Ru(ffEVxr zrYGs00iX{9fP0`11B5=f4+F#tFaQkT^h6>$o}QxfogptY=)HRefxdJ)ed!!Uce$|` zO?f;X6L_TbwK2x*E&&G|#7OAt4saT80AN(WW5f(m4zMPnfso?`tUgnt^A%vModJO1 zh@5MJ0ApVes=fx37$#G0T74L|CO~)pb5FOJt7RACS{|o1*o&DI134^mJZ)-$vyan^K zqO=Z4U&^3p$5TpsWz($G7PjPPYu94{4u>-o8Z6(PD@V6`?Pt%_Z;4%B(EV$sXBG|A|( zHfp8nYid%FU{U0l8{TPn<}5LwPrBN}|BM`BwQn80WuQ_U{y2m6$yRo4(?<+}Ide85 zyex=fnzKs2^h!3Dw<2c%#LXpBo!Y7FUV+YY5d~_+;^p?wuj5!U+a;2G9zycUSG6!2X9A!xTgEVF-Xw+bFCr zN{A&xb&R3JuK<)-HxrcJT3H}^^I_AIbcN-?6xw{?>m%40BN+=oHzRjlM{$bwQiVx%@Vb5cW88S8og)*dkHWANik??pfGDH00000NkvXXu0mjf D9?Oyy literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_setting_read_light.png b/app/src/main/res/drawable-mdpi/ic_setting_read_light.png new file mode 100644 index 0000000000000000000000000000000000000000..25de69238380a465185b4ede3b9f272b71b3d9d8 GIT binary patch literal 363 zcmV-x0hIoUP)qHf9B>rH(T(T>LWml=I+ET@ z2$@#_K%7F?M0T111pWM!n&(Y|s5hWh82}N(ZnLuuU>C~(86X2>fWHG^(D+^e6W($| zEP!Rhy9c!Hb-xpG$4CSPkiwT0@8nzy!YW*kz`+2nQUJXhwYdStzr+xR`s9NTK(Cfj z@I5LaW(Z~0dT%IaKAb!f>t=&O5xQA$0-USTDjiAJtt`x4;=cK?6#*IA3Ooq0Mde!U zuK?}gj^a6??qmSqR$~donR;&6o@SL=w+A$iPqU@Srr1+}0RYg>MrFTNuzvsm002ov JPDHLkV1n)QkG%i@ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_similar.png b/app/src/main/res/drawable-mdpi/ic_similar.png new file mode 100644 index 0000000000000000000000000000000000000000..621bd9517699124e0c3e0b45457d85d537792607 GIT binary patch literal 1249 zcmV<71Rnc|P)YIBDhebv=p4D*EwBb)0(**!*iY$OD75M}2Cvznd0CK`!MKL*6Bo$xl1~Z2C)wk;Q;j6J4oax z247Ne2$`KNi{X|6;i=x~Df4h%*HpvX(#G|!+~Cr*-Wf|rs2 z!b6MlytE%O`XaIbML<1d(fq~+9T>EiWC(fD$K?P>Z+ta^$~yE{5z@0wN z+8Y3S7~gvx&%a1R>iyD?NC1fTl>Ii&qiz_zwjeMtZ{gIy&t3~rbpYJvT1lK^FtDw3 zMB}fxW8kC60pQ?l$6bWagWORMZ1nDs%03pz*FEsd!V2;1boqyEC^+b*UJD zmWq4vSNG>&^RlEkl{r;$fLx}Fs8W_x{FLOGh6G!QGGpSXRNNYoiYk+Bo@t0t0KMY9 zc5Ck9ucC^%Ce8}(9K1euKqCO!@KcF)tTqlHl@o=~m=54p_qkC3EtPpmlo8bd#HuP6 zUSqlhfOaDQxy=cQpho0fl`{=9M9*elAViUX%fFBD0v{d>NZ=eE!s1gOK_>eK3K{(wx3Lt%m zDh?k3f~qq7AKE=ir57+Gq@XeK@F`)fS5jnjJ_`kPz2<@Qd*j;{WRxpP|MISRz;}u*vOm90w`sq^v4&H z6GN#{Nlw*%Sk-@KRuJfKs;RB#4}KpCluCFRh8BZwT?U4c~ z^3o6S`813Orw&0+S?{ z3$9vk)RYG;ZHXADkj(^gqx!+MmdAx10jnTD@*#Xnd3USNF?I|-3NPoT7Q6y!TOWMH zyso96WkcX%X#W4im3Eu;EZsEV+U4{9hWtjP&hX4NxegOP!7$}TReeM7N N002ovPDHLkV1ljm=0^Yk literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_touch.png b/app/src/main/res/drawable-mdpi/ic_touch.png new file mode 100644 index 0000000000000000000000000000000000000000..f7101db31abe5744aaecb2b264a4de9c58d28433 GIT binary patch literal 1251 zcmV<91RVQ`P)_RMY4g2Zt0AxO{22=L2GQ*#+|YM0}8FpaL<{09Y^xQ1;=yFbN;{c zJm>AXa@h%gkub*z**?v~gDjp1H~Tb&gHMj7 z(w1&hNBZD2DjOUe{Gp_zgbs6m33bM@ktty0Y)ebaP)$wEbK&81_c*%8<#Nf4Mq__% zZEYob5ljrQ0FYXBWo2byT3Xu8va+%>xZ55X!Q$egdthMT?WU%t8|dV3p^jJq$P1AO zrS~#3Gp`pE6kt=Pe@XjL_~5){X>4qK0bhI8VO{Wg$qGP9l`54=TVG%QRZ2?AOIVeH zq=vvnh5&jo`gB;N6-e;1&jUbp)p%nuLZ843uViFo1bIud^ZJHjzo)0?x7OCyyrrcj z3)(Y%AmITZO;kTLDk|z6CUift3D07^=GFS2`V=~2V`J{Asi|%(7Vn_KA1tGPAc$80 zL7ai`BrJ7bV}D4%?TOdMBlXQ@GlkCg&CSiPp@3qaY4m*spmvnQ(Ug{!zKs3B01%C@ zp+uo$Fc^L}nM~Kgwt)V-#;Xolcc61Yu!e_mKyB5uay&MSJc9uvh@v3@8hIj^@SwBJQmiIw|wa^D@$`AIbpy?+%!znI|)37{*)B zSqj_%#Pbo|Q^Ku4yas_t1YiikP9?epz~Y|)aQjlkqU>xX`hkrAaqkx*PzL~5La5Fn zb_T#;$^j6sMP%7QXaI7%IZFeGy3q>|BRr8L&&` ztH#Qp+O2mEn}K+;b2@eafGHB1TyCc`1K;UK08DBFAYMiA2HWk4`~aBL>IUM<3Slb% zz$Sr}9&h`A|3<}BU`ssWuL0=t1VFrjpqT^*&nbNzh?98duMPmvM!-ml0OkSMbp;S7 y5c0p5Z$L6v3~~7`{8(C_;$%`}05To0cHj$xMV}$(%*Fx$0000g3`qxD>|@b+Jw!x@0NhAUG(dP||+~bhHpsI)osm zO`0|i=gqyz?M-fO?wk9^FJ!s*ec${0@tmCVywCF}ib9vzDE|(eq9`2d@Bv3~3e$KC z0aKX37=~Ir;su^!8H;#;KQNDnF~+t1YHQnA8R1F1gAeg0PGDreA{to7BizO;e#26X zv8{)+WvnQQaeRPJaTcR({aX91;x4Y^rx@cud%bZ_#)_gC!Z}>Vdk)@EBdhoo-{MY; z@kOf}hFe`w6vY@m#@F)yQdLW?9Y$~(@8jPP!e1$+_1$jR)mXXoF5?TlQkBXM7{|L9 z3n9#>ly=-)b~IL+`VtrA9j;$EEU%p7A%t5ynQLmSD2h=zQWtULz$EUUVfkno2_ej- zl%6-cU>oH|@rk_r`YBS44C4$QhY;?kl(rrI%?{Ab-S1cz#C6IsS?jsRFSZ#gcfd*1rI%X7PT_pdBvr?G*-OpXwEXHZ=$w$f)Qnw_QV+wbWjtSrU9l?nA9~Lsf^YD0^m&U%XVi zPbqDL5dM_Y|LJDe^a*RYhHEj#m-dCGqEkxGLkROYF28E`o4GaofGaV^rYmbl!6~J+ z5W+3F%6dls6nj?WNL|^jC%tPVwwQY?M{c}QiS4t1Z}8ntk=mr?5=$wqhY;?|iSny> zP50~jtM~<9;pZ4*%Y=Qu;}D+HUM6ikA(}W n89ADhUp@ZrWaP;Ibx7<#2)Xa=vVI9v00000NkvXXu0mjf@qW#4 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/btn_check_off.png b/app/src/main/res/drawable-xhdpi/btn_check_off.png new file mode 100644 index 0000000000000000000000000000000000000000..f80d003830b711829372e12b0dccda959cdb4e02 GIT binary patch literal 370 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7)3l?978H@y_u!Yf5iK?lLTuR zb%eTQfSfr`rPSOnCEvWZ>HDPl(bw-}uYc9U2(+c4aMGV0dQ(#-xjJmUqnawu^lj3f zX#!IGCj*0i>dnwHx9}*NRJp+Xme;gNv)?y$etKTXlDjs`x3|I9B|rMv?}t7=KbU@v zF)Y})x~=K59*fDtuKh9 mb1JN~dm1R@7!GuAXJBSv;*!d9&bYo1B;x7n=d#Wzp$Py|RfmuO literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/btn_check_off_dark.png b/app/src/main/res/drawable-xhdpi/btn_check_off_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..f2235b963de14c5e032b8b61bf92f384c4171f16 GIT binary patch literal 381 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+809@(978H@y_uyb++-lpW`5pn`R#?b-w!0ZP z>n9x#bQ62n7-p2Gv}mSlLyP_Phsn!?7{l+MoXz;~Gs_`suA8s(iWm>sEv%1zXXx?Y_orm zuvS<6+=TyL4tF?0w zz^$m&?&l1CPj7fWJ-c=Fg$n*cmQ`Uhj3&*^trCAw_5Pe-%l=a_((f7OyPaGW{boYz zO^wRQAJ&R_{;WIM$+0wa+pW3$^TQ&+UYpv_z|7!QY0`Y<{M*MM5l>e?mvv4FO#mk; BmMH)L literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/btn_check_off_disabled.png b/app/src/main/res/drawable-xhdpi/btn_check_off_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..f0d1cac779ef480b9d44f06cf0df3855f6b48889 GIT binary patch literal 362 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7WR=Uch$qtY7b$UeH`0b1kBBZ7}Q*yvg+V zen&q7?67|qa%)dqZ-+q(yAqPQe$Yq2NRWV02mzf81VDhim>R$Cs{jLktxMd~*Mz5M z0#5L@zAsjL3JC*|@TXV_Mb`z?!`s>E&S%miQ9u*?tjo7RaCpuFqQG+%5DlJMKqU>H zQh@FF|LSsdc#|RVRV#I8&AFWfc&ZWbv*Z3Vv}Ev90+wfI+$AK1rxGwbcGg!ya(K!g zL0<_h;Hk@?s{}UiAtpji5?H}gmBBg*26|p2VPD%K5q!w0aQ#SN1|NDY&@3S_e2m4= z?-Cfm$65`ZOJD*Yb2r60EBF)w6a+v31c()I5X5x|As|;Y zPkv*hpci2G-aQC_U+ca)kAO?BuYP?i)$PrPvVTZkF@%57@Q*$X+HRFKyRfE;*H+ow m=gK1pf*=TjAbroL00RI$Q?n zL)f5f&`tnskWK&_v?FAM9Jq1m26Tjsz;iK`poZAt*?uqie<`w5HIlzCPTzB7V-N&E z5ClPxY36FkP4(OBl7@<|r4WveuC~6pJtP-E@Xs_n3VomrT^kb7h2Ah95xmXHjrZ&; zz%BcCC9n2%=<6^WU{^z`&@aXaSSS*(NFiX9fdB~57hB`EQx#z0@2JGpw&9tYfG_w{ zeW!8HAz`Kw{@k2>N@&L}zzgrX(_Ku{qftN~{Cdo{TR^7`o>RbS z;{UA6+2L*G%unq=leM4iG{AGMfNv8Y(4iHB=MwO6G4XH-Md7&wtleZ|B@~C}{1J?m zAOfDd42DV&1D~Q1>XRS}o~sP{Bv|N;N5bBBLn-)_t+00`NP|zk7wDBx8a~Hn=vN66 z;B)N;FC<8T&$%5wOOOPgstWuip)zs~RTTL{L==1p0R{pf00PtsI4a^grV!8=o_~IG zq-7W2&fYyKfZx(wldOQB`+A9aac2jDGxVseJ%zP0vX06&F;5;r5ClOG1etn11Q-B? W;@O~V&NLzb0000R$ zrfC**d!uWigrlbG+gkf0E`Z?IbXzHXpe0>f5>fYUFdGrPOF`Erq`@q3*sn5ptqbbw ztHcN>GWxc?O9+@}Bw#M`4H#n}00Q!ho8$LfKlYL#{D-UfXsWA1=ltA9Kns5=VXZy5 zs&6U+uHf4`{w(1*b^)F6zB=9bMcHTtJcD13`4td{*DN3kyjB6x;JF3VxxsS^ICcD= z^Kx`}u1fH-$d7%D*p zJohx%SArUNiTwa4!9j0tCHUE4tqERgL*$_Zx*fV2q=J_$g*#sdZSc}-fu|Bu!^c<* zJ(QpUKGtgRPJ$Nrn9Jc-f+l#WS>P`TnhsSY*$`0$pF)6v00@A9Yz0&qaa9rmO2f1B z8zV<{0sidWX9nTB;25L5`=tm`!%n);$X?t++x{0m8N@P*NJRJIIx@(6+;2!bHU c)blIA0Plq34TrIM!2kdN07*qoM6N<$f{1eHw*UYD literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/btn_check_on_disabled.png b/app/src/main/res/drawable-xhdpi/btn_check_on_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..f29b4772076ab301245419862ba38586bad0f725 GIT binary patch literal 509 zcmVDj2A=fDRM{6znWHl2s+CUHR<0Z;HHA;L67)NM@j5I%e5w&}+ws^H zTCwn{1dJV%g-R$IK9zuGV#wczj?K`VgyP|I?FPprhzOr^JM2pk6Q1iWFegFM zrHUd4BBH{V5MUqx0w6%GfL>9qo zcT9svugcmLWNl0)apv5R4N?5xzOcXW9F`!RPL^5m10Fz)pyubx>s7fYo5_ z|4M+G>1sgGFOjkFr3@bg%)nL*fm1}R!R}HfphEMB4O&tJ+`_4@f~p7f>2x0E2M%D zpqx0@1b^i*1JoK4=J@V$=zzzTj;u7Y_ZfVLE6r@LYzDI-U{0jFJ<2{5s1#T}>odF( zKovUI965Oax(s9D8!G_?G5ak;sIN197MjvD7B+@HvJr5i*_mUZHuRB= zfRb=vACZ7PK|<+^0BVQbW%02y^p&*^=y0C^0Zu*~JIFu7mNqsX41M*YfHnaF+E5C3 zm2f4XNFatH6aiENGD(1diJ`Av6fjMIfN3ZNIOSW9aH>y+Dxe|i?mS}fIZ^vJz7(Kv zMV`1nc8v!*(56+0nwK*F~KubtWrJ-en;Fm-SY=#y_Hd%n`!A}G41v!4x`M(Jp z0(OK7x}&jBxF85`Y`Rn1I$*77K+XaLf2L`6);5FLo(nu`a_|h-W$iKerAGR{LK4tP zZcc1Ov+VCd#es3nUux2x(|LsJ*`NyYWA^8auAdn`fjwpJ7aG)ze+GL;yi;R;cOhDi z#~CF$+6xl>%50QZWcr)nXfH|i+tg8Blgo&VFv^RP|M~+XiL0^#fj}S-2n2$$; z>@atS8TQVw=PTH=*L!E!nW^0#QsthDW1sD`z9&UNYO&uvKc5fVMZsV&7z_r3!N?P3 z><;hu`w~7j+MmQHp|X7YR|3d@EBR{SX)M2{#2C~g1-br70br!N5kdbZMjy%&UK2rBa-tUiD!c|sc>9PY{2uz$r@}IZ12F5KDCfLQPEdVXa-$Of3Ko6zuf9v2 zqX=iHF?1JHAsv8KXnm0my(Pz>0t^WS7-9>c+kc2T_YJvVc*2oJ07HfqMS2KJuC$fl zw^}?kyg1A}?5}W{fB+XuuJjHFyq_0UIL6QIzqsg!x#aujK7$`ATE4XC*LVh zPe6b=iUSP$^34PUXe_ysI6${n3)@wx=KmZieMP7oKvH`X!~3@hg#cYb0lKIHK+&OF ztb(oU*!EX_>Pw&)TNt^l0@!cxbCPoVY8CG|}W8Xd&cE!(w65(=!>{+Q#xt?|N+RTnWMvwJ?Zqpv(!XEjWh5f)_r= z<$O3wl#%m19@hxxc|k%GBF`%_nh5z`lG238^O_tYM4lHVpEv{C$VhTkmcd{!7z_r3 dkt;p~7yt;3DOcnVT@C;M002ovPDHLkV1oV7J39aX literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/btn_radio_off_dark_disabled.png b/app/src/main/res/drawable-xhdpi/btn_radio_off_dark_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..576b92f10ce740a1554bb6236bea984f5131c03f GIT binary patch literal 753 zcmVAy;Ujtbvb$N5WEq3OU@#aA1|v=6 zp$B|C9tFOfDIcA`2<5r>R|0^+V|bL<3d5gC5gOS*kn5im0B8EGh0(u>arEUGxd+21 z@K`!9oDyOWLVQFk02nW4yz(4XHHdJGN&ql?2#>YFtT#B57ihO z;P?051wi9B&?cI%NF!@ZX!jO;^P?R#LE@F+qjNywG)SRCEaNXnZEmhZkP5h~;q z%mbDhSM+AQB7!jF5eQHia&_VXYR4sRSM?SDL!$H*zH)$*+S@U#|C_iFpz2Y8 zDyRThbf_*i#;xnn?k~qwmq5|BFzT`jP{&e>khuWIS{QP3D*$F#)eD~c_Mx!Oejb#F zE+@!c>wrDA9Kp=20?htOv;6LZmBM&LO>IZbeTsm6Q~9RudTidg62v2Fp%G!@GAGEs z@EDFVUf3L$^WmtYjGX52xI#G13lfSDX zLk|jyhlnG_-vEp;M)_X>z)BAdCOr+tZ764i0MM7Q>tW&) zh-(>dO$IQ;2&r3)MghoV9BQok7*`Tj@nhothC0zh_vBbFX1U=)M%4rWF!^Ilo>GY` zHa18&LkL5JLkS?ByFyq9IC)6yM#h8V7@IP7a4hc-Tmc-{*xs8(HEyv2Aw&mISMXrP zM}Z399y-EXVYCGRmOqI+`B@N7Z61+}mPs5C#R;r<8KJX$!X`@{f7zzq*Z>$1)`xrj zOvYEF79O4`TQWLI%UzhZ&Ncw{3HnzuULx*c0Q4u(FZ4`ZU<#m%k0kXuKN9ivhP5D5 z@_R6O`j`TM0oRmkH@^FG1670zN)tDB3cs(6AxbT$96Ap67r3QOtq;vygE*jU0Dv-<1Ljh$0J;sBLpK%xs@nD%0HEjc zbaHh-zX1UHu^jO1Qf&h&*UZ(60}7(;%o0vMBU)dh=4{boKy@?pp0FX>AOPH%Y=%q$ zz#e>Cxwe9XrqChbz^jnk;Y$2Hj>@nZiS2?-@%L=7FkK9r6QPnA7@*vXct>IY{fMYx zBN5?9tR!3jiL46umms==$uAMBZ+tXvdn)`Yehwwojtt@{@Z|Su4m~cgMnwVeS+nn6 z2@GM{i;+j0f5@-KJS5iYm!q>F{1(l*eLb#Y(Miqa2NIKN?I$wo_O!JhNvYjoD?gJH zM1$1I4<)BLgF47a@~JF~#bU8oEEcP+`W9dS9vP!}JpQ-;00000NkvXXu0mjf2>C{8 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/btn_radio_on.png b/app/src/main/res/drawable-xhdpi/btn_radio_on.png new file mode 100644 index 0000000000000000000000000000000000000000..7e7e401e22ac68577308ce10ef4aa1b7196c0da3 GIT binary patch literal 903 zcmV;219<$2P)^@RCwC#T1}F&Fc6JX-vK-V-oWn! z;0eSV;MScxccVLY?%3)LWKJNSKyDyM@CXw~oFAtX@{2RluZomaVAB2QyzV3rf$q{}tWcDVlb%DxQmgEDdNI(`yPiEkaGg8@+N$NcA@fU~l?W!)X~A z8()zz@Zk8Ewp#~|Lqseo5v9vcUmQEXrtR5-^Eb^n^D%x|1Z3%uwg&~rb9$ZVRlmjR zV}3K8sINMDLarHY(J-U`zdoJ-=KPTx520%2FcY+^MxQa)kU;QMP2$9sgn>uruYAt# zEyqZ>rVM?MV5sBXx$C%sjfCyk>^a|YBQ={jeQtOVk32>)Q zlk4sXV5#ohsq^h5{5MIF{|wF8f?==XJR>jWYX21{E|g`n7pp*_|Dbuc{tt5$iUO8l z)NFGY0W9<`Ki=44H8QLb+8|GJS$Y9T9qBBfbR!@J6&j#lNM1q_RA@o2%D5)EW=`^s50MGk%UD)tKdJa1RSYh;7SlXaNKp`kw z)s)#DCrRn7Vyl-aFxDtgY>{i);Fth-l@$t}EhQPk1eIi&94gxCBmxScOdSfIT{vP??kML!Rd@9j8B676Y!ZL|I9~H_?aB>joyAJDUS?7e#xh@2m}IwKp+r^ dzT#bg0RZVDJx?VIjh_Gj002ovPDHLkV1i9vpZ@>= literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/btn_radio_on_dark.png b/app/src/main/res/drawable-xhdpi/btn_radio_on_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..0a24dcd1ccef07a8f49147a4a0bb65eece0e44f4 GIT binary patch literal 902 zcmV;119|+3P)t@h*ouix!c5daP+a4DfJ38z(%B)mgwfRO9ABmn>E zR|}_gWYn=DV-~^j1zdIk9H)rb01;itPG15$zlF;=g7f#BamF#eRs?`ibGRG~9B-hV zpviyndY|8zCuEb+lVQ#niiQ=ufBSR-(D@@8PeD3aV?w)G^jTsKNfeJs5({4vW)Ypg z;hcT*9HYXVGLJ!mZw@#poR=Y*`Z|xgK=zT|bwUpdv|;=Rz-07F+df;m6+`xal%B|4NQqOR*jsnsfZo{sPK+N z!b^3@2;M8v$YfxMD<;ooAo*U1#8}uWMwcW=&HIhYd1)-P1177)pnv(yLB4~IQc>_x zVGL~x7XcW0m;OHn2BnS~VF=nX2mN0NQd64+lx+l13a!>WzxQzdUgq|i`?aBs!3bC? zbxA-166{l$*r2dA ziWKfFa)tL)u(TmK0lA`PQ^+C^kZE*-aGeHT5fFR7ZVVe<#Khqj0sn>3=SG014K@U{ z3fXFunFHXYk7F(rj_`4ABZYof4Js)B!G@8AE($L0W1T>aw@U^E>>#z@-y@@6o0Vq0JLZ?@n z#Y*8A3C+M>Kt)u5)>arzeoM@CSNKTiZOMLN@UGXUsPFip2zZ)paVH^!15w*i6&g6U z^R?@-x$oKFQL*%t^DxNi?Zcc`ojyB-ekdutQj|Gq=tWf>ES;|~;pK`lx$^0skH<#{ z(SB`zt`8)7L~Mn_>5L_uPlBTp@K7SZ%|}xFnOx*>o$iN{1Cb%b7x`3{!C){L3k07*qoM6N<$f?}eOKL7v# literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/btn_radio_on_dark_disabled.png b/app/src/main/res/drawable-xhdpi/btn_radio_on_dark_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..76277477a64a7a65a75c5ed75f6954d8f3c95e45 GIT binary patch literal 908 zcmV;719SX|P)wPmj&N%B`=~>chwa&7H zgoK2IgoK2IVPe$xgxBk}pr4Pezg~X}G#WMk6#<}d4wsTzp?nNUsIdVdr+<5wjki% zbB;M&me9sANbn_KEtq{U6IHla1M^g_W8ZlOJWilJ3KU9tN{%k1Bxg+p$7Asd?!z)!IuMP|tPLdW$y(Y0F^_2hoFY3z|F4DwNrNekA*RJj8KV+V8+^XZ{87?a znR4!^1hmcHl-@1_u7FEJET}tV3`4+&N=|#}l9swWAOX2yoo|$~hRp;CZ^Hdqggj_w zDMN4q9+`V*rK^O1H0D_;Yj_KYU9amyhP!Ow@FKuA>4*Ve8Ego6W$sP1kvRaQF(*n{ zqqx9>aQ_g2fPR9Nz4M@P&u-Pho+|QN( zb?#03`8t5|wV^hk-kZcEz;qCT6(XOLoFk1PVbLoAlyo#qo<28hRw_qJs53*!o91jU zfsJiqpc{KKTFfL8OX46zxNwj)FP7A_bS@C?+_EWtqt)ne1M5^TALk?e65umzl{V z00{{R2?+@a2?_ng(LKC#f}eTq`wf0x|8E3j+6uJX@jr$Pg;xEm1elpFJqB$fW7U>2 z+92Rq+gXHxQ$&2zwmoD5O0+hyLOUn|hT2{^%(}vvG_&|?Z+)9MF+%m^7%!UC@LAjK zArN2&e}T?ZsBp%Vhr>f8p+s^h7!v1F5{_CNym4%)Z7qDqRNE=OD>?}egwCC7d(24m zgSMX$6lMl_sqKVN!$bUjeIEprI6HM1XTE6LMGSm9e6As2Ow<|sg;Xa57|Fk62ES-A z16(mAOfbgWgskBZoH4RrIFeWFmBg@D0xO3+zrc)<6F$wm2!h7(*F?a3*AR>f3}#uw z^D7LVs$t^Z5)=5*w_*%g0?&y-e6<8T$>n^06mA4fb0Oqkaa}>6>Ue%Z`|_ zGKy$^{u zq2~fO%nogg>+-7O!LM+cL01IaNp3;}qA~nEsEBnsbhqXU#*bjtNzVq&AfLdWGrD?a zc!o-mUTB!&;2$w*@{@$< z`P#pThmp0e*F7)2@9L0w{z?4&t7v;2pk6R&X!8!Ae?2b-Af)`{PZP{?t=g zW%Y8BB*Tvd50mm&W_J9&GMf>mfx+`v;M1& literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_action_clear.png b/app/src/main/res/drawable-xhdpi/ic_action_clear.png new file mode 100644 index 0000000000000000000000000000000000000000..89decfbf9a173c8982d7312f589500ff730c6232 GIT binary patch literal 164 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWw1GZk{fVAr-gYTs_FkpuoZ6_-22`Qa0tz7e0)~{uT%_F#MNV zE;noCm&Kk94NVLUY2G!rcZw(+c*K;iS2(>`(*bD0?P(0m49*?~y!SsZC;;*qJYD@< J);T3K0RRxLG=cyC literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_action_close.png b/app/src/main/res/drawable-xhdpi/ic_action_close.png new file mode 100644 index 0000000000000000000000000000000000000000..4bc0c7773ccccf5d0c5dc97107b6f797a4ef0014 GIT binary patch literal 304 zcmV-00nh%4P)p^AIoixeyI_ zvk(pVCj|O|&=6)`9wE#ZlG2jP7#Deb(0fpbybN*oqQGdkLzftOgjJlMdF6OAqY3hcCx}~IU>Z!*n)PpVh z(c&M5yWHdTMhNrM5_ZAy5yGurfn704k|dey5?}y|qA=~kXUJ**00000 p3E*n+?`nW9+=(RmMrL_IB1M zRf!lXEB$hrH0#mUGl%tEe;k=$^)skOWas-+`$`!v-2Z%&b+`ZG7cUR{^8n350}0#q y70#<;3NUASHSxUK%AVKPcHK)CRaWx4u6%&O$Mra)Y3Ei~kRDH0KbLh*2~7aO`AhQv literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_action_filter.png b/app/src/main/res/drawable-xhdpi/ic_action_filter.png new file mode 100644 index 0000000000000000000000000000000000000000..3a7d2ac36b0ba6356ddccf307361f5b2c267f871 GIT binary patch literal 170 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWw1GKAtX)Ar-gYTs0J85a4ljd{>`Q#Kz$nhd&=vP6{PQmB=}as;b{vSGZjl|g`B_i{(40PBMm7d#4+CDejp>tt OdNTlJqelZKHofG(q$$0o;Vy8!-zEtF001Bu<(t%6 z=hWqPcv4C|!~{Uj7t|d;%o_@#weuNuMKDK|szxm!2cPGZgcw$Hy+dQ^k z$QMAz*lw`}F!jupOb(RJvq|V3b|C-&=-Yu(YI|yTQ-P^f(gJ9hieAMgC46FzThvi4 zm^n4#98k=uu~K5FK8jWgaaFgKbRICuv!RwbLT*W4vU?|aNOel@z5JTYxeW#Y0K%Wh zjC9!AVNF}v!PQDpZ==R}Mv|;0U8^h@+e(s@7<$zbZ2&dnd@U`BajgOX008`xmjD9* X>9FWgCe3X300000NkvXXu0mjfwLX_8 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_action_move.png b/app/src/main/res/drawable-xhdpi/ic_action_move.png new file mode 100644 index 0000000000000000000000000000000000000000..8b195ec5e97e742b2d3964bf9deb4af03620e0e2 GIT binary patch literal 492 zcmVm$t>Zvau08Tdo~_kmn|# zj5Rv_ZwA-PA2FJ93S;D1&)JU$c4LWa=WOPMe82FAY>>FU5!-|$zas91xyzlad6*Ou zvIFNi&2a=%|0VHR*+-Mjh_)4xNEMwtveB*Ad~I7{?v6G7lM|P!f?0iBeKLan;QW50s*ar(1+jGNtQ!K!zkjrbx+3?UQR!<|S9=@wdnTWEoKA z1hOGe<_VBZg4qzrhCm7kG&D4H!p$nQBbUp)kJ9B-3;_UiCg_Xk6OfB+?3?vE!fd$3`IBE;JL;4nT4LKnKu%K>Q5~B6a{w zU@dW~JL=CNB|IYD-4cB)6itp!|Ff`_5RT2eslm3KmFPd1%SZYIYH0iIuwO! z&(GO__wQ1_+HcKJu)_pD8UtFL4G-v8Qq`3S2LZ6l%V0qO2z~~Qu0&dxlwx`7^!`;p z=K=#D_<1zC1bNNKes<%$|3IBX&p)>q2?T)PXFF-HNiT|`H2r04$S>Qp9yp3L`F%CE zX`i$Reh7^oLV9;wi`MPhxI9Mn=neXSJ|a0Orv<=oO|RYHY2*gsHTTvK_#LZbjrB|! zK=7)!15Gp-_(q*tq_z4jocdyT#B3H+gtd&;?LX08@CN8hHWx&HW2#AI`iC z8+88jE&#q$XE!>5J?k*~&n;^P?DXk<_4@iCE3hDdngLM9^Bb_IcYUbh3jpK&v2Y4$ zJ?>W4;mp}ko_IXm1b_}kYOtrdBy_O=sKm-@4@>HWg;5+%xY=+3oaDYH;bT$SvH(!< zg5XwG;Lw%OUE!Spdh_(dPF>V?ECBXmbexM}&F~dC_9<$|l4{QaK=0pyE)Lxz`1wgW z8;2bW0HwDMG@2{d0I)m`$C7H(0-zRWv=VEsoCm<#q@0bzjs?Iq40m-geh~m(Z-_X2 zjtgi=0G3ATQNoQ`0360Aw?hvdKvn`lySh{s3xF*|Hq|9M?c(MEKzs01 z77Ks{M7G!?y5j)QMk$rW0$>c0?emCEyRz9p&{vw&6$=0@L$wYc;1S(D?o~y#K}nLL zF912iW@HhUg+7iwM^H~62pSIB7GFa5)9`jM0dTTl7S~nA^wito zDY}7_5l>Kkki$x&ozT^S0MIe`9;6QF`qD@3ec=|2h=aQJUYOsMVg|U@QGqBDIEaToGo^}Qkb}k${+(!RQx4i zJj;L*=!BxWkYzwo@t1({ECWiQ6N>6W*=N8%N?bosQ>ztg00000NkvXXu0mjf#>E;M literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_action_search.png b/app/src/main/res/drawable-xhdpi/ic_action_search.png new file mode 100644 index 0000000000000000000000000000000000000000..e57f46cfba340d6726ba648159f4a23bc72f7ce7 GIT binary patch literal 543 zcmV+)0^t3LP)&|M%p0 z=a&%j8O!>$Bd)1OzErUi)@yS|?hL^zl*M}0%C#<`^I?wl8_z<(0WTCWW1o7ic?Q-z zha%8nS>IT9zzn0@@tau!TO`}`#&;Tu095ZWU>c%`MIaQ)SOl=D#T%w)^H`P&u+CEg zre#hTO`&`oFpERJpjwpbaP#C)&DaO+9H79V!`GgiYM=qm03Ny*YB2g2$2`G86QGuE z^fK2I+VYU{VXZ$=fLTazS0AIT&VaBPfl`ggOcE+=Y|7qrBDRw{Ujo&}eCJ1h{+83( zZOh4QDNK)a!CGp7MCAyHXW)9qfI>1V397v@EKr<{3OaIX6$#=&L~ZsVSJyX1JN+c4AL?RxESDuL<_54vfkCyqM&-Mbq6Lq#XOi1ic h`Gkaoe}=mN0|4!?A&0w={F(p&002ovPDHLkV1kE@?S23N literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_action_start.png b/app/src/main/res/drawable-xhdpi/ic_action_start.png new file mode 100644 index 0000000000000000000000000000000000000000..3633a202cd843c9c4defa7e7b76f3eedbe7b2e7f GIT binary patch literal 393 zcmV;40e1e0P)T-e>jf2on&7!KYoy%8+Jm@BvBMa5zjPDXHrc% zAseui=mwace?vB4sYrWd1I&k%$C}M}cm^l%A``3wVgkQu6vzfF!7A5m^jvPJR!rqO zBoM4}O#uJA*!5PGYXSzz%ia5~Tod?h^j^6p(pu&7k{753Y#Ppym0#4N^A%RuZ7&8Gc)fg)QZ`Bz3RwC-v;#va_)XQDy>-7@i n)c+W0VgF)M6h+yLuK)u8lP2FW^%ixR00000NkvXXu0mjfLeif4 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_action_stop.png b/app/src/main/res/drawable-xhdpi/ic_action_stop.png new file mode 100644 index 0000000000000000000000000000000000000000..3e911d45bfc510854ad776d7d96add2c43b9b4d8 GIT binary patch literal 169 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWw1G-kvUwAr-gYTsX+fpuodyc;v}ln;wUsjURXgPD(mYTBql^ t^ya~|?YDBzUgrmyjTKB-vOkK6he6*y?Blt21@}Q}JYD@<);T3K0RT(4Funi) literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_action_voice_search.png b/app/src/main/res/drawable-xhdpi/ic_action_voice_search.png new file mode 100644 index 0000000000000000000000000000000000000000..8e746d5979b9e373c1ca720b67e8c069d7e43484 GIT binary patch literal 438 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@j1itLjv*Dd-puIDJ!Bxz`q=LPhh!r!m+%1&xkkYoDz8{N zIC+FEqz|xdXsn*-mt|pj!?Sp0=w9`?3VrqOHp{*~7s~1=@MXP-Q^Z8ml>QHk*{84d zDzDR(nf~hDTJL#3qdi`mJ@=|Pb0Xp7tDY*;>$-di%R|Zy8|(#v4upaOLJT5I@-7iJ z(H>_OR>ZUSie?<$?BnxJ`PVM>UsT-3j(TvBpo_mP{_x56%U=~=napA+^{ewvo%ZGkpY^|dyS8zyft%mo#b;SI&fH$~_itV|_nH$cZRZDV4E^{g&;I!gH$k;MJT6WaydV!X eDXcK4>(t`w*MA_*Yfl>!w&BmtzmQlJ8eB!F~R z3RD1*1d#4ZfeIj!0McD4u#0J=i(0ZAs}^9WKYb(3X`ceP+Xy2E8|w6J0|uQG1zD8< zSNEn{0-AOVU{L z@^l$tA1ogaZgGNG@c?DuomVK^lWK2%c0R>cG{8_idn=_=%4>8?+u04qQ|-a}ss!i( zJpRaXXsA6HtvZ0w{;?HS&iV=#zC==a+$~LL&Pc_Uo~|l@d3Zc+7Z)2Ik6%$WFcSY| zC8Ig6JYV2d6u@vIy^0Wx!@CxvJdM?~4L-1B*lmo8P++JpeLJm;^um2{TzZ|wfTTY8fqst_ysYV$gH*~D36gv!sO38oDJjx#&_!*M-VZ@ zk1^pyb}J$5Js7m+f`J)L{;rKbbSl>J4`xD6zLBKa$_P1H3h;z$U^s5R2FSbSIzNJ( zd;c2$?ctv`t(b5EA@e&l213wPISRg4%lPMgJW-xd4cyqn0>gD<`#^b5IZm9C&&lGk z&8bM_oTIIPLB8f5!NSXFTHE*cH=Pj}dR|k<8o0J6ec434)(t81pzA>y{iLC;=@HTA z3v~mMI;CY?4VgQq7Kcw$E8Gw0-9~>Igw~9iH z_Kig?%i_wrQg&?cD@xi50bKK0W>M}EG60R6m57BLNX7Ig37ov$#DK=YozNKQN5UJi z2sKW$>u24JDQn}H!aQ&^@oS?+&V2*_w#W_y7hr!X*0jwD;xxR#7qH`BAwL%J1)i%- z#9joP!Ir`qj6=L$c43T>eaGAN9dkhpB{O$XMgbnVTy_lb^9`O5KyI9WpM*nn{dlkt z1Z45h!^uK=&6@r|5o2=>vpXt0gXJ^QE3LaUE=7;h_|0uQ8M~JK%K3UsSxv z^}0PVq^GqSW(=+iracKK83d)k84x!E#BTT;ker90TWP@aM1lW7Oqg5XYP=7hJ-H}z z6D=Kho&w~-U}M=18_QlnISH7T7Cg@Z@_^9te+7;=ZwgRxXUfQ_8jTs>#+ozk9Q?5= zO1g!T+1Dv!uX?tG5`|Y31=`TUN%JjegT0J~SiCd{4Czj{#`Vu`~mI>4!# zm^62R5?_#j`17P?89#JfXG-|`=2DazSU7vwSl)(><#~Zsl?6Os6lg<>Bs1?YM(2HD zJ8xSy@O%XbPxdH0;oA*^?wCi3Wd+Y)fP6H77)oT{g;v=1Ss=#SRqWM?_T^FM`4 zLNne@Vqp~M?HU#|ngWQ#uK+55NCHTAr9cG`NdW1t6sQ0q2_W5-0u?|c0i?T9paO^_ nfOJ<1Q~;3#knT!>NPzzWFj^pQ*=9Z`00000NkvXXu0mjf#+>L_ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_dir_dark_dialog.png b/app/src/main/res/drawable-xhdpi/ic_dir_dark_dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..f2e1b5c6ab7ca5d4f4f3632161a2ade9bca1a538 GIT binary patch literal 499 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEVBF>D;uum9_cqGfkJ*vOSK!J^ z3zsadtRt#L{4A$DmVdI=lbqoW|_JC95m6Kb=#fW7Ff<+ov*~xo#5iee?Wd z^ljtKGwarW^56gIN3}|WWPl~JPU9)z2Hy#)2ZUD4VVu@r#2B`4JLk=YHO^sE%Kl%S zXHdXX`99-)-m;SorWLY|ZGQLU8;hBK7927@n89(QsXce{^vcJaZRZr@&+SQW{yclPDVE-3u{idRU6sYgVVy$0CcCQ8 z2kFno`(zxY!skrl6j0KuH%d%Yn&NkWTgg5{tN9Y=gk?LTjTwc0^KoV*Echanz_x+q z2J;P;om>TyPmFfVV0_0M^gQ*C+)pQ$hxgVTQab^L5pSgx=do2vJ;|J~+%d0Zhu|Z( z75924#2mV@d78tOzYL!Z{TU5f&n=wV&>^kVwR1Pn@2+}DGqxSNSKbn%6x?vf`MHSF zfBsn<0?F&6le#Krhb(!PK0*3KtX9RxC6%gavjX28Q&IS?E@Eq+=3sJo=VN2$qI#!` lpOe{KjOSR}86!*8Gw+IZajg26rVNZs22WQ%mvv4FO#m@p&W!*7 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_drawer_download.png b/app/src/main/res/drawable-xhdpi/ic_drawer_download.png new file mode 100644 index 0000000000000000000000000000000000000000..42292689a2157d35779aeb21b494cd6dd7a0919b GIT binary patch literal 355 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@jI5q6jv*Dd-rTh1YjO}^O(?K!x+>Q&tHmon<*ntKT-#o= zNfFVr6CTUXv1RyeY<&92hd3vN#@PGfOpKXQERp3=FIf&7lx(pwY@dI;u*S2l{`32E zpxscAaJIqh^?(28J$rB9PwlDoNB9a>N^*QOb87kY z{feVrx*D@f*w35;hdSPTa&2f8aDCd%I92fI)5r!dj<8BI2g!qa`b-*%E>Dj#C_2T& zu^w<)F(sT?MCr<@)l5c$tDatEsnsCqGWF9{=l8 vn{$Pm%eRyqYj|O9@@FF}LzBXyKP(aq`EJuvqWHDdLC*Gc^>bP0l+XkKLNbcP literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_drawer_favorite.png b/app/src/main/res/drawable-xhdpi/ic_drawer_favorite.png new file mode 100644 index 0000000000000000000000000000000000000000..8cc62fc168e9c48d02d8f195504396e1d46d5fb9 GIT binary patch literal 615 zcmV-t0+{`YP)NWv3=(FC!0^1uOR_X;@1xiCmfVwE+I_wE z{Px~m?_MFiyu40Yf$k4RqtO7DK3d3ceWPuYB*|*V*`ejSaRSB8y0|=`-RyN(6d8C! zi(q2gWE?F3bV?oGYbGj2dx3!+#|Sybon{=05g7xZ<40V^hQl*QoALdWhl`0;fS79o z)HJ^jA!?YHLUc;Zk9n-t)j7Ai>i2rR?PjxC7e6>VV4lY7#K9GF!rrO@Br8Ol3fY?! zVCe93Yp4P+a2Q~q0uVY3pfm?gN*=&PfZSofxe7q$FhHgPuyPn+B?Z8yDR(pr8GBQj z#F2vlk@70U$GX%Z^J&>pvuYV#^6{>1?bwS@J`a$v_x`~i{0q0YEdxB`-~Z4CV2DpZ z(Ej79CG#=N$5mhG$RYsZ9T0RRyli3IFKlfk7Ds+U=iH}M9hEO33%|(1D{ikXopGhr zjQ^616@Vi`XqndnI0A&Wd2N6OA&kr$0W<(%Y~C24071`)ZjJ5nlc{-9U?cyAWys2! zXX6VJ`qP)5{m+fs@1z?DE2?Uw9 zeq0SiL&C(S`7q^|vqQ&0=S2_<;^pP#by$A_3;+*=*$bG-VR--m002ovPDHLkV1m)T B5K906 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_drawer_history.png b/app/src/main/res/drawable-xhdpi/ic_drawer_history.png new file mode 100644 index 0000000000000000000000000000000000000000..aa689ee975f455d5f7f2b85057910a18ddf019cc GIT binary patch literal 827 zcmV-B1H}A^P)0Sz@xXklqhqcRtin8qN)B?X5pT|o*=3T(;9B;W!<`Zk(KtLd)y zb9>H5_oUIC_jYGzc4ud1$5B+HMvWRziMZ+sI-O1vk0xuLPbQP%St>vRw~5jfU4;Tu zP!9U-t56(R@DtFH$8aJ1Pte#^kvM?h+jtyK?HRt`^D&!_bDSt_{&JP@bY8mBN`E!%#0+D;H_1(3d7BUk z!XxX*!ff8qxYQ7txjE(Bl**m~&S2>ha6B+%zniMF;GzH@T&3_4?d*&-r(Oz>>*Ss% zUqiN>vEPaB4(##6TgGKNU>alx6v1V_j?0ShNTxov3MA1p#iqjE&n$BK6#c&W1T^7E z$V~>phGN?Wio;`qOEm65i3>{NN~56(={Z98uBGX$6dt^m;B|-BcT#%r5LwY4gXXFf zK?I-l9mgQbQO5Mn?DqYGzi>N|S($-UCzE`F017I**LnZIA(!r}5 zW2->sc}MQrmO(s=1zO&X0K%V>t2=<*-oUo+N}7%8Qh!}VUFcEx0WbcEP)A~3* zC^4@!LQSy}JFLXXIRKcCF)W^#FG8FEadZa2n4cSiHtB2L0r5)#p$N=>Vwg4ftA_9Z zvF{83=Jl+9{NWZ8K#T#gZM6%THmPSlCVGXwQ3*QKnSBLvJw7CCU_sNYPz}uo4Efh6 z@Vpx4%X&PeY6YHh%zW*@Qx(it2`DZIYv3uufZ~9#2A*OFC~B1ko?;3pYCR1+k2#>I zbr*QU2N!*rDh5s4+bKa=Tj>peA=^LLQeDjU0e-9py_dd!IOsjVkl~(d%kL|v0c`u0 z`hAjjKmY_l00ck)1fUORp<{(J4%JyH|4flcBywC=0R{lmfq`j!)~+J}0000{Yh5rUDCgEm$fl4$+c;?3xo?g1I#nAZq%idD~9*-kZN$ zNH5)&ObE=(`+3m3Zam7;RxBxKEP zuuxY~SrTA2D;dmn5ED@(FxP~dI8wCv*Gw1*PkbpmJ2#unZL`_Tm=YVn+#J^YG%ceY zCdtTSEQ^r$h%$gf2p;484}|xZAYjPk%X9pCqss}5`E&ewPdNnE^of%YNqk*Iqzwk6 z_{8RML5Qd#Zwl@G0)D^dusb^Eoy;F&e_W_yV@zsU4~A zWTIPxN!XC+%yRu5B2EesI*SID+H_p-aE1*nK*gp8ck|Sw6NlJr4hka-(eV2sY_AjE z;X$la-{raMW8EFPP97aeI$cj6q4!curC2VmO_{dll}h?xJRI&IG)sbbCj_y)aYavzAWghrajg2eg%0p`wnY4BQ+KOJT` zaU=Scc@Lh6Emgc?o?e)4guuCp$ z_S*OEyZi3FyXQD-S2TMg#kbeR|Cc-~3?dkEcuo@W{X zrcF1-7&3YYPfj%7?sL9)x=7&Ln=ApGYB*1!zsBN$Vg3+^j%N5;<$z_xjoEFE6hl+! z1r~iXU0^<+2i0oz8ILXKW@LB|V^4VJ+ZI4s^S`h0{H0Cu;Tr;es~94xwbMkPtV!Iu zCfwFL2UBIJNoq$j0xI4IrkqcZ0Y`FCJf^G|0M%q7hYv?^q!`-G6hPXYA|ew*r=?0_ z-616*Gs(%y&Wc64GF>?UX)yt?CAlDW94~7C(Gb_TwviH5!oEuaP$liYAb^IZM9CC@ zFG!z;;nr#mKy}b~Tveu(6P5SVw3_85PRkD0Nw(hivpD5-yHH<|%p_eH(y%nXqw)So zydOY){)IaIhwAmn5Y_Vs7^^^Elj?ZlJl;EtsW`7N$46#OJq$RBSv;f~CEzSPVL4Ij zoUC!t%C@C6?29ENY?P*A6k4FtH}nxS*cQH4sYPhn!Wu6&baD;*+6fkzaZYSn8crU* zf~UzLz9P4*K)9waV&XSy!14IQS0$At41a*(_n~o^r>$^=6^N2854a87jj3(O@QLb> zO1LmvDkn-#P9g=u%?kk0gy^EVFEXZ6ZUCe}euK-{HmeAcp-zbd~13;;8;J^4;|lxP3|002ovPDHLk FV1f`cR(Jpa literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_drawer_settings.png b/app/src/main/res/drawable-xhdpi/ic_drawer_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..adf96ce1f055da58c87d4c45e65248d7f5ba00b6 GIT binary patch literal 706 zcmV;z0zLhSP)+4b46Jx|L?r+{4Um%Ddo2SHCg z{cjqWQZycqBY3QUBZ+7OJl}4&jS0J zKx9d_8wvrs+`wZBTmu)t@Rg$N9dHi1_9sY|0#C3{Ht_yUI{;9~lq^m% z#+-l0U|M5c=qo9x=bB?{2|?7NkB1WF8RvJmSxOC6!Q@O5BJ|-|xd$NotRxZ-hHZkr zC^vED(Z-M|1ppDV@`ybOAV#0$0U#3il>P6&fWAqaI5mLN3{@&bk+vC=j*Z`fN1=j3 zs>Cq;Spa(#WRMxZHCdhmK@Utc%7{7tBwLfQ3}zZZu%TCw2N_P-nL;@|oEtbC-3y}L z1{1Saf>Hcnjw4@M^Hk@LbeF_+V=i)D+5=b)YslD(#89PCzLl)0!T!4QHj4X@jB1Sl zygO3<3WbDvrDRvNSr2%LBf?*vj7g3WSs@@VM*3m0JSAq orzO-7>;Dk+)KgDJ=u?0J07m`YRZfh1Q~&?~07*qoM6N<$g5nH0djJ3c literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_drawer_whatshot.png b/app/src/main/res/drawable-xhdpi/ic_drawer_whatshot.png new file mode 100644 index 0000000000000000000000000000000000000000..9232a187dfcbc18ffad511622f7d6be4e48be48b GIT binary patch literal 737 zcmV<70v`Q|P)B$<0RWB$ zuayJTq5D=6fj+2Do|6zarAfDR0@O7&sS^n=zcBnCOQX=R@L(qkNNDhqaZb0)2qcs` z8}TWfxRGmdq9o}^`$F&Ef*$7nIu3&14D@xmTz+o37B_KMOjCIOcevMrbMk>Of46xC zJ$na0$8em2u0fBe$S;|m&kEMoSet;UV0}t4r>%jsIWz?66L}R1*lcMQajOK3D@wO* za`u-A;B{$~$H#$ZF%v4C>yitWC zsCookJnz)u8cG0D%N&miI^0Z?z?3nR+eqSIQob3b}CE2S8l`cwD%8 zMJdZ&Mvu(}Vqp!S4e?=c1zY^S5V2ZT$(1f*MuX95r!3Nrz~ z^UP?;-RsQBhF3}iGSk^Wd5ir$BS>FA?MtoTlZPM(Uf|<7K9TrM@KywJB=8Z)sltHs zX#3*N`7LFpB}&ImY|BHM%&bN2H7jkqBhuy%ZE{5e4lYnr!MspQEwvQLmjD9*SE<6Q T?6fan00000NkvXXu0mjf2)jhu literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_favorite.png b/app/src/main/res/drawable-xhdpi/ic_favorite.png new file mode 100644 index 0000000000000000000000000000000000000000..7ae2a47ef9afa2e88a64043fd2695845ca67b504 GIT binary patch literal 2607 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 R^dJBL002ovPDHLkV1hfP(TM;6 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_more_of_uploader.png b/app/src/main/res/drawable-xhdpi/ic_more_of_uploader.png new file mode 100644 index 0000000000000000000000000000000000000000..a445a66f287b650ab00096000d8ef5571ea415ff GIT binary patch literal 2938 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_rate.png b/app/src/main/res/drawable-xhdpi/ic_rate.png new file mode 100644 index 0000000000000000000000000000000000000000..9a8e02cc44a2965432f699a65429df30e4c92826 GIT binary patch literal 5455 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 FV1n6Ob~*q6 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_reading_action_refresh.png b/app/src/main/res/drawable-xhdpi/ic_reading_action_refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..0250a1b732a617edf0f6667407834c83972e485c GIT binary patch literal 844 zcmV-S1GD^zP)-Cz#&kPKI-@zswkJexpuoGAnhT%zLOdR72-jO*GfJiUF77W5X zG++m?9S}Lok_SuRe{WU=AmVFUag(UPwmh%6CI3s%mH;qt3_nE=#M?lR$bneHBQGLA zWQaVvdTD`tKp}O6F&8}YR+1nLyn&yUqa=*4WhAl%CDT}IpYKBeLAS>}{Szi|Fpyt1s#~e?BuL4JM;=!^ z(WKpHgCSLjGo}i=7C- zalM@-@`>qpM}iYb7<)6i*W`r+pyW4UWaYt< z7xjP{=WIV%@}dHyUF1#2zHRcN0!$f2&Y;O7UId=$I(Sq@&*XpJl-a&eh=P$rM}T_l z0yJY6AQ`&=qsala!U5^21h|Yn1iFfy1sz_VUVuuWO0))8gcaC#hb7tqP`TSE+!NSB z*Bok*FUq33Kv&h5P`T`C^_PsJx=^JW3J!#>R5Esntgl-kPVsbP+*QnBPPGvaAXlmw z#nRe=1F){>D-rUt2O-wQ=Zf6Xrt2>i=c*4EVhWTa~ZT z&(a`)ai6R3u-zh4QEgGR;uZ-jx5%iH*A{?8aNZ=P^McS&lG18pQ9wfpPrn1jV5W{q zguW}pgG#pVU4R=QoVLn37oa7ClV<6M5a5m&|LyWq#@1%6UEagP!^6YF!-Iz(0R{ke WT4aSb z8_}w+wSiIQ%q)pyo)<>ehyP6X@##?e^hZ8%p$5)X>9mA-CJNVrtRBUtcYdYx^;;mtux%8|AiZ`dO| zL|t=% z@}dGht%hcWe{6+HPkx11NLU2;R~aseY&h|BgBjx^Zl#0HXH28^?+&X3!~6A*80PO< QXA2VYboFyt=akR{08&t=uK)l5 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_reading_action_settings.png b/app/src/main/res/drawable-xhdpi/ic_reading_action_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..e718c0d8c268cfc167cf06a817b28561ac97806a GIT binary patch literal 837 zcmV-L1G@Z)P)G_YQCSa}-J!6chJ_sqYFSXiihMuden7h0+03|^#MVmdt$f}$qe&(s zX?LF`?bA9*VlWsC27_U!NVNs#TswBJ(lmXm;n$T?@b4JGBd%%8%OC*oT4_wQ z1l%f(iI#v&X^asND*-o3V{{0B6Z#I?8rma%Z|H}odCtrL^-PfC94&sJ58Ea`M0Ef; zv^fc?gtmaTg8W@kKMZ{C8T>z*0Oy8zi<r_ZG+{0-Vy>_dGeun*n>wG8kOuo5t!53VYWw%sy7Orf2RVDE=y7Vvedq92^C;q=Z$7Pz%^$-3!LNpDF6S7S7B+kv=BU>5ifQdYqiNqYDw93E#o!Qv+G(Z0?)NDEO+ukN2b_ic5Zo6W~GE)?+28 z{8>_~BD7Z;6D=)ys~0|1>u z6^|?T9E;qK38fRVBhJ_$UIB z@WBGo@Ie9^;1>&MfnOw`37##W4W1>S5uPld6`mxZ86FkT4nHgK3-FjgtsNeFoK_DX z`~Se#;bRJjz{e60gO4E~3Lh#U4j&?*0^VY=SmbjTU;vP};UGRSe;5D&002ovPDHLk FV1i`!p+f)w literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_setting_about_dark.png b/app/src/main/res/drawable-xhdpi/ic_setting_about_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..66458df7a1d5d85327618f3c0f097a765fe750bc GIT binary patch literal 786 zcmV+t1MU2YP)%^}AlI4Le z@%_#pXZs?AmzS5<1*>7|Mg4w1hAM*6p*l|~lnm?DYL!?G01QS$dsJe@#~_3?766i2 z0f0O`B*S$IK`eCljD}{z$0fYY=@m0wAQ~DB51<;6*G*~xWey;IDgyw<<776WiLDCZ zyf6g_spX z@ez&rx(gv}0K&J-kDH98Zemf0KcP130YK|~$07h7YIFG^KoQ=<8P@MD)%zkeFR5J^ z3JDB_!aYP=;z8pxcxVa1%j_TMv8>k^!e_oH4*~LiEbLWGHYm>lLt(#$N&pV_^R}x; zwy=AE3&x+crCkw#5qM=}5Ws-8&#WF0JJbWBV}Vh#VD@)SH=G_2ISAmE10Zk^z%^$Q zG;y$*A2W?F`05S&w{9bww_>u--DmlTh zw%ke3B?#%UTjU}DYli?VRhB`Q0Ib;nC@04S2uV?X(WyXa;Q)Y{awJHrL1MZ03)|n8 zB>1ekVS}7ww~lG=qfXup;@w&phIm5M)!49r3_dKnPoXfS?nBpRXl_|}oKc(g>;@@_ zm|6s2Dw{qhogso57npt`>VISO4#t1V{+N{`gt<30hk(W}Isd@U600!;w(&{;Y6?Ng zcx3lpdLVp4b05`w&O3ZD% Q82|tP07*qoM6N<$f`F-2X8-^I literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_setting_about_light.png b/app/src/main/res/drawable-xhdpi/ic_setting_about_light.png new file mode 100644 index 0000000000000000000000000000000000000000..0589e49abc4475a9cd0680559495e2a0b130c958 GIT binary patch literal 675 zcmV;U0$lxxP)IMNv!w`*UI0BR@V^Z#A)6Wdld0)$5qkm6=e z4M1P!qzXYN0#XkF2ps^4hX4|touG>kn`KL%!u1qbPBa?jm#wh#+_PYTD*}iBB7pX- zqlaEA9RLpx0o0*KK&cGCRdK?cr@&WYgkOhiT>v+a09;L$!MjHQ?%DvbCdY>;D7)&5 z`Zhur4*(R#J3(9xO5&s6`TW5jjj5gv?-{*A5?NArwDmGn;HXj2vv2NeMF2*Dmjx2a zufur50|D~~owB)DBxTY|&}DKngoc7R}Q#-^8U%?3O(-L z`}OX%cRdV)#bU9JSd}CywQYM$hi~)wJUpKPWZ`1jAIQLqJ zAs~U^LF#WfB$KFYBF-lYKmcK1CFVTp6u@+=Pq*0I$AEz-kU@H)d~48)DLcwF0JuUp=1sw=03x}Mxk5E@Wld^B zn`)YYT9WU()O}5bI<}Ry6nj#-0O-k02EbHwUZt#+*CBA0*1cP2Fw_aaG}4oqM*z(f z0i@X$tJG4S4N{aJM5eg7;XW;I|{HkriD&> zs;z$CA+|1bK5zVTF)91yIVYzx#_s1(?8=t*k95*p^J>{HKefN2)2bX?Zg%M#d!=)f z=r8bbnq~U$&Fo8^r=G4lHreh)T*|HR#E+&KP8I>Tu1GDJylTzVW6`V&8Vc5~F^$Xm zbe}!v;g8GP?{TY5liF{5qhh7#^_O-GMI|ShXJqD9bd~O7Uor1W`y4LI{WtDLpK|kK zUZB1u?9BThOZ|jTd{?(jox71YJ-ms3*8S^uYMc2=W3;q$_gI^Ae>nBFc*08Wj@It= z4lj6BCO0qiWN0*>!z9jlhKJ>kCQP2Zb&C%Bhv z*fybSH(TPb9!AgPT76zMnFl*MZO`c2N`|{e(E^d}=~uF2~fo%2tQ2TCri{ru)Qf9m6Tz6Z^# z+&Yvup1$Rud!O^tvC`|Cj=zd-DP|CH*cY|y-J@HZX0S0r+>ubj&}GPUK{uk$*39gt zEpvydztz<>-?&_F-gp&vs7(Cv(~pzCS8vjOESqv~)BF|xf?e`*g2c^YfJtES)qVzM YhVCnO4_z10bp#pb>FVdQ&MBb@0B4RXaR2}S literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_setting_display_dark.png b/app/src/main/res/drawable-xhdpi/ic_setting_display_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..3f673c6af814e434639870971f9cb1316071debe GIT binary patch literal 463 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7z;gJ978H@y_s$6cf>%z?JU#TZMPM?GlP{|+pIUdz1KKx z@d4Hy%;gHa6IW<#TWa7tOE)7!Liu~5@if{}-{(SnhU3Sh+6ASz%90&Kgyp;U{(OEg1>{^$S3j3^P6z?@El@&%= z592;+aU5RDzD7wngSWf<;XAWu4G|2tnb>|_6LN@UXjmb0O|t6KoShGzPsyDw<*>Z* z%f!u9y!ZCX{SJA)_Rb+zpl68!2biYZQn<*%?CfXi%3SbyrfMc51H*znk_Jol&2txj zHrw$nkg+5w}Mr4J57zz2W(!RiUdR#NE%@ zw(o#Wl3e4#3#uMYzlz`IdY|{4r8sNZ^W+UF(U%oCnnLDuUOD8lWox_aV7 z^XNDbG>=t*ktw>;jKP)V+NU)PTpVwn-ehDEbUD?{z@gAFRh&7&ML<`d>p+7-T%3#o zljG7oTn-FPQI)0){47^LtzzKgc=hxuW1WJE=6se1+=rAye?C!MbkstM#Zf@Tw`Lyi zJ8!Kt{nhHzEA7^wF8^P__TVqjUkl#-_`~^)w@HEHQ+y@20fUamO_@t)1DQcy_H^}g JS?83{1OOQwo_YWP literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_setting_download_light.png b/app/src/main/res/drawable-xhdpi/ic_setting_download_light.png new file mode 100644 index 0000000000000000000000000000000000000000..5637493d275036682c4257ed283850b964e87ae8 GIT binary patch literal 416 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7@a&_978H@y}5Nz@Q{K4YrwUSvzT%Yuw801OJKg1!fdu6 zcM0R|4Gb!L;l~+%iwpgmf3hI?hyLnzm2-wXhf8D}bKmT>Z-2UE%iUlXxsKv@jXT;4 zdcCTBS2(|``p+rQz`&xwz{ugizyxPD{M+Fu2bU7A#NIa>zM*L z9hdHrYA|eystjdNX1V(56+;5c*G~cr0!&M6cpM}d!{oRYv@q!Mvun6COgCpd;bNef z&o-eWA?%(=f?`u*XNP)`38*3m zwBQX2Y$*z`K|xIlY#M5V7MK(yv@oO~p)GW1s6z@|P*8(5k^@>PPaP=*c~anllI}+P z(%1RhyS=}6qU2XPy<^|~&hG8)?e1MXucVSnDygKB?k)L}&YPQ?0X(YEYN@X;p+zSr zC-E|Y1C~{IgwWPfU!Orc2P|`^3LwF^q1B-Uif$3K5ipMm7954{lJHrC!k@YNF>x&XE<757 zA&7Yku*87nT&w_EV*{b^39SO-02u#pBs?y^Ez98{VEGPM{$%}FVFvaEW}iiEQlblI z*Us^ZfMoHE6_E0Z!e=*I4kIxxPz*P7YE!oxrwl&V~MubmU!kjE2wv>V025}$WWybDtW>4d5WF?VQ zQB;QvDJD4MYK>&6@0i!^tHvvr0GLjfn5*{HFA6XX82grr7$H@1UZCUROi>|Fi{8$PVvvo%M3dc@Nx-sj${Qb1LhRW%(K zQA~;(O<@*Wl_((4#OLCGBNaEe=#$A)lXMkY5i%`ZMUMJNcv@A2ilB)}EI@rG%K{}< zfWrCYN=X%4+I$(#ljI~IlN#+#OR5l%QN^LbM5GcgvXl2D^2G5DQ_TjsM!`fbcnKS;rp6(KO^icnO>7K zIUl%USafxmu=F%3yY@UlqyT{uJleBIt}jl~FM*^i@ z%L{DJBz*_#vhTUQ%OjzPk}Ye0<2!Otx(m!j2=3%!w(k;E>Q<*@?Rr9AGIqJc_(e?- zdGCfNxFeY1(UZG^)1u-Fe_4w)bq5IBnxfk!&2+r%f50fIq>@T1sib>I{{$ESJPyxj T2TbN}hYtuJAU;43aDZev!^prMASiom?hHpW6u>>OH&&?; zMi^m)5k~m55J?ulyu8Hv=R!Y8=XIl>DvF~0DGArKoC6)x&g)7)wWj3|Q~M+crls^2 zfJQh2=A~dvViB16N95EoFhbb4{+tjtcI!!RqW9+kna@3$H*gEQ$OIr%dmGF##6o8` z@9~Kk)8Yx?Vf#Y%PQH=24&%(R;WCi+UOK$E+bO+{A|Yv6a|MtDLK4>FH@KeA9Ng_TP93%p$KA-?9b|wfR<2fMm1_<~K z&aF8vo&e_*oaGlwq0<+xZ3WwzBNXj5L0duI?P7WZwE#jQ!x+z;xA6X-G_FT2|O{Ff0MicI#rK^CZJkC zX+TamyEkJVG~oc6(0KBIHbkggJja+f`;EggEj2hP@#(_e467i4hMXvco%_#_jWZ&y z)(*l`AgCLz4*EUYPuQZ5erHbk-qzzY-zD;vnJ5zkgukR&712X9+24#QfDIDwtvx;} zd)0fsN;*QA_beUErwVVNPN`-{$MmpdXKAAkXex>CvV8#NRd)_=%;_E{fz~V?eq|^9 zu8#n0wsaM^iNa}aTnVe#3yAaC&uwhJY>W zuI}HO?Wu?o3&1C-fvQYWB^Jt~E$2g`docTA3RKX8)PII|^A-Uo;TAP|CrJiZ7$IN8YusOvg5^CAHVh0qG( zsjo%=&Kt$MrxY~j|D<~ zRTuc)Nk#$ea;d`3@<^b9V$0j#M1dR>HNZRs<3R?qqmWc}raC3ItAxH}9Wug1Lmd%# z?^Fq91T7xDofRyH$}gf*8Ea<-=p4}wbIN;VnIRfsgb_vk literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_setting_read_dark.png b/app/src/main/res/drawable-xhdpi/ic_setting_read_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..eabb5a7da87543dc0de2b3be1e562a5b50712192 GIT binary patch literal 623 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m;^mt978H@y_vnbuh~JQ?dI&2Z?z_MnVOo;{=l&{cDMfK z_W|9xuDoT}7X|vR$`;@dnRd%I-B^Y9q1kp`p8xZG6k5%S_s#tLy+HDOvjWGgjdH*I zIGPeXXX|dgdL-%eilfgz?+jpbI9T8HVELL3g(JD_^JVzwZT(pLf7_>2l}@t-HLn0(IB6t zmh|6a?F``tpPoPSGv)^?&XH;edR+bcS366ie5rdd6t&maghvH6?I@AW-DNjqhsH8Uoh8dQ^4F z;~bN6-2(0h?N&t-Wwx$~dz0aC;Ty9oL-?x$+aIlX%UjvMP2MJ3#Cl;;d+wF5Hk*I! zUG}v8Y(v1cow;7e`9m0FOy=BHV^OuA;LE7?SBmA}@e}z=baEIU$lt5v9Jzf1=);T3K0RWa2 B2G{@q literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_setting_read_light.png b/app/src/main/res/drawable-xhdpi/ic_setting_read_light.png new file mode 100644 index 0000000000000000000000000000000000000000..cd02d2d99b9f0b28e78fe51d249291365748e51d GIT binary patch literal 630 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m?S)1978H@y_tPKx7k48*xMEBc_#2pV3k-fcftD=@6R%% zG#D}POkl0ZmuO%$a*%A`nZWoW(kQ6)z{cLYO+WoMISKi^tNT{_&pCmIt@)r?&Yz2% zvDa-cdpkYdu<3@Bmg@68TiF~AE;ssN8to_GmCVq{@?U@{yyW-#hM>QGPiu8#%Y;RR z#HP81#2wo=gQ+0lVPp=If=l-{z6M617jS_Cix@lh{<-wM-$qx$;h^RIlGyFbA75Yl zm7y^Bvr@mY)^=US42>Uq>Ya}(iC)^uxa0haa+x|0fg@|*)#x&~l-Di}ioRldQY>9r z!0UAOKg)&?HpQathAY(b6659PJdWXyVLp*k{kDt*wa8y`c1iI@v0%%`j@)d!SS93~Pa!d)DEw?6%2sB?Z!6ADpxGz*HHF+1sSfzAbfU%e7sQ zeQeun*4uIglbz@E9$vfh#m_6u(F`lw6>ikOXyk3kX)F5OlCYG0nUsRZd(G!|V*4fv z{bW+m+w^GyL@sD0S%zmRtUr@(>joDD`B9vnRpkjXrud$x@N!_g;Uvsa1re(#j| zwn%h)&BCbCS9T_>wj{MY~h literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_similar.png b/app/src/main/res/drawable-xhdpi/ic_similar.png new file mode 100644 index 0000000000000000000000000000000000000000..115c3169485a1499671d6bcd3a67d86a5cd55a86 GIT binary patch literal 2748 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 FV1gUDcIf~B literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_warning.png b/app/src/main/res/drawable-xhdpi/ic_warning.png new file mode 100644 index 0000000000000000000000000000000000000000..c4640270c55df72dab03f06f5b0963cbd457d5cc GIT binary patch literal 1098 zcmV-Q1hxB#P)PkG^12HolgV-LPcR6t2*?6C(s6X2? zGN|-=o!|`s)gO--Ip6P*K~x1*eU$n=_G8&To5A)Q-^q1?mn%k}08WwkUd-5S(@Z0Cq@%8p(z20CC+6fc+XVv`m}< zK>QRU9%pvdh-?GkC;qE|a9bme(!?czMxsqrM*L%713=c_13;x_tej>}08st8MA>VQ zkSzcSwF?MW4Ps_AwFZFrzIc|y-Ux$aTmX1ZJUJ|WG>H43sS5y`@w&<|2^Rn)SB01z+7`X=V+FhM=6d4kUsK#+3;qY)Os zJfiG2iqz3~JDY83>&w}$weYzI09Q=c8v~&FgM#W0HVu|y@9HR!n!@X#3*MOI6$Zdo z31ku^gRKt$5~hhRIAoHW5df+`3g_pT@`TrB_yQnLmX`zJscv$n0EF&LUFSLMd;yS< zHvk}Asx`C$Q2kkWboCa8Ts6K(%Wyc`Ra#lAjcR~!U2XR74UL?(!AlM2F)G(_S^zd} zVs!BGoS zXN48WFPv9FIipx7&Dn}6H^wgiiPecS=XUP0UZ;|{PTSnTV60|W{^P*N9r QJ^%m!07*qoM6N<$g5h-C_y7O^ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/toast_frame.9.png b/app/src/main/res/drawable-xhdpi/toast_frame.9.png new file mode 100644 index 0000000000000000000000000000000000000000..77e69c72ab9afccc0afa1772c9087ad3333189d4 GIT binary patch literal 2090 zcmV+_2-WwAP)4>Bp-m){Nm_DgYHl-Wa(q~OE@yJ)GaJvv_TlM|*!x$Sb3-OK#!Jfgt@Iv+d#iA$%Zmk0q zs#<1m!d`qBAHr^|6P7YE7GBX1mUj+^a1c+R#L1#41~!nsPfR7wxLDTq<73E#V9Sw{ zcou)bqnN~WzeVe-P?Qmjf5qXVD6aJDhJBf^N}!wZ zapbs7Sk+2o8qcG^LwLC#g4Hfmp655>E_?;=!}@;QXT)gWEdGt3KD)^L&-?;J%GdVq+cGU8bxTuNv>fD_NFJmSx$q zSUn4s=lPrQS$qd~U{$ZzTmh^SPSgi+KFhMhWm!)3a=|4peU;n23HRdj*wV{DRxl^< z8~hBXm+HnZ$)c*r!Y%j~z91qAiPo$YE6rxSkY(Bb%ChYGB3%nriL_Nbk3L-uB+;H5 z@D98cPiI+nu3NFX5~^D6b_kI^BjR<5&X^Hv3;vU3*=ya1)om1dGw#DDsv9S|;|4Kz z_ldden2s%~^6u`%7p3K{7iKK$N|t5McIpjwBvkdDbPv8RISzF&Bi4y5%gRpPv#u#r z6>+#ryeDn%d5DP_){4EjGRv}wvMk%~adq;H+KI2?Z8Z!o(abIQnwaCZw6&-z!1_%Q zyGs#=`WX|e)L52f|18V$^~L^hQH!dUygP+Rt7{)(VusaXj_<7Iyd~|HzKtU4u&w5S zC9YtbJF|Lpx7Y-xKFikEE-o?R26;LC3a_1AZO*CK79l~CKo{?ba#aTsOB#oS-@zS<;IWpa0mEY-wt*n_(l zE`JLxYO~0@TQ{IVC9Y*%l=w^!QCTGY%*zw~?>9mYibZ##2B?exaY6(41`ip|9)i+UsHi;pTBEwHFPhIeK* zu{hU@!+*0v?HJxz64zh{NvP#OlTe$6IFLkpHZdnuawJBV4TMk)+&J8^B-*f+Ig3iW zxueTCz1I{<42T(_riVI;L_4Mlp~g^aO@u_PoM+CWULE2<674y|oKUBSIFLkpPBSOe z(IE~b(Vn9up_T(pLcJ&|KPERlrKr$`qcdv}uamwM8-r35Ur*lO@Ms7P%K5D& z7H%A#!o!kNF-*=04NlGfvPr0_-sz(vq?8yAFXGXKwP#yx#ZTgQ(ys3?m=<$Cx$qaQ zgqp@9!d*|#s=?6^bN+}HMsk%d@Y_y1w{?7Ow=B!4 zEX$q~-9FzgiM3SDiw2elilXQ^p{U!_9H;PO@nW{9hmu&7OJeR0SP*o*7G0f$FUwMv zWoPj;-Xc7~WLceY9)H01aID*gUtO0hsKlB{hIx~4-IG}DxgdhAKN9`tyN;nQskTHV z)@!1jX+u1-k~i3z6QYCY0nwv(Nf+t)1vby~4WbhMH$*v@^imu&#QV`t@L*9CXL`9< z&(&HgZ?MF3BHXlH3O7yRANVf*R7dmVTFUBF?^zQfH}E=9#A1CN*ItGk7wyQuFA^wg zIed{?kk`Zf7UaLc!I5o2KA)<$$IYUJK`tufuT;@6v5voYJ2Y%RyIE9@pM2#B$}KPx!?!!gb?avzr~r)P$s6TkiSM0 zWZEm7j?7%i)D4DL=<%}1!#^(e;+_>wLRo!(aVe37UKyGH;5^UQ3%7o!SSI&WKW`JI z&^8Kj$Ms$s=!&qEiy}|qw8&eS5DAbo2m6i|MRB&rd(}g$rc%q8#)8j^AkTCE2cO&t UA4kTif&c&j07*qoM6N<$g6?_+uK)l5 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/btn_check_off.png b/app/src/main/res/drawable-xxhdpi/btn_check_off.png new file mode 100644 index 0000000000000000000000000000000000000000..ccfc04c2e80e42379c253f7116efbde8db148ca8 GIT binary patch literal 439 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGok|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7$ZGh978H@y}6?p%;YH1kSM~-*5szl#(a<=aM9Hl{h#DN zsIOtuKFDzKQO{K`g#ex!>*A9~Dyk1x{kAc_&y)H5#`kv$Z$VZA!I!(9&%WvEaRkj&NZ99t-{2pw4%*q4Z{48&-~@|+0|Scy10#zA z10y-i2Mn?DH%xjZZ$Ep?I^*V}ckzz1yT0d#J2iwJWwi6+*l?y${ZH`gZQM^REWfKi zGk&J`Y|o;z{^oIl?&9jV)ts#Se=ce|>%ac)bQx}5od&% h-v@_7gY#Qf35MUl6j&drYi59iJYD@<);T3K0RRWIqN)G@ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/btn_check_off_dark.png b/app/src/main/res/drawable-xxhdpi/btn_check_off_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..9d03c2fcb46af3f31161544c9c719fd315dc5df4 GIT binary patch literal 421 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGok|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7~MTx978H@y}6@XbjU%X;o*|t_T!JE`}KBJ_%$vMf2sK> zIQBf_qt_R2>pSxu;F*2$kllG7HND9-`Twq%@d7PjVEFLn)*NBtBEjn*OGzDXz@xNCmLyY1oraEm_|)2d!g z=M}Ki{@3=-n`6UTiv!DA816hQefGHiX#1-_FPyu|Yt653v}NQLc3@x>aA05&U|?h+ zhuI)~W^ck}cdwUcXH4hbVEpK3ykouS`S(Fi4f`H3>{n%ZQFMos|5f@&hReTBF6UA% zu?%ZD^GRAt-N;8U>!;k8!n5}y)24r3^JseU)tKp@W&hXd#Pfp#K;R#%1jC%)rpY^O Q`eH#Mp00i_>zopr0H$D_*#H0l literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/btn_check_off_dark_disabled.png b/app/src/main/res/drawable-xxhdpi/btn_check_off_dark_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..c8dad1a4d93aac05521d594f41b9c1793a499546 GIT binary patch literal 421 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGok|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7~MTx978H@y}7e6=&*x`!$n@^WsBHcSVcn`j((7=F# z`@Q4Wy4Ph_SvwxCfBfyviDv7T8*UxWqKrvD{0pmd-n`z}&yfFIt|&C&$97)6`VBUF zzg}kJnB7+IY;MXDVJedl>CAAh!1mM6!}hJ~z2$>yUdJD|x-D&B$jBnVz{ujjz$oCr zKo0Xq`$loIwZ-SkkJ%(LD9im9{&M%jA3jr-9~l-0(s>zw3Hcvtz2z(SV2gd_?Sz(n zN8Cq>a^3Zx_tam%W(51cPKH&2;a#!W>}gj% QECz{qy85}Sb4q9e0QzB?J^%m! literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/btn_check_off_disabled.png b/app/src/main/res/drawable-xxhdpi/btn_check_off_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..aa059803356fa0f7a68b7566685527018b7e4c25 GIT binary patch literal 429 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGok|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+82vq6978H@y}4uPeb_;w;bE4mh|H9rEeY)G?El;T&$qG` zX;%{M+F>yz=*WA8$-$X=-)2?I{aA3h@X214xkeH|Yk;7(d{X7!?irb9cKp0+e&4Wr zMrtRcnxX$xnL8p$Vw-b!Uog1bCYL0qT~##acu&$jZ;pcCizoj}C*HloTWx)I!^GcR z_ZME?#{GoDV4cN*P)^2-hie{xjZfNFRWNtn?x-8G1qn{8aNmQt!!uaf)x?7uWP5H_riXJlhI WYpdXKJ&x@SNXXOG&t;ucLK6U2c$?k; literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/btn_check_on.png b/app/src/main/res/drawable-xxhdpi/btn_check_on.png new file mode 100644 index 0000000000000000000000000000000000000000..c3ca649a2b198c56677578435b1f71d5aed864ff GIT binary patch literal 677 zcmV;W0$TlvP)#b{_$T83(wLaSs3j5C8~303ZMX(q~P8 zm`}FG+=YxlfL-}+TA?VHAtMlASN@iO5eRT-LM>2)E9Cz(o7qHoCO{1Nj=FM_ZBqio zlkfh|(IT}jCg6<}Zf7XJ~FlOH0$>QDLpBP1z5M1X-gwsf(5gk?s5p5!I;r4iK0j~U?H2qTU1 z9X`IK_uctJ)bV48C%-6rsh$C+p9u=z`H})$9wBl0k^-C`L4kZ}0ZxygM83oT$0I0` zFEzm72+HJ34)8mILivts;m7A9xZg{S@>2-VL};cXG;RQu%(@_z@>2?62mk~i01$ux zKmY>lv&z_?h>ct483&k42rw~$_t+7{Bz}NqJkpE`o zd)dUp_xANp{*xW|rdgV<@tQx|e9Z~~0000000000007`5egqf*#47k@Vdt^N00000 LNkvXXu0mjf9f=`5 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/btn_check_on_dark.png b/app/src/main/res/drawable-xxhdpi/btn_check_on_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..94c2a204ce7a7dff7a021586342fdf169648430f GIT binary patch literal 681 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGok|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n7ln*978H@y_s`1@Q{Oq19LLt6bH_@J(U;u7BDSk+REh2 z>cM)ev19>zUW`Vg&~Q4wy)?6U9n z%zhvLhl`gjSC%(^p!hgD<8ktL!GsBg4c?;H859^88yHv^m;??mWHY(!**vvS+K7p9 z+mqd?tdgfE2y$#tt^e$Nf`jG8<^^I}PU1g*ha11<@Mq~z*w3|L#_prrI2j+EEdT%f zh2Vh|e`J_fOqw6fQ2MEuYe7(*9^;i$=M@{WeqLiVx;2y`dC)s=-)SFy?_HjS`{ak6s`K7KuHCa|DT{$f%7SAyCOtGWd)8!%y z6c_{=7#tXw7#KM=oYqRN*NVGib=u;9k8?wz;15~rE(v%30Q;XdrvmrM{PsG`Qy_RJ zL?+EdIPS>|Z+YoYe}7eN&-kYCc-@amle>>B>!a$=g3=xgJkZ$3D!~wZLZ>Y8)&g^o NsHdx+%Q~loCIFxk7&rg` literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/btn_check_on_dark_disabled.png b/app/src/main/res/drawable-xxhdpi/btn_check_on_dark_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..a9eaccdf0b9311e273445c8a7fbf184dca34f264 GIT binary patch literal 681 zcmV;a0#^NrP)qZln+_6oM9m z28zYv{~wEmVj)m06oLc_;rJGh8Bmdf+uQy2+xKR6)ENi8_r}FrmMsbZ0000000000 z006K~>GQ$rx*p1hM;T)o1B1R+GUjDjeyR%~@}C;{Ik1%RE+IBuAE@#qf0B`W$4JJD zj|Zwe$alZYLx$7$NUr@}!(Tv-^1WTVj{xJ01Kg^34gdlW00=+;AOHajupvM^rZV0_ zMj*g}e7DLeV-+$20S@HvzX%zD06*l1j!+^0^ouJ^gbxBlkngDL+AX$C2oO*HYti#d zs}=I&$D*3quP$xg904n9@5muv4l{rWt-+wFpO3ul82 zam|o3~j300000000000002AVk^J^-dr(g!JXP6 P00000NkvXXu0mjf%AXve literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/btn_check_on_disabled.png b/app/src/main/res/drawable-xxhdpi/btn_check_on_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..8bbf651b69fbc61302c6eb7a0d8d1e0718b7717f GIT binary patch literal 668 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGok|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+nCv`V978H@y_sX{&Fm<`a6LoNQDCA-laglBlCS^&v$4(= zQPLC%TGEj)K}7S8>U5>LBd71)dw2VL+JnUMn6w?Xo98{30onxv8P7F7ef(9|R>xN7 zC-N)mSLIjZ$r1?>y{z~C+Wd^%ZhN6{iLlhqou*rgTD1S#9Gfbje4ur{b@S=+jt#4( zEj{){Y0inQ5367J-ex|+VPGKhz{iO}fq}7sfrWudz=1iN=}GdV=ZdK;Om9x{S6iQ+ zBFM2pyWV>f2g?sl`^*-(Pv7%2+d0m&oKUtGFuU!>azg$8z0yC+ugZP;d~&;l!<3qA z#v>=Ui#RN)(PlhyYP*2LmYTPWM^2v?RH)fpQ*!tHMiE8((uTM9P8}-UeLSe=&lJXe z`8lOqFNRL}6TgTe~DWM4f&m|U4 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/btn_radio_off.png b/app/src/main/res/drawable-xxhdpi/btn_radio_off.png new file mode 100644 index 0000000000000000000000000000000000000000..320a35caecc8ef89ab85e7800ce812fbb3f48df5 GIT binary patch literal 1129 zcmV-v1eW`WP)G5YB zWmQ)ExvWY70sE0vWwcdXkZ+YW1l$je>`7I|p8wCHDu1-h3=IM(#7sHNgoMJ=tZfl( z3ZPK)CcucdnGu(5g{HOyP;5VD2^yfzKj$?fc>%`{9Q8}?u=1WYgoHkMfY=3#NKycG z%6p46a#sdZaC&J&kJpwanG}gV4Hc3?37`%+*`xDUyg<508#ig~l-~_ zEN>lr51fHqSLH8D=+^v>zB{VQuNVa!bLFM1!}qA)#VwESt}sZt9J2D51kj@Q+A`(4 zGwpH59V^}B>+?*#i9x_7?sqih+ZL&=B!Oo0Z`T9Hhk$ogc@|OU+e+wgIC2__9(hRf z#=?SoY4mb=&lUDIV{W5b0aJ_nEo;RBZ0b1GDq!>EY8NwMbZMdFy6$4o;C4z70WU2s zfMQ`Tq{b6%0x}`?pPTr&5Y~cBlYo&h`ilq!0!9tn9!x+XR6gd#I1>aE8Uzq|qO-vJlfDoFrV$I6rXZjf z=`0ut8IURgbh~y@^ZnZh1=Q7fVI19}c6lW{7iPgEqRyWPqu<$Eel7)+LRv=gG=}>_ zeIfQQjl<^yuI~lSgp^Lx?)av4bo!#1>kS$!Q@_cfYdmana0qQKITB+3T(f}96n$lO zZ&BJDyrfNnbc6yey5CF$Ta>7Rw3+RN5N9<>usOft%y@V<>}!|gjj`K#*y9<~)=tTc z!;aMX6ncL8sDRIX!Z}~ebOi|%b7|xBZ424XiIMj*)2RRIs}jC<%3pBcy5%uK99q@A;2m` vC3}25000000000000000007eGzW@UO|5@k+Y|`i%00000NkvXXu0mjfu_5WR literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/btn_radio_off_dark.png b/app/src/main/res/drawable-xxhdpi/btn_radio_off_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..3447a932240fa446b356c8a5e1e57607888c7b17 GIT binary patch literal 1088 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 Eg57BIZ~y=R literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/btn_radio_on.png b/app/src/main/res/drawable-xxhdpi/btn_radio_on.png new file mode 100644 index 0000000000000000000000000000000000000000..f54566024ac25e4395433728d2b30ff5e15aa221 GIT binary patch literal 1328 zcmV-01<(44P)tU@#aA27|$1Fc=I5gTY`h7z_rZ>&TMt;cAh~pQ;RG>B};-d}Aj|Ez4e(O5EO6 zOn{rhd-CU%ESLJX4{T(4I{v=BJ1_wsD_)K4i7Yq7EtRtTOvN&?1Qf5_%UrvekWd8N z$0j0+sCeaS9s-Pbj77|1xuz_V@|AC2%hF540V?Gel7vn730N)GEk}K&6}Er#_kkvK z*n-$nl2CM$0B|{5L>PH0f>9x5jclv0=aeRqlnP_Vp75 zJJ)$cQY2)7M*c6PTd1`!u88`Q>$wL*gi`(pZFNZ!fOk6w9TNTsWRX*C5xv-!@P$}Z zjPhTF45NGnXltz7Hesk`5a&k#L_0R+msxS)r#Q$Dz)!s3ozmrfG400{H8BvA#dmMYhFAxV}|RdeC29hgr7Zv{8$&%>5?ay2%Dre z34`x6MG%Ff%Xt)jm#TZmI-zbXW`@shw9Ls^z?Eh6R}LeeEa^GcjkN@b`Rj%<2Pfdt z@Y%hi&!@&(w}rudLz$DZfDKwPVRp!R4n%L#w*ZKKP78pE`N22mA{=tmd;oplG?YD< z1(-VVgsfvC!UNmpQ$yK<5iqdC;F!c`7=eJHZX1(FG?6_R0sWJiFE07H67gVf+blyr z1p($ngrkz8q*TSr$##hh>vVA z0n|wBtbW%B7<5QagL5PR>))%0img1$5`}idZfF7(V@q+!NRa zT$aEcErCSwx50X|6fD5zOR=k+YU4gcUT_fjLjpc_`hpN(+O(KEWYr;#EV`T#nm3d= zDGTU3o_(C7YnSOEC=((oS-4t1$|u06Y>gMj1ZrNehjeJ*`1qxj5(mli9S zI*_17*T_?rTnRWzh##@g*M5kojSuI(Z|_VG=R)Um!N=1h5&%NjZknWdK_gP%DON*g zmI?HR^3ofj@(;p%U^fv0z`fW;Nm?Y}F6k8%8%SAXp-K_)sThYRbI9B#^-cK~fxD<( z1ivc`llmIfzuUVJRX3>jgz>l0UGjwX%AX3CTl|Cw@mPmSXkG;Vv5pzp!%2`+U|&rk zxfdLs0DOG?mI!{XCNc@H;0K!`L`Sx@9n+I#d5gL`{AAhxB=Dnsfuf*O$=c>P!P%dR z34u~iwoy)J@WZX2_Tko#6QfX17cZkc5|uOo@54rZygLy>a?#=E>lq9NgTY`h7z_r3 m!C){L3n- zW_(m&yMmYsfC?}bI8~uNkve%`$`}Da2 z0d5NK=+Ct-SN!#XwJwkJ)r1^Dz}t$akv-PsHgHo*UB2aF8AS$)C-*XAHxm;?z&te( zMMA}stGSOb;w2U_P34-3M9L@M{-8@I6AwtrA88WSttUV$|DoZiuUKLG7k}?Dq5THL zE;R|GRuaH2XCnwBPem{kQXYhDb$L#y6G>4RJB;O$ib%>Qg{&NuPl}%V2pj(kYiFEC zWJN*|Y2^PDx`hM#;wn&IN}hWpM3C|aQmf0700*~omSxXmH=!AJtjay~AILh2 z>l5b>yPDQ(3mk?%!(`tCYz)dd)|qg1o#YnAH8N;wXQA6$)*yOguwu%{huIMz82wY1 zP?NLg2i1I;y5i*S;L!HO;%)}MA~&CNz!y`7>!^HkHCyFpkAUjfa-A-Df{CzkR+BLM zPE!Oia&$Q><#(x?>RNH#QcMlM-LlNtSirSq^j8ieKUmU3bxSRQWBx-;nWGbMW%zCG z=<`G4f!mT`UQ_0*EI^t)#@Zq4I1s(g-U2xKIVpf7=4anrs(2_R^8xpLT~qdG7GUbc zqp*&#iVta~fL@FA)H_E4r2f51sMyN0EJ+C1H4O2d5)vS7yKI>21SR0mFiKh{B*5CXMTSET z1niq6I=r{KOQ8Ina9_6!nIHHdz~o1zn-+@%1W4Pw3ORpyITfMJ^ta*XkHS5HO~hph z(a{o46n`14x5&W)Y`#>w+DRMtG4g_=k{=TAvC|iY0Mn+$%pt3eab(fujL@v6%vo7L z*YPC6D6sNHBbWQ!Jqrt9AHOvGlY57{sGb|o?uhETaXhAI>hsdqGK$Ykcww=Ei315V zx(1%IWU6D}a<-rQ+Kmyl;c)Kj_RjNgE_c3EIG&!402ab_(+Z8rb_002ovPDHLkV1o71VAKEr literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/btn_radio_on_dark_disabled.png b/app/src/main/res/drawable-xxhdpi/btn_radio_on_dark_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..e1bc64b1f832680a1cd8f7192f3474c07c38bf5e GIT binary patch literal 1283 zcmV+e1^oJnP) zDGC4p0000000000000000001nj#B#o-{0RW^K)z3#I!O0+R&QT@$Xw%mJJB-Q~1dI zOix+}?2pY5asUCJE1qWd+_YKbu6ES4SVpNp@#JE@nN}t4nh5xtn}{-_;>p$ACz$aP zi&*D!O=TwKlW(t08!7RCr2Lg3VLx~RjPmOXXMMv8Yd`#b!h}vPAePpK)gTFAm$MXv zk>?_q#we!~_BG};rB5V9#6G8TNo6AClR_RmluwG@ga{i?!rBGr5voWi6V3deLbuSd zFK!}@rQ)?mLIf%QNosW}3Gi?`B<#iryG>+~bGC?4ZcB(wzz^Zp!5<@Jn<4T0E*YO0 z+wZF*Kp5?0jln%~k!aD;O~g1~Bvj758S0xAyL0^48Ox-f0G?H~ws_k?goK2eWm9hf z_m0I)mGV7EIJ&E#_!1$^n>iNT^#zOO9Vb|oB}=CE+H+B-v3<2H%;buK?xdE130PW` zbKsD!_53CgI`5G|Q$JhN->wJION$j#M*ghM1@Y{+Z7FYjluy|+6I%CB{&8=J{PvPF zaj7e6ISW6Bib+zp2H0Fr=aXb{#PL{!IU}HNJo-d2JXTuj95_-&6Kv7H}J2_Rov-msCFA0P=>B~;QLTE{y zhhi?l%Hz?+Vh99`J#QkcA}e2{I3Dx(DhS{$1)ER3_xKil=OwT^qHn!;UQ@J%_`UCy)5 zLpvVh!Vy6Q%UeRW1$4%O zUYqg{MbF12NeN*0;+iF@ND#j0jPjr`%6R}s9FmuZbRbApHdZASJk%cQLv4?! z;^<#dsvwfEl+ZjD@?st9Smw?*qNP<+Na?>~UmQqPuEw`k5X#jkAmIoUY|0Swx%PH! zCCe_kYdYMLWgkdD(Y~AnoVX;EtyGYOqHwE{?SPb}i?^v0KlKEB5;mfEw-Q1IUA`Uw t0000000000000000001h;p3kG0{};KF<9o=wYvZS002ovPDHLkV1mFmOsxO_ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/btn_radio_on_disabled.png b/app/src/main/res/drawable-xxhdpi/btn_radio_on_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..9b63efcfef7318df382fa052e3df6ecc9c744d40 GIT binary patch literal 1394 zcmV-&1&#WNP)VpI+A=sipD@!7YNaw&=8cKKO%sRK-oDT*c)VZ8*&TVc& zFc=I5gTY`h7z_r3!C){L3NOIwx$ z{Ir3IEK~ekF#-Pv%07*v%25Xu=LjLeH*z*aAb z|4God6p5sO*b84K$rk}2kW&NUFVR8T2phAowuk+Qii9msGyen8EG*FkxQa|#XaXrj zfbc&VXk8(^@MLxp!eFmtxpX{WsQVh*+mofH_-zdd`z6b|tOT4Bmkz$7o%e->EVoF) zOU3WtyM0-1%1A&TJFkm6T+jMEJmBmGA@6EPNL)=2R|J65dqWKQ)*>9%DA+_p!Y*p+ zT^R_-?0zk#cv}Ms37IKc$4Y7w+?Wam2Hh=?L8lmUjtdZdrkI>_97l8}C|g#} z!X4!LB6Dq2rmrP@323hR`+WCD&zb5#*s0ipO?B^20O_M0$ohGZWLrov+F%i(#r2jL6disu&L+a}2M3W%4 zVfEtrOYt#heSrf2g0^G*g zRY9YS1Z-~@;`zYb@>qfpkZu^_g}fj^ZM*3zIA!3cpt7TFVzM>%?2PAvc3dU9Tj0!p*@}W} z>>Pdflozb9EedSx^m!pbwQ12K){$M#aE*bWpT_PL9CK6}kO;Gr2m{B$pBT*kq%0b+ z(%94^gSw!eZG+V`VkV9!~JZ~ zHURdE4>g(;-OZwNSOq{qDF0QHSZorO>?!!szQ6=zCW)V|j9~Wh!>y6P=hMa4B9i0t{CGDLKr&I4 zeZHQ-U@#aA27|$1Fc=I5gTY`h7z{@B@kf9G08$<^sxXzfy#N3J07*qoM6N<$g1Fv# A)c^nh literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_add.png b/app/src/main/res/drawable-xxhdpi/ic_action_add.png new file mode 100644 index 0000000000000000000000000000000000000000..37064d19a97dcbd7fd8c6efa8d070a6b6d613085 GIT binary patch literal 229 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5X+C5zyLn>~)xfRIeASmE)G4X$OnwY{LkD_;6&*rWOS9Y4D z;wcr9$A4kd%Z8h0f37lS;t)`9XkcJuQUQ}JKrSN_kX)dCD{AGMFyr%ee~x*b~)x#7siAi%@m_~-xmFYGcdD`swX$%(lIGM9lN z?CRv{Ukk22KK#b#vpb8Bhe9I*!vn+H*JSnA8TCjpajHykU|^VV@sgLqYpug86el<^ gak7B)edtv@z|i)XM`=#-l^GxbPgg&ebxsLQ0B1iwi~s-t literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_close.png b/app/src/main/res/drawable-xxhdpi/ic_action_close.png new file mode 100644 index 0000000000000000000000000000000000000000..bdf84cfc5ebd11aa1fc9d2403d0c9eaec0726b92 GIT binary patch literal 393 zcmV;40e1e0P)ZOpoM%_Ffh~~CJ1Qf|bW=o<>!egj_N-PY zCVySDEa>4!!>p(q9E-y4aAp>Fi!);a?r}y=Bn-}o35CPyIkB)fJti0)r)8qc;4~nu zgYzfSS~vrc;^A~eii^_$DLzgEk{Qk|5x!zvq`~1o(7H(6YH+j4;|?B}@yInGPB{DZftA}nZB(D$7R)osHY-VXhT6;TG1&IxHCrE6` nJV9bpFCl~wLI_zm4*>=M+_=dEuFx3n00000NkvXXu0mjf$MT%R literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_core_overflow.png b/app/src/main/res/drawable-xxhdpi/ic_action_core_overflow.png new file mode 100644 index 0000000000000000000000000000000000000000..eb6a7e8e7f031c30e6ccc751310917f2194c5629 GIT binary patch literal 435 zcmV;k0ZjghP) z86g{#5wZa*bc2=&7$Iz8HgFvYexM?DKYVw&_aqA`=p^TXa(9ls+W`Oo003wt#<)-8 zJHE19+k2ODit9{XxYn|&q^_71@)C;%nC9Cf|LrnRf;hy=v*nr zbgG{VQvB?(Q0&tP8FGSD4WvO*o5tmhoaMV#a*mCbe^dGxo-Y41O>-rW0ssJ*P4o9U zXH4VpM{DtwQvJ>Cuao2qE7f#xyKsMhn~}@lvBd}lf`U}bA+_AGJabBJ`0<=_Bj`{m z#;hPSAP7M=1+gZ*l{7D1Md#wWnpWLv(5wOg0BjasJu5F1JMEhnq~+vpf9X96VguD1 zIpaSzl8+K|WldVCpte$sSwUt%kWxX{N-?HuAQQ)-BqdD+a*kfB>50>Z>ykLs3jhEB d0QUG5U;xPd#6M0}^Q-^>002ovPDHLkV1iyHv6cV; literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_delete.png b/app/src/main/res/drawable-xxhdpi/ic_action_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..27039e6b1ebca9ace0c2c7454e721991aa59b3e6 GIT binary patch literal 319 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5X9(uYshE&{obNeiBlY;=;1y`vJEM5n?au53D9OT>3TfYq#ufMZkIwtPcug%kP!C8LteFragL^@6lK=U>noCtDS$37O zOu6R$OlbC_qJFD#gKE8vhvb%S&nehcP=DO?`b?41T%N6a=39zpN~fE`EO0;T-N49T Y&9TVphoatLpl2C8UHx3vIVCg!044}*~)xwes)L4m{7@os%aDNE9nj-9JBE3W4A0#z_5 zta9%=_UY;8ZU-h#l?e{5%!~{N{!Lo9W1IilltvaI4~0fKJ{AUs^64edZk>xc30A~p fE5iZSo5y9q(Cy+OeBpR#8%V&@)z4*}Q$iB}v!^_e literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_list.png b/app/src/main/res/drawable-xxhdpi/ic_action_list.png new file mode 100644 index 0000000000000000000000000000000000000000..987afa57c74628b387de1a3cf43480ab680146d4 GIT binary patch literal 510 zcmVrJRm-3vqVV_MJTl?&RP%s@x_3;+NC^fDQ@IeSp7V%Tc+pj3gHi2XyWKsSl~UR|8A69M`& z@O!O6ptBOi@yn_l9#4tl=sAmhd;l)8K?_H~;_u07*qoM6N<$f=8d? ARsaA1 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_move.png b/app/src/main/res/drawable-xxhdpi/ic_action_move.png new file mode 100644 index 0000000000000000000000000000000000000000..033bb733b58e0f41c485bef01a86382cb303d09d GIT binary patch literal 704 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 T^;v|K00000NkvXXu0mjff7?TD literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_start.png b/app/src/main/res/drawable-xxhdpi/ic_action_start.png new file mode 100644 index 0000000000000000000000000000000000000000..0ba37e36f5c86301a983ad9a2ef3e3563841001f GIT binary patch literal 525 zcmV+o0`mQdP)^3GVK}h&6zLToKRDuT*WU>;3Oz)kvX;NPaLgx2>w4!DZWV8|lMZb&6 zN)VL&!#F`ZO_d-hJ^E@YfmT6Xnm%(^0?mR{R00cvvQ`31g0fTsYl4zj0;__ORRYU` zl2if@f2vD#IK4{Av7DfNo0?ksMb;7&Wp#TM6m4~TJ7UpS zx2FV&tZokp5?$Sv6O?3iTTM{1)om?7$yc`}L0ML}g(H@Ab!$&h_SLO5L26dFwgjnL z-I@}la&;%+A0M;Kiy?#0q3#z{dHDoNLeL6Et_#9Py#N3J003Z^{0T4s^=AA&1nk$- P00000NkvXXu0mjfIVs!$ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_stop.png b/app/src/main/res/drawable-xxhdpi/ic_action_stop.png new file mode 100644 index 0000000000000000000000000000000000000000..65b614c8c02ff5c25e40a6cd4b3942f279d0f678 GIT binary patch literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5XN~)xpkjYiA$x1Rb1T&CQ}0I!VRz zQum}cXI>pFUb1?U?X1x30mB)8wubkx1l`Y>V2B;?S{brQEa_1q?Dj;Yu>F}8OHf7tid6_@W zw}xBJyt(em`L({=%q>}u2ETFj%i5OLu;8}gOF5R<42$WucWjd*zvxv(-w0GxX^U`_ zcS`CIox<6v7uWuz-=B(z<()oRLyuHb86A& z3rwu%uS^POedM|Cu1xsk^Vg56ecr12Qqi+)k^1uQzSZ2py%sZ93vQhHxN*Z=CT%&F z+n+-&8MX$@nO|}~c>P@;*PMnWza3v5KTv*T~m?G;-hUW-qK*E@pYH rKkU7{-ERkIfC7UR5_J#y>KWM>vM0>krFUxWE0CC{tDnm{r-UW|?<)pu literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_checkmark.png b/app/src/main/res/drawable-xxhdpi/ic_checkmark.png new file mode 100644 index 0000000000000000000000000000000000000000..bb577d0b129046972d248e3b93825bbc1857f96d GIT binary patch literal 2876 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_dir_dark_dialog.png b/app/src/main/res/drawable-xxhdpi/ic_dir_dark_dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..daa02c3471ae2f886cb44ca58aa7fdd5c71935f2 GIT binary patch literal 853 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>V3zT8aSW-5dpl=iUvHpDYr2ce z7Ott6m{+(goc@7n-T~x$p4 z*<6&r&$CSV_5I&}?w5a`G0|eSbd4)#j@xqM8fM0a{ESQj0%`(s=h+k#d=zZv@j5un zaHz16ZfG!Q{7_-Wz?jJN@M8`G(;=2ak8d)tv~ac@-^|FtAb<)?p$>}so+ zt@l6J-CzCu?ke@RdmpDQX1Z0ZzS=KbcFqSe-$QR#r3=vrv5Vj>Qm>dpI4cmE{Q2+JiX_4oXzjW zv$n|hz7MaJ$?Rwl%-TJ_GQPf~xLSLcmC^$4>-C4YMlCKdlUsQ$=$Ar+kX= z%4-+6;GA9fE!8zor|++)!h-f|k8*xr{PF3L1XGp7*OdCOA4%t#a~$|}b*mk_|N1E| zXrDICwc*&wX-*BiYp+Q#N~uS4F`8|ON@mHJlcULU<3d)i;DW+UO9c{~SDU&G7KPrc z?kX0pT>kmQf5F?U7+=25s=8O0_AQoo_buZB7l&Vmmd*P|bN bp&@=oVrrtG|Fr+W?84yb>gTe~DWM4fUr2T6 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_drawer_download.png b/app/src/main/res/drawable-xxhdpi/ic_drawer_download.png new file mode 100644 index 0000000000000000000000000000000000000000..44790c9299ed36270ff64b7915fc29aeb3df376e GIT binary patch literal 426 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7=1il978H@y}7-Yx5+`GA<@K)FYz=}c;xQpt=1Op))7GZ zs;YfsqA7xIk$j2gH}$`eJFV*UOl;;1Ah#&*sQ$IS5}`%5!{y#rme=@ZHT#%DTrQ zc|`;=Mg2qr6qa1Ixaz<-VO4=kgOuBUR z!p;Xfm~+9vmsng~TwJ!jUhs*-Zc3v3pIP>) zyZ;iwFL_n=4`HKd&X_tz`(?z(9qG*abn+k24;qdXWBGgzAu#q20nwQ LtDnm{r-UW|8QP;D literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_drawer_favorite.png b/app/src/main/res/drawable-xxhdpi/ic_drawer_favorite.png new file mode 100644 index 0000000000000000000000000000000000000000..9b4039219402787766f87e958044de6042432cd7 GIT binary patch literal 927 zcmV;Q17Q4#P)%TWAM8Ur?RL92 z+wJzZHFYnb4TW`~o`;}kDQ-aoXY6pnKnuF0=eIfi3|D4gNfR*(0ysFO&K$3(ZD>#F zBXk_=>VYM7z>>a~1_W@1!agSpQrnZRK0w+4q@Qd+lHD2+)QDXc-ZpB?r(dB0#IchgFP`VEi_MBL{|Gzj9ZxBm(P69b;-l z5wi?nMUPFmK{L$2ek!{Ux(?WSGRKw~v?nXVm~=n1sRha;Kg#W*f*}ZEPcR2><*P#fK z^x18HwsM}D@^W8{&TdkW_HewU#=TvrvmiY({CE;VdG8C3S3nk|Iqj2<8E{x3i^c#V zNOufKdpO<$buCk&Aw{Bo$AOlsj zfRPLoEK)U)!e+3g85n5j;5yjA4z@I8Af+J#Ep5RjHKdhNFtCt(iKnh0ozxE@Y zZs&it&-S^EzhqsL-kqIy|9}7g?Te!zti%C902BZPKmkwy6aWQ40Z@=ES@C5c>+96j&RXgAQN*e)+GZ*7mOQZkel9GcJ&&QpzBAfPd{5x|;02tWV@LA0T5YC;jo z1ll=(8r@4k7Qv(iHE9T-Cieyq`Ij#F7gvI&cLbp3MQ-xQ0h|sT-don1ELny2rdF#> zX0zFE?=*-^YKNS#iXWMfnPffrI<eJ0zQD;xWQHHgNLF_w;LIZj zLTSe#1F1op<^~XTR`&r;3eu1{r9%^nlWcyQv3KQYmViG`eDzCbipFcm^xFbc!|#u+ z0-_%y3fj&zv+NBD29qIyZW4Mz!D`kRH}e3QFtN6%6ajFK%dRt#_6|f2JI@!z1+|AO zZj}xv0cnxKWX47|HK@sG&nWUs&PBh0bJn&1jXi_2h>wKRzAZqa8~dg`3%POzh^EiX zsohq6*ns>Ld|$VrL87VsCh}KAV|!r(5EnUnDU4=YbJAII7d;?Z#+6m20^JJZgnN-( z$uuonaP1cJe_Z4nsQNpMRa_^$c^Y(R!Jp{BSxg9@sUTNW8b$!o(=rK37f#ZVVu_+h z>ulxwAcK%p;S88)9L@d{KlG4fhyn0V)WokXhmC1GH1GQYG_PT9MSV`PZ!DecGiFyMv+LyC>b2_BA1e-27LqYIiN3Lf(uY3} znEr1W|I=b3=83is(l9sUa5Bv98;k)X_Lm8>!yRWwjCs8i@PL`kv&Mr5t%dWb_j^K33k?*SJ zPn!Mp7&!QV`m*<{ID(TN;0aCBCi{}z6M3MrtF7V~PC9z^ehZ#WK(ZCd<=VuVQhA(3 zS_+pf$2Q;;sBKdJG6Vz2evySt>pShSn3YrbvL&O)z=qNAGK(*O9DustuR}4RKzN}} z5|)>*WCmH*Y(4-AfC8WZC;$q8f&@STP%-%{zyMIr0yaAIP1OJZ002ovPDHLkV1fgp B9OM81 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_drawer_home.png b/app/src/main/res/drawable-xxhdpi/ic_drawer_home.png new file mode 100644 index 0000000000000000000000000000000000000000..2ffa1c23d761cd75e7bece958fcfa28211e6b1ab GIT binary patch literal 581 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7@vE(IEGZ*dNX^iAG4!~TltQVfM$lRCvQpdoJ_iqp|h(= zz2UjWyzmFE202YmSDZGjTE@iEW_?n{gm@=O1 zQ&jgZv$3BXaOR5d*?qRpXI>8u4V}Murh@wOzUz_AyT!lN+4$<)_}*_8-=etuL&c3k zEl;&ga}J%Hxvhq)dZz7%r{C;$a0*RGp7~+d1JA0*o>Ba66SC*rcCfrX>6&3wr_#xp zyT#Rxs)Pkh+BD}a!xT4L4bQ-H>wpYXjozji%NR9!y#gjR&4DwNyxS`Rq!cZMdX+mP z0xmH;?O;=7JvEWXLD$pK(X+8=33GsRP(VPCx8*Iy8#5Oy5i3e!O;B&TRMFun+Thtd z$>zxtt^-RPJ>`nJax6^vye=O;)+sf`B+0V3>C&4C#gELM{C^_z^~I*m!k@OLPkM7~ zuh4IuV?yaQJKlFLVBP;CEPj=23PY0uN7JRk86V1j=grz2$oZ&zYU}ge&y@8H-#7}e z01;3cju=1!nZlo1Xa2PgK2u*2%{EUP5*Z3o_Dnns!q+z&S!Fusg2X&s{an^LB{Ts5 Dc}Ve# literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_drawer_panda.png b/app/src/main/res/drawable-xxhdpi/ic_drawer_panda.png new file mode 100644 index 0000000000000000000000000000000000000000..62a10146ee4c86401812fe1ca18b82e57debb04d GIT binary patch literal 1325 zcmV+|1=9M7P)J5l#fvMROq_uc#V z@9v*}2Z0x>3_Jo4$OH0#JRlFq1A41+;quV+^>u81B<31ceqEYt_x$|4Kczw1x`o1* zr{>4RT(RZ0!dz>kOx80P(CyY-k>xf3w2|l*GG7j}F~^zV3MjMe+N028X2hf&%g$Vn zMz^vxAfo`x$R}cEngkSBbOj0F+#f9`u7Ib1GrASdLAv5Ok)sXp{5_7N4QJQ_PJlMe zDcz_nb|ZA%CP?!R9BAGG&Ou_dQ4R=~)b0xWu5mgH&IarT$cYR^<|d=Vb>8qs~?_psWKx0Ag#d-zIJE z9E!vDnBuj8Wloz_qZE|$qn0l(QGpG$F%-Z$RQahw%Fm5nkFIHc8T{LPl7RV%U`oyD zM$jt*>IZ`Q&_c&$+jfUbO99sOQX34hv!4mQoz;rrHq5w?;H9g+yNMM&VaZLIdb0Xd z+&U-{y!0e+aOuIj7pM!>f?@Tz1_Jk0nUNkK?U|5foJJnUgKh}#IU6`aNW*|=mXUy% zO|#`W!8wAoq&CmCW*H~VQ*fY$$QgliRPjQ1X5$!yCzVW9;#iplQf{8fiR=bru3Q17 z#NSs$=BxW01MSz?DE}mcb62C!2%*`$ctrq8ZFbmt(jy}FWKETEr7URL5~S!^lJwnl z|LeQ(olQlhEwc{MR&flX8dDof*5lMnlA?~fm-@U!E$PGskZnU)T3nh_s0G5xoIBps z60l1bTU|Y9XUTeLa5_mXS!t*3K{k15`29FIy^MuAt86v6B0b#D#l5OceQiLJZ}?e9 z_eSRo=#qJ#=r9xByF0~t9VmMWXPuX}!i*?NIsK(V)3XNBOhv5ooMWiV-VLAOfX%hC zG6l$HMAVQ@Gs|-@`%i89MfmKK#gq1R@v3qTKy0h42i0z5vBkPLQu5HWf6!>A;TR7% zW^pt*i-tBYwdroOmHMjj-BQJZBA@%}JG%;QKfc=RFgAR24bt1T4W}~@s^Pb7lf~AI zhEsgT-V-yH*V-8nqg4Ph@8XcA($b>NMrN%}xiagIDCp=UYfCuyMCM}EVzcAorR4<*C=4%w-y!=otZ+eCU!FbB)3>e)W zxZ{Q$x$mJmT}h9*IDknI3|FUxq;?A(*SO>#TL926?N8F!$R7*!c$5~lVG9NC0$F>t z#G)+6c1P(jF#rP^_*a3frwvDNTsjEufKX{rb{4pu&DrB#>ILELYl1$E`wr8!OYko- zTjj1ch6=9kayF+AQ8}X&D#iqG|J$JFgWe6XX04SRLT&9%2uc-9XMif8BFr*rpxj75 ziep>MlAWCE4*Qr|gf&91idz6H5DIun-W8PI0a=#fkdd8UKO1*laC&hR|IaKQkO$-e jc|abJ2lQs;uK)u8Y!A|#KeA{Y00000NkvXXu0mjf^htOz literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_drawer_search.png b/app/src/main/res/drawable-xxhdpi/ic_drawer_search.png new file mode 100644 index 0000000000000000000000000000000000000000..b667092e17fa28eafd89ae25de2b1cbeb7e03874 GIT binary patch literal 1178 zcmV;L1ZDe)P)f;CIcPlz~0+l zq^L^not=Akj&o0XN+h4{^XGf--rc?Pf(oLQg@7Oc3V;Hj0I0n3NaCXF>+4PUi9uVS z%b?MOqIaMv=m0c1KR>@H8=#tHFq`o6MgxQs1a=A916YH~03t9Q(5A(@C7_4CLDi-N zgDqueiWXxcJylUo!y+;OxTBTZi?;m#{&Q zvQh&gIGY|?`y%KTK*|78T1p>O0M3AVrEBcYNBs!-FLdf_pl#4bj_ZDO<-BV-ea<_> z;(gE~c!(^=_4D+EcLXUbg^AJI-ZN}5pP}L>UC7#H^8Hc( zf=a(P=&kwy=F_x24V-@i_q!rUx_dSeCYJXKh?@Q@12|u7RSF@)!=GXI&Olm$a;H%K zUJ6j%piIXloS~3IKOlcINg6b?QC(M#Qfdrffkh)B<*HF)fuNK6;ov+eP5+Efs z+|ae6wsSx#H5d#QYeC!&~vgX*O z>wy@Vv+kutt^w(8c&+@%fN|Dd;-GPodIFSA;WOO<@-@5XL+UC>0OTAHO@H{1Cz@lK zYd|_b)AR%=Difg8N94*#TrdO>0i?TO+cg=QSx{vHl<0yog9_W0EuDEu4SnjGSm%QA zLmh{$CuYn`Ea`#k#dKq?CarY15NWp*&X-*A9CI!qbN5WGw_U5v578x0Y652Td6s!S^+aKah|RPU%-I`q}&Nq zn1um;0t3hmrO&{yE$DBaPPixfMoiN?v%OAVCTbyNBv(*TeJiQyYcU%agG5X>yT_` z+7A;6)>QsUwT%4C#L$4Qzpu;|39UHpnxx~Puppw?DINo47y=kCWxIwX3P=#VGe|SJ z9SVJmVT#_=K51t6i@Aa{(;MnsK$`g-htiNLu_aRyQf2n6NHqe5ChBh=fw4`7GC4(I($Y|)tp&1!S!6q$akuuPMPX-z(1^cAn2aN1)x=2x! z%`_~%M-dT%@)PfXgom%^c( zrZhJ*0F_djn;C$PQ<`f(KoEaQHt4X~gQ}M3uoj@QB|5AH=+F`!?uw;G6e&o4AXg7y zdMl#hPif8BBN)?M)pZK={~8p-E*>PHB(I9@I2sXhi&Jc|?ZRZYmK) zLbjvXxPX}OWDB)u7@!wjupy8oigV5E*WYB+LI}7C9oCh`SU8tX^N<3cAr*!i(mT@p zJJ|e_=mfAO1Y5qrpC>S0G24R&^`OLqz_2M8hL!VM7)2(}xs$D^$(Fxh%MbYToXM7m zYmgPOm59IU7LwO+{%^Fa8+UE>WrGTNE^v0x}rX<+u}IM<+qx#-z~KQHyz` z*jG%!L&mrBY3^>vN5lGk=yO%YN^OG^@l0BsRxXvdFK#DTWh)y1Ak_uTxo7=PpVJQd#gUa8-IMUTtkT@icHcUWePn7tIEqyHHcr@@D+O@f>pUJNK0PK^}3@-AcfoF(teERTx&=S*b#UK zhCO1RN8(!DczT|`)Uc(q$&w!{`PS{qEEk&klc#etBO2fFkswFh}5ov?tbDte+!*_?z~`Re~IV6Ug+(_cS1nAo?(F4PDES*75}p z&}XmaSIA@A%kDGaq3ma$U3y54UsFG6e@LFNmxC`fl0Jr*z$Sa9Y&QLWHKvy6uoj>{ zmguk+pwJQ>?uum+AeDzxI&|3VL0&>OZk)-d0Z^3E+{^$pN@=bWCr+F=ak69bPk;dc Xj%I!$>C6)200000NkvXXu0mjf#zy)4 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_drawer_whatshot.png b/app/src/main/res/drawable-xxhdpi/ic_drawer_whatshot.png new file mode 100644 index 0000000000000000000000000000000000000000..7ca74365148daf3c7493b90d2c749cd51f3c1c04 GIT binary patch literal 1099 zcmV-R1ho5!P);T z7(oUCx^&O~O(@-35Cr64MH2@q!lu@&G(qGAgF@uXg|at3s4PL`0YmzF-E)*0yzG?KaWmy%uAn1ila=>`Mz~`*W z1f4h{XH4WEvMM(*+x~;$z^An?J1u*#s!=NuWGX?v1?V$Bpu7ii?hyjtGl}yk5p*c3 z3slUQ{AQOKXC>8al^|0Fol==fsAE-fzvDV82vssALQ=(ZWF|unR|MVp3Xn2pIM>Q# zMO6vXNyy_9zW{P_%MazqCP9&ga?VRkC7Xb~ZeAw-Si_|wauC-x2~sj-Am>Wd3^oax z8anSu6$zSKY?fT(thr5sRC*wG@1Y@2V6e_=1W^~ASHL35PV3~VVTFzd$$Cn}tRC#P z+lK1OLXd2a#Jyff983%NxC5lum-_@YYL?i}_X zDfrlDxQ145+hFnpe6q4DOtIqMq0F|8&*+7fu29SOXiYhD6Lk09ghp4MlHspt=nx3K zBv{6kHDDpAK+*@nzq(Z=Vt>b#HDG(8j$VBG1)Hyg{<0Mva?UxEaSiSo(!!@xVSnBX zo7GpHn8yl1h2}OA_U7-=!!ArD;~FMwO|K;M4Ehph^4~r|1vL!>|5_p(Ul~u~Hf$LL zjl()bl+C>#m9 Rs*?Z!002ovPDHLkV1f#k@eBX} literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_favorite.png b/app/src/main/res/drawable-xxhdpi/ic_favorite.png new file mode 100644 index 0000000000000000000000000000000000000000..35af11889a11de902262a04f927ee5d62ac6aa8c GIT binary patch literal 4173 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 BqDBA! literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_more_of_uploader.png b/app/src/main/res/drawable-xxhdpi/ic_more_of_uploader.png new file mode 100644 index 0000000000000000000000000000000000000000..6f6c1e066e6c83d42e831e33024987b5867cc2de GIT binary patch literal 5053 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 F{|9^h8X^Dy literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_rate.png b/app/src/main/res/drawable-xxhdpi/ic_rate.png new file mode 100644 index 0000000000000000000000000000000000000000..12eb574a89e5bfb706f37fab48dfe12a3c82abb1 GIT binary patch literal 9157 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 P00000NkvXXu0mjfo-0Gf literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_reading_action_refresh.png b/app/src/main/res/drawable-xxhdpi/ic_reading_action_refresh.png new file mode 100644 index 0000000000000000000000000000000000000000..f7147892ace5e0b5ab2c8582cb2f6278a93398c0 GIT binary patch literal 1203 zcmV;k1WfyhP)Q|{b)z3!=xfogqf?^Wv^`P#E;E7caNEmeCe z%W@f@uU+-=L*45#YXQ_ihU(*ji0=vg>_!OH9u+}zU*u~eQNRBoI|0bjuT&eU*7bZl zEru51LBcb($=?u~lK`soXGHukQWvJG-PsE9mi%l4Ad4SIBz~6=w~D;lh5Rf8P#v$x z>bpf+sL2~1Y{^er0M%*T*>Af#of!0FeX}nt9U|XGfKDLtM~6C{7eEE zg`7!(A?FY_M3dooBj0-nQk|YqYAF$)U$z!kfA(DWx==eKOBS1;x#TA^2MCb=ny*jQ zpq?Gl+@ZkwqUL@-2>~3**OTCP=@l!5lS*)Pou&X3^D9f@bsD$`kPvtiBB}s1xQVa) zTX^s0bv@Dy0cdqQGmN!5wqJ+XtvoPC+)HL?G1md61JRS~Wi0ZEEFJF7$q=HTBV;K7}a7`cAo|K2%NG=Yte?Buy^ERcq)NW42XH6er;v-RoXB?-fNlLCUswP-I?`oY{{rO8 zd%#6b)}Jc*E&^~Kb}Un{{*=kL5`eNF&Q|Um#UDnx(L9%srG`a46WbfP0DF- z0qC?d+Y9JCQ7+{74Lo;hQszb^4c+Q!)-P!68I$R$igTBv_fET^hF;L8H|SHx#DIL~ zvS+1$#hAVyoa{?_h9wqivi{oTp3ct$$O25VfKiH`U{T-V)|Gq_57-*OtT236SZWXg zF<+B!Qol~W_wZ5T{%tQ+U{gj-B=m@dpf{n&G`8!3&tj|-=$=@3fGq1LSqx;=ORI z!<>);#AAMN0Z0Vx1>+*0+MzX(@BJMIlrG+#1l)vLo1l32|5kq&x~>Jv*Z*cLEqEilqr(6qKe zT7A_67uMSj1rsLjSbKx_=lj3*%8`yv3KI`Hd&u|;1MPu>48!xEqvic;-&jaLzw9UQ z!O+g;%f|PQmu~%ba^;=41Nm|79p!$RJq_~HW`5Yi@zRKI&b_y+K2JQI87hT@9Ok*O zOi&kOeB$Zh@Y7}`hsJDmQ^u#Cmgp>SnJ2@t`n7baVfU2O zjo02<*!B3|oxGfvMP}k=>CMkhm3;dXJ#W?_|8$e;u=Bye|E~(qwO##mrSCJ2GxIio zO3`=7_*V0LW%rH$PrLQhKI{^8Z0b_&us;;j_A9>aQ@_M^rB8Rd9;C(WVaRh|e+jY3&Z*i2fZ=J0M|4J+fN(?Leu8zN>*4Q#l2>4*+fj`;Ik+#BNOz0*=S%Pr-{ z6tr*A69t8@-c_fVy!I?xrMBQs_bn}^Q+c5Rx(oiK$qF*vSf}Q}AZ^CVnQ(d&17ix1 uGm}%uA&rS;!c2}Ed!*qJ0A#HBA(X)Izsvn&x#`s>APG-bKbLh*2~7YlmgN@! literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_reading_action_settings.png b/app/src/main/res/drawable-xxhdpi/ic_reading_action_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..4ce6e358cd81026bc8e37637cd72dea40279705d GIT binary patch literal 1196 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_setting_advanced_dark.png b/app/src/main/res/drawable-xxhdpi/ic_setting_advanced_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..1c938ddd9b5fe411c5c8f93b42ab5b4ce2cb72d5 GIT binary patch literal 1039 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGok|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m^XU5IEGZ*dNXHz{%r@5<9nB=F4LT`sD)=sLc-)GA%mb@ zmoBvVF8wR*)xFx2Sx)!v3pQa9O=i_)olhFucq~#9-d_oVBJ>#oDb7hx^qP)NR&yp7o(kVGYNj{O3}M?;>KO1DNAB>nd3Z?)liyYWPn_ zP;}+Dg~bjoTi^PYmu)e$yZ!0K8DR(2b&CZ*`e?@eUv{bS;Y+K+e_I~h@ZNTErj|zu z%O$nhOI9h&olxwu^pjFx>zmblbL+Pr|LNSBwc^`Gg}$0R8|%>DPkLNLOWxgFy^lNB z;eHasb3KL^kvt2YGcf=;`?wht9vXbHzQgAj&?##u=Ue0cvgiMjFlC+MzP+1nD%zAy zN!`vhf4jEvJk7se`n7_L2Tr>klN5A0VSD<^uQ}(ZG5?qnxFgT#7sso8a=z1pw|N!( zQ&F!8X1g!vs9QMuvSaY~oIR7)r$)RLIh*$Nb;SF~sW-kYn)mxgiqr8YoG&uVV%&K3 z{#tGOt!A4c%pl&8c&4^--i8gp)W^WU#K6!1q!fUd1BkJ6FIBbuK2w{>l(4k&&aQ)f zvD4ple`5IXsd%oge7%W8L*LuPdqID!ZZ#}eFoi|!m}hq`Ki9ko4r>I}n5Qz9-#fid z<>YsUE4QD0FU@t0F=Q#_|6b!CeYEC$`J3kE(x9fsjhUJm3@sN{XSBz58klUkaeeD; zHO2=AH|M0nfD zz{k)-}g0X^8=fKok&!;SIny9G9|HuFbQ4`X%Ae8HKff|K4L~`P#$&_4t}^i*L?gT+>@WUxaDq zd?|Z93*H|Gett0EnLkA{(AoRK{Ixuvzm#@*87@7sBi7ZSRO`z7aIt6mSG*QmBgY}h zJ?qR#6P7H&`Jq-@)}IUg<@!Ct_H%bg|8UswPd~v>7!Y}x&n`r~P(|w`jeJzeR zLS8_y z99lK&*HxZ7#{@Xz%V+!#nl1V=;mza5@Ap@1>0YM)t)y7+>C;RThLE@WH@v6{TNNf& zQd`8*P_+Gw!~XeYC9dA3wJ&dTuQO~YWep0N=d?`h!{tTqzh7nD+t0d+wMNp0VOFUP zn^EaPIB9L_Y}VA|#^a3xv6O?&p`s4cTr zH>BMydb>5lIcnR=6*b2X?w>0ut(AO~uVGv78R;|uR@Z6I{>=%0$Y5t0vhH}BgyZzr zvv<~?e67=QW`F+7eJ^?J`4>#D$m6_FGIMVECtsP}b1TCW9v5HEy}xKrIcs|Hr$g1; z9lFfZzw~79_r0e)o#Pg(gOJ0yI0d0JF<^>fZ~$TfAZ7t#MqJ#6mAr3?zaL^faGS#+ zx9Zy>dxkB4?>swk{Czsl0k!w8x5H}WlO3)FdK*nYQ`r4!Ug?$zJ-GtQ4(qOK+JBtu z%6;Y&p}Pw2TI;hk=^f^snz_8|RL#?>1MJ5GS<}BZ9TQl-ur=ZT-sio{2Vy_`pLtPe z%d$N`a|>gZE_I&r>QesVCDYut&dpoO|8R3zopr0LdG%ApigX literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_setting_display_dark.png b/app/src/main/res/drawable-xxhdpi/ic_setting_display_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..78f0ffe0a9bd92ba0e40bf734d51b4f1d3c6e422 GIT binary patch literal 568 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGok|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7;kyHIEGZ*dUJPS{%r@5hC+R5E=`GUksyH>%@r0Uix;NWzKe7zNPHXocro?Wgg9WUt4~@{K$F@ptTHd zr04&7SFkh2?{SgLTa8;u3WYaZc6XOWPkVYegVp6jc;Qd6T&oM5H(CB$sjP8VT`>2n z`r9?<9X3np{f^+BJddqpmc|d)-)olFmVDXSe)GR_P zsTGqqWgVYj#+u4kr=L8d#NzjI&*1Z`zdKq_E748}>}hv(WBe@0IU`-9;s1lvdRD#6 z=gup#DHr{9yFhp}zu!)OCv(*1f9jm0Ht`+-=foHo7!JgJRyn{B XvNSB%%C}YtD8S(9>gTe~DWM4foq*tJ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_setting_display_light.png b/app/src/main/res/drawable-xxhdpi/ic_setting_display_light.png new file mode 100644 index 0000000000000000000000000000000000000000..1c6cd285b3d3d08e9812fceef2e3513c90599beb GIT binary patch literal 549 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGok|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7>|3pIEGZ*dUMw?@UVkO!$aTpY}SSb2@NJK24+_V4hM#2 z29^efECxnV2i_}gtW&0E`q?_DZ}2qx`)}slIg`#-|9_YEebRIxpnVPAi(l{vt@B%C=+(MSOiDjj@33*1-CyEhcCwzI^R*{uhWp=Q>sQ_jX7cLS zo@v|1QgdXfUQXq`)`s7A=ANnY_!r!GGJnPGa{X`zmJ2m0;jH;VVYxg<&oxY%eBYaA zbI;=XxC`#z)u-j^96j6MI*YGci1muFP{4EzhV%kcm*pGx?~_e7N?QB>)oy>|gai(S z1_l8K1`Y)V76Aq(4hBY`rwgW^6_$@`8=QigQu&X%Q~loCIH#k B*8%_l literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_setting_download_dark.png b/app/src/main/res/drawable-xxhdpi/ic_setting_download_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..0d4f359444282403eac63a57cf4c725da33dc513 GIT binary patch literal 534 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGok|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7$T}GuGgJ0&XOBLR-ghnP{o;F!{q5=opMKnzUh^*H^vTnk zYR-KAbLU;$*2gtRKHk3iUyjYdpy}ifH3nv-8NK;z1_Dhle=K8QR+!PB&$l7DMelJr z;|)cp?H_kDzTsH-`bR8dS;K|eKkpbi1!IntGkSD9Fxw~Xpu}?hV=SYP!jAs;dBLzbAdsa-sed$VA=f9myylUK=!^&!gG#ik9RY?5jmJzW8N@VaYyfat_{L0*FWB6>{8y* zFV7SpqL}i>pdpDP@o_toh$Dxry^Mp5V$B~BhQO97A1^Z;5x8{BpH;xoL)KnaK~k}{ zhOn9A5Am~wnNn}O2=xp=t*<)%MB zR2k%4Gz{w`8|sy&NbVOoATQ|E8_)MZyrt;zbe0{>0Y!f<>2jR@%=!BD0i{Zg9)A}Q zSbFQ%l0VwQ=ajd7o5uR#;Gg>p`u+Q++rReU14fzx1Sx^QqK17e5)9{T??ks~KXC&} OGI+ZBxvX^z11Tuas=by`t_0BjD|<~rE)97i$A&iT zkFLEoh>wizHM9+%RFrrid&^#H#79Q@tYYVrZ{7 z5_uv)W82u+wxit8*aO?>Y02b+94|9&Q-UcP6-%B4VPlsSWPfC@XRTK2>iYWnLrLU= z#lOO~xMIm(-&lG6wDNpXR{(a?Jq1h2VujmiMa%Cik)JjyP{Qw4p1&y%G#D@M7eKzj zckZHsef#{P?JamvVwH~nJft2SydI5gpxd(Z6R1|=* zannPCljNsptQO>YvS=cA&OKnwqia}uAm1Q=>kEoME2F{aDqvdUX{H^I=lE}k!EaMj zAb|HUeUM6$d@3N9yWB*~67h@Y@O+E}YrDM|735O^#wz|h*B;;-MG@5WG?uZ@FabdU zK>-1kR=_0CB*sKT0n@;v7`fb0q99oH*(ynZ8?>Ti8So|((|>N7Az2blmPb3DGT&L; zx#a;U*ej0-*%(P5yDar(+TXge^ zjIDz6f>X*(5yOglhQ9dD@sY4dKPtmNSeZ6VF;mlENJo?#IhpVY`qZ|P#h;c%e5z-_ z_eZFRu2PSfB3C4kmn30{t<`G1v;SN%_v@chfGc=yg}) zbkqEQu1U3weMZ?iy!(uk3^M1$exw*7X%Myp%eRaV??V(p5C)=#ACUZPmwf{NZ+{;{ zlB6)*@IAgWjk`G^`wjTcgi@Od_Zj-k@U4C4M9T$HegBA}btGkvkoxyH{&Ysq?d9AC z_v~St@>~(fHqzv*K@@^Y!jPCp^cXyL9XbH#*&eZ;^NjeuCdGI(E92t4%?a3`T_%Yx z<%-;Ack@WYafupWtZ68KI_EQ{Seams#bq^1@~xhdtR(Iz3TSF503#8fWsXvG2t~Ej z=uE{0QAx{-RyJ!C`fn;JK&3jGlQC*UQdPi)qpbQ<5qSeJ!Tm@vF5_83o8#Ijz_=}A zj!SyBT_*+P4K~t92D2#8HV2Y95E5EBgG#8FmN1yG}qx%*<#q6Ra%Hjwhh+?TEt zRt}Hv9^kj)8E)sNdDhR|H=WBJbwzBC==Y$a`H(nZCqmqobz}`5*zq_q0^(XmpV$MY z_I(2_1wd-vBg(a3hXO2z?3h|5eneUBY%sbKbpY#{hYPA~y-6fnUcYwoLtDOkduaOwMCK1ZM5XOe$nt$$p&0#FH;me6HJ1Ct{iKbCbw zC4>AmB%gCMC(IrFD*2wvaW&jcd&$6DsZ(<^E=O{DF1K*ky$7Un1Iea67|`Ir)_HGz%6X6 zG&&pMpj+j=Ne+B~bHrZN}!mdY`*f79VPbMnFJ7KtMo1 lKtMo1KtMo1fB^prFaR^o&KiB}!yEtr002ovPDHLkV1jYxije>S literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_setting_panda_light.png b/app/src/main/res/drawable-xxhdpi/ic_setting_panda_light.png new file mode 100644 index 0000000000000000000000000000000000000000..908235c07fb6f58f4b4cabeb3671f0c8d0b0b566 GIT binary patch literal 1724 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 OggjmST-G@yGywo_jBv;R literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_setting_read_light.png b/app/src/main/res/drawable-xxhdpi/ic_setting_read_light.png new file mode 100644 index 0000000000000000000000000000000000000000..10a65a4415ca66e0dce3287d11337cc3bb874fae GIT binary patch literal 827 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGok|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+nC^PIIEGZ*dNcQQ;cWu}2i4y7dIxw_SS`Mu7mFv!K-Li8Fe@lVC`r4`6X8J8(f0M=I!~2xap|?y8>&3Sd-4ClH6v57xf?C0bQZH}HQ_u%-S`8?JabC!zV$^6p5deHyTv3{9m)-#={ zM)OTs46gb-U+g8=_9c;n@t7k6lK=yU1A_tsV*>*VF`Vbe^zZu=nofGK`*XSU{~K30 z+ml~1dY7~9w(s7X$f0$*nPuPa&n|Mi6JH22rdCXAu>bpe(VlM^OXXrVF--sZTKK*B zJ~IV{;ESYnBn*sXi|tHO4MwK}$QB$w)K^SRbMhv~|u zF9~T|d}DJW*Rm^K=dJaO$+_#V$I*G1|K=Svjz1y0*!N63cPS`)hRj!m_6D`%S2mdK zeAT#(VQMT>o9c?|%)X4bcC)PU3W$}@;0(C0YA~&8!Plc}?q3o!3YM7IT&q7LdTyK3 z(M%1ATcV7hC|V#jx2>Vi;+p6Mq2no0yo|Fu8}kL{KX%eh*&nfHPRHQC;oF?;Xqjh}O4TlutG)pTB1IME1R( l*Y16itO!nm3y#lbU}kW*vvYq_2K!Htkf*Dk%Q~loCIF!~SUvy% literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_similar.png b/app/src/main/res/drawable-xxhdpi/ic_similar.png new file mode 100644 index 0000000000000000000000000000000000000000..589149fd94708faf0404dc35b4f1f0893d7dcdbd GIT binary patch literal 4556 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 gG(Zjvs-I5zKTQ;`~+@&Et; literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_warning.png b/app/src/main/res/drawable-xxhdpi/ic_warning.png new file mode 100644 index 0000000000000000000000000000000000000000..425cd4b46bfb00bfec0a7ea54a68e92a12d9df94 GIT binary patch literal 1709 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 LqV)OrnK%9d>~1QA literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/textfield_search_right_default.9.png b/app/src/main/res/drawable-xxhdpi/textfield_search_right_default.9.png new file mode 100644 index 0000000000000000000000000000000000000000..ff164aca479ea20b6867fd7bfc2cc8df1aa430f9 GIT binary patch literal 17124 zcmeI4Ux*t;7{Ip_#9l2@q+Ah^HLXz5&CdSK?vmt8lRNLg)Z==kS6}RAcXGE}vKx2z zF1drk;h%@1zElM%eUm~(6rT!Gu?6XiSn%10>Z8_LUlbG{&e`jvC*awW+65_3M?ZDb}%b zsgp_>lp963&o@c8<%d24;y((Tklek|KEkb^o5O}169xq;EjrP_WC^gAl%Q*66R zxSUHBqJZo|d6q4DK4g`&!dXC&*^HhR6hYR6=U5R45)UL^kT^j!fNls1+kK_-W9V1b zx2winX{s9zNx9Tg7&Z)^Z#J9hrj+*lMPAT#od+T>iX5uJ1#50-wm3I9)NvB~DZ#+< zokr+*E*tqZE8a?&OQoVfy;rwi^+vhe3*`n~dWa+6G8?>*27JBF53>xyIoNOHP|IL` ztpWKt7$&X8{)9Qp;H|JFw%@PgDq!-S)}QhwdYi4sIv%4xoo`TJ81fw zwUTG8ME#m6<&h$7JGL=(RMDl9E|hdpRW(5C{1mgE8`WciUU`ZKB*lo3gT5Vc_ZVIZ!WmHMz zs#Q6|nKrOFP!(*cf;?NM9lK7L54bKt-&uwBl<(Esi=|=u0gNVYE)~z*-suYs8eCc&}y=#0a&#;S*=z$n2{Au*MtnGTUtevfUc^d8v9R}kB-q?f^Ht| z3o-{34rJy5Fo0^v%CkT-0Ek_+YmIgic-63J`mnHwmUOw~Nc*8?Jz&_viM1`8Dz$#R^ zj3fiDszx~|V^+Be%8N=ywKKY&iMx2O6IBolNuU#T=d#OoH;yQK-NT@1^P*tLnjx!n z(0YT`OA+yE_$T?LomK9VNTOWROFVIqeGn>T11AQ2v*_=fJfCTwnmeWF=?{AF#P^;&4IHR@x0Idy(eKetdc}OvAQ%v` zNO6(*U|Na`140%lE;1iXOL1X9$Rfo>=7VV|E({1+q`1g@FfGM}0U?VN7nu*HrMNI4 zWRc<`^TD(f7Y2kZQe0#{n3m$gfRIIsi_8bpQd}4ivPf}}`CwX#3j;zHDK0V}OiOWL zK*%D+MdpKPDJ~2MS){ngd@wD=g#jUp6c?Efrlq(rAY_r^BJ;tt6c+}BEK*!#KA4u` z!hn!Pii^w#(^6a*5VA;dk@;X+iVFim7AY<=A52SeVL-?t#YN_WX(=uY2w5cJ8tea; z19Z_JI5g2eHL!o|x`_VefwiXR$_%sqG{bDX&M<#&qVJy=W(6?Jtsor=?^zQ-*a>S6XVa% neD%xa^EWSlgQ9G1?q>el_vD!ah1=^$WM(Fhl|DUs`nA6SpDdvi literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/textfield_search_right_selected.9.png b/app/src/main/res/drawable-xxhdpi/textfield_search_right_selected.9.png new file mode 100644 index 0000000000000000000000000000000000000000..cda96f2183e4e3ba384c3a9cee86943cac517d56 GIT binary patch literal 16685 zcmeI4Uu@e%7{HA-5ELd5NZT|t$;yTV0>?hvNo_05mL=_4K}zX@*0KR}>`Q7jv5oCE zX|;y|LIO=7#2;xwh$r5V217hCB=*1__pmqqydt3?1TVZGjg@m8C+;P=R@*>;E^7XK z_ucn>cfb4Y^Ep!U#@y`DU1R&k7>3z3GhLcT?<{)WfA?MJ^|dos_Mo?W-04N1VeWk> zeBQx)@ZnyDIegfvECdVXr!>Q9rghV)!gRaoqTURXJJfb{V;Kf)71k|#BK6zFuT!jL zPNa@!%0k&K!X;~Z-Gj&0XDi0~vZ0!(L;0~>TSE?-Fwog{vtj#Mdm`2GtD(c{R5Gc*iQ-i zhG)5f<=AZKSFbuN!9*$*2I@V!^J==~axawacj+OHd|P*UkQVr6lOJa32lKE$$f1^g zWzB{BJoKFv&w!}=f#HrOSJCLYv)zWH$yI0vLtUXYBRd)v-!ytQ?uyswfHn;tHeeIQ z@euB*kmgIs^$nhgx^fRg(W{7)1 zR3_D|I9bdJM*x@<3gdR?jR zb1Rt;MHu3uA1Q{O06UnD)0Q7N-daKtPCYDE$KXqwTVqAT+UEN7RteP=O*g_am^Y(U z4&f(S`-6^b!cb8Mlm$Af^U(A2hGVZnFMuXn3vUGGwcAX6zR(IWoW$0lDe$_U+ z)w8$t<8TRYc*q_WF-5L~bgPT}1 zr6K|?BWYlqE?Td;^h!jWI9u+vL`CRSl`RTMSZjKPCk~SE@ed~G?W?5!ojKofCEea( z<)$m?|7}x7^>y<}AU;+Mo*crXr&jc!j!qO6$7yVFO~8Bi_fFptEa?Hnu2h1oNlIMw zdQPIAn2)k!TD7&!J_gq)^pWU^`#^V@rXJ|q@{;VE@M9WU&GDLgkWUnkeq*9%@5rJR zordF~VZl7QwhwDgScUf~;hhD#5O>Z%d644=+VK7h;jy~0(m9rmivoJ^AbjnbCe^s< zBH1*l#!dT{&)Qay7rW80=7d#vGZn!&hQ2_6`D`X5WpX^;j9biEZP-{SS?C_i4-cLa ziqx^e6+;ad`-UJ6)-g;dx`xpnN!;ppPLNS6`XT{+hr##0#27eS_Kv%^^Fx0|x6>=; ziw41fkVT4%%m>p_To@3tNO6(*U|Na`140%lE;1iXOL1X9$Rfo>=7VV|E({1+q`1g@ zFfGM}0U?VN7nu*HrMNI4WRc<`^TD(f7Y2kZQe0#{n3m$gfRIIsi_8bpQd}4ivPf}} z`CwX#3j;zHDK0V}OiOWLK*%D+MdpKPDJ~2MS){ngd@wD=g#jUp6c?Efrlq(rAY_r^ zBJ;tt6c+}BEK*!#KA4u`!hn!Pii^w#(^6a*5VA;dk@;X+iVFim7AY<=A52SeVL-?t z5!YD%X91v%ehZ+5{^|d%OCNoLelUPFrsvBHv;GLfoOzXD{@6h8*BE9+V3?mzF^qPO zVfHxhym)yE^{vm83YGTvzr0@lgN1Ek1X3_lIX6c>TNbdwb`0 z|8V8trE3=tJ~In1e|!GUkKg>}`O^>9_bXqVYrlK_$3IVhy5qpf>tF6TarpNeKOOw) r=w~O-y!`9x!{y}@FQ6D38#|b@?EVXHFYeokL}q4kwsii;saO62jejQm literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/textfield_search_selected.9.png b/app/src/main/res/drawable-xxhdpi/textfield_search_selected.9.png new file mode 100644 index 0000000000000000000000000000000000000000..521c497a138c4800bc81dcbc394ee6bb93f54d73 GIT binary patch literal 16688 zcmeI4Ux*t;7{FKCN=+$+`cM%cwyATCB>)X}46W5HjjA6Ip|lhF*j2{OHo1A9|c;YiQX^z*={n_$tjis_%PT~E`d zQB0qb>s;NbfF*Ng%>^gdPBiqj^SWZBkCak{o(dgwfUnWLPTTU-UNPPGtHSR=Gn=OS zUHtRKbU6q}FVyGgitPeg&d7|;@e-X=GQ7-7dHxYv;CPYcM3xsBUQjtjU^~}2nVHNddc@4m1Vo#Zl)_{Y_F%kK%h!60%&l%H=qYOvOUdV`3%Q)I_x-0&z}eJAjevI zja3J*^T4y0T^+#ghsHaaUV)<<%yt`&rdNRFk97st4DV=Md_y1FILmIk58BXK&;}hC z$Ab*`u>hPU+qb4AW3+Q?epf6~3wn*=(NY%UNEml`9n`FNso_D;L;_F5(?E2s6e# zC*-GxB7;>nd=~6=2j{yVE}`j^=tG*to@Xv~!22Ye2NW z^;L6pOnOTYc~Q(9cz*HZCZ1jwyA4D2zJFX z2HA6X^rLVIZ)nH@8%(-w{sVLNG`O#$Aq+U6;o}*P+0J54QktUJWMm);ObbXdqe(`V z5&0G;#{@LDJ_oXl{!2FIy9ugcknxxC78k*j{M$xb}G<##m~ zlow$dD}@)|xhNOYl?wgPDbq6Su6HBg3cxHTf?u+IDkLa18#& zqU9R`Trpmi1hQzu>@rLd(Z<>Gv?Ve^KdWq!NP=85OgvGLgpYr5gWkSO`u~~pEoaj0 z6)WMTha5^e%&(g(fvBt)tsH{VQ?0l_gB3-?w%c1=6VTrA*@+#2s^)|EN|kL0qAJ0= zwtSTIhEBqsh>yB$n61^#K1LH~;3Lr!^#SiP4b9iK6HraAyH8#Qhqm#246+HnjgjP*%5>`(;^9;NXkpgTDjQ zq#89njWJAfL9_%H z0t7E&ai!v)1ppTOEr2fkr~j`X`1Jz(!2nvHnXgmS+Jh9e{sKk)u>n7Spr~bzqJBI_ zQR*ucb-=#(^o<(q`^;>$-00o>>E&|jeQx*8?$o8vH?F__+|9FV`}go~?R@O_Z_01z z)f-nY-}%|GtA{_h`0W>$Q#-2b?!hzruYU5?qqRGa|9SSKH{ZT-lj^imD|?>Z_35$i xe>?Q-MdCZ{3Q-Vlf5m5s%0D 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 S)ARO##5`U7T-G@yGywqAjMMu7 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/btn_check_off_dark.png b/app/src/main/res/drawable-xxxhdpi/btn_check_off_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..9159c3b1dd2975e5cf270bda4b8dc29ff52c9d65 GIT binary patch literal 575 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7$17NIEGZ*dUHcDfXPv$A#vKqi>BGv^0wc8S+ZMp_g%l` zmrcFRv)lIUOP1+Ax8m`;x!U*5Hy88vRoj&JAm@)hB{~8vX-CK_4MsX$N#vQfYDsrH9&Dxow`e>r(Fh*~^_J7y@zwq|QeX~mp faPVPkI3pWFwWIHrfa4oJf`mO?{an^LB{Ts5tH|KY literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/btn_check_off_dark_disabled.png b/app/src/main/res/drawable-xxxhdpi/btn_check_off_dark_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..76f0be4c73a83ca97dcec040c7fe9ffb4052b829 GIT binary patch literal 574 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7$11LIEGZ*dUHcNm?==AA(4-{bwQ((V#a|4!_WWQlMEwN z5)?cZ3h1jBT{H3S z|AThd%&u82-C{qJp|5%V(OI)TZeN?ETiiY&N}|s8%k2nfca|(MK5%2tsT<38GVL%~ ze=OBKXRnB`gV##A zf178TmVY^Jr~8U0!2QFN*IP>4rDF5GwjV#fmwWS{s~_Zx z@)Wjy^;c?{{bR?g(jS)=Z*bq2%2PkJ+Ii*MdNDbPgo!>FVdQ&MBb@0Du|Yp8x;= literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/btn_check_off_disabled.png b/app/src/main/res/drawable-xxxhdpi/btn_check_off_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..d565db74aba67bd0fddfb54209499b654a942f83 GIT binary patch literal 555 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7|(jTIEGZ*dUM0jn<-J^K%w`rHuej#-{%8F2*u{)MdmzB!qh4Rlp4Btz&XoEbfB8RE z(zt)-(KUXW4Ot%#&uEl=pCe^_QudG`(~4r~8o;r_o5%5N=o)f%FIYdxOxjO)R% zeUnaqy0_$=`{tc{^J1=V?cT*Kar^JFwa_N`X5<8^9}dT*cL0d<=|+oz1M6P49Sza zXZNn^;hsG8)!kD2?kGGnTb6vj-tKjMRTmQj3?%5=Gx0F!&e4ykVA($nB=(YocL*UE;hUuDfg^yS|CaA^El4;x$ zvaq=Q<&{>GtdG1-ll6=b@Sfm2BxvCjHiM^C{ot{v%JAR+&a86z_Ah^}&8>r>tU!B_ zK*Jl!uT|TY&1-wS+~8iDq1j*UJFSJn$5KmecFX%Q_|^*aEl-pw*dw;~PrhPP*V{skB_%6&w0mquXx(=ImfsXPK7`BDdJ+7sldSKz#zcD z0-+XdSTU`?oHby{pT{?!ud3ZPKZ1qng@4`GA13-MIan@C|Fd63tdZf@l=%rj@#wlF zL5>9RPvOct#2qp}&Cf7D*T)*L=+Cl!9LJTt8GbS;#4|kMZm40LpwIA$)nOmQ32~6% z{xb|>p;G5hv+LSlWeB=yXIk-7lWoGOZ!49aEEjXoy1l$>;`|_nl{xdZyy~$$?}9> zt>LV)LVW?_8hOT_&AbnOa-5K_Vff$I@F$5WqPOAC|4sH6zHhB5+wfdcTiveqnxjaoqiDMlFl8c7~tdwBgj-!{IOFjxZx7N+2`A b;g4VfgTG|Z=>XB@zaTkJS3j3^P60uch=H_i6t<{so#$Kl9#@zd$aqu6XVLGY>xsZrPksW_&=oeC|h07lTu3 z4d34Ef3nut?d{!QGvfo*=RQeXj$n-Wc-3;cC2NC^Km&sU0|x^WgbJJyHv9N)CXK*4 z+j%uSzi(er5^$*hw7M+OJxGvaf%?z=p1MqoUo`BGaQPR6U*NVIqYLNASF%nx^=W0&ljR}~TBnzHO`7k;uu^BfmR}tg;~$HJpI0XR zdB{>A%vSW=u}8#l{zdVFh@gnR{npzD?35Mk4>J7fYWUN_+#$>O zQ<6nMo9UA=*Mip^C(au*tW#33f6UO=+wjMH$B8GUW`ZU~%d1@q_P%%YnmqqBPr(Hr zhkqZKI>a1Tek$f!@R`GF+Wc&Wxvl}b{>)*vuwwf3ng789juY~J48QCB9sWIFx?#!m zsa^WOY{3cke;EErGX8wZvO$#b=l|zP3*Nu}ded?1&#k_nxOpC!GF6@4!{Wdoz`(-5 z2&Z0XH*8&aHrXec)!^~!;<{%d2e{)adorgmy!x0TTIN5e)a~>AE7L5`GVsP9-u7sn zu-vBI+!_BJKA&Sh^w@t%M0)*~+Xue2-k-(E_e`@Vaqide47n`V?S$XB79YCPy0N;W zcEM)5`=NVhnA=aPiO;?Es<-0aBFo4$$343>kkTQD`R1HHBOAm2_cITFO}_RXB;x7n K=d#Wzp$PyGlWVa6 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/btn_check_on_dark_disabled.png b/app/src/main/res/drawable-xxxhdpi/btn_check_on_dark_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..256fe8b7820e70e9a35acb016f7c7fe26fca901f GIT binary patch literal 837 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n4WvOIEGZ*dUJPev9Ke9!^fEAhJvm~L>dX;?P^WSzE zTk`@Y7eUc0E&_tWg>RM}=P_oS)qnf+ySMMpzFD&;<^Jh=sk`R-r~&Om1{(9yPIPb0 ztrlE%J@H6o*3az`ts6NWugskFHG_%c(euPA@ndry&+YiMznx>Bn7qRT`J?g0RRX6C z-UYp5OYeMoe?q;;$FQG@Pg&0io!ZZHS+xDQ`-QiQj=p6uzw`CjZGnSZW-#pX|KGFy zUdJbW?^}#EZ^bt)Tgg;$H2nP;FD`~w1qMb31_1^Z2sP=0MdiQG>lm`8+&4M<+~EFG zu||fr$@VY#%2Grd8Q3QMpM6J3;6THt;vNSE=F{hOnHc9d{EJtL;9g+#vtFS8oG)v@ z!av(`^ACSHrrS`%I6JMf z7n~5Ez)XzM{5&itK|0a~uAAWSyYX6!mnv zgu{EmD?0nHFjRFfDEq0(UQojFWV-x;gMt(6=P`Wi11a24%<`n)yg^)9q5co!A7iFZ zuQ?O6nLhnL|I_3C^=J9)lb^@mR@r}u!A$T%^mAPXrUnKD1`arNAb@d=ZmwkZEUgCC z_`RETG#k|SRXuk%WHj;J%ypSL{qNfqiN4*p7?tiHiYUC!9pCj^?84vXXX^Yd$Ll8q z)&4S{7qtJ^wp{%~U+j7$r&oU#cyfRF9tYziyEz1p+&{LL?{U}VxgFcj{Z_F5dAP** z$?jK99qC^lMefx+AN}Wz+>e@WCgg<3z;NK*AE5+>zX7wiU%hOR0h04{^>bP0l+XkK D-I;9J literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/btn_check_on_disabled.png b/app/src/main/res/drawable-xxxhdpi/btn_check_on_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..6c0685cc1236422a3b492ec5e34938c323a7ca7f GIT binary patch literal 896 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3X(T978H@y_vh$`-p>pyLnYzLsM6rV*621F|nwKh#&ux z6BB``u(kDtri@00;v>FeZo21E)C$w?H$PnU`_JCnD!V7Er~<9S00sV6KmK0(Tzko1XnD=Gm||=5fsI6dQ4&f4=KHSs0I%9=u<9t~Ak7DuR2%{Oo?2Xzzk| zfebgV$xoWkCA|7<<5Gs@JHt-;<<5U_CMh`J3c4Ois;4mt9nJCgTG#{8}y=k=W!)_z;rak#F6 z(d71WrM%D1+zam7i59l&uVD~-8!Y(!n=_X}_}(3JKAY;QekfQW9@KF4SGVnVjmGJZ z|7@7ukk~I&G3yEEUf~<+yC#2No!sqO zZDspsy|Uwy58d}zN)#L0vZogc=e+D z1cyP|{Di!?*AJFo%MU62bJT_9<;BTm8&!+`eT}HU=K296`2qz5I`|pc7?wN8@144= QT?(Ya)78&qol`;+0I*kQ@&Et; literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/btn_radio_off.png b/app/src/main/res/drawable-xxxhdpi/btn_radio_off.png new file mode 100644 index 0000000000000000000000000000000000000000..0b364f74b7ab1205f195d49f2333e406273c1073 GIT binary patch literal 1606 zcmV-M2D$l(P)^VgHrDf`X8Mg9!*QsdNUdbO!=VD*1r}M*<8Eyn+LL zo6M6|QFWfR*SkBr-%nauk=`kOGvCh6?0eoJ0ssI20000000000000000000000000 z008P46mXBDZdBc<`QPI$*RT1r=|$)2YZ9*8p@ns7^ivWz{rx8Q_ zE=$9bg#ZEUS;EcvQzNE?yiB>2Oj!VC#$RMorM%;a009UvVg7~3wf>IBhU<8Gq?YeD ziE7R{#{B6kH+av>0|fAh^Iv%g`F5S&diYNEKEu9Z{5>TRq$Ge5JHOh)xJyq?-zJ9X z9wKd71a}aCALlomZHL~X9dmV3YZk#kvGX^aT*cmwop7=`)F^^)5?J$p+rjx}#=q&t zrvwE2$;$A@4u5yljc48XyBiNB3ZPH;zIB}SX4ZU6uzG1Y8l5-@`iQUp7)t~(1@I%^ zyh~>P2c^BmdQV>-4juE5@RY}@BmtN#`Ul4v`fbcm>A(Ze=%GWUFyfrxwgdr~n|oVZrf&Y;A?$x3{oFb%2T!ep zG6+us8e+(6zchSaQa)TdBzaTv_xvF#0nCKu(IzwJ+ai!uFcsS>D*+fqOP}t&p~Rk; zd7|ZelW-O)3WUl5E`)2~g3{dN@wrc67z-o8Du)Olo&6h9@Cua&r0)v`i2@-6@Z6!~ zD}{HSZI`qFP6gLAl);ZI99RiUy;JQ1kp5CX%av!i~vAxi&~QaBsB1Qd|Mn!0Au)6hfQNf0OSA( z*y0InUp5c`1OOla00969Ad9jFC0-f=K#8ZsrZFQx4WGuh#i0Zs+1eb!?@qR8GzmcR zn0ExfJLLeaCIQ?D1yI5FpKUPoWYeT_07=N2!2co}9PDY~AqB980K5rctx*8d2vG7e zP~{#0T10`p@4eL;0SuBiUZ({MVCKn&ss%8jGPi}Ly$I9`p!GBpN}TaBY8UTH<8iFyfxvJ85WK4(8<67AHyzyjVShT$(8K%YhmHQ2Kp?Ot$}%!h*E;9wKsvk5Ediy(jAy>f z9)?*8UsJ@hl45rbec-VE%s~XirQ{iJmc;HJYO0!F$o*kf-#RwxQ8{QHy*p?gosbCn zwKP=w_!;f~!xprU-(uibwEqL4l{H#VPM^^J?=0N0^OHFrF*O1727f{FCR5wk@ba*+ z636u;VVyXv4@v&ombBD+Pl+9lnW6xCTzAn@CfN?CgTxJImV!CyeMi8A{L56GnQ@YH|_4*R_0Yefpr`y@Q4_I{+(30sLPnSy^J(_Dl#DOaK4?07*qoM6N<$ Ef&qThmjD0& literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/btn_radio_off_dark.png b/app/src/main/res/drawable-xxxhdpi/btn_radio_off_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..c1fd41f936e03f6269303c8b010fc4ee920f61d3 GIT binary patch literal 1585 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 zfDFgFZa*Cuy^);40RR910000000000000000000000000000000002MJN_470O&Dl U8s59sKL7v#07*qoM6N<$g5c(D*#H0l literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/btn_radio_on_dark.png b/app/src/main/res/drawable-xxxhdpi/btn_radio_on_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..3dc5be66e49d55add51ba58ca4e41bfb9f030ae6 GIT binary patch literal 1866 zcmaJ?_dDAQAN_{#;th>;3AHL_QMF1d2#Hdo=1XpgMvS^uTZ5ZxMCr{niV~~VYj#+v z?U4qx_b!UG_9)@nl-vFT_dL%z=XuWg;q!ccI%F%$yWCtNTmS%Yo0_0(et6_ha2BkWa|`wFan}U^_AOHs(k^6X%X#j} zHB;zzwF;oZvIt?ex8y8MbHuBw^7^3w6rB&`_V8*{*J#UJbPrvdlnvQ#DmN4<=%~1; z6sO%68R+%!51yEsu-BZ^ya)Pm`d`pdxN!yuU7jOG>!WDDosP`ix?E1%PN2B@M4$$_BVDIYD=Tjf9A-Q^s2!VnFWb%o?)Ka-pjjLoWYE`K zl5uYUUEg*flka7@_1MfyM{7cHg$Lfy1LL>LOz;(D zW@0WSH7jL(58c+esqC>1u!CBv2F?GVJ|#{OKC9f*+ad4SQrN3u>m8^2p!_pgks~Z# zwC`7AimP=R@@DL=@2KEQ@>&nVDN*KOAZKT(Ol!AZTv7HOhe5I$VQzuqea_G4-M9;^ zmqnNXh@l9439%&W2x^*`b%Dt{ev!=qiiM4^z3xSG!?N#21ay90E&C^F&Q z^3+1Nr(OiSeHUXWZ)Dzm_DA|P9utbGuPy-lf^e5y!n{EoK#=zaC?KW<*A2fkzr(<) zeNCD*m1Z8bj*glB#fP@oL6n0H*IQOW-l?A2tX9cug<#@$!=(vAgx-Hx$lmK~=QE~2o zrN@U#m^^poq~msGq#1AR!^g!Znu8_9dZmHoFYn&u;q?#>0jdOZULCwd(3^C0D3CZ@ zA*wBtD^x`xchde_WabZUz!O)dN8XRKa9Z6N9A7nPQW|4zXm}taq(XH+0IU_G`m^eT zmxuN;4OJW+b~4gh<+IVQp@1cGHRUuRjo8rr2|W5*n8nX&@@`R+p-)5iF|SGK*A1Wm zVc-maHc(Aai%5p`<9??GC}t;Y8FV`7Q*P(%Bv@1`Ld9x<04M-}BI9HXSTSd*Yyzp1 z2NX+*GjoC892Bo7rH=UMVpn*Y^JjVxR0eGPv zR+N_%|2MZJ9`6f0af>jkj0@T_`wj3Kymel6^+jm^*$wH6vU_`gQ~L7W7EvMOu(?}{ z=jZnfRQcr4phBC^v%zqo#%YT8>@OKWPEI^D)zCts;8lY35j@(A;?}7f zZ|9JN1a7m>v1f69IK3E(?aMS_%zjN%U(tH1z9mr1P%edj4TCfpky87Ri+G~QV=)uL zS57+O`zCRINSMT>s=#8z&pgp0VA&>foLGhxU_39M(=7NDgqstVxcdq7>}ywQ9#(vx z6>x4FmQD0Lp06XDO)I3%BF+rpk62S)jLGkfuOUUzHY2M8u7`470-03XHJjb}?I!{? z{uY&kN=W_n-bqDD=f|7jZ~1;{OsR{j-&%C|e20Z409)&Dq z?yXTdp^@OOT8)X##Z?}4?5kZgZ+;;I`fXu=%DhT~h!rkeztjrGEbuGk0%9?hb`9w= zS07~_TP=*A-OuHB8v8@oBvH9T>HnjS{@^v?_Buy6%++ir&8^(psmeOkh3m`ao?Oa8 z2sSMr-CJF%Xwy8-q3RQ^T>d|6CD5N>qrYepF>y`BHTza1Y@Cz<2qoXk?BHWOd41WNmNAdvxDED|Kc9}(i+8n1(=+V? uhNapG&h;*LjD2NF{r+WfepXEKC7UoXSTl#IiGzIjnV6z2Q5A*{N&f&gZDa8O literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/btn_radio_on_dark_disabled.png b/app/src/main/res/drawable-xxxhdpi/btn_radio_on_dark_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..f9951f378bf6559640ba6720d970320fe04f1fa5 GIT binary patch literal 1657 zcmV-<28Q{GP)5tmK{F9D8 zx9aMplsgEZEQhz`?AE~9rgU^|l^{9@fS=Q6>f>-Vk-x52*G6AT3>yd_pVKGm<5FE0 z37mdP43Fx1P-56a0O=e*C&zO_3t?575F{KwZXw7*oWG!O-xQ+Pp03vI zp{a^4A-{4iB_(6$U#X9Gl~h|llnB-kfSKbPa{NWWRrpg~zr>&Bj(Dw>ryhc!KEfIG z5hek111*Fe0yypb>i{8tGpN=30Am{V8GOa~FDDU5M$Vs-^BWK2u0uH8KnzzNBJD{8 zI|v|*^L04CPu-%W?P4L^+Rbc4APaW>p7zvD=JC%cXPJ~J0-4!(+UYkA&R=n>v8h)v zcM!pC&YVDIcD`-Gr&s+Q*Seab+0rr^WK9fn3gClfsi#BzH*RIrE5iO@Sus4Sd3_s= z034b;*$k9W;8~hQGJ|1snEcG$Fqwq@CH3}}Lja#G_Wfd=?&e zZ3js!9GTDvIy3%cSn}y^-d&_|4Ey~|D^gofn>M}_GExQpzscT zeABL7C!X&m>;sLXp#@-4A|{J|GU4jKNtpX5Tw;?nE>CWhys$?>i6FN$jI+S zxSxa%h49^1oECXdk|-bSwIzPvcMyEa^Fr0HkHY_#*m<)dBkzp@I-d_w)4MX`$Q9#dm7s!T13t;WI`a_g> zLAC=Liwo;&1weJUQPx-U`}CQJ{qYo>>UKEFggAJ^F9#ZNoox6${^I4?;6CTt_+71_6eM7CfpfZ&dbSqm33*0!n$P}^~9 zrP|_6L3TRFSjQ~@009690E|`uTa2|L0hCnM)ohDc0tjB#lvFlG8f#Zo1aK^sN)SPp z$QG;xV6q3EIsCsR9MA4D#3?HAG+Ww@Po|56b4Rp3o z@{kQCfo#eu2QYDz%dJz+CvdxrZ5GIu;}8I4*d5c=-$4L&Nz2Uf$(rvB1z z62Pbepw-YviUN8+TrqO~%5jf2Ix!?>4C2oJj<}N4%QMxsQDqc8XT;qe&yg^)cvy z2vTkbosaWru5BRO`7P4eUw|WGUL5Cmj)ArUHC1bAUYIK$wTF*OOm z(Li`*07o_UrHFJ~Y6Ntc zf8f@uC{_R@f(d2c7tDvM_xe?;`}w00KmkH1%JIbt;7ACUB!paBLr;!>D9!Q33*bl$ z!#cf5;PgX}VN<&8st~{nwfs5N@S%Phet0B(TiY4O)d}F0QnC@xKLTwHoUPGKqn>jK z2LJ#70000000000000000000000000006*m{u5vTia`FBg30MK00000NkvXXu0mjf DC0Xjz literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/btn_radio_on_disabled.png b/app/src/main/res/drawable-xxxhdpi/btn_radio_on_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..c63012cdeaf575e631920bce06cf497109a3a6fc GIT binary patch literal 1932 zcmV;72Xpv|P)|txrZK+roRZAccKtvqWZN#5h$A4>8w`tvG{0RXBBCp#~-3~nr z`eS04)a|lvO9;Twm7Qq4na5MN7d)vOZ zXonn~6e){fknrFyc})$y9ecn&OP-<#oLn{AQ+lo$PtW(@r{q^Z>4jxCh3Y2syA96W6~l-@jXUo=nfNFBcO zj?SaK-~Z5yS9GzVbWZoXh!X#3;>+31JkNf~-&;B!{G2=ffaJS&O-MI&I1uM-1#-cd zlpRih|1t15`+pQ3`-a@?j@fM0J`{tit!Cu_zZM?*lMgH4%Lh0Xu7P8+-{eu9e8PjD zI}-093+l6_mwxkL8CquvW_=KV>cAI5(|_h*nK>bM_wXNH$JY*=w2EX;GVif3uu5G3 zC5JbXHPUo%KjZMy#{;87ZpuF}-I!WxL_;6DyqGNy9d;<36;q4Be?{)z0+LXW-Lnf* zp%v*|&|M2a`limK0%f zrdz!(LOfT?L23BiOGkYer2vLP^Efq}c_qa|X=sTg*PfBygC@}kpb&O|Ntp93#fQl` zt%M>dQndiK?V5upGI!ENZK zK8;2I+rluYi>M+rCg(VJ)Thx1pb%OB2teK!MM?sYP<14%7?uGaeG#<)hKc+pZTj_x zv;e(Ix0Y&OY$eo51qALN+6ZuWr^y?340g~D= z>-ZdtzF7cCCzw3DpF(Nb;P?7#! z&P-?l7AXlpYO+jjoyMGj+vMC19Q9>%0+4vEBg5aVBAxvvp8Cj9UnXk-Ox~>dBSHa~ zoKx0!3$PN3z{JOhX(<>RuF;ZfU!`gRq#p5+MW@HG6jT;sEFJY>v;vSk^S(>wTcN*E z*j&2{J_i$|wFqV@3qbnjkLk1iArmg!=Ke2**O#;fP)r2J6lDIyZzBSq%Ryy1_|k+` zlddnsM~0AV;X1Ffh?u@9eAnu%uJ5d=_5Pgf($<~|qo23bgA_3*f;;F;u)9{Z8)c0ypVwvRG!n!e_}C)%nuuAXBnW8TXi# zA@Z9lR`CPLH-6rbr&O`NYX+4XA>V{NrAzg|bCPHL8k5IePT$$WE1m_Cbn(H8C^$x^ zIL3lKf9k6M6rbomabGZ1A6xURj)r5Z4&0W{^%1 zg=RTWA=F2TziyCna=#~eD%i%~2~r#*WYw^WXScL`%no**>>~a9ltU^PVrT|^MD|(AY#$=& zy#SgHKPGp@5|+F?v|lQjKVsI2?h`%^$=z+`3CH@Ym^wQ~0W|3DqQy+K6;KC}cbr-Z zUQpPg5Vj`&T`cQ;iUMfI*hl2I(4`G4p7G<@PjrybY|2>(hmoJ>ga>|^LZ_xAfY^nQ z)WD}KfVL2hNLGZgOH)4~caM!h0EV>V^vuJcxAz&6rY{0`7|R#rwzN$1>bL1D;1EFI zP;$E^hIKEF%v*zg8*>N;0000000000000000000000000000000Kk9zE5HD(T$S>- S>%1=j00008vDiAX)*soMSHf2OHPpG^`{7LV1mK#kF%?(x08Gw3_ZNzZKgni)+ aj12qt^V~e&k$ep3DF#nhKbLh*2~7ZNKTB)? literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_close.png b/app/src/main/res/drawable-xxxhdpi/ic_action_close.png new file mode 100644 index 0000000000000000000000000000000000000000..63ae4b09dcfba6ccce76af4d9f2f51b35cabe5e2 GIT binary patch literal 496 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGok|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7$?@)k%TjHaxh@kKP9|^5H;_;~2wQJ-5 zGkfoQR;bP`UNmKrpT0QISRix9o&{GC;}TI2t@DT4b>?%y0b#Xhw~ z{>k-@4a)VaIQXBWEB{fd@8a-3ao(jOsO~if`;+<74;TEICKxxNKCVTae~oX8{fYl` zHvVA@>${M)O;UNw?962xx#=?%oqkJN@f)0tFFfwJ?6=HolM7qYi)7hm|1{b4r{VJH z^)@ezK56sqne)f1Szmj9Q^V7z>aG*yMV#sacz!xMK4Dj$Am8IqXTbFHC&!a^MYB&yOuFy|(_fAI{`)vs zpL7R!^l-4YbOboGR5RaMuyNa;ROKC>{eMiAdCapW?@wt-jAZ}m?Is~+c5~X+Cpw2O z?YrZ1_|~a6IfoxzYd4-(*bpZEe)HVYhH2%?pUC$l)Ycj_v0JbxSG0Hu~e#S%c z1e9GKa$*U3J@{P>RjvY5K;5z@;F{zf)h6JQ611Zed=nhV h00000@RM!=3;^r)Q|qqPUK9WT002ovPDHLkV1g?Due|^O literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_delete.png b/app/src/main/res/drawable-xxxhdpi/ic_action_delete.png new file mode 100644 index 0000000000000000000000000000000000000000..03e076e6c6ef5ee4519b6cac4828d44ab9ef726a GIT binary patch literal 352 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGok|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7@0g>978H@y}5Cbx5+@nA@G%pRhRSz$wHx<4sHc3W))Lf z1XpbNI;Z7B``5c#Er~#585+1OvOY#?f?OwR~{@)6&PZC`WObQJQ9109fVBCP<63GmaNXV4!~)x#7siAi(45_^v)RfssYP0ZL3_2gyE=e|Y%f+sCnAcBnNlFmWhA33eusrObdp8v62sy;bJ zvF7UZ1@V^04~rjAzap;07tb)iy4vZ;L+%D?@r3t=W!^yMh7S_IeS!3c$d6hPCxj#2 zj%=?KjQ*rlHg{#m77lBDE~9iUCa^meY|WY6TpDyv#%@uKS-a$=_)O#MxAQWTwbULx z>zf%ccgk;tr5)i7>wZsM7jBfJ6z=rOqE6-RZD+Pu5nYcjtrV<_QmiqZwz10k*1VsQ zN^8&mjDIg|IN$jD+>XQI3LhnID?j?cdO%jR;rK-k))O|z{s|QRVDi!GyS#k9q~P&N z{tM54te+Hp-a*dsdf#K=&kJ6=t=}f*HGSUv8tZ2naV$L%;RiewzXX2X!I6E|MgZ($ zhiubW@hKkWMr*it)iIW=S9p1D?f;7(+NUo|o!OVS;z!vN)!j+cw!HgTe%k!>a;b-} zuN2>mv$sk+#jEDT==1e$)~_Sd%NaV4Uv&~x5^1;_lo!*m>i^U2eWeRCf*O*RI!{&d z>DV5#{8^!MruXBx-z)W#H&2QVcKdVo9&6w3r8$1vRwgcYfkh$%Lq@wjBO8MOvz_Oq R%jZKt;-0R4F6*2UngHF?5SIV| literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_move.png b/app/src/main/res/drawable-xxxhdpi/ic_action_move.png new file mode 100644 index 0000000000000000000000000000000000000000..8da72a2cea0c1d1022fcac65c69f7ce3a6b3de5b GIT binary patch literal 937 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 E0AAa0fdBvi literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_drawer_download.png b/app/src/main/res/drawable-xxxhdpi/ic_drawer_download.png new file mode 100644 index 0000000000000000000000000000000000000000..0d4f359444282403eac63a57cf4c725da33dc513 GIT binary patch literal 534 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 QuK)l507*qoM6N<$f-2SOu>b%7 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_drawer_home.png b/app/src/main/res/drawable-xxxhdpi/ic_drawer_home.png new file mode 100644 index 0000000000000000000000000000000000000000..f3aa1c8b99e1e7387afddebfbaabb3cfee4af8c5 GIT binary patch literal 685 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGok|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+nEX9m978H@y_vnx?{I)fTeaok1cMc-TW3g4N?dj^!lX&P z;kg9=q2`7}POe7TlOkzJY=<+0wzjOz_J41?{rA2n%hGm7{@=fSXZ&_iR-l~_aKLWa z<(HABMRx2bEo*FTUd`w|{`mXVtgqgT9}nx7&UroKNtDwGgFl=2>VIF`eDUL+2%Twb z8h$(spYv+RwW%LgYA5-P^-dS#%$EGu>*BkNs*cyKKi5A-ydv{AG^q`NpeP*QJ>}o!uvXS1~t&@$B~> z!iO`x*RNCXJFH(l)DQER#y&Uv!cSEl8D$cA4U ziaR%W9{O;bYnrKNtFKV&{m>0-Jk@d(i#}XyO7$&CoP6S8H1{;qi>)~Ll(19KEK~4b0L$st4JR2N@`rAi z#5LQ5r~XCLRG%A6O;f{exbdo0u5}M~l>C+Z%C-GwIg2&-irLwJK6r+oF^swDefH>^ z#ygzlom&DJSOXY^WBD^CCb literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_drawer_panda.png b/app/src/main/res/drawable-xxxhdpi/ic_drawer_panda.png new file mode 100644 index 0000000000000000000000000000000000000000..f1bd0443b07dd0cbf009491643cfb49b0d1f243d GIT binary patch literal 1951 zcmV;Q2VnS#P)^z11Tuas=by`t_0BjD|<~rE)97i$A&iT zkFLEoh>wizHM9+%RFrrid&^#H#79Q@tYYVrZ{7 z5_uv)W82u+wxit8*aO?>Y02b+94|9&Q-UcP6-%B4VPlsSWPfC@XRTK2>iYWnLrLU= z#lOO~xMIm(-&lG6wDNpXR{(a?Jq1h2VujmiMa%Cik)JjyP{Qw4p1&y%G#D@M7eKzj zckZHsef#{P?JamvVwH~nJft2SydI5gpxd(Z6R1|=* zannPCljNsptQO>YvS=cA&OKnwqia}uAm1Q=>kEoME2F{aDqvdUX{H^I=lE}k!EaMj zAb|HUeUM6$d@3N9yWB*~67h@Y@O+E}YrDM|735O^#wz|h*B;;-MG@5WG?uZ@FabdU zK>-1kR=_0CB*sKT0n@;v7`fb0q99oH*(ynZ8?>Ti8So|((|>N7Az2blmPb3DGT&L; zx#a;U*ej0-*%(P5yDar(+TXge^ zjIDz6f>X*(5yOglhQ9dD@sY4dKPtmNSeZ6VF;mlENJo?#IhpVY`qZ|P#h;c%e5z-_ z_eZFRu2PSfB3C4kmn30{t<`G1v;SN%_v@chfGc=yg}) zbkqEQu1U3weMZ?iy!(uk3^M1$exw*7X%Myp%eRaV??V(p5C)=#ACUZPmwf{NZ+{;{ zlB6)*@IAgWjk`G^`wjTcgi@Od_Zj-k@U4C4M9T$HegBA}btGkvkoxyH{&Ysq?d9AC z_v~St@>~(fHqzv*K@@^Y!jPCp^cXyL9XbH#*&eZ;^NjeuCdGI(E92t4%?a3`T_%Yx z<%-;Ack@WYafupWtZ68KI_EQ{Seams#bq^1@~xhdtR(Iz3TSF503#8fWsXvG2t~Ej z=uE{0QAx{-RyJ!C`fn;JK&3jGlQC*UQdPi)qpbQ<5qSeJ!Tm@vF5_83o8#Ijz_=}A zj!SyBT_*+P4K~t92D2#8HV2Y95E5EBgG#8FmN1yG}qx%*<#q6Ra%Hjwhh+?TEt zRt}Hv9^kj)8E)sNdDhR|H=WBJbwzBC==Y$a`H(nZCqmqobz}`5*zq_q0^(XmpV$MY z_I(2_1wd-vBg(a3hXO2z?3h|5eneUBY%sbKbpY#{hYPA~y-6fnUcYwoLtDOkduaOwMCK1ZM5XOe$nt$$p&0#FH;me6HJ1Ct{iKbCbw zC4>AmB%gCMC(IrFD*2wvaW&jcd&$6DsZ(<^E=O{DF1K*ky$7Un1Iea67|`Ir)_HGz%6X6 zG&&pMpj+j=Ne+B~bHrZN}!mdY`*f79VPbMnFJ7KtMo1 lKtMo1KtMo1fB^prFaR^o&KiB}!yEtr002ovPDHLkV1jYxije>S literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_drawer_search.png b/app/src/main/res/drawable-xxxhdpi/ic_drawer_search.png new file mode 100644 index 0000000000000000000000000000000000000000..7876e8692638abedecd2ffde958419f3b6eb3753 GIT binary patch literal 1476 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 M07*qoM6N<$f~vEchyVZp literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_drawer_whatshot.png b/app/src/main/res/drawable-xxxhdpi/ic_drawer_whatshot.png new file mode 100644 index 0000000000000000000000000000000000000000..b30b33bb94ee8fc8cedabf15dda6d6c483c6f9b2 GIT binary patch literal 1423 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 bnE}K=dT1>=THH?l?<-q^lhqSTpVYqq^Bo_r literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_reading_action_send.png b/app/src/main/res/drawable-xxxhdpi/ic_reading_action_send.png new file mode 100644 index 0000000000000000000000000000000000000000..27f90bdd8a0d2dc9debcb721c427a8c97ba2f124 GIT binary patch literal 622 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m;^js978H@y}5JH_ppP6%R`+Z>>8 zo(eI|v9-Bcxw=?qX-(uk8^*es{{I%9s}_BjGV?Qw#q%8<9};&Ovj*_9P#ei|ZtyU8 z&sz}weCN5pMKvpuKX;say>QK6rqg^%pKa!zG?b6O;{3ZxV{N!&)t0sGmp-3rS?WHy zN+UXMW8>w=SziOnnvFcF*2M82<}!$=S`)^0slk4 z`hV`s7Y!eA(`)i8r!#NkXkm%=k=R)NorQ0Wyw=>Vv-sKXg{sC4F;dj9pB)?_A literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_reading_action_settings.png b/app/src/main/res/drawable-xxxhdpi/ic_reading_action_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..e9952bd6c9bfcf250c1b65d6a3b8d19879056f15 GIT binary patch literal 1640 zcmV-u2ABDXP)z$sp4QU?go5WP(500A1Jn@JrY z00;mAG{HqCb$|d(z4T1#00A0?fZ5dr0{9HiUV;Ga3<0wX1#==mH`BU601!YAFN_^E z@}O%rq4(iimSwpWD7R}Io?TF#q3s(XnGC{r9X=}WgM>^@MQKcc#lieZ9bJZR7COCd zP+pG-HHtKV=B%*I8EqdNgqG`2-Y`1?gbt6xkMR?KvSuR8PaN^%h>CAa1U|1&A8{n9 ztPj}`fX&>O;Zmmfm zz}sZk0fO=eQ2trCWErMs0od_d6q)UxN5F=sf^BLRpoa2Au!|tlu>kC`FN)0e&LcaO zLy46~D+@4&@gY%#!ZSpTfAN%B6F!Jfac6kPC`Hu%wN*`=zOl>lN0}d`gS7`35Zw#ZpaPK zwXv12a)^S<>^raob1CJEt^@3ilo8LHG0gsr_6eHEmS7^Cq^eRFDb&DWgpo4QR{owP zm`1W};uf}26cU-sm6f-DYKSLDa;v_r3dOU57H0w}XJ4&cO1 zPd_}y_pU6!!FqvY`1HMq4@qaRXQ_O10w~}s+R$zSYzrVA0w^46%xwyQ1pqQ8KyYxR z=SvS+J1;?ic)%INmJ{F#ZAi-r>lHSqJTP_AE-9a$GZrl-{RQYm}IEw7gS2h z@A2rcijfDFz5t(lwCWqxv(WMjDkb5>$PRL6;XH92))uGBBoM!SC4D=X1QYX$m}){L zCpVx)6G-A>NZc^C@p?hw>0$_vd@qS1y3%13n_e%<--U`I(@-TSbWb2feGH2G$l+S! zP>&;-yTaeT11^W25*^T-P1o0-V-e_Yi9E)UOca?TLBxMZEkT4>-ayGH;Mju@fE>hD z2$3WzJ&1<7VhcgspegiYGWx~VIgltaEr`D#ps^S59(vLn{n7<$f!Zr#S_vY^Q=LI?Vwh=daN)_A1j4?c{JvJdQO`}d(|I~w8#;(J|isLJ;O>_K!%A}lU-IXYV+ibHqT7b-dRWlk4OK0?R-$Q z@$qODLhGN8=Yc>ZIQD$}^f4kOO#l|a;XVN|eA(38IUvGGihsg4svofm;txZ4BEatb zJ5n5_cUwxBxd9UgL#_Y-000000000000000 m000000000003?B50t^87Saj<7Ij7wK0000uqLpIB`}IG?DJ*} zNni|WGjU)vb70(Y(dK}{td#f9Za0OYNI;8`RfWu*CkqEzj0d;NRf=^YxC3-qXh!l?5L7{9E*Mhm^*li4502t!7ky zbBBG0Vtoh4?jO?9*QYQ1f5Luw-GNVe6`fJ1<9E+vFPV@pyKwuTlPoV3?`t_t`s2ys zF=4)xV&uOU%n~QwOCNOkw}M&X)O)FeKL0Y9QzqOuJEXL(e)F9Khq~V!mK-OpyFu{( z$-gS!+dXGXJF)+y;`Tp}m^Ge;+nYc8GmrVor`fv~+c8&6j89?k|J2R>z_%`t(dLxC zT0{6xSC$Vt@p1=t*JLt0;bvBdmtgpLh~b1do5MZ>hEJ`G6ZCl+Y7&W&`o$oBVtX=A z{we>ke-cbSQFSjF)%5p2VbFUrUFtyZA0g%)lj5To%0GE?JqW7PWV~}qU$G(k=PQPH kPqpFJvcsZtdkv=ngVwe=yKis#JPjo1>FVdQ&MBb@0Iu^0;Q#;t literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_setting_about_dark.png b/app/src/main/res/drawable-xxxhdpi/ic_setting_about_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..c193341cb73b2b451ccb35160ee5f1f8640022a9 GIT binary patch literal 1583 zcmV+~2GIG5P)RCwC#ov(A-NEF8<>q<-te8^LS z1~{M~nQ@5&6WUPIl$I2!CQCME^;j7vlcL`wSJ^r`1* z(wrsjkGHGuXGW8N#FqAb^!91B(k2N20000000000001C<(zYDvXfztiuU*;(zxlHj zZH4^4p3mn61PC+G1KE1C4I6&slD1s7E0SUf0dxhN1AQog9yg{ZrcZ5ci*On>?Uw@R_o~*JuThA>0?V{fU@g<1TFvNrF!;O=bjM>F1|ak??dL_)*~TWifC&meZw2A0*BU^^`Z3_+kFmtSTLw5m47_vd zMS!hm^?+Xvnp_by$p8oF`_lqi=dIgS?41m60A4e_) zz?i?nVzACXRemx^(*Iv*Zwj!hi_@I0Shh7h3L45|(aRT7}?r3P@y zJ)0Q-)4cVw4*`$?tO00Mw^3a~I!g)_EyJ*GXc;Is3Wg?iQeRP@C@y)hT0jq)9_1ihfoJI=K{!p18M;U zXMdAh^&Ma40b?{;HKdUMGVuUe!0*}beZ`O|YJo7%ekL`s7qV3BBkh+;Nj!^2G4FO= z1L-zQy&qtfgt5}rqe#yD!c_fbWLmgXp>L6qopEkzlCH7m0%(W${s8U~*k>Vaov0F^ zG!q4(_0)fE@&k~*aI@Lu{eGWcQL=}48D&PnH$L!|+1OK(;3G+} zpWw}+rr^1LRx+#2heLUX@kn`dc>Lm(tq;#^eK2K#ZwMb&wps1}j@0K-k4B?O(qQH5NZ=7ZZ+B8NpGPhuhWt45gBI2W z+~5CWV|X{146v06_};-;(w=2$`F8HPh8J(8np%ZKU;!o(*9L_IUd_CTR9g#4HsHG8 zIEibOl6TVItC}y7YHcl9`HCl-jsp)oPeh&isPKap;06&60?P=h10Re4zjO^_=^nJ! z2<6oCStxH-O_KnTRm_i+WCad1zntMB#J8vgDZmXf@YMtVbhWY>kKA#v7oop8FL(iN z=q7m8&wcsXGayCzG7KKD;+HV4V|sCRR08}@Az9W9gIl_dBKdC`Tduu;0{{R300000 h00000fZg~mzyS9a*XM}<;R*l%002ovPDHLkV1ml^&^G`8 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_setting_about_light.png b/app/src/main/res/drawable-xxxhdpi/ic_setting_about_light.png new file mode 100644 index 0000000000000000000000000000000000000000..54e4563db9b917fa521deb0e3d379a8cf03f2e7e GIT binary patch literal 1474 zcmV;z1wHzSP)qG4h28~_>O(I3xIp#d!PWN02-hGD1ZXUPBMN`2zHRPe`d7b@4bC+z!>fKX=Zk{ z(qE4#2B?3=FIafZkP+5WjbMZdKjGHR z+5ts`H;xD=7~u0E+>_uO@1jO{wI(QFfPc^WC0F$+Kn8f05Dkowv;L(;;l4^*c#J)< zWFm_{voEtOZKy7IP09cq^69J}W`NjnGzgv&I+4cpD~SIW)E6tB;cRaN-~(JGnD499 z1@n+o$_C=6Z2|-F(>8&D_-UKKcov@uzON8pt_eCG#eW~_LHznq-#u|(sv8E9_ycIz zm;v{t9s}?UAIA9(=Lu||E>swR=k{mN%sCU=x4GydkaN{{}@yVIA8ny?0fI!vL7?m%}uu0eF=UCVd3W3zQy#6C?tR(dXhA zhcrNr{+3q^*7>8#*8({Fe?xzZWM1`N!w$~=R#91-ItIW}FS!h1fV3G@ofN8MfVx8s z5Y_}fBLJ3pw=4}*HNZXkH|+?3*+2R8eHs8YK&lbI=lyBGEmanY-1%~rKxhT+zQVC; zHaw;3gKGn5hcH07LNIV4q7y!Tk00sbQ8eos^ zQp3rI0Z!;wHSB#D03ZbbB^;d_Acc*T+d`FxMpR7!J9KY*@_!uz9MQkxIrucd0sWhH z1V9RqkH1^PV()PW;zs~P0pa3T9j(Ct35Wl>1}MP>KW;0bVdq@MoOQ;s+lwMR`aR9VkD#9W#c% zXZWVE9>HT`AH9tRxqVqCSQn7s&pxJiRT-d^637I3BH|aBhYLGu4R8?+7Fl~&7Ku3P z#TuX#4JO>$lor;U??p|G8>zj;J73Wv&XIEAY1zD!1)ND4;EE9*ltl#5!mBaBMQWgE zFcn-Q&}7a=VyIVdfD1Fw1kzYYR3M@0;S3v%Pf=58fGcL8(F47P+S!a|?nvxH`=j&J z8{kTs7?jM=cVG$<`%n^n-=+s=x5@zjDJ0+PhR?QzEpIP-lwQCA00000000000002M cZTu8q0G>g%QkQb6PXGV_07*qoM6N<$g4KtFH2?qr literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_setting_advanced_dark.png b/app/src/main/res/drawable-xxxhdpi/ic_setting_advanced_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..5bea36a0a014ecd508b88feb64a26a005ae69c38 GIT binary patch literal 1364 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+SiX6>IEGZ*dNbFs__mpZgW_$;HCN}ATwK+6QFgYY?5>Q` zX?(F(D*F}hS(TU8J8!DmI-Td!g@dz7mh0{94*TgByQ3uFT3tNT_Xx914AYh{eAsI( zd)~;GSzeA&fWsIA(lVsd)Zz8tl0$A2bmyumhYpYhF}r;A_yvfc8^!90#OeToxjpH-9RVSBzb%Arzie*V+)`V)5btRX*cx&Q37bNK66AN+zt zta6io!s^HQIl-6ob50+x(8#er^)XD8`@d{BBl|RmZ_>Bpes;^UK0WkYZg0)BqC2Ts zt@2A5_=N7znYP^iS1%th$?cX*=H?cLyRZI;N*>cVSeVlK z-^=WB!(@w|wU!orD?XG4JYUW+Y1P?-%^n}r!x_Jd?&I6Dr*Flks@%&x45h4BLgdfc zvRvf2-TmOrb!p4#e7j!-yxsHVuB-3K9lC$tSY4^v`noMhV~&f$OX=f(i#Wgedd`bt z`Ejbyy^rZdgzE+4<~}C--^Z8zn{Bl=PV|P2f`jWawYsKrhdmFZ@1OV6?(-|Z*|XZe zPklV^SBzLz+!D!?f(Q8TuPT0VBXZ55RNl&&7g>H78om_BiE9!$z_*A;SSX=`q0Yv< zQ}9(WllvUQyXS&eFntjgScst6wF=E0lWgK`Cq55U4C^mekV`i0f2DQ6&S`D&qf?9HXBkdS zQu=*HCHeZC=KnTNP3{_&E!aN!Yx(=x5yCH5G@h9vUUKW`m9#mB?%QWR`8NQGDWRYrKZTq+JX7K4V|K$Co&fM6l$DX}Ur*)Zj?7W+^9DU<1{r?xf zW=rh%Q5y?9r*yzZSgek+*8t@2bnb zvgRQ>#4i*IFfihVmlW1#%CmGVF*v;@`Ve6 zepxH0v$M2v&8c^fjT?*eE;li-JE%wN$#Q2tQ@nmAaNX3x`YqZXRcefzbY`D;HudMH zFM&I4_pkk>GO0G``Jq4MRlBzRG}pAbHLZoAmFaSmRjtmFU2-cYJ)S4}-=FdxocZF24X8V7tHkR`jDp0BQx8Ig}Qvzr$1bEEX znbsoaYPezd#y>kwZOU1%Q#-w^Ja_y2vgmB@_$y2vALQSfO^M%mXzp#Nds?3N11_0Q zh|kTvweMKKw!I!w4T+jR+_-)Pa>Ye|h`*d&U*F1bwy2DgkLCW9C{30XOXr{3)cg9o zsb_)84{hDM!A8vvnqPKba@Y`co3D>^iRHdYc_{(6!uvPszBO06r+IMyW1R9;(zS1PWltE7DN!jchZx=nQ-@W;^`R=npoON56WF|h4agqG9 zf;B+Jam&H}za|$p22N-zJ;N$dEMAaY9;|vY@4+$^|X>O1@ zF352~m2t~6&(#dgEUXd=4hL2SH!)pcZLr%C$8k$7;#zbGGccNUO{$DnXmFMD*2Gw` z7HuxO!!z@#yKyR$TFj9n@xozRm1RHYdhEOZ++okU?u7l@d%PGbTYjCZsrjnCU2?ZO z!}{fO?noYq74G)?BE?Zx>bTt5NA>aCW1j`LUpM7kl2gi^aPQs2-)bdNr<1&RS^uB< zab7}d2HP|DFInds>X~E2@3%~64noZba|6`k2(6(Rfji2`2`QvflJimzZN{328s)4)t)$VvGsg%=%<+zENiqqxtZoPRzEiH zzsIV{KSBHZlx*e9j%jC990EmzcNZIX=xf#A@;Vzozv9kBnerd~cJ&hvEzb4i2;iE* zedV{vr`Xv3keiR=raifJ`*9vGtRP`{tz%YrmN#K_2drpwBr>mdKI;Vst0HNq7 AK>z>% literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_setting_display_dark.png b/app/src/main/res/drawable-xxxhdpi/ic_setting_display_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..668042f19e498634226ea5664def8619cb1d43d2 GIT binary patch literal 757 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n5KKWIEGZ*dUIoK{%r?|0}q#YdS=YndU1-Ikyh?Yr|$0K z-=_2QPwkmfX0q6En$?X|Jg%*^NGFR#h>4~$N{vIL1N!W z!#gRl*FWzldHu%Y+gHVF>oRA}Wr#ETaysI4-H+odxF6VEv$`&EY$jiw=-bOcET}&_TU_QqfB4-b<{c7+ zfhi0hUqti$D19G)=GR`0cbpqcet)T+Xw+c-Y+jvy5Zi}qcP_s_+PPQ%w#nn_>aJ4j z3AOu9eeIlH%y8cKvJGG2ufkLH=f2+i!y@9xz~sQd!N91%z|z1Vz`#U1j=ss+ANRyc zW9yBS{826AOR##d|E>3Blk)Z-3wEswFRXmd*}%Z};^epG_q(Ss=A5>B6GlZKl@3ap!W{9x9wiFYFGZDO$Q#pc?>gJNL2R=TNt3`oS&)z4*} HQ$iB}X~j5# literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_setting_display_light.png b/app/src/main/res/drawable-xxxhdpi/ic_setting_display_light.png new file mode 100644 index 0000000000000000000000000000000000000000..027edf0b828040bdd286ef2d7e5eefd3443d5af7 GIT binary patch literal 728 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n5sNo978H@y}5ZX@V0|ULt_51aB&9KC5+|?@0KwvXL#<% z#UL*5u8?sd!;Z&HkJvOAL>DkkS-wEI{mg=t@LTU^-@JHM`Tg&S*V8rvZDe40rtkYu zZTGs}J*QpQe0BNuRc5uP=3cG`$cNz6%~)%y3WsU#@EUdS^{RmBZ_;n?eqJ_&xb=zt{fO+Fp)x zSJ?y(zqfx~@AqSh{ju#W`ht$LU$fbz-fM_GdN`iveQo$>;Rlt+)c@`OmnPufu=>?J z2E#wS5C3nJ`nG6Se?#xh=l8qkFxHrT|5L5Vz!jjU4dRhY=G;8q zFz@lERe#p+?>+n~bZz?qcK`o-sso%De2*T^U&W^&SDPF2PK)nB-i_v}diD-pi{noN z9)93ITz!7`|D|u-WH{D8E{eYRLEd}E#E?ttcm(g9`7zt^|Bn6osXvag8n_hftoVJm z)_U_pd)<5g>_0|b@?9EKp3RXco-O=3>Co~&!S$E)b3Ytq|KP_{%ksLDXP^BY)1`c} f3_!5rY(66!L+poU4n8@7bdZRrtDnm{r-UW|<)11t literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_setting_download_dark.png b/app/src/main/res/drawable-xxxhdpi/ic_setting_download_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..07a26998597f66ef51bb672cb705e98b496a24e7 GIT binary patch literal 684 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+nEX6l978H@y}7I1ClV;p_VD2%gCBkyk|%8C-H^k`|2si3 zB297*Z-V<4_XLA0DjrTd<^+F@pJ%@E%nGlkHRrd?T=M#v+awiFAPV{`ms;~8_ja6n z%{s$l%X89ynrPk$cbu;++i={DrJ5aRnF9j{1ET^1O9O)d0~4{F#M%FJ6K`+Zd$WE| z#GgCc9nXJ0{pk1oenY!+dmeV>bMC&qzMNg5_&ir_q!9u`s z=zih*Og$&C&Z-)I2AGW^!@%Hw%w>J+zRRKpJFb-RzKi)eK zeS8-%0Df2s~Z=T-G@yGywoG=Lc{A literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_setting_download_light.png b/app/src/main/res/drawable-xxxhdpi/ic_setting_download_light.png new file mode 100644 index 0000000000000000000000000000000000000000..f5b3e5948d21fa25e944e7e4cca654a9a08826b4 GIT binary patch literal 642 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_setting_read_light.png b/app/src/main/res/drawable-xxxhdpi/ic_setting_read_light.png new file mode 100644 index 0000000000000000000000000000000000000000..808871ce1e5bc139a67558b1d10e4c2800b43c4e GIT binary patch literal 1104 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m_K>CIEGZ*dNcQQ;cWu}2TuQWS`K;+yek;R6T}mCGu+d? zt1PyFX%Sn9Lr8;E0JENgo&&3a1B>>&jdctkBPYB+TTsy9#X z`setqZF{e8-?(oXPj11tUsdMQgR=C!7;HcK|N6FOJ?G~sEo=8(i)x7tWBhVt_SAhF zR?A&VX35yXQ9p@0A^flZGlwJB48HfWb)9_AyS1O?RPra^8~=}1boa2VUw8Z9+~dt+ zJzG88epMC!^)ByQ{lBxt--N%aPt8#IfN`?Uyy+TDH`Mq`_th-hsKl^vKhLqMjeSWR z4%ORb)C^e~NJs@;xsT4}Wpl{F#5eyffzqtwmv?oaV)qhy5K*U^QmOWGw+L$&kT3Y)b3@AJ<9R~1#mV!Z`*{9z&}5S7 z_qhJuwba5awq%Q@s=S6{!`04D*>Z&i`%SB@6zt?D=rw5W4^t1jzTdOUWJmAoeep$h z3Xu){-cP5`T~hfVeO~Ujk_iqaAhDZU4c7dx5ie+s&HeVpP=12efwPGb^>^PhOg(t^ z?7V$XnT1$y-0&=oOpMiTwhFiu9%HBA+o1P!dVcB#o~d7^&fxbDda(KC=I=5w^G_Yv zee?448iz>?$J6}V50##iSJcGwP{v~1y ziZ?Gm|KtRF5Z8uDL3Nzd6*BE6cr`pYJ8z!%1RZ$~MTaeE{68Hena+6pdh(2UBWuQz z>G96z+3Q^X+1qT({e2?jlw<;B9N*4nut;dU4^(P+ z`#fJV{$tK!j)X|#eUEOs2p@Q-Yke$2C;Phjx|&l5+PWOx+ literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/btn_check_material.xml b/app/src/main/res/drawable/btn_check_material.xml new file mode 100644 index 000000000..4376097c3 --- /dev/null +++ b/app/src/main/res/drawable/btn_check_material.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/btn_check_material_dark.xml b/app/src/main/res/drawable/btn_check_material_dark.xml new file mode 100644 index 000000000..fbe1b0664 --- /dev/null +++ b/app/src/main/res/drawable/btn_check_material_dark.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/btn_radio_material.xml b/app/src/main/res/drawable/btn_radio_material.xml new file mode 100644 index 000000000..b07ddb118 --- /dev/null +++ b/app/src/main/res/drawable/btn_radio_material.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/btn_radio_material_dark.xml b/app/src/main/res/drawable/btn_radio_material_dark.xml new file mode 100644 index 000000000..8313f13aa --- /dev/null +++ b/app/src/main/res/drawable/btn_radio_material_dark.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/clickable_bg_purple.xml b/app/src/main/res/drawable/clickable_bg_purple.xml new file mode 100644 index 000000000..0065b845f --- /dev/null +++ b/app/src/main/res/drawable/clickable_bg_purple.xml @@ -0,0 +1,32 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dialog_background_material.xml b/app/src/main/res/drawable/dialog_background_material.xml new file mode 100644 index 000000000..3039bdb3e --- /dev/null +++ b/app/src/main/res/drawable/dialog_background_material.xml @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/dialog_background_material_dark.xml b/app/src/main/res/drawable/dialog_background_material_dark.xml new file mode 100644 index 000000000..cc7489f30 --- /dev/null +++ b/app/src/main/res/drawable/dialog_background_material_dark.xml @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_setting_about.xml b/app/src/main/res/drawable/ic_setting_about.xml new file mode 100644 index 000000000..8ce846851 --- /dev/null +++ b/app/src/main/res/drawable/ic_setting_about.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_setting_advanced.xml b/app/src/main/res/drawable/ic_setting_advanced.xml new file mode 100644 index 000000000..016800879 --- /dev/null +++ b/app/src/main/res/drawable/ic_setting_advanced.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_setting_display.xml b/app/src/main/res/drawable/ic_setting_display.xml new file mode 100644 index 000000000..fba817a30 --- /dev/null +++ b/app/src/main/res/drawable/ic_setting_display.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_setting_download.xml b/app/src/main/res/drawable/ic_setting_download.xml new file mode 100644 index 000000000..be0733332 --- /dev/null +++ b/app/src/main/res/drawable/ic_setting_download.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_setting_panda.xml b/app/src/main/res/drawable/ic_setting_panda.xml new file mode 100644 index 000000000..cba077c32 --- /dev/null +++ b/app/src/main/res/drawable/ic_setting_panda.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_setting_read.xml b/app/src/main/res/drawable/ic_setting_read.xml new file mode 100644 index 000000000..a293671cb --- /dev/null +++ b/app/src/main/res/drawable/ic_setting_read.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/textfield_searchview.xml b/app/src/main/res/drawable/textfield_searchview.xml new file mode 100644 index 000000000..6d8d9cb1e --- /dev/null +++ b/app/src/main/res/drawable/textfield_searchview.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/textfield_searchview_right.xml b/app/src/main/res/drawable/textfield_searchview_right.xml new file mode 100644 index 000000000..98e361eb8 --- /dev/null +++ b/app/src/main/res/drawable/textfield_searchview_right.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/start_activity.xml b/app/src/main/res/layout-land/start_activity.xml new file mode 100644 index 000000000..dc5abe85c --- /dev/null +++ b/app/src/main/res/layout-land/start_activity.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/actionable_toast_row.xml b/app/src/main/res/layout/actionable_toast_row.xml new file mode 100644 index 000000000..f91745c90 --- /dev/null +++ b/app/src/main/res/layout/actionable_toast_row.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/add_to_favorite.xml b/app/src/main/res/layout/add_to_favorite.xml new file mode 100644 index 000000000..b0c6ccac1 --- /dev/null +++ b/app/src/main/res/layout/add_to_favorite.xml @@ -0,0 +1,39 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/alert_dialog_material.xml b/app/src/main/res/layout/alert_dialog_material.xml new file mode 100644 index 000000000..5a01244f1 --- /dev/null +++ b/app/src/main/res/layout/alert_dialog_material.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    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%0ACYjanZqGq@A7LLL1CR<(1D>3` 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

    mKXyHInZ=TpxsvLdSm~S#tm}gvvl*HUc#C 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 literal 0 HcmV?d00001 diff --git a/app/src/main/jniLibs/armeabi/libimage.so b/app/src/main/jniLibs/armeabi/libimage.so new file mode 100644 index 0000000000000000000000000000000000000000..ccfbde9f5f6ab0edd7f2b059bdb9f81e9b434e51 GIT binary patch literal 287996 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 Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..75b0999ea --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/assets/fonts/GloriaHallelujah.ttf b/app/src/main/assets/fonts/GloriaHallelujah.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3b42f5b385f0f38da4dd1a9f3e9a7d0ed4f57e95 GIT binary patch literal 60052 zcmd?ScYq{UbuWDHtTCna#V}piNpyD-;nDNCG27mO%zg zHaRR8%z?x7z}3X``zl9MezI{-}}Dz-aOmtzW3a7f9IUv zIk!Y8A;d-QCIY#B=SqFxwtV0RgwR`Y_UPW(#kE_~Pn#yh^i)EmuU~WF*!A5*g)u@L z2MG~Aam~#)B}hOD65_p#&wEc@fBJ&_C$H!c;>q9|mrftMbiJ+akH35E$zvyeaVh;ny!T&tbg#t;>j%WU@!d9FXRf_))2)T?sSK~bgx`Pt z`HR;a`^X2s{V*YKdK)3^ix-aFdcANr`!wEP!{-y%9lLPyvkyLAA>^&NM(^#{U%YhF z17iI@3Heq7V>odAjVG^<=y$#XzyAy&n}RlAP^sW0w3V#_Yoi7Bg~Hn z)31>4k|<+j2l*CRMIVc7jrhn9@cD;`gJJgA1^rj_hxpEyaBhug{G9Px!*@SM%=B$I z?7Tne>l;#E<&dsiyw`VDf~|0&wGp@e zpLo}U)SyTWLFB&z>AQAtey8G0Q@>nBP5 zrzAj9XoDo9|Ad4{R{t>xlN{OzDd5n`BP^78xK5Xj^29{!P**+w^ac4%v>jOLpjACq1%+c987Uzea}0GTLFX zTmNS=LRR#zl2NjXc8u)R|A~x~HMA3CUH=NKhKWSSh(zYO2w2-;b4RR0p0 zBgfFrlWX*UAPeLK+AZXi{y5o6PV0Y9wvlVmZYO8;FJku3pPla-IHnWGA^E z?JjbI{*#Ss_nByNBGO{|#{YR{gKZUUD1SedG@Pb7YM?7415Cn*LX0 zKe-F-0dlwgS#pp(L;nmpMD9U*nB1p-nj9g|M0=DxOaBx(MxKrKIJsZ{B)Nt>pnrm# zAkRg6k~~j;jGQ7bKzo|JQ2#i&mb_U1OLB%hi1sY`WBpNbj=U7@dGe6{F>-;t4DEH~ z<@!g-Me+*$BUllyM0*2ymHuIJBY6$lOXRishsaIj^=O|${!ITMxtY8{{{Xp#yb0~C z^H@&*0P(Eb_uuKsHB2J$_$ZzSK>Uq#+T zeu(zX-1`k#`wlAoY`8~LgJ3i5XH?`Ypaey+cqyp#Ng{xWiz{1WZE$bag8 zLf%b&jrPyUf9VgAhskfyzK7`gOUZjF(O*K|M;Y2jsG$Ebc|R4={soow2gwJh3GD}| zMSn5*5Vh(rA|Ix9v>%}k{e|SC)QR?E)UCgOJW4%i|B@>D^U24lPk$bHjH+lqLH#-; zO9N;>MML@nNk@o=q|KBrn~j0 zke|>M{U-8Lx{CH^bgzDi{5#!;_UCk6zmfb0-H-Mc^q_tN`6WH1Ur+v%9!C2sdQ`ti zeoc>|{V#frejWJ@J)vJ9SLjK!Iz6q!qb4vr{J|av((}IvUf~%$Cz*amv;bkhjx!?S z0GXJ8{}$lB4S4SW&bxr`9^kqHJXe9^e&BZyxE;n-qQL1mzMTXvr-8>=;BX%JTLkWw zfwxuQY#sR81g^G#rybyE5BNC*+#CU3jsYhpfR9ta#Tnq?9B^;}__r0fw;g!52%Osq zd|L*ttpLwffn)oCU+ciF1Hh|8z^Nm^r(+n?GqHM~jg|UhtglyMg}fH);|*9HZ^TM? zE7rl=p!x5F-d~2M|2ee!Ve(#htnVWqBp)Cjg1+tt_M8BkouLPTFs}hBoukKrJ7;n2 zKf*dCKqdZn8YuMJzwBWq=$f(&j7x>lsc)4?xrhrA6=t+ z=qh9dYB%er|Gry0J)9srR#X-ULegqz?u7jFE0SDJP#Q15}bb!2=S-D zil5SHx`o_8XXzZBC%4nBbb-!b_I`}d3}QA!%+S+lg*Isq?>&vai0UlJKFPisyD#>$ zgf9_F#1g4QE-{>#O}s6cN_lfq?&N>`<*yl{WPgsSg)2|Ja{im2`R3toOnhVX8zbKs z{zmT`*>6O?{+_Q-er@?HN4`R=uai%0=p%mifBWzMw(&w)NoD&Z#IkGcU6ej={Sm6) z_XxQ+`hKid;mG0YBZO8GiMcbgm+4WwU=^GwCGkwCB<3#*+4