From 1633bc06fef06f613f652fbdf68e77d049c11c10 Mon Sep 17 00:00:00 2001 From: Artem Umerov Date: Wed, 20 Nov 2024 23:45:07 +0300 Subject: [PATCH] 74 74 --- app_fenrir/src/main/AndroidManifest.xml | 1 - .../kotlin/dev/ragnarok/fenrir/Constants.kt | 6 +- .../dev/ragnarok/fenrir/UserAgentTool.kt | 16 +- .../fenrir/activity/LottieActivity.kt | 103 +- .../ragnarok/fenrir/activity/MainActivity.kt | 4 + .../fenrir/activity/SinglePhotoActivity.kt | 23 +- .../activity/gifpager/GifPagerActivity.kt | 5 +- .../activity/photopager/PhotoPagerActivity.kt | 34 +- .../ShortVideoPagerActivity.kt | 35 +- .../activity/storypager/StoryPagerActivity.kt | 45 +- .../ragnarok/fenrir/api/impl/AccountApi.kt | 6 +- .../dev/ragnarok/fenrir/api/impl/AuthApi.kt | 59 +- .../fenrir/api/impl/StoriesShortVideosApi.kt | 16 + .../dev/ragnarok/fenrir/api/impl/VKApies.kt | 17 +- .../fenrir/api/interfaces/IAccountApi.kt | 4 +- .../fenrir/api/interfaces/IAuthApi.kt | 29 +- .../fenrir/api/interfaces/IStatusApi.kt | 2 +- .../api/interfaces/IStoriesShortVideosApi.kt | 6 + .../AnonymTokenResponse.kt} | 4 +- .../response/GetAuthCodeStatusResponse.kt | 19 + .../response/SetAuthCodeStatusResponse.kt | 22 + .../api/model/response/StoriesResponse.kt | 2 +- ...BlockResponce.kt => StoryBlockResponse.kt} | 4 +- .../fenrir/api/services/IAccountService.kt | 7 +- .../fenrir/api/services/IAuthService.kt | 52 +- .../fenrir/api/services/IStatusService.kt | 2 +- .../services/IStoriesShortVideosService.kt | 10 + .../fenrir/db/impl/TempDataStorage.kt | 2 +- .../ragnarok/fenrir/db/model/IdPairEntity.kt | 2 +- .../db/model/entity/ArticleDboEntity.kt | 2 +- .../db/model/entity/AudioArtistDboEntity.kt | 2 +- .../fenrir/db/model/entity/AudioDboEntity.kt | 2 +- .../db/model/entity/AudioMessageDboEntity.kt | 2 +- .../fenrir/db/model/entity/CommentEntity.kt | 2 +- .../db/model/entity/CommunityDetailsEntity.kt | 4 +- .../db/model/entity/CountryDboEntity.kt | 2 +- .../db/model/entity/DocumentDboEntity.kt | 6 +- .../db/model/entity/MarketAlbumDboEntity.kt | 2 +- .../fenrir/db/model/entity/MarketDboEntity.kt | 2 +- .../db/model/entity/MessageDboEntity.kt | 2 +- .../fenrir/db/model/entity/PageDboEntity.kt | 2 +- .../db/model/entity/PhotoAlbumDboEntity.kt | 2 +- .../fenrir/db/model/entity/PhotoDboEntity.kt | 2 +- .../fenrir/db/model/entity/PollDboEntity.kt | 4 +- .../fenrir/db/model/entity/PostDboEntity.kt | 4 +- .../fenrir/db/model/entity/PrivacyEntity.kt | 4 +- .../db/model/entity/StickerDboEntity.kt | 4 +- .../db/model/entity/StickerSetEntity.kt | 2 +- .../fenrir/db/model/entity/TopicDboEntity.kt | 2 +- .../db/model/entity/UserDetailsEntity.kt | 2 +- .../fenrir/db/model/entity/VideoDboEntity.kt | 2 +- .../audioduplicate/AudioDuplicateDialog.kt | 4 +- .../domain/IStoriesShortVideosInteractor.kt | 3 + .../impl/StoriesShortVideosInteractor.kt | 16 +- .../fenrir/fragment/PreferencesFragment.kt | 11 +- .../fragment/accounts/AccountAdapter.kt | 14 +- .../fragment/accounts/AccountsFragment.kt | 44 + .../fragment/accounts/AccountsPresenter.kt | 139 +- .../fragment/audio/AudioPlayerFragment.kt | 24 +- .../audio/audios/AudioRecyclerAdapter.kt | 4 +- .../catalog_v2/lists/CatalogV2ListFragment.kt | 18 +- .../sections/CatalogV2SectionAdapter.kt | 4 +- .../audioslocal/AudioLocalRecyclerAdapter.kt | 4 +- .../LocalAudioAlbumsAdapter.kt | 14 +- .../fragment/base/AttachmentsViewBinder.kt | 11 +- .../fragment/base/RecyclerBindableAdapter.kt | 2 +- .../fragment/base/compat/ViewHostDelegate.kt | 4 +- .../fave/favephotos/FavePhotosAdapter.kt | 13 +- .../FileManagerSelectFragment.kt | 19 +- .../AudioLocalServerRecyclerAdapter.kt | 4 +- .../FileManagerRemoteAdapter.kt | 26 +- .../FileManagerRemoteFragment.kt | 19 +- .../LocalServerPhotosAdapter.kt | 13 +- .../fragment/messages/chat/ChatFragment.kt | 13 +- .../fragment/messages/chat/MessagesAdapter.kt | 7 +- .../LocalJsonToChatFragment.kt | 18 +- .../photos/vkphotos/BigVKPhotosAdapter.kt | 13 +- .../search/photosearch/SearchPhotosAdapter.kt | 13 +- .../fenrir/fragment/theme/ThemeAdapter.kt | 25 +- .../videopreview/VideoPreviewFragment.kt | 2 +- .../fenrir/fragment/wall/AbsWallFragment.kt | 12 +- .../wall/groupwall/GroupWallFragment.kt | 33 +- .../wall/groupwall/GroupWallPresenter.kt | 6 + .../wall/userwall/UserWallFragment.kt | 37 +- .../wall/userwall/UserWallPresenter.kt | 6 + .../wall/wallpost/WallPostFragment.kt | 21 +- .../wall/wallpost/WallPostPresenter.kt | 2 +- .../dev/ragnarok/fenrir/model/Account.kt | 3 +- .../dev/ragnarok/fenrir/model/AppChatUser.kt | 12 +- .../dev/ragnarok/fenrir/model/Banned.kt | 2 +- .../dev/ragnarok/fenrir/model/Dialog.kt | 11 +- .../kotlin/dev/ragnarok/fenrir/model/Event.kt | 7 +- .../dev/ragnarok/fenrir/model/Message.kt | 10 +- .../kotlin/dev/ragnarok/fenrir/model/News.kt | 4 +- .../kotlin/dev/ragnarok/fenrir/model/Owner.kt | 22 - .../kotlin/dev/ragnarok/fenrir/model/Post.kt | 4 +- .../dev/ragnarok/fenrir/model/ProxyConfig.kt | 2 +- .../dev/ragnarok/fenrir/model/SaveAccount.kt | 2 +- .../kotlin/dev/ragnarok/fenrir/model/Topic.kt | 10 +- .../dev/ragnarok/fenrir/model/UserDetails.kt | 4 +- .../model/catalog_v2_audio/CatalogV2List.kt | 9 + .../CatalogV2RecommendationPlaylist.kt | 2 +- .../fenrir/push/PushRegistrationResolver.kt | 4 + .../fenrir/settings/AccountsSettings.kt | 18 + .../ragnarok/fenrir/settings/AnonymToken.kt | 22 + .../dev/ragnarok/fenrir/settings/ISettings.kt | 2 + .../fenrir/settings/VKPushRegistration.kt | 2 +- .../kotlin/dev/ragnarok/fenrir/util/Utils.kt | 34 +- .../ragnarok/fenrir/util/spots/SpotsDialog.kt | 9 +- .../ragnarok/fenrir/view/AudioContainer.kt | 4 +- .../fenrir/view/LoadMoreFooterHelper.kt | 21 +- .../ragnarok/fenrir/view/PhotosViewHelper.kt | 6 +- .../ragnarok/fenrir/view/ProgressButton.kt | 55 +- .../ragnarok/fenrir/view/ReactionContainer.kt | 9 +- .../ragnarok/fenrir/view/TouchImageView.kt | 350 +- .../fenrir/view/emoji/MyStickersAdapter.kt | 12 +- .../fenrir/view/emoji/StickersAdapter.kt | 19 +- .../fenrir/view/media/PathAnimator.kt | 44 +- .../animation/AnimatedShapeableImageView.kt | 306 +- .../ThorVGLottieNetworkCache.kt} | 10 +- .../animation/ThorVGLottieShapeableView.kt | 366 + .../natives/animation/ThorVGLottieView.kt | 362 + .../view/natives/rlottie/RLottieImageView.kt | 294 - .../rlottie/RLottieShapeableImageView.kt | 297 - .../view/navigation/SideNavigationView.kt | 20 +- .../src/main/res/layout/activity_lottie.xml | 6 +- .../main/res/layout/activity_photo_pager.xml | 2 +- .../res/layout/activity_shortvideo_pager.xml | 4 +- .../main/res/layout/activity_story_pager.xml | 2 +- .../main/res/layout/content_photo_page.xml | 2 +- .../res/layout/content_shortvideo_page.xml | 2 +- .../main/res/layout/content_story_page.xml | 2 +- .../src/main/res/layout/dialog_about_us.xml | 2 +- .../src/main/res/layout/dialog_progress.xml | 2 +- .../src/main/res/layout/footer_load_more.xml | 2 +- .../res/layout/fragment_catalog_v2_list.xml | 2 +- .../src/main/res/layout/fragment_chat.xml | 2 +- .../layout/fragment_file_remote_explorer.xml | 2 +- .../layout/fragment_file_select_explorer.xml | 2 +- .../main/res/layout/fragment_json_chat.xml | 2 +- .../src/main/res/layout/fragment_post.xml | 2 +- .../src/main/res/layout/header_group.xml | 6 +- .../main/res/layout/header_user_profile.xml | 6 +- .../src/main/res/layout/item_account.xml | 2 +- app_fenrir/src/main/res/layout/item_audio.xml | 2 +- .../res/layout/item_audio_local_server.xml | 2 +- .../src/main/res/layout/item_fave_photo.xml | 2 +- .../src/main/res/layout/item_local_audio.xml | 2 +- .../res/layout/item_local_server_photo.xml | 2 +- .../main/res/layout/item_manager_audio.xml | 4 +- .../src/main/res/layout/item_manager_file.xml | 2 +- .../main/res/layout/item_manager_folder.xml | 2 +- .../layout/item_message_friend_sticker.xml | 2 +- .../res/layout/item_message_my_sticker.xml | 2 +- .../src/main/res/layout/item_reaction.xml | 2 +- .../main/res/layout/item_special_theme.xml | 2 +- app_fenrir/src/main/res/layout/item_theme.xml | 2 +- .../res/layout/local_audio_album_item.xml | 2 +- .../main/res/layout/player_cover_picture.xml | 2 +- .../main/res/layout/side_header_navi_menu.xml | 2 +- .../res/layout/sticker_grid_item_animated.xml | 7 +- .../src/main/res/layout/vk_photo_item.xml | 2 +- .../src/main/res/layout/vk_photo_item_big.xml | 2 +- .../src/main/res/menu/menu_accounts.xml | 18 +- .../src/main/res/values-night-v31/styles.xml | 1 + app_fenrir/src/main/res/values-ru/strings.xml | 5 +- app_fenrir/src/main/res/values-v31/styles.xml | 1 + app_fenrir/src/main/res/values/attrs.xml | 8 +- app_fenrir/src/main/res/values/strings.xml | 3 + .../activity/photopager/PhotoPagerActivity.kt | 36 +- .../fragment/AudioPlayerFragment.kt | 42 +- .../fragment/PreferencesFragment.kt | 8 +- .../fragment/base/compat/ViewHostDelegate.kt | 4 +- .../filemanager/FileManagerAdapter.kt | 26 +- .../filemanager/FileManagerFragment.kt | 19 +- .../FileManagerSelectFragment.kt | 19 +- .../AudioLocalServerRecyclerAdapter.kt | 4 +- .../FileManagerRemoteAdapter.kt | 26 +- .../FileManagerRemoteFragment.kt | 19 +- .../LocalServerPhotosAdapter.kt | 13 +- .../fragment/tagdir/TagDirAdapter.kt | 26 +- .../fragment/theme/ThemeAdapter.kt | 25 +- .../dev/ragnarok/filegallery/util/Utils.kt | 30 +- .../filegallery/view/TouchImageView.kt | 338 +- .../filegallery/view/media/PathAnimator.kt | 42 +- .../animation/AnimatedShapeableImageView.kt | 306 +- .../ThorVGLottieNetworkCache.kt} | 10 +- .../animation/ThorVGLottieShapeableView.kt | 366 + .../natives/animation/ThorVGLottieView.kt | 362 + .../view/natives/rlottie/RLottieImageView.kt | 294 - .../rlottie/RLottieShapeableImageView.kt | 297 - .../main/res/layout/activity_photo_pager.xml | 2 +- .../main/res/layout/content_photo_page.xml | 2 +- .../src/main/res/layout/dialog_about_us.xml | 2 +- .../res/layout/fragment_file_explorer.xml | 2 +- .../layout/fragment_file_remote_explorer.xml | 2 +- .../layout/fragment_file_select_explorer.xml | 2 +- .../res/layout/item_audio_local_server.xml | 2 +- .../res/layout/item_local_server_photo.xml | 2 +- .../main/res/layout/item_manager_audio.xml | 4 +- .../src/main/res/layout/item_manager_file.xml | 2 +- .../main/res/layout/item_manager_folder.xml | 2 +- .../main/res/layout/item_special_theme.xml | 2 +- .../src/main/res/layout/item_theme.xml | 2 +- .../main/res/layout/player_cover_picture.xml | 2 +- .../src/main/res/values-night-v31/styles.xml | 1 + .../src/main/res/values-v31/styles.xml | 1 + app_filegallery/src/main/res/values/attrs.xml | 8 +- build.gradle | 34 +- camera2/build.gradle | 2 +- .../main/kotlin/dev/ragnarok/fenrir/Common.kt | 9 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../minetsh/imaging/core/homing/IMGHoming.kt | 2 +- .../imaging/core/homing/IMGHomingEvaluator.kt | 2 +- libfenrir/build.gradle | 2 +- libfenrir/ffmpeg.sh | 2 +- .../result/EmailDoCoMoResultParser.java | 8 +- .../google/zxing/pdf417/encoder/PDF417.java | 18 +- .../encoder/PDF417HighLevelEncoder.java | 29 +- libfenrir/src/main/jni/CMakeLists.txt | 61 +- .../src/main/jni/animation/gif_builder.h | 836 -- .../animation/libyuv/include/libyuv/cpu_id.h | 71 +- .../jni/animation/libyuv/include/libyuv/row.h | 117 + .../libyuv/include/libyuv/scale_row.h | 37 +- .../animation/libyuv/include/libyuv/version.h | 2 +- .../animation/libyuv/source/convert_argb.cc | 110 + .../jni/animation/libyuv/source/cpu_id.cc | 23 +- .../libyuv/source/planar_functions.cc | 37 +- .../jni/animation/libyuv/source/rotate.cc | 5 + .../animation/libyuv/source/rotate_argb.cc | 5 + .../jni/animation/libyuv/source/row_any.cc | 20 + .../jni/animation/libyuv/source/row_gcc.cc | 159 +- .../jni/animation/libyuv/source/row_neon64.cc | 54 +- .../jni/animation/libyuv/source/row_sme.cc | 225 + .../jni/animation/libyuv/source/row_sve.cc | 699 ++ .../main/jni/animation/libyuv/source/scale.cc | 7 + .../jni/animation/libyuv/source/scale_argb.cc | 5 + .../animation/libyuv/source/scale_neon64.cc | 498 +- .../jni/animation/libyuv/source/scale_sme.cc | 292 + .../jni/animation/libyuv/source/scale_uv.cc | 20 +- .../src/main/jni/animation/lottie_jni.cpp | 337 - .../jni/animation/rlottie/inc/colorreplace.h | 23 - .../main/jni/animation/rlottie/inc/rlottie.h | 456 - .../jni/animation/rlottie/inc/rlottiecommon.h | 218 - .../jni/animation/rlottie/rlottie-master.zip | Bin 4303852 -> 0 bytes .../rlottie/src/lottie/lottieanimation.cpp | 303 - .../rlottie/src/lottie/lottiefiltermodel.h | 453 - .../rlottie/src/lottie/lottieitem.cpp | 1558 ---- .../animation/rlottie/src/lottie/lottieitem.h | 637 -- .../rlottie/src/lottie/lottieitem_capi.cpp | 339 - .../rlottie/src/lottie/lottiekeypath.cpp | 86 - .../rlottie/src/lottie/lottiekeypath.h | 53 - .../rlottie/src/lottie/lottieloader.cpp | 40 - .../rlottie/src/lottie/lottiemodel.cpp | 379 - .../rlottie/src/lottie/lottiemodel.h | 1344 --- .../rlottie/src/lottie/lottieparser.cpp | 2853 ------- .../jni/animation/rlottie/src/vector/config.h | 13 - .../rlottie/src/vector/freetype/v_ft_math.cpp | 461 - .../rlottie/src/vector/freetype/v_ft_math.h | 438 - .../src/vector/freetype/v_ft_raster.cpp | 1423 ---- .../rlottie/src/vector/freetype/v_ft_raster.h | 607 -- .../src/vector/freetype/v_ft_stroker.cpp | 1936 ----- .../src/vector/freetype/v_ft_stroker.h | 319 - .../rlottie/src/vector/freetype/v_ft_types.h | 160 - .../rlottie/src/vector/stb/stb_image.cpp | 51 - .../rlottie/src/vector/stb/stb_image.h | 7517 ----------------- .../rlottie/src/vector/varenaalloc.cpp | 166 - .../rlottie/src/vector/varenaalloc.h | 232 - .../animation/rlottie/src/vector/vbezier.cpp | 135 - .../animation/rlottie/src/vector/vbezier.h | 139 - .../animation/rlottie/src/vector/vbitmap.cpp | 219 - .../animation/rlottie/src/vector/vbitmap.h | 97 - .../animation/rlottie/src/vector/vbrush.cpp | 69 - .../jni/animation/rlottie/src/vector/vbrush.h | 92 - .../animation/rlottie/src/vector/vcowptr.h | 126 - .../animation/rlottie/src/vector/vdasher.cpp | 254 - .../animation/rlottie/src/vector/vdasher.h | 64 - .../animation/rlottie/src/vector/vdebug.cpp | 758 -- .../jni/animation/rlottie/src/vector/vdebug.h | 187 - .../rlottie/src/vector/vdrawable.cpp | 133 - .../animation/rlottie/src/vector/vdrawable.h | 93 - .../rlottie/src/vector/vdrawhelper.cpp | 769 -- .../rlottie/src/vector/vdrawhelper.h | 269 - .../rlottie/src/vector/vdrawhelper_common.cpp | 189 - .../rlottie/src/vector/vdrawhelper_neon.cpp | 290 - .../rlottie/src/vector/vdrawhelper_sse2.cpp | 261 - .../rlottie/src/vector/velapsedtimer.cpp | 50 - .../rlottie/src/vector/velapsedtimer.h | 41 - .../animation/rlottie/src/vector/vglobal.h | 305 - .../rlottie/src/vector/vimageloader.cpp | 233 - .../rlottie/src/vector/vimageloader.h | 26 - .../rlottie/src/vector/vinterpolator.cpp | 124 - .../rlottie/src/vector/vinterpolator.h | 86 - .../jni/animation/rlottie/src/vector/vline.h | 97 - .../animation/rlottie/src/vector/vmatrix.cpp | 684 -- .../animation/rlottie/src/vector/vmatrix.h | 115 - .../animation/rlottie/src/vector/vpainter.cpp | 174 - .../animation/rlottie/src/vector/vpainter.h | 60 - .../animation/rlottie/src/vector/vpath.cpp | 709 -- .../jni/animation/rlottie/src/vector/vpath.h | 285 - .../rlottie/src/vector/vpathmesure.cpp | 67 - .../jni/animation/rlottie/src/vector/vpoint.h | 210 - .../animation/rlottie/src/vector/vraster.cpp | 605 -- .../animation/rlottie/src/vector/vrect.cpp | 68 - .../jni/animation/rlottie/src/vector/vrect.h | 172 - .../jni/animation/rlottie/src/vector/vrle.cpp | 763 -- .../jni/animation/rlottie/src/vector/vrle.h | 121 - .../animation/rlottie/src/vector/vsharedptr.h | 123 - .../rlottie/src/vector/vstackallocator.h | 156 - .../animation/rlottie/src/vector/vtaskqueue.h | 87 - .../jni/animation/thorvg/inc/colorreplace.h | 213 + .../main/jni/animation/thorvg/inc/thorvg.h | 302 +- .../thorvg/src/common/tvgCompressor.cpp | 183 +- .../thorvg/src/common/tvgCompressor.h | 1 - .../animation/thorvg/src/common/tvgMath.cpp | 9 + .../jni/animation/thorvg/src/common/tvgMath.h | 64 +- .../animation/thorvg/src/common/tvgStr.cpp | 12 + .../jni/animation/thorvg/src/common/tvgStr.h | 7 +- .../loaders}/lottie/rapidjson/allocators.h | 0 .../lottie/rapidjson/cursorstreamwrapper.h | 0 .../src/loaders}/lottie/rapidjson/document.h | 0 .../loaders}/lottie/rapidjson/encodedstream.h | 0 .../src/loaders}/lottie/rapidjson/encodings.h | 0 .../src/loaders}/lottie/rapidjson/error/en.h | 0 .../loaders}/lottie/rapidjson/error/error.h | 0 .../lottie/rapidjson/filereadstream.h | 0 .../lottie/rapidjson/filewritestream.h | 0 .../src/loaders}/lottie/rapidjson/fwd.h | 0 .../lottie/rapidjson/internal/biginteger.h | 0 .../lottie/rapidjson/internal/clzll.h | 0 .../lottie/rapidjson/internal/diyfp.h | 0 .../loaders}/lottie/rapidjson/internal/dtoa.h | 0 .../lottie/rapidjson/internal/ieee754.h | 0 .../loaders}/lottie/rapidjson/internal/itoa.h | 0 .../loaders}/lottie/rapidjson/internal/meta.h | 0 .../lottie/rapidjson/internal/pow10.h | 0 .../lottie/rapidjson/internal/regex.h | 0 .../lottie/rapidjson/internal/stack.h | 0 .../lottie/rapidjson/internal/strfunc.h | 0 .../lottie/rapidjson/internal/strtod.h | 0 .../loaders}/lottie/rapidjson/internal/swap.h | 0 .../lottie/rapidjson/istreamwrapper.h | 0 .../loaders}/lottie/rapidjson/memorybuffer.h | 0 .../loaders}/lottie/rapidjson/memorystream.h | 0 .../lottie/rapidjson/msinttypes/inttypes.h | 0 .../lottie/rapidjson/msinttypes/stdint.h | 0 .../lottie/rapidjson/ostreamwrapper.h | 0 .../src/loaders}/lottie/rapidjson/pointer.h | 0 .../loaders}/lottie/rapidjson/prettywriter.h | 0 .../src/loaders}/lottie/rapidjson/rapidjson.h | 0 .../src/loaders}/lottie/rapidjson/reader.h | 0 .../src/loaders}/lottie/rapidjson/schema.h | 0 .../src/loaders}/lottie/rapidjson/stream.h | 0 .../loaders}/lottie/rapidjson/stringbuffer.h | 0 .../src/loaders}/lottie/rapidjson/uri.h | 0 .../src/loaders}/lottie/rapidjson/writer.h | 0 .../thorvg/src/loaders/lottie/thorvg_lottie.h | 94 + .../src/loaders/lottie/tvgLottieAnimation.cpp | 88 + .../src/loaders/lottie/tvgLottieBuilder.cpp | 1498 ++++ .../src/loaders/lottie/tvgLottieBuilder.h | 134 + .../src/loaders/lottie/tvgLottieCommon.h | 98 + .../loaders/lottie/tvgLottieExpressions.cpp | 1423 ++++ .../src/loaders/lottie/tvgLottieExpressions.h | 168 + .../loaders/lottie/tvgLottieInterpolator.cpp | 140 + .../loaders/lottie/tvgLottieInterpolator.h} | 35 +- .../src/loaders/lottie/tvgLottieLoader.cpp | 425 + .../src/loaders/lottie/tvgLottieLoader.h | 85 + .../src/loaders/lottie/tvgLottieModel.cpp | 505 ++ .../src/loaders/lottie/tvgLottieModel.h | 925 ++ .../src/loaders/lottie/tvgLottieModifier.cpp | 395 + .../src/loaders/lottie/tvgLottieModifier.h | 71 + .../src/loaders/lottie/tvgLottieParser.cpp | 1564 ++++ .../src/loaders/lottie/tvgLottieParser.h | 131 + .../loaders/lottie/tvgLottieParserHandler.cpp | 233 + .../loaders/lottie/tvgLottieParserHandler.h | 200 + .../src/loaders/lottie/tvgLottieProperty.h | 837 ++ .../loaders/lottie/tvgLottieRenderPooler.h} | 54 +- .../thorvg/src/loaders/raw/tvgRawLoader.cpp | 6 +- .../thorvg/src/loaders/raw/tvgRawLoader.h | 2 +- .../thorvg/src/loaders/svg/tvgSvgLoader.cpp | 146 +- .../thorvg/src/loaders/svg/tvgSvgLoader.h | 4 +- .../thorvg/src/loaders/svg/tvgSvgPath.cpp | 28 - .../src/loaders/svg/tvgSvgSceneBuilder.cpp | 447 +- .../thorvg/src/loaders/svg/tvgXmlParser.cpp | 7 +- .../animation/thorvg/src/renderer/config.h | 3 + .../src/renderer/sw_engine/tvgSwCommon.h | 23 +- .../src/renderer/sw_engine/tvgSwFill.cpp | 63 +- .../src/renderer/sw_engine/tvgSwMath.cpp | 13 +- .../renderer/sw_engine/tvgSwPostEffect.cpp | 317 +- .../src/renderer/sw_engine/tvgSwRaster.cpp | 76 +- .../src/renderer/sw_engine/tvgSwRasterC.h | 32 + .../renderer/sw_engine/tvgSwRasterTexmap.h | 4 +- .../src/renderer/sw_engine/tvgSwRenderer.cpp | 75 +- .../src/renderer/sw_engine/tvgSwRenderer.h | 4 +- .../src/renderer/sw_engine/tvgSwRle.cpp | 194 +- .../src/renderer/sw_engine/tvgSwShape.cpp | 1 - .../thorvg/src/renderer/tvgAccessor.cpp | 14 - .../thorvg/src/renderer/tvgBinaryDesc.h | 100 - .../thorvg/src/renderer/tvgCanvas.cpp | 10 +- .../animation/thorvg/src/renderer/tvgCanvas.h | 14 +- .../animation/thorvg/src/renderer/tvgCommon.h | 3 +- .../animation/thorvg/src/renderer/tvgFill.cpp | 37 +- .../animation/thorvg/src/renderer/tvgFill.h | 12 +- .../thorvg/src/renderer/tvgInitializer.cpp | 16 +- .../thorvg/src/renderer/tvgLoadModule.h | 4 +- .../thorvg/src/renderer/tvgLoader.cpp | 113 +- .../animation/thorvg/src/renderer/tvgLoader.h | 12 +- .../thorvg/src/renderer/tvgPaint.cpp | 78 +- .../animation/thorvg/src/renderer/tvgPaint.h | 50 +- .../thorvg/src/renderer/tvgPicture.cpp | 36 +- .../thorvg/src/renderer/tvgPicture.h | 12 +- .../animation/thorvg/src/renderer/tvgRender.h | 114 +- .../thorvg/src/renderer/tvgSaveModule.h | 2 +- .../thorvg/src/renderer/tvgSaver.cpp | 36 +- .../thorvg/src/renderer/tvgScene.cpp | 18 +- .../animation/thorvg/src/renderer/tvgScene.h | 12 +- .../thorvg/src/renderer/tvgShape.cpp | 92 +- .../animation/thorvg/src/renderer/tvgShape.h | 29 +- .../thorvg/src/renderer/tvgSwCanvas.cpp | 5 +- .../animation/thorvg/src/renderer/tvgText.cpp | 12 +- .../animation/thorvg/src/renderer/tvgText.h | 2 +- .../animation/thorvg/thorvg-1.0-pre5.tar.gz | Bin 0 -> 632665 bytes .../src/main/jni/animation/thorvg_jni.cpp | 509 +- .../src/main/jni/animation/tvgGifEncoder.h | 655 ++ .../jni/audio/taglib/toolkit/tbytevector.cpp | 9 +- .../main/jni/audio/taglib/toolkit/tstring.cpp | 48 +- .../main/jni/audio/taglib/utf8/utf8/core.h | 8 +- .../jni/audio/taglib/utf8/utf8/unchecked.h | 6 +- libfenrir/src/main/jni/jni_call.cpp | 4 +- .../module/animation/AnimatedFileDrawable.kt | 18 +- .../thorvg/ThorVGLottie2Gif.kt} | 77 +- .../animation/thorvg/ThorVGLottieDrawable.kt | 603 ++ .../thorvg/ThorVGSVGRender.kt} | 4 +- .../fenrir/module/rlottie/RLottieDrawable.kt | 786 -- material/build.gradle | 1 + .../HideBottomViewOnScrollBehavior.java | 16 +- .../HideBottomViewOnScrollDelegate.java | 65 + .../HideLeftViewOnScrollDelegate.java | 65 + .../material/behavior/HideOnScrollView.java | 52 + .../HideRightViewOnScrollDelegate.java | 65 + .../behavior/HideViewOnScrollBehavior.java | 363 + .../behavior/HideViewOnScrollDelegate.java | 63 + .../bottomnavigation/res/values/styles.xml | 1 + .../material/button/MaterialSplitButton.java | 109 + .../button/res-public/values/public.xml | 6 + .../animator/chevron_checked_unchecked.xml | 22 + .../animator/chevron_unchecked_checked.xml | 22 + ..._button_chevron_overshoot_interpolator.xml | 18 + ..._split_button_chevron_reverse_rotation.xml | 25 + .../m3_split_button_chevron_rotation.xml | 25 + .../res/drawable/ic_expand_less_24px.xml | 31 + .../res/drawable/ic_expand_more_24px.xml | 31 + .../drawable/m3_split_button_chevron_avd.xml | 43 + .../material/button/res/values/attrs.xml | 6 + .../material/button/res/values/styles.xml | 39 + .../dialog/res/values/themes_base.xml | 6 + .../ExtendedFloatingActionButton.java | 30 +- .../FloatingToolbarLayout.java | 112 + .../res-public/values/public.xml | 20 + .../floatingtoolbar/res/values/attrs.xml | 25 + .../floatingtoolbar/res/values/dimens.xml | 21 + .../floatingtoolbar/res/values/styles.xml | 27 + .../navigation/NavigationBarItemView.java | 54 +- .../navigation/NavigationBarMenuView.java | 31 + .../navigation/NavigationBarView.java | 23 + .../navigation/res-public/values/public.xml | 1 + .../material/navigation/res/values/attrs.xml | 4 + .../navigationrail/LabelMoveTransition.java | 71 + .../navigationrail/NavigationRailView.java | 134 +- .../navigationrail/res/values/dimens.xml | 1 + .../navigationrail/res/values/strings.xml | 24 + .../navigationrail/res/values/styles.xml | 1 + .../android/material/slider/BaseSlider.java | 432 +- .../slider/res-public/values/public.xml | 7 + .../material/slider/res/values/attrs.xml | 14 + .../material/slider/res/values/dimens.xml | 1 + .../android/material/snackbar/Snackbar.java | 10 +- .../res/values-ar/strings_custom.xml | 20 + .../material/theme/res/values/themes_base.xml | 6 + .../timepicker/res/values-ja/strings.xml | 8 +- other_tool/camera_src.txt | 10 +- .../preferences/colorpicker/ColorCircle.kt | 2 +- .../renderer/FlowerColorWheelRenderer.kt | 2 +- .../renderer/SimpleColorWheelRenderer.kt | 2 +- 484 files changed, 20127 insertions(+), 38307 deletions(-) rename app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/{AnonymToken.kt => response/AnonymTokenResponse.kt} (82%) create mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/GetAuthCodeStatusResponse.kt create mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/SetAuthCodeStatusResponse.kt rename app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/{StoryBlockResponce.kt => StoryBlockResponse.kt} (79%) create mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/settings/AnonymToken.kt rename app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/{rlottie/RLottieNetworkCache.kt => animation/ThorVGLottieNetworkCache.kt} (86%) create mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieShapeableView.kt create mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieView.kt delete mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieImageView.kt delete mode 100644 app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieShapeableImageView.kt rename app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/{rlottie/RLottieNetworkCache.kt => animation/ThorVGLottieNetworkCache.kt} (86%) create mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieShapeableView.kt create mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieView.kt delete mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieImageView.kt delete mode 100644 app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieShapeableImageView.kt delete mode 100644 libfenrir/src/main/jni/animation/gif_builder.h create mode 100644 libfenrir/src/main/jni/animation/libyuv/source/row_sme.cc create mode 100644 libfenrir/src/main/jni/animation/libyuv/source/scale_sme.cc delete mode 100644 libfenrir/src/main/jni/animation/lottie_jni.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/inc/colorreplace.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/inc/rlottie.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/inc/rlottiecommon.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/rlottie-master.zip delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieanimation.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiefiltermodel.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieitem.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieitem.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieitem_capi.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiekeypath.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiekeypath.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieloader.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiemodel.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/lottie/lottiemodel.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/lottie/lottieparser.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/config.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_math.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_math.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_raster.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_raster.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_stroker.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_stroker.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/freetype/v_ft_types.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/stb/stb_image.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/stb/stb_image.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/varenaalloc.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/varenaalloc.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vbezier.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vbezier.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vbitmap.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vbitmap.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vbrush.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vbrush.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vcowptr.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vdasher.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vdasher.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vdebug.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vdebug.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawable.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawable.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper_common.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper_neon.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vdrawhelper_sse2.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/velapsedtimer.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/velapsedtimer.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vglobal.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vimageloader.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vimageloader.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vinterpolator.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vinterpolator.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vline.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vmatrix.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vmatrix.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vpainter.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vpainter.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vpath.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vpath.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vpathmesure.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vpoint.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vraster.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vrect.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vrect.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vrle.cpp delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vrle.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vsharedptr.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vstackallocator.h delete mode 100644 libfenrir/src/main/jni/animation/rlottie/src/vector/vtaskqueue.h create mode 100644 libfenrir/src/main/jni/animation/thorvg/inc/colorreplace.h rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/allocators.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/cursorstreamwrapper.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/document.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/encodedstream.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/encodings.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/error/en.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/error/error.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/filereadstream.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/filewritestream.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/fwd.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/internal/biginteger.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/internal/clzll.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/internal/diyfp.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/internal/dtoa.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/internal/ieee754.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/internal/itoa.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/internal/meta.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/internal/pow10.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/internal/regex.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/internal/stack.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/internal/strfunc.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/internal/strtod.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/internal/swap.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/istreamwrapper.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/memorybuffer.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/memorystream.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/msinttypes/inttypes.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/msinttypes/stdint.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/ostreamwrapper.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/pointer.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/prettywriter.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/rapidjson.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/reader.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/schema.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/stream.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/stringbuffer.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/uri.h (100%) rename libfenrir/src/main/jni/animation/{rlottie/src => thorvg/src/loaders}/lottie/rapidjson/writer.h (100%) create mode 100644 libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/thorvg_lottie.h create mode 100644 libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieAnimation.cpp create mode 100644 libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieBuilder.cpp create mode 100644 libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieBuilder.h create mode 100644 libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieCommon.h create mode 100644 libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieExpressions.cpp create mode 100644 libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieExpressions.h create mode 100644 libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieInterpolator.cpp rename libfenrir/src/main/jni/animation/{rlottie/src/vector/vpathmesure.h => thorvg/src/loaders/lottie/tvgLottieInterpolator.h} (62%) create mode 100644 libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieLoader.cpp create mode 100644 libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieLoader.h create mode 100644 libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieModel.cpp create mode 100644 libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieModel.h create mode 100644 libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieModifier.cpp create mode 100644 libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieModifier.h create mode 100644 libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieParser.cpp create mode 100644 libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieParser.h create mode 100644 libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieParserHandler.cpp create mode 100644 libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieParserHandler.h create mode 100644 libfenrir/src/main/jni/animation/thorvg/src/loaders/lottie/tvgLottieProperty.h rename libfenrir/src/main/jni/animation/{rlottie/src/vector/vraster.h => thorvg/src/loaders/lottie/tvgLottieRenderPooler.h} (55%) delete mode 100644 libfenrir/src/main/jni/animation/thorvg/src/renderer/tvgBinaryDesc.h create mode 100644 libfenrir/src/main/jni/animation/thorvg/thorvg-1.0-pre5.tar.gz create mode 100644 libfenrir/src/main/jni/animation/tvgGifEncoder.h rename libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/{rlottie/RLottie2Gif.kt => animation/thorvg/ThorVGLottie2Gif.kt} (67%) create mode 100644 libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/animation/thorvg/ThorVGLottieDrawable.kt rename libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/{thorvg/ThorVGRender.kt => animation/thorvg/ThorVGSVGRender.kt} (95%) delete mode 100644 libfenrir/src/main/kotlin/dev/ragnarok/fenrir/module/rlottie/RLottieDrawable.kt create mode 100644 material/java/com/google/android/material/behavior/HideBottomViewOnScrollDelegate.java create mode 100644 material/java/com/google/android/material/behavior/HideLeftViewOnScrollDelegate.java create mode 100644 material/java/com/google/android/material/behavior/HideOnScrollView.java create mode 100644 material/java/com/google/android/material/behavior/HideRightViewOnScrollDelegate.java create mode 100644 material/java/com/google/android/material/behavior/HideViewOnScrollBehavior.java create mode 100644 material/java/com/google/android/material/behavior/HideViewOnScrollDelegate.java create mode 100644 material/java/com/google/android/material/button/MaterialSplitButton.java create mode 100644 material/java/com/google/android/material/button/res/animator/chevron_checked_unchecked.xml create mode 100644 material/java/com/google/android/material/button/res/animator/chevron_unchecked_checked.xml create mode 100644 material/java/com/google/android/material/button/res/animator/m3_split_button_chevron_overshoot_interpolator.xml create mode 100644 material/java/com/google/android/material/button/res/animator/m3_split_button_chevron_reverse_rotation.xml create mode 100644 material/java/com/google/android/material/button/res/animator/m3_split_button_chevron_rotation.xml create mode 100644 material/java/com/google/android/material/button/res/drawable/ic_expand_less_24px.xml create mode 100644 material/java/com/google/android/material/button/res/drawable/ic_expand_more_24px.xml create mode 100644 material/java/com/google/android/material/button/res/drawable/m3_split_button_chevron_avd.xml create mode 100644 material/java/com/google/android/material/floatingtoolbar/FloatingToolbarLayout.java create mode 100644 material/java/com/google/android/material/floatingtoolbar/res-public/values/public.xml create mode 100644 material/java/com/google/android/material/floatingtoolbar/res/values/attrs.xml create mode 100644 material/java/com/google/android/material/floatingtoolbar/res/values/dimens.xml create mode 100644 material/java/com/google/android/material/floatingtoolbar/res/values/styles.xml create mode 100644 material/java/com/google/android/material/navigationrail/LabelMoveTransition.java create mode 100644 material/java/com/google/android/material/navigationrail/res/values/strings.xml create mode 100644 material/java/com/google/android/material/textfield/res/values-ar/strings_custom.xml diff --git a/app_fenrir/src/main/AndroidManifest.xml b/app_fenrir/src/main/AndroidManifest.xml index 26fb8dcc3..c20ca6b02 100644 --- a/app_fenrir/src/main/AndroidManifest.xml +++ b/app_fenrir/src/main/AndroidManifest.xml @@ -462,7 +462,6 @@ android:configChanges="keyboardHidden|orientation" android:exported="true" android:label="@string/lottie_preview" - android:screenOrientation="portrait" android:windowSoftInputMode="adjustResize|stateHidden" tools:ignore="DiscouragedApi,LockedOrientationActivity"> diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/Constants.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/Constants.kt index dda26d504..135b640bd 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/Constants.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/Constants.kt @@ -17,11 +17,11 @@ object Constants { const val FILE_PROVIDER_AUTHORITY: String = "${BuildConfig.APPLICATION_ID}.file_provider" const val VK_ANDROID_APP_VERSION_NAME = "8.15" - const val VK_ANDROID_APP_VERSION_CODE = "15271" + const val VK_ANDROID_APP_VERSION_CODE = 15271 const val KATE_APP_VERSION_NAME = "118 lite" - const val KATE_APP_VERSION_CODE = "566" + const val KATE_APP_VERSION_CODE = 566 - const val IOS_APP_VERSION_CODE = "3893" + const val IOS_APP_VERSION_CODE = 3893 const val API_ID: Int = BuildConfig.VK_API_APP_ID const val SECRET: String = BuildConfig.VK_CLIENT_SECRET diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/UserAgentTool.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/UserAgentTool.kt index ebe512624..7ff2ebabf 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/UserAgentTool.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/UserAgentTool.kt @@ -20,7 +20,7 @@ object UserAgentTool { private val KATE_USER_AGENT = String.format( Locale.US, - "KateMobileAndroid/%s-%s (Android %s; SDK %d; %s; %s; %s; %s)", + "KateMobileAndroid/%s-%d (Android %s; SDK %d; %s; %s; %s; %s)", Constants.KATE_APP_VERSION_NAME, Constants.KATE_APP_VERSION_CODE, Build.VERSION.RELEASE, @@ -33,7 +33,7 @@ object UserAgentTool { private val KATE_USER_AGENT_FAKE = String.format( Locale.US, - "KateMobileAndroid/%s-%s (Android %s; SDK %d; %s; %s; %s; %s)", + "KateMobileAndroid/%s-%d (Android %s; SDK %d; %s; %s; %s; %s)", Constants.KATE_APP_VERSION_NAME, Constants.KATE_APP_VERSION_CODE, Build.VERSION.RELEASE, @@ -46,7 +46,7 @@ object UserAgentTool { private val VK_ANDROID_USER_AGENT = String.format( Locale.US, - "VKAndroidApp/%s-%s (Android %s; SDK %d; %s; %s; %s; %s)", + "VKAndroidApp/%s-%d (Android %s; SDK %d; %s; %s; %s; %s)", Constants.VK_ANDROID_APP_VERSION_NAME, Constants.VK_ANDROID_APP_VERSION_CODE, Build.VERSION.RELEASE, @@ -59,7 +59,7 @@ object UserAgentTool { private val VK_ANDROID_USER_AGENT_FAKE = String.format( Locale.US, - "VKAndroidApp/%s-%s (Android %s; SDK %d; %s; %s; %s; %s)", + "VKAndroidApp/%s-%d (Android %s; SDK %d; %s; %s; %s; %s)", Constants.VK_ANDROID_APP_VERSION_NAME, Constants.VK_ANDROID_APP_VERSION_CODE, Build.VERSION.RELEASE, @@ -72,7 +72,7 @@ object UserAgentTool { private val VK_iOS_USER_AGENT_FAKE = String.format( Locale.US, - "com.vk.vkclient/%s (iPhone, iOS 16.1, iPhone11,2, Scale/3.0)", + "com.vk.vkclient/%d (iPhone, iOS 16.1, iPhone11,2, Scale/3.0)", Constants.IOS_APP_VERSION_CODE ) @@ -82,7 +82,7 @@ object UserAgentTool { return when (type) { AccountType.KATE_HIDDEN -> String.format( Locale.US, - "KateMobileAndroid/%s-%s (Android %s; SDK %d; %s; %s; %s; %s)", + "KateMobileAndroid/%s-%d (Android %s; SDK %d; %s; %s; %s; %s)", Constants.KATE_APP_VERSION_NAME, Constants.KATE_APP_VERSION_CODE, Build.VERSION.RELEASE, @@ -95,14 +95,14 @@ object UserAgentTool { AccountType.IOS_HIDDEN -> String.format( Locale.US, - "com.vk.vkclient/%s (iPhone, iOS 16.1, %s, Scale/3.0)", + "com.vk.vkclient/%d (iPhone, iOS 16.1, %s, Scale/3.0)", Constants.IOS_APP_VERSION_CODE, device ) else -> String.format( Locale.US, - "VKAndroidApp/%s-%s (Android %s; SDK %d; %s; %s; %s; %s)", + "VKAndroidApp/%s-%d (Android %s; SDK %d; %s; %s; %s; %s)", Constants.VK_ANDROID_APP_VERSION_NAME, Constants.VK_ANDROID_APP_VERSION_CODE, Build.VERSION.RELEASE, diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/LottieActivity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/LottieActivity.kt index b6676a1b3..e80f7c7f7 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/LottieActivity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/LottieActivity.kt @@ -14,10 +14,9 @@ import androidx.documentfile.provider.DocumentFile import com.google.android.material.button.MaterialButton import dev.ragnarok.fenrir.Extra import dev.ragnarok.fenrir.R -import dev.ragnarok.fenrir.module.BufferWriteNative import dev.ragnarok.fenrir.module.FenrirNative -import dev.ragnarok.fenrir.module.rlottie.RLottie2Gif -import dev.ragnarok.fenrir.module.rlottie.RLottie2Gif.Lottie2GifListener +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottie2Gif +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottie2Gif.Lottie2GifListener import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.settings.ISettings import dev.ragnarok.fenrir.settings.Settings @@ -26,7 +25,11 @@ import dev.ragnarok.fenrir.util.AppPerms import dev.ragnarok.fenrir.util.AppPerms.requestPermissionsAbs import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.Utils.hasVanillaIceCreamTarget -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView +import okio.BufferedSource +import okio.buffer +import okio.sink +import okio.source import java.io.File class LottieActivity : AppCompatActivity() { @@ -56,46 +59,40 @@ class LottieActivity : AppCompatActivity() { result.data?.getStringExtra(Extra.PATH), title ) toGif?.isEnabled = false - RLottie2Gif.create( - BufferWriteNative.fromStreamEndlessNull( - contentResolver.openInputStream( - intent.data ?: return@registerForActivityResult - ) ?: return@registerForActivityResult - ) - ).setListener(object : Lottie2GifListener { - var start: Long = 0 - var logs: String? = null - override fun onStarted() { - start = System.currentTimeMillis() - logs = "Wait a moment...\r\n" - log(logs) - } + ThorVGLottie2Gif.create(File(parentDir(), "lottie_view.json")) + .setListener(object : Lottie2GifListener { + var start: Long = 0 + var logs: String? = null + override fun onStarted() { + start = System.currentTimeMillis() + logs = "Wait a moment...\r\n" + log(logs) + } - @SuppressLint("SetTextI18n") - override fun onProgress(frame: Int, totalFrame: Int) { - log(logs + "progress : " + frame + "/" + totalFrame) - } + @SuppressLint("SetTextI18n") + override fun onProgress(frame: Int, totalFrame: Int) { + log(logs + "progress : " + frame + "/" + totalFrame) + } - override fun onFinished() { - logs = - "GIF created (" + (System.currentTimeMillis() - start) + "ms)\r\n" + - "Resolution : " + 500 + "x" + 500 + "\r\n" + - "Path : " + file + "\r\n" + - "File Size : " + file.length() / 1024 + "kb" - log(logs) - toGif?.post { toGif?.isEnabled = true } - } - }) + override fun onFinished() { + logs = + "GIF created (" + (System.currentTimeMillis() - start) + "ms)\r\n" + + "Resolution : " + 500 + "x" + 500 + "\r\n" + + "Path : " + file + "\r\n" + + "File Size : " + file.length() / 1024 + "kb" + log(logs) + toGif?.post { toGif?.isEnabled = true } + } + }) .setBackgroundColor(Color.TRANSPARENT) .setOutputPath(file) .setSize(500, 500) .setBackgroundTask(true) - .setDithering(false) .build() } } - private var lottie: RLottieImageView? = null + private var lottie: ThorVGLottieView? = null private var lg: TextView? = null internal fun log(log: String?) { lg?.post { lg?.text = log?.trim() } @@ -141,7 +138,30 @@ class LottieActivity : AppCompatActivity() { } if (savedInstanceState == null) { handleIntent(intent) + } else { + lottie?.fromFile(File(parentDir(), "lottie_view.json"), true) + lottie?.startAnimation() + } + } + + private fun parentDir(): File { + val file = File(cacheDir, "lottie_cache") + if (file.isFile) { + file.delete() + } + if (!file.exists()) { + file.mkdirs() } + return file + } + + private fun writeTempCacheFile(source: BufferedSource): File { + val file = File(parentDir(), "lottie_view.json") + + file.sink().buffer().use { output -> + output.writeAll(source) + } + return file } private fun handleIntent(intent: Intent?) { @@ -157,15 +177,14 @@ class LottieActivity : AppCompatActivity() { val action = intent.action if (Intent.ACTION_VIEW == action) { try { - lottie?.setAutoRepeat(true) - lottie?.fromString( - BufferWriteNative.fromStreamEndlessNull( - contentResolver.openInputStream( - getIntent().data ?: return - ) ?: return - ), Utils.dp(500f), Utils.dp(500f) + writeTempCacheFile( + (contentResolver.openInputStream( + getIntent().data ?: return + ) ?: return).source().buffer() ) - lottie?.playAnimation() + + lottie?.fromFile(File(parentDir(), "lottie_view.json"), true) + lottie?.startAnimation() } catch (e: Throwable) { e.printStackTrace() } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/MainActivity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/MainActivity.kt index cb67d9c0d..4aee28b55 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/MainActivity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/MainActivity.kt @@ -551,6 +551,10 @@ open class MainActivity : AppCompatActivity(), NavigationDrawerCallbacks, OnSect } private fun updateNotificationCount(account: Long) { + if (account == ISettings.IAccountsSettings.INVALID_ID) { + removeNotificationsBadge() + return + } mCompositeJob.add(CountersInteractor(networkInterfaces).getApiCounters(account) .fromIOToMain({ counters -> updateNotificationsBadge(counters) }) { removeNotificationsBadge() }) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/SinglePhotoActivity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/SinglePhotoActivity.kt index 21ead2941..a666e253f 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/SinglePhotoActivity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/SinglePhotoActivity.kt @@ -46,7 +46,7 @@ import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.toMain import dev.ragnarok.fenrir.util.toast.CustomToast import dev.ragnarok.fenrir.view.CircleCounterButton import dev.ragnarok.fenrir.view.TouchImageView -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import dev.ragnarok.fenrir.view.pager.WeakPicassoLoadCallback import java.io.File import java.lang.ref.WeakReference @@ -232,7 +232,7 @@ class SinglePhotoActivity : NoMainActivity(), PlaceProvider, AppStyleable { val reload: FloatingActionButton private val mPicassoLoadCallback: WeakPicassoLoadCallback val photo: TouchImageView - val progress: RLottieImageView + val progress: ThorVGLottieView var animationDispose = CancelableJob() private var mAnimationLoaded = false private var mLoadingNow = false @@ -258,13 +258,19 @@ class SinglePhotoActivity : NoMainActivity(), PlaceProvider, AppStyleable { val k = ObjectAnimator.ofFloat(progress, View.ALPHA, 0.0f).setDuration(1000) k.addListener(object : StubAnimatorListener() { override fun onAnimationEnd(animation: Animator) { - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE progress.alpha = 1f } override fun onAnimationCancel(animation: Animator) { - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE progress.alpha = 1f } @@ -272,7 +278,10 @@ class SinglePhotoActivity : NoMainActivity(), PlaceProvider, AppStyleable { k.start() } else if (mAnimationLoaded && !mLoadingNow) { mAnimationLoaded = false - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE } else if (mLoadingNow) { animationDispose += delayTaskFlow(300).toMain { @@ -280,8 +289,6 @@ class SinglePhotoActivity : NoMainActivity(), PlaceProvider, AppStyleable { progress.visibility = View.VISIBLE progress.fromRes( dev.ragnarok.fenrir_common.R.raw.loading, - Utils.dp(100F), - Utils.dp(100F), intArrayOf( 0x000000, CurrentTheme.getColorPrimary(ref.get()), @@ -289,7 +296,7 @@ class SinglePhotoActivity : NoMainActivity(), PlaceProvider, AppStyleable { CurrentTheme.getColorSecondary(ref.get()) ) ) - progress.playAnimation() + progress.startAnimation() } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/gifpager/GifPagerActivity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/gifpager/GifPagerActivity.kt index f6ba28b4e..35048d04f 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/gifpager/GifPagerActivity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/gifpager/GifPagerActivity.kt @@ -267,11 +267,12 @@ class GifPagerActivity : AbsDocumentPreviewActivity(), IPhotoPagerView, @@ -252,7 +252,7 @@ class PhotoPagerActivity : BaseMvpActivity private var mButtonLike: CircleCounterButton? = null private var mButtonComments: CircleCounterButton? = null private var buttonShare: CircleCounterButton? = null - private var mLoadingProgressBar: RLottieImageView? = null + private var mLoadingProgressBar: ThorVGLottieView? = null private var mLoadingProgressBarDispose = CancelableJob() private var mLoadingProgressBarLoaded = false private var mToolbar: Toolbar? = null @@ -748,8 +748,6 @@ class PhotoPagerActivity : BaseMvpActivity mLoadingProgressBar?.visibility = View.VISIBLE mLoadingProgressBar?.fromRes( dev.ragnarok.fenrir_common.R.raw.loading, - Utils.dp(100F), - Utils.dp(100F), intArrayOf( 0x000000, Color.WHITE, @@ -757,12 +755,15 @@ class PhotoPagerActivity : BaseMvpActivity Color.WHITE ) ) - mLoadingProgressBar?.playAnimation() + mLoadingProgressBar?.startAnimation() } } else if (mLoadingProgressBarLoaded) { mLoadingProgressBarLoaded = false mLoadingProgressBar?.visibility = View.GONE - mLoadingProgressBar?.clearAnimationDrawable() + mLoadingProgressBar?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } @@ -851,7 +852,7 @@ class PhotoPagerActivity : BaseMvpActivity private val mPicassoLoadCallback: WeakPicassoLoadCallback val photo: TouchImageView val tagsPlaceholder: FrameLayout - val progress: RLottieImageView + val progress: ThorVGLottieView var animationDispose = CancelableJob() private var mAnimationLoaded = false private var mLoadingNow = false @@ -961,13 +962,19 @@ class PhotoPagerActivity : BaseMvpActivity val k = ObjectAnimator.ofFloat(progress, View.ALPHA, 0.0f).setDuration(1000) k.addListener(object : StubAnimatorListener() { override fun onAnimationEnd(animation: Animator) { - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE progress.alpha = 1f } override fun onAnimationCancel(animation: Animator) { - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE progress.alpha = 1f } @@ -975,7 +982,10 @@ class PhotoPagerActivity : BaseMvpActivity k.start() } else if (mAnimationLoaded && !mLoadingNow) { mAnimationLoaded = false - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE } else if (mLoadingNow) { animationDispose += delayTaskFlow(300).toMain { @@ -983,8 +993,6 @@ class PhotoPagerActivity : BaseMvpActivity progress.visibility = View.VISIBLE progress.fromRes( dev.ragnarok.fenrir_common.R.raw.loading, - Utils.dp(100F), - Utils.dp(100F), intArrayOf( 0x000000, CurrentTheme.getColorPrimary(this@PhotoPagerActivity), @@ -992,7 +1000,7 @@ class PhotoPagerActivity : BaseMvpActivity CurrentTheme.getColorSecondary(this@PhotoPagerActivity) ) ) - progress.playAnimation() + progress.startAnimation() } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/shortvideopager/ShortVideoPagerActivity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/shortvideopager/ShortVideoPagerActivity.kt index f37d62947..fdaea4111 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/shortvideopager/ShortVideoPagerActivity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/activity/shortvideopager/ShortVideoPagerActivity.kt @@ -60,7 +60,7 @@ import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.toMain import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.view.CircleCounterButton import dev.ragnarok.fenrir.view.ExpandableSurfaceView -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import java.lang.ref.WeakReference class ShortVideoPagerActivity : BaseMvpActivity(), @@ -81,7 +81,7 @@ class ShortVideoPagerActivity : BaseMvpActivity(R.id.swipe_helper) + val mHelper = findViewById(R.id.swipe_helper) if (HelperSimple.needHelp(HelperSimple.SHORT_VIDEO_HELPER, 2)) { mHelper?.visibility = View.VISIBLE mHelper?.fromRes( dev.ragnarok.fenrir_common.R.raw.story_guide_hand_swipe, - Utils.dp(500F), - Utils.dp(500F), intArrayOf(0x333333, CurrentTheme.getColorSecondary(this)) ) - mHelper?.playAnimation() + mHelper?.startAnimation() helpDisposable += delayTaskFlow(5000).toMain { - mHelper?.clearAnimationDrawable() + mHelper?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) mHelper?.visibility = View.GONE } } else { @@ -220,8 +221,6 @@ class ShortVideoPagerActivity : BaseMvpActivity if (stateSpeed) CurrentTheme.getColorPrimary(this) else "#ffffff".toColor() ) } - val mHelper = findViewById(R.id.swipe_helper) + val mHelper = findViewById(R.id.swipe_helper) if (HelperSimple.needHelp(HelperSimple.STORY_HELPER, 2)) { mHelper?.visibility = View.VISIBLE mHelper?.fromRes( dev.ragnarok.fenrir_common.R.raw.story_guide_hand_swipe, - Utils.dp(500F), - Utils.dp(500F), intArrayOf(0x333333, CurrentTheme.getColorSecondary(this)) ) - mHelper?.playAnimation() + mHelper?.startAnimation() helpDisposable += delayTaskFlow(5000).toMain { - mHelper?.clearAnimationDrawable() + mHelper?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) mHelper?.visibility = View.GONE } } else { @@ -452,7 +453,7 @@ class StoryPagerActivity : BaseMvpActivity private inner class Holder(rootView: View) : MultiHolder(rootView), SurfaceHolder.Callback { val mSurfaceView: ExpandableSurfaceView = rootView.findViewById(R.id.videoSurface) - val mProgressBar: RLottieImageView + val mProgressBar: ThorVGLottieView override var isSurfaceReady = false override fun surfaceCreated(holder: SurfaceHolder) { isSurfaceReady = true @@ -469,8 +470,6 @@ class StoryPagerActivity : BaseMvpActivity if (visible) { mProgressBar.fromRes( dev.ragnarok.fenrir_common.R.raw.loading, - Utils.dp(100F), - Utils.dp(100F), intArrayOf( 0x000000, CurrentTheme.getColorPrimary(this@StoryPagerActivity), @@ -478,9 +477,12 @@ class StoryPagerActivity : BaseMvpActivity CurrentTheme.getColorSecondary(this@StoryPagerActivity) ) ) - mProgressBar.playAnimation() + mProgressBar.startAnimation() } else { - mProgressBar.clearAnimationDrawable() + mProgressBar.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } @@ -504,7 +506,7 @@ class StoryPagerActivity : BaseMvpActivity val reload: FloatingActionButton private val mPicassoLoadCallback: WeakPicassoLoadCallback val photo: TouchImageView - val progress: RLottieImageView + val progress: ThorVGLottieView var animationDispose = CancelableJob() private var mAnimationLoaded = false private var mLoadingNow = false @@ -540,13 +542,19 @@ class StoryPagerActivity : BaseMvpActivity val k = ObjectAnimator.ofFloat(progress, View.ALPHA, 0.0f).setDuration(1000) k.addListener(object : StubAnimatorListener() { override fun onAnimationEnd(animation: Animator) { - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE progress.alpha = 1f } override fun onAnimationCancel(animation: Animator) { - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE progress.alpha = 1f } @@ -554,7 +562,10 @@ class StoryPagerActivity : BaseMvpActivity k.start() } else if (mAnimationLoaded && !mLoadingNow) { mAnimationLoaded = false - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE } else if (mLoadingNow) { animationDispose += delayTaskFlow(300).toMain { @@ -562,8 +573,6 @@ class StoryPagerActivity : BaseMvpActivity progress.visibility = View.VISIBLE progress.fromRes( dev.ragnarok.fenrir_common.R.raw.loading, - Utils.dp(100F), - Utils.dp(100F), intArrayOf( 0x000000, CurrentTheme.getColorPrimary(this@StoryPagerActivity), @@ -571,7 +580,7 @@ class StoryPagerActivity : BaseMvpActivity CurrentTheme.getColorSecondary(this@StoryPagerActivity) ) ) - progress.playAnimation() + progress.startAnimation() } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AccountApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AccountApi.kt index ca00eacc8..0f1c9534b 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AccountApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AccountApi.kt @@ -58,9 +58,11 @@ internal class AccountApi(accountId: Long, provider: IServiceProvider) : } override fun registerDevice( + api_id: Int?, + app_id: Int?, token: String?, pushes_granted: Int?, - app_version: String?, + app_version: Int?, push_provider: String?, companion_apps: String?, type: Int?, @@ -72,6 +74,8 @@ internal class AccountApi(accountId: Long, provider: IServiceProvider) : return provideService(IAccountService(), TokenType.USER) .flatMapConcat { it.registerDevice( + api_id, + app_id, token, pushes_granted, app_version, diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AuthApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AuthApi.kt index 8799eaeb5..4e7480623 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AuthApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/AuthApi.kt @@ -9,10 +9,12 @@ import dev.ragnarok.fenrir.api.CaptchaNeedException import dev.ragnarok.fenrir.api.IDirectLoginServiceProvider import dev.ragnarok.fenrir.api.NeedValidationException import dev.ragnarok.fenrir.api.interfaces.IAuthApi -import dev.ragnarok.fenrir.api.model.AnonymToken import dev.ragnarok.fenrir.api.model.LoginResponse import dev.ragnarok.fenrir.api.model.VKApiValidationResponse +import dev.ragnarok.fenrir.api.model.response.AnonymTokenResponse import dev.ragnarok.fenrir.api.model.response.BaseResponse +import dev.ragnarok.fenrir.api.model.response.GetAuthCodeStatusResponse +import dev.ragnarok.fenrir.api.model.response.SetAuthCodeStatusResponse import dev.ragnarok.fenrir.api.model.response.VKUrlResponse import dev.ragnarok.fenrir.nonNullNoEmpty import dev.ragnarok.fenrir.util.Utils.getDeviceId @@ -162,7 +164,7 @@ class AuthApi(private val service: IDirectLoginServiceProvider) : IAuthApi { clientSecret: String?, v: String?, device_id: String? - ): Flow { + ): Flow { return service.provideAuthService() .flatMapConcat { it.get_anonym_token( @@ -173,16 +175,61 @@ class AuthApi(private val service: IDirectLoginServiceProvider) : IAuthApi { device_id, DEVICE_COUNTRY_CODE ) - .map { s -> - if (s.error != null) { - throw AuthException(s.error.orEmpty(), s.errorDescription) + .map { + if (it.error != null) { + throw AuthException( + it.error.orEmpty(), + it.errorDescription + ) } else { - s + it } } } } + override fun setAuthCodeStatus( + auth_code: String?, + apiId: Int, + device_id: String?, + accessToken: String?, + v: String? + ): Flow { + return service.provideAuthService() + .flatMapConcat { + it.setAuthCodeStatus( + auth_code, + apiId, + device_id, + accessToken, + DEVICE_COUNTRY_CODE, + v + ) + .map(extractResponseWithErrorHandling()) + } + } + + override fun getAuthCodeStatus( + auth_code: String?, + apiId: Int, + device_id: String?, + accessToken: String?, + v: String? + ): Flow { + return service.provideAuthService() + .flatMapConcat { + it.getAuthCodeStatus( + auth_code, + apiId, + device_id, + accessToken, + DEVICE_COUNTRY_CODE, + v + ) + .map(extractResponseWithErrorHandling()) + } + } + companion object { fun extractResponseWithErrorHandling(): (BaseResponse) -> T = { err -> err.error?.let { throw ApiException(it) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/StoriesShortVideosApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/StoriesShortVideosApi.kt index 8df6b2a3b..e2541e070 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/StoriesShortVideosApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/StoriesShortVideosApi.kt @@ -156,4 +156,20 @@ internal class StoriesShortVideosApi(accountId: Long, provider: IServiceProvider } } } + + override fun subscribe(owner_id: Long?): Flow { + return provideService(IStoriesShortVideosService(), TokenType.USER) + .flatMapConcat { + it.subscribe(owner_id) + .map(extractResponseWithErrorHandling()) + } + } + + override fun unsubscribe(owner_id: Long?): Flow { + return provideService(IStoriesShortVideosService(), TokenType.USER) + .flatMapConcat { + it.unsubscribe(owner_id) + .map(extractResponseWithErrorHandling()) + } + } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/VKApies.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/VKApies.kt index 37ed83246..116601fce 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/VKApies.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/impl/VKApies.kt @@ -31,7 +31,9 @@ import dev.ragnarok.fenrir.api.interfaces.IVideoApi import dev.ragnarok.fenrir.api.interfaces.IWallApi import dev.ragnarok.fenrir.api.rest.IServiceRest import dev.ragnarok.fenrir.api.rest.SimplePostHttp +import dev.ragnarok.fenrir.settings.ISettings import dev.ragnarok.fenrir.util.Utils +import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.toFlowThrowable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -196,8 +198,13 @@ internal class VKApies private constructor( } fun provideRest(aid: Long, vararg tokenPolicy: Int): Flow { + if (aid == ISettings.IAccountsSettings.INVALID_ID) { + return toFlowThrowable(UnsupportedOperationException("Please select account!")) + } if (useCustomToken) { - return provider.provideCustomRest(aid, customAccessToken!!) + customAccessToken?.let { + return provider.provideCustomRest(aid, it) + } } val isCommunity = aid < 0 return if (isCommunity) { @@ -211,7 +218,7 @@ internal class VKApies private constructor( } else -> { - throw UnsupportedOperationException("Unsupported account_id: $aid with token_policy: " + tokenPolicy.contentToString()) + toFlowThrowable(UnsupportedOperationException("Unsupported account_id: $aid with token_policy: " + tokenPolicy.contentToString())) } } } else { @@ -225,8 +232,10 @@ internal class VKApies private constructor( } else -> { - throw UnsupportedOperationException( - "Unsupported account_id: " + aid + " with token_policy: " + tokenPolicy.contentToString() + toFlowThrowable( + UnsupportedOperationException( + "Unsupported account_id: " + aid + " with token_policy: " + tokenPolicy.contentToString() + ) ) } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IAccountApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IAccountApi.kt index 79bce9c19..e5dddd159 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IAccountApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IAccountApi.kt @@ -24,9 +24,11 @@ interface IAccountApi { @CheckResult fun registerDevice( + api_id: Int?, + app_id: Int?, token: String?, pushes_granted: Int?, - app_version: String?, + app_version: Int?, push_provider: String?, companion_apps: String?, type: Int?, diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IAuthApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IAuthApi.kt index e9bbe8c57..2bf1ae195 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IAuthApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IAuthApi.kt @@ -1,12 +1,16 @@ package dev.ragnarok.fenrir.api.interfaces -import dev.ragnarok.fenrir.api.model.AnonymToken +import androidx.annotation.CheckResult import dev.ragnarok.fenrir.api.model.LoginResponse import dev.ragnarok.fenrir.api.model.VKApiValidationResponse +import dev.ragnarok.fenrir.api.model.response.AnonymTokenResponse +import dev.ragnarok.fenrir.api.model.response.GetAuthCodeStatusResponse +import dev.ragnarok.fenrir.api.model.response.SetAuthCodeStatusResponse import dev.ragnarok.fenrir.api.model.response.VKUrlResponse import kotlinx.coroutines.flow.Flow interface IAuthApi { + @CheckResult fun directLogin( grantType: String?, clientId: Int, @@ -23,6 +27,7 @@ interface IAuthApi { libverify_support: Boolean ): Flow + @CheckResult fun validatePhone( phone: String?, apiId: Int, @@ -34,6 +39,7 @@ interface IAuthApi { allow_callreset: Boolean ): Flow + @CheckResult fun authByExchangeToken( clientId: Int, apiId: Int, @@ -46,11 +52,30 @@ interface IAuthApi { v: String? ): Flow + @CheckResult fun get_anonym_token( apiId: Int, clientId: Int, clientSecret: String?, v: String?, device_id: String? - ): Flow + ): Flow + + @CheckResult + fun setAuthCodeStatus( + auth_code: String?, + apiId: Int, + device_id: String?, + accessToken: String?, + v: String? + ): Flow + + @CheckResult + fun getAuthCodeStatus( + auth_code: String?, + apiId: Int, + device_id: String?, + accessToken: String?, + v: String? + ): Flow } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IStatusApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IStatusApi.kt index 00e5a0217..bb35ffbff 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IStatusApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IStatusApi.kt @@ -5,5 +5,5 @@ import kotlinx.coroutines.flow.Flow interface IStatusApi { @CheckResult - operator fun set(text: String?, groupId: Long?): Flow + fun set(text: String?, groupId: Long?): Flow } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IStoriesShortVideosApi.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IStoriesShortVideosApi.kt index 9ecd339c3..b42116c1c 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IStoriesShortVideosApi.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/interfaces/IStoriesShortVideosApi.kt @@ -69,4 +69,10 @@ interface IStoriesShortVideosApi { extended: Int?, fields: String? ): Flow + + @CheckResult + fun subscribe(owner_id: Long?): Flow + + @CheckResult + fun unsubscribe(owner_id: Long?): Flow } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/AnonymToken.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/AnonymTokenResponse.kt similarity index 82% rename from app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/AnonymToken.kt rename to app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/AnonymTokenResponse.kt index b5c1bc0f2..b0ee004aa 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/AnonymToken.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/AnonymTokenResponse.kt @@ -1,10 +1,10 @@ -package dev.ragnarok.fenrir.api.model +package dev.ragnarok.fenrir.api.model.response import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -class AnonymToken { +class AnonymTokenResponse { @SerialName("token") var token: String? = null diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/GetAuthCodeStatusResponse.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/GetAuthCodeStatusResponse.kt new file mode 100644 index 000000000..b0d2517ec --- /dev/null +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/GetAuthCodeStatusResponse.kt @@ -0,0 +1,19 @@ +package dev.ragnarok.fenrir.api.model.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +class GetAuthCodeStatusResponse { + @SerialName("expires_in") + var expires_in: Long = 0 + + @SerialName("access_token") + var access_token: String? = null + + @SerialName("user_id") + var user_id: Long = 0 + + @SerialName("status") + var status: Int = 0 +} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/SetAuthCodeStatusResponse.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/SetAuthCodeStatusResponse.kt new file mode 100644 index 000000000..bc6e31cc0 --- /dev/null +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/SetAuthCodeStatusResponse.kt @@ -0,0 +1,22 @@ +package dev.ragnarok.fenrir.api.model.response + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +class SetAuthCodeStatusResponse { + @SerialName("domain") + var domain: String? = null + + @SerialName("expires_in") + var expires_in: Long = 0 + + @SerialName("faq_url") + var faq_url: String? = null + + @SerialName("polling_delay") + var polling_delay: Int = 0 + + @SerialName("status") + var status: Int = 0 +} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoriesResponse.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoriesResponse.kt index 9b7cf18ca..ff3f9f5fe 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoriesResponse.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoriesResponse.kt @@ -8,7 +8,7 @@ import kotlinx.serialization.Serializable @Serializable class StoriesResponse { @SerialName("items") - var items: List? = null + var items: List? = null @SerialName("profiles") var profiles: List? = null diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoryBlockResponce.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoryBlockResponse.kt similarity index 79% rename from app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoryBlockResponce.kt rename to app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoryBlockResponse.kt index c7de1c4ba..603a722df 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoryBlockResponce.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/model/response/StoryBlockResponse.kt @@ -5,10 +5,10 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -class StoryBlockResponce { +class StoryBlockResponse { @SerialName("stories") var stories: List? = null @SerialName("grouped") - var grouped: List? = null + var grouped: List? = null } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAccountService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAccountService.kt index bd3b8d187..24edaf405 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAccountService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAccountService.kt @@ -63,9 +63,11 @@ class IAccountService : IServiceRest() { //https://vk.com/dev/account.registerDevice fun registerDevice( + api_id: Int?, + app_id: Int?, token: String?, pushes_granted: Int?, - app_version: String?, + app_version: Int?, push_provider: String?, companion_apps: String?, type: Int?, @@ -86,6 +88,9 @@ class IAccountService : IServiceRest() { "device_model" to deviceModel, "device_id" to deviceId, "system_version" to systemVersion, + "has_google_services" to 1, + "app_id" to app_id, + "api_id" to api_id, "settings" to settings ), baseInt ) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAuthService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAuthService.kt index f39b65614..ff9525747 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAuthService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IAuthService.kt @@ -1,9 +1,11 @@ package dev.ragnarok.fenrir.api.services -import dev.ragnarok.fenrir.api.model.AnonymToken import dev.ragnarok.fenrir.api.model.LoginResponse import dev.ragnarok.fenrir.api.model.VKApiValidationResponse +import dev.ragnarok.fenrir.api.model.response.AnonymTokenResponse import dev.ragnarok.fenrir.api.model.response.BaseResponse +import dev.ragnarok.fenrir.api.model.response.GetAuthCodeStatusResponse +import dev.ragnarok.fenrir.api.model.response.SetAuthCodeStatusResponse import dev.ragnarok.fenrir.api.model.response.VKUrlResponse import dev.ragnarok.fenrir.api.rest.IServiceRest import kotlinx.coroutines.flow.Flow @@ -120,7 +122,7 @@ class IAuthService : IServiceRest() { v: String?, device_id: String?, lang: String? - ): Flow { + ): Flow { return rest.request( "get_anonym_token", form( @@ -131,7 +133,51 @@ class IAuthService : IServiceRest() { "device_id" to device_id, "lang" to lang, "https" to 1 - ), AnonymToken.serializer() + ), AnonymTokenResponse.serializer() + ) + } + + fun setAuthCodeStatus( + auth_code: String?, + apiId: Int, + device_id: String?, + accessToken: String?, + lang: String?, + v: String? + ): Flow> { + return rest.request( + "auth.setAuthCodeStatus", + form( + "api_id" to apiId, + "auth_code" to auth_code, + "device_id" to device_id, + "access_token" to accessToken, + "lang" to lang, + "https" to 1, + "v" to v + ), base(SetAuthCodeStatusResponse.serializer()) + ) + } + + fun getAuthCodeStatus( + auth_code: String?, + apiId: Int, + device_id: String?, + accessToken: String?, + lang: String?, + v: String? + ): Flow> { + return rest.request( + "auth.getAuthCodeStatus", + form( + "api_id" to apiId, + "auth_code" to auth_code, + "device_id" to device_id, + "access_token" to accessToken, + "lang" to lang, + "https" to 1, + "v" to v + ), base(GetAuthCodeStatusResponse.serializer()) ) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStatusService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStatusService.kt index 62a69a5c6..2c6d01ac7 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStatusService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStatusService.kt @@ -12,7 +12,7 @@ class IStatusService : IServiceRest() { * @param groupId Identifier of a community to set a status in. If left blank the status is set to current user. * @return 1 */ - operator fun set( + fun set( text: String?, groupId: Long? ): Flow> { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStoriesShortVideosService.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStoriesShortVideosService.kt index 78e946651..49705d817 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStoriesShortVideosService.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/api/services/IStoriesShortVideosService.kt @@ -175,4 +175,14 @@ class IStoriesShortVideosService : IServiceRest() { ), base(ShortVideosResponse.serializer()) ) } + + //is_subscribed_stories + + fun subscribe(owner_id: Long?): Flow> { + return rest.request("stories.subscribe", form("owner_id" to owner_id), baseInt) + } + + fun unsubscribe(owner_id: Long?): Flow> { + return rest.request("stories.unsubscribe", form("owner_id" to owner_id), baseInt) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/TempDataStorage.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/TempDataStorage.kt index f1ec03c0d..e85e5eccc 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/TempDataStorage.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/impl/TempDataStorage.kt @@ -245,7 +245,7 @@ class TempDataStorage internal constructor(context: Context) : ITempDataStorage return flow { val db = helper.writableDatabase db.beginTransaction() - if (isActive()) { + if (!isActive()) { db.endTransaction() } else { try { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/IdPairEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/IdPairEntity.kt index bb82b2d8d..cd08e7921 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/IdPairEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/IdPairEntity.kt @@ -12,7 +12,7 @@ class IdPairEntity { var ownerId = 0L private set - operator fun set(id: Int, ownerId: Long): IdPairEntity { + fun set(id: Int, ownerId: Long): IdPairEntity { this.id = id this.ownerId = ownerId return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/ArticleDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/ArticleDboEntity.kt index d75e215a9..bffb0403f 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/ArticleDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/ArticleDboEntity.kt @@ -27,7 +27,7 @@ class ArticleDboEntity : DboEntity() { var isFavorite = false private set - operator fun set(id: Int, owner_id: Long): ArticleDboEntity { + fun set(id: Int, owner_id: Long): ArticleDboEntity { this.id = id ownerId = owner_id return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioArtistDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioArtistDboEntity.kt index 9b24aed69..aa22d1698 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioArtistDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioArtistDboEntity.kt @@ -40,7 +40,7 @@ class AudioArtistDboEntity : DboEntity() { var height = 0 private set - operator fun set(url: String?, width: Int, height: Int): AudioArtistImageEntity { + fun set(url: String?, width: Int, height: Int): AudioArtistImageEntity { this.url = url this.width = width this.height = height diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioDboEntity.kt index 2ea78eeae..a24f1638c 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioDboEntity.kt @@ -47,7 +47,7 @@ class AudioDboEntity : DboEntity() { var isHq = false private set - operator fun set(id: Int, ownerId: Long): AudioDboEntity { + fun set(id: Int, ownerId: Long): AudioDboEntity { this.id = id this.ownerId = ownerId return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioMessageDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioMessageDboEntity.kt index 9b49e0cfc..20bbe3687 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioMessageDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/AudioMessageDboEntity.kt @@ -27,7 +27,7 @@ class AudioMessageDboEntity : DboEntity() { var was_listened = false private set - operator fun set(id: Int, ownerId: Long): AudioMessageDboEntity { + fun set(id: Int, ownerId: Long): AudioMessageDboEntity { this.id = id this.ownerId = ownerId return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CommentEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CommentEntity.kt index f538d4bb8..2fb7e6c61 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CommentEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CommentEntity.kt @@ -67,7 +67,7 @@ class CommentEntity { var threads: List? = null private set - operator fun set( + fun set( sourceId: Int, sourceOwnerId: Long, @CommentedType sourceType: Int, diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CommunityDetailsEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CommunityDetailsEntity.kt index 09a399054..9b24a049d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CommunityDetailsEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CommunityDetailsEntity.kt @@ -204,7 +204,7 @@ class CommunityDetailsEntity { var width = 0 private set - operator fun set(url: String?, height: Int, width: Int): CoverImage { + fun set(url: String?, height: Int, width: Int): CoverImage { this.url = url this.height = height this.width = width @@ -226,7 +226,7 @@ class CommunityDetailsEntity { var cover: String? = null private set - operator fun set( + fun set( id: Int, url: String?, title: String?, diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CountryDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CountryDboEntity.kt index d5f2ce486..1988a63db 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CountryDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/CountryDboEntity.kt @@ -11,7 +11,7 @@ class CountryDboEntity { var title: String? = null private set - operator fun set(id: Int, title: String?): CountryDboEntity { + fun set(id: Int, title: String?): CountryDboEntity { this.id = id this.title = title return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/DocumentDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/DocumentDboEntity.kt index b9fc9a937..9a3512140 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/DocumentDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/DocumentDboEntity.kt @@ -33,7 +33,7 @@ class DocumentDboEntity : DboEntity() { var video: VideoPreviewDbo? = null private set - operator fun set(id: Int, ownerId: Long): DocumentDboEntity { + fun set(id: Int, ownerId: Long): DocumentDboEntity { this.id = id this.ownerId = ownerId return this @@ -104,7 +104,7 @@ class DocumentDboEntity : DboEntity() { var fileSize: Long = 0 private set - operator fun set(src: String?, width: Int, height: Int, fileSize: Long): VideoPreviewDbo { + fun set(src: String?, width: Int, height: Int, fileSize: Long): VideoPreviewDbo { this.src = src this.width = width this.height = height @@ -123,7 +123,7 @@ class DocumentDboEntity : DboEntity() { var height = 0 private set - operator fun set(src: String?, width: Int, height: Int): GraffitiDbo { + fun set(src: String?, width: Int, height: Int): GraffitiDbo { this.src = src this.width = width this.height = height diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MarketAlbumDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MarketAlbumDboEntity.kt index 6a6dd4064..42e538713 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MarketAlbumDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MarketAlbumDboEntity.kt @@ -23,7 +23,7 @@ class MarketAlbumDboEntity : DboEntity() { var updated_time: Long = 0 private set - operator fun set(id: Int, owner_id: Long): MarketAlbumDboEntity { + fun set(id: Int, owner_id: Long): MarketAlbumDboEntity { this.id = id this.owner_id = owner_id return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MarketDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MarketDboEntity.kt index 0c7da220a..7bcb84992 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MarketDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MarketDboEntity.kt @@ -37,7 +37,7 @@ class MarketDboEntity : DboEntity() { var photos: List? = null private set - operator fun set(id: Int, owner_id: Long): MarketDboEntity { + fun set(id: Int, owner_id: Long): MarketDboEntity { this.id = id this.owner_id = owner_id return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MessageDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MessageDboEntity.kt index 50e4ba518..632686796 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MessageDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/MessageDboEntity.kt @@ -74,7 +74,7 @@ class MessageDboEntity : DboEntity() { var updateTime: Long = 0 private set - operator fun set(id: Int, peerId: Long, fromId: Long): MessageDboEntity { + fun set(id: Int, peerId: Long, fromId: Long): MessageDboEntity { this.id = id this.peerId = peerId this.fromId = fromId diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PageDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PageDboEntity.kt index 73da755ef..87730c8f3 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PageDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PageDboEntity.kt @@ -31,7 +31,7 @@ class PageDboEntity : DboEntity() { var viewUrl: String? = null private set - operator fun set(id: Int, ownerId: Long): PageDboEntity { + fun set(id: Int, ownerId: Long): PageDboEntity { this.id = id this.ownerId = ownerId return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PhotoAlbumDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PhotoAlbumDboEntity.kt index 3c6f57359..6a57d0ae7 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PhotoAlbumDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PhotoAlbumDboEntity.kt @@ -35,7 +35,7 @@ class PhotoAlbumDboEntity : DboEntity() { var privacyComment: PrivacyEntity? = null private set - operator fun set(id: Int, ownerId: Long): PhotoAlbumDboEntity { + fun set(id: Int, ownerId: Long): PhotoAlbumDboEntity { this.id = id this.ownerId = ownerId return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PhotoDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PhotoDboEntity.kt index ab8dee644..de7ab884e 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PhotoDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PhotoDboEntity.kt @@ -43,7 +43,7 @@ class PhotoDboEntity : DboEntity() { var sizes: PhotoSizeEntity? = null private set - operator fun set(id: Int, ownerId: Long): PhotoDboEntity { + fun set(id: Int, ownerId: Long): PhotoDboEntity { this.id = id this.ownerId = ownerId return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PollDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PollDboEntity.kt index eb6a6c81c..a0ce031ee 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PollDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PollDboEntity.kt @@ -40,7 +40,7 @@ class PollDboEntity : DboEntity() { var background: BackgroundEntity? = null private set - operator fun set(id: Int, ownerId: Long): PollDboEntity { + fun set(id: Int, ownerId: Long): PollDboEntity { this.id = id this.ownerId = ownerId return this @@ -156,7 +156,7 @@ class PollDboEntity : DboEntity() { var rate = 0.0 private set - operator fun set(id: Long, text: String?, voteCount: Int, rate: Double): Answer { + fun set(id: Long, text: String?, voteCount: Int, rate: Double): Answer { this.id = id this.text = text this.voteCount = voteCount diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PostDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PostDboEntity.kt index 3e5c3e45c..2ed3844a7 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PostDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PostDboEntity.kt @@ -72,7 +72,7 @@ class PostDboEntity : DboEntity() { var copyHierarchy: List? = null private set - operator fun set(id: Int, ownerId: Long): PostDboEntity { + fun set(id: Int, ownerId: Long): PostDboEntity { this.id = id this.ownerId = ownerId return this @@ -251,7 +251,7 @@ class PostDboEntity : DboEntity() { var url: String? = null private set - operator fun set(type: Int, platform: String?, data: Int, url: String?): SourceDbo { + fun set(type: Int, platform: String?, data: Int, url: String?): SourceDbo { this.type = type this.platform = platform this.data = data diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PrivacyEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PrivacyEntity.kt index a3b2c025c..c67d71ad9 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PrivacyEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/PrivacyEntity.kt @@ -12,7 +12,7 @@ class PrivacyEntity { var entries: List? = null private set - operator fun set(type: String?, entries: List?): PrivacyEntity { + fun set(type: String?, entries: List?): PrivacyEntity { this.type = type this.entries = entries return this @@ -30,7 +30,7 @@ class PrivacyEntity { var isAllowed = false private set - operator fun set(type: Int, id: Long, allowed: Boolean): Entry { + fun set(type: Int, id: Long, allowed: Boolean): Entry { this.type = type this.id = id isAllowed = allowed diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/StickerDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/StickerDboEntity.kt index b4cb674cd..6b5473d40 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/StickerDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/StickerDboEntity.kt @@ -57,7 +57,7 @@ class StickerDboEntity : DboEntity() { var url: String? = null private set - operator fun set(url: String?, type: String?): AnimationEntity { + fun set(url: String?, type: String?): AnimationEntity { this.url = url this.type = type return this @@ -76,7 +76,7 @@ class StickerDboEntity : DboEntity() { var height = 0 private set - operator fun set(url: String?, width: Int, height: Int): Img { + fun set(url: String?, width: Int, height: Int): Img { this.url = url this.width = width this.height = height diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/StickerSetEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/StickerSetEntity.kt index bc9b14ba0..f860159a3 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/StickerSetEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/StickerSetEntity.kt @@ -66,7 +66,7 @@ class StickerSetEntity(val id: Int) { var height = 0 private set - operator fun set(url: String?, width: Int, height: Int): Img { + fun set(url: String?, width: Int, height: Int): Img { this.url = url this.width = width this.height = height diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/TopicDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/TopicDboEntity.kt index 0b9e13d9d..3dd4941cb 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/TopicDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/TopicDboEntity.kt @@ -35,7 +35,7 @@ class TopicDboEntity : DboEntity() { var poll: PollDboEntity? = null private set - operator fun set(id: Int, ownerId: Long): TopicDboEntity { + fun set(id: Int, ownerId: Long): TopicDboEntity { this.id = id this.ownerId = ownerId return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/UserDetailsEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/UserDetailsEntity.kt index c027a2d30..89e7fcf60 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/UserDetailsEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/UserDetailsEntity.kt @@ -462,7 +462,7 @@ class UserDetailsEntity { var width = 0 private set - operator fun set(url: String?, height: Int, width: Int): CoverImage { + fun set(url: String?, height: Int, width: Int): CoverImage { this.url = url this.height = height this.width = width diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/VideoDboEntity.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/VideoDboEntity.kt index eee215cf6..91a4f7adc 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/VideoDboEntity.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/db/model/entity/VideoDboEntity.kt @@ -85,7 +85,7 @@ class VideoDboEntity : DboEntity() { var trailer: String? = null private set - operator fun set(id: Int, ownerId: Long): VideoDboEntity { + fun set(id: Int, ownerId: Long): VideoDboEntity { this.id = id this.ownerId = ownerId return this diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/dialog/audioduplicate/AudioDuplicateDialog.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/dialog/audioduplicate/AudioDuplicateDialog.kt index d9a7213ae..e8a988ce3 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/dialog/audioduplicate/AudioDuplicateDialog.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/dialog/audioduplicate/AudioDuplicateDialog.kt @@ -42,7 +42,7 @@ import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.toColor import dev.ragnarok.fenrir.util.AppTextUtils import dev.ragnarok.fenrir.util.Utils -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class AudioDuplicateDialog : BaseMvpDialogFragment(), IAudioDuplicateView { @@ -210,7 +210,7 @@ class AudioDuplicateDialog : val play: View = itemView.findViewById(R.id.item_audio_play) val play_cover: ImageView = itemView.findViewById(R.id.item_audio_play_cover) val selectionView: MaterialCardView = itemView.findViewById(R.id.item_audio_selection) - val visual: RLottieImageView = itemView.findViewById(R.id.item_audio_visual) + val visual: ThorVGLottieView = itemView.findViewById(R.id.item_audio_visual) val time: TextView = itemView.findViewById(R.id.item_audio_time) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/IStoriesShortVideosInteractor.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/IStoriesShortVideosInteractor.kt index 67acf664a..ec290b943 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/IStoriesShortVideosInteractor.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/IStoriesShortVideosInteractor.kt @@ -34,4 +34,7 @@ interface IStoriesShortVideosInteractor { startFrom: String?, count: Int? ): Flow, String?>> + + fun subscribe(accountId: Long, owner_id: Long): Flow + fun unsubscribe(accountId: Long, owner_id: Long): Flow } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/StoriesShortVideosInteractor.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/StoriesShortVideosInteractor.kt index b0f5df0b4..1e6ae2124 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/StoriesShortVideosInteractor.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/domain/impl/StoriesShortVideosInteractor.kt @@ -4,7 +4,7 @@ import dev.ragnarok.fenrir.api.Fields import dev.ragnarok.fenrir.api.interfaces.INetworker import dev.ragnarok.fenrir.api.model.AccessIdPair import dev.ragnarok.fenrir.api.model.VKApiStory -import dev.ragnarok.fenrir.api.model.response.StoryBlockResponce +import dev.ragnarok.fenrir.api.model.response.StoryBlockResponse import dev.ragnarok.fenrir.domain.IOwnersRepository import dev.ragnarok.fenrir.domain.IStoriesShortVideosInteractor import dev.ragnarok.fenrir.domain.mappers.Dto2Model @@ -64,7 +64,7 @@ class StoriesShortVideosInteractor( } } - private fun parseStoryBlock(resp: StoryBlockResponce, dtos: MutableList) { + private fun parseStoryBlock(resp: StoryBlockResponse, dtos: MutableList) { resp.stories.nonNullNoEmpty { parseParentStory(it, dtos) dtos.addAll(it) @@ -222,4 +222,16 @@ class StoriesShortVideosInteractor( Pair(dbos, nextFrom) } } + + override fun subscribe(accountId: Long, owner_id: Long): Flow { + return networker.vkDefault(accountId) + .stories() + .subscribe(owner_id) + } + + override fun unsubscribe(accountId: Long, owner_id: Long): Flow { + return networker.vkDefault(accountId) + .stories() + .unsubscribe(owner_id) + } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/PreferencesFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/PreferencesFragment.kt index e5e4afc13..ceae60577 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/PreferencesFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/PreferencesFragment.kt @@ -87,6 +87,7 @@ import dev.ragnarok.fenrir.settings.backup.SettingsBackup import dev.ragnarok.fenrir.util.AppPerms import dev.ragnarok.fenrir.util.AppPerms.requestPermissionsAbs import dev.ragnarok.fenrir.util.Utils +import dev.ragnarok.fenrir.util.coroutines.CancelableJob import dev.ragnarok.fenrir.util.coroutines.CompositeJob import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.delayTaskFlow import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain @@ -97,7 +98,7 @@ import dev.ragnarok.fenrir.util.serializeble.prefs.Preferences import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.util.toast.CustomToast.Companion.createCustomToast import dev.ragnarok.fenrir.view.MySearchView -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import dev.ragnarok.fenrir.view.navigation.AbsNavigationView import java.io.File import java.io.FileOutputStream @@ -109,7 +110,7 @@ class PreferencesFragment : AbsPreferencesFragment(), PreferencesAdapter.OnScree private var layoutManager: LinearLayoutManager? = null private var searchView: MySearchView? = null private val disposables = CompositeJob() - private var sleepDataDisposable = CompositeJob() + private var sleepDataDisposable = CancelableJob() private val SEARCH_DELAY = 2000 override val keyInstanceState: String = "root_preferences" @@ -1954,7 +1955,7 @@ class PreferencesFragment : AbsPreferencesFragment(), PreferencesAdapter.OnScree summary = Utils.getAppVersionName(requireActivity()) onClick { val view = View.inflate(requireActivity(), R.layout.dialog_about_us, null) - val anim: RLottieImageView = view.findViewById(R.id.lottie_animation) + val anim: ThorVGLottieView = view.findViewById(R.id.lottie_animation) val txt: TextView = view.findViewById(dev.ragnarok.fenrir_common.R.id.sub_header) txt.setText(Common.getAboutUsHeader(Settings.get().main().paganSymbol)) @@ -1966,11 +1967,9 @@ class PreferencesFragment : AbsPreferencesFragment(), PreferencesAdapter.OnScree anim.visibility = View.VISIBLE anim.fromRes( cbc.lottieRes, - Utils.dp(cbc.lottie_widthHeight), - Utils.dp(cbc.lottie_widthHeight), cbc.lottie_replacement ) - anim.playAnimation() + anim.startAnimation() } else { anim.visibility = View.GONE } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountAdapter.kt index bdd1b2d98..31623ef43 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountAdapter.kt @@ -19,11 +19,10 @@ import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.toColor import dev.ragnarok.fenrir.util.UserInfoResolveUtil -import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.ViewUtils.displayAvatar import dev.ragnarok.fenrir.util.ViewUtils.getOnlineIcon import dev.ragnarok.fenrir.view.OnlineView -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import kotlin.math.abs class AccountAdapter( @@ -100,17 +99,18 @@ class AccountAdapter( if (isCurrent) { holder.active.fromRes( dev.ragnarok.fenrir_common.R.raw.select_check_box, - Utils.dp(40f), - Utils.dp(40f), intArrayOf( 0x333333, CurrentTheme.getColorPrimary( context ), 0x777777, CurrentTheme.getColorSecondary(context) ) ) - holder.active.playAnimation() + holder.active.startAnimation() } else { - holder.active.clearAnimationDrawable() + holder.active.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } else { if (isCurrent) { @@ -134,7 +134,7 @@ class AccountAdapter( val vOnline: OnlineView = itemView.findViewById(R.id.item_user_online) val tvLastTime: TextView = itemView.findViewById(R.id.last_time) val avatar: ImageView = itemView.findViewById(R.id.avatar) - val active: RLottieImageView = itemView.findViewById(R.id.active) + val active: ThorVGLottieView = itemView.findViewById(R.id.active) val account: View = itemView.findViewById(R.id.account_select) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsFragment.kt index 947b7d513..2c138ff6e 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsFragment.kt @@ -43,6 +43,7 @@ import dev.ragnarok.fenrir.activity.EnterPinActivity import dev.ragnarok.fenrir.activity.FileManagerSelectActivity import dev.ragnarok.fenrir.activity.LoginActivity.Companion.createIntent import dev.ragnarok.fenrir.activity.ProxyManagerActivity +import dev.ragnarok.fenrir.activity.qr.CameraScanActivity import dev.ragnarok.fenrir.api.Auth.scope import dev.ragnarok.fenrir.dialog.directauth.DirectAuthDialog import dev.ragnarok.fenrir.dialog.directauth.DirectAuthDialog.Companion.newInstance @@ -52,6 +53,7 @@ import dev.ragnarok.fenrir.modalbottomsheetdialogfragment.ModalBottomSheetDialog import dev.ragnarok.fenrir.modalbottomsheetdialogfragment.OptionRequest import dev.ragnarok.fenrir.model.Account import dev.ragnarok.fenrir.model.SaveAccount +import dev.ragnarok.fenrir.nonNullNoEmpty import dev.ragnarok.fenrir.place.PlaceFactory.getPreferencesPlace import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.util.AppPerms.hasReadStoragePermission @@ -63,6 +65,8 @@ import dev.ragnarok.fenrir.util.Utils.isHiddenAccount import dev.ragnarok.fenrir.util.ViewUtils.setupSwipeRefreshLayoutWithCurrentTheme import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.util.toast.CustomToast.Companion.createCustomToast +import java.util.Calendar +import java.util.regex.Pattern class AccountsFragment : BaseMvpFragment(), IAccountsView, AccountAdapter.Callback, @@ -641,6 +645,30 @@ class AccountsFragment : BaseMvpFragment(), IA } } + private val requestQRScan = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result -> + if (result.resultCode == RESULT_OK) { + val scanner = result.data?.extras?.getString(Extra.URL) + if (scanner.nonNullNoEmpty()) { + val PATTERN: Pattern = Pattern.compile("qr\\.vk\\.com/w2a[?]q=(\\w+)") + val matcher = PATTERN.matcher(scanner) + try { + if (matcher.find()) { + matcher.group(1) + ?.let { + presenter?.fireAuthByQR(it) + return@registerForActivityResult + } + } + showError(R.string.auth_by_qr_error) + } catch (e: Exception) { + showThrowable(e) + } + } + } + } + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { when (menuItem.itemId) { R.id.action_proxy -> { @@ -699,6 +727,22 @@ class AccountsFragment : BaseMvpFragment(), IA return true } + R.id.auth_by_qr -> { + if (Utils.isOfficialVKCurrent && Settings.get() + .accounts().anonymToken.expired_at <= Calendar.getInstance().time.time / 1000 + ) { + showError(R.string.auth_by_qr_error) + return false + } + val intent = + Intent( + requireActivity(), + CameraScanActivity::class.java + ) + requestQRScan.launch(intent) + return true + } + else -> return false } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsPresenter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsPresenter.kt index 337435d75..898e25922 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsPresenter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/accounts/AccountsPresenter.kt @@ -8,6 +8,7 @@ import android.widget.Toast import dev.ragnarok.fenrir.AccountType import dev.ragnarok.fenrir.Constants import dev.ragnarok.fenrir.Includes +import dev.ragnarok.fenrir.Includes.provideApplicationContext import dev.ragnarok.fenrir.R import dev.ragnarok.fenrir.api.ApiException import dev.ragnarok.fenrir.api.Auth @@ -19,6 +20,7 @@ import dev.ragnarok.fenrir.api.adapters.AbsDtoAdapter.Companion.hasPrimitive import dev.ragnarok.fenrir.api.interfaces.INetworker import dev.ragnarok.fenrir.api.model.VKApiUser import dev.ragnarok.fenrir.api.model.response.BaseResponse +import dev.ragnarok.fenrir.api.model.response.SetAuthCodeStatusResponse import dev.ragnarok.fenrir.api.rest.HttpException import dev.ragnarok.fenrir.api.util.VKStringUtils import dev.ragnarok.fenrir.db.DBHelper @@ -42,6 +44,7 @@ import dev.ragnarok.fenrir.nonNullNoEmpty import dev.ragnarok.fenrir.nonNullNoEmptyOr import dev.ragnarok.fenrir.requireNonNull import dev.ragnarok.fenrir.service.ErrorLocalizer +import dev.ragnarok.fenrir.settings.AnonymToken import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.settings.backup.SettingsBackup import dev.ragnarok.fenrir.toColor @@ -50,6 +53,8 @@ import dev.ragnarok.fenrir.util.ShortcutUtils import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.hiddenIO +import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.inMainThread +import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.isActive import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.syncSingle import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.syncSingleSafe import dev.ragnarok.fenrir.util.serializeble.json.Json @@ -66,7 +71,9 @@ import dev.ragnarok.fenrir.util.serializeble.json.longOrNull import dev.ragnarok.fenrir.util.serializeble.json.put import dev.ragnarok.fenrir.util.serializeble.msgpack.MsgPack import dev.ragnarok.fenrir.util.toast.CustomToast +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map @@ -77,6 +84,7 @@ import okio.buffer import okio.source import java.io.File import java.io.FileOutputStream +import java.util.Calendar import kotlin.coroutines.cancellation.CancellationException class AccountsPresenter(savedInstanceState: Bundle?) : @@ -377,7 +385,7 @@ class AccountsPresenter(savedInstanceState: Bundle?) : out.write(bom) out.write(bytes) out.flush() - Includes.provideApplicationContext().sendBroadcast( + provideApplicationContext().sendBroadcast( Intent( Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file) @@ -566,6 +574,112 @@ class AccountsPresenter(savedInstanceState: Bundle?) : }) { saveAccounts(context, file, null) }) } + private fun checkQRAuthState( + q: String, + token: String, + data: SetAuthCodeStatusResponse + ): Flow { + return flow { + delay((data.polling_delay * 1000).toLong()) + if (isActive() && data.expires_in > Calendar.getInstance().time.time / 1000) { + networker.vkAuth().getAuthCodeStatus( + q, Constants.API_ID, Utils.getDeviceId( + Constants.DEFAULT_ACCOUNT_TYPE, + provideApplicationContext() + ), token, Constants.AUTH_API_VERSION + ).catch { + if (Constants.IS_DEBUG) { + it.printStackTrace() + } + inMainThread { + view?.showThrowable(it) + } + emit(false) + }.collect { + if (it.access_token.nonNullNoEmpty()) { + inMainThread { + processNewAccount( + it.user_id, + it.access_token, + Constants.DEFAULT_ACCOUNT_TYPE, + null, + null, + "fenrir_qr", + isCurrent = true, + needSave = false + ) + view?.showColoredSnack(R.string.refreshing_token, "#AA48BE2D".toColor()) + appendJob( + accountsInteractor.getExchangeToken(it.user_id).fromIOToMain { t -> + t.token.nonNullNoEmpty { st -> + appendJob( + networker.vkDirectAuth(Constants.DEFAULT_ACCOUNT_TYPE) + .authByExchangeToken( + Constants.API_ID, + Constants.API_ID, + st, + Auth.scope, + "expired_token", + Utils.getDeviceId( + Constants.DEFAULT_ACCOUNT_TYPE, + provideApplicationContext() + ), + "1.102", + null, + Constants.API_VERSION + ).fromIOToMain({ p -> + val aToken = p.resultUrl?.let { it1 -> + tryExtractAccessToken(it1) + } ?: return@fromIOToMain + Settings.get().accounts() + .storeAccessToken(it.user_id, aToken) + + view?.showColoredSnack( + R.string.success, + "#AA48BE2D".toColor() + ) + }, { + view?.showThrowable(it) + }) + ) + } + } + ) + } + emit(true) + } else { + checkQRAuthState(q, token, data).collect { + emit(it) + } + } + } + } else { + inMainThread { + view?.showError(R.string.auth_by_qr_error) + } + emit(false) + } + } + } + + fun fireAuthByQR(q: String) { + Settings.get().accounts().anonymToken.token?.let { + appendJob( + networker.vkAuth().setAuthCodeStatus( + q, Constants.API_ID, Utils.getDeviceId( + Constants.DEFAULT_ACCOUNT_TYPE, + provideApplicationContext() + ), it, Constants.AUTH_API_VERSION + ) + .fromIOToMain({ rt -> + if (rt.status != 0 && rt.polling_delay > 0) { + appendJob(checkQRAuthState(q, it, rt).hiddenIO()) + } + }, { showError(it) }) + ) + } + } + @Suppress("DEPRECATION") private fun saveAccounts(context: Context, file: File, Users: IOwnersBundle?) { var out: FileOutputStream? = null @@ -636,7 +750,7 @@ class AccountsPresenter(savedInstanceState: Bundle?) : out.write(bom) out.write(bytes) out.flush() - Includes.provideApplicationContext().sendBroadcast( + provideApplicationContext().sendBroadcast( Intent( Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file) @@ -673,7 +787,7 @@ class AccountsPresenter(savedInstanceState: Bundle?) : .add("lang", Constants.DEVICE_COUNTRY_CODE) .add("https", "1") .add( - "device_id", Utils.getDeviceId(type, Includes.provideApplicationContext()) + "device_id", Utils.getDeviceId(type, provideApplicationContext()) ) return Includes.networkInterfaces.getVkRestProvider().provideRawHttpClient(type, null) .flatMapConcat { client -> @@ -722,5 +836,24 @@ class AccountsPresenter(savedInstanceState: Bundle?) : init { fireLoad(false) + + if (Utils.isOfficialVKCurrent && Settings.get() + .accounts().anonymToken.expired_at <= Calendar.getInstance().time.time / 1000 + ) { + appendJob(networker.vkDirectAuth().get_anonym_token( + Constants.API_ID, + Constants.API_ID, + Constants.SECRET, + Constants.AUTH_API_VERSION, + Utils.getDeviceId( + Constants.DEFAULT_ACCOUNT_TYPE, + provideApplicationContext() + ) + ).fromIOToMain { + if (it.token.nonNullNoEmpty() && it.expired_at > Calendar.getInstance().time.time / 1000) { + Settings.get().accounts().anonymToken = AnonymToken().set(it) + } + }) + } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/AudioPlayerFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/AudioPlayerFragment.kt index 95e13d502..f48fb541d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/AudioPlayerFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/AudioPlayerFragment.kt @@ -64,7 +64,7 @@ import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.util.toast.CustomToast.Companion.createCustomToast import dev.ragnarok.fenrir.view.CustomSeekBar import dev.ragnarok.fenrir.view.media.* -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieShapeableImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieShapeableView import kotlinx.coroutines.Job import java.io.File import java.io.FileOutputStream @@ -706,7 +706,7 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee } if (tvAlbum != null) { var album = "" - if (audioTrack?.album_title.nonNullNoEmpty()) album += requireActivity().getString(R.string.album) + " " + audioTrack?.album_title + if (audioTrack?.album_title.nonNullNoEmpty()) album += requireActivity().getString(R.string.album) + " " + audioTrack.album_title tvAlbum?.text = album } tvTitle?.text = audioTrack?.artist @@ -1019,7 +1019,7 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee } private inner class CoverViewHolder(view: View) : RecyclerView.ViewHolder(view) { - val ivCover: RLottieShapeableImageView = view.findViewById(R.id.cover) + val ivCover: ThorVGLottieShapeableView = view.findViewById(R.id.cover) val holderTarget = object : BitmapTarget { override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) { @@ -1038,8 +1038,6 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee if (FenrirNative.isNativeLoaded) { ivCover.fromRes( Common.getPlayerNullArtAnimation(Settings.get().main().paganSymbol), - 450, - 450, intArrayOf( 0x333333, CurrentTheme.getColorSurface(requireActivity()), @@ -1047,7 +1045,7 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee CurrentTheme.getColorOnSurface(requireActivity()) ) ) - ivCover.playAnimation() + ivCover.startAnimation() } else { ivCover.setImageResource(R.drawable.itunes) ivCover.drawable?.setTint( @@ -1077,8 +1075,6 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee if (FenrirNative.isNativeLoaded) { ivCover.fromRes( Common.getPlayerNullArtAnimation(Settings.get().main().paganSymbol), - 450, - 450, intArrayOf( 0x333333, CurrentTheme.getColorSurface(requireActivity()), @@ -1086,7 +1082,7 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee CurrentTheme.getColorOnSurface(requireActivity()) ) ) - ivCover.playAnimation() + ivCover.startAnimation() } else { ivCover.setImageResource(R.drawable.itunes) ivCover.drawable.setTint(CurrentTheme.getColorOnSurface(requireActivity())) @@ -1118,16 +1114,6 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee override fun onViewDetachedFromWindow(holder: CoverViewHolder) { super.onViewDetachedFromWindow(holder) PicassoInstance.with().cancelRequest(holder.ivCover) - if (holder.ivCover.drawable is Animatable) { - (holder.ivCover.drawable as Animatable).stop() - } - } - - override fun onViewAttachedToWindow(holder: CoverViewHolder) { - super.onViewAttachedToWindow(holder) - if (holder.ivCover.drawable is Animatable) { - (holder.ivCover.drawable as Animatable).start() - } } override fun onBindViewHolder(holder: CoverViewHolder, position: Int) { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/audios/AudioRecyclerAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/audios/AudioRecyclerAdapter.kt index 9ac62220a..a27b375c4 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/audios/AudioRecyclerAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/audios/AudioRecyclerAdapter.kt @@ -66,7 +66,7 @@ import dev.ragnarok.fenrir.util.hls.M3U8 import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.util.toast.CustomToast.Companion.createCustomToast import dev.ragnarok.fenrir.view.WeakViewAnimatorAdapter -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class AudioRecyclerAdapter( context: Context, @@ -843,7 +843,7 @@ class AudioRecyclerAdapter( val title: TextView = itemView.findViewById(R.id.dialog_message) val play: View = itemView.findViewById(R.id.item_audio_play) val play_cover: ImageView = itemView.findViewById(R.id.item_audio_play_cover) - val visual: RLottieImageView = itemView.findViewById(R.id.item_audio_visual) + val visual: ThorVGLottieView = itemView.findViewById(R.id.item_audio_visual) val time: TextView = itemView.findViewById(R.id.item_audio_time) val saved: ImageView = itemView.findViewById(R.id.saved) val lyric: ImageView = itemView.findViewById(R.id.lyric) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/catalog_v2/lists/CatalogV2ListFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/catalog_v2/lists/CatalogV2ListFragment.kt index 707873e5e..82d48ca30 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/catalog_v2/lists/CatalogV2ListFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/audio/catalog_v2/lists/CatalogV2ListFragment.kt @@ -43,13 +43,13 @@ import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.coroutines.CancelableJob import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.delayTaskFlow import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.toMain -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class CatalogV2ListFragment : BaseMvpFragment(), ICatalogV2ListView, MenuProvider { private var viewPager: ViewPager2? = null private var mAdapter: Adapter? = null - private var loading: RLottieImageView? = null + private var loading: ThorVGLottieView? = null private var animLoad: ObjectAnimator? = null private var animationDispose = CancelableJob() private var mAnimationLoaded = false @@ -161,13 +161,19 @@ class CatalogV2ListFragment : BaseMvpFragment(private notifyItemRangeInserted(position + headersCount + size, items.size - position) } - operator fun set(position: Int, item: T) { + fun set(position: Int, item: T) { items[position] = item notifyItemChanged(position + headersCount) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/base/compat/ViewHostDelegate.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/base/compat/ViewHostDelegate.kt index 078220179..2413d40ca 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/base/compat/ViewHostDelegate.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/base/compat/ViewHostDelegate.kt @@ -86,9 +86,9 @@ class ViewHostDelegate

, V : IMvpView> { } fun onSaveInstanceState(outState: Bundle) { - presenter?.run { + presenter?.let { lastKnownPresenterState = Bundle() - saveState(lastKnownPresenterState ?: return@run) + it.saveState(lastKnownPresenterState ?: return@let) } outState.putBundle(SAVE_PRESENTER_STATE, lastKnownPresenterState) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/fave/favephotos/FavePhotosAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/fave/favephotos/FavePhotosAdapter.kt index eb15957b1..e04de9a4f 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/fave/favephotos/FavePhotosAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/fave/favephotos/FavePhotosAdapter.kt @@ -16,7 +16,7 @@ import dev.ragnarok.fenrir.picasso.PicassoInstance.Companion.with import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.util.AppTextUtils import dev.ragnarok.fenrir.util.Utils -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class FavePhotosAdapter(context: Context, private var data: List) : RecyclerView.Adapter() { @@ -54,15 +54,16 @@ class FavePhotosAdapter(context: Context, private var data: List) : viewHolder.current.visibility = View.VISIBLE viewHolder.current.fromRes( dev.ragnarok.fenrir_common.R.raw.donater_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - viewHolder.current.playAnimation() + viewHolder.current.startAnimation() } else { viewHolder.current.visibility = View.GONE - viewHolder.current.clearAnimationDrawable() + viewHolder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } with() @@ -125,6 +126,6 @@ class FavePhotosAdapter(context: Context, private var data: List) : val tvComment: TextView = itemView.findViewById(R.id.vk_photo_item_comment_counter) val ivLike: ImageView = itemView.findViewById(R.id.vk_photo_item_like) val ivComment: ImageView = itemView.findViewById(R.id.vk_photo_item_comment) - val current: RLottieImageView = itemView.findViewById(R.id.current) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/filemanagerselect/FileManagerSelectFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/filemanagerselect/FileManagerSelectFragment.kt index dab673eec..66da0045c 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/filemanagerselect/FileManagerSelectFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/filemanagerselect/FileManagerSelectFragment.kt @@ -26,13 +26,12 @@ import dev.ragnarok.fenrir.listener.PicassoPauseOnScrollListener import dev.ragnarok.fenrir.listener.UpdatableNavigation import dev.ragnarok.fenrir.model.FileItem import dev.ragnarok.fenrir.settings.CurrentTheme -import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.coroutines.CancelableJob import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.delayTaskFlow import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.toMain import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.view.MySearchView -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import java.io.File class FileManagerSelectFragment : @@ -41,7 +40,7 @@ class FileManagerSelectFragment : private var mRecyclerView: RecyclerView? = null private var mLayoutManager: GridLayoutManager? = null private var empty: TextView? = null - private var loading: RLottieImageView? = null + private var loading: ThorVGLottieView? = null private var tvCurrentDir: TextView? = null private var mAdapter: FileManagerSelectAdapter? = null private var mSelected: FloatingActionButton? = null @@ -113,13 +112,19 @@ class FileManagerSelectFragment : animLoad = ObjectAnimator.ofFloat(loading, View.ALPHA, 0.0f).setDuration(1000) animLoad?.addListener(object : StubAnimatorListener() { override fun onAnimationEnd(animation: Animator) { - loading?.clearAnimationDrawable() + loading?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) loading?.visibility = View.GONE loading?.alpha = 1f } override fun onAnimationCancel(animation: Animator) { - loading?.clearAnimationDrawable() + loading?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) loading?.visibility = View.GONE loading?.alpha = 1f } @@ -189,8 +194,6 @@ class FileManagerSelectFragment : loading?.alpha = 1f loading?.fromRes( dev.ragnarok.fenrir_common.R.raw.s_loading, - Utils.dp(180f), - Utils.dp(180f), intArrayOf( 0x333333, CurrentTheme.getColorPrimary(requireActivity()), @@ -198,7 +201,7 @@ class FileManagerSelectFragment : CurrentTheme.getColorSecondary(requireActivity()) ) ) - loading?.playAnimation() + loading?.startAnimation() } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/audioslocalserver/AudioLocalServerRecyclerAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/audioslocalserver/AudioLocalServerRecyclerAdapter.kt index c7fefe1a0..3b2e49d2d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/audioslocalserver/AudioLocalServerRecyclerAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/audioslocalserver/AudioLocalServerRecyclerAdapter.kt @@ -53,7 +53,7 @@ import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.sharedFlowToMain import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.util.toast.CustomToast.Companion.createCustomToast import dev.ragnarok.fenrir.view.WeakViewAnimatorAdapter -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -560,7 +560,7 @@ class AudioLocalServerRecyclerAdapter( view.visibility = View.GONE } } - val visual: RLottieImageView = itemView.findViewById(R.id.item_audio_visual) + val visual: ThorVGLottieView = itemView.findViewById(R.id.item_audio_visual) val time: TextView = itemView.findViewById(R.id.item_audio_time) var animator: ObjectAnimator? = null fun startSelectionAnimation() { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/filemanagerremote/FileManagerRemoteAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/filemanagerremote/FileManagerRemoteAdapter.kt index 730fb14d7..f77de59e5 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/filemanagerremote/FileManagerRemoteAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/filemanagerremote/FileManagerRemoteAdapter.kt @@ -41,7 +41,7 @@ import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.sharedFlowToMain import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.util.toast.CustomToast.Companion.createCustomToast -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class FileManagerRemoteAdapter(private var context: Context, private var data: List) : RecyclerView.Adapter() { @@ -435,15 +435,16 @@ class FileManagerRemoteAdapter(private var context: Context, private var data: L holder.current.visibility = View.VISIBLE holder.current.fromRes( dev.ragnarok.fenrir_common.R.raw.select_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - holder.current.playAnimation() + holder.current.startAnimation() } else { holder.current.visibility = View.GONE - holder.current.clearAnimationDrawable() + holder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } holder.fileInfo.setBackgroundColor(messageBubbleColor) @@ -624,15 +625,16 @@ class FileManagerRemoteAdapter(private var context: Context, private var data: L holder.current.visibility = View.VISIBLE holder.current.fromRes( dev.ragnarok.fenrir_common.R.raw.select_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - holder.current.playAnimation() + holder.current.startAnimation() } else { holder.current.visibility = View.GONE - holder.current.clearAnimationDrawable() + holder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } holder.fileInfo.setBackgroundColor(messageBubbleColor) @@ -675,7 +677,7 @@ class FileManagerRemoteAdapter(private var context: Context, private var data: L val fileName: TextView = itemView.findViewById(R.id.item_file_name) val fileDetails: TextView = itemView.findViewById(R.id.item_file_details) val icon: ImageView = itemView.findViewById(R.id.item_file_icon) - val current: RLottieImageView = itemView.findViewById(R.id.current) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) val fileInfo: LinearLayout = itemView.findViewById(R.id.item_file_info) } @@ -683,8 +685,8 @@ class FileManagerRemoteAdapter(private var context: Context, private var data: L val fileName: TextView = itemView.findViewById(R.id.item_file_name) val fileDetails: TextView = itemView.findViewById(R.id.item_file_details) val icon: ImageView = itemView.findViewById(R.id.item_file_icon) - val current: RLottieImageView = itemView.findViewById(R.id.current) - val visual: RLottieImageView = itemView.findViewById(R.id.item_audio_visual) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) + val visual: ThorVGLottieView = itemView.findViewById(R.id.item_audio_visual) val fileInfo: LinearLayout = itemView.findViewById(R.id.item_file_info) } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/filemanagerremote/FileManagerRemoteFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/filemanagerremote/FileManagerRemoteFragment.kt index 2502482db..665ddc146 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/filemanagerremote/FileManagerRemoteFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/filemanagerremote/FileManagerRemoteFragment.kt @@ -40,7 +40,6 @@ import dev.ragnarok.fenrir.place.PlaceFactory import dev.ragnarok.fenrir.service.ErrorLocalizer import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.settings.Settings -import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.ViewUtils import dev.ragnarok.fenrir.util.coroutines.CancelableJob import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.delayTaskFlow @@ -48,7 +47,7 @@ import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.toMain import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.util.toast.CustomToast import dev.ragnarok.fenrir.view.MySearchView -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class FileManagerRemoteFragment : BaseMvpFragment(), @@ -56,7 +55,7 @@ class FileManagerRemoteFragment : private var mRecyclerView: RecyclerView? = null private var mLayoutManager: GridLayoutManager? = null private var empty: TextView? = null - private var loading: RLottieImageView? = null + private var loading: ThorVGLottieView? = null private var tvCurrentDir: TextView? = null private var mAdapter: FileManagerRemoteAdapter? = null private var mSwipeRefreshLayout: SwipeRefreshLayout? = null @@ -126,13 +125,19 @@ class FileManagerRemoteFragment : animLoad = ObjectAnimator.ofFloat(loading, View.ALPHA, 0.0f).setDuration(1000) animLoad?.addListener(object : StubAnimatorListener() { override fun onAnimationEnd(animation: Animator) { - loading?.clearAnimationDrawable() + loading?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) loading?.visibility = View.GONE loading?.alpha = 1f } override fun onAnimationCancel(animation: Animator) { - loading?.clearAnimationDrawable() + loading?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) loading?.visibility = View.GONE loading?.alpha = 1f } @@ -205,8 +210,6 @@ class FileManagerRemoteFragment : loading?.alpha = 1f loading?.fromRes( dev.ragnarok.fenrir_common.R.raw.s_loading, - Utils.dp(180f), - Utils.dp(180f), intArrayOf( 0x333333, CurrentTheme.getColorPrimary(requireActivity()), @@ -214,7 +217,7 @@ class FileManagerRemoteFragment : CurrentTheme.getColorSecondary(requireActivity()) ) ) - loading?.playAnimation() + loading?.startAnimation() } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/photoslocalserver/LocalServerPhotosAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/photoslocalserver/LocalServerPhotosAdapter.kt index 5395a6e71..6ae3f1d3d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/photoslocalserver/LocalServerPhotosAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/localserver/photoslocalserver/LocalServerPhotosAdapter.kt @@ -17,7 +17,7 @@ import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.util.AppTextUtils import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.view.AspectRatioImageView -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class LocalServerPhotosAdapter(private val mContext: Context, private var data: List) : RecyclerView.Adapter() { @@ -47,15 +47,16 @@ class LocalServerPhotosAdapter(private val mContext: Context, private var data: viewHolder.current.visibility = View.VISIBLE viewHolder.current.fromRes( dev.ragnarok.fenrir_common.R.raw.donater_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - viewHolder.current.playAnimation() + viewHolder.current.startAnimation() } else { viewHolder.current.visibility = View.GONE - viewHolder.current.clearAnimationDrawable() + viewHolder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } val targetUrl = photo.getUrlForSize(PhotoSize.X, false) @@ -98,7 +99,7 @@ class LocalServerPhotosAdapter(private val mContext: Context, private var data: val photoImageView: AspectRatioImageView = itemView.findViewById(R.id.imageView) val tvDate: TextView = itemView.findViewById(R.id.vk_photo_item_date) val bottomTop: ViewGroup = itemView.findViewById(R.id.vk_photo_item_top) - val current: RLottieImageView = itemView.findViewById(R.id.current) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) } } \ No newline at end of file diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/ChatFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/ChatFragment.kt index 16e3ee06f..fbc30e962 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/ChatFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/ChatFragment.kt @@ -136,7 +136,7 @@ import dev.ragnarok.fenrir.view.emoji.BotKeyboardView import dev.ragnarok.fenrir.view.emoji.EmojiconTextView import dev.ragnarok.fenrir.view.emoji.EmojiconsPopup import dev.ragnarok.fenrir.view.emoji.StickersKeyWordsAdapter -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import me.minetsh.imaging.IMGEditActivity import java.io.File import java.lang.ref.WeakReference @@ -162,7 +162,7 @@ class ChatFragment : PlaceSupportMvpFragment(), IChatV private var inputViewController: InputViewController? = null private var emptyText: TextView? = null - private var emptyAnimation: RLottieImageView? = null + private var emptyAnimation: ThorVGLottieView? = null private var pinnedView: View? = null private var pinnedAvatar: ImageView? = null @@ -1843,8 +1843,6 @@ class ChatFragment : PlaceSupportMvpFragment(), IChatV if (visible) { emptyAnimation?.fromRes( dev.ragnarok.fenrir_common.R.raw.valknut, - Utils.dp(120f), - Utils.dp(120f), intArrayOf( 0x333333, CurrentTheme.getColorPrimary(requireActivity()), @@ -1852,9 +1850,12 @@ class ChatFragment : PlaceSupportMvpFragment(), IChatV CurrentTheme.getColorSecondary(requireActivity()) ) ) - emptyAnimation?.playAnimation() + emptyAnimation?.startAnimation() } else { - emptyAnimation?.clearAnimationDrawable() + emptyAnimation?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/MessagesAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/MessagesAdapter.kt index 8ff1271de..157d6903f 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/MessagesAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/chat/MessagesAdapter.kt @@ -49,7 +49,7 @@ import dev.ragnarok.fenrir.view.ReactionContainer import dev.ragnarok.fenrir.view.emoji.BotKeyboardView import dev.ragnarok.fenrir.view.emoji.BotKeyboardView.BotKeyboardViewDelegate import dev.ragnarok.fenrir.view.emoji.EmojiconTextView -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import java.text.SimpleDateFormat import java.util.Date @@ -127,8 +127,7 @@ class MessagesAdapter( holder.sticker.fromNet( sticker.getAnimationByType(if (isNightSticker) "dark" else "light"), Utils.createOkHttp(Constants.GIF_TIMEOUT, true), - Utils.dp(128f), - Utils.dp(128f) + true ) } else { val image = sticker?.getImage(256, isNightSticker) @@ -639,7 +638,7 @@ class MessagesAdapter( private class StickerMessageHolder(itemView: View) : BaseMessageHolder(itemView) { - val sticker: RLottieImageView = itemView.findViewById(R.id.sticker) + val sticker: ThorVGLottieView = itemView.findViewById(R.id.sticker) val attachmentsRoot: View = itemView.findViewById(R.id.item_message_attachment_container) val attachmentsHolder: AttachmentsHolder = AttachmentsHolder() val forwardMessagesRoot: ViewGroup = itemView.findViewById(R.id.forward_messages) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/localjsontochat/LocalJsonToChatFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/localjsontochat/LocalJsonToChatFragment.kt index 5b9a65eb1..c2cf1af28 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/localjsontochat/LocalJsonToChatFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/messages/localjsontochat/LocalJsonToChatFragment.kt @@ -37,13 +37,13 @@ import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.coroutines.CancelableJob import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.delayTaskFlow import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.toMain -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class LocalJsonToChatFragment : PlaceSupportMvpFragment(), ILocalJsonToChatView, OnMessageActionListener { private var mEmpty: TextView? = null - private var mLoadingProgressBar: RLottieImageView? = null + private var mLoadingProgressBar: ThorVGLottieView? = null private var mLoadingProgressBarDispose = CancelableJob() private var mLoadingProgressBarLoaded = false private var mAdapter: MessagesAdapter? = null @@ -225,13 +225,19 @@ class LocalJsonToChatFragment : val k = ObjectAnimator.ofFloat(mLoadingProgressBar, View.ALPHA, 0.0f).setDuration(1000) k.addListener(object : StubAnimatorListener() { override fun onAnimationEnd(animation: Animator) { - mLoadingProgressBar?.clearAnimationDrawable() + mLoadingProgressBar?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) mLoadingProgressBar?.visibility = View.GONE mLoadingProgressBar?.alpha = 1f } override fun onAnimationCancel(animation: Animator) { - mLoadingProgressBar?.clearAnimationDrawable() + mLoadingProgressBar?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) mLoadingProgressBar?.visibility = View.GONE mLoadingProgressBar?.alpha = 1f } @@ -243,8 +249,6 @@ class LocalJsonToChatFragment : mLoadingProgressBar?.visibility = View.VISIBLE mLoadingProgressBar?.fromRes( dev.ragnarok.fenrir_common.R.raw.loading, - Utils.dp(100F), - Utils.dp(100F), intArrayOf( 0x000000, CurrentTheme.getColorPrimary(requireActivity()), @@ -252,7 +256,7 @@ class LocalJsonToChatFragment : CurrentTheme.getColorSecondary(requireActivity()) ) ) - mLoadingProgressBar?.playAnimation() + mLoadingProgressBar?.startAnimation() } } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/photos/vkphotos/BigVKPhotosAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/photos/vkphotos/BigVKPhotosAdapter.kt index 2748d51d7..f8273e62d 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/photos/vkphotos/BigVKPhotosAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/photos/vkphotos/BigVKPhotosAdapter.kt @@ -25,7 +25,7 @@ import dev.ragnarok.fenrir.util.AppTextUtils import dev.ragnarok.fenrir.util.Logger import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.view.CircleRoadProgress -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class BigVKPhotosAdapter( private val mContext: Context, @@ -126,15 +126,16 @@ class BigVKPhotosAdapter( holder.current.visibility = View.VISIBLE holder.current.fromRes( dev.ragnarok.fenrir_common.R.raw.donater_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, CurrentTheme.getColorPrimary(mContext)), true ) - holder.current.playAnimation() + holder.current.startAnimation() } else { holder.current.visibility = View.GONE - holder.current.clearAnimationDrawable() + holder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } holder.setSelected(photoWrapper.isSelected) @@ -263,7 +264,7 @@ class BigVKPhotosAdapter( val ivLike: ImageView = itemView.findViewById(R.id.vk_photo_item_like) val ivComment: ImageView = itemView.findViewById(R.id.vk_photo_item_comment) val ivDownload: ImageView = itemView.findViewById(R.id.is_downloaded) - val current: RLottieImageView = itemView.findViewById(R.id.current) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) fun setSelected(selected: Boolean) { index.visibility = if (selected) View.VISIBLE else View.GONE darkView.visibility = if (selected) View.VISIBLE else View.GONE diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/search/photosearch/SearchPhotosAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/search/photosearch/SearchPhotosAdapter.kt index adf536a70..3fdc74484 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/search/photosearch/SearchPhotosAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/search/photosearch/SearchPhotosAdapter.kt @@ -16,7 +16,7 @@ import dev.ragnarok.fenrir.picasso.PicassoInstance.Companion.with import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.util.AppTextUtils import dev.ragnarok.fenrir.util.Utils -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class SearchPhotosAdapter( private val mContext: Context, @@ -62,15 +62,16 @@ class SearchPhotosAdapter( holder.current.visibility = View.VISIBLE holder.current.fromRes( dev.ragnarok.fenrir_common.R.raw.donater_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - holder.current.playAnimation() + holder.current.startAnimation() } else { holder.current.visibility = View.GONE - holder.current.clearAnimationDrawable() + holder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } holder.tvLike.text = AppTextUtils.getCounterWithK(photo.likesCount) @@ -125,7 +126,7 @@ class SearchPhotosAdapter( val ivLike: ImageView = itemView.findViewById(R.id.vk_photo_item_like) val ivComment: ImageView = itemView.findViewById(R.id.vk_photo_item_comment) val ivDownload: ImageView = itemView.findViewById(R.id.is_downloaded) - val current: RLottieImageView = itemView.findViewById(R.id.current) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) fun setSelected(selected: Boolean) { index.visibility = if (selected) View.VISIBLE else View.GONE darkView.visibility = if (selected) View.VISIBLE else View.GONE diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/theme/ThemeAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/theme/ThemeAdapter.kt index d0b0e8090..9f0e8c6ff 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/theme/ThemeAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/theme/ThemeAdapter.kt @@ -16,8 +16,7 @@ import dev.ragnarok.fenrir.settings.CurrentTheme.getColorSecondary import dev.ragnarok.fenrir.settings.CurrentTheme.getColorWhiteContrastFix import dev.ragnarok.fenrir.settings.Settings.get import dev.ragnarok.fenrir.settings.theme.ThemeValue -import dev.ragnarok.fenrir.util.Utils -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class ThemeAdapter(private var data: List, context: Context) : RecyclerView.Adapter() { @@ -75,8 +74,6 @@ class ThemeAdapter(private var data: List, context: Context) : if (isSelected) { holder.selected.fromRes( dev.ragnarok.fenrir_common.R.raw.theme_selected, - Utils.dp(120f), - Utils.dp(120f), intArrayOf( 0x333333, getColorWhiteContrastFix(holder.selected.context), @@ -86,9 +83,12 @@ class ThemeAdapter(private var data: List, context: Context) : getColorSecondary(holder.selected.context) ) ) - holder.selected.playAnimation() + holder.selected.startAnimation() } else { - holder.selected.clearAnimationDrawable() + holder.selected.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } else { if (isSelected) { @@ -113,8 +113,6 @@ class ThemeAdapter(private var data: List, context: Context) : if (isSelected) { holder.selected.fromRes( dev.ragnarok.fenrir_common.R.raw.theme_selected, - Utils.dp(120f), - Utils.dp(120f), intArrayOf( 0x333333, getColorWhiteContrastFix(holder.selected.context), @@ -124,9 +122,12 @@ class ThemeAdapter(private var data: List, context: Context) : getColorSecondary(holder.selected.context) ) ) - holder.selected.playAnimation() + holder.selected.startAnimation() } else { - holder.selected.clearAnimationDrawable() + holder.selected.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } else { if (isSelected) { @@ -169,7 +170,7 @@ class ThemeAdapter(private var data: List, context: Context) : internal class ThemeHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val primary: ImageView = itemView.findViewById(R.id.theme_icon_primary) val secondary: ImageView = itemView.findViewById(R.id.theme_icon_secondary) - val selected: RLottieImageView = itemView.findViewById(R.id.selected) + val selected: ThorVGLottieView = itemView.findViewById(R.id.selected) val gradient: ImageView = itemView.findViewById(R.id.theme_icon_gradient) val clicked: ViewGroup = itemView.findViewById(R.id.theme_type) val title: TextView = itemView.findViewById(R.id.item_title) @@ -177,7 +178,7 @@ class ThemeAdapter(private var data: List, context: Context) : } internal class SpecialThemeHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - val selected: RLottieImageView = itemView.findViewById(R.id.selected) + val selected: ThorVGLottieView = itemView.findViewById(R.id.selected) val clicked: ViewGroup = itemView.findViewById(R.id.theme_type) val title: TextView = itemView.findViewById(R.id.item_title) val special_title: TextView = itemView.findViewById(R.id.special_text) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/videos/videopreview/VideoPreviewFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/videos/videopreview/VideoPreviewFragment.kt index 570238646..512b1c54f 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/videos/videopreview/VideoPreviewFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/videos/videopreview/VideoPreviewFragment.kt @@ -309,7 +309,7 @@ class VideoPreviewFragment : BaseMvpFragment> : private var mUploadAdapter: DocsUploadAdapter? = null private var mUploadRoot: View? = null - protected fun setupPaganContent(Runes: View?, paganSymbol: RLottieImageView?) { + protected fun setupPaganContent(Runes: View?, paganSymbol: ThorVGLottieView?) { Runes?.visibility = if (Settings.get() .main().isRunes_show ) View.VISIBLE else View.GONE @@ -133,14 +133,12 @@ abstract class AbsWallFragment> : if (pic.isAnimation) { paganSymbol?.fromRes( pic.lottieRes, - dp(pic.lottie_widthHeight), - dp(pic.lottie_widthHeight), pic.lottie_replacement, pic.lottie_useMoveColor ) - paganSymbol?.playAnimation() + paganSymbol?.startAnimation() } else { paganSymbol?.setImageBitmap( - ThorVGRender.createBitmap( + ThorVGSVGRender.createBitmap( pic.iconRes, dp(pic.icon_width), dp(pic.icon_height) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/groupwall/GroupWallFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/groupwall/GroupWallFragment.kt index 69a0c28f1..bd3d05931 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/groupwall/GroupWallFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/groupwall/GroupWallFragment.kt @@ -65,11 +65,10 @@ import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.toColor import dev.ragnarok.fenrir.util.Utils -import dev.ragnarok.fenrir.util.Utils.dp import dev.ragnarok.fenrir.util.Utils.getVerifiedColor import dev.ragnarok.fenrir.util.Utils.setBackgroundTint import dev.ragnarok.fenrir.view.ProfileCoverDrawable -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import kotlin.math.abs class GroupWallFragment : AbsWallFragment(), IGroupWallView { @@ -136,7 +135,7 @@ class GroupWallFragment : AbsWallFragment(), val donate_anim = Settings.get().main().donate_anim_set if (donate_anim > 0 && community.isDonated) { mHeaderHolder?.bDonate?.visibility = View.VISIBLE - mHeaderHolder?.bDonate?.setAutoRepeat(true) + mHeaderHolder?.bDonate?.setRepeat(true) if (donate_anim == 2) { val cur = Settings.get().ui().mainThemeKey if ("fire" == cur || "yellow_violet" == cur) { @@ -145,8 +144,6 @@ class GroupWallFragment : AbsWallFragment(), setBackgroundTint(mHeaderHolder?.ivVerified, "#df9d00".toColor()) mHeaderHolder?.bDonate?.fromRes( dev.ragnarok.fenrir_common.R.raw.donater_fire, - dp(100f), - dp(100f) ) } else { mHeaderHolder?.tvName?.setTextColor(CurrentTheme.getColorPrimary(requireActivity())) @@ -161,8 +158,6 @@ class GroupWallFragment : AbsWallFragment(), ) mHeaderHolder?.bDonate?.fromRes( dev.ragnarok.fenrir_common.R.raw.donater_fire, - dp(100f), - dp(100f), intArrayOf(0xFF812E, CurrentTheme.getColorPrimary(requireActivity())), true ) @@ -170,8 +165,6 @@ class GroupWallFragment : AbsWallFragment(), } else { mHeaderHolder?.bDonate?.fromRes( dev.ragnarok.fenrir_common.R.raw.donater, - dp(100f), - dp(100f), intArrayOf( 0xffffff, CurrentTheme.getColorPrimary(requireActivity()), @@ -180,7 +173,7 @@ class GroupWallFragment : AbsWallFragment(), ) ) } - mHeaderHolder?.bDonate?.playAnimation() + mHeaderHolder?.bDonate?.startAnimation() } else { mHeaderHolder?.bDonate?.setImageDrawable(null) mHeaderHolder?.bDonate?.visibility = View.GONE @@ -212,20 +205,18 @@ class GroupWallFragment : AbsWallFragment(), if (community.isBlacklisted) { mHeaderHolder?.blacklisted?.visibility = View.VISIBLE if (FenrirNative.isNativeLoaded) { - mHeaderHolder?.blacklisted?.fromRes( - dev.ragnarok.fenrir_common.R.raw.skull, - dp(48f), - dp(48f), - null - ) - mHeaderHolder?.blacklisted?.playAnimation() + mHeaderHolder?.blacklisted?.fromRes(dev.ragnarok.fenrir_common.R.raw.skull) + mHeaderHolder?.blacklisted?.startAnimation() } else { mHeaderHolder?.blacklisted?.setImageResource(R.drawable.audio_died) mHeaderHolder?.blacklisted?.setColorFilter("#AAFF0000".toColor()) } } else { mHeaderHolder?.blacklisted?.visibility = View.GONE - mHeaderHolder?.blacklisted?.clearAnimationDrawable() + mHeaderHolder?.blacklisted?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } mHeaderHolder?.blacklisted?.visibility = if (community.isBlacklisted) View.VISIBLE else View.GONE @@ -569,11 +560,11 @@ class GroupWallFragment : AbsWallFragment(), } private inner class GroupHeaderHolder(root: View) { - val blacklisted: RLottieImageView = root.findViewById(R.id.item_blacklisted) + val blacklisted: ThorVGLottieView = root.findViewById(R.id.item_blacklisted) val vgCover: ViewGroup = root.findViewById(R.id.cover) val ivAvatar: ImageView = root.findViewById(R.id.header_group_avatar) val ivVerified: ImageView = root.findViewById(R.id.item_verified) - val bDonate: RLottieImageView = root.findViewById(R.id.donated_anim) + val bDonate: ThorVGLottieView = root.findViewById(R.id.donated_anim) val tvName: TextView = root.findViewById(R.id.header_group_name) val tvStatus: TextView = root.findViewById(R.id.header_group_status) val tvAudioStatus: ImageView = root.findViewById(R.id.fragment_group_audio) @@ -599,7 +590,7 @@ class GroupWallFragment : AbsWallFragment(), val mFiltersAdapter: HorizontalOptionsAdapter val mMenuAdapter: HorizontalMenuAdapter val menuList: RecyclerView = root.findViewById(R.id.menu_recyclerview) - val paganSymbol: RLottieImageView = root.findViewById(R.id.pagan_symbol) + val paganSymbol: ThorVGLottieView = root.findViewById(R.id.pagan_symbol) val Runes: View = root.findViewById(R.id.runes_container) init { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/groupwall/GroupWallPresenter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/groupwall/GroupWallPresenter.kt index ede531322..83737047e 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/groupwall/GroupWallPresenter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/groupwall/GroupWallPresenter.kt @@ -506,11 +506,17 @@ class GroupWallPresenter( fun fireSubscribe() { appendJob(wallsRepository.subscribe(accountId, ownerId) .fromIOToMain({ onExecuteComplete() }) { t -> onExecuteError(t) }) + + appendJob(storiesInteractor.subscribe(accountId, ownerId) + .fromIOToMain({ onExecuteComplete() }) { t -> onExecuteError(t) }) } fun fireUnSubscribe() { appendJob(wallsRepository.unsubscribe(accountId, ownerId) .fromIOToMain({ onExecuteComplete() }) { t -> onExecuteError(t) }) + + appendJob(storiesInteractor.unsubscribe(accountId, ownerId) + .fromIOToMain({ onExecuteComplete() }) { t -> onExecuteError(t) }) } fun fireAddToBookmarksClick() { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/userwall/UserWallFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/userwall/UserWallFragment.kt index 319b45f0f..68ce6b5b0 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/userwall/UserWallFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/userwall/UserWallFragment.kt @@ -66,7 +66,6 @@ import dev.ragnarok.fenrir.toColor import dev.ragnarok.fenrir.util.InputTextDialog import dev.ragnarok.fenrir.util.UserInfoResolveUtil import dev.ragnarok.fenrir.util.Utils -import dev.ragnarok.fenrir.util.Utils.dp import dev.ragnarok.fenrir.util.Utils.firstNonEmptyString import dev.ragnarok.fenrir.util.Utils.getVerifiedColor import dev.ragnarok.fenrir.util.Utils.setBackgroundTint @@ -74,7 +73,7 @@ import dev.ragnarok.fenrir.util.ViewUtils.getOnlineIcon import dev.ragnarok.fenrir.util.toast.CustomToast import dev.ragnarok.fenrir.view.OnlineView import dev.ragnarok.fenrir.view.ProfileCoverDrawable -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import java.io.File class UserWallFragment : AbsWallFragment(), IUserWallView { @@ -156,18 +155,14 @@ class UserWallFragment : AbsWallFragment(), IU val donate_anim = Settings.get().main().donate_anim_set if (donate_anim > 0 && user.isDonated) { mHeaderHolder?.bDonate?.visibility = View.VISIBLE - mHeaderHolder?.bDonate?.setAutoRepeat(true) + mHeaderHolder?.bDonate?.setRepeat(true) if (donate_anim == 2) { val cur = Settings.get().ui().mainThemeKey if ("fire" == cur || "yellow_violet" == cur) { mHeaderHolder?.tvName?.setTextColor("#df9d00".toColor()) mHeaderHolder?.tvScreenName?.setTextColor("#df9d00".toColor()) setBackgroundTint(mHeaderHolder?.ivVerified, "#df9d00".toColor()) - mHeaderHolder?.bDonate?.fromRes( - dev.ragnarok.fenrir_common.R.raw.donater_fire, - dp(100f), - dp(100f) - ) + mHeaderHolder?.bDonate?.fromRes(dev.ragnarok.fenrir_common.R.raw.donater_fire) } else { mHeaderHolder?.tvName?.setTextColor(CurrentTheme.getColorPrimary(requireActivity())) mHeaderHolder?.tvScreenName?.setTextColor( @@ -181,8 +176,6 @@ class UserWallFragment : AbsWallFragment(), IU ) mHeaderHolder?.bDonate?.fromRes( dev.ragnarok.fenrir_common.R.raw.donater_fire, - dp(100f), - dp(100f), intArrayOf(0xFF812E, CurrentTheme.getColorPrimary(requireActivity())), true ) @@ -190,8 +183,6 @@ class UserWallFragment : AbsWallFragment(), IU } else { mHeaderHolder?.bDonate?.fromRes( dev.ragnarok.fenrir_common.R.raw.donater, - dp(100f), - dp(100f), intArrayOf( 0xffffff, CurrentTheme.getColorPrimary(requireActivity()), @@ -200,7 +191,7 @@ class UserWallFragment : AbsWallFragment(), IU ) ) } - mHeaderHolder?.bDonate?.playAnimation() + mHeaderHolder?.bDonate?.startAnimation() } else { mHeaderHolder?.bDonate?.setImageDrawable(null) mHeaderHolder?.bDonate?.visibility = View.GONE @@ -226,20 +217,18 @@ class UserWallFragment : AbsWallFragment(), IU if (user.blacklisted) { mHeaderHolder?.blacklisted?.visibility = View.VISIBLE if (FenrirNative.isNativeLoaded) { - mHeaderHolder?.blacklisted?.fromRes( - dev.ragnarok.fenrir_common.R.raw.skull, - dp(48f), - dp(48f), - null - ) - mHeaderHolder?.blacklisted?.playAnimation() + mHeaderHolder?.blacklisted?.fromRes(dev.ragnarok.fenrir_common.R.raw.skull) + mHeaderHolder?.blacklisted?.startAnimation() } else { mHeaderHolder?.blacklisted?.setImageResource(R.drawable.audio_died) mHeaderHolder?.blacklisted?.setColorFilter("#AAFF0000".toColor()) } } else { mHeaderHolder?.blacklisted?.visibility = View.GONE - mHeaderHolder?.blacklisted?.clearAnimationDrawable() + mHeaderHolder?.blacklisted?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } mHeaderHolder?.blacklisted?.visibility = if (user.blacklisted) View.VISIBLE else View.GONE } @@ -700,7 +689,7 @@ class UserWallFragment : AbsWallFragment(), IU val tvAudioStatus: ImageView = root.findViewById(R.id.fragment_user_profile_audio) val tvLastSeen: TextView = root.findViewById(R.id.fragment_user_profile_activity) val ivOnline: OnlineView = root.findViewById(R.id.header_navi_menu_online) - val blacklisted: RLottieImageView = root.findViewById(R.id.item_blacklisted) + val blacklisted: ThorVGLottieView = root.findViewById(R.id.item_blacklisted) val bFriends: TextView = root.findViewById(R.id.fragment_user_profile_bfriends) val bGroups: TextView = root.findViewById(R.id.fragment_user_profile_bgroups) val bPhotos: TextView = root.findViewById(R.id.fragment_user_profile_bphotos) @@ -714,8 +703,8 @@ class UserWallFragment : AbsWallFragment(), IU root.findViewById(R.id.header_user_profile_fab_message) val fabMoreInfo: FloatingActionButton = root.findViewById(R.id.info_btn) val bPrimaryAction: MaterialButton = root.findViewById(R.id.subscribe_btn) - val bDonate: RLottieImageView = root.findViewById(R.id.donated_anim) - val paganSymbol: RLottieImageView = root.findViewById(R.id.pagan_symbol) + val bDonate: ThorVGLottieView = root.findViewById(R.id.donated_anim) + val paganSymbol: ThorVGLottieView = root.findViewById(R.id.pagan_symbol) val Runes: View = root.findViewById(R.id.runes_container) val mPostFilterAdapter: HorizontalOptionsAdapter diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/userwall/UserWallPresenter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/userwall/UserWallPresenter.kt index 31bb5a82a..66eaa80f1 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/userwall/UserWallPresenter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/userwall/UserWallPresenter.kt @@ -480,11 +480,17 @@ class UserWallPresenter( fun fireSubscribe() { appendJob(wallsRepository.subscribe(accountId, ownerId) .fromIOToMain({ onExecuteComplete() }) { t -> onExecuteError(t) }) + + appendJob(storiesInteractor.subscribe(accountId, ownerId) + .fromIOToMain({ onExecuteComplete() }) { t -> onExecuteError(t) }) } fun fireUnSubscribe() { appendJob(wallsRepository.unsubscribe(accountId, ownerId) .fromIOToMain({ onExecuteComplete() }) { t -> onExecuteError(t) }) + + appendJob(storiesInteractor.unsubscribe(accountId, ownerId) + .fromIOToMain({ onExecuteComplete() }) { t -> onExecuteError(t) }) } private fun executeAddToFriendsRequest(text: String?, follow: Boolean) { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/wallpost/WallPostFragment.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/wallpost/WallPostFragment.kt index cc2dad777..42c053ce6 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/wallpost/WallPostFragment.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/fragment/wall/wallpost/WallPostFragment.kt @@ -44,7 +44,6 @@ import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.util.AppTextUtils.getDateFromUnixTime import dev.ragnarok.fenrir.util.PostDownload -import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.ViewUtils.displayAvatar import dev.ragnarok.fenrir.util.coroutines.CancelableJob import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.delayTaskFlow @@ -52,7 +51,7 @@ import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.toMain import dev.ragnarok.fenrir.util.toast.CustomToast import dev.ragnarok.fenrir.view.CircleCounterButton import dev.ragnarok.fenrir.view.emoji.EmojiconTextView -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class WallPostFragment : PlaceSupportMvpFragment(), EmojiconTextView.OnHashTagClickListener, IWallPostView, MenuProvider { @@ -68,7 +67,7 @@ class WallPostFragment : PlaceSupportMvpFragment(R.id.item_progress_text).text = if (messageId != 0) context.getString(messageId) else message - val anim: RLottieImageView = root.findViewById(R.id.lottie_animation) + val anim: ThorVGLottieView = root.findViewById(R.id.lottie_animation) anim.fromRes( dev.ragnarok.fenrir_common.R.raw.s_loading, - dp(180f), - dp(180f), intArrayOf( 0x333333, CurrentTheme.getColorPrimary(context), @@ -163,7 +160,7 @@ class SpotsDialog internal constructor( CurrentTheme.getColorSecondary(context) ) ) - anim.playAnimation() + anim.startAnimation() return MaterialAlertDialogBuilder(context).setView(root) .setCancelable(cancelable) .setOnCancelListener(cancelListener).create() diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/AudioContainer.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/AudioContainer.kt index d12c3391c..c68a45763 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/AudioContainer.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/AudioContainer.kt @@ -66,7 +66,7 @@ import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.sharedFlowToMain import dev.ragnarok.fenrir.util.hls.M3U8 import dev.ragnarok.fenrir.util.toast.CustomSnackbars import dev.ragnarok.fenrir.util.toast.CustomToast.Companion.createCustomToast -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class AudioContainer : LinearLayout { private val mAudioInteractor: IAudioInteractor by lazy { @@ -781,7 +781,7 @@ class AudioContainer : LinearLayout { val selectionView: MaterialCardView = root.findViewById(R.id.item_audio_selection) private val isSelectedView: MaterialCardView = root.findViewById(R.id.item_audio_select_add) private val animationAdapter: Animator.AnimatorListener - val visual: RLottieImageView + val visual: ThorVGLottieView var animator: ObjectAnimator? = null fun startSomeAnimation() { selectionView.setCardBackgroundColor(CurrentTheme.getColorSecondary(context)) diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/LoadMoreFooterHelper.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/LoadMoreFooterHelper.kt index 15f3c9e0a..4bee4ef1c 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/LoadMoreFooterHelper.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/LoadMoreFooterHelper.kt @@ -6,8 +6,7 @@ import dev.ragnarok.fenrir.R import dev.ragnarok.fenrir.model.LoadMoreState import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.settings.Settings -import dev.ragnarok.fenrir.util.Utils -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class LoadMoreFooterHelper { var callback: Callback? = null @@ -28,14 +27,12 @@ class LoadMoreFooterHelper { LoadMoreState.END_OF_LIST -> { holder?.tvEndOfList?.visibility = View.VISIBLE - holder?.tvEndOfList?.setAutoRepeat(false) + holder?.tvEndOfList?.setRepeat(false) when (animation_id) { 0 -> { - holder?.tvEndOfList?.setAutoRepeat(false) + holder?.tvEndOfList?.setRepeat(false) holder?.tvEndOfList?.fromRes( dev.ragnarok.fenrir_common.R.raw.end_list_succes, - Utils.dp(40f), - Utils.dp(40f), intArrayOf( 0xffffff, CurrentTheme.getColorControlNormal( holder?.bLoadMore?.context @@ -45,11 +42,9 @@ class LoadMoreFooterHelper { } 1 -> { - holder?.tvEndOfList?.setAutoRepeat(false) + holder?.tvEndOfList?.setRepeat(false) holder?.tvEndOfList?.fromRes( dev.ragnarok.fenrir_common.R.raw.end_list_balls, - Utils.dp(40f), - Utils.dp(40f), intArrayOf( 0xffffff, CurrentTheme.getColorControlNormal( holder?.bLoadMore?.context @@ -59,11 +54,9 @@ class LoadMoreFooterHelper { } else -> { - holder?.tvEndOfList?.setAutoRepeat(true) + holder?.tvEndOfList?.setRepeat(true) holder?.tvEndOfList?.fromRes( dev.ragnarok.fenrir_common.R.raw.end_list_wave, - Utils.dp(80f), - Utils.dp(40f), intArrayOf( 0x777777, CurrentTheme.getColorPrimary( holder?.bLoadMore?.context @@ -74,7 +67,7 @@ class LoadMoreFooterHelper { ) } } - holder?.tvEndOfList?.playAnimation() + holder?.tvEndOfList?.startAnimation() holder?.bLoadMore?.visibility = View.INVISIBLE holder?.progress?.visibility = View.INVISIBLE } @@ -98,7 +91,7 @@ class LoadMoreFooterHelper { val container: View = root.findViewById(R.id.footer_load_more_root) val progress: CircularProgressIndicator = root.findViewById(R.id.footer_load_more_progress) val bLoadMore: View = root.findViewById(R.id.footer_load_more_run) - val tvEndOfList: RLottieImageView = root.findViewById(R.id.footer_load_more_end_of_list) + val tvEndOfList: ThorVGLottieView = root.findViewById(R.id.footer_load_more_end_of_list) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/PhotosViewHelper.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/PhotosViewHelper.kt index 81e9f0f2d..23fb30292 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/PhotosViewHelper.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/PhotosViewHelper.kt @@ -97,7 +97,8 @@ class PhotosViewHelper internal constructor( holder.vgVideo.fromNet( (video.ownerId.toString() + "_" + image.attachment.id.toString()), video.trailer, - Utils.createOkHttp(Constants.GIF_TIMEOUT, true) + Utils.createOkHttp(Constants.GIF_TIMEOUT, true), + true ) } else if (url.nonNullNoEmpty()) { PicassoInstance.with() @@ -259,7 +260,8 @@ class PhotosViewHelper internal constructor( holder.vgPhoto.fromNet( ((image.attachment as Document).ownerId.toString() + "_" + image.attachment.id.toString()), image.attachment.videoPreview?.src, - Utils.createOkHttp(Constants.GIF_TIMEOUT, true) + Utils.createOkHttp(Constants.GIF_TIMEOUT, true), + true ) } else if (url.nonNullNoEmpty()) { PicassoInstance.with() diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/ProgressButton.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/ProgressButton.kt index 99dc03de4..bc1fc3c24 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/ProgressButton.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/ProgressButton.kt @@ -7,13 +7,13 @@ import android.widget.FrameLayout import com.google.android.material.button.MaterialButton import dev.ragnarok.fenrir.R import dev.ragnarok.fenrir.module.FenrirNative -import dev.ragnarok.fenrir.module.rlottie.RLottieDrawable +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.util.Utils class ProgressButton : FrameLayout { private var mButton: MaterialButton? = null - private var animatedDrawable: RLottieDrawable? = null + private var animatedDrawable: ThorVGLottieDrawable? = null private var mProgressNow = true constructor(context: Context) : super(context) { @@ -59,16 +59,25 @@ class ProgressButton : FrameLayout { mButton?.text = charSequence } + private fun clearAnimationDrawable() { + if (mButton?.icon is ThorVGLottieDrawable) { + (mButton?.icon as ThorVGLottieDrawable).release() + } + mButton?.icon = null + if (animatedDrawable != null) { + animatedDrawable?.release() + animatedDrawable = null + } + } + override fun onAttachedToWindow() { super.onAttachedToWindow() - animatedDrawable?.setCurrentParentView(this) - animatedDrawable?.start() + resolveViews() } override fun onDetachedFromWindow() { super.onDetachedFromWindow() - animatedDrawable?.stop() - animatedDrawable?.setCurrentParentView(null) + clearAnimationDrawable() } fun onButtonClick(listener: OnClickListener) { @@ -79,20 +88,13 @@ class ProgressButton : FrameLayout { mButton?.setOnLongClickListener(listener) } - private fun setAnimation(rLottieDrawable: RLottieDrawable) { - mButton?.icon = null - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - rLottieDrawable.setAutoRepeat(1) - rLottieDrawable.setAllowDecodeSingleFrame(true) - rLottieDrawable.setCurrentParentView(this) - rLottieDrawable.start() - animatedDrawable = rLottieDrawable - mButton?.icon = rLottieDrawable + private fun setAnimation(thorVGLottieDrawable: ThorVGLottieDrawable) { + clearAnimationDrawable() + thorVGLottieDrawable.setRepeatCount(Int.MAX_VALUE) + thorVGLottieDrawable.setSize(Utils.dp(24f), Utils.dp(24f)) + thorVGLottieDrawable.start() + animatedDrawable = thorVGLottieDrawable + mButton?.icon = thorVGLottieDrawable } private fun resolveViews() { @@ -101,11 +103,8 @@ class ProgressButton : FrameLayout { mButton?.setIconResource(R.drawable.ic_progress_button_icon_vector) } else { setAnimation( - RLottieDrawable( + ThorVGLottieDrawable( dev.ragnarok.fenrir_common.R.raw.loading, - Utils.dp(40f), - Utils.dp(40f), - false, intArrayOf( 0x000000, CurrentTheme.getColorPrimary(context), @@ -117,13 +116,7 @@ class ProgressButton : FrameLayout { ) } } else { - mButton?.icon = null - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } + clearAnimationDrawable() } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/ReactionContainer.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/ReactionContainer.kt index ab3c6f727..43bdf62a7 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/ReactionContainer.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/ReactionContainer.kt @@ -12,7 +12,7 @@ import dev.ragnarok.fenrir.model.ReactionWithAsset import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.ViewUtils -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class ReactionContainer : RowLayout { private var colorPrimary = 0 @@ -67,7 +67,7 @@ class ReactionContainer : RowLayout { } visibility = VISIBLE val i = reactionsData.size - childCount - for (j in 0 until i) { + (0 until i).forEach { j -> val itemView = LayoutInflater.from(context).inflate(R.layout.item_reaction, this, false) val holder = ReactionHolder(itemView) @@ -95,8 +95,7 @@ class ReactionContainer : RowLayout { reactionHolder.ivReaction.fromNet( reaction.small_animation, Utils.createOkHttp(Constants.GIF_TIMEOUT, true), - Utils.dp(28f), - Utils.dp(28f) + !isEdit ) } root.setOnClickListener { @@ -115,6 +114,6 @@ class ReactionContainer : RowLayout { private inner class ReactionHolder(root: View) { val tvCount: TextView = root.findViewById(R.id.count) - val ivReaction: RLottieImageView = root.findViewById(R.id.reaction) + val ivReaction: ThorVGLottieView = root.findViewById(R.id.reaction) } } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/TouchImageView.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/TouchImageView.kt index e11be54ba..5cf9882b1 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/TouchImageView.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/TouchImageView.kt @@ -25,12 +25,16 @@ import android.view.animation.LinearInterpolator import android.widget.OverScroller import androidx.appcompat.widget.AppCompatImageView import com.squareup.picasso3.Rotatable +import dev.ragnarok.fenrir.Constants import dev.ragnarok.fenrir.R import dev.ragnarok.fenrir.getParcelableCompat import dev.ragnarok.fenrir.getSerializableCompat import dev.ragnarok.fenrir.module.FenrirNative import dev.ragnarok.fenrir.module.animation.AnimatedFileDrawable +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LoadedFrom +import dev.ragnarok.fenrir.nonNullNoEmpty import dev.ragnarok.fenrir.picasso.PicassoInstance +import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.coroutines.CancelableJob import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain import dev.ragnarok.fenrir.view.natives.animation.AnimationNetworkCache @@ -48,12 +52,27 @@ import kotlin.math.min @SuppressLint("ClickableViewAccessibility") @Suppress("unused") -open class TouchImageView @JvmOverloads constructor( +class TouchImageView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : AppCompatImageView(context, attrs, defStyle) { + //%%~Animation + private var mDisposable = CancelableJob() + private val cache: AnimationNetworkCache = AnimationNetworkCache(context) + private var animatedDrawable: AnimatedFileDrawable? = null + private var attachedToWindow = false + //~%%Animation + + @LoadedFrom + private var loadedFrom = LoadedFrom.NO + private var filePathTmp: String? = null + private var keyTmp: String? = null + private var isPlaying: Boolean? = null + private var tmpFade: Boolean? = null + private var fallbackTmp: String? = null + /** * Get the current zoom. This is the zoom relative to the initial * scale, not the original resource. @@ -62,8 +81,6 @@ open class TouchImageView @JvmOverloads constructor( */ // Scale of image ranges from minScale to maxScale, where minScale == 1 // when the image is stretched to fit view. - private var mDisposable = CancelableJob() - private val cache: AnimationNetworkCache by lazy { AnimationNetworkCache(context) } var currentZoom = 0f private set @@ -137,7 +154,7 @@ open class TouchImageView @JvmOverloads constructor( var orientationLocked = OrientationLocked.HORIZONTAL init { - super.setClickable(true) + super.isClickable = true orientation = resources.configuration.orientation scaleDetector = ScaleGestureDetector(context, ScaleListener()) gestureDetector = GestureDetector(context, GestureListener()) @@ -169,90 +186,61 @@ open class TouchImageView @JvmOverloads constructor( } } - fun setRotateImageToFitScreen(rotateImageToFitScreen: Boolean) { - isRotateImageToFitScreen = rotateImageToFitScreen - } - - interface StateListener { - fun onChangeState(imageActionState: ImageActionState, zoomed: Boolean) - } - - fun setOnStateChangeListener(onStateListener: StateListener) { - stateListener = onStateListener - } - - override fun setOnTouchListener(onTouchListener: OnTouchListener?) { - userTouchListener = onTouchListener - } - - fun setOnTouchImageViewListener(onTouchImageViewListener: OnTouchImageViewListener) { - touchImageViewListener = onTouchImageViewListener - } - - fun setOnDoubleTapListener(onDoubleTapListener: OnDoubleTapListener) { - doubleTapListener = onDoubleTapListener - } - - fun setOnTouchCoordinatesListener(onTouchCoordinatesListener: OnTouchCoordinatesListener) { - touchCoordinatesListener = onTouchCoordinatesListener - } - - open fun fromAnimFile(file: File) { + //%%~Animation + private fun setAnimationByUrlCache( + key: String, + fallback: String?, + autoPlay: Boolean, + fade: Boolean + ) { if (!FenrirNative.isNativeLoaded) { return } - clearAnimationDrawable() - setAnimation( - AnimatedFileDrawable( - file, - 0, - 100, - 100, - true, - object : AnimatedFileDrawable.DecoderListener { - override fun onError() { - - } - }) - ) - } - - private fun setAnimationByUrlCache(url: String, fallback: String?, fade: Boolean) { - if (!FenrirNative.isNativeLoaded) { - PicassoInstance.with().load(fallback).into(this) + val ch = cache.fetch(key) + if (ch == null) { return } - val ch = cache.fetch(url) - if (ch == null) { - setImageDrawable(null) - PicassoInstance.with().load(fallback).into(this) + if (filePathTmp == ch.absolutePath && fallbackTmp == fallback && loadedFrom == LoadedFrom.FILE) { return } - val ref = WeakReference(this) - setAnimation( - AnimatedFileDrawable( - ch, - 0, - 100, - 100, - fade, - object : AnimatedFileDrawable.DecoderListener { - override fun onError() { - ref.get()?.let { PicassoInstance.with().load(fallback).into(it) } - } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + loadedFrom = LoadedFrom.FILE + filePathTmp = ch.absolutePath + tmpFade = fade + isPlaying = autoPlay + fallbackTmp = fallback - }) - ) + if (attachedToWindow) { + createAnimationDrawable() + } } - fun fromNet(key: String, url: String?, fallback: String?, client: OkHttpClient.Builder) { + fun fromAnimationNet( + key: String, + url: String?, + fallback: String?, + client: OkHttpClient.Builder, + autoPlay: Boolean + ) { if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { - PicassoInstance.with().load(fallback).into(this) + if (loadedFrom == LoadedFrom.NET) { + loadedFrom = LoadedFrom.NO + } return } - clearAnimationDrawable() + if (filePathTmp == url && keyTmp == key && fallbackTmp == fallback && loadedFrom == LoadedFrom.NET) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.NET + filePathTmp = url + keyTmp = key + isPlaying = autoPlay + tmpFade = true + fallbackTmp = fallback + if (cache.isCachedFile(key)) { - setAnimationByUrlCache(key, fallback, true) + setAnimationByUrlCache(key, fallback, autoPlay, true) return } mDisposable.set(flow { @@ -267,89 +255,207 @@ open class TouchImageView @JvmOverloads constructor( emit(false) return@flow } - cache.writeTempCacheFile(url, response.body.source()) + cache.writeTempCacheFile(key, response.body.source()) response.close() - cache.renameTempFile(url) + cache.renameTempFile(key) emit(true) } catch (e: CancellationException) { call?.cancel() throw e } - }.fromIOToMain { - if (it) { - setAnimationByUrlCache(key, fallback, true) - } else { - PicassoInstance.with().load(fallback).into(this) + }.fromIOToMain { u -> + if (u) { + setAnimationByUrlCache(key, fallback, autoPlay, true) } }) } - open fun clearAnimationDrawable() { - mDisposable.cancel() - if (animDrawable != null) { - animDrawable?.stop() - animDrawable?.callback = null - animDrawable?.recycle() - animDrawable = null + fun clearAnimationDrawable(callSuper: Boolean, clearState: Boolean, cancelTask: Boolean) { + if (cancelTask) { + mDisposable.cancel() + } + if (animatedDrawable != null) { + animatedDrawable?.callback = null + animatedDrawable?.recycle() + animatedDrawable = null + } + if (callSuper) { + super.setImageDrawable(null) + } + if (clearState) { + isPlaying = false + loadedFrom = LoadedFrom.NO + filePathTmp = null + tmpFade = null + fallbackTmp = null } } - private fun setAnimation(videoDrawable: AnimatedFileDrawable) { - if (!videoDrawable.isDecoded) return - animDrawable = videoDrawable - animDrawable?.setAllowDecodeSingleFrame(true) - animDrawable?.callback = this - animDrawable?.start() - setImageDrawable(animDrawable) + private fun createAnimationDrawable() { + if (FenrirNative.isNativeLoaded && attachedToWindow && loadedFrom != LoadedFrom.NO && animatedDrawable == null && filePathTmp != null) { + val ref = WeakReference(this) + animatedDrawable = AnimatedFileDrawable( + filePathTmp ?: "", + 0, + 100, + 100, + tmpFade == true, + object : AnimatedFileDrawable.DecoderListener { + override fun onError() { + ref.get()?.let { + fallbackTmp.nonNullNoEmpty { k -> + PicassoInstance.with().load(k).into(it) + } + } + } + + }) + if (animatedDrawable?.isDecoded != true) { + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + return + } + tmpFade = false + animatedDrawable?.setAllowDecodeSingleFrame(true) + + imageRenderedAtLeastOnce = false + super.setImageDrawable(animatedDrawable) + savePreviousImageValues() + fitImageToView() + + if (isPlaying == true) { + playAnimation() + } + } + } + + fun fromAnimationFile(file: File, autoPlay: Boolean) { + if (!FenrirNative.isNativeLoaded || !file.exists()) { + return + } + if (filePathTmp == file.absolutePath && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.FILE + filePathTmp = file.absolutePath + tmpFade = false + isPlaying = autoPlay + if (attachedToWindow) { + createAnimationDrawable() + } } override fun onAttachedToWindow() { + attachedToWindow = true super.onAttachedToWindow() - animDrawable?.callback = this - animDrawable?.start() + when { + loadedFrom == LoadedFrom.NET -> { + filePathTmp?.let { + keyTmp?.let { s -> + fromAnimationNet( + s, + it, + fallbackTmp, + Utils.createOkHttp(Constants.GIF_TIMEOUT, true), + isPlaying == true, + ) + } + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } + + loadedFrom != LoadedFrom.NO -> { + createAnimationDrawable() + } + } } override fun onDetachedFromWindow() { + attachedToWindow = false super.onDetachedFromWindow() - mDisposable.cancel() - animDrawable?.stop() - animDrawable?.callback = null + if (loadedFrom != LoadedFrom.NO) { + clearAnimationDrawable(callSuper = true, clearState = false, cancelTask = true) + } } - open fun isPlaying(): Boolean { + fun isPlayingAnimation(): Boolean { return animDrawable?.isRunning == true } + fun playAnimation() { + animatedDrawable?.start() + isPlaying = true + } + + fun resetFrame() { + animatedDrawable?.seekTo(0, true) + } + + fun stopAnimation() { + animatedDrawable?.let { + it.stop() + isPlaying = false + } + } + //~Animation + + fun setRotateImageToFitScreen(rotateImageToFitScreen: Boolean) { + isRotateImageToFitScreen = rotateImageToFitScreen + } + + interface StateListener { + fun onChangeState(imageActionState: ImageActionState, zoomed: Boolean) + } + + fun setOnStateChangeListener(onStateListener: StateListener) { + stateListener = onStateListener + } + + override fun setOnTouchListener(onTouchListener: OnTouchListener?) { + userTouchListener = onTouchListener + } + + fun setOnTouchImageViewListener(onTouchImageViewListener: OnTouchImageViewListener) { + touchImageViewListener = onTouchImageViewListener + } + + fun setOnDoubleTapListener(onDoubleTapListener: OnDoubleTapListener) { + doubleTapListener = onDoubleTapListener + } + + fun setOnTouchCoordinatesListener(onTouchCoordinatesListener: OnTouchCoordinatesListener) { + touchCoordinatesListener = onTouchCoordinatesListener + } + override fun setImageResource(resId: Int) { - clearAnimationDrawable() imageRenderedAtLeastOnce = false super.setImageResource(resId) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) savePreviousImageValues() fitImageToView() } override fun setImageBitmap(bm: Bitmap?) { - clearAnimationDrawable() imageRenderedAtLeastOnce = false super.setImageBitmap(bm) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) savePreviousImageValues() fitImageToView() } override fun setImageDrawable(drawable: Drawable?) { - if (drawable !is AnimatedFileDrawable) { - clearAnimationDrawable() - } imageRenderedAtLeastOnce = false super.setImageDrawable(drawable) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) savePreviousImageValues() fitImageToView() } override fun setImageURI(uri: Uri?) { - clearAnimationDrawable() imageRenderedAtLeastOnce = false super.setImageURI(uri) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) savePreviousImageValues() fitImageToView() } @@ -367,7 +473,7 @@ open class TouchImageView @JvmOverloads constructor( } } - override fun getScaleType() = touchScaleType!! + override fun getScaleType() = touchScaleType ?: ScaleType.MATRIX /** * Returns false if image is in initial, unzoomed state. False, otherwise. @@ -626,21 +732,22 @@ open class TouchImageView @JvmOverloads constructor( } internal fun orientationMismatch(drawable: Drawable?): Boolean { - return viewWidth > viewHeight != drawable!!.intrinsicWidth > drawable.intrinsicHeight + return viewWidth > viewHeight != (drawable?.intrinsicWidth + ?: 0) > (drawable?.intrinsicHeight ?: 0) } private fun getDrawableWidth(drawable: Drawable?): Int { return if (orientationMismatch(drawable) && isRotateImageToFitScreen) { - drawable!!.intrinsicHeight + drawable?.intrinsicHeight ?: 0 } else - drawable!!.intrinsicWidth + drawable?.intrinsicWidth ?: 0 } private fun getDrawableHeight(drawable: Drawable?): Int { return if (orientationMismatch(drawable) && isRotateImageToFitScreen) { - drawable!!.intrinsicWidth + drawable?.intrinsicWidth ?: 0 } else - drawable!!.intrinsicHeight + drawable?.intrinsicHeight ?: 0 } /** @@ -825,14 +932,11 @@ open class TouchImageView @JvmOverloads constructor( } ScaleType.CENTER_INSIDE -> { - run { - scaleY = min(1f, min(scaleX, scaleY)) - scaleX = scaleY - } - run { - scaleY = min(scaleX, scaleY) - scaleX = scaleY - } + scaleY = min(1f, min(scaleX, scaleY)) + scaleX = scaleY + + scaleY = min(scaleX, scaleY) + scaleX = scaleY } ScaleType.FIT_CENTER, ScaleType.FIT_START, ScaleType.FIT_END -> { @@ -1317,7 +1421,7 @@ open class TouchImageView @JvmOverloads constructor( * to the bounds of the bitmap size. * @return Coordinates of the point touched, in the coordinate system of the original drawable. */ - protected fun transformCoordTouchToBitmap(x: Float, y: Float, clipToBitmap: Boolean): PointF { + private fun transformCoordTouchToBitmap(x: Float, y: Float, clipToBitmap: Boolean): PointF { touchMatrix.getValues(floatMatrix) val origW = drawable.intrinsicWidth.toFloat() val origH = drawable.intrinsicHeight.toFloat() @@ -1340,7 +1444,7 @@ open class TouchImageView @JvmOverloads constructor( * @param by y-coordinate in original bitmap coordinate system * @return Coordinates of the point in the view's coordinate system. */ - protected fun transformCoordBitmapToTouch(bx: Float, by: Float): PointF { + private fun transformCoordBitmapToTouch(bx: Float, by: Float): PointF { touchMatrix.getValues(floatMatrix) val origW = drawable.intrinsicWidth.toFloat() val origH = drawable.intrinsicHeight.toFloat() diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/emoji/MyStickersAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/emoji/MyStickersAdapter.kt index cda9eae65..f1ad98bdc 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/emoji/MyStickersAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/emoji/MyStickersAdapter.kt @@ -11,7 +11,7 @@ import dev.ragnarok.fenrir.R import dev.ragnarok.fenrir.picasso.PicassoInstance.Companion.with import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.view.emoji.EmojiconsPopup.OnMyStickerClickedListener -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView import java.io.File class MyStickersAdapter(private val context: Context) : @@ -48,14 +48,14 @@ class MyStickersAdapter(private val context: Context) : when (getItemViewType(position)) { TYPE_ANIMATED -> { val animatedHolder = holder as StickerAnimatedHolder - animatedHolder.animation.fromFile(File(item.path), Utils.dp(128f), Utils.dp(128f)) + animatedHolder.animation.fromFile(File(item.path), true) animatedHolder.root.setOnClickListener { myStickerClickedListener?.onMyStickerClick( item ) } animatedHolder.root.setOnLongClickListener { - animatedHolder.animation.playAnimation() + animatedHolder.animation.startAnimation() true } } @@ -82,14 +82,14 @@ class MyStickersAdapter(private val context: Context) : else -> { val animatedHolder = holder as StickerAnimatedHolder - animatedHolder.animation.fromFile(File(item.path), Utils.dp(128f), Utils.dp(128f)) + animatedHolder.animation.fromFile(File(item.path), true) animatedHolder.root.setOnClickListener { myStickerClickedListener?.onMyStickerClick( item ) } animatedHolder.root.setOnLongClickListener { - animatedHolder.animation.playAnimation() + animatedHolder.animation.startAnimation() true } } @@ -107,7 +107,7 @@ class MyStickersAdapter(private val context: Context) : internal class StickerAnimatedHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val root: View = itemView.rootView - val animation: RLottieImageView = itemView.findViewById(R.id.sticker_animated) + val animation: ThorVGLottieView = itemView.findViewById(R.id.sticker_animated) } companion object { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/emoji/StickersAdapter.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/emoji/StickersAdapter.kt index 0a73de5a9..279148c97 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/emoji/StickersAdapter.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/emoji/StickersAdapter.kt @@ -14,7 +14,7 @@ import dev.ragnarok.fenrir.picasso.PicassoInstance.Companion.with import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.view.emoji.EmojiconsPopup.OnStickerClickedListener -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class StickersAdapter(private val context: Context, private val stickers: StickerSet) : RecyclerView.Adapter() { @@ -70,8 +70,7 @@ class StickersAdapter(private val context: Context, private val stickers: Sticke animatedHolder.animation.fromNet( item?.getAnimationByType(if (isNightSticker) "dark" else "light"), Utils.createOkHttp(Constants.GIF_TIMEOUT, true), - Utils.dp(128f), - Utils.dp(128f) + false ) animatedHolder.root.setOnClickListener { if (item != null) { @@ -81,7 +80,7 @@ class StickersAdapter(private val context: Context, private val stickers: Sticke } } animatedHolder.root.setOnLongClickListener { - animatedHolder.animation.playAnimation() + animatedHolder.animation.startAnimation() true } } @@ -111,8 +110,7 @@ class StickersAdapter(private val context: Context, private val stickers: Sticke animatedHolder.animation.fromNet( item?.getAnimationByType(if (isNightSticker) "dark" else "light"), Utils.createOkHttp(Constants.GIF_TIMEOUT, true), - Utils.dp(128f), - Utils.dp(128f) + false ) animatedHolder.root.setOnClickListener { if (item != null) { @@ -122,7 +120,7 @@ class StickersAdapter(private val context: Context, private val stickers: Sticke } } animatedHolder.root.setOnLongClickListener { - animatedHolder.animation.playAnimation() + animatedHolder.animation.startAnimation() true } } @@ -132,7 +130,10 @@ class StickersAdapter(private val context: Context, private val stickers: Sticke override fun onViewRecycled(holder: RecyclerView.ViewHolder) { super.onViewRecycled(holder) if (holder is StickerAnimatedHolder) { - holder.animation.clearAnimationDrawable() + holder.animation.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } @@ -147,7 +148,7 @@ class StickersAdapter(private val context: Context, private val stickers: Sticke internal class StickerAnimatedHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val root: View = itemView.rootView - val animation: RLottieImageView = itemView.findViewById(R.id.sticker_animated) + val animation: ThorVGLottieView = itemView.findViewById(R.id.sticker_animated) } companion object { diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/media/PathAnimator.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/media/PathAnimator.kt index b27d2b642..5fbf03f87 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/media/PathAnimator.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/media/PathAnimator.kt @@ -63,23 +63,19 @@ class PathAnimator( fun draw(canvas: Canvas, paint: Paint?, time: Float) { var startKeyFrame: KeyFrame? = null var endKeyFrame: KeyFrame? = null - run { - var a = 0 - val N = keyFrames.size - while (a < N) { - val keyFrame = keyFrames[a] - if ((startKeyFrame == null || (startKeyFrame - ?: return@run).time < keyFrame.time) && keyFrame.time <= time - ) { - startKeyFrame = keyFrame - } - if ((endKeyFrame == null || (endKeyFrame - ?: return@run).time > keyFrame.time) && keyFrame.time >= time - ) { - endKeyFrame = keyFrame - } - a++ + var a = 0 + var N = keyFrames.size + while (a < N) { + val keyFrame = keyFrames[a] + if ((startKeyFrame == null || startKeyFrame.time < keyFrame.time) && keyFrame.time <= time + ) { + startKeyFrame = keyFrame + } + if ((endKeyFrame == null || endKeyFrame.time > keyFrame.time) && keyFrame.time >= time + ) { + endKeyFrame = keyFrame } + a++ } if (endKeyFrame === startKeyFrame) { startKeyFrame = null @@ -88,24 +84,22 @@ class PathAnimator( endKeyFrame = startKeyFrame startKeyFrame = null } - if (endKeyFrame == null || startKeyFrame != null && (startKeyFrame - ?: return).commands.size != (endKeyFrame ?: return).commands.size + if (endKeyFrame == null || startKeyFrame != null && startKeyFrame.commands.size != endKeyFrame.commands.size ) { return } path.reset() - var a = 0 - val N = (endKeyFrame ?: return).commands.size + a = 0 + N = endKeyFrame.commands.size while (a < N) { val startCommand = - if (startKeyFrame != null) (startKeyFrame ?: return).commands[a] else null - val endCommand = (endKeyFrame ?: return).commands[a] + if (startKeyFrame != null) startKeyFrame.commands[a] else null + val endCommand = endKeyFrame.commands[a] if (startCommand != null && startCommand.javaClass != endCommand.javaClass) { return } val progress: Float = if (startKeyFrame != null) { - (time - (startKeyFrame ?: return).time) / ((endKeyFrame - ?: return).time - (startKeyFrame ?: return).time) + (time - startKeyFrame.time) / (endKeyFrame.time - startKeyFrame.time) } else { 1.0f } @@ -180,4 +174,4 @@ class PathAnimator( var x2 = 0f var y2 = 0f } -} \ No newline at end of file +} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/AnimatedShapeableImageView.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/AnimatedShapeableImageView.kt index e53561945..cde3c486e 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/AnimatedShapeableImageView.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/AnimatedShapeableImageView.kt @@ -3,6 +3,8 @@ package dev.ragnarok.fenrir.view.natives.animation import android.content.Context import android.graphics.Bitmap import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Parcelable import android.util.AttributeSet import androidx.annotation.RawRes import com.google.android.material.imageview.ShapeableImageView @@ -10,6 +12,8 @@ import dev.ragnarok.fenrir.Constants import dev.ragnarok.fenrir.R import dev.ragnarok.fenrir.module.FenrirNative import dev.ragnarok.fenrir.module.animation.AnimatedFileDrawable +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LoadedFrom +import dev.ragnarok.fenrir.util.Utils import dev.ragnarok.fenrir.util.coroutines.CancelableJob import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain import dev.ragnarok.fenrir.view.natives.animation.AnimationNetworkCache.Companion.filenameForRes @@ -32,77 +36,91 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( private val defaultHeight: Int private var animatedDrawable: AnimatedFileDrawable? = null private var attachedToWindow = false - private var playing = false private var decoderCallback: OnDecoderInit? = null private var mDisposable = CancelableJob() + + @LoadedFrom + private var loadedFrom = LoadedFrom.NO + private var filePathTmp: String? = null + private var keyTmp: String? = null + + @RawRes + private var rawResTmp: Int? = null + private var isPlaying: Boolean? = null + private var tmpFade: Boolean? = null + fun setDecoderCallback(decoderCallback: OnDecoderInit?) { this.decoderCallback = decoderCallback } - private fun setAnimationByUrlCache(url: String, fade: Boolean) { + private fun setAnimationByUrlCache(key: String, autoPlay: Boolean, fade: Boolean) { if (!FenrirNative.isNativeLoaded) { decoderCallback?.onLoaded(false) return } - val ch = cache.fetch(url) + val ch = cache.fetch(key) if (ch == null) { - setImageDrawable(null) decoderCallback?.onLoaded(false) return } - setAnimation( - AnimatedFileDrawable( - ch, - 0, - defaultWidth, - defaultHeight, - fade, - object : AnimatedFileDrawable.DecoderListener { - override fun onError() { - decoderCallback?.onLoaded(false) - } + if (filePathTmp == ch.absolutePath && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + loadedFrom = LoadedFrom.FILE + filePathTmp = ch.absolutePath + tmpFade = fade + isPlaying = autoPlay - }) - ) - playAnimation() + if (attachedToWindow) { + createAnimationDrawable() + } } - private fun setAnimationByResCache(@RawRes res: Int, fade: Boolean) { + private fun setAnimationByResCache(@RawRes res: Int, autoPlay: Boolean, fade: Boolean) { if (!FenrirNative.isNativeLoaded) { decoderCallback?.onLoaded(false) return } val ch = cache.fetch(res) if (ch == null) { - setImageDrawable(null) decoderCallback?.onLoaded(false) return } - setAnimation( - AnimatedFileDrawable( - ch, - 0, - defaultWidth, - defaultHeight, - fade, - object : AnimatedFileDrawable.DecoderListener { - override fun onError() { - decoderCallback?.onLoaded(false) - } + if (filePathTmp == ch.absolutePath && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + loadedFrom = LoadedFrom.FILE + filePathTmp = ch.absolutePath + tmpFade = fade + isPlaying = autoPlay - }) - ) - playAnimation() + if (attachedToWindow) { + createAnimationDrawable() + } } - fun fromNet(key: String, url: String?, client: OkHttpClient.Builder) { + fun fromNet(key: String, url: String?, client: OkHttpClient.Builder, autoPlay: Boolean) { if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { + if (loadedFrom == LoadedFrom.NET) { + loadedFrom = LoadedFrom.NO + } decoderCallback?.onLoaded(false) return } - clearAnimationDrawable() + if (filePathTmp == url && keyTmp == key && loadedFrom == LoadedFrom.NET) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.NET + filePathTmp = url + keyTmp = key + isPlaying = autoPlay + tmpFade = true + if (cache.isCachedFile(key)) { - setAnimationByUrlCache(key, true) + setAnimationByUrlCache(key, autoPlay, true) return } mDisposable.set(flow { @@ -127,7 +145,7 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( } }.fromIOToMain({ u -> if (u) { - setAnimationByUrlCache(key, true) + setAnimationByUrlCache(key, autoPlay, true) } else { decoderCallback?.onLoaded(false) } @@ -136,23 +154,34 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( })) } - fun fromRes(@RawRes res: Int) { - if (!FenrirNative.isNativeLoaded) { + fun fromRes(@RawRes resId: Int, autoPlay: Boolean) { + if (!FenrirNative.isNativeLoaded || resId == -1) { + if (loadedFrom == LoadedFrom.RES) { + loadedFrom = LoadedFrom.NO + } decoderCallback?.onLoaded(false) return } - clearAnimationDrawable() - if (cache.isCachedRes(res)) { - setAnimationByResCache(res, true) + if (rawResTmp == resId && loadedFrom == LoadedFrom.RES) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.RES + rawResTmp = resId + tmpFade = true + isPlaying = autoPlay + + if (cache.isCachedRes(resId)) { + setAnimationByResCache(resId, autoPlay, true) return } mDisposable.set(flow { try { - if (!copyRes(res)) { + if (!copyRes(resId)) { emit(false) return@flow } - cache.renameTempFile(res) + cache.renameTempFile(resId) } catch (_: Exception) { emit(false) return@flow @@ -160,69 +189,121 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( emit(true) }.fromIOToMain { if (it) { - setAnimationByResCache(res, true) + setAnimationByResCache(resId, autoPlay, true) } else { decoderCallback?.onLoaded(false) } }) } - private fun setAnimation(videoDrawable: AnimatedFileDrawable) { - decoderCallback?.onLoaded(videoDrawable.isDecoded) - if (!videoDrawable.isDecoded) return - animatedDrawable = videoDrawable - animatedDrawable?.setAllowDecodeSingleFrame(true) - setImageDrawable(animatedDrawable) - } - - fun fromFile(file: File) { - if (!FenrirNative.isNativeLoaded) { - decoderCallback?.onLoaded(false) - return - } - clearAnimationDrawable() - setAnimation( - AnimatedFileDrawable( - file, + private fun createAnimationDrawable() { + if (FenrirNative.isNativeLoaded && attachedToWindow && loadedFrom != LoadedFrom.NO && animatedDrawable == null && filePathTmp != null) { + animatedDrawable = AnimatedFileDrawable( + filePathTmp ?: "", 0, defaultWidth, defaultHeight, - false, + tmpFade == true, object : AnimatedFileDrawable.DecoderListener { override fun onError() { decoderCallback?.onLoaded(false) } }) - ) + decoderCallback?.onLoaded(animatedDrawable?.isDecoded == true) + if (animatedDrawable?.isDecoded != true) { + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + return + } + tmpFade = false + animatedDrawable?.setAllowDecodeSingleFrame(true) + super.setImageDrawable(animatedDrawable) + if (isPlaying == true) { + playAnimation() + } + } } - fun clearAnimationDrawable() { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() + fun fromFile(file: File) { + if (!FenrirNative.isNativeLoaded || !file.exists()) { + decoderCallback?.onLoaded(false) + return + } + if (filePathTmp == file.absolutePath && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.FILE + filePathTmp = file.absolutePath + tmpFade = false + if (attachedToWindow) { + createAnimationDrawable() + } + } + + fun clearAnimationDrawable(callSuper: Boolean, clearState: Boolean, cancelTask: Boolean) { + if (cancelTask) { + mDisposable.cancel() + } + if (animatedDrawable != null) { + animatedDrawable?.callback = null + animatedDrawable?.recycle() animatedDrawable = null } - setImageDrawable(null) + if (callSuper) { + super.setImageDrawable(null) + } + if (clearState) { + isPlaying = false + loadedFrom = LoadedFrom.NO + filePathTmp = null + rawResTmp = null + tmpFade = null + } } override fun onAttachedToWindow() { - super.onAttachedToWindow() attachedToWindow = true - animatedDrawable?.callback = this - if (playing) { - animatedDrawable?.start() + super.onAttachedToWindow() + when { + loadedFrom == LoadedFrom.NET -> { + filePathTmp?.let { + keyTmp?.let { s -> + fromNet( + s, + it, + Utils.createOkHttp(Constants.GIF_TIMEOUT, true), + isPlaying == true, + ) + } + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } + + loadedFrom == LoadedFrom.RES -> { + rawResTmp?.let { + fromRes( + it, + isPlaying == true, + ) + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } + + loadedFrom != LoadedFrom.NO -> { + createAnimationDrawable() + } } } override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - mDisposable.cancel() attachedToWindow = false - animatedDrawable?.stop() - animatedDrawable?.callback = null + super.onDetachedFromWindow() + if (loadedFrom != LoadedFrom.NO) { + clearAnimationDrawable(callSuper = true, clearState = false, cancelTask = true) + } } fun isPlaying(): Boolean { @@ -231,69 +312,48 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( override fun setImageDrawable(dr: Drawable?) { super.setImageDrawable(dr) - if (dr !is AnimatedFileDrawable) { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) } override fun setImageBitmap(bm: Bitmap?) { super.setImageBitmap(bm) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) } override fun setImageResource(resId: Int) { super.setImageResource(resId) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageURI(uri: Uri?) { + super.setImageURI(uri) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) } fun playAnimation() { - if (animatedDrawable == null) { - return - } - playing = true - if (attachedToWindow) { - animatedDrawable?.start() - } + animatedDrawable?.start() + isPlaying = true } fun resetFrame() { - if (animatedDrawable == null) { - return - } - playing = true - if (attachedToWindow) { - animatedDrawable?.seekTo(0, true) - } + animatedDrawable?.seekTo(0, true) } fun stopAnimation() { - if (animatedDrawable == null) { - return - } - playing = false - if (attachedToWindow) { - animatedDrawable?.stop() + animatedDrawable?.let { + it.stop() + isPlaying = false } } + protected override fun onSaveInstanceState(): Parcelable? { + return super.onSaveInstanceState() + } + + protected override fun onRestoreInstanceState(state: Parcelable?) { + super.onRestoreInstanceState(state) + } + private fun copyRes(@RawRes rawRes: Int): Boolean { try { context.resources.openRawResource(rawRes).use { inputStream -> diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieNetworkCache.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieNetworkCache.kt similarity index 86% rename from app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieNetworkCache.kt rename to app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieNetworkCache.kt index 67b8df661..c63618f42 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieNetworkCache.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieNetworkCache.kt @@ -1,4 +1,4 @@ -package dev.ragnarok.fenrir.view.natives.rlottie +package dev.ragnarok.fenrir.view.natives.animation import android.content.Context import android.util.Log @@ -8,13 +8,13 @@ import okio.buffer import okio.sink import java.io.File -class RLottieNetworkCache(context: Context) { +class ThorVGLottieNetworkCache(context: Context) { private val appContext = context.applicationContext fun fetch(url: String): File? { val cachedFile = getCachedFile(url) ?: return null if (Constants.IS_DEBUG) { - Log.d("RLottieNetworkCache", "Cache hit for $url at ${cachedFile.absolutePath}") + Log.d("ThorVGLottieNetworkCache", "Cache hit for $url at ${cachedFile.absolutePath}") } return cachedFile } @@ -40,12 +40,12 @@ class RLottieNetworkCache(context: Context) { val newFile = File(newFileName) val renamed = file.renameTo(newFile) if (Constants.IS_DEBUG) { - Log.d("RLottieNetworkCache", "Copying temp file to real file ($newFile)") + Log.d("ThorVGLottieNetworkCache", "Copying temp file to real file ($newFile)") } if (!renamed) { if (Constants.IS_DEBUG) { Log.w( - "RLottieNetworkCache", + "ThorVGLottieNetworkCache", "Unable to rename cache file ${file.absolutePath} to ${newFile.absolutePath}." ) } diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieShapeableView.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieShapeableView.kt new file mode 100644 index 000000000..fad5045d4 --- /dev/null +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieShapeableView.kt @@ -0,0 +1,366 @@ +package dev.ragnarok.fenrir.view.natives.animation + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Parcelable +import android.util.AttributeSet +import androidx.annotation.RawRes +import com.google.android.material.imageview.ShapeableImageView +import dev.ragnarok.fenrir.Constants +import dev.ragnarok.fenrir.R +import dev.ragnarok.fenrir.module.FenrirNative +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.Companion.RESTART +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LoadedFrom +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LottieAnimationListener +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.RepeatMode +import dev.ragnarok.fenrir.util.Utils +import dev.ragnarok.fenrir.util.coroutines.CancelableJob +import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain +import kotlinx.coroutines.flow.flow +import okhttp3.Call +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import java.io.File +import kotlin.coroutines.cancellation.CancellationException + +@SuppressLint("CustomViewStyleable") +class ThorVGLottieShapeableView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null +) : + ShapeableImageView(context, attrs) { + private val cache: ThorVGLottieNetworkCache = ThorVGLottieNetworkCache(context) + private var mDisposable = CancelableJob() + private var animatedDrawable: ThorVGLottieDrawable? = null + private var mListener: LottieAnimationListener? = null + + @LoadedFrom + private var loadedFrom = LoadedFrom.NO + private var colorReplacementTmp: IntArray? = null + private var useMoveColorTmp: Boolean = false + private var deleteInvalidFileTmp: Boolean = false + private var filePathTmp: String? = null + + @RawRes + private var rawResTmp: Int? = null + private var isPlaying: Boolean? = null + private var repeatTmp: Boolean? = null + + @RepeatMode + private var repeatModeTmp: Int? = null + + private var mOnAttached = false + + private fun createLottieDrawable() { + if (FenrirNative.isNativeLoaded && mOnAttached && loadedFrom != LoadedFrom.NO && animatedDrawable == null) { + when (loadedFrom) { + LoadedFrom.RES -> { + animatedDrawable = rawResTmp?.let { + ThorVGLottieDrawable( + it, + colorReplacementTmp, + useMoveColorTmp + ) + } + } + + LoadedFrom.FILE -> { + animatedDrawable = filePathTmp?.let { + ThorVGLottieDrawable( + it, + deleteInvalidFileTmp, + colorReplacementTmp, + useMoveColorTmp + ) + } + } + } + if (animatedDrawable == null) { + return + } + repeatModeTmp?.let { animatedDrawable?.setRepeatMode(it) } + repeatTmp?.let { animatedDrawable?.setRepeatCount(if (it) Int.MAX_VALUE else 1) } + super.setImageDrawable(animatedDrawable) + animatedDrawable?.callback = this + animatedDrawable?.setAnimationListener(mListener) + if (isPlaying == true) { + startAnimation() + } + } + } + + private fun setAnimationByUrlCache( + url: String, + autoPlay: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded) { + return + } + val ch = cache.fetch(url) + if (ch == null) { + return + } + if (filePathTmp == ch.absolutePath && deleteInvalidFileTmp == true && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + deleteInvalidFileTmp = true + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.FILE + isPlaying = autoPlay + filePathTmp = ch.absolutePath + if (mOnAttached) { + createLottieDrawable() + } + } + + fun fromNet( + url: String?, + client: OkHttpClient.Builder, + autoPlay: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { + if (loadedFrom == LoadedFrom.NET) { + loadedFrom = LoadedFrom.NO + } + return + } + if (filePathTmp == url && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.NET) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.NET + filePathTmp = url + isPlaying = autoPlay + + if (cache.isCachedFile(url)) { + setAnimationByUrlCache(url, autoPlay, colorReplacement, useMoveColor) + return + } + mDisposable.set(flow { + var call: Call? = null + try { + val request: Request = Request.Builder() + .url(url) + .build() + call = client.build().newCall(request) + val response: Response = call.execute() + if (!response.isSuccessful) { + emit(false) + return@flow + } + cache.writeTempCacheFile(url, response.body.source()) + response.close() + cache.renameTempFile(url) + emit(true) + } catch (e: CancellationException) { + call?.cancel() + throw e + } + }.fromIOToMain { + if (it) { + setAnimationByUrlCache(url, autoPlay, colorReplacement, useMoveColor) + } + }) + } + + fun fromRes( + @RawRes resId: Int, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || resId == -1) { + return + } + if (rawResTmp == resId && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.RES) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.RES + rawResTmp = resId + if (mOnAttached) { + createLottieDrawable() + } + } + + fun fromFile( + file: File, + deleteInvalidFile: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || !file.exists()) { + return + } + if (filePathTmp == file.absolutePath && deleteInvalidFileTmp == deleteInvalidFile && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.FILE + filePathTmp = file.absolutePath + deleteInvalidFileTmp = deleteInvalidFile + if (mOnAttached) { + createLottieDrawable() + } + } + + fun isPlaying(): Boolean { + return animatedDrawable != null && animatedDrawable?.isRunning == true + } + + fun setRepeat(repeat: Boolean) { + animatedDrawable?.setRepeatCount(if (repeat) Int.MAX_VALUE else 1) + repeatTmp = repeat + } + + fun setRepeatMode(@RepeatMode repeatMode: Int) { + animatedDrawable?.setRepeatMode(repeatMode) + repeatModeTmp = repeatMode + } + + fun startAnimation() { + animatedDrawable?.start() + isPlaying = true + } + + fun stopAnimation() { + animatedDrawable?.let { + it.stop() + isPlaying = false + } + } + + fun pauseAnimation() { + animatedDrawable?.pause() + } + + fun resumeAnimation() { + animatedDrawable?.resume() + } + + fun clearAnimationDrawable(callSuper: Boolean, clearState: Boolean, cancelTask: Boolean) { + if (cancelTask) { + mDisposable.cancel() + } + if (animatedDrawable != null) { + animatedDrawable?.callback = null + animatedDrawable?.release() + animatedDrawable = null + } + if (callSuper) { + super.setImageDrawable(null) + } + if (clearState) { + isPlaying = false + loadedFrom = LoadedFrom.NO + filePathTmp = null + rawResTmp = null + deleteInvalidFileTmp = false + } + } + + override fun setImageDrawable(dr: Drawable?) { + super.setImageDrawable(dr) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageBitmap(bm: Bitmap?) { + super.setImageBitmap(bm) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageResource(resId: Int) { + super.setImageResource(resId) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageURI(uri: Uri?) { + super.setImageURI(uri) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + if (measuredWidth <= 0 || measuredHeight <= 0) { + return + } + animatedDrawable?.setSize(measuredWidth, measuredHeight) + } + + override fun onAttachedToWindow() { + mOnAttached = true + + super.onAttachedToWindow() + + if (loadedFrom == LoadedFrom.NET) { + filePathTmp?.let { + fromNet( + it, + Utils.createOkHttp(Constants.GIF_TIMEOUT, true), + isPlaying == true, + colorReplacementTmp, + useMoveColorTmp + ) + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } else if (loadedFrom != LoadedFrom.NO) { + createLottieDrawable() + } + } + + override fun onDetachedFromWindow() { + mOnAttached = false + super.onDetachedFromWindow() + if (loadedFrom != LoadedFrom.NO) { + clearAnimationDrawable(callSuper = true, clearState = false, cancelTask = true) + } + } + + fun setAnimationListener(listener: LottieAnimationListener?) { + mListener = listener + } + + protected override fun onSaveInstanceState(): Parcelable? { + return super.onSaveInstanceState() + } + + protected override fun onRestoreInstanceState(state: Parcelable?) { + super.onRestoreInstanceState(state) + } + + init { + @SuppressLint("CustomViewStyleable") val a = + context.obtainStyledAttributes(attrs, R.styleable.ThorVGLottieView) + rawResTmp = a.getResourceId(R.styleable.ThorVGLottieView_fromRes, 0) + if (rawResTmp == 0) { + rawResTmp = null + } + repeatTmp = a.getBoolean(R.styleable.ThorVGLottieView_loopAnimation, false) + repeatModeTmp = a.getInt(R.styleable.ThorVGLottieView_loopMode, RESTART) + a.recycle() + if (FenrirNative.isNativeLoaded && rawResTmp != null) { + loadedFrom = LoadedFrom.RES + isPlaying = true + } else { + rawResTmp = null + } + } +} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieView.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieView.kt new file mode 100644 index 000000000..e995b0d15 --- /dev/null +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/animation/ThorVGLottieView.kt @@ -0,0 +1,362 @@ +package dev.ragnarok.fenrir.view.natives.animation + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Parcelable +import android.util.AttributeSet +import androidx.annotation.RawRes +import androidx.appcompat.widget.AppCompatImageView +import dev.ragnarok.fenrir.Constants +import dev.ragnarok.fenrir.R +import dev.ragnarok.fenrir.module.FenrirNative +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.Companion.RESTART +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LoadedFrom +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LottieAnimationListener +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.RepeatMode +import dev.ragnarok.fenrir.util.Utils +import dev.ragnarok.fenrir.util.coroutines.CancelableJob +import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain +import kotlinx.coroutines.flow.flow +import okhttp3.Call +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import java.io.File +import kotlin.coroutines.cancellation.CancellationException + +@SuppressLint("CustomViewStyleable") +class ThorVGLottieView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : + AppCompatImageView(context, attrs) { + private val cache: ThorVGLottieNetworkCache = ThorVGLottieNetworkCache(context) + private var mDisposable = CancelableJob() + private var animatedDrawable: ThorVGLottieDrawable? = null + private var mListener: LottieAnimationListener? = null + + @LoadedFrom + private var loadedFrom = LoadedFrom.NO + private var colorReplacementTmp: IntArray? = null + private var useMoveColorTmp: Boolean = false + private var deleteInvalidFileTmp: Boolean = false + private var filePathTmp: String? = null + + @RawRes + private var rawResTmp: Int? = null + private var isPlaying: Boolean? = null + private var repeatTmp: Boolean? = null + + @RepeatMode + private var repeatModeTmp: Int? = null + + private var mOnAttached = false + + private fun createLottieDrawable() { + if (FenrirNative.isNativeLoaded && mOnAttached && loadedFrom != LoadedFrom.NO && animatedDrawable == null) { + when (loadedFrom) { + LoadedFrom.RES -> { + animatedDrawable = rawResTmp?.let { + ThorVGLottieDrawable( + it, + colorReplacementTmp, + useMoveColorTmp + ) + } + } + + LoadedFrom.FILE -> { + animatedDrawable = filePathTmp?.let { + ThorVGLottieDrawable( + it, + deleteInvalidFileTmp, + colorReplacementTmp, + useMoveColorTmp + ) + } + } + } + if (animatedDrawable == null) { + return + } + repeatModeTmp?.let { animatedDrawable?.setRepeatMode(it) } + repeatTmp?.let { animatedDrawable?.setRepeatCount(if (it) Int.MAX_VALUE else 1) } + super.setImageDrawable(animatedDrawable) + animatedDrawable?.callback = this + animatedDrawable?.setAnimationListener(mListener) + if (isPlaying == true) { + startAnimation() + } + } + } + + private fun setAnimationByUrlCache( + url: String, + autoPlay: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded) { + return + } + val ch = cache.fetch(url) + if (ch == null) { + return + } + if (filePathTmp == ch.absolutePath && deleteInvalidFileTmp == true && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + deleteInvalidFileTmp = true + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.FILE + isPlaying = autoPlay + filePathTmp = ch.absolutePath + if (mOnAttached) { + createLottieDrawable() + } + } + + fun fromNet( + url: String?, + client: OkHttpClient.Builder, + autoPlay: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { + if (loadedFrom == LoadedFrom.NET) { + loadedFrom = LoadedFrom.NO + } + return + } + if (filePathTmp == url && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.NET) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.NET + filePathTmp = url + isPlaying = autoPlay + + if (cache.isCachedFile(url)) { + setAnimationByUrlCache(url, autoPlay, colorReplacement, useMoveColor) + return + } + mDisposable.set(flow { + var call: Call? = null + try { + val request: Request = Request.Builder() + .url(url) + .build() + call = client.build().newCall(request) + val response: Response = call.execute() + if (!response.isSuccessful) { + emit(false) + return@flow + } + cache.writeTempCacheFile(url, response.body.source()) + response.close() + cache.renameTempFile(url) + emit(true) + } catch (e: CancellationException) { + call?.cancel() + throw e + } + }.fromIOToMain { + if (it) { + setAnimationByUrlCache(url, autoPlay, colorReplacement, useMoveColor) + } + }) + } + + fun fromRes( + @RawRes resId: Int, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || resId == -1) { + return + } + if (rawResTmp == resId && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.RES) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.RES + rawResTmp = resId + if (mOnAttached) { + createLottieDrawable() + } + } + + fun fromFile( + file: File, + deleteInvalidFile: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || !file.exists()) { + return + } + if (filePathTmp == file.absolutePath && deleteInvalidFileTmp == deleteInvalidFile && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.FILE + filePathTmp = file.absolutePath + deleteInvalidFileTmp = deleteInvalidFile + if (mOnAttached) { + createLottieDrawable() + } + } + + fun isPlaying(): Boolean { + return animatedDrawable != null && animatedDrawable?.isRunning == true + } + + fun setRepeat(repeat: Boolean) { + animatedDrawable?.setRepeatCount(if (repeat) Int.MAX_VALUE else 1) + repeatTmp = repeat + } + + fun setRepeatMode(@RepeatMode repeatMode: Int) { + animatedDrawable?.setRepeatMode(repeatMode) + repeatModeTmp = repeatMode + } + + fun startAnimation() { + animatedDrawable?.start() + isPlaying = true + } + + fun stopAnimation() { + animatedDrawable?.let { + it.stop() + isPlaying = false + } + } + + fun pauseAnimation() { + animatedDrawable?.pause() + } + + fun resumeAnimation() { + animatedDrawable?.resume() + } + + fun clearAnimationDrawable(callSuper: Boolean, clearState: Boolean, cancelTask: Boolean) { + if (cancelTask) { + mDisposable.cancel() + } + if (animatedDrawable != null) { + animatedDrawable?.callback = null + animatedDrawable?.release() + animatedDrawable = null + } + if (callSuper) { + super.setImageDrawable(null) + } + if (clearState) { + isPlaying = false + loadedFrom = LoadedFrom.NO + filePathTmp = null + rawResTmp = null + deleteInvalidFileTmp = false + } + } + + override fun setImageDrawable(dr: Drawable?) { + super.setImageDrawable(dr) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageBitmap(bm: Bitmap?) { + super.setImageBitmap(bm) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageResource(resId: Int) { + super.setImageResource(resId) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageURI(uri: Uri?) { + super.setImageURI(uri) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + if (measuredWidth <= 0 || measuredHeight <= 0) { + return + } + animatedDrawable?.setSize(measuredWidth, measuredHeight) + } + + override fun onAttachedToWindow() { + mOnAttached = true + + super.onAttachedToWindow() + + if (loadedFrom == LoadedFrom.NET) { + filePathTmp?.let { + fromNet( + it, + Utils.createOkHttp(Constants.GIF_TIMEOUT, true), + isPlaying == true, + colorReplacementTmp, + useMoveColorTmp + ) + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } else if (loadedFrom != LoadedFrom.NO) { + createLottieDrawable() + } + } + + override fun onDetachedFromWindow() { + mOnAttached = false + super.onDetachedFromWindow() + if (loadedFrom != LoadedFrom.NO) { + clearAnimationDrawable(callSuper = true, clearState = false, cancelTask = true) + } + } + + fun setAnimationListener(listener: LottieAnimationListener?) { + mListener = listener + } + + protected override fun onSaveInstanceState(): Parcelable? { + return super.onSaveInstanceState() + } + + protected override fun onRestoreInstanceState(state: Parcelable?) { + super.onRestoreInstanceState(state) + } + + init { + val a = context.obtainStyledAttributes(attrs, R.styleable.ThorVGLottieView) + rawResTmp = a.getResourceId(R.styleable.ThorVGLottieView_fromRes, 0) + if (rawResTmp == 0) { + rawResTmp = null + } + repeatTmp = a.getBoolean(R.styleable.ThorVGLottieView_loopAnimation, false) + repeatModeTmp = a.getInt(R.styleable.ThorVGLottieView_loopMode, RESTART) + a.recycle() + if (FenrirNative.isNativeLoaded && rawResTmp != null) { + loadedFrom = LoadedFrom.RES + isPlaying = true + } else { + rawResTmp = null + } + } +} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieImageView.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieImageView.kt deleted file mode 100644 index 6b38d240e..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieImageView.kt +++ /dev/null @@ -1,294 +0,0 @@ -package dev.ragnarok.fenrir.view.natives.rlottie - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.drawable.Drawable -import android.util.AttributeSet -import androidx.annotation.RawRes -import androidx.appcompat.widget.AppCompatImageView -import dev.ragnarok.fenrir.R -import dev.ragnarok.fenrir.module.BufferWriteNative -import dev.ragnarok.fenrir.module.FenrirNative -import dev.ragnarok.fenrir.module.rlottie.RLottieDrawable -import dev.ragnarok.fenrir.util.coroutines.CancelableJob -import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain -import kotlinx.coroutines.flow.flow -import okhttp3.Call -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import java.io.File -import kotlin.coroutines.cancellation.CancellationException - -class RLottieImageView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - AppCompatImageView(context, attrs) { - private val cache: RLottieNetworkCache = RLottieNetworkCache(context) - private var layerColors: HashMap? = null - private var animatedDrawable: RLottieDrawable? = null - private var autoRepeat: Boolean - private var attachedToWindow = false - private var playing = false - private var mDisposable = CancelableJob() - fun clearLayerColors() { - layerColors?.clear() - } - - fun setLayerColor(layer: String, color: Int) { - if (layerColors == null) { - layerColors = HashMap() - } - (layerColors ?: return)[layer] = color - animatedDrawable?.setLayerColor(layer, color) - } - - fun replaceColors(colors: IntArray?) { - animatedDrawable?.replaceColors(colors) - } - - private fun setAnimationByUrlCache(url: String, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - val ch = cache.fetch(url) - if (ch == null) { - setImageDrawable(null) - return - } - autoRepeat = false - setAnimation( - RLottieDrawable( - ch, true, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - playAnimation() - } - - fun fromNet(url: String?, client: OkHttpClient.Builder, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { - return - } - clearAnimationDrawable() - if (cache.isCachedFile(url)) { - setAnimationByUrlCache(url, w, h) - return - } - mDisposable.set(flow { - var call: Call? = null - try { - val request: Request = Request.Builder() - .url(url) - .build() - call = client.build().newCall(request) - val response: Response = call.execute() - if (!response.isSuccessful) { - emit(false) - return@flow - } - cache.writeTempCacheFile(url, response.body.source()) - response.close() - cache.renameTempFile(url) - emit(true) - } catch (e: CancellationException) { - call?.cancel() - throw e - } - }.fromIOToMain { - if (it) { - setAnimationByUrlCache(url, w, h) - } - }) - } - - private fun setAnimation(rLottieDrawable: RLottieDrawable) { - animatedDrawable = rLottieDrawable - animatedDrawable?.setAutoRepeat(if (autoRepeat) 1 else 0) - if (layerColors != null) { - animatedDrawable?.beginApplyLayerColors() - for ((key, value) in layerColors ?: return) { - animatedDrawable?.setLayerColor(key, value) - } - animatedDrawable?.commitApplyLayerColors() - } - animatedDrawable?.setAllowDecodeSingleFrame(true) - animatedDrawable?.setCurrentParentView(this) - setImageDrawable(animatedDrawable) - } - - @JvmOverloads - fun fromRes( - @RawRes resId: Int, - w: Int, - h: Int, - colorReplacement: IntArray? = null, - useMoveColor: Boolean = false - ) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - resId, - w, - h, - false, - colorReplacement, - useMoveColor - ) - ) - } - - fun fromFile(file: File, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - file, false, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - } - - fun fromString(jsonString: BufferWriteNative, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - jsonString, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - } - - fun clearAnimationDrawable() { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - setImageDrawable(null) - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - attachedToWindow = true - animatedDrawable?.setCurrentParentView(this) - if (playing) { - animatedDrawable?.start() - } - } - - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - mDisposable.cancel() - attachedToWindow = false - animatedDrawable?.stop() - animatedDrawable?.setCurrentParentView(null) - } - - fun isPlaying(): Boolean { - return animatedDrawable != null && animatedDrawable?.isRunning == true - } - - fun setAutoRepeat(repeat: Boolean) { - autoRepeat = repeat - } - - fun setProgress(progress: Float) { - animatedDrawable?.setProgress(progress) - } - - override fun setImageDrawable(dr: Drawable?) { - super.setImageDrawable(dr) - if (dr !is RLottieDrawable) { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - } - - override fun setImageBitmap(bm: Bitmap?) { - super.setImageBitmap(bm) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - - override fun setImageResource(resId: Int) { - super.setImageResource(resId) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - - fun playAnimation() { - playing = true - if (attachedToWindow) { - animatedDrawable?.start() - } - } - - fun replayAnimation() { - if (animatedDrawable == null) { - return - } - playing = true - if (attachedToWindow) { - animatedDrawable?.stop() - animatedDrawable?.setAutoRepeat(1) - animatedDrawable?.start() - } - } - - fun resetFrame() { - playing = true - if (attachedToWindow) { - animatedDrawable?.setProgress(0f) - } - } - - fun stopAnimation() { - playing = false - if (attachedToWindow) { - animatedDrawable?.stop() - } - } - - init { - val a = context.obtainStyledAttributes(attrs, R.styleable.RLottieImageView) - val animRes = a.getResourceId(R.styleable.RLottieImageView_fromRes, 0) - autoRepeat = a.getBoolean(R.styleable.RLottieImageView_loopAnimation, false) - val width = a.getDimension(R.styleable.RLottieImageView_w, 28f).toInt() - val height = a.getDimension(R.styleable.RLottieImageView_h, 28f).toInt() - a.recycle() - if (FenrirNative.isNativeLoaded && animRes != 0) { - setAnimation(RLottieDrawable(animRes, width, height, false, null, false)) - playAnimation() - } - } -} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieShapeableImageView.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieShapeableImageView.kt deleted file mode 100644 index 42561a439..000000000 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/natives/rlottie/RLottieShapeableImageView.kt +++ /dev/null @@ -1,297 +0,0 @@ -package dev.ragnarok.fenrir.view.natives.rlottie - -import android.annotation.SuppressLint -import android.content.Context -import android.graphics.Bitmap -import android.graphics.drawable.Drawable -import android.util.AttributeSet -import androidx.annotation.RawRes -import com.google.android.material.imageview.ShapeableImageView -import dev.ragnarok.fenrir.R -import dev.ragnarok.fenrir.module.BufferWriteNative -import dev.ragnarok.fenrir.module.FenrirNative -import dev.ragnarok.fenrir.module.rlottie.RLottieDrawable -import dev.ragnarok.fenrir.util.coroutines.CancelableJob -import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain -import kotlinx.coroutines.flow.flow -import okhttp3.Call -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import java.io.File -import kotlin.coroutines.cancellation.CancellationException - -class RLottieShapeableImageView @JvmOverloads constructor( - context: Context, attrs: AttributeSet? = null -) : ShapeableImageView(context, attrs) { - private val cache: RLottieNetworkCache = RLottieNetworkCache(context) - private var layerColors: HashMap? = null - private var animatedDrawable: RLottieDrawable? = null - private var autoRepeat: Boolean - private var attachedToWindow = false - private var playing = false - private var mDisposable = CancelableJob() - fun clearLayerColors() { - layerColors?.clear() - } - - fun setLayerColor(layer: String, color: Int) { - if (layerColors == null) { - layerColors = HashMap() - } - (layerColors ?: return)[layer] = color - animatedDrawable?.setLayerColor(layer, color) - } - - fun replaceColors(colors: IntArray?) { - animatedDrawable?.replaceColors(colors) - } - - private fun setAnimationByUrlCache(url: String, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - val ch = cache.fetch(url) - if (ch == null) { - setImageDrawable(null) - return - } - autoRepeat = false - setAnimation( - RLottieDrawable( - ch, true, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - playAnimation() - } - - fun fromNet(url: String?, client: OkHttpClient.Builder, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { - return - } - clearAnimationDrawable() - if (cache.isCachedFile(url)) { - setAnimationByUrlCache(url, w, h) - return - } - mDisposable.set(flow { - var call: Call? = null - try { - val request: Request = Request.Builder() - .url(url) - .build() - call = client.build().newCall(request) - val response: Response = call.execute() - if (!response.isSuccessful) { - emit(false) - return@flow - } - cache.writeTempCacheFile(url, response.body.source()) - response.close() - cache.renameTempFile(url) - emit(true) - } catch (e: CancellationException) { - call?.cancel() - throw e - } - }.fromIOToMain { - if (it) { - setAnimationByUrlCache(url, w, h) - } - }) - } - - private fun setAnimation(rLottieDrawable: RLottieDrawable) { - animatedDrawable = rLottieDrawable - animatedDrawable?.setAutoRepeat(if (autoRepeat) 1 else 0) - if (layerColors != null) { - animatedDrawable?.beginApplyLayerColors() - for ((key, value) in layerColors ?: return) { - animatedDrawable?.setLayerColor(key, value) - } - animatedDrawable?.commitApplyLayerColors() - } - animatedDrawable?.setAllowDecodeSingleFrame(true) - animatedDrawable?.setCurrentParentView(this) - setImageDrawable(animatedDrawable) - } - - @JvmOverloads - fun fromRes( - @RawRes resId: Int, - w: Int, - h: Int, - colorReplacement: IntArray? = null, - useMoveColor: Boolean = false - ) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - resId, - w, - h, - false, - colorReplacement, - useMoveColor - ) - ) - } - - fun fromFile(file: File, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - file, false, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - } - - fun fromString(jsonString: BufferWriteNative, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - jsonString, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - } - - fun clearAnimationDrawable() { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - setImageDrawable(null) - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - attachedToWindow = true - animatedDrawable?.setCurrentParentView(this) - if (playing) { - animatedDrawable?.start() - } - } - - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - mDisposable.cancel() - attachedToWindow = false - animatedDrawable?.stop() - animatedDrawable?.setCurrentParentView(null) - } - - fun isPlaying(): Boolean { - return animatedDrawable != null && animatedDrawable?.isRunning == true - } - - fun setAutoRepeat(repeat: Boolean) { - autoRepeat = repeat - } - - fun setProgress(progress: Float) { - animatedDrawable?.setProgress(progress) - } - - override fun setImageDrawable(dr: Drawable?) { - super.setImageDrawable(dr) - if (dr !is RLottieDrawable) { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - } - - override fun setImageBitmap(bm: Bitmap?) { - super.setImageBitmap(bm) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - - override fun setImageResource(resId: Int) { - super.setImageResource(resId) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - - fun playAnimation() { - playing = true - if (attachedToWindow) { - animatedDrawable?.start() - } - } - - fun replayAnimation() { - if (animatedDrawable == null) { - return - } - playing = true - if (attachedToWindow) { - animatedDrawable?.stop() - animatedDrawable?.setAutoRepeat(1) - animatedDrawable?.start() - } - } - - fun resetFrame() { - playing = true - if (attachedToWindow) { - animatedDrawable?.setProgress(0f) - } - } - - fun stopAnimation() { - playing = false - if (attachedToWindow) { - animatedDrawable?.stop() - } - } - - init { - @SuppressLint("CustomViewStyleable") val a = - context.obtainStyledAttributes(attrs, R.styleable.RLottieImageView) - val animRes = a.getResourceId(R.styleable.RLottieImageView_fromRes, 0) - autoRepeat = a.getBoolean(R.styleable.RLottieImageView_loopAnimation, false) - val width = a.getDimension(R.styleable.RLottieImageView_w, 28f).toInt() - val height = a.getDimension(R.styleable.RLottieImageView_h, 28f).toInt() - a.recycle() - if (FenrirNative.isNativeLoaded && animRes != 0) { - setAnimation(RLottieDrawable(animRes, width, height, false, null, false)) - playAnimation() - } - } -} diff --git a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/navigation/SideNavigationView.kt b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/navigation/SideNavigationView.kt index b2cfb14d1..8edd8a8b4 100644 --- a/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/navigation/SideNavigationView.kt +++ b/app_fenrir/src/main/kotlin/dev/ragnarok/fenrir/view/navigation/SideNavigationView.kt @@ -32,14 +32,13 @@ import dev.ragnarok.fenrir.settings.CurrentTheme import dev.ragnarok.fenrir.settings.ISettings import dev.ragnarok.fenrir.settings.Settings import dev.ragnarok.fenrir.toColor -import dev.ragnarok.fenrir.util.Utils.dp import dev.ragnarok.fenrir.util.Utils.firstNonEmptyString import dev.ragnarok.fenrir.util.Utils.getVerifiedColor import dev.ragnarok.fenrir.util.Utils.setBackgroundTint import dev.ragnarok.fenrir.util.coroutines.CompositeJob import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.fromIOToMain import dev.ragnarok.fenrir.util.coroutines.CoroutinesUtils.sharedFlowToMain -import dev.ragnarok.fenrir.view.natives.rlottie.RLottieImageView +import dev.ragnarok.fenrir.view.natives.animation.ThorVGLottieView class SideNavigationView : AbsNavigationView, MenuListAdapter.ActionListener { private val mCompositeJob = CompositeJob() @@ -47,7 +46,7 @@ class SideNavigationView : AbsNavigationView, MenuListAdapter.ActionListener { private var mDrawerLayout: DrawerLayout? = null private var ivHeaderAvatar: ImageView? = null private var ivVerified: ImageView? = null - private var bDonate: RLottieImageView? = null + private var bDonate: ThorVGLottieView? = null private var tvUserName: TextView? = null private var tvDomain: TextView? = null private var mRecentChats: MutableList? = null @@ -345,27 +344,20 @@ class SideNavigationView : AbsNavigationView, MenuListAdapter.ActionListener { val donate_anim = Settings.get().main().donate_anim_set if (donate_anim > 0 && user.isDonated) { bDonate?.visibility = VISIBLE - bDonate?.setAutoRepeat(true) + bDonate?.setRepeat(true) if (donate_anim == 2) { val cur = Settings.get().ui().mainThemeKey if ("fire" == cur || "orange" == cur || "orange_gray" == cur || "yellow_violet" == cur) { tvUserName?.setTextColor("#df9d00".toColor()) tvDomain?.setTextColor("#df9d00".toColor()) setBackgroundTint(ivVerified, "#df9d00".toColor()) - bDonate?.fromRes( - dev.ragnarok.fenrir_common.R.raw.donater_fire, - dp(100f), - dp(100f), - null - ) + bDonate?.fromRes(dev.ragnarok.fenrir_common.R.raw.donater_fire) } else { tvUserName?.setTextColor(CurrentTheme.getColorPrimary(context)) tvDomain?.setTextColor(CurrentTheme.getColorPrimary(context)) setBackgroundTint(ivVerified, CurrentTheme.getColorPrimary(context)) bDonate?.fromRes( dev.ragnarok.fenrir_common.R.raw.donater_fire, - dp(100f), - dp(100f), intArrayOf(0xFF812E, CurrentTheme.getColorPrimary(context)), true ) @@ -373,8 +365,6 @@ class SideNavigationView : AbsNavigationView, MenuListAdapter.ActionListener { } else { bDonate?.fromRes( dev.ragnarok.fenrir_common.R.raw.donater, - dp(100f), - dp(100f), intArrayOf( 0xffffff, CurrentTheme.getColorPrimary(context), @@ -383,7 +373,7 @@ class SideNavigationView : AbsNavigationView, MenuListAdapter.ActionListener { ) ) } - bDonate?.playAnimation() + bDonate?.startAnimation() } else { bDonate?.setImageDrawable(null) bDonate?.visibility = GONE diff --git a/app_fenrir/src/main/res/layout/activity_lottie.xml b/app_fenrir/src/main/res/layout/activity_lottie.xml index 98b6fec9f..1ea14fc5f 100644 --- a/app_fenrir/src/main/res/layout/activity_lottie.xml +++ b/app_fenrir/src/main/res/layout/activity_lottie.xml @@ -1,19 +1,21 @@ - + android:adjustViewBounds="true" + app:loopAnimation="true" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app_fenrir/src/main/res/layout/item_message_my_sticker.xml b/app_fenrir/src/main/res/layout/item_message_my_sticker.xml index df7487b70..5d87cd373 100644 --- a/app_fenrir/src/main/res/layout/item_message_my_sticker.xml +++ b/app_fenrir/src/main/res/layout/item_message_my_sticker.xml @@ -61,7 +61,7 @@ android:gravity="end" android:orientation="vertical"> - diff --git a/app_fenrir/src/main/res/layout/item_reaction.xml b/app_fenrir/src/main/res/layout/item_reaction.xml index a11a11c35..1159ca6ca 100644 --- a/app_fenrir/src/main/res/layout/item_reaction.xml +++ b/app_fenrir/src/main/res/layout/item_reaction.xml @@ -11,7 +11,7 @@ android:paddingRight="8dp" android:paddingBottom="2dp"> - diff --git a/app_fenrir/src/main/res/layout/item_special_theme.xml b/app_fenrir/src/main/res/layout/item_special_theme.xml index 67edf4444..be23512ee 100644 --- a/app_fenrir/src/main/res/layout/item_special_theme.xml +++ b/app_fenrir/src/main/res/layout/item_special_theme.xml @@ -39,7 +39,7 @@ tools:text="Theme Name" /> - - - - - - - + android:layout_width="84dp" + android:layout_height="84dp" /> \ No newline at end of file diff --git a/app_fenrir/src/main/res/layout/vk_photo_item.xml b/app_fenrir/src/main/res/layout/vk_photo_item.xml index 73fed2a1c..b909f6478 100644 --- a/app_fenrir/src/main/res/layout/vk_photo_item.xml +++ b/app_fenrir/src/main/res/layout/vk_photo_item.xml @@ -25,7 +25,7 @@ app:aspectRatioW="1" app:dominantMeasurement="width" /> - - + android:id="@+id/action_proxy" + android:title="@string/button_proxy" /> + + @drawable/runes_dark @drawable/background_rectangle_dash @style/CustomBottomSheetDialog + @color/m3_ref_palette_dynamic_secondary30 diff --git a/app_fenrir/src/main/res/values-ru/strings.xml b/app_fenrir/src/main/res/values-ru/strings.xml index dfc9bfe70..d8b4b039a 100644 --- a/app_fenrir/src/main/res/values-ru/strings.xml +++ b/app_fenrir/src/main/res/values-ru/strings.xml @@ -700,7 +700,7 @@ Добавить в закладки Убрать из закладок Уведомлять о записях - Неуведомлять о записях + Не уведомлять о записях Загрузка Все друзья Поделиться ссылкой @@ -1695,4 +1695,7 @@ Однострочные элементы Видео Фото + Вход по qr + Вход по qr не возможен! + Перевыпуск токена… \ No newline at end of file diff --git a/app_fenrir/src/main/res/values-v31/styles.xml b/app_fenrir/src/main/res/values-v31/styles.xml index 51a046a11..db6f53912 100644 --- a/app_fenrir/src/main/res/values-v31/styles.xml +++ b/app_fenrir/src/main/res/values-v31/styles.xml @@ -57,5 +57,6 @@ @drawable/background_rectangle_dash_inverse @style/CustomBottomSheetDialog + @color/m3_ref_palette_dynamic_secondary90 diff --git a/app_fenrir/src/main/res/values/attrs.xml b/app_fenrir/src/main/res/values/attrs.xml index 1115499e5..4587bac80 100644 --- a/app_fenrir/src/main/res/values/attrs.xml +++ b/app_fenrir/src/main/res/values/attrs.xml @@ -199,11 +199,13 @@ - + + + + + - - diff --git a/app_fenrir/src/main/res/values/strings.xml b/app_fenrir/src/main/res/values/strings.xml index 99578a9dd..246c3cb9b 100644 --- a/app_fenrir/src/main/res/values/strings.xml +++ b/app_fenrir/src/main/res/values/strings.xml @@ -1933,4 +1933,7 @@ Single line elements Videos Photos + Auth by qr + QR code entry is not possible! + Refreshing Token… diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/activity/photopager/PhotoPagerActivity.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/activity/photopager/PhotoPagerActivity.kt index 6a577f469..264138ea2 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/activity/photopager/PhotoPagerActivity.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/activity/photopager/PhotoPagerActivity.kt @@ -63,7 +63,7 @@ import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.delayTaskFlow import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.toMain import dev.ragnarok.filegallery.util.toast.CustomToast.Companion.createCustomToast import dev.ragnarok.filegallery.view.TouchImageView -import dev.ragnarok.filegallery.view.natives.rlottie.RLottieImageView +import dev.ragnarok.filegallery.view.natives.animation.ThorVGLottieView import dev.ragnarok.filegallery.view.pager.WeakPicassoLoadCallback class PhotoPagerActivity : BaseMvpActivity(), IPhotoPagerView, @@ -107,7 +107,7 @@ class PhotoPagerActivity : BaseMvpActivity private var mViewPager: ViewPager2? = null private var mContentRoot: RelativeLayout? = null - private var mLoadingProgressBar: RLottieImageView? = null + private var mLoadingProgressBar: ThorVGLottieView? = null private var mLoadingProgressBarDispose = CancelableJob() private var mLoadingProgressBarLoaded = false private var mToolbar: Toolbar? = null @@ -352,8 +352,6 @@ class PhotoPagerActivity : BaseMvpActivity mLoadingProgressBar?.visibility = View.VISIBLE mLoadingProgressBar?.fromRes( R.raw.loading, - Utils.dp(100F), - Utils.dp(100F), intArrayOf( 0x000000, Color.WHITE, @@ -361,12 +359,15 @@ class PhotoPagerActivity : BaseMvpActivity Color.WHITE ) ) - mLoadingProgressBar?.playAnimation() + mLoadingProgressBar?.startAnimation() }) } else if (mLoadingProgressBarLoaded) { mLoadingProgressBarLoaded = false mLoadingProgressBar?.visibility = View.GONE - mLoadingProgressBar?.clearAnimationDrawable() + mLoadingProgressBar?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } @@ -449,7 +450,7 @@ class PhotoPagerActivity : BaseMvpActivity private val mPicassoLoadCallback: WeakPicassoLoadCallback val photo: TouchImageView val selected: View - val progress: RLottieImageView + val progress: ThorVGLottieView var animationDispose = CancelableJob() private var mAnimationLoaded = false private var mLoadingNow = false @@ -500,13 +501,19 @@ class PhotoPagerActivity : BaseMvpActivity val k = ObjectAnimator.ofFloat(progress, View.ALPHA, 0.0f).setDuration(1000) k.addListener(object : StubAnimatorListener() { override fun onAnimationEnd(animation: Animator) { - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE progress.alpha = 1f } override fun onAnimationCancel(animation: Animator) { - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE progress.alpha = 1f } @@ -514,7 +521,10 @@ class PhotoPagerActivity : BaseMvpActivity k.start() } else if (mAnimationLoaded && !mLoadingNow) { mAnimationLoaded = false - progress.clearAnimationDrawable() + progress.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) progress.visibility = View.GONE } else if (mLoadingNow) { animationDispose.set(delayTaskFlow(300).toMain { @@ -522,8 +532,6 @@ class PhotoPagerActivity : BaseMvpActivity progress.visibility = View.VISIBLE progress.fromRes( R.raw.loading, - Utils.dp(100F), - Utils.dp(100F), intArrayOf( 0x000000, CurrentTheme.getColorPrimary(this@PhotoPagerActivity), @@ -531,7 +539,7 @@ class PhotoPagerActivity : BaseMvpActivity CurrentTheme.getColorSecondary(this@PhotoPagerActivity) ) ) - progress.playAnimation() + progress.startAnimation() }) } } @@ -553,7 +561,7 @@ class PhotoPagerActivity : BaseMvpActivity .load(image.photo_url) .into(photo, mPicassoLoadCallback) } else { - photo.fromAnimFile(Uri.parse(image.photo_url).toFile()) + photo.fromAnimationFile(Uri.parse(image.photo_url).toFile(), true) mLoadingNow = false resolveProgressVisibility(true) } diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/AudioPlayerFragment.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/AudioPlayerFragment.kt index 3d47792db..1ac7ac514 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/AudioPlayerFragment.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/AudioPlayerFragment.kt @@ -59,7 +59,7 @@ import dev.ragnarok.filegallery.view.media.PlayPauseButton import dev.ragnarok.filegallery.view.media.RepeatButton import dev.ragnarok.filegallery.view.media.RepeatingImageButton import dev.ragnarok.filegallery.view.media.ShuffleButton -import dev.ragnarok.filegallery.view.natives.rlottie.RLottieShapeableImageView +import dev.ragnarok.filegallery.view.natives.animation.ThorVGLottieShapeableView import kotlinx.coroutines.Job import kotlin.math.min @@ -94,7 +94,7 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee private var ivBackground: View? = null // Handler used to update the current time - private var mRefreshDisposable = CancelableJob() + private var mRefreshJob = CancelableJob() private var mStartSeekPos: Long = 0 private var mLastSeekEventTime: Long = 0 private var coverAdapter: CoverAdapter? = null @@ -259,10 +259,9 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee super.onPageSelected(position) if (currentPage != position) { currentPage = position - playDispose.set( - delayTaskFlow(400) - .toMain { MusicPlaybackController.skip(position) } - ) + playDispose.cancel() + playDispose += delayTaskFlow(400) + .toMain { MusicPlaybackController.skip(position) } ivCoverPager?.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) } } @@ -363,7 +362,7 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee override fun onDestroy() { playDispose.cancel() mCompositeJob.cancel() - mRefreshDisposable.cancel() + mRefreshJob.cancel() PicassoInstance.with().cancelTag(PLAYER_TAG) super.onDestroy() } @@ -520,10 +519,11 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee * @param delay When to update */ private fun queueNextRefresh(delay: Long) { - mRefreshDisposable.cancel() - mRefreshDisposable.set(delayTaskFlow(delay) - .toMain { queueNextRefresh(refreshCurrentTime()) } - ) + mRefreshJob.cancel() + mRefreshJob += delayTaskFlow(delay) + .toMain { + queueNextRefresh(refreshCurrentTime()) + } } private fun resolveControlViews() { @@ -677,7 +677,7 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee } private inner class CoverViewHolder(view: View) : RecyclerView.ViewHolder(view) { - val ivCover: RLottieShapeableImageView = view.findViewById(R.id.cover) + val ivCover: ThorVGLottieShapeableView = view.findViewById(R.id.cover) val holderTarget = object : BitmapTarget { override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) { @@ -696,8 +696,6 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee if (FenrirNative.isNativeLoaded) { ivCover.fromRes( R.raw.auidio_no_cover, - 450, - 450, intArrayOf( 0x333333, CurrentTheme.getColorSurface(requireActivity()), @@ -705,7 +703,7 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee CurrentTheme.getColorOnSurface(requireActivity()) ) ) - ivCover.playAnimation() + ivCover.startAnimation() } else { ivCover.setImageResource(R.drawable.itunes) ivCover.drawable?.setTint( @@ -731,8 +729,6 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee if (FenrirNative.isNativeLoaded) { ivCover.fromRes( R.raw.auidio_no_cover, - 450, - 450, intArrayOf( 0x333333, CurrentTheme.getColorSurface(requireActivity()), @@ -740,7 +736,7 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee CurrentTheme.getColorOnSurface(requireActivity()) ) ) - ivCover.playAnimation() + ivCover.startAnimation() } else { ivCover.setImageResource(R.drawable.itunes) ivCover.drawable.setTint(CurrentTheme.getColorOnSurface(requireActivity())) @@ -772,16 +768,6 @@ class AudioPlayerFragment : BottomSheetDialogFragment(), CustomSeekBar.CustomSee override fun onViewDetachedFromWindow(holder: CoverViewHolder) { super.onViewDetachedFromWindow(holder) PicassoInstance.with().cancelRequest(holder.ivCover) - if (holder.ivCover.drawable is Animatable) { - (holder.ivCover.drawable as Animatable).stop() - } - } - - override fun onViewAttachedToWindow(holder: CoverViewHolder) { - super.onViewAttachedToWindow(holder) - if (holder.ivCover.drawable is Animatable) { - (holder.ivCover.drawable as Animatable).start() - } } override fun onBindViewHolder(holder: CoverViewHolder, position: Int) { diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/PreferencesFragment.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/PreferencesFragment.kt index 73dc61606..11258aa78 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/PreferencesFragment.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/PreferencesFragment.kt @@ -75,7 +75,7 @@ import dev.ragnarok.filegallery.util.serializeble.prefs.Preferences import dev.ragnarok.filegallery.util.toast.CustomSnackbars import dev.ragnarok.filegallery.util.toast.CustomToast.Companion.createCustomToast import dev.ragnarok.filegallery.view.MySearchView -import dev.ragnarok.filegallery.view.natives.rlottie.RLottieImageView +import dev.ragnarok.filegallery.view.natives.animation.ThorVGLottieView import okio.buffer import okio.source import java.io.File @@ -940,12 +940,10 @@ class PreferencesFragment : AbsPreferencesFragment(), PreferencesAdapter.OnScree summary = BuildConfig.VERSION_NAME onClick { val view = View.inflate(requireActivity(), R.layout.dialog_about_us, null) - val anim: RLottieImageView = view.findViewById(R.id.lottie_animation) + val anim: ThorVGLottieView = view.findViewById(R.id.lottie_animation) if (FenrirNative.isNativeLoaded) { anim.fromRes( R.raw.fenrir, - Utils.dp(170f), - Utils.dp(170f), intArrayOf( 0x333333, getColorPrimary(requireActivity()), @@ -953,7 +951,7 @@ class PreferencesFragment : AbsPreferencesFragment(), PreferencesAdapter.OnScree getColorSecondary(requireActivity()) ) ) - anim.playAnimation() + anim.startAnimation() } MaterialAlertDialogBuilder(requireActivity()) .setView(view) diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/base/compat/ViewHostDelegate.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/base/compat/ViewHostDelegate.kt index bbadc340d..30084c279 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/base/compat/ViewHostDelegate.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/base/compat/ViewHostDelegate.kt @@ -86,9 +86,9 @@ class ViewHostDelegate

, V : IMvpView> { } fun onSaveInstanceState(outState: Bundle) { - presenter?.run { + presenter?.let { lastKnownPresenterState = Bundle() - saveState(lastKnownPresenterState ?: return@run) + it.saveState(lastKnownPresenterState ?: return@let) } outState.putBundle(SAVE_PRESENTER_STATE, lastKnownPresenterState) diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/filemanager/FileManagerAdapter.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/filemanager/FileManagerAdapter.kt index b59de5a47..b9217e0b0 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/filemanager/FileManagerAdapter.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/filemanager/FileManagerAdapter.kt @@ -39,7 +39,7 @@ import dev.ragnarok.filegallery.util.Utils import dev.ragnarok.filegallery.util.coroutines.CancelableJob import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.fromIOToMain import dev.ragnarok.filegallery.util.toast.CustomToast -import dev.ragnarok.filegallery.view.natives.rlottie.RLottieImageView +import dev.ragnarok.filegallery.view.natives.animation.ThorVGLottieView import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import java.io.File @@ -408,15 +408,16 @@ class FileManagerAdapter(private var context: Context, private var data: List) : RecyclerView.Adapter() { @@ -410,15 +410,16 @@ class FileManagerRemoteAdapter(private var context: Context, private var data: L holder.current.visibility = View.VISIBLE holder.current.fromRes( R.raw.select_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - holder.current.playAnimation() + holder.current.startAnimation() } else { holder.current.visibility = View.GONE - holder.current.clearAnimationDrawable() + holder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } holder.fileInfo.setBackgroundColor(messageBubbleColor) @@ -619,15 +620,16 @@ class FileManagerRemoteAdapter(private var context: Context, private var data: L holder.current.visibility = View.VISIBLE holder.current.fromRes( R.raw.select_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - holder.current.playAnimation() + holder.current.startAnimation() } else { holder.current.visibility = View.GONE - holder.current.clearAnimationDrawable() + holder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } holder.fileInfo.setBackgroundColor(messageBubbleColor) @@ -671,7 +673,7 @@ class FileManagerRemoteAdapter(private var context: Context, private var data: L val fileName: TextView = itemView.findViewById(R.id.item_file_name) val fileDetails: TextView = itemView.findViewById(R.id.item_file_details) val icon: ImageView = itemView.findViewById(R.id.item_file_icon) - val current: RLottieImageView = itemView.findViewById(R.id.current) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) val tagged: ImageView = itemView.findViewById(R.id.item_tagged) val fileInfo: LinearLayout = itemView.findViewById(R.id.item_file_info) } @@ -680,8 +682,8 @@ class FileManagerRemoteAdapter(private var context: Context, private var data: L val fileName: TextView = itemView.findViewById(R.id.item_file_name) val fileDetails: TextView = itemView.findViewById(R.id.item_file_details) val icon: ImageView = itemView.findViewById(R.id.item_file_icon) - val current: RLottieImageView = itemView.findViewById(R.id.current) - val visual: RLottieImageView = itemView.findViewById(R.id.item_audio_visual) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) + val visual: ThorVGLottieView = itemView.findViewById(R.id.item_audio_visual) val tagged: ImageView = itemView.findViewById(R.id.item_tagged) val fileInfo: LinearLayout = itemView.findViewById(R.id.item_file_info) } diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/filemanagerremote/FileManagerRemoteFragment.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/filemanagerremote/FileManagerRemoteFragment.kt index a9d3db7fa..391c34ea0 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/filemanagerremote/FileManagerRemoteFragment.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/filemanagerremote/FileManagerRemoteFragment.kt @@ -34,14 +34,13 @@ import dev.ragnarok.filegallery.model.Video import dev.ragnarok.filegallery.place.PlaceFactory import dev.ragnarok.filegallery.settings.CurrentTheme import dev.ragnarok.filegallery.settings.Settings -import dev.ragnarok.filegallery.util.Utils import dev.ragnarok.filegallery.util.ViewUtils import dev.ragnarok.filegallery.util.coroutines.CancelableJob import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.delayTaskFlow import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.toMain import dev.ragnarok.filegallery.util.toast.CustomToast import dev.ragnarok.filegallery.view.MySearchView -import dev.ragnarok.filegallery.view.natives.rlottie.RLottieImageView +import dev.ragnarok.filegallery.view.natives.animation.ThorVGLottieView class FileManagerRemoteFragment : BaseMvpFragment(), @@ -49,7 +48,7 @@ class FileManagerRemoteFragment : private var mRecyclerView: RecyclerView? = null private var mLayoutManager: GridLayoutManager? = null private var empty: TextView? = null - private var loading: RLottieImageView? = null + private var loading: ThorVGLottieView? = null private var tvCurrentDir: TextView? = null private var mAdapter: FileManagerRemoteAdapter? = null private var mSwipeRefreshLayout: SwipeRefreshLayout? = null @@ -117,13 +116,19 @@ class FileManagerRemoteFragment : animLoad = ObjectAnimator.ofFloat(loading, View.ALPHA, 0.0f).setDuration(1000) animLoad?.addListener(object : StubAnimatorListener() { override fun onAnimationEnd(animation: Animator) { - loading?.clearAnimationDrawable() + loading?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) loading?.visibility = View.GONE loading?.alpha = 1f } override fun onAnimationCancel(animation: Animator) { - loading?.clearAnimationDrawable() + loading?.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) loading?.visibility = View.GONE loading?.alpha = 1f } @@ -192,8 +197,6 @@ class FileManagerRemoteFragment : loading?.alpha = 1f loading?.fromRes( R.raw.s_loading, - Utils.dp(180f), - Utils.dp(180f), intArrayOf( 0x333333, CurrentTheme.getColorPrimary(requireActivity()), @@ -201,7 +204,7 @@ class FileManagerRemoteFragment : CurrentTheme.getColorSecondary(requireActivity()) ) ) - loading?.playAnimation() + loading?.startAnimation() }) } } diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/photoslocalserver/LocalServerPhotosAdapter.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/photoslocalserver/LocalServerPhotosAdapter.kt index 2eb073b51..68eb4b46d 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/photoslocalserver/LocalServerPhotosAdapter.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/localserver/photoslocalserver/LocalServerPhotosAdapter.kt @@ -17,7 +17,7 @@ import dev.ragnarok.filegallery.settings.CurrentTheme.getColorSecondary import dev.ragnarok.filegallery.util.AppTextUtils import dev.ragnarok.filegallery.util.Utils import dev.ragnarok.filegallery.view.AspectRatioImageView -import dev.ragnarok.filegallery.view.natives.rlottie.RLottieImageView +import dev.ragnarok.filegallery.view.natives.animation.ThorVGLottieView class LocalServerPhotosAdapter(private val mContext: Context, private var data: List) : RecyclerView.Adapter() { @@ -47,15 +47,16 @@ class LocalServerPhotosAdapter(private val mContext: Context, private var data: viewHolder.current.visibility = View.VISIBLE viewHolder.current.fromRes( R.raw.select_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - viewHolder.current.playAnimation() + viewHolder.current.startAnimation() } else { viewHolder.current.visibility = View.GONE - viewHolder.current.clearAnimationDrawable() + viewHolder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } val targetUrl = photo.preview_url @@ -98,7 +99,7 @@ class LocalServerPhotosAdapter(private val mContext: Context, private var data: val photoImageView: AspectRatioImageView = itemView.findViewById(R.id.imageView) val tvDate: TextView = itemView.findViewById(R.id.vk_photo_item_date) val bottomTop: ViewGroup = itemView.findViewById(R.id.vk_photo_item_top) - val current: RLottieImageView = itemView.findViewById(R.id.current) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) } } \ No newline at end of file diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/tagdir/TagDirAdapter.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/tagdir/TagDirAdapter.kt index 1b03aec8a..467195fb1 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/tagdir/TagDirAdapter.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/tagdir/TagDirAdapter.kt @@ -26,7 +26,7 @@ import dev.ragnarok.filegallery.toColor import dev.ragnarok.filegallery.util.Utils import dev.ragnarok.filegallery.util.coroutines.CancelableJob import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.sharedFlowToMain -import dev.ragnarok.filegallery.view.natives.rlottie.RLottieImageView +import dev.ragnarok.filegallery.view.natives.animation.ThorVGLottieView class TagDirAdapter(context: Context, private var data: List) : RecyclerView.Adapter() { @@ -127,15 +127,16 @@ class TagDirAdapter(context: Context, private var data: List) : holder.current.visibility = View.VISIBLE holder.current.fromRes( R.raw.select_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - holder.current.playAnimation() + holder.current.startAnimation() } else { holder.current.visibility = View.GONE - holder.current.clearAnimationDrawable() + holder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } @@ -203,15 +204,16 @@ class TagDirAdapter(context: Context, private var data: List) : holder.current.visibility = View.VISIBLE holder.current.fromRes( R.raw.select_fire, - Utils.dp(100f), - Utils.dp(100f), intArrayOf(0xFF812E, colorPrimary), true ) - holder.current.playAnimation() + holder.current.startAnimation() } else { holder.current.visibility = View.GONE - holder.current.clearAnimationDrawable() + holder.current.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } @@ -265,7 +267,7 @@ class TagDirAdapter(context: Context, private var data: List) : val fileName: TextView = itemView.findViewById(R.id.item_file_name) val fileDetails: TextView = itemView.findViewById(R.id.item_file_details) val icon: ImageView = itemView.findViewById(R.id.item_file_icon) - val current: RLottieImageView = itemView.findViewById(R.id.current) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) init { itemView.setOnCreateContextMenuListener(this) @@ -293,8 +295,8 @@ class TagDirAdapter(context: Context, private var data: List) : val fileName: TextView = itemView.findViewById(R.id.item_file_name) val fileDetails: TextView = itemView.findViewById(R.id.item_file_details) val icon: ImageView = itemView.findViewById(R.id.item_file_icon) - val current: RLottieImageView = itemView.findViewById(R.id.current) - val visual: RLottieImageView = itemView.findViewById(R.id.item_audio_visual) + val current: ThorVGLottieView = itemView.findViewById(R.id.current) + val visual: ThorVGLottieView = itemView.findViewById(R.id.item_audio_visual) init { itemView.setOnCreateContextMenuListener(this) diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/theme/ThemeAdapter.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/theme/ThemeAdapter.kt index 4ebdd989b..8cfdcbd50 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/theme/ThemeAdapter.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/fragment/theme/ThemeAdapter.kt @@ -16,8 +16,7 @@ import dev.ragnarok.filegallery.settings.CurrentTheme.getColorSecondary import dev.ragnarok.filegallery.settings.CurrentTheme.getColorWhiteContrastFix import dev.ragnarok.filegallery.settings.Settings.get import dev.ragnarok.filegallery.settings.theme.ThemeValue -import dev.ragnarok.filegallery.util.Utils -import dev.ragnarok.filegallery.view.natives.rlottie.RLottieImageView +import dev.ragnarok.filegallery.view.natives.animation.ThorVGLottieView class ThemeAdapter(private var data: List, context: Context) : RecyclerView.Adapter() { @@ -75,8 +74,6 @@ class ThemeAdapter(private var data: List, context: Context) : if (isSelected) { holder.selected.fromRes( R.raw.theme_selected, - Utils.dp(120f), - Utils.dp(120f), intArrayOf( 0x333333, getColorWhiteContrastFix(holder.selected.context), @@ -86,9 +83,12 @@ class ThemeAdapter(private var data: List, context: Context) : getColorSecondary(holder.selected.context) ) ) - holder.selected.playAnimation() + holder.selected.startAnimation() } else { - holder.selected.clearAnimationDrawable() + holder.selected.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } else { if (isSelected) { @@ -113,8 +113,6 @@ class ThemeAdapter(private var data: List, context: Context) : if (isSelected) { holder.selected.fromRes( R.raw.theme_selected, - Utils.dp(120f), - Utils.dp(120f), intArrayOf( 0x333333, getColorWhiteContrastFix(holder.selected.context), @@ -124,9 +122,12 @@ class ThemeAdapter(private var data: List, context: Context) : getColorSecondary(holder.selected.context) ) ) - holder.selected.playAnimation() + holder.selected.startAnimation() } else { - holder.selected.clearAnimationDrawable() + holder.selected.clearAnimationDrawable( + callSuper = true, clearState = true, + cancelTask = true + ) } } else { if (isSelected) { @@ -169,7 +170,7 @@ class ThemeAdapter(private var data: List, context: Context) : internal class ThemeHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val primary: ImageView = itemView.findViewById(R.id.theme_icon_primary) val secondary: ImageView = itemView.findViewById(R.id.theme_icon_secondary) - val selected: RLottieImageView = itemView.findViewById(R.id.selected) + val selected: ThorVGLottieView = itemView.findViewById(R.id.selected) val gradient: ImageView = itemView.findViewById(R.id.theme_icon_gradient) val clicked: ViewGroup = itemView.findViewById(R.id.theme_type) val title: TextView = itemView.findViewById(R.id.item_title) @@ -177,7 +178,7 @@ class ThemeAdapter(private var data: List, context: Context) : } internal class SpecialThemeHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - val selected: RLottieImageView = itemView.findViewById(R.id.selected) + val selected: ThorVGLottieView = itemView.findViewById(R.id.selected) val clicked: ViewGroup = itemView.findViewById(R.id.theme_type) val title: TextView = itemView.findViewById(R.id.item_title) val special_title: TextView = itemView.findViewById(R.id.special_text) diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/Utils.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/Utils.kt index 198bd4592..2fe1217b3 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/Utils.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/util/Utils.kt @@ -17,7 +17,6 @@ import androidx.core.graphics.ColorUtils import androidx.media3.common.MediaItem import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.floatingactionbutton.FloatingActionButton -import dev.ragnarok.fenrir.module.rlottie.RLottieDrawable import dev.ragnarok.filegallery.BuildConfig import dev.ragnarok.filegallery.Constants import dev.ragnarok.filegallery.R @@ -25,7 +24,7 @@ import dev.ragnarok.filegallery.media.exo.OkHttpDataSource import dev.ragnarok.filegallery.model.Lang import dev.ragnarok.filegallery.settings.Settings.get import dev.ragnarok.filegallery.util.AppTextUtils.updateDateLang -import dev.ragnarok.filegallery.view.natives.rlottie.RLottieImageView +import dev.ragnarok.filegallery.view.natives.animation.ThorVGLottieView import dev.ragnarok.filegallery.view.pager.* import okhttp3.Interceptor import okhttp3.OkHttpClient @@ -209,28 +208,26 @@ object Utils { } } - fun doWavesLottie(visual: RLottieImageView, Play: Boolean) { - visual.clearAnimationDrawable() + fun doWavesLottie(visual: ThorVGLottieView?, Play: Boolean) { if (Play) { - visual.setAutoRepeat(true) - visual.fromRes(R.raw.waves, dp(28f), dp(28f)) + visual?.setRepeat(true) + visual?.fromRes(R.raw.waves) } else { - visual.setAutoRepeat(false) - visual.fromRes(R.raw.waves_end, dp(28f), dp(28f)) + visual?.setRepeat(false) + visual?.fromRes(R.raw.waves_end) } - visual.playAnimation() + visual?.startAnimation() } - fun doWavesLottieBig(visual: RLottieImageView, Play: Boolean) { - visual.clearAnimationDrawable() + fun doWavesLottieBig(visual: ThorVGLottieView?, Play: Boolean) { if (Play) { - visual.setAutoRepeat(true) - visual.fromRes(R.raw.s_waves, dp(128f), dp(128f)) + visual?.setRepeat(true) + visual?.fromRes(R.raw.s_waves) } else { - visual.setAutoRepeat(false) - visual.fromRes(R.raw.s_waves_end, dp(128f), dp(128f)) + visual?.setRepeat(false) + visual?.fromRes(R.raw.s_waves_end) } - visual.playAnimation() + visual?.startAnimation() } fun isColorDark(color: Int): Boolean { @@ -263,7 +260,6 @@ object Utils { } } if (display != null) { - RLottieDrawable.updateScreenRefreshRate(display.refreshRate.toInt()) val configuration = context.resources.configuration if (configuration.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) { val newSize = ceil((configuration.screenWidthDp * density).toDouble()) diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/TouchImageView.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/TouchImageView.kt index fcee6f785..c84f4645d 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/TouchImageView.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/TouchImageView.kt @@ -27,22 +27,52 @@ import androidx.appcompat.widget.AppCompatImageView import com.squareup.picasso3.Rotatable import dev.ragnarok.fenrir.module.FenrirNative import dev.ragnarok.fenrir.module.animation.AnimatedFileDrawable +import dev.ragnarok.fenrir.module.animation.AnimatedFileDrawable.LoadedFrom +import dev.ragnarok.filegallery.Constants import dev.ragnarok.filegallery.R import dev.ragnarok.filegallery.getParcelableCompat import dev.ragnarok.filegallery.getSerializableCompat +import dev.ragnarok.filegallery.nonNullNoEmpty +import dev.ragnarok.filegallery.picasso.PicassoInstance +import dev.ragnarok.filegallery.util.Utils +import dev.ragnarok.filegallery.util.coroutines.CancelableJob +import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.fromIOToMain +import dev.ragnarok.filegallery.view.natives.animation.AnimationNetworkCache +import kotlinx.coroutines.flow.flow +import okhttp3.Call +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response import java.io.File +import java.lang.ref.WeakReference +import kotlin.coroutines.cancellation.CancellationException import kotlin.math.abs import kotlin.math.max import kotlin.math.min @SuppressLint("ClickableViewAccessibility") @Suppress("unused") -open class TouchImageView @JvmOverloads constructor( +class TouchImageView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : AppCompatImageView(context, attrs, defStyle) { + //%%~Animation + private var mDisposable = CancelableJob() + private val cache: AnimationNetworkCache = AnimationNetworkCache(context) + private var animatedDrawable: AnimatedFileDrawable? = null + private var attachedToWindow = false + //~%%Animation + + @LoadedFrom + private var loadedFrom = LoadedFrom.NO + private var filePathTmp: String? = null + private var keyTmp: String? = null + private var isPlaying: Boolean? = null + private var tmpFade: Boolean? = null + private var fallbackTmp: String? = null + /** * Get the current zoom. This is the zoom relative to the initial * scale, not the original resource. @@ -124,7 +154,7 @@ open class TouchImageView @JvmOverloads constructor( var orientationLocked = OrientationLocked.HORIZONTAL init { - super.setClickable(true) + super.isClickable = true orientation = resources.configuration.orientation scaleDetector = ScaleGestureDetector(context, ScaleListener()) gestureDetector = GestureDetector(context, GestureListener()) @@ -156,118 +186,276 @@ open class TouchImageView @JvmOverloads constructor( } } - fun setRotateImageToFitScreen(rotateImageToFitScreen: Boolean) { - isRotateImageToFitScreen = rotateImageToFitScreen - } - - interface StateListener { - fun onChangeState(imageActionState: ImageActionState, zoomed: Boolean) - } - - fun setOnStateChangeListener(onStateListener: StateListener) { - stateListener = onStateListener - } - - override fun setOnTouchListener(onTouchListener: OnTouchListener?) { - userTouchListener = onTouchListener - } - - fun setOnTouchImageViewListener(onTouchImageViewListener: OnTouchImageViewListener) { - touchImageViewListener = onTouchImageViewListener + //%%~Animation + private fun setAnimationByUrlCache( + key: String, + fallback: String?, + autoPlay: Boolean, + fade: Boolean + ) { + if (!FenrirNative.isNativeLoaded) { + return + } + val ch = cache.fetch(key) + if (ch == null) { + return + } + if (filePathTmp == ch.absolutePath && fallbackTmp == fallback && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + loadedFrom = LoadedFrom.FILE + filePathTmp = ch.absolutePath + tmpFade = fade + isPlaying = autoPlay + fallbackTmp = fallback + + if (attachedToWindow) { + createAnimationDrawable() + } } - fun setOnDoubleTapListener(onDoubleTapListener: OnDoubleTapListener) { - doubleTapListener = onDoubleTapListener + fun fromAnimationNet( + key: String, + url: String?, + fallback: String?, + client: OkHttpClient.Builder, + autoPlay: Boolean + ) { + if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { + if (loadedFrom == LoadedFrom.NET) { + loadedFrom = LoadedFrom.NO + } + return + } + if (filePathTmp == url && keyTmp == key && fallbackTmp == fallback && loadedFrom == LoadedFrom.NET) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.NET + filePathTmp = url + keyTmp = key + isPlaying = autoPlay + tmpFade = true + fallbackTmp = fallback + + if (cache.isCachedFile(key)) { + setAnimationByUrlCache(key, fallback, autoPlay, true) + return + } + mDisposable.set(flow { + var call: Call? = null + try { + val request: Request = Request.Builder() + .url(url) + .build() + call = client.build().newCall(request) + val response: Response = call.execute() + if (!response.isSuccessful) { + emit(false) + return@flow + } + cache.writeTempCacheFile(key, response.body.source()) + response.close() + cache.renameTempFile(key) + emit(true) + } catch (e: CancellationException) { + call?.cancel() + throw e + } + }.fromIOToMain { u -> + if (u) { + setAnimationByUrlCache(key, fallback, autoPlay, true) + } + }) } - fun setOnTouchCoordinatesListener(onTouchCoordinatesListener: OnTouchCoordinatesListener) { - touchCoordinatesListener = onTouchCoordinatesListener + fun clearAnimationDrawable(callSuper: Boolean, clearState: Boolean, cancelTask: Boolean) { + if (cancelTask) { + mDisposable.cancel() + } + if (animatedDrawable != null) { + animatedDrawable?.callback = null + animatedDrawable?.recycle() + animatedDrawable = null + } + if (callSuper) { + super.setImageDrawable(null) + } + if (clearState) { + isPlaying = false + loadedFrom = LoadedFrom.NO + filePathTmp = null + tmpFade = null + fallbackTmp = null + } } - open fun fromAnimFile(file: File) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - AnimatedFileDrawable( - file, + private fun createAnimationDrawable() { + if (FenrirNative.isNativeLoaded && attachedToWindow && loadedFrom != LoadedFrom.NO && animatedDrawable == null && filePathTmp != null) { + val ref = WeakReference(this) + animatedDrawable = AnimatedFileDrawable( + filePathTmp ?: "", 0, 100, 100, - true, + tmpFade == true, object : AnimatedFileDrawable.DecoderListener { override fun onError() { - + ref.get()?.let { + fallbackTmp.nonNullNoEmpty { k -> + PicassoInstance.with().load(k).into(it) + } + } } + }) - ) - } + if (animatedDrawable?.isDecoded != true) { + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + return + } + tmpFade = false + animatedDrawable?.setAllowDecodeSingleFrame(true) - open fun clearAnimationDrawable() { - if (animDrawable != null) { - animDrawable?.stop() - animDrawable?.callback = null - animDrawable?.recycle() - animDrawable = null + imageRenderedAtLeastOnce = false + super.setImageDrawable(animatedDrawable) + savePreviousImageValues() + fitImageToView() + + if (isPlaying == true) { + playAnimation() + } } } - private fun setAnimation(videoDrawable: AnimatedFileDrawable) { - if (!videoDrawable.isDecoded) return - animDrawable = videoDrawable - animDrawable?.setAllowDecodeSingleFrame(true) - animDrawable?.callback = this - animDrawable?.start() - setImageDrawable(animDrawable) + fun fromAnimationFile(file: File, autoPlay: Boolean) { + if (!FenrirNative.isNativeLoaded || !file.exists()) { + return + } + if (filePathTmp == file.absolutePath && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.FILE + filePathTmp = file.absolutePath + tmpFade = false + isPlaying = autoPlay + if (attachedToWindow) { + createAnimationDrawable() + } } override fun onAttachedToWindow() { + attachedToWindow = true super.onAttachedToWindow() - animDrawable?.callback = this - animDrawable?.start() + when { + loadedFrom == LoadedFrom.NET -> { + filePathTmp?.let { + keyTmp?.let { s -> + fromAnimationNet( + s, + it, + fallbackTmp, + Utils.createOkHttp(Constants.PICASSO_TIMEOUT), + isPlaying == true, + ) + } + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } + + loadedFrom != LoadedFrom.NO -> { + createAnimationDrawable() + } + } } override fun onDetachedFromWindow() { + attachedToWindow = false super.onDetachedFromWindow() - animDrawable?.stop() - animDrawable?.callback = null + if (loadedFrom != LoadedFrom.NO) { + clearAnimationDrawable(callSuper = true, clearState = false, cancelTask = true) + } } - open fun isPlaying(): Boolean { + fun isPlayingAnimation(): Boolean { return animDrawable?.isRunning == true } + fun playAnimation() { + animatedDrawable?.start() + isPlaying = true + } + + fun resetFrame() { + animatedDrawable?.seekTo(0, true) + } + + fun stopAnimation() { + animatedDrawable?.let { + it.stop() + isPlaying = false + } + } + //~Animation + + fun setRotateImageToFitScreen(rotateImageToFitScreen: Boolean) { + isRotateImageToFitScreen = rotateImageToFitScreen + } + + interface StateListener { + fun onChangeState(imageActionState: ImageActionState, zoomed: Boolean) + } + + fun setOnStateChangeListener(onStateListener: StateListener) { + stateListener = onStateListener + } + + override fun setOnTouchListener(onTouchListener: OnTouchListener?) { + userTouchListener = onTouchListener + } + + fun setOnTouchImageViewListener(onTouchImageViewListener: OnTouchImageViewListener) { + touchImageViewListener = onTouchImageViewListener + } + + fun setOnDoubleTapListener(onDoubleTapListener: OnDoubleTapListener) { + doubleTapListener = onDoubleTapListener + } + + fun setOnTouchCoordinatesListener(onTouchCoordinatesListener: OnTouchCoordinatesListener) { + touchCoordinatesListener = onTouchCoordinatesListener + } + override fun setImageResource(resId: Int) { - clearAnimationDrawable() imageRenderedAtLeastOnce = false super.setImageResource(resId) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) savePreviousImageValues() fitImageToView() } override fun setImageBitmap(bm: Bitmap?) { - clearAnimationDrawable() imageRenderedAtLeastOnce = false super.setImageBitmap(bm) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) savePreviousImageValues() fitImageToView() } override fun setImageDrawable(drawable: Drawable?) { - if (drawable !is AnimatedFileDrawable) { - clearAnimationDrawable() - } imageRenderedAtLeastOnce = false super.setImageDrawable(drawable) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) savePreviousImageValues() fitImageToView() } override fun setImageURI(uri: Uri?) { - clearAnimationDrawable() imageRenderedAtLeastOnce = false super.setImageURI(uri) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) savePreviousImageValues() fitImageToView() } @@ -285,7 +473,7 @@ open class TouchImageView @JvmOverloads constructor( } } - override fun getScaleType() = touchScaleType!! + override fun getScaleType() = touchScaleType ?: ScaleType.MATRIX /** * Returns false if image is in initial, unzoomed state. False, otherwise. @@ -544,21 +732,22 @@ open class TouchImageView @JvmOverloads constructor( } internal fun orientationMismatch(drawable: Drawable?): Boolean { - return viewWidth > viewHeight != drawable!!.intrinsicWidth > drawable.intrinsicHeight + return viewWidth > viewHeight != (drawable?.intrinsicWidth + ?: 0) > (drawable?.intrinsicHeight ?: 0) } private fun getDrawableWidth(drawable: Drawable?): Int { return if (orientationMismatch(drawable) && isRotateImageToFitScreen) { - drawable!!.intrinsicHeight + drawable?.intrinsicHeight ?: 0 } else - drawable!!.intrinsicWidth + drawable?.intrinsicWidth ?: 0 } private fun getDrawableHeight(drawable: Drawable?): Int { return if (orientationMismatch(drawable) && isRotateImageToFitScreen) { - drawable!!.intrinsicWidth + drawable?.intrinsicWidth ?: 0 } else - drawable!!.intrinsicHeight + drawable?.intrinsicHeight ?: 0 } /** @@ -743,14 +932,11 @@ open class TouchImageView @JvmOverloads constructor( } ScaleType.CENTER_INSIDE -> { - run { - scaleY = min(1f, min(scaleX, scaleY)) - scaleX = scaleY - } - run { - scaleY = min(scaleX, scaleY) - scaleX = scaleY - } + scaleY = min(1f, min(scaleX, scaleY)) + scaleX = scaleY + + scaleY = min(scaleX, scaleY) + scaleX = scaleY } ScaleType.FIT_CENTER, ScaleType.FIT_START, ScaleType.FIT_END -> { @@ -1235,7 +1421,7 @@ open class TouchImageView @JvmOverloads constructor( * to the bounds of the bitmap size. * @return Coordinates of the point touched, in the coordinate system of the original drawable. */ - protected fun transformCoordTouchToBitmap(x: Float, y: Float, clipToBitmap: Boolean): PointF { + private fun transformCoordTouchToBitmap(x: Float, y: Float, clipToBitmap: Boolean): PointF { touchMatrix.getValues(floatMatrix) val origW = drawable.intrinsicWidth.toFloat() val origH = drawable.intrinsicHeight.toFloat() @@ -1258,7 +1444,7 @@ open class TouchImageView @JvmOverloads constructor( * @param by y-coordinate in original bitmap coordinate system * @return Coordinates of the point in the view's coordinate system. */ - protected fun transformCoordBitmapToTouch(bx: Float, by: Float): PointF { + private fun transformCoordBitmapToTouch(bx: Float, by: Float): PointF { touchMatrix.getValues(floatMatrix) val origW = drawable.intrinsicWidth.toFloat() val origH = drawable.intrinsicHeight.toFloat() diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/media/PathAnimator.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/media/PathAnimator.kt index ab7df1610..1153ed552 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/media/PathAnimator.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/media/PathAnimator.kt @@ -63,23 +63,19 @@ class PathAnimator( fun draw(canvas: Canvas, paint: Paint?, time: Float) { var startKeyFrame: KeyFrame? = null var endKeyFrame: KeyFrame? = null - run { - var a = 0 - val N = keyFrames.size - while (a < N) { - val keyFrame = keyFrames[a] - if ((startKeyFrame == null || (startKeyFrame - ?: return@run).time < keyFrame.time) && keyFrame.time <= time - ) { - startKeyFrame = keyFrame - } - if ((endKeyFrame == null || (endKeyFrame - ?: return@run).time > keyFrame.time) && keyFrame.time >= time - ) { - endKeyFrame = keyFrame - } - a++ + var a = 0 + var N = keyFrames.size + while (a < N) { + val keyFrame = keyFrames[a] + if ((startKeyFrame == null || startKeyFrame.time < keyFrame.time) && keyFrame.time <= time + ) { + startKeyFrame = keyFrame + } + if ((endKeyFrame == null || endKeyFrame.time > keyFrame.time) && keyFrame.time >= time + ) { + endKeyFrame = keyFrame } + a++ } if (endKeyFrame === startKeyFrame) { startKeyFrame = null @@ -88,24 +84,22 @@ class PathAnimator( endKeyFrame = startKeyFrame startKeyFrame = null } - if (endKeyFrame == null || startKeyFrame != null && (startKeyFrame - ?: return).commands.size != (endKeyFrame ?: return).commands.size + if (endKeyFrame == null || startKeyFrame != null && startKeyFrame.commands.size != endKeyFrame.commands.size ) { return } path.reset() - var a = 0 - val N = (endKeyFrame ?: return).commands.size + a = 0 + N = endKeyFrame.commands.size while (a < N) { val startCommand = - if (startKeyFrame != null) (startKeyFrame ?: return).commands[a] else null - val endCommand = (endKeyFrame ?: return).commands[a] + if (startKeyFrame != null) startKeyFrame.commands[a] else null + val endCommand = endKeyFrame.commands[a] if (startCommand != null && startCommand.javaClass != endCommand.javaClass) { return } val progress: Float = if (startKeyFrame != null) { - (time - (startKeyFrame ?: return).time) / ((endKeyFrame - ?: return).time - (startKeyFrame ?: return).time) + (time - startKeyFrame.time) / (endKeyFrame.time - startKeyFrame.time) } else { 1.0f } diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/AnimatedShapeableImageView.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/AnimatedShapeableImageView.kt index ff43604fe..d3d3ae5d8 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/AnimatedShapeableImageView.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/AnimatedShapeableImageView.kt @@ -3,13 +3,17 @@ package dev.ragnarok.filegallery.view.natives.animation import android.content.Context import android.graphics.Bitmap import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Parcelable import android.util.AttributeSet import androidx.annotation.RawRes import com.google.android.material.imageview.ShapeableImageView import dev.ragnarok.fenrir.module.FenrirNative import dev.ragnarok.fenrir.module.animation.AnimatedFileDrawable +import dev.ragnarok.fenrir.module.animation.AnimatedFileDrawable.LoadedFrom import dev.ragnarok.filegallery.Constants import dev.ragnarok.filegallery.R +import dev.ragnarok.filegallery.util.Utils import dev.ragnarok.filegallery.util.coroutines.CancelableJob import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.fromIOToMain import dev.ragnarok.filegallery.view.natives.animation.AnimationNetworkCache.Companion.filenameForRes @@ -32,77 +36,91 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( private val defaultHeight: Int private var animatedDrawable: AnimatedFileDrawable? = null private var attachedToWindow = false - private var playing = false private var decoderCallback: OnDecoderInit? = null private var mDisposable = CancelableJob() + + @LoadedFrom + private var loadedFrom = LoadedFrom.NO + private var filePathTmp: String? = null + private var keyTmp: String? = null + + @RawRes + private var rawResTmp: Int? = null + private var isPlaying: Boolean? = null + private var tmpFade: Boolean? = null + fun setDecoderCallback(decoderCallback: OnDecoderInit?) { this.decoderCallback = decoderCallback } - private fun setAnimationByUrlCache(url: String, fade: Boolean) { + private fun setAnimationByUrlCache(key: String, autoPlay: Boolean, fade: Boolean) { if (!FenrirNative.isNativeLoaded) { decoderCallback?.onLoaded(false) return } - val ch = cache.fetch(url) + val ch = cache.fetch(key) if (ch == null) { - setImageDrawable(null) decoderCallback?.onLoaded(false) return } - setAnimation( - AnimatedFileDrawable( - ch, - 0, - defaultWidth, - defaultHeight, - fade, - object : AnimatedFileDrawable.DecoderListener { - override fun onError() { - decoderCallback?.onLoaded(false) - } + if (filePathTmp == ch.absolutePath && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + loadedFrom = LoadedFrom.FILE + filePathTmp = ch.absolutePath + tmpFade = fade + isPlaying = autoPlay - }) - ) - playAnimation() + if (attachedToWindow) { + createAnimationDrawable() + } } - private fun setAnimationByResCache(@RawRes res: Int, fade: Boolean) { + private fun setAnimationByResCache(@RawRes res: Int, autoPlay: Boolean, fade: Boolean) { if (!FenrirNative.isNativeLoaded) { decoderCallback?.onLoaded(false) return } val ch = cache.fetch(res) if (ch == null) { - setImageDrawable(null) decoderCallback?.onLoaded(false) return } - setAnimation( - AnimatedFileDrawable( - ch, - 0, - defaultWidth, - defaultHeight, - fade, - object : AnimatedFileDrawable.DecoderListener { - override fun onError() { - decoderCallback?.onLoaded(false) - } + if (filePathTmp == ch.absolutePath && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + loadedFrom = LoadedFrom.FILE + filePathTmp = ch.absolutePath + tmpFade = fade + isPlaying = autoPlay - }) - ) - playAnimation() + if (attachedToWindow) { + createAnimationDrawable() + } } - fun fromNet(key: String, url: String?, client: OkHttpClient.Builder) { + fun fromNet(key: String, url: String?, client: OkHttpClient.Builder, autoPlay: Boolean) { if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { + if (loadedFrom == LoadedFrom.NET) { + loadedFrom = LoadedFrom.NO + } decoderCallback?.onLoaded(false) return } - clearAnimationDrawable() + if (filePathTmp == url && keyTmp == key && loadedFrom == LoadedFrom.NET) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.NET + filePathTmp = url + keyTmp = key + isPlaying = autoPlay + tmpFade = true + if (cache.isCachedFile(key)) { - setAnimationByUrlCache(key, true) + setAnimationByUrlCache(key, autoPlay, true) return } mDisposable.set(flow { @@ -127,7 +145,7 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( } }.fromIOToMain({ u -> if (u) { - setAnimationByUrlCache(key, true) + setAnimationByUrlCache(key, autoPlay, true) } else { decoderCallback?.onLoaded(false) } @@ -136,23 +154,34 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( })) } - fun fromRes(@RawRes res: Int) { - if (!FenrirNative.isNativeLoaded) { + fun fromRes(@RawRes resId: Int, autoPlay: Boolean) { + if (!FenrirNative.isNativeLoaded || resId == -1) { + if (loadedFrom == LoadedFrom.RES) { + loadedFrom = LoadedFrom.NO + } decoderCallback?.onLoaded(false) return } - clearAnimationDrawable() - if (cache.isCachedRes(res)) { - setAnimationByResCache(res, true) + if (rawResTmp == resId && loadedFrom == LoadedFrom.RES) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.RES + rawResTmp = resId + tmpFade = true + isPlaying = autoPlay + + if (cache.isCachedRes(resId)) { + setAnimationByResCache(resId, autoPlay, true) return } mDisposable.set(flow { try { - if (!copyRes(res)) { + if (!copyRes(resId)) { emit(false) return@flow } - cache.renameTempFile(res) + cache.renameTempFile(resId) } catch (_: Exception) { emit(false) return@flow @@ -160,69 +189,121 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( emit(true) }.fromIOToMain { if (it) { - setAnimationByResCache(res, true) + setAnimationByResCache(resId, autoPlay, true) } else { decoderCallback?.onLoaded(false) } }) } - private fun setAnimation(videoDrawable: AnimatedFileDrawable) { - decoderCallback?.onLoaded(videoDrawable.isDecoded) - if (!videoDrawable.isDecoded) return - animatedDrawable = videoDrawable - animatedDrawable?.setAllowDecodeSingleFrame(true) - setImageDrawable(animatedDrawable) - } - - fun fromFile(file: File) { - if (!FenrirNative.isNativeLoaded) { - decoderCallback?.onLoaded(false) - return - } - clearAnimationDrawable() - setAnimation( - AnimatedFileDrawable( - file, + private fun createAnimationDrawable() { + if (FenrirNative.isNativeLoaded && attachedToWindow && loadedFrom != LoadedFrom.NO && animatedDrawable == null && filePathTmp != null) { + animatedDrawable = AnimatedFileDrawable( + filePathTmp ?: "", 0, defaultWidth, defaultHeight, - false, + tmpFade == true, object : AnimatedFileDrawable.DecoderListener { override fun onError() { decoderCallback?.onLoaded(false) } }) - ) + decoderCallback?.onLoaded(animatedDrawable?.isDecoded == true) + if (animatedDrawable?.isDecoded != true) { + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + return + } + tmpFade = false + animatedDrawable?.setAllowDecodeSingleFrame(true) + super.setImageDrawable(animatedDrawable) + if (isPlaying == true) { + playAnimation() + } + } } - fun clearAnimationDrawable() { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() + fun fromFile(file: File) { + if (!FenrirNative.isNativeLoaded || !file.exists()) { + decoderCallback?.onLoaded(false) + return + } + if (filePathTmp == file.absolutePath && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + loadedFrom = LoadedFrom.FILE + filePathTmp = file.absolutePath + tmpFade = false + if (attachedToWindow) { + createAnimationDrawable() + } + } + + fun clearAnimationDrawable(callSuper: Boolean, clearState: Boolean, cancelTask: Boolean) { + if (cancelTask) { + mDisposable.cancel() + } + if (animatedDrawable != null) { + animatedDrawable?.callback = null + animatedDrawable?.recycle() animatedDrawable = null } - setImageDrawable(null) + if (callSuper) { + super.setImageDrawable(null) + } + if (clearState) { + isPlaying = false + loadedFrom = LoadedFrom.NO + filePathTmp = null + rawResTmp = null + tmpFade = null + } } override fun onAttachedToWindow() { - super.onAttachedToWindow() attachedToWindow = true - animatedDrawable?.callback = this - if (playing) { - animatedDrawable?.start() + super.onAttachedToWindow() + when { + loadedFrom == LoadedFrom.NET -> { + filePathTmp?.let { + keyTmp?.let { s -> + fromNet( + s, + it, + Utils.createOkHttp(Constants.PICASSO_TIMEOUT), + isPlaying == true, + ) + } + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } + + loadedFrom == LoadedFrom.RES -> { + rawResTmp?.let { + fromRes( + it, + isPlaying == true, + ) + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } + + loadedFrom != LoadedFrom.NO -> { + createAnimationDrawable() + } } } override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - mDisposable.cancel() attachedToWindow = false - animatedDrawable?.stop() - animatedDrawable?.callback = null + super.onDetachedFromWindow() + if (loadedFrom != LoadedFrom.NO) { + clearAnimationDrawable(callSuper = true, clearState = false, cancelTask = true) + } } fun isPlaying(): Boolean { @@ -231,69 +312,48 @@ open class AnimatedShapeableImageView @JvmOverloads constructor( override fun setImageDrawable(dr: Drawable?) { super.setImageDrawable(dr) - if (dr !is AnimatedFileDrawable) { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) } override fun setImageBitmap(bm: Bitmap?) { super.setImageBitmap(bm) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) } override fun setImageResource(resId: Int) { super.setImageResource(resId) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageURI(uri: Uri?) { + super.setImageURI(uri) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) } fun playAnimation() { - if (animatedDrawable == null) { - return - } - playing = true - if (attachedToWindow) { - animatedDrawable?.start() - } + animatedDrawable?.start() + isPlaying = true } fun resetFrame() { - if (animatedDrawable == null) { - return - } - playing = true - if (attachedToWindow) { - animatedDrawable?.seekTo(0, true) - } + animatedDrawable?.seekTo(0, true) } fun stopAnimation() { - if (animatedDrawable == null) { - return - } - playing = false - if (attachedToWindow) { - animatedDrawable?.stop() + animatedDrawable?.let { + it.stop() + isPlaying = false } } + protected override fun onSaveInstanceState(): Parcelable? { + return super.onSaveInstanceState() + } + + protected override fun onRestoreInstanceState(state: Parcelable?) { + super.onRestoreInstanceState(state) + } + private fun copyRes(@RawRes rawRes: Int): Boolean { try { context.resources.openRawResource(rawRes).use { inputStream -> diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieNetworkCache.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieNetworkCache.kt similarity index 86% rename from app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieNetworkCache.kt rename to app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieNetworkCache.kt index a8d89eaa3..95073bc2c 100644 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieNetworkCache.kt +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieNetworkCache.kt @@ -1,4 +1,4 @@ -package dev.ragnarok.filegallery.view.natives.rlottie +package dev.ragnarok.filegallery.view.natives.animation import android.content.Context import android.util.Log @@ -8,13 +8,13 @@ import okio.buffer import okio.sink import java.io.File -class RLottieNetworkCache(context: Context) { +class ThorVGLottieNetworkCache(context: Context) { private val appContext = context.applicationContext fun fetch(url: String): File? { val cachedFile = getCachedFile(url) ?: return null if (Constants.IS_DEBUG) { - Log.d("RLottieNetworkCache", "Cache hit for $url at ${cachedFile.absolutePath}") + Log.d("ThorVGLottieNetworkCache", "Cache hit for $url at ${cachedFile.absolutePath}") } return cachedFile } @@ -40,12 +40,12 @@ class RLottieNetworkCache(context: Context) { val newFile = File(newFileName) val renamed = file.renameTo(newFile) if (Constants.IS_DEBUG) { - Log.d("RLottieNetworkCache", "Copying temp file to real file ($newFile)") + Log.d("ThorVGLottieNetworkCache", "Copying temp file to real file ($newFile)") } if (!renamed) { if (Constants.IS_DEBUG) { Log.w( - "RLottieNetworkCache", + "ThorVGLottieNetworkCache", "Unable to rename cache file ${file.absolutePath} to ${newFile.absolutePath}." ) } diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieShapeableView.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieShapeableView.kt new file mode 100644 index 000000000..47b2f3375 --- /dev/null +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieShapeableView.kt @@ -0,0 +1,366 @@ +package dev.ragnarok.filegallery.view.natives.animation + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Parcelable +import android.util.AttributeSet +import androidx.annotation.RawRes +import com.google.android.material.imageview.ShapeableImageView +import dev.ragnarok.fenrir.module.FenrirNative +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.Companion.RESTART +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LoadedFrom +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LottieAnimationListener +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.RepeatMode +import dev.ragnarok.filegallery.Constants +import dev.ragnarok.filegallery.R +import dev.ragnarok.filegallery.util.Utils +import dev.ragnarok.filegallery.util.coroutines.CancelableJob +import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.fromIOToMain +import kotlinx.coroutines.flow.flow +import okhttp3.Call +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import java.io.File +import kotlin.coroutines.cancellation.CancellationException + +@SuppressLint("CustomViewStyleable") +class ThorVGLottieShapeableView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null +) : + ShapeableImageView(context, attrs) { + private val cache: ThorVGLottieNetworkCache = ThorVGLottieNetworkCache(context) + private var mDisposable = CancelableJob() + private var animatedDrawable: ThorVGLottieDrawable? = null + private var mListener: LottieAnimationListener? = null + + @LoadedFrom + private var loadedFrom = LoadedFrom.NO + private var colorReplacementTmp: IntArray? = null + private var useMoveColorTmp: Boolean = false + private var deleteInvalidFileTmp: Boolean = false + private var filePathTmp: String? = null + + @RawRes + private var rawResTmp: Int? = null + private var isPlaying: Boolean? = null + private var repeatTmp: Boolean? = null + + @RepeatMode + private var repeatModeTmp: Int? = null + + private var mOnAttached = false + + private fun createLottieDrawable() { + if (FenrirNative.isNativeLoaded && mOnAttached && loadedFrom != LoadedFrom.NO && animatedDrawable == null) { + when (loadedFrom) { + LoadedFrom.RES -> { + animatedDrawable = rawResTmp?.let { + ThorVGLottieDrawable( + it, + colorReplacementTmp, + useMoveColorTmp + ) + } + } + + LoadedFrom.FILE -> { + animatedDrawable = filePathTmp?.let { + ThorVGLottieDrawable( + it, + deleteInvalidFileTmp, + colorReplacementTmp, + useMoveColorTmp + ) + } + } + } + if (animatedDrawable == null) { + return + } + repeatModeTmp?.let { animatedDrawable?.setRepeatMode(it) } + repeatTmp?.let { animatedDrawable?.setRepeatCount(if (it) Int.MAX_VALUE else 1) } + super.setImageDrawable(animatedDrawable) + animatedDrawable?.callback = this + animatedDrawable?.setAnimationListener(mListener) + if (isPlaying == true) { + startAnimation() + } + } + } + + private fun setAnimationByUrlCache( + url: String, + autoPlay: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded) { + return + } + val ch = cache.fetch(url) + if (ch == null) { + return + } + if (filePathTmp == ch.absolutePath && deleteInvalidFileTmp == true && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + deleteInvalidFileTmp = true + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.FILE + isPlaying = autoPlay + filePathTmp = ch.absolutePath + if (mOnAttached) { + createLottieDrawable() + } + } + + fun fromNet( + url: String?, + client: OkHttpClient.Builder, + autoPlay: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { + if (loadedFrom == LoadedFrom.NET) { + loadedFrom = LoadedFrom.NO + } + return + } + if (filePathTmp == url && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.NET) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.NET + filePathTmp = url + isPlaying = autoPlay + + if (cache.isCachedFile(url)) { + setAnimationByUrlCache(url, autoPlay, colorReplacement, useMoveColor) + return + } + mDisposable.set(flow { + var call: Call? = null + try { + val request: Request = Request.Builder() + .url(url) + .build() + call = client.build().newCall(request) + val response: Response = call.execute() + if (!response.isSuccessful) { + emit(false) + return@flow + } + cache.writeTempCacheFile(url, response.body.source()) + response.close() + cache.renameTempFile(url) + emit(true) + } catch (e: CancellationException) { + call?.cancel() + throw e + } + }.fromIOToMain { + if (it) { + setAnimationByUrlCache(url, autoPlay, colorReplacement, useMoveColor) + } + }) + } + + fun fromRes( + @RawRes resId: Int, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || resId == -1) { + return + } + if (rawResTmp == resId && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.RES) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.RES + rawResTmp = resId + if (mOnAttached) { + createLottieDrawable() + } + } + + fun fromFile( + file: File, + deleteInvalidFile: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || !file.exists()) { + return + } + if (filePathTmp == file.absolutePath && deleteInvalidFileTmp == deleteInvalidFile && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.FILE + filePathTmp = file.absolutePath + deleteInvalidFileTmp = deleteInvalidFile + if (mOnAttached) { + createLottieDrawable() + } + } + + fun isPlaying(): Boolean { + return animatedDrawable != null && animatedDrawable?.isRunning == true + } + + fun setRepeat(repeat: Boolean) { + animatedDrawable?.setRepeatCount(if (repeat) Int.MAX_VALUE else 1) + repeatTmp = repeat + } + + fun setRepeatMode(@RepeatMode repeatMode: Int) { + animatedDrawable?.setRepeatMode(repeatMode) + repeatModeTmp = repeatMode + } + + fun startAnimation() { + animatedDrawable?.start() + isPlaying = true + } + + fun stopAnimation() { + animatedDrawable?.let { + it.stop() + isPlaying = false + } + } + + fun pauseAnimation() { + animatedDrawable?.pause() + } + + fun resumeAnimation() { + animatedDrawable?.resume() + } + + fun clearAnimationDrawable(callSuper: Boolean, clearState: Boolean, cancelTask: Boolean) { + if (cancelTask) { + mDisposable.cancel() + } + if (animatedDrawable != null) { + animatedDrawable?.callback = null + animatedDrawable?.release() + animatedDrawable = null + } + if (callSuper) { + super.setImageDrawable(null) + } + if (clearState) { + isPlaying = false + loadedFrom = LoadedFrom.NO + filePathTmp = null + rawResTmp = null + deleteInvalidFileTmp = false + } + } + + override fun setImageDrawable(dr: Drawable?) { + super.setImageDrawable(dr) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageBitmap(bm: Bitmap?) { + super.setImageBitmap(bm) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageResource(resId: Int) { + super.setImageResource(resId) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageURI(uri: Uri?) { + super.setImageURI(uri) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + if (measuredWidth <= 0 || measuredHeight <= 0) { + return + } + animatedDrawable?.setSize(measuredWidth, measuredHeight) + } + + override fun onAttachedToWindow() { + mOnAttached = true + + super.onAttachedToWindow() + + if (loadedFrom == LoadedFrom.NET) { + filePathTmp?.let { + fromNet( + it, + Utils.createOkHttp(Constants.PICASSO_TIMEOUT), + isPlaying == true, + colorReplacementTmp, + useMoveColorTmp + ) + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } else if (loadedFrom != LoadedFrom.NO) { + createLottieDrawable() + } + } + + override fun onDetachedFromWindow() { + mOnAttached = false + super.onDetachedFromWindow() + if (loadedFrom != LoadedFrom.NO) { + clearAnimationDrawable(callSuper = true, clearState = false, cancelTask = true) + } + } + + fun setAnimationListener(listener: LottieAnimationListener?) { + mListener = listener + } + + protected override fun onSaveInstanceState(): Parcelable? { + return super.onSaveInstanceState() + } + + protected override fun onRestoreInstanceState(state: Parcelable?) { + super.onRestoreInstanceState(state) + } + + init { + @SuppressLint("CustomViewStyleable") val a = + context.obtainStyledAttributes(attrs, R.styleable.ThorVGLottieView) + rawResTmp = a.getResourceId(R.styleable.ThorVGLottieView_fromRes, 0) + if (rawResTmp == 0) { + rawResTmp = null + } + repeatTmp = a.getBoolean(R.styleable.ThorVGLottieView_loopAnimation, false) + repeatModeTmp = a.getInt(R.styleable.ThorVGLottieView_loopMode, RESTART) + a.recycle() + if (FenrirNative.isNativeLoaded && rawResTmp != null) { + loadedFrom = LoadedFrom.RES + isPlaying = true + } else { + rawResTmp = null + } + } +} diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieView.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieView.kt new file mode 100644 index 000000000..a655f4e0e --- /dev/null +++ b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/animation/ThorVGLottieView.kt @@ -0,0 +1,362 @@ +package dev.ragnarok.filegallery.view.natives.animation + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Parcelable +import android.util.AttributeSet +import androidx.annotation.RawRes +import androidx.appcompat.widget.AppCompatImageView +import dev.ragnarok.fenrir.module.FenrirNative +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.Companion.RESTART +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LoadedFrom +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.LottieAnimationListener +import dev.ragnarok.fenrir.module.animation.thorvg.ThorVGLottieDrawable.RepeatMode +import dev.ragnarok.filegallery.Constants +import dev.ragnarok.filegallery.R +import dev.ragnarok.filegallery.util.Utils +import dev.ragnarok.filegallery.util.coroutines.CancelableJob +import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.fromIOToMain +import kotlinx.coroutines.flow.flow +import okhttp3.Call +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import java.io.File +import kotlin.coroutines.cancellation.CancellationException + +@SuppressLint("CustomViewStyleable") +class ThorVGLottieView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : + AppCompatImageView(context, attrs) { + private val cache: ThorVGLottieNetworkCache = ThorVGLottieNetworkCache(context) + private var mDisposable = CancelableJob() + private var animatedDrawable: ThorVGLottieDrawable? = null + private var mListener: LottieAnimationListener? = null + + @LoadedFrom + private var loadedFrom = LoadedFrom.NO + private var colorReplacementTmp: IntArray? = null + private var useMoveColorTmp: Boolean = false + private var deleteInvalidFileTmp: Boolean = false + private var filePathTmp: String? = null + + @RawRes + private var rawResTmp: Int? = null + private var isPlaying: Boolean? = null + private var repeatTmp: Boolean? = null + + @RepeatMode + private var repeatModeTmp: Int? = null + + private var mOnAttached = false + + private fun createLottieDrawable() { + if (FenrirNative.isNativeLoaded && mOnAttached && loadedFrom != LoadedFrom.NO && animatedDrawable == null) { + when (loadedFrom) { + LoadedFrom.RES -> { + animatedDrawable = rawResTmp?.let { + ThorVGLottieDrawable( + it, + colorReplacementTmp, + useMoveColorTmp + ) + } + } + + LoadedFrom.FILE -> { + animatedDrawable = filePathTmp?.let { + ThorVGLottieDrawable( + it, + deleteInvalidFileTmp, + colorReplacementTmp, + useMoveColorTmp + ) + } + } + } + if (animatedDrawable == null) { + return + } + repeatModeTmp?.let { animatedDrawable?.setRepeatMode(it) } + repeatTmp?.let { animatedDrawable?.setRepeatCount(if (it) Int.MAX_VALUE else 1) } + super.setImageDrawable(animatedDrawable) + animatedDrawable?.callback = this + animatedDrawable?.setAnimationListener(mListener) + if (isPlaying == true) { + startAnimation() + } + } + } + + private fun setAnimationByUrlCache( + url: String, + autoPlay: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded) { + return + } + val ch = cache.fetch(url) + if (ch == null) { + return + } + if (filePathTmp == ch.absolutePath && deleteInvalidFileTmp == true && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = false) + deleteInvalidFileTmp = true + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.FILE + isPlaying = autoPlay + filePathTmp = ch.absolutePath + if (mOnAttached) { + createLottieDrawable() + } + } + + fun fromNet( + url: String?, + client: OkHttpClient.Builder, + autoPlay: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { + if (loadedFrom == LoadedFrom.NET) { + loadedFrom = LoadedFrom.NO + } + return + } + if (filePathTmp == url && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.NET) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.NET + filePathTmp = url + isPlaying = autoPlay + + if (cache.isCachedFile(url)) { + setAnimationByUrlCache(url, autoPlay, colorReplacement, useMoveColor) + return + } + mDisposable.set(flow { + var call: Call? = null + try { + val request: Request = Request.Builder() + .url(url) + .build() + call = client.build().newCall(request) + val response: Response = call.execute() + if (!response.isSuccessful) { + emit(false) + return@flow + } + cache.writeTempCacheFile(url, response.body.source()) + response.close() + cache.renameTempFile(url) + emit(true) + } catch (e: CancellationException) { + call?.cancel() + throw e + } + }.fromIOToMain { + if (it) { + setAnimationByUrlCache(url, autoPlay, colorReplacement, useMoveColor) + } + }) + } + + fun fromRes( + @RawRes resId: Int, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || resId == -1) { + return + } + if (rawResTmp == resId && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.RES) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.RES + rawResTmp = resId + if (mOnAttached) { + createLottieDrawable() + } + } + + fun fromFile( + file: File, + deleteInvalidFile: Boolean, + colorReplacement: IntArray? = null, + useMoveColor: Boolean = false + ) { + if (!FenrirNative.isNativeLoaded || !file.exists()) { + return + } + if (filePathTmp == file.absolutePath && deleteInvalidFileTmp == deleteInvalidFile && colorReplacementTmp == colorReplacement && useMoveColorTmp == useMoveColor && loadedFrom == LoadedFrom.FILE) { + return + } + clearAnimationDrawable(callSuper = true, clearState = true, cancelTask = true) + colorReplacementTmp = colorReplacement + useMoveColorTmp = useMoveColor + loadedFrom = LoadedFrom.FILE + filePathTmp = file.absolutePath + deleteInvalidFileTmp = deleteInvalidFile + if (mOnAttached) { + createLottieDrawable() + } + } + + fun isPlaying(): Boolean { + return animatedDrawable != null && animatedDrawable?.isRunning == true + } + + fun setRepeat(repeat: Boolean) { + animatedDrawable?.setRepeatCount(if (repeat) Int.MAX_VALUE else 1) + repeatTmp = repeat + } + + fun setRepeatMode(@RepeatMode repeatMode: Int) { + animatedDrawable?.setRepeatMode(repeatMode) + repeatModeTmp = repeatMode + } + + fun startAnimation() { + animatedDrawable?.start() + isPlaying = true + } + + fun stopAnimation() { + animatedDrawable?.let { + it.stop() + isPlaying = false + } + } + + fun pauseAnimation() { + animatedDrawable?.pause() + } + + fun resumeAnimation() { + animatedDrawable?.resume() + } + + fun clearAnimationDrawable(callSuper: Boolean, clearState: Boolean, cancelTask: Boolean) { + if (cancelTask) { + mDisposable.cancel() + } + if (animatedDrawable != null) { + animatedDrawable?.callback = null + animatedDrawable?.release() + animatedDrawable = null + } + if (callSuper) { + super.setImageDrawable(null) + } + if (clearState) { + isPlaying = false + loadedFrom = LoadedFrom.NO + filePathTmp = null + rawResTmp = null + deleteInvalidFileTmp = false + } + } + + override fun setImageDrawable(dr: Drawable?) { + super.setImageDrawable(dr) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageBitmap(bm: Bitmap?) { + super.setImageBitmap(bm) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageResource(resId: Int) { + super.setImageResource(resId) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun setImageURI(uri: Uri?) { + super.setImageURI(uri) + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = true) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + if (measuredWidth <= 0 || measuredHeight <= 0) { + return + } + animatedDrawable?.setSize(measuredWidth, measuredHeight) + } + + override fun onAttachedToWindow() { + mOnAttached = true + + super.onAttachedToWindow() + + if (loadedFrom == LoadedFrom.NET) { + filePathTmp?.let { + fromNet( + it, + Utils.createOkHttp(Constants.PICASSO_TIMEOUT), + isPlaying == true, + colorReplacementTmp, + useMoveColorTmp + ) + return + } + clearAnimationDrawable(callSuper = false, clearState = true, cancelTask = false) + } else if (loadedFrom != LoadedFrom.NO) { + createLottieDrawable() + } + } + + override fun onDetachedFromWindow() { + mOnAttached = false + super.onDetachedFromWindow() + if (loadedFrom != LoadedFrom.NO) { + clearAnimationDrawable(callSuper = true, clearState = false, cancelTask = true) + } + } + + fun setAnimationListener(listener: LottieAnimationListener?) { + mListener = listener + } + + protected override fun onSaveInstanceState(): Parcelable? { + return super.onSaveInstanceState() + } + + protected override fun onRestoreInstanceState(state: Parcelable?) { + super.onRestoreInstanceState(state) + } + + init { + val a = context.obtainStyledAttributes(attrs, R.styleable.ThorVGLottieView) + rawResTmp = a.getResourceId(R.styleable.ThorVGLottieView_fromRes, 0) + if (rawResTmp == 0) { + rawResTmp = null + } + repeatTmp = a.getBoolean(R.styleable.ThorVGLottieView_loopAnimation, false) + repeatModeTmp = a.getInt(R.styleable.ThorVGLottieView_loopMode, RESTART) + a.recycle() + if (FenrirNative.isNativeLoaded && rawResTmp != null) { + loadedFrom = LoadedFrom.RES + isPlaying = true + } else { + rawResTmp = null + } + } +} diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieImageView.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieImageView.kt deleted file mode 100644 index 916b6db6c..000000000 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieImageView.kt +++ /dev/null @@ -1,294 +0,0 @@ -package dev.ragnarok.filegallery.view.natives.rlottie - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.drawable.Drawable -import android.util.AttributeSet -import androidx.annotation.RawRes -import androidx.appcompat.widget.AppCompatImageView -import dev.ragnarok.fenrir.module.BufferWriteNative -import dev.ragnarok.fenrir.module.FenrirNative -import dev.ragnarok.fenrir.module.rlottie.RLottieDrawable -import dev.ragnarok.filegallery.R -import dev.ragnarok.filegallery.util.coroutines.CancelableJob -import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.fromIOToMain -import kotlinx.coroutines.flow.flow -import okhttp3.Call -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import java.io.File -import kotlin.coroutines.cancellation.CancellationException - -class RLottieImageView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - AppCompatImageView(context, attrs) { - private val cache: RLottieNetworkCache = RLottieNetworkCache(context) - private var layerColors: HashMap? = null - private var animatedDrawable: RLottieDrawable? = null - private var autoRepeat: Boolean - private var attachedToWindow = false - private var playing = false - private var mDisposable = CancelableJob() - fun clearLayerColors() { - layerColors?.clear() - } - - fun setLayerColor(layer: String, color: Int) { - if (layerColors == null) { - layerColors = HashMap() - } - (layerColors ?: return)[layer] = color - animatedDrawable?.setLayerColor(layer, color) - } - - fun replaceColors(colors: IntArray?) { - animatedDrawable?.replaceColors(colors) - } - - private fun setAnimationByUrlCache(url: String, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - val ch = cache.fetch(url) - if (ch == null) { - setImageDrawable(null) - return - } - autoRepeat = false - setAnimation( - RLottieDrawable( - ch, true, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - playAnimation() - } - - fun fromNet(url: String?, client: OkHttpClient.Builder, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { - return - } - clearAnimationDrawable() - if (cache.isCachedFile(url)) { - setAnimationByUrlCache(url, w, h) - return - } - mDisposable.set(flow { - var call: Call? = null - try { - val request: Request = Request.Builder() - .url(url) - .build() - call = client.build().newCall(request) - val response: Response = call.execute() - if (!response.isSuccessful) { - emit(false) - return@flow - } - cache.writeTempCacheFile(url, response.body.source()) - response.close() - cache.renameTempFile(url) - emit(true) - } catch (e: CancellationException) { - call?.cancel() - throw e - } - }.fromIOToMain { - if (it) { - setAnimationByUrlCache(url, w, h) - } - }) - } - - private fun setAnimation(rLottieDrawable: RLottieDrawable) { - animatedDrawable = rLottieDrawable - animatedDrawable?.setAutoRepeat(if (autoRepeat) 1 else 0) - if (layerColors != null) { - animatedDrawable?.beginApplyLayerColors() - for ((key, value) in layerColors ?: return) { - animatedDrawable?.setLayerColor(key, value) - } - animatedDrawable?.commitApplyLayerColors() - } - animatedDrawable?.setAllowDecodeSingleFrame(true) - animatedDrawable?.setCurrentParentView(this) - setImageDrawable(animatedDrawable) - } - - @JvmOverloads - fun fromRes( - @RawRes resId: Int, - w: Int, - h: Int, - colorReplacement: IntArray? = null, - useMoveColor: Boolean = false - ) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - resId, - w, - h, - false, - colorReplacement, - useMoveColor - ) - ) - } - - fun fromFile(file: File, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - file, false, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - } - - fun fromString(jsonString: BufferWriteNative, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - jsonString, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - } - - fun clearAnimationDrawable() { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - setImageDrawable(null) - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - attachedToWindow = true - animatedDrawable?.setCurrentParentView(this) - if (playing) { - animatedDrawable?.start() - } - } - - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - mDisposable.cancel() - attachedToWindow = false - animatedDrawable?.stop() - animatedDrawable?.setCurrentParentView(null) - } - - fun isPlaying(): Boolean { - return animatedDrawable != null && animatedDrawable?.isRunning == true - } - - fun setAutoRepeat(repeat: Boolean) { - autoRepeat = repeat - } - - fun setProgress(progress: Float) { - animatedDrawable?.setProgress(progress) - } - - override fun setImageDrawable(dr: Drawable?) { - super.setImageDrawable(dr) - if (dr !is RLottieDrawable) { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - } - - override fun setImageBitmap(bm: Bitmap?) { - super.setImageBitmap(bm) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - - override fun setImageResource(resId: Int) { - super.setImageResource(resId) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - - fun playAnimation() { - playing = true - if (attachedToWindow) { - animatedDrawable?.start() - } - } - - fun replayAnimation() { - if (animatedDrawable == null) { - return - } - playing = true - if (attachedToWindow) { - animatedDrawable?.stop() - animatedDrawable?.setAutoRepeat(1) - animatedDrawable?.start() - } - } - - fun resetFrame() { - playing = true - if (attachedToWindow) { - animatedDrawable?.setProgress(0f) - } - } - - fun stopAnimation() { - playing = false - if (attachedToWindow) { - animatedDrawable?.stop() - } - } - - init { - val a = context.obtainStyledAttributes(attrs, R.styleable.RLottieImageView) - val animRes = a.getResourceId(R.styleable.RLottieImageView_fromRes, 0) - autoRepeat = a.getBoolean(R.styleable.RLottieImageView_loopAnimation, false) - val width = a.getDimension(R.styleable.RLottieImageView_w, 28f).toInt() - val height = a.getDimension(R.styleable.RLottieImageView_h, 28f).toInt() - a.recycle() - if (FenrirNative.isNativeLoaded && animRes != 0) { - setAnimation(RLottieDrawable(animRes, width, height, false, null, false)) - playAnimation() - } - } -} diff --git a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieShapeableImageView.kt b/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieShapeableImageView.kt deleted file mode 100644 index a0f32599f..000000000 --- a/app_filegallery/src/main/kotlin/dev/ragnarok/filegallery/view/natives/rlottie/RLottieShapeableImageView.kt +++ /dev/null @@ -1,297 +0,0 @@ -package dev.ragnarok.filegallery.view.natives.rlottie - -import android.annotation.SuppressLint -import android.content.Context -import android.graphics.Bitmap -import android.graphics.drawable.Drawable -import android.util.AttributeSet -import androidx.annotation.RawRes -import com.google.android.material.imageview.ShapeableImageView -import dev.ragnarok.fenrir.module.BufferWriteNative -import dev.ragnarok.fenrir.module.FenrirNative -import dev.ragnarok.fenrir.module.rlottie.RLottieDrawable -import dev.ragnarok.filegallery.R -import dev.ragnarok.filegallery.util.coroutines.CancelableJob -import dev.ragnarok.filegallery.util.coroutines.CoroutinesUtils.fromIOToMain -import kotlinx.coroutines.flow.flow -import okhttp3.Call -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import java.io.File -import kotlin.coroutines.cancellation.CancellationException - -class RLottieShapeableImageView @JvmOverloads constructor( - context: Context, attrs: AttributeSet? = null -) : ShapeableImageView(context, attrs) { - private val cache: RLottieNetworkCache = RLottieNetworkCache(context) - private var layerColors: HashMap? = null - private var animatedDrawable: RLottieDrawable? = null - private var autoRepeat: Boolean - private var attachedToWindow = false - private var playing = false - private var mDisposable = CancelableJob() - fun clearLayerColors() { - layerColors?.clear() - } - - fun setLayerColor(layer: String, color: Int) { - if (layerColors == null) { - layerColors = HashMap() - } - (layerColors ?: return)[layer] = color - animatedDrawable?.setLayerColor(layer, color) - } - - fun replaceColors(colors: IntArray?) { - animatedDrawable?.replaceColors(colors) - } - - private fun setAnimationByUrlCache(url: String, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - val ch = cache.fetch(url) - if (ch == null) { - setImageDrawable(null) - return - } - autoRepeat = false - setAnimation( - RLottieDrawable( - ch, true, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - playAnimation() - } - - fun fromNet(url: String?, client: OkHttpClient.Builder, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded || url.isNullOrEmpty()) { - return - } - clearAnimationDrawable() - if (cache.isCachedFile(url)) { - setAnimationByUrlCache(url, w, h) - return - } - mDisposable.set(flow { - var call: Call? = null - try { - val request: Request = Request.Builder() - .url(url) - .build() - call = client.build().newCall(request) - val response: Response = call.execute() - if (!response.isSuccessful) { - emit(false) - return@flow - } - cache.writeTempCacheFile(url, response.body.source()) - response.close() - cache.renameTempFile(url) - emit(true) - } catch (e: CancellationException) { - call?.cancel() - throw e - } - }.fromIOToMain { - if (it) { - setAnimationByUrlCache(url, w, h) - } - }) - } - - private fun setAnimation(rLottieDrawable: RLottieDrawable) { - animatedDrawable = rLottieDrawable - animatedDrawable?.setAutoRepeat(if (autoRepeat) 1 else 0) - if (layerColors != null) { - animatedDrawable?.beginApplyLayerColors() - for ((key, value) in layerColors ?: return) { - animatedDrawable?.setLayerColor(key, value) - } - animatedDrawable?.commitApplyLayerColors() - } - animatedDrawable?.setAllowDecodeSingleFrame(true) - animatedDrawable?.setCurrentParentView(this) - setImageDrawable(animatedDrawable) - } - - @JvmOverloads - fun fromRes( - @RawRes resId: Int, - w: Int, - h: Int, - colorReplacement: IntArray? = null, - useMoveColor: Boolean = false - ) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - resId, - w, - h, - false, - colorReplacement, - useMoveColor - ) - ) - } - - fun fromFile(file: File, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - file, false, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - } - - fun fromString(jsonString: BufferWriteNative, w: Int, h: Int) { - if (!FenrirNative.isNativeLoaded) { - return - } - clearAnimationDrawable() - setAnimation( - RLottieDrawable( - jsonString, w, h, - limitFps = false, - colorReplacement = null, - useMoveColor = false - ) - ) - } - - fun clearAnimationDrawable() { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - setImageDrawable(null) - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - attachedToWindow = true - animatedDrawable?.setCurrentParentView(this) - if (playing) { - animatedDrawable?.start() - } - } - - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - mDisposable.cancel() - attachedToWindow = false - animatedDrawable?.stop() - animatedDrawable?.setCurrentParentView(null) - } - - fun isPlaying(): Boolean { - return animatedDrawable != null && animatedDrawable?.isRunning == true - } - - fun setAutoRepeat(repeat: Boolean) { - autoRepeat = repeat - } - - fun setProgress(progress: Float) { - animatedDrawable?.setProgress(progress) - } - - override fun setImageDrawable(dr: Drawable?) { - super.setImageDrawable(dr) - if (dr !is RLottieDrawable) { - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - } - - override fun setImageBitmap(bm: Bitmap?) { - super.setImageBitmap(bm) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - - override fun setImageResource(resId: Int) { - super.setImageResource(resId) - mDisposable.cancel() - animatedDrawable?.let { - it.stop() - it.callback = null - it.recycle() - animatedDrawable = null - } - } - - fun playAnimation() { - playing = true - if (attachedToWindow) { - animatedDrawable?.start() - } - } - - fun replayAnimation() { - if (animatedDrawable == null) { - return - } - playing = true - if (attachedToWindow) { - animatedDrawable?.stop() - animatedDrawable?.setAutoRepeat(1) - animatedDrawable?.start() - } - } - - fun resetFrame() { - playing = true - if (attachedToWindow) { - animatedDrawable?.setProgress(0f) - } - } - - fun stopAnimation() { - playing = false - if (attachedToWindow) { - animatedDrawable?.stop() - } - } - - init { - @SuppressLint("CustomViewStyleable") val a = - context.obtainStyledAttributes(attrs, R.styleable.RLottieImageView) - val animRes = a.getResourceId(R.styleable.RLottieImageView_fromRes, 0) - autoRepeat = a.getBoolean(R.styleable.RLottieImageView_loopAnimation, false) - val width = a.getDimension(R.styleable.RLottieImageView_w, 28f).toInt() - val height = a.getDimension(R.styleable.RLottieImageView_h, 28f).toInt() - a.recycle() - if (FenrirNative.isNativeLoaded && animRes != 0) { - setAnimation(RLottieDrawable(animRes, width, height, false, null, false)) - playAnimation() - } - } -} diff --git a/app_filegallery/src/main/res/layout/activity_photo_pager.xml b/app_filegallery/src/main/res/layout/activity_photo_pager.xml index ec7a94b24..4414b6657 100644 --- a/app_filegallery/src/main/res/layout/activity_photo_pager.xml +++ b/app_filegallery/src/main/res/layout/activity_photo_pager.xml @@ -13,7 +13,7 @@ - - - - - - - - - - - - - - -#151515 #313131 @style/CustomBottomSheetDialog + @color/m3_ref_palette_dynamic_secondary30 diff --git a/app_filegallery/src/main/res/values-v31/styles.xml b/app_filegallery/src/main/res/values-v31/styles.xml index 53f2b73af..a63fd9a99 100644 --- a/app_filegallery/src/main/res/values-v31/styles.xml +++ b/app_filegallery/src/main/res/values-v31/styles.xml @@ -41,5 +41,6 @@ ?attr/colorPrimary @style/CustomBottomSheetDialog + @color/m3_ref_palette_dynamic_secondary90 diff --git a/app_filegallery/src/main/res/values/attrs.xml b/app_filegallery/src/main/res/values/attrs.xml index 59c94fa11..28466435a 100644 --- a/app_filegallery/src/main/res/values/attrs.xml +++ b/app_filegallery/src/main/res/values/attrs.xml @@ -103,11 +103,13 @@ - + + + + + - - diff --git a/build.gradle b/build.gradle index 918316a90..9a6261394 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ buildscript { ext.appCompileSDK = 35 ext.appBuildTools = "35.0.0" - ext.appNdk = "28.0.12433566" + ext.appNdk = "28.0.12674087" ext.appMinSDK = is_developer_build ? 29 : 26 ext.appTargetSDK = 31 ext.appFenrirVersionCode = 999 @@ -13,40 +13,40 @@ buildscript { ext.appFileGalleryVersionName = "1.999" //androidx libraries - ext.activityVersion = "1.9.3" - ext.annotationVersion = "1.9.0" + ext.activityVersion = "1.10.0-beta01" + ext.annotationVersion = "1.9.1" ext.appcompatVersion = "1.7.0" ext.biometricVersion = "1.4.0-alpha02" ext.browserVersion = "1.8.0" ext.cardviewVersion = "1.0.0" - ext.collectionVersion = "1.5.0-alpha04" + ext.collectionVersion = "1.5.0-alpha06" ext.concurentVersion = "1.2.0" - ext.constraintlayoutVersion = "2.2.0-rc01" + ext.constraintlayoutVersion = "2.2.0" ext.coordinatorlayoutVersion = "1.3.0-alpha02" - ext.coreVersion = "1.15.0-rc01" + ext.coreVersion = "1.15.0" ext.customviewVersion = "1.2.0-alpha02" ext.customviewPoolingcontainerVersion = "1.0.0" ext.documentfile = "1.0.1" ext.drawerlayoutVersion = "1.2.0" ext.dynamicanimationVersion = "1.1.0-alpha03" - ext.exifinterfaceVersion = "1.3.7" - ext.fragmentVersion = "1.8.4" + ext.exifinterfaceVersion = "1.4.0-alpha01" + ext.fragmentVersion = "1.8.5" ext.graphicsShapesVersion = "1.0.1" - ext.lifecycleVersion = "2.8.6" + ext.lifecycleVersion = "2.8.7" ext.mediaVersion = "1.7.0" - ext.media3Version = "1.5.0-alpha01" + ext.media3Version = "1.5.0-rc02" ext.resourceInspectionAnnotation = "1.0.1" - ext.savedStateVersion = "1.3.0-alpha03" + ext.savedStateVersion = "1.3.0-alpha05" ext.swiperefreshlayoutVersion = "1.2.0-alpha01" ext.tracingVersion = "1.2.0" ext.transitionVersion = "1.5.1" ext.vectordrawableVersion = "1.2.0" ext.webkitVersion = "1.12.1" - ext.workVersion = "2.10.0-beta01" + ext.workVersion = "2.10.0" //firebase libraries ext.firebaseDatatransportVersion = "19.0.0" - ext.firebaseMessagingVersion = "24.0.2" + ext.firebaseMessagingVersion = "24.1.0" //firebase common libraries ext.firebaseCommonVersion = "21.0.0" @@ -58,16 +58,16 @@ buildscript { ext.autoValueVersion = "1.11.0" //common libraries - ext.kotlin_version = "2.0.21" + ext.kotlin_version = "2.1.0-RC2" ext.kotlin_coroutines = "1.9.0" ext.kotlin_serializer = "1.7.3" ext.okhttpLibraryVersion = "5.0.0-SNAPSHOT" //ext.okhttpLibraryVersion = "5.0.0-alpha.14" ext.okioVersion = "3.9.1" ext.guavaVersion = "33.3.1-android" - ext.errorProneVersion = "2.33.0" + ext.errorProneVersion = "2.36.0" ext.checkerCompatQualVersion = "2.5.6" - ext.checkerQualAndroidVersion = "3.48.1" + ext.checkerQualAndroidVersion = "3.48.2" //APP_PROPS ext.targetAbi = is_developer_build ? ["arm64-v8a", "x86_64"] : ["arm64-v8a", "armeabi-v7a", "x86_64"] @@ -92,7 +92,7 @@ buildscript { //maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' } } dependencies { - classpath "com.android.tools.build:gradle:8.7.1" + classpath "com.android.tools.build:gradle:8.7.2" classpath "com.google.gms:google-services:4.4.2" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" diff --git a/camera2/build.gradle b/camera2/build.gradle index 2863311b1..e1a76556d 100644 --- a/camera2/build.gradle +++ b/camera2/build.gradle @@ -3,7 +3,7 @@ plugins { id("kotlin-android") } -//1.4.0-rc04 +//1.4.0 android { namespace "androidx.camera" diff --git a/fenrir_common/src/main/kotlin/dev/ragnarok/fenrir/Common.kt b/fenrir_common/src/main/kotlin/dev/ragnarok/fenrir/Common.kt index ae118bbc9..ff3f44772 100644 --- a/fenrir_common/src/main/kotlin/dev/ragnarok/fenrir/Common.kt +++ b/fenrir_common/src/main/kotlin/dev/ragnarok/fenrir/Common.kt @@ -59,7 +59,7 @@ object Common { 8 -> PaganSymbolWall(R.raw.svg_pagan_celtic_flower, 180f, 180f) 9 -> PaganSymbolWall(R.raw.svg_pagan_slepnir, 108f, 108f) 10 -> PaganSymbolWall( - R.raw.fenrir, 140f, intArrayOf( + R.raw.fenrir, intArrayOf( 0x333333, getColorPrimary(context), 0x777777, @@ -78,7 +78,7 @@ object Common { 19 -> PaganSymbolWall(R.raw.svg_pagan_chur, 150f, 150f) 20 -> PaganSymbolWall(R.raw.svg_pagan_fire, 180f, 180f) 21 -> PaganSymbolWall( - R.raw.flame, 140f, intArrayOf( + R.raw.flame, intArrayOf( 0xFF812E, getColorPrimary(context) ), true @@ -115,7 +115,6 @@ object Common { fun getAboutUsAnimation(paganSymbol: Int, context: Context): PaganSymbolWall { return PaganSymbolWall( R.raw.fenrir, - 140f, intArrayOf( 0x333333, getColorPrimary(context), @@ -131,7 +130,6 @@ object Common { iconRes = icon lottieRes = R.raw.fenrir this.lottie_replacement = null - lottie_widthHeight = 140f icon_width = width icon_height = height lottie_useMoveColor = false @@ -139,7 +137,6 @@ object Common { constructor( @RawRes animation: Int, - widthHeight: Float, replacement: IntArray? = null, useMoveColor: Boolean = false ) { @@ -147,7 +144,6 @@ object Common { iconRes = R.raw.svg_pagan_cat lottieRes = animation this.lottie_replacement = replacement - this.lottie_widthHeight = widthHeight this.lottie_useMoveColor = useMoveColor icon_width = 160f icon_height = 160f @@ -169,7 +165,6 @@ object Common { @RawRes val lottieRes: Int val lottie_replacement: IntArray? - val lottie_widthHeight: Float val lottie_useMoveColor: Boolean var icon_width: Float diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 213a76afa..e52cec12e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed Mar 20 16:00:00 MSK 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/image/src/main/kotlin/me/minetsh/imaging/core/homing/IMGHoming.kt b/image/src/main/kotlin/me/minetsh/imaging/core/homing/IMGHoming.kt index 35e52f321..70c39a070 100644 --- a/image/src/main/kotlin/me/minetsh/imaging/core/homing/IMGHoming.kt +++ b/image/src/main/kotlin/me/minetsh/imaging/core/homing/IMGHoming.kt @@ -4,7 +4,7 @@ package me.minetsh.imaging.core.homing * Created by felix on 2017/11/28 下午4:14. */ class IMGHoming(var x: Float, var y: Float, var scale: Float, var rotate: Float) { - operator fun set(x: Float, y: Float, scale: Float, rotate: Float) { + fun set(x: Float, y: Float, scale: Float, rotate: Float) { this.x = x this.y = y this.scale = scale diff --git a/image/src/main/kotlin/me/minetsh/imaging/core/homing/IMGHomingEvaluator.kt b/image/src/main/kotlin/me/minetsh/imaging/core/homing/IMGHomingEvaluator.kt index f125bff9b..68aa9a314 100644 --- a/image/src/main/kotlin/me/minetsh/imaging/core/homing/IMGHomingEvaluator.kt +++ b/image/src/main/kotlin/me/minetsh/imaging/core/homing/IMGHomingEvaluator.kt @@ -19,7 +19,7 @@ class IMGHomingEvaluator : TypeEvaluator { val scale = startValue.scale + fraction * (endValue.scale - startValue.scale) val rotate = startValue.rotate + fraction * (endValue.rotate - startValue.rotate) homing?.let { - it[x, y, scale] = rotate + it.set(x, y, scale, rotate) return it } ?: run { IMGHoming(x, y, scale, rotate).let { diff --git a/libfenrir/build.gradle b/libfenrir/build.gradle index 576f87f97..c05556242 100644 --- a/libfenrir/build.gradle +++ b/libfenrir/build.gradle @@ -49,7 +49,7 @@ android { externalNativeBuild { cmake { - version = "3.30.5" + version = "3.31.0" path = file("src/main/jni/CMakeLists.txt") } } diff --git a/libfenrir/ffmpeg.sh b/libfenrir/ffmpeg.sh index 0ecb5c775..f7c12c055 100644 --- a/libfenrir/ffmpeg.sh +++ b/libfenrir/ffmpeg.sh @@ -8,7 +8,7 @@ rm -r -f ".git" ENABLED_DECODERS=(mpeg4 h264 hevc mp3 aac ac3 eac3 flac vorbis alac) HOST_PLATFORM="linux-x86_64" -NDK_PATH="$HOME/Android/Sdk/ndk/28.0.12433566" +NDK_PATH="$HOME/Android/Sdk/ndk/28.0.12674087" echo 'Please input platform version (Example 21 - Android 5.0): ' read ANDROID_PLATFORM diff --git a/libfenrir/src/main/java/com/google/zxing/client/result/EmailDoCoMoResultParser.java b/libfenrir/src/main/java/com/google/zxing/client/result/EmailDoCoMoResultParser.java index ab301f42f..49992df26 100644 --- a/libfenrir/src/main/java/com/google/zxing/client/result/EmailDoCoMoResultParser.java +++ b/libfenrir/src/main/java/com/google/zxing/client/result/EmailDoCoMoResultParser.java @@ -29,8 +29,10 @@ */ public final class EmailDoCoMoResultParser extends AbstractDoCoMoResultParser { - private static final Pattern ATEXT_ALPHANUMERIC = Pattern.compile("[a-zA-Z0-9@.!#$%&'*+\\-/=?^_`{|}~]+"); - + private static final String EMAIL_LOCAL = "[^:]+"; + private static final String EMAIL_DOMAIN = "([0-9a-zA-Z]+[0-9a-zA-Z\\-]+[0-9a-zA-Z]+\\.)+[a-zA-Z]{2,}"; + private static final Pattern EMAIL = Pattern.compile("^" + EMAIL_LOCAL + "@" + EMAIL_DOMAIN + "$"); + @Override public EmailAddressParsedResult parse(Result result) { String rawText = getMassagedText(result); @@ -58,7 +60,7 @@ public EmailAddressParsedResult parse(Result result) { * in a barcode, not "judge" it. */ static boolean isBasicallyValidEmailAddress(String email) { - return email != null && ATEXT_ALPHANUMERIC.matcher(email).matches() && email.indexOf('@') >= 0; + return email != null && EMAIL.matcher(email).matches(); } } diff --git a/libfenrir/src/main/java/com/google/zxing/pdf417/encoder/PDF417.java b/libfenrir/src/main/java/com/google/zxing/pdf417/encoder/PDF417.java index a9d63dc23..3402bb3d1 100644 --- a/libfenrir/src/main/java/com/google/zxing/pdf417/encoder/PDF417.java +++ b/libfenrir/src/main/java/com/google/zxing/pdf417/encoder/PDF417.java @@ -659,7 +659,8 @@ public void generateBarcodeLogic(String msg, int errorCorrectionLevel, boolean a String highLevel = PDF417HighLevelEncoder.encodeHighLevel(msg, compaction, encoding, autoECI); int sourceCodeWords = highLevel.length(); - int[] dimension = determineDimensions(sourceCodeWords, errorCorrectionCodeWords); + int[] dimension = determineDimensions(minCols, maxCols, minRows, maxRows, + sourceCodeWords, errorCorrectionCodeWords); int cols = dimension[0]; int rows = dimension[1]; @@ -692,16 +693,25 @@ public void generateBarcodeLogic(String msg, int errorCorrectionLevel, boolean a * Determine optimal nr of columns and rows for the specified number of * codewords. * + * @param minCols minimum number of columns + * @param maxCols maximum number of columns + * @param minRows minimum number of rows + * @param maxRows maximum number of rows * @param sourceCodeWords number of code words * @param errorCorrectionCodeWords number of error correction code words * @return dimension object containing cols as width and rows as height + * @throws WriterException when dimensions can't be determined */ - private int[] determineDimensions(int sourceCodeWords, int errorCorrectionCodeWords) throws WriterException { + protected static int[] determineDimensions(int minCols, int maxCols, + int minRows, int maxRows, + int sourceCodeWords, int errorCorrectionCodeWords) throws WriterException { float ratio = 0.0f; int[] dimension = null; + int currentCol = minCols; for (int cols = minCols; cols <= maxCols; cols++) { - + currentCol = cols; + int rows = calculateNumberOfRows(sourceCodeWords, errorCorrectionCodeWords, cols); if (rows < minRows) { @@ -725,7 +735,7 @@ private int[] determineDimensions(int sourceCodeWords, int errorCorrectionCodeWo // Handle case when min values were larger than necessary if (dimension == null) { - int rows = calculateNumberOfRows(sourceCodeWords, errorCorrectionCodeWords, minCols); + int rows = calculateNumberOfRows(sourceCodeWords, errorCorrectionCodeWords, currentCol); if (rows < minRows) { dimension = new int[]{minCols, minRows}; } diff --git a/libfenrir/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoder.java b/libfenrir/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoder.java index 564d7c968..8ac202b9d 100644 --- a/libfenrir/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoder.java +++ b/libfenrir/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoder.java @@ -174,16 +174,15 @@ static String encodeHighLevel(String msg, Compaction compaction, Charset encodin if (msg.isEmpty()) { throw new WriterException("Empty message not allowed"); } + + if (Compaction.TEXT == compaction) { + checkCharset(msg, 127, "Consider specifying Compaction.AUTO instead of Compaction.TEXT"); + } if (encoding == null && !autoECI) { - for (int i = 0; i < msg.length(); i++) { - if (msg.charAt(i) > 255) { - throw new WriterException("Non-encodable character detected: " + msg.charAt(i) + " (Unicode: " + - (int) msg.charAt(i) + - "). Consider specifying EncodeHintType.PDF417_AUTO_ECI and/or EncodeTypeHint.CHARACTER_SET."); - } - } + checkCharset(msg, 255, "Consider specifying EncodeHintType.PDF417_AUTO_ECI and/or EncodeTypeHint.CHARACTER_SET"); } + //the codewords 0..928 are encoded as Unicode characters StringBuilder sb = new StringBuilder(msg.length()); @@ -283,6 +282,22 @@ static String encodeHighLevel(String msg, Compaction compaction, Charset encodin return sb.toString(); } + + /** + * Check if input is only made of characters between 0 and the upper limit + * @param input the input + * @param max the upper limit for charset + * @param errorMessage the message to explain the error + * @throws WriterException exception highlighting the offending character and a suggestion to avoid the error + */ + protected static void checkCharset(String input, int max, String errorMessage) throws WriterException { + for (int i = 0; i < input.length(); i++) { + if (input.charAt(i) > max) { + throw new WriterException("Non-encodable character detected: " + input.charAt(i) + " (Unicode: " + + (int) input.charAt(i) + ") at position #" + i + " - " + errorMessage); + } + } + } /** * Encode parts of the message using Text Compaction as described in ISO/IEC 15438:2001(E), diff --git a/libfenrir/src/main/jni/CMakeLists.txt b/libfenrir/src/main/jni/CMakeLists.txt index e9c7944f9..d83d5f6a3 100644 --- a/libfenrir/src/main/jni/CMakeLists.txt +++ b/libfenrir/src/main/jni/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.30.5 FATAL_ERROR) +cmake_minimum_required(VERSION 3.31.0 FATAL_ERROR) project(fenrir_jni C CXX ASM) if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") @@ -64,6 +64,15 @@ add_library(thorvg STATIC animation/thorvg/src/renderer/tvgTaskScheduler.cpp animation/thorvg/src/renderer/tvgText.cpp animation/thorvg/src/renderer/tvgWgCanvas.cpp + animation/thorvg/src/loaders/lottie/tvgLottieAnimation.cpp + animation/thorvg/src/loaders/lottie/tvgLottieBuilder.cpp + animation/thorvg/src/loaders/lottie/tvgLottieExpressions.cpp + animation/thorvg/src/loaders/lottie/tvgLottieInterpolator.cpp + animation/thorvg/src/loaders/lottie/tvgLottieLoader.cpp + animation/thorvg/src/loaders/lottie/tvgLottieModel.cpp + animation/thorvg/src/loaders/lottie/tvgLottieModifier.cpp + animation/thorvg/src/loaders/lottie/tvgLottieParser.cpp + animation/thorvg/src/loaders/lottie/tvgLottieParserHandler.cpp animation/thorvg/src/loaders/raw/tvgRawLoader.cpp animation/thorvg/src/loaders/svg/tvgSvgCssStyle.cpp animation/thorvg/src/loaders/svg/tvgSvgLoader.cpp @@ -76,57 +85,16 @@ add_library(thorvg STATIC animation/thorvg/src/common/tvgStr.cpp) target_compile_options(thorvg PRIVATE - -DTVG_STATIC=1 -fno-exceptions -ffast-math ${OPTIMIZE_NORMAL} -fno-rtti -fno-unwind-tables -fno-asynchronous-unwind-tables -Woverloaded-virtual -Wno-unused-parameter -Wno-nan-infinity-disabled ${SYM_VISIBILITY}) + -DTVG_STATIC=1 -DBIG_ENDIAN_COLORS -fno-exceptions -ffast-math ${OPTIMIZE_NORMAL} -fno-rtti -fno-unwind-tables -fno-asynchronous-unwind-tables -Woverloaded-virtual -Wno-unused-parameter -Wno-nan-infinity-disabled ${SYM_VISIBILITY}) target_include_directories(thorvg PRIVATE animation/thorvg/inc animation/thorvg/src/renderer animation/thorvg/src/renderer/sw_engine + animation/thorvg/src/loaders/lottie animation/thorvg/src/loaders/raw animation/thorvg/src/loaders/svg animation/thorvg/src/common) -add_library(rlottie STATIC - animation/rlottie/src/lottie/lottieanimation.cpp - animation/rlottie/src/lottie/lottieitem.cpp - animation/rlottie/src/lottie/lottiekeypath.cpp - animation/rlottie/src/lottie/lottieloader.cpp - animation/rlottie/src/lottie/lottiemodel.cpp - animation/rlottie/src/lottie/lottieparser.cpp - animation/rlottie/src/lottie/lottieitem_capi.cpp - animation/rlottie/src/vector/freetype/v_ft_math.cpp - animation/rlottie/src/vector/freetype/v_ft_raster.cpp - animation/rlottie/src/vector/freetype/v_ft_stroker.cpp - animation/rlottie/src/vector/stb/stb_image.cpp - animation/rlottie/src/vector/vbezier.cpp - animation/rlottie/src/vector/vbitmap.cpp - animation/rlottie/src/vector/vbrush.cpp - animation/rlottie/src/vector/varenaalloc.cpp - animation/rlottie/src/vector/vdasher.cpp - animation/rlottie/src/vector/vdebug.cpp - animation/rlottie/src/vector/vdrawable.cpp - animation/rlottie/src/vector/vdrawhelper.cpp - animation/rlottie/src/vector/vdrawhelper_sse2.cpp - animation/rlottie/src/vector/vdrawhelper_neon.cpp - animation/rlottie/src/vector/vdrawhelper_common.cpp - animation/rlottie/src/vector/velapsedtimer.cpp - animation/rlottie/src/vector/vimageloader.cpp - animation/rlottie/src/vector/vinterpolator.cpp - animation/rlottie/src/vector/vmatrix.cpp - animation/rlottie/src/vector/vpainter.cpp - animation/rlottie/src/vector/vpath.cpp - animation/rlottie/src/vector/vpathmesure.cpp - animation/rlottie/src/vector/vraster.cpp - animation/rlottie/src/vector/vrect.cpp - animation/rlottie/src/vector/vrle.cpp) -target_compile_options(rlottie PRIVATE - -fno-exceptions -ffast-math ${OPTIMIZE_NORMAL} -fno-rtti -fno-unwind-tables -fno-asynchronous-unwind-tables -Woverloaded-virtual -Wno-unused-parameter -Wno-nan-infinity-disabled ${SYM_VISIBILITY}) -target_include_directories(rlottie PRIVATE - animation/rlottie/inc - animation/rlottie/src/vector - animation/rlottie/src/vector/pixman - animation/rlottie/src/vector/freetype - animation/rlottie/src/vector/stb) - add_library(libyuv STATIC animation/libyuv/source/compare.cc animation/libyuv/source/compare_common.cc @@ -164,6 +132,7 @@ add_library(libyuv STATIC animation/libyuv/source/row_neon64.cc animation/libyuv/source/row_neon.cc animation/libyuv/source/row_rvv.cc + animation/libyuv/source/row_sme.cc animation/libyuv/source/row_sve.cc animation/libyuv/source/scale_any.cc animation/libyuv/source/scale_argb.cc @@ -176,6 +145,7 @@ add_library(libyuv STATIC animation/libyuv/source/scale_neon.cc animation/libyuv/source/scale_rgb.cc animation/libyuv/source/scale_rvv.cc + animation/libyuv/source/scale_sme.cc animation/libyuv/source/scale_uv.cc animation/libyuv/source/video_common.cc) target_compile_options(libyuv PRIVATE -ffast-math ${OPTIMIZE_NORMAL} -funroll-loops -fno-strict-aliasing -fno-math-errno ${SYM_VISIBILITY}) @@ -487,7 +457,6 @@ target_compile_options(common PRIVATE add_library(fenrir_jni SHARED jni_call.cpp - animation/lottie_jni.cpp animation/thorvg_jni.cpp animation/animation_jni.cpp animation/image_processing_util_jni.cpp @@ -511,7 +480,6 @@ add_library(fenrir_jni target_include_directories(fenrir_jni PRIVATE ./ - animation/rlottie/inc animation/thorvg/inc animation/libyuv/include compress/lz4 @@ -544,7 +512,6 @@ target_link_libraries(fenrir_jni PRIVATE swresample ${LTO_LINK} PRIVATE avutil ${LTO_LINK} PRIVATE common ${LTO_LINK} - PRIVATE rlottie ${LTO_LINK} PRIVATE thorvg ${LTO_LINK} PRIVATE libyuv ${LTO_LINK} PRIVATE opus ${LTO_LINK}) diff --git a/libfenrir/src/main/jni/animation/gif_builder.h b/libfenrir/src/main/jni/animation/gif_builder.h deleted file mode 100644 index bd07c896b..000000000 --- a/libfenrir/src/main/jni/animation/gif_builder.h +++ /dev/null @@ -1,836 +0,0 @@ -#ifndef gif_builder_h -#define gif_builder_h - -#include -#include -#include -#include - -#ifndef GIF_TEMP_MALLOC - -#include - -#define GIF_TEMP_MALLOC malloc -#endif - -#ifndef GIF_TEMP_FREE - -#include - -#define GIF_TEMP_FREE free -#endif - -#ifndef GIF_MALLOC - -#include - -#define GIF_MALLOC malloc -#endif - -#ifndef GIF_FREE - -#include - -#define GIF_FREE free -#endif - -#include -#include -#include -#include - -const int kGifTransIndex = 0; - -typedef struct { - int bitDepth; - - uint8_t r[256]; - uint8_t g[256]; - uint8_t b[256]; - - // k-d tree over RGB space, organized in heap fashion - // i.e. left child of node i is node i*2, right child is node i*2+1 - // nodes 256-511 are implicitly the leaves, containing a color - uint8_t treeSplitElt[256]; - uint8_t treeSplit[256]; -} GifPalette; - -// max, min, and abs functions -int GifIMax(int l, int r) { return l > r ? l : r; } - -int GifIMin(int l, int r) { return l < r ? l : r; } - -int GifIAbs(int i) { return i < 0 ? -i : i; } - -// walks the k-d tree to pick the palette entry for a desired color. -// Takes as in/out parameters the current best color and its error - -// only changes them if it finds a better color in its subtree. -// this is the major hotspot in the code at the moment. -void GifGetClosestPaletteColor(GifPalette *pPal, int r, int g, int b, int *bestInd, int *bestDiff, - int treeRoot) { - // base case, reached the bottom of the tree - if (treeRoot > (1 << pPal->bitDepth) - 1) { - int ind = treeRoot - (1 << pPal->bitDepth); - if (ind == kGifTransIndex) return; - - // check whether this color is better than the current winner - int r_err = r - ((int32_t) pPal->r[ind]); - int g_err = g - ((int32_t) pPal->g[ind]); - int b_err = b - ((int32_t) pPal->b[ind]); - int diff = GifIAbs(r_err) + GifIAbs(g_err) + GifIAbs(b_err); - - if (diff < *bestDiff) { - *bestInd = ind; - *bestDiff = diff; - } - - return; - } - - // take the appropriate color (r, g, or b) for this node of the k-d tree - int comps[3]; - comps[0] = r; - comps[1] = g; - comps[2] = b; - int splitComp = comps[pPal->treeSplitElt[treeRoot]]; - - int splitPos = pPal->treeSplit[treeRoot]; - if (splitPos > splitComp) { - // check the left subtree - GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot * 2); - if (*bestDiff > splitPos - splitComp) { - // cannot prove there's not a better value in the right subtree, check that too - GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot * 2 + 1); - } - } else { - GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot * 2 + 1); - if (*bestDiff > splitComp - splitPos) { - GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot * 2); - } - } -} - -void GifSwapPixels(uint8_t *image, int pixA, int pixB) { - uint8_t rA = image[pixA * 4]; - uint8_t gA = image[pixA * 4 + 1]; - uint8_t bA = image[pixA * 4 + 2]; - uint8_t aA = image[pixA * 4 + 3]; - - uint8_t rB = image[pixB * 4]; - uint8_t gB = image[pixB * 4 + 1]; - uint8_t bB = image[pixB * 4 + 2]; - uint8_t aB = image[pixA * 4 + 3]; - - image[pixA * 4] = rB; - image[pixA * 4 + 1] = gB; - image[pixA * 4 + 2] = bB; - image[pixA * 4 + 3] = aB; - - image[pixB * 4] = rA; - image[pixB * 4 + 1] = gA; - image[pixB * 4 + 2] = bA; - image[pixB * 4 + 3] = aA; -} - -// just the partition operation from quicksort -int GifPartition(uint8_t *image, const int left, const int right, const int elt, int pivotIndex) { - const int pivotValue = image[(pivotIndex) * 4 + elt]; - GifSwapPixels(image, pivotIndex, right - 1); - int storeIndex = left; - bool split = 0; - for (int ii = left; ii < right - 1; ++ii) { - int arrayVal = image[ii * 4 + elt]; - if (arrayVal < pivotValue) { - GifSwapPixels(image, ii, storeIndex); - ++storeIndex; - } else if (arrayVal == pivotValue) { - if (split) { - GifSwapPixels(image, ii, storeIndex); - ++storeIndex; - } - split = !split; - } - } - GifSwapPixels(image, storeIndex, right - 1); - return storeIndex; -} - -// Perform an incomplete sort, finding all elements above and below the desired median -void GifPartitionByMedian(uint8_t *image, int left, int right, int com, int neededCenter) { - if (left < right - 1) { - int pivotIndex = left + (right - left) / 2; - - pivotIndex = GifPartition(image, left, right, com, pivotIndex); - - // Only "sort" the section of the array that contains the median - if (pivotIndex > neededCenter) - GifPartitionByMedian(image, left, pivotIndex, com, neededCenter); - - if (pivotIndex < neededCenter) - GifPartitionByMedian(image, pivotIndex + 1, right, com, neededCenter); - } -} - -// Builds a palette by creating a balanced k-d tree of all pixels in the image -void GifSplitPalette(uint8_t *image, int numPixels, int firstElt, int lastElt, int splitElt, - int splitDist, int treeNode, bool buildForDither, GifPalette *pal) { - if (lastElt <= firstElt || numPixels == 0) - return; - - // base case, bottom of the tree - if (lastElt == firstElt + 1) { - if (buildForDither) { - // Dithering needs at least one color as dark as anything - // in the image and at least one brightest color - - // otherwise it builds up error and produces strange artifacts - if (firstElt == 1) { - // special case: the darkest color in the image - uint32_t r = 255, g = 255, b = 255; - for (int ii = 0; ii < numPixels; ++ii) { - r = (uint32_t) GifIMin((int32_t) r, image[ii * 4 + 0]); - g = (uint32_t) GifIMin((int32_t) g, image[ii * 4 + 1]); - b = (uint32_t) GifIMin((int32_t) b, image[ii * 4 + 2]); - } - - pal->r[firstElt] = (uint8_t) r; - pal->g[firstElt] = (uint8_t) g; - pal->b[firstElt] = (uint8_t) b; - - return; - } - - if (firstElt == (1 << pal->bitDepth) - 1) { - // special case: the lightest color in the image - uint32_t r = 0, g = 0, b = 0; - for (int ii = 0; ii < numPixels; ++ii) { - r = (uint32_t) GifIMax((int32_t) r, image[ii * 4 + 0]); - g = (uint32_t) GifIMax((int32_t) g, image[ii * 4 + 1]); - b = (uint32_t) GifIMax((int32_t) b, image[ii * 4 + 2]); - } - - pal->r[firstElt] = (uint8_t) r; - pal->g[firstElt] = (uint8_t) g; - pal->b[firstElt] = (uint8_t) b; - - return; - } - } - - // otherwise, take the average of all colors in this subcube - uint64_t r = 0, g = 0, b = 0; - for (int ii = 0; ii < numPixels; ++ii) { - r += image[ii * 4 + 0]; - g += image[ii * 4 + 1]; - b += image[ii * 4 + 2]; - } - - r += (uint64_t) numPixels / 2; // round to nearest - g += (uint64_t) numPixels / 2; - b += (uint64_t) numPixels / 2; - - r /= (uint64_t) numPixels; - g /= (uint64_t) numPixels; - b /= (uint64_t) numPixels; - - pal->r[firstElt] = (uint8_t) r; - pal->g[firstElt] = (uint8_t) g; - pal->b[firstElt] = (uint8_t) b; - - return; - } - - // Find the axis with the largest range - int minR = 255, maxR = 0; - int minG = 255, maxG = 0; - int minB = 255, maxB = 0; - for (int ii = 0; ii < numPixels; ++ii) { - int r = image[ii * 4 + 0]; - int g = image[ii * 4 + 1]; - int b = image[ii * 4 + 2]; - - if (r > maxR) maxR = r; - if (r < minR) minR = r; - - if (g > maxG) maxG = g; - if (g < minG) minG = g; - - if (b > maxB) maxB = b; - if (b < minB) minB = b; - } - - int rRange = maxR - minR; - int gRange = maxG - minG; - int bRange = maxB - minB; - - // and split along that axis. (incidentally, this means this isn't a "proper" k-d tree but I don't know what else to call it) - int splitCom = 1; - if (bRange > gRange) splitCom = 2; - if (rRange > bRange && rRange > gRange) splitCom = 0; - - int subPixelsA = numPixels * (splitElt - firstElt) / (lastElt - firstElt); - int subPixelsB = numPixels - subPixelsA; - - GifPartitionByMedian(image, 0, numPixels, splitCom, subPixelsA); - - pal->treeSplitElt[treeNode] = (uint8_t) splitCom; - pal->treeSplit[treeNode] = image[subPixelsA * 4 + splitCom]; - - GifSplitPalette(image, subPixelsA, firstElt, splitElt, splitElt - splitDist, splitDist / 2, - treeNode * 2, buildForDither, pal); - GifSplitPalette(image + subPixelsA * 4, subPixelsB, splitElt, lastElt, splitElt + splitDist, - splitDist / 2, treeNode * 2 + 1, buildForDither, pal); -} - -// Finds all pixels that have changed from the previous image and -// moves them to the fromt of th buffer. -// This allows us to build a palette optimized for the colors of the -// changed pixels only. -int GifPickChangedPixels(const uint8_t *lastFrame, uint8_t *frame, int numPixels) { - int numChanged = 0; - uint8_t *writeIter = frame; - - for (int ii = 0; ii < numPixels; ++ii) { - if (lastFrame[0] != frame[0] || - lastFrame[1] != frame[1] || - lastFrame[2] != frame[2]) { - writeIter[0] = frame[0]; - writeIter[1] = frame[1]; - writeIter[2] = frame[2]; - ++numChanged; - writeIter += 4; - } - lastFrame += 4; - frame += 4; - } - - return numChanged; -} - -// Creates a palette by placing all the image pixels in a k-d tree and then averaging the blocks at the bottom. -// This is known as the "modified median split" technique -void -GifMakePalette(const uint8_t *lastFrame, const uint8_t *nextFrame, uint32_t width, uint32_t height, - int bitDepth, bool buildForDither, GifPalette *pPal) { - pPal->bitDepth = bitDepth; - - // SplitPalette is destructive (it sorts the pixels by color) so - // we must create a copy of the image for it to destroy - size_t imageSize = (size_t)(width * height * 4 * sizeof(uint8_t)); - uint8_t *destroyableImage = (uint8_t *) GIF_TEMP_MALLOC(imageSize); - memcpy(destroyableImage, nextFrame, imageSize); - - int numPixels = (int) (width * height); - if (lastFrame) - numPixels = GifPickChangedPixels(lastFrame, destroyableImage, numPixels); - - const int lastElt = 1 << bitDepth; - const int splitElt = lastElt / 2; - const int splitDist = splitElt / 2; - - GifSplitPalette(destroyableImage, numPixels, 1, lastElt, splitElt, splitDist, 1, buildForDither, - pPal); - - GIF_TEMP_FREE(destroyableImage); - - // add the bottom node for the transparency index - pPal->treeSplit[1 << (bitDepth - 1)] = 0; - pPal->treeSplitElt[1 << (bitDepth - 1)] = 0; - - pPal->r[0] = pPal->g[0] = pPal->b[0] = 0; -} - -// Implements Floyd-Steinberg dithering, writes palette value to alpha -void GifDitherImage(const uint8_t *lastFrame, const uint8_t *nextFrame, uint8_t *outFrame, - uint32_t width, uint32_t height, GifPalette *pPal) { - int numPixels = (int) (width * height); - - // quantPixels initially holds color*256 for all pixels - // The extra 8 bits of precision allow for sub-single-color error values - // to be propagated - int32_t *quantPixels = (int32_t *) GIF_TEMP_MALLOC(sizeof(int32_t) * (size_t) numPixels * 4); - - for (int ii = 0; ii < numPixels * 4; ++ii) { - uint8_t pix = nextFrame[ii]; - int32_t pix16 = (int32_t)(pix) * 256; - quantPixels[ii] = pix16; - } - - for (uint32_t yy = 0; yy < height; ++yy) { - for (uint32_t xx = 0; xx < width; ++xx) { - int32_t *nextPix = quantPixels + 4 * (yy * width + xx); - const uint8_t *lastPix = lastFrame ? lastFrame + 4 * (yy * width + xx) : NULL; - - // Compute the colors we want (rounding to nearest) - int32_t rr = (nextPix[0] + 127) / 256; - int32_t gg = (nextPix[1] + 127) / 256; - int32_t bb = (nextPix[2] + 127) / 256; - - // if it happens that we want the color from last frame, then just write out - // a transparent pixel - if (lastFrame && - lastPix[0] == rr && - lastPix[1] == gg && - lastPix[2] == bb) { - nextPix[0] = rr; - nextPix[1] = gg; - nextPix[2] = bb; - nextPix[3] = kGifTransIndex; - continue; - } - - int32_t bestDiff = 1000000; - int32_t bestInd = kGifTransIndex; - - // Search the palete - GifGetClosestPaletteColor(pPal, rr, gg, bb, &bestInd, &bestDiff, 1); - - // Write the result to the temp buffer - int32_t r_err = nextPix[0] - (int32_t)(pPal->r[bestInd]) * 256; - int32_t g_err = nextPix[1] - (int32_t)(pPal->g[bestInd]) * 256; - int32_t b_err = nextPix[2] - (int32_t)(pPal->b[bestInd]) * 256; - - nextPix[0] = pPal->r[bestInd]; - nextPix[1] = pPal->g[bestInd]; - nextPix[2] = pPal->b[bestInd]; - nextPix[3] = bestInd; - - // Propagate the error to the four adjacent locations - // that we haven't touched yet - int quantloc_7 = (int) (yy * width + xx + 1); - int quantloc_3 = (int) (yy * width + width + xx - 1); - int quantloc_5 = (int) (yy * width + width + xx); - int quantloc_1 = (int) (yy * width + width + xx + 1); - - if (quantloc_7 < numPixels) { - int32_t *pix7 = quantPixels + 4 * quantloc_7; - pix7[0] += GifIMax(-pix7[0], r_err * 7 / 16); - pix7[1] += GifIMax(-pix7[1], g_err * 7 / 16); - pix7[2] += GifIMax(-pix7[2], b_err * 7 / 16); - } - - if (quantloc_3 < numPixels) { - int32_t *pix3 = quantPixels + 4 * quantloc_3; - pix3[0] += GifIMax(-pix3[0], r_err * 3 / 16); - pix3[1] += GifIMax(-pix3[1], g_err * 3 / 16); - pix3[2] += GifIMax(-pix3[2], b_err * 3 / 16); - } - - if (quantloc_5 < numPixels) { - int32_t *pix5 = quantPixels + 4 * quantloc_5; - pix5[0] += GifIMax(-pix5[0], r_err * 5 / 16); - pix5[1] += GifIMax(-pix5[1], g_err * 5 / 16); - pix5[2] += GifIMax(-pix5[2], b_err * 5 / 16); - } - - if (quantloc_1 < numPixels) { - int32_t *pix1 = quantPixels + 4 * quantloc_1; - pix1[0] += GifIMax(-pix1[0], r_err / 16); - pix1[1] += GifIMax(-pix1[1], g_err / 16); - pix1[2] += GifIMax(-pix1[2], b_err / 16); - } - } - } - - // Copy the palettized result to the output buffer - for (int ii = 0; ii < numPixels * 4; ++ii) { - outFrame[ii] = (uint8_t) quantPixels[ii]; - } - - GIF_TEMP_FREE(quantPixels); -} - -// Picks palette colors for the image using simple thresholding, no dithering -void GifThresholdImage(const uint8_t *lastFrame, const uint8_t *nextFrame, uint8_t *outFrame, - uint32_t width, uint32_t height, GifPalette *pPal) { - uint32_t numPixels = width * height; - for (uint32_t ii = 0; ii < numPixels; ++ii) { - // if a previous color is available, and it matches the current color, - // set the pixel to transparent - if (lastFrame && - lastFrame[0] == nextFrame[0] && - lastFrame[1] == nextFrame[1] && - lastFrame[2] == nextFrame[2]) { - outFrame[0] = lastFrame[0]; - outFrame[1] = lastFrame[1]; - outFrame[2] = lastFrame[2]; - outFrame[3] = kGifTransIndex; - } else { - // palettize the pixel - int32_t bestDiff = 1000000; - int32_t bestInd = 1; - GifGetClosestPaletteColor(pPal, nextFrame[0], nextFrame[1], nextFrame[2], &bestInd, - &bestDiff, 1); - - // Write the resulting color to the output buffer - outFrame[0] = pPal->r[bestInd]; - outFrame[1] = pPal->g[bestInd]; - outFrame[2] = pPal->b[bestInd]; - outFrame[3] = (uint8_t) bestInd; - } - - if (lastFrame) lastFrame += 4; - outFrame += 4; - nextFrame += 4; - } -} - -// Simple structure to write out the LZW-compressed portion of the image -// one bit at a time -typedef struct { - uint8_t bitIndex; // how many bits in the partial byte written so far - uint8_t byte; // current partial byte - - uint32_t chunkIndex; - uint8_t chunk[256]; // bytes are written in here until we have 256 of them, then written to the file -} GifBitStatus; - -// insert a single bit -void GifWriteBit(GifBitStatus *stat, uint32_t bit) { - bit = bit & 1; - bit = bit << stat->bitIndex; - stat->byte |= bit; - - ++stat->bitIndex; - if (stat->bitIndex > 7) { - // move the newly-finished byte to the chunk buffer - stat->chunk[stat->chunkIndex++] = stat->byte; - // and start a new byte - stat->bitIndex = 0; - stat->byte = 0; - } -} - -// write all bytes so far to the file -void GifWriteChunk(FILE *f, GifBitStatus *stat) { - fputc((int) stat->chunkIndex, f); - fwrite(stat->chunk, 1, stat->chunkIndex, f); - - stat->bitIndex = 0; - stat->byte = 0; - stat->chunkIndex = 0; -} - -void GifWriteCode(FILE *f, GifBitStatus *stat, uint32_t code, uint32_t length) { - for (uint32_t ii = 0; ii < length; ++ii) { - GifWriteBit(stat, code); - code = code >> 1; - - if (stat->chunkIndex == 255) { - GifWriteChunk(f, stat); - } - } -} - -// The LZW dictionary is a 256-ary tree constructed as the file is encoded, -// this is one node -typedef struct { - uint16_t m_next[256]; -} GifLzwNode; - -// write a 256-color (8-bit) image palette to the file -void GifWritePalette(const GifPalette *pPal, FILE *f) { - fputc(0, f); // first color: transparency - fputc(0, f); - fputc(0, f); - - for (int ii = 1; ii < (1 << pPal->bitDepth); ++ii) { - uint32_t r = pPal->r[ii]; - uint32_t g = pPal->g[ii]; - uint32_t b = pPal->b[ii]; - - fputc((int) r, f); - fputc((int) g, f); - fputc((int) b, f); - } -} - -// write the image header, LZW-compress and write out the image -void GifWriteLzwImage(FILE *f, uint8_t *image, uint32_t left, uint32_t top, uint32_t width, - uint32_t height, uint32_t delay, GifPalette *pPal) { - // graphics control extension - fputc(0x21, f); - fputc(0xf9, f); - fputc(0x04, f); - fputc(0x05, f); // leave prev frame in place, this frame has transparency - fputc(delay & 0xff, f); - fputc((delay >> 8) & 0xff, f); - fputc(kGifTransIndex, f); // transparent color index - fputc(0, f); - - fputc(0x2c, f); // image descriptor block - - fputc(left & 0xff, f); // corner of image in canvas space - fputc((left >> 8) & 0xff, f); - fputc(top & 0xff, f); - fputc((top >> 8) & 0xff, f); - - fputc(width & 0xff, f); // width and height of image - fputc((width >> 8) & 0xff, f); - fputc(height & 0xff, f); - fputc((height >> 8) & 0xff, f); - - //fputc(0, f); // no local color table, no transparency - //fputc(0x80, f); // no local color table, but transparency - - fputc(0x80 + pPal->bitDepth - 1, f); // local color table present, 2 ^ bitDepth entries - GifWritePalette(pPal, f); - - const int minCodeSize = pPal->bitDepth; - const uint32_t clearCode = 1 << pPal->bitDepth; - - fputc(minCodeSize, f); // min code size 8 bits - - GifLzwNode *codetree = (GifLzwNode *) GIF_TEMP_MALLOC(sizeof(GifLzwNode) * 4096); - - memset(codetree, 0, sizeof(GifLzwNode) * 4096); - int32_t curCode = -1; - uint32_t codeSize = (uint32_t) minCodeSize + 1; - uint32_t maxCode = clearCode + 1; - - GifBitStatus stat; - stat.byte = 0; - stat.bitIndex = 0; - stat.chunkIndex = 0; - - GifWriteCode(f, &stat, clearCode, codeSize); // start with a fresh LZW dictionary - - for (uint32_t yy = 0; yy < height; ++yy) { - for (uint32_t xx = 0; xx < width; ++xx) { -#ifdef GIF_FLIP_VERT - // bottom-left origin image (such as an OpenGL capture) - uint8_t nextValue = image[((height-1-yy)*width+xx)*4+3]; -#else - // top-left origin - uint8_t nextValue = image[(yy * width + xx) * 4 + 3]; -#endif - - // "loser mode" - no compression, every single code is followed immediately by a clear - //WriteCode( f, stat, nextValue, codeSize ); - //WriteCode( f, stat, 256, codeSize ); - - if (curCode < 0) { - // first value in a new run - curCode = nextValue; - } else if (codetree[curCode].m_next[nextValue]) { - // current run already in the dictionary - curCode = codetree[curCode].m_next[nextValue]; - } else { - // finish the current run, write a code - GifWriteCode(f, &stat, (uint32_t) curCode, codeSize); - - // insert the new run into the dictionary - codetree[curCode].m_next[nextValue] = (uint16_t)++ - maxCode; - - if (maxCode >= (1ul << codeSize)) { - // dictionary entry count has broken a size barrier, - // we need more bits for codes - codeSize++; - } - if (maxCode == 4095) { - // the dictionary is full, clear it out and begin anew - GifWriteCode(f, &stat, clearCode, codeSize); // clear tree - - memset(codetree, 0, sizeof(GifLzwNode) * 4096); - codeSize = (uint32_t)(minCodeSize + 1); - maxCode = clearCode + 1; - } - - curCode = nextValue; - } - } - } - - // compression footer - GifWriteCode(f, &stat, (uint32_t) curCode, codeSize); - GifWriteCode(f, &stat, clearCode, codeSize); - GifWriteCode(f, &stat, clearCode + 1, (uint32_t) minCodeSize + 1); - - // write out the last partial chunk - while (stat.bitIndex) GifWriteBit(&stat, 0); - if (stat.chunkIndex) GifWriteChunk(f, &stat); - - fputc(0, f); // image block terminator - - GIF_TEMP_FREE(codetree); -} - -typedef struct { - FILE *f; - uint8_t *oldImage; - bool firstFrame; -} GifWriter; - -// Creates a gif file. -// The input GIFWriter is assumed to be uninitialized. -// The delay value is the time between frames in hundredths of a second - note that not all viewers pay much attention to this value. -bool -GifBegin(GifWriter *writer, const char *filename, uint32_t width, uint32_t height, uint32_t delay, - int32_t bitDepth = 8, bool dither = false) { - (void) bitDepth; - (void) dither; // Mute "Unused argument" warnings -#if defined(_MSC_VER) && (_MSC_VER >= 1400) - writer->f = 0; - fopen_s(&writer->f, filename, "wb"); -#else - writer->f = fopen(filename, "wb"); -#endif - if (!writer->f) return false; - - writer->firstFrame = true; - - // allocate - writer->oldImage = (uint8_t *) GIF_MALLOC(width * height * 4); - - fputs("GIF89a", writer->f); - - // screen descriptor - fputc(width & 0xff, writer->f); - fputc((width >> 8) & 0xff, writer->f); - fputc(height & 0xff, writer->f); - fputc((height >> 8) & 0xff, writer->f); - - fputc(0xf0, writer->f); // there is an unsorted global color table of 2 entries - fputc(0, writer->f); // background color - fputc(0, writer->f); // pixels are square (we need to specify this because it's 1989) - - // now the "global" palette (really just a dummy palette) - // color 0: black - fputc(0, writer->f); - fputc(0, writer->f); - fputc(0, writer->f); - // color 1: also black - fputc(0, writer->f); - fputc(0, writer->f); - fputc(0, writer->f); - - if (delay != 0) { - // animation header - fputc(0x21, writer->f); // extension - fputc(0xff, writer->f); // application specific - fputc(11, writer->f); // length 11 - fputs("NETSCAPE2.0", writer->f); // yes, really - fputc(3, writer->f); // 3 bytes of NETSCAPE2.0 data - - fputc(1, writer->f); // JUST BECAUSE - fputc(0, writer->f); // loop infinitely (byte 0) - fputc(0, writer->f); // loop infinitely (byte 1) - - fputc(0, writer->f); // block terminator - } - - return true; -} - -// Writes out a new frame to a GIF in progress. -// The GIFWriter should have been created by GIFBegin. -// AFAIK, it is legal to use different bit depths for different frames of an image - -// this may be handy to save bits in animations that don't change much. -bool GifWriteFrame(GifWriter *writer, const uint8_t *image, uint32_t width, uint32_t height, - uint32_t delay, int bitDepth = 8, bool dither = false) { - if (!writer->f) return false; - - const uint8_t *oldImage = writer->firstFrame ? NULL : writer->oldImage; - writer->firstFrame = false; - - GifPalette pal; - GifMakePalette((dither ? NULL : oldImage), image, width, height, bitDepth, dither, &pal); - - if (dither) - GifDitherImage(oldImage, image, writer->oldImage, width, height, &pal); - else - GifThresholdImage(oldImage, image, writer->oldImage, width, height, &pal); - - GifWriteLzwImage(writer->f, writer->oldImage, 0, 0, width, height, delay, &pal); - - return true; -} - -// Writes the EOF code, closes the file handle, and frees temp memory used by a GIF. -// Many if not most viewers will still display a GIF properly if the EOF code is missing, -// but it's still a good idea to write it out. -bool GifEnd(GifWriter *writer) { - if (!writer->f) return false; - - fputc(0x3b, writer->f); // end of file - fclose(writer->f); - GIF_FREE(writer->oldImage); - - writer->f = NULL; - writer->oldImage = NULL; - - return true; -} - -class GifBuilder { -public: - explicit GifBuilder(const std::string &fileName, const uint32_t width, - const uint32_t height, const int bgColor = 0xffffffff, - const uint32_t delay = 2, const int32_t bitDepth = 8, - const bool dither = false) { - GifBegin(&handle, fileName.c_str(), width, height, delay, bitDepth, dither); - bgColorR = (uint8_t)((bgColor & 0xff0000) >> 16); - bgColorG = (uint8_t)((bgColor & 0x00ff00) >> 8); - bgColorB = (uint8_t)((bgColor & 0x0000ff)); - } - - ~GifBuilder() { - GifEnd(&handle); - } - - void - addFrame(uint32_t *buffer, size_t width, size_t height, size_t bytesPerLine, bool transparent, - uint32_t delay = 2, int32_t bitDepth = 8, bool dither = false) { - if (!transparent) { - argbToRGBA(buffer, height, bytesPerLine); - } - GifWriteFrame(&handle, - reinterpret_cast(buffer), - width, - height, - delay, bitDepth, dither); - } - -private: - void argbToRGBA(uint32_t *bufferMP, size_t height, size_t bytesPerLine) { - uint8_t *buffer = reinterpret_cast(bufferMP); - uint32_t totalBytes = height * bytesPerLine; - - for (uint32_t i = 0; i < totalBytes; i += 4) { - unsigned char a = buffer[i + 3]; - // compute only if alpha is non zero - if (a) { - unsigned char r = buffer[i + 2]; - unsigned char g = buffer[i + 1]; - unsigned char b = buffer[i]; - - if (a != 255) { //un premultiply - unsigned char r2 = (unsigned char) ((float) bgColorR * - ((float) (255 - a) / 255)); - unsigned char g2 = (unsigned char) ((float) bgColorG * - ((float) (255 - a) / 255)); - unsigned char b2 = (unsigned char) ((float) bgColorB * - ((float) (255 - a) / 255)); - buffer[i + 2] = r + r2; - buffer[i + 1] = g + g2; - buffer[i] = b + b2; - - } else { - // only swizzle r and b - buffer[i + 2] = r; - buffer[i + 1] = g; - buffer[i] = b; - } - } else { - buffer[i] = bgColorB; - buffer[i + 1] = bgColorG; - buffer[i + 2] = bgColorR; - } - } - } - - GifWriter handle; - uint8_t bgColorR, bgColorG, bgColorB; -}; - -#endif diff --git a/libfenrir/src/main/jni/animation/libyuv/include/libyuv/cpu_id.h b/libfenrir/src/main/jni/animation/libyuv/include/libyuv/cpu_id.h index 8a295658d..57a6a12e9 100644 --- a/libfenrir/src/main/jni/animation/libyuv/include/libyuv/cpu_id.h +++ b/libfenrir/src/main/jni/animation/libyuv/include/libyuv/cpu_id.h @@ -23,49 +23,50 @@ static const int kCpuInitialized = 0x1; // These flags are only valid on Arm processors. static const int kCpuHasARM = 0x2; -static const int kCpuHasNEON = 0x4; -// Leave a gap to avoid setting kCpuHasX86. -static const int kCpuHasNeonDotProd = 0x10; -static const int kCpuHasNeonI8MM = 0x20; -static const int kCpuHasSVE = 0x40; -static const int kCpuHasSVE2 = 0x80; -static const int kCpuHasSME = 0x100; +static const int kCpuHasNEON = 0x100; +static const int kCpuHasNeonDotProd = 0x200; +static const int kCpuHasNeonI8MM = 0x400; +static const int kCpuHasSVE = 0x800; +static const int kCpuHasSVE2 = 0x1000; +static const int kCpuHasSME = 0x2000; + +// These flags are only valid on RISCV processors. +static const int kCpuHasRISCV = 0x4; +static const int kCpuHasRVV = 0x100; +static const int kCpuHasRVVZVFH = 0x200; // These flags are only valid on x86 processors. static const int kCpuHasX86 = 0x8; -static const int kCpuHasSSE2 = 0x10; -static const int kCpuHasSSSE3 = 0x20; -static const int kCpuHasSSE41 = 0x40; -static const int kCpuHasSSE42 = 0x80; -static const int kCpuHasAVX = 0x100; -static const int kCpuHasAVX2 = 0x200; -static const int kCpuHasERMS = 0x400; -static const int kCpuHasFMA3 = 0x800; -static const int kCpuHasF16C = 0x1000; -static const int kCpuHasAVX512BW = 0x2000; -static const int kCpuHasAVX512VL = 0x4000; -static const int kCpuHasAVX512VNNI = 0x8000; -static const int kCpuHasAVX512VBMI = 0x10000; -static const int kCpuHasAVX512VBMI2 = 0x20000; -static const int kCpuHasAVX512VBITALG = 0x40000; -static const int kCpuHasAVX10 = 0x80000; -static const int kCpuHasAVXVNNI = 0x100000; -static const int kCpuHasAVXVNNIINT8 = 0x200000; -static const int kCpuHasAMXINT8 = 0x400000; +static const int kCpuHasSSE2 = 0x100; +static const int kCpuHasSSSE3 = 0x200; +static const int kCpuHasSSE41 = 0x400; +static const int kCpuHasSSE42 = 0x800; +static const int kCpuHasAVX = 0x1000; +static const int kCpuHasAVX2 = 0x2000; +static const int kCpuHasERMS = 0x4000; +static const int kCpuHasFSMR = 0x8000; +static const int kCpuHasFMA3 = 0x10000; +static const int kCpuHasF16C = 0x20000; +static const int kCpuHasAVX512BW = 0x40000; +static const int kCpuHasAVX512VL = 0x80000; +static const int kCpuHasAVX512VNNI = 0x100000; +static const int kCpuHasAVX512VBMI = 0x200000; +static const int kCpuHasAVX512VBMI2 = 0x400000; +static const int kCpuHasAVX512VBITALG = 0x800000; +static const int kCpuHasAVX10 = 0x1000000; +static const int kCpuHasAVXVNNI = 0x2000000; +static const int kCpuHasAVXVNNIINT8 = 0x4000000; +static const int kCpuHasAMXINT8 = 0x8000000; // These flags are only valid on MIPS processors. -static const int kCpuHasMIPS = 0x800000; -static const int kCpuHasMSA = 0x1000000; +static const int kCpuHasMIPS = 0x10; +static const int kCpuHasMSA = 0x100; // These flags are only valid on LOONGARCH processors. -static const int kCpuHasLOONGARCH = 0x2000000; -static const int kCpuHasLSX = 0x4000000; -static const int kCpuHasLASX = 0x8000000; +static const int kCpuHasLOONGARCH = 0x20; +static const int kCpuHasLSX = 0x100; +static const int kCpuHasLASX = 0x200; -// These flags are only valid on RISCV processors. -static const int kCpuHasRISCV = 0x10000000; -static const int kCpuHasRVV = 0x20000000; -static const int kCpuHasRVVZVFH = 0x40000000; // Optional init function. TestCpuFlag does an auto-init. // Returns cpu_info flags. diff --git a/libfenrir/src/main/jni/animation/libyuv/include/libyuv/row.h b/libfenrir/src/main/jni/animation/libyuv/include/libyuv/row.h index 1eda17ad4..70f89134c 100644 --- a/libfenrir/src/main/jni/animation/libyuv/include/libyuv/row.h +++ b/libfenrir/src/main/jni/animation/libyuv/include/libyuv/row.h @@ -265,6 +265,7 @@ extern "C" { #define HAS_SPLITARGBROW_SSE2 #define HAS_SPLITARGBROW_SSSE3 #define HAS_SPLITRGBROW_SSSE3 +#define HAS_SPLITRGBROW_SSE41 #define HAS_SPLITXRGBROW_SSE2 #define HAS_SPLITXRGBROW_SSSE3 #define HAS_SWAPUVROW_SSSE3 @@ -330,6 +331,7 @@ extern "C" { #define HAS_P410TOAR30ROW_AVX2 #define HAS_P410TOARGBROW_AVX2 #define HAS_RGBATOYJROW_AVX2 +#define HAS_SPLITRGBROW_AVX2 #define HAS_SPLITARGBROW_AVX2 #define HAS_SPLITUVROW_16_AVX2 #define HAS_SPLITXRGBROW_AVX2 @@ -353,6 +355,7 @@ extern "C" { // TODO(b/42280744): re-enable HAS_ARGBTORGB24ROW_AVX512VBMI. #if !defined(LIBYUV_DISABLE_X86) && \ (defined(__x86_64__) || defined(__i386__)) && defined(CLANG_HAS_AVX512) +#define HAS_COPYROW_AVX512BW #define HAS_ARGBTORGB24ROW_AVX512VBMI #define HAS_CONVERT16TO8ROW_AVX512BW #define HAS_MERGEUVROW_AVX512BW @@ -548,14 +551,25 @@ extern "C" { #define HAS_AYUVTOUVROW_SVE2 #define HAS_AYUVTOVUROW_SVE2 #define HAS_BGRATOUVROW_SVE2 +#define HAS_DIVIDEROW_16_SVE2 +#define HAS_HALFFLOATROW_SVE2 +#define HAS_I210TOARGBROW_SVE2 #define HAS_I400TOARGBROW_SVE2 #define HAS_I422ALPHATOARGBROW_SVE2 +#define HAS_I422TOARGB1555ROW_SVE2 +#define HAS_I422TOARGB4444ROW_SVE2 #define HAS_I422TOARGBROW_SVE2 +#define HAS_I422TORGB24ROW_SVE2 +#define HAS_I422TORGB565ROW_SVE2 #define HAS_I422TORGBAROW_SVE2 #define HAS_I444ALPHATOARGBROW_SVE2 #define HAS_I444TOARGBROW_SVE2 #define HAS_NV12TOARGBROW_SVE2 #define HAS_NV21TOARGBROW_SVE2 +#define HAS_P210TOAR30ROW_SVE2 +#define HAS_P210TOARGBROW_SVE2 +#define HAS_P410TOAR30ROW_SVE2 +#define HAS_P410TOARGBROW_SVE2 #define HAS_RAWTOARGBROW_SVE2 #define HAS_RAWTORGB24ROW_SVE2 #define HAS_RAWTORGBAROW_SVE2 @@ -565,6 +579,13 @@ extern "C" { #define HAS_YUY2TOARGBROW_SVE2 #endif +// The following are available on AArch64 SME platforms: +#if !defined(LIBYUV_DISABLE_SME) && defined(CLANG_HAS_SME) && \ + defined(__aarch64__) +#define HAS_I422TOARGBROW_SME +#define HAS_I444TOARGBROW_SME +#endif + // The following are available on AArch64 platforms: #if !defined(LIBYUV_DISABLE_NEON) && defined(__aarch64__) #define HAS_GAUSSCOL_F32_NEON @@ -1062,6 +1083,12 @@ void I444ToARGBRow_SVE2(const uint8_t* src_y, uint8_t* dst_argb, const struct YuvConstants* yuvconstants, int width); +void I444ToARGBRow_SME(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width); void I444ToRGB24Row_NEON(const uint8_t* src_y, const uint8_t* src_u, const uint8_t* src_v, @@ -1074,6 +1101,12 @@ void I210ToARGBRow_NEON(const uint16_t* src_y, uint8_t* rgb_buf, const struct YuvConstants* yuvconstants, int width); +void I210ToARGBRow_SVE2(const uint16_t* src_y, + const uint16_t* src_u, + const uint16_t* src_v, + uint8_t* rgb_buf, + const struct YuvConstants* yuvconstants, + int width); void I410ToARGBRow_NEON(const uint16_t* src_y, const uint16_t* src_u, const uint16_t* src_v, @@ -1116,6 +1149,12 @@ void I422ToARGBRow_SVE2(const uint8_t* src_y, uint8_t* dst_argb, const struct YuvConstants* yuvconstants, int width); +void I422ToARGBRow_SME(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width); void I422ToAR30Row_NEON(const uint8_t* src_y, const uint8_t* src_u, const uint8_t* src_v, @@ -1168,24 +1207,48 @@ void I422ToRGB24Row_NEON(const uint8_t* src_y, uint8_t* dst_rgb24, const struct YuvConstants* yuvconstants, int width); +void I422ToRGB24Row_SVE2(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_rgb24, + const struct YuvConstants* yuvconstants, + int width); void I422ToRGB565Row_NEON(const uint8_t* src_y, const uint8_t* src_u, const uint8_t* src_v, uint8_t* dst_rgb565, const struct YuvConstants* yuvconstants, int width); +void I422ToRGB565Row_SVE2(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_rgb565, + const struct YuvConstants* yuvconstants, + int width); void I422ToARGB1555Row_NEON(const uint8_t* src_y, const uint8_t* src_u, const uint8_t* src_v, uint8_t* dst_argb1555, const struct YuvConstants* yuvconstants, int width); +void I422ToARGB1555Row_SVE2(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_argb1555, + const struct YuvConstants* yuvconstants, + int width); void I422ToARGB4444Row_NEON(const uint8_t* src_y, const uint8_t* src_u, const uint8_t* src_v, uint8_t* dst_argb4444, const struct YuvConstants* yuvconstants, int width); +void I422ToARGB4444Row_SVE2(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_argb4444, + const struct YuvConstants* yuvconstants, + int width); void NV12ToARGBRow_NEON(const uint8_t* src_y, const uint8_t* src_uv, uint8_t* dst_argb, @@ -2743,6 +2806,16 @@ void SplitRGBRow_SSSE3(const uint8_t* src_rgb, uint8_t* dst_g, uint8_t* dst_b, int width); +void SplitRGBRow_SSE41(const uint8_t* src_rgb, + uint8_t* dst_r, + uint8_t* dst_g, + uint8_t* dst_b, + int width); +void SplitRGBRow_AVX2(const uint8_t* src_rgb, + uint8_t* dst_r, + uint8_t* dst_g, + uint8_t* dst_b, + int width); void SplitRGBRow_NEON(const uint8_t* src_rgb, uint8_t* dst_r, uint8_t* dst_g, @@ -2758,6 +2831,16 @@ void SplitRGBRow_Any_SSSE3(const uint8_t* src_ptr, uint8_t* dst_g, uint8_t* dst_b, int width); +void SplitRGBRow_Any_SSE41(const uint8_t* src_ptr, + uint8_t* dst_r, + uint8_t* dst_g, + uint8_t* dst_b, + int width); +void SplitRGBRow_Any_AVX2(const uint8_t* src_ptr, + uint8_t* dst_r, + uint8_t* dst_g, + uint8_t* dst_b, + int width); void SplitRGBRow_Any_NEON(const uint8_t* src_ptr, uint8_t* dst_r, uint8_t* dst_g, @@ -3255,6 +3338,10 @@ void DivideRow_16_NEON(const uint16_t* src_y, uint16_t* dst_y, int scale, int width); +void DivideRow_16_SVE2(const uint16_t* src_y, + uint16_t* dst_y, + int scale, + int width); void DivideRow_16_Any_NEON(const uint16_t* src_ptr, uint16_t* dst_ptr, int scale, @@ -3320,6 +3407,7 @@ void Convert16To8Row_Any_NEON(const uint16_t* src_ptr, void CopyRow_SSE2(const uint8_t* src, uint8_t* dst, int width); void CopyRow_AVX(const uint8_t* src, uint8_t* dst, int width); +void CopyRow_AVX512BW(const uint8_t* src, uint8_t* dst, int width); void CopyRow_ERMS(const uint8_t* src, uint8_t* dst, int width); void CopyRow_NEON(const uint8_t* src, uint8_t* dst, int width); void CopyRow_MIPS(const uint8_t* src, uint8_t* dst, int count); @@ -3327,6 +3415,7 @@ void CopyRow_RVV(const uint8_t* src, uint8_t* dst, int count); void CopyRow_C(const uint8_t* src, uint8_t* dst, int count); void CopyRow_Any_SSE2(const uint8_t* src_ptr, uint8_t* dst_ptr, int width); void CopyRow_Any_AVX(const uint8_t* src_ptr, uint8_t* dst_ptr, int width); +void CopyRow_Any_AVX512BW(const uint8_t* src_ptr, uint8_t* dst_ptr, int width); void CopyRow_Any_NEON(const uint8_t* src_ptr, uint8_t* dst_ptr, int width); void CopyRow_16_C(const uint16_t* src, uint16_t* dst, int count); @@ -5323,21 +5412,41 @@ void P210ToARGBRow_NEON(const uint16_t* y_buf, uint8_t* dst_argb, const struct YuvConstants* yuvconstants, int width); +void P210ToARGBRow_SVE2(const uint16_t* y_buf, + const uint16_t* uv_buf, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width); void P410ToARGBRow_NEON(const uint16_t* y_buf, const uint16_t* uv_buf, uint8_t* dst_argb, const struct YuvConstants* yuvconstants, int width); +void P410ToARGBRow_SVE2(const uint16_t* y_buf, + const uint16_t* uv_buf, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width); void P210ToAR30Row_NEON(const uint16_t* y_buf, const uint16_t* uv_buf, uint8_t* dst_ar30, const struct YuvConstants* yuvconstants, int width); +void P210ToAR30Row_SVE2(const uint16_t* y_buf, + const uint16_t* uv_buf, + uint8_t* dst_ar30, + const struct YuvConstants* yuvconstants, + int width); void P410ToAR30Row_NEON(const uint16_t* y_buf, const uint16_t* uv_buf, uint8_t* dst_ar30, const struct YuvConstants* yuvconstants, int width); +void P410ToAR30Row_SVE2(const uint16_t* y_buf, + const uint16_t* uv_buf, + uint8_t* dst_ar30, + const struct YuvConstants* yuvconstants, + int width); void P210ToARGBRow_Any_NEON(const uint16_t* y_buf, const uint16_t* uv_buf, uint8_t* dst_argb, @@ -6557,6 +6666,10 @@ void HalfFloatRow_Any_NEON(const uint16_t* src_ptr, uint16_t* dst_ptr, float param, int width); +void HalfFloatRow_SVE2(const uint16_t* src, + uint16_t* dst, + float scale, + int width); void HalfFloat1Row_NEON(const uint16_t* src, uint16_t* dst, float scale, @@ -6565,6 +6678,10 @@ void HalfFloat1Row_Any_NEON(const uint16_t* src_ptr, uint16_t* dst_ptr, float param, int width); +void HalfFloat1Row_SVE2(const uint16_t* src, + uint16_t* dst, + float scale, + int width); void HalfFloatRow_MSA(const uint16_t* src, uint16_t* dst, float scale, diff --git a/libfenrir/src/main/jni/animation/libyuv/include/libyuv/scale_row.h b/libfenrir/src/main/jni/animation/libyuv/include/libyuv/scale_row.h index a8ec4776a..101ccbf84 100644 --- a/libfenrir/src/main/jni/animation/libyuv/include/libyuv/scale_row.h +++ b/libfenrir/src/main/jni/animation/libyuv/include/libyuv/scale_row.h @@ -116,6 +116,15 @@ extern "C" { #define HAS_SCALEUVROWUP2_BILINEAR_16_NEON #endif +// The following are available on AArch64 SME platforms: +#if !defined(LIBYUV_DISABLE_SME) && defined(CLANG_HAS_SME) && \ + defined(__aarch64__) +#define HAS_SCALEROWDOWN2_SME +#define HAS_SCALEUVROWDOWN2_SME +#define HAS_SCALEUVROWDOWN2LINEAR_SME +#define HAS_SCALEUVROWDOWN2BOX_SME +#endif + #if !defined(LIBYUV_DISABLE_MSA) && defined(__mips_msa) #define HAS_SCALEADDROW_MSA #define HAS_SCALEARGBCOLS_MSA @@ -1148,14 +1157,26 @@ void ScaleUVRowDown2_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, uint8_t* dst, int dst_width); +void ScaleUVRowDown2_SME(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width); void ScaleUVRowDown2Linear_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, uint8_t* dst_uv, int dst_width); +void ScaleUVRowDown2Linear_SME(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst_uv, + int dst_width); void ScaleUVRowDown2Box_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, uint8_t* dst, int dst_width); +void ScaleUVRowDown2Box_SME(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width); void ScaleUVRowDown2_MSA(const uint8_t* src_ptr, ptrdiff_t src_stride, uint8_t* dst_uv, @@ -1397,21 +1418,31 @@ void ScaleUVRowUp2_Bilinear_16_Any_NEON(const uint16_t* src_ptr, int dst_width); // ScaleRowDown2Box also used by planar functions -// NEON downscalers with interpolation. - -// Note - not static due to reuse in convert for 444 to 420. +// NEON/SME downscalers with interpolation. void ScaleRowDown2_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, uint8_t* dst, int dst_width); +void ScaleRowDown2_SME(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width); void ScaleRowDown2Linear_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, uint8_t* dst, int dst_width); +void ScaleRowDown2Linear_SME(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width); void ScaleRowDown2Box_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, uint8_t* dst, int dst_width); +void ScaleRowDown2Box_SME(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width); void ScaleRowDown4_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, diff --git a/libfenrir/src/main/jni/animation/libyuv/include/libyuv/version.h b/libfenrir/src/main/jni/animation/libyuv/include/libyuv/version.h index 23d3ad67a..1e01bac5a 100644 --- a/libfenrir/src/main/jni/animation/libyuv/include/libyuv/version.h +++ b/libfenrir/src/main/jni/animation/libyuv/include/libyuv/version.h @@ -11,6 +11,6 @@ #ifndef INCLUDE_LIBYUV_VERSION_H_ #define INCLUDE_LIBYUV_VERSION_H_ -#define LIBYUV_VERSION 1897 +#define LIBYUV_VERSION 1898 #endif // INCLUDE_LIBYUV_VERSION_H_ diff --git a/libfenrir/src/main/jni/animation/libyuv/source/convert_argb.cc b/libfenrir/src/main/jni/animation/libyuv/source/convert_argb.cc index a77358df8..7a2f7813f 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/convert_argb.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/convert_argb.cc @@ -114,6 +114,11 @@ int I420ToARGBMatrix(const uint8_t* src_y, I422ToARGBRow = I422ToARGBRow_SVE2; } #endif +#if defined(HAS_I422TOARGBROW_SME) + if (TestCpuFlag(kCpuHasSME)) { + I422ToARGBRow = I422ToARGBRow_SME; + } +#endif #if defined(HAS_I422TOARGBROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToARGBRow = I422ToARGBRow_Any_MSA; @@ -373,6 +378,11 @@ int I422ToARGBMatrix(const uint8_t* src_y, I422ToARGBRow = I422ToARGBRow_SVE2; } #endif +#if defined(HAS_I422TOARGBROW_SME) + if (TestCpuFlag(kCpuHasSME)) { + I422ToARGBRow = I422ToARGBRow_SME; + } +#endif #if defined(HAS_I422TOARGBROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToARGBRow = I422ToARGBRow_Any_MSA; @@ -621,6 +631,11 @@ int I444ToARGBMatrix(const uint8_t* src_y, I444ToARGBRow = I444ToARGBRow_SVE2; } #endif +#if defined(HAS_I444TOARGBROW_SME) + if (TestCpuFlag(kCpuHasSME)) { + I444ToARGBRow = I444ToARGBRow_SME; + } +#endif #if defined(HAS_I444TOARGBROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I444ToARGBRow = I444ToARGBRow_Any_MSA; @@ -1429,6 +1444,11 @@ int I010ToARGBMatrix(const uint16_t* src_y, } } #endif +#if defined(HAS_I210TOARGBROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + I210ToARGBRow = I210ToARGBRow_SVE2; + } +#endif #if defined(HAS_I210TOARGBROW_AVX2) if (TestCpuFlag(kCpuHasAVX2)) { I210ToARGBRow = I210ToARGBRow_Any_AVX2; @@ -1665,6 +1685,11 @@ int I210ToARGBMatrix(const uint16_t* src_y, } } #endif +#if defined(HAS_I210TOARGBROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + I210ToARGBRow = I210ToARGBRow_SVE2; + } +#endif #if defined(HAS_I210TOARGBROW_AVX2) if (TestCpuFlag(kCpuHasAVX2)) { I210ToARGBRow = I210ToARGBRow_Any_AVX2; @@ -1899,6 +1924,11 @@ int P010ToARGBMatrix(const uint16_t* src_y, P210ToARGBRow = P210ToARGBRow_NEON; } } +#endif +#if defined(HAS_P210TOARGBROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + P210ToARGBRow = P210ToARGBRow_SVE2; + } #endif for (y = 0; y < height; ++y) { P210ToARGBRow(src_y, src_uv, dst_argb, yuvconstants, width); @@ -1958,6 +1988,11 @@ int P210ToARGBMatrix(const uint16_t* src_y, P210ToARGBRow = P210ToARGBRow_NEON; } } +#endif +#if defined(HAS_P210TOARGBROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + P210ToARGBRow = P210ToARGBRow_SVE2; + } #endif for (y = 0; y < height; ++y) { P210ToARGBRow(src_y, src_uv, dst_argb, yuvconstants, width); @@ -2015,6 +2050,11 @@ int P010ToAR30Matrix(const uint16_t* src_y, P210ToAR30Row = P210ToAR30Row_NEON; } } +#endif +#if defined(HAS_P210TOAR30ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + P210ToAR30Row = P210ToAR30Row_SVE2; + } #endif for (y = 0; y < height; ++y) { P210ToAR30Row(src_y, src_uv, dst_ar30, yuvconstants, width); @@ -2074,6 +2114,11 @@ int P210ToAR30Matrix(const uint16_t* src_y, P210ToAR30Row = P210ToAR30Row_NEON; } } +#endif +#if defined(HAS_P210TOAR30ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + P210ToAR30Row = P210ToAR30Row_SVE2; + } #endif for (y = 0; y < height; ++y) { P210ToAR30Row(src_y, src_uv, dst_ar30, yuvconstants, width); @@ -5362,6 +5407,11 @@ int I420ToRGB24Matrix(const uint8_t* src_y, } } #endif +#if defined(HAS_I422TORGB24ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + I422ToRGB24Row = I422ToRGB24Row_SVE2; + } +#endif #if defined(HAS_I422TORGB24ROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToRGB24Row = I422ToRGB24Row_Any_MSA; @@ -5564,6 +5614,11 @@ int I422ToRGB24Matrix(const uint8_t* src_y, } } #endif +#if defined(HAS_I422TORGB24ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + I422ToRGB24Row = I422ToRGB24Row_SVE2; + } +#endif #if defined(HAS_I422TORGB24ROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToRGB24Row = I422ToRGB24Row_Any_MSA; @@ -5691,6 +5746,11 @@ int I420ToARGB1555(const uint8_t* src_y, } } #endif +#if defined(HAS_I422TOARGB1555ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + I422ToARGB1555Row = I422ToARGB1555Row_SVE2; + } +#endif #if defined(HAS_I422TOARGB1555ROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToARGB1555Row = I422ToARGB1555Row_Any_MSA; @@ -5780,6 +5840,11 @@ int I420ToARGB4444(const uint8_t* src_y, } } #endif +#if defined(HAS_I422TOARGB4444ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + I422ToARGB4444Row = I422ToARGB4444Row_SVE2; + } +#endif #if defined(HAS_I422TOARGB4444ROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToARGB4444Row = I422ToARGB4444Row_Any_MSA; @@ -5870,6 +5935,11 @@ int I420ToRGB565Matrix(const uint8_t* src_y, } } #endif +#if defined(HAS_I422TORGB565ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + I422ToRGB565Row = I422ToRGB565Row_SVE2; + } +#endif #if defined(HAS_I422TORGB565ROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToRGB565Row = I422ToRGB565Row_Any_MSA; @@ -6010,6 +6080,11 @@ int I422ToRGB565Matrix(const uint8_t* src_y, } } #endif +#if defined(HAS_I422TORGB565ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + I422ToRGB565Row = I422ToRGB565Row_SVE2; + } +#endif #if defined(HAS_I422TORGB565ROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToRGB565Row = I422ToRGB565Row_Any_MSA; @@ -6138,6 +6213,11 @@ int I420ToRGB565Dither(const uint8_t* src_y, I422ToARGBRow = I422ToARGBRow_SVE2; } #endif +#if defined(HAS_I422TOARGBROW_SME) + if (TestCpuFlag(kCpuHasSME)) { + I422ToARGBRow = I422ToARGBRow_SME; + } +#endif #if defined(HAS_I422TOARGBROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToARGBRow = I422ToARGBRow_Any_MSA; @@ -6437,6 +6517,11 @@ static int I420ToARGBMatrixBilinear(const uint8_t* src_y, I444ToARGBRow = I444ToARGBRow_SVE2; } #endif +#if defined(HAS_I444TOARGBROW_SME) + if (TestCpuFlag(kCpuHasSME)) { + I444ToARGBRow = I444ToARGBRow_SME; + } +#endif #if defined(HAS_I444TOARGBROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I444ToARGBRow = I444ToARGBRow_Any_MSA; @@ -6589,6 +6674,11 @@ static int I422ToARGBMatrixLinear(const uint8_t* src_y, I444ToARGBRow = I444ToARGBRow_SVE2; } #endif +#if defined(HAS_I444TOARGBROW_SME) + if (TestCpuFlag(kCpuHasSME)) { + I444ToARGBRow = I444ToARGBRow_SME; + } +#endif #if defined(HAS_I444TOARGBROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I444ToARGBRow = I444ToARGBRow_Any_MSA; @@ -7986,6 +8076,11 @@ static int P010ToARGBMatrixBilinear(const uint16_t* src_y, } } #endif +#if defined(HAS_P410TOARGBROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + P410ToARGBRow = P410ToARGBRow_SVE2; + } +#endif #ifdef HAS_SCALEUVROWUP2_BILINEAR_16_SSE41 if (TestCpuFlag(kCpuHasSSE41)) { @@ -8087,6 +8182,11 @@ static int P210ToARGBMatrixLinear(const uint16_t* src_y, } } #endif +#if defined(HAS_P410TOARGBROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + P410ToARGBRow = P410ToARGBRow_SVE2; + } +#endif #ifdef HAS_SCALEUVROWUP2_LINEAR_16_SSE41 if (TestCpuFlag(kCpuHasSSE41)) { @@ -8174,6 +8274,11 @@ static int P010ToAR30MatrixBilinear(const uint16_t* src_y, } } #endif +#if defined(HAS_P410TOAR30ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + P410ToAR30Row = P410ToAR30Row_SVE2; + } +#endif #ifdef HAS_SCALEUVROWUP2_BILINEAR_16_SSE41 if (TestCpuFlag(kCpuHasSSE41)) { @@ -8275,6 +8380,11 @@ static int P210ToAR30MatrixLinear(const uint16_t* src_y, } } #endif +#if defined(HAS_P410TOAR30ROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + P410ToAR30Row = P410ToAR30Row_SVE2; + } +#endif #ifdef HAS_SCALEUVROWUP2_LINEAR_16_SSE41 if (TestCpuFlag(kCpuHasSSE41)) { diff --git a/libfenrir/src/main/jni/animation/libyuv/source/cpu_id.cc b/libfenrir/src/main/jni/animation/libyuv/source/cpu_id.cc index cd0112af4..48a350c6e 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/cpu_id.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/cpu_id.cc @@ -409,17 +409,20 @@ static SAFEBUFFERS int GetCpuFlags(void) { int cpu_info1[4] = {0, 0, 0, 0}; int cpu_info7[4] = {0, 0, 0, 0}; int cpu_einfo7[4] = {0, 0, 0, 0}; + int cpu_amdinfo21[4] = {0, 0, 0, 0}; CpuId(0, 0, cpu_info0); CpuId(1, 0, cpu_info1); if (cpu_info0[0] >= 7) { CpuId(7, 0, cpu_info7); CpuId(7, 1, cpu_einfo7); + CpuId(0x80000021, 0, cpu_amdinfo21); } cpu_info = kCpuHasX86 | ((cpu_info1[3] & 0x04000000) ? kCpuHasSSE2 : 0) | ((cpu_info1[2] & 0x00000200) ? kCpuHasSSSE3 : 0) | ((cpu_info1[2] & 0x00080000) ? kCpuHasSSE41 : 0) | ((cpu_info1[2] & 0x00100000) ? kCpuHasSSE42 : 0) | - ((cpu_info7[1] & 0x00000200) ? kCpuHasERMS : 0); + ((cpu_info7[1] & 0x00000200) ? kCpuHasERMS : 0) | + ((cpu_info7[3] & 0x00000010) ? kCpuHasFSMR : 0); // AVX requires OS saves YMM registers. if (((cpu_info1[2] & 0x1c000000) == 0x1c000000) && // AVX and OSXSave @@ -430,16 +433,18 @@ static SAFEBUFFERS int GetCpuFlags(void) { ((cpu_einfo7[0] & 0x00000010) ? kCpuHasAVXVNNI : 0) | ((cpu_einfo7[3] & 0x00000010) ? kCpuHasAVXVNNIINT8 : 0); + cpu_info |= ((cpu_amdinfo21[0] & 0x00008000) ? kCpuHasERMS : 0); + // Detect AVX512bw if ((GetXCR0() & 0xe0) == 0xe0) { - cpu_info |= (cpu_info7[1] & 0x40000000) ? kCpuHasAVX512BW : 0; - cpu_info |= (cpu_info7[1] & 0x80000000) ? kCpuHasAVX512VL : 0; - cpu_info |= (cpu_info7[2] & 0x00000002) ? kCpuHasAVX512VBMI : 0; - cpu_info |= (cpu_info7[2] & 0x00000040) ? kCpuHasAVX512VBMI2 : 0; - cpu_info |= (cpu_info7[2] & 0x00000800) ? kCpuHasAVX512VNNI : 0; - cpu_info |= (cpu_info7[2] & 0x00001000) ? kCpuHasAVX512VBITALG : 0; - cpu_info |= (cpu_einfo7[3] & 0x00080000) ? kCpuHasAVX10 : 0; - cpu_info |= (cpu_info7[3] & 0x02000000) ? kCpuHasAMXINT8 : 0; + cpu_info |= ((cpu_info7[1] & 0x40000000) ? kCpuHasAVX512BW : 0) | + ((cpu_info7[1] & 0x80000000) ? kCpuHasAVX512VL : 0) | + ((cpu_info7[2] & 0x00000002) ? kCpuHasAVX512VBMI : 0) | + ((cpu_info7[2] & 0x00000040) ? kCpuHasAVX512VBMI2 : 0) | + ((cpu_info7[2] & 0x00000800) ? kCpuHasAVX512VNNI : 0) | + ((cpu_info7[2] & 0x00001000) ? kCpuHasAVX512VBITALG : 0) | + ((cpu_einfo7[3] & 0x00080000) ? kCpuHasAVX10 : 0) | + ((cpu_info7[3] & 0x02000000) ? kCpuHasAMXINT8 : 0); } } #endif diff --git a/libfenrir/src/main/jni/animation/libyuv/source/planar_functions.cc b/libfenrir/src/main/jni/animation/libyuv/source/planar_functions.cc index 7c50599ad..be67a1ded 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/planar_functions.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/planar_functions.cc @@ -62,6 +62,11 @@ void CopyPlane(const uint8_t* src_y, CopyRow = IS_ALIGNED(width, 64) ? CopyRow_AVX : CopyRow_Any_AVX; } #endif +#if defined(HAS_COPYROW_AVX512BW) + if (TestCpuFlag(kCpuHasAVX512BW)) { + CopyRow = IS_ALIGNED(width, 128) ? CopyRow_AVX512BW : CopyRow_Any_AVX512BW; + } +#endif #if defined(HAS_COPYROW_ERMS) if (TestCpuFlag(kCpuHasERMS)) { CopyRow = CopyRow_ERMS; @@ -877,6 +882,11 @@ void ConvertToLSBPlane_16(const uint16_t* src_y, } } #endif +#if defined(HAS_DIVIDEROW_16_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + DivideRow = DivideRow_16_SVE2; + } +#endif for (y = 0; y < height; ++y) { DivideRow(src_y, dst_y, scale, width); @@ -1280,6 +1290,22 @@ void SplitRGBPlane(const uint8_t* src_rgb, } } #endif +#if defined(HAS_SPLITRGBROW_SSE41) + if (TestCpuFlag(kCpuHasSSE41)) { + SplitRGBRow = SplitRGBRow_Any_SSE41; + if (IS_ALIGNED(width, 16)) { + SplitRGBRow = SplitRGBRow_SSE41; + } + } +#endif +#if defined(HAS_SPLITRGBROW_AVX2) + if (TestCpuFlag(kCpuHasAVX2)) { + SplitRGBRow = SplitRGBRow_Any_AVX2; + if (IS_ALIGNED(width, 32)) { + SplitRGBRow = SplitRGBRow_AVX2; + } + } +#endif #if defined(HAS_SPLITRGBROW_NEON) if (TestCpuFlag(kCpuHasNEON)) { SplitRGBRow = SplitRGBRow_Any_NEON; @@ -5184,12 +5210,17 @@ int HalfFloatPlane(const uint16_t* src_y, #if defined(HAS_HALFFLOATROW_NEON) if (TestCpuFlag(kCpuHasNEON)) { HalfFloatRow = - (scale == 1.0f) ? HalfFloat1Row_Any_NEON : HalfFloatRow_Any_NEON; - if (IS_ALIGNED(width, 8)) { - HalfFloatRow = (scale == 1.0f) ? HalfFloat1Row_NEON : HalfFloatRow_NEON; + scale == 1.0f ? HalfFloat1Row_Any_NEON : HalfFloatRow_Any_NEON; + if (IS_ALIGNED(width, 16)) { + HalfFloatRow = scale == 1.0f ? HalfFloat1Row_NEON : HalfFloatRow_NEON; } } #endif +#if defined(HAS_HALFFLOATROW_SVE2) + if (TestCpuFlag(kCpuHasSVE2)) { + HalfFloatRow = scale == 1.0f ? HalfFloat1Row_SVE2 : HalfFloatRow_SVE2; + } +#endif #if defined(HAS_HALFFLOATROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { HalfFloatRow = HalfFloatRow_Any_MSA; diff --git a/libfenrir/src/main/jni/animation/libyuv/source/rotate.cc b/libfenrir/src/main/jni/animation/libyuv/source/rotate.cc index 08ec2ccfb..6b0b84f59 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/rotate.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/rotate.cc @@ -234,6 +234,11 @@ void RotatePlane180(const uint8_t* src, CopyRow = IS_ALIGNED(width, 64) ? CopyRow_AVX : CopyRow_Any_AVX; } #endif +#if defined(HAS_COPYROW_AVX512BW) + if (TestCpuFlag(kCpuHasAVX512BW)) { + CopyRow = IS_ALIGNED(width, 128) ? CopyRow_AVX512BW : CopyRow_Any_AVX512BW; + } +#endif #if defined(HAS_COPYROW_ERMS) if (TestCpuFlag(kCpuHasERMS)) { CopyRow = CopyRow_ERMS; diff --git a/libfenrir/src/main/jni/animation/libyuv/source/rotate_argb.cc b/libfenrir/src/main/jni/animation/libyuv/source/rotate_argb.cc index d55fac4f6..7cfaedc52 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/rotate_argb.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/rotate_argb.cc @@ -189,6 +189,11 @@ static int ARGBRotate180(const uint8_t* src_argb, CopyRow = IS_ALIGNED(width * 4, 64) ? CopyRow_AVX : CopyRow_Any_AVX; } #endif +#if defined(HAS_COPYROW_AVX512BW) + if (TestCpuFlag(kCpuHasAVX512BW)) { + CopyRow = IS_ALIGNED(width * 4, 128) ? CopyRow_AVX512BW : CopyRow_Any_AVX512BW; + } +#endif #if defined(HAS_COPYROW_ERMS) if (TestCpuFlag(kCpuHasERMS)) { CopyRow = CopyRow_ERMS; diff --git a/libfenrir/src/main/jni/animation/libyuv/source/row_any.cc b/libfenrir/src/main/jni/animation/libyuv/source/row_any.cc index d2f8a5419..a61ab817c 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/row_any.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/row_any.cc @@ -967,6 +967,9 @@ ANY21PT(MergeUVRow_16_Any_NEON, MergeUVRow_16_NEON, uint16_t, 2, 7) memcpy(dst_ptr + n * BPP, vout, r * BPP); \ } +#ifdef HAS_COPYROW_AVX512BW +ANY11(CopyRow_Any_AVX512BW, CopyRow_AVX512BW, 0, 1, 1, 127) +#endif #ifdef HAS_COPYROW_AVX ANY11(CopyRow_Any_AVX, CopyRow_AVX, 0, 1, 1, 63) #endif @@ -1810,6 +1813,16 @@ ANY11P16(HalfFloat1Row_Any_F16C, 15) #endif #ifdef HAS_HALFFLOATROW_NEON +#ifdef __aarch64__ +ANY11P16(HalfFloatRow_Any_NEON, HalfFloatRow_NEON, uint16_t, uint16_t, 2, 2, 15) +ANY11P16(HalfFloat1Row_Any_NEON, + HalfFloat1Row_NEON, + uint16_t, + uint16_t, + 2, + 2, + 15) +#else ANY11P16(HalfFloatRow_Any_NEON, HalfFloatRow_NEON, uint16_t, uint16_t, 2, 2, 7) ANY11P16(HalfFloat1Row_Any_NEON, HalfFloat1Row_NEON, @@ -1819,6 +1832,7 @@ ANY11P16(HalfFloat1Row_Any_NEON, 2, 7) #endif +#endif #ifdef HAS_HALFFLOATROW_MSA ANY11P16(HalfFloatRow_Any_MSA, HalfFloatRow_MSA, uint16_t, uint16_t, 2, 2, 31) #endif @@ -2194,6 +2208,12 @@ ANY12PT(SplitUVRow_16_Any_NEON, SplitUVRow_16_NEON, uint16_t, 2, 7) #ifdef HAS_SPLITRGBROW_SSSE3 ANY13(SplitRGBRow_Any_SSSE3, SplitRGBRow_SSSE3, 3, 15) #endif +#ifdef HAS_SPLITRGBROW_SSE41 +ANY13(SplitRGBRow_Any_SSE41, SplitRGBRow_SSE41, 3, 15) +#endif +#ifdef HAS_SPLITRGBROW_AVX2 +ANY13(SplitRGBRow_Any_AVX2, SplitRGBRow_AVX2, 3, 31) +#endif #ifdef HAS_SPLITRGBROW_NEON ANY13(SplitRGBRow_Any_NEON, SplitRGBRow_NEON, 3, 15) #endif diff --git a/libfenrir/src/main/jni/animation/libyuv/source/row_gcc.cc b/libfenrir/src/main/jni/animation/libyuv/source/row_gcc.cc index cb757c755..2ec59759f 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/row_gcc.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/row_gcc.cc @@ -3361,7 +3361,7 @@ void OMITFP I422ToRGBARow_SSSE3(const uint8_t* y_buf, "vpunpcklbw %%zmm1,%%zmm3,%%zmm3 \n" \ "vpermq $0xd8,%%zmm3,%%zmm3 \n" \ "vpunpcklwd %%zmm3,%%zmm3,%%zmm3 \n" \ - "vmovdqu8 (%[y_buf]),%%ymm4 \n" \ + "vmovups (%[y_buf]),%%ymm4 \n" \ "vpermq %%zmm4,%%zmm17,%%zmm4 \n" \ "vpermq $0xd8,%%zmm4,%%zmm4 \n" \ "vpunpcklbw %%zmm4,%%zmm4,%%zmm4 \n" \ @@ -3572,17 +3572,17 @@ void OMITFP I422ToRGBARow_SSSE3(const uint8_t* y_buf, "vpbroadcastq %%xmm8, %%zmm8 \n" \ "vpsllw $7,%%xmm13,%%xmm13 \n" \ "vpbroadcastb %%xmm13,%%zmm13 \n" \ - "movq 32(%[yuvconstants]),%%xmm9 \n" \ + "movq 32(%[yuvconstants]),%%xmm9 \n" \ "vpbroadcastq %%xmm9,%%zmm9 \n" \ - "movq 64(%[yuvconstants]),%%xmm10 \n" \ + "movq 64(%[yuvconstants]),%%xmm10 \n" \ "vpbroadcastq %%xmm10,%%zmm10 \n" \ - "movq 96(%[yuvconstants]),%%xmm11 \n" \ + "movq 96(%[yuvconstants]),%%xmm11 \n" \ "vpbroadcastq %%xmm11,%%zmm11 \n" \ - "movq 128(%[yuvconstants]),%%xmm12 \n" \ + "movq 128(%[yuvconstants]),%%xmm12 \n" \ "vpbroadcastq %%xmm12,%%zmm12 \n" \ - "vmovdqu8 (%[quadsplitperm]),%%zmm16 \n" \ - "vmovdqu8 (%[dquadsplitperm]),%%zmm17 \n" \ - "vmovdqu8 (%[unperm]),%%zmm18 \n" + "vmovups (%[quadsplitperm]),%%zmm16 \n" \ + "vmovups (%[dquadsplitperm]),%%zmm17 \n" \ + "vmovups (%[unperm]),%%zmm18 \n" #define YUVTORGB16_AVX2(yuvconstants) \ "vpsubb %%ymm13,%%ymm3,%%ymm3 \n" \ @@ -3672,8 +3672,8 @@ void OMITFP I422ToRGBARow_SSSE3(const uint8_t* y_buf, "vpermq %%zmm2,%%zmm18,%%zmm2 \n" \ "vpunpcklwd %%zmm2,%%zmm0,%%zmm1 \n" \ "vpunpckhwd %%zmm2,%%zmm0,%%zmm0 \n" \ - "vmovdqu8 %%zmm1,(%[dst_argb]) \n" \ - "vmovdqu8 %%zmm0,0x40(%[dst_argb]) \n" \ + "vmovups %%zmm1,(%[dst_argb]) \n" \ + "vmovups %%zmm0,0x40(%[dst_argb]) \n" \ "lea 0x80(%[dst_argb]), %[dst_argb] \n" // Store 16 AR30 values. @@ -5340,15 +5340,15 @@ void Convert16To8Row_AVX512BW(const uint16_t* src_y, // 64 pixels per loop. LABELALIGN "1: \n" - "vmovdqu8 (%0),%%zmm0 \n" - "vmovdqu8 0x40(%0),%%zmm1 \n" + "vmovups (%0),%%zmm0 \n" + "vmovups 0x40(%0),%%zmm1 \n" "add $0x80,%0 \n" "vpmulhuw %%zmm2,%%zmm0,%%zmm0 \n" "vpmulhuw %%zmm2,%%zmm1,%%zmm1 \n" "vpmovuswb %%zmm0,%%ymm0 \n" "vpmovuswb %%zmm1,%%ymm1 \n" - "vmovdqu8 %%ymm0,(%1) \n" - "vmovdqu8 %%ymm1,0x20(%1) \n" + "vmovups %%ymm0,(%1) \n" + "vmovups %%ymm1,0x20(%1) \n" "add $0x40,%1 \n" "sub $0x40,%2 \n" "jg 1b \n" @@ -5503,6 +5503,116 @@ void SplitRGBRow_SSSE3(const uint8_t* src_rgb, } #endif // HAS_SPLITRGBROW_SSSE3 +#ifdef HAS_SPLITRGBROW_SSE41 +// Shuffle table for converting RGB to Planar, SSE4.1. Note: these are used for +// the AVX2 implementation as well. +static const uvec8 kSplitRGBShuffleSSE41[5] = { + {0u, 3u, 6u, 9u, 12u, 15u, 2u, 5u, 8u, 11u, 14u, 1u, 4u, 7u, 10u, 13u}, + {1u, 4u, 7u, 10u, 13u, 0u, 3u, 6u, 9u, 12u, 15u, 2u, 5u, 8u, 11u, 14u}, + {2u, 5u, 8u, 11u, 14u, 1u, 4u, 7u, 10u, 13u, 0u, 3u, 6u, 9u, 12u, 15u}, + {0u, 128u, 0u, 0u, 128u, 0u, 0u, 128u, 0u, 0u, 128u, 0u, 0u, 128u, 0u, 0u}, + {0u, 0u, 128u, 0u, 0u, 128u, 0u, 0u, 128u, 0u, 0u, 128u, 0u, 0u, 128u, 0u}, +}; + +void SplitRGBRow_SSE41(const uint8_t* src_rgb, uint8_t* dst_r, + uint8_t* dst_g, uint8_t* dst_b, int width) { + asm volatile( + "movdqa 48(%5), %%xmm0 \n" + "1: \n" + "movdqu (%0),%%xmm1 \n" + "movdqu 0x10(%0),%%xmm2 \n" + "movdqu 0x20(%0),%%xmm3 \n" + "lea 0x30(%0),%0 \n" + "movdqa %%xmm1, %%xmm4 \n" + "pblendvb %%xmm3, %%xmm1 \n" + "pblendvb %%xmm2, %%xmm3 \n" + "pblendvb %%xmm4, %%xmm2 \n" + "palignr $0xF, %%xmm0, %%xmm0 \n" + "pblendvb %%xmm2, %%xmm1 \n" + "pblendvb %%xmm3, %%xmm2 \n" + "pblendvb %%xmm4, %%xmm3 \n" + "palignr $0x1, %%xmm0, %%xmm0 \n" + "pshufb 0(%5), %%xmm1 \n" + "pshufb 16(%5), %%xmm2 \n" + "pshufb 32(%5), %%xmm3 \n" + "movdqu %%xmm1,(%1) \n" + "lea 0x10(%1),%1 \n" + "movdqu %%xmm2,(%2) \n" + "lea 0x10(%2),%2 \n" + "movdqu %%xmm3,(%3) \n" + "lea 0x10(%3),%3 \n" + "sub $0x10,%4 \n" + "jg 1b \n" + : "+r"(src_rgb), // %0 + "+r"(dst_r), // %1 + "+r"(dst_g), // %2 + "+r"(dst_b), // %3 + "+r"(width) // %4 + : "r"(&kSplitRGBShuffleSSE41[0]) // %5 + : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4"); +} +#endif // HAS_SPLITRGBROW_SSE41 + +#ifdef HAS_SPLITRGBROW_AVX2 +void SplitRGBRow_AVX2(const uint8_t* src_rgb, uint8_t* dst_r, + uint8_t* dst_g, uint8_t* dst_b, int width) { + asm volatile( + "vbroadcasti128 48(%5), %%ymm0 \n" + "vbroadcasti128 64(%5), %%ymm7 \n" +#if defined(__x86_64__) + "vbroadcasti128 0(%5), %%ymm8 \n" + "vbroadcasti128 16(%5), %%ymm9 \n" + "vbroadcasti128 32(%5), %%ymm10 \n" +#endif + "1: \n" + "vmovdqu (%0),%%ymm4 \n" + "vmovdqu 0x20(%0),%%ymm5 \n" + "vmovdqu 0x40(%0),%%ymm6 \n" + "lea 0x60(%0),%0 \n" + "vpblendd $240, %%ymm5, %%ymm4, %%ymm1 \n" + "vperm2i128 $33, %%ymm6, %%ymm4, %%ymm2 \n" + "vpblendd $240, %%ymm6, %%ymm5, %%ymm3 \n" + "vpblendvb %%ymm0, %%ymm3, %%ymm1, %%ymm4 \n" + "vpblendvb %%ymm0, %%ymm1, %%ymm2, %%ymm5 \n" + "vpblendvb %%ymm0, %%ymm2, %%ymm3, %%ymm6 \n" + "vpblendvb %%ymm7, %%ymm5, %%ymm4, %%ymm1 \n" + "vpblendvb %%ymm7, %%ymm6, %%ymm5, %%ymm2 \n" + "vpblendvb %%ymm7, %%ymm4, %%ymm6, %%ymm3 \n" +#if defined(__x86_64__) + "vpshufb %%ymm8, %%ymm1, %%ymm1 \n" + "vpshufb %%ymm9, %%ymm2, %%ymm2 \n" + "vpshufb %%ymm10, %%ymm3, %%ymm3 \n" +#else + "vbroadcasti128 0(%5), %%ymm4 \n" + "vbroadcasti128 16(%5), %%ymm5 \n" + "vbroadcasti128 32(%5), %%ymm6 \n" + "vpshufb %%ymm4, %%ymm1, %%ymm1 \n" + "vpshufb %%ymm5, %%ymm2, %%ymm2 \n" + "vpshufb %%ymm6, %%ymm3, %%ymm3 \n" +#endif + "vmovdqu %%ymm1,(%1) \n" + "lea 0x20(%1),%1 \n" + "vmovdqu %%ymm2,(%2) \n" + "lea 0x20(%2),%2 \n" + "vmovdqu %%ymm3,(%3) \n" + "lea 0x20(%3),%3 \n" + "sub $0x20,%4 \n" + "jg 1b \n" + : "+r"(src_rgb), // %0 + "+r"(dst_r), // %1 + "+r"(dst_g), // %2 + "+r"(dst_b), // %3 + "+r"(width) // %4 + : "r"(&kSplitRGBShuffleSSE41[0]) // %5 + : "memory", "cc", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", + "xmm7" +#if defined(__x86_64__) + , "xmm8", "xmm9", "xmm10" +#endif + ); +} +#endif // HAS_SPLITRGBROW_AVX2 + #ifdef HAS_MERGERGBROW_SSSE3 // Shuffle table for converting Planar to RGB. static const uvec8 kMergeRGBShuffle[9] = { @@ -6395,6 +6505,27 @@ void CopyRow_AVX(const uint8_t* src, uint8_t* dst, int width) { } #endif // HAS_COPYROW_AVX +#ifdef HAS_COPYROW_AVX512BW +void CopyRow_AVX512BW(const uint8_t* src, uint8_t* dst, int width) { + asm volatile ( + "1: \n" + "vmovups (%0),%%zmm0 \n" + "vmovups 0x40(%0),%%zmm1 \n" + "lea 0x80(%0),%0 \n" + "vmovups %%zmm0,(%1) \n" + "vmovups %%zmm1,0x40(%1) \n" + "lea 0x80(%1),%1 \n" + "sub $0x80,%2 \n" + "jg 1b \n" + "vzeroupper \n" + : "+r"(src), // %0 + "+r"(dst), // %1 + "+r"(width) // %2 + : + : "memory", "cc", "xmm0", "xmm1"); +} +#endif // HAS_COPYROW_AVX512 + #ifdef HAS_COPYROW_ERMS // Multiple of 1. void CopyRow_ERMS(const uint8_t* src, uint8_t* dst, int width) { diff --git a/libfenrir/src/main/jni/animation/libyuv/source/row_neon64.cc b/libfenrir/src/main/jni/animation/libyuv/source/row_neon64.cc index 7ad54b430..4b1ed2c0c 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/row_neon64.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/row_neon64.cc @@ -4669,50 +4669,64 @@ void HalfFloat1Row_NEON(const uint16_t* src, uint16_t* dst, float /*unused*/, int width) { - asm volatile ( + asm volatile( "1: \n" - "ld1 {v1.16b}, [%0], #16 \n" // load 8 shorts - "subs %w2, %w2, #8 \n" // 8 pixels per loop - "uxtl v2.4s, v1.4h \n" // 8 int's + "ldp q0, q1, [%0], #32 \n" // load 16 shorts + "subs %w2, %w2, #16 \n" // 16 pixels per loop + "uxtl v2.4s, v0.4h \n" + "uxtl v4.4s, v1.4h \n" + "uxtl2 v3.4s, v0.8h \n" + "uxtl2 v5.4s, v1.8h \n" "prfm pldl1keep, [%0, 448] \n" - "uxtl2 v3.4s, v1.8h \n" - "scvtf v2.4s, v2.4s \n" // 8 floats + "scvtf v2.4s, v2.4s \n" + "scvtf v4.4s, v4.4s \n" "scvtf v3.4s, v3.4s \n" - "fcvtn v1.4h, v2.4s \n" // 8 half floats - "fcvtn2 v1.8h, v3.4s \n" - "st1 {v1.16b}, [%1], #16 \n" // store 8 shorts + "scvtf v5.4s, v5.4s \n" + "fcvtn v0.4h, v2.4s \n" + "fcvtn v1.4h, v4.4s \n" + "fcvtn2 v0.8h, v3.4s \n" + "fcvtn2 v1.8h, v5.4s \n" + "stp q0, q1, [%1], #32 \n" // store 16 shorts "b.gt 1b \n" : "+r"(src), // %0 "+r"(dst), // %1 "+r"(width) // %2 : - : "cc", "memory", "v1", "v2", "v3"); + : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5"); } void HalfFloatRow_NEON(const uint16_t* src, uint16_t* dst, float scale, int width) { - asm volatile ( + asm volatile( "1: \n" - "ld1 {v1.16b}, [%0], #16 \n" // load 8 shorts - "subs %w2, %w2, #8 \n" // 8 pixels per loop - "uxtl v2.4s, v1.4h \n" // 8 int's + "ldp q0, q1, [%0], #32 \n" // load 16 shorts + "subs %w2, %w2, #16 \n" // 16 pixels per loop + "uxtl v2.4s, v0.4h \n" + "uxtl v4.4s, v1.4h \n" + "uxtl2 v3.4s, v0.8h \n" + "uxtl2 v5.4s, v1.8h \n" "prfm pldl1keep, [%0, 448] \n" - "uxtl2 v3.4s, v1.8h \n" - "scvtf v2.4s, v2.4s \n" // 8 floats + "scvtf v2.4s, v2.4s \n" + "scvtf v4.4s, v4.4s \n" "scvtf v3.4s, v3.4s \n" + "scvtf v5.4s, v5.4s \n" "fmul v2.4s, v2.4s, %3.s[0] \n" // adjust exponent + "fmul v4.4s, v4.4s, %3.s[0] \n" "fmul v3.4s, v3.4s, %3.s[0] \n" - "uqshrn v1.4h, v2.4s, #13 \n" // isolate halffloat - "uqshrn2 v1.8h, v3.4s, #13 \n" - "st1 {v1.16b}, [%1], #16 \n" // store 8 shorts + "fmul v5.4s, v5.4s, %3.s[0] \n" + "uqshrn v0.4h, v2.4s, #13 \n" // isolate halffloat + "uqshrn v1.4h, v4.4s, #13 \n" // isolate halffloat + "uqshrn2 v0.8h, v3.4s, #13 \n" + "uqshrn2 v1.8h, v5.4s, #13 \n" + "stp q0, q1, [%1], #32 \n" // store 16 shorts "b.gt 1b \n" : "+r"(src), // %0 "+r"(dst), // %1 "+r"(width) // %2 : "w"(scale * 1.9259299444e-34f) // %3 - : "cc", "memory", "v1", "v2", "v3"); + : "cc", "memory", "v0", "v1", "v2", "v3", "v4", "v5"); } void ByteToFloatRow_NEON(const uint8_t* src, diff --git a/libfenrir/src/main/jni/animation/libyuv/source/row_sme.cc b/libfenrir/src/main/jni/animation/libyuv/source/row_sme.cc new file mode 100644 index 000000000..7676d9e64 --- /dev/null +++ b/libfenrir/src/main/jni/animation/libyuv/source/row_sme.cc @@ -0,0 +1,225 @@ +/* + * Copyright 2024 The LibYuv Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "libyuv/row.h" + +#ifdef __cplusplus +namespace libyuv { +extern "C" { +#endif + +#if !defined(LIBYUV_DISABLE_SME) && defined(CLANG_HAS_SME) && \ + defined(__aarch64__) + +#define YUVTORGB_SVE_SETUP \ + "ld1rb {z28.b}, p0/z, [%[kUVCoeff], #0] \n" \ + "ld1rb {z29.b}, p0/z, [%[kUVCoeff], #1] \n" \ + "ld1rb {z30.b}, p0/z, [%[kUVCoeff], #2] \n" \ + "ld1rb {z31.b}, p0/z, [%[kUVCoeff], #3] \n" \ + "ld1rh {z24.h}, p0/z, [%[kRGBCoeffBias], #0] \n" \ + "ld1rh {z25.h}, p0/z, [%[kRGBCoeffBias], #2] \n" \ + "ld1rh {z26.h}, p0/z, [%[kRGBCoeffBias], #4] \n" \ + "ld1rh {z27.h}, p0/z, [%[kRGBCoeffBias], #6] \n" + +// Read twice as much data from YUV, putting the even elements from the Y data +// in z0.h and odd elements in z1.h. U/V data is not duplicated, stored in +// z2.h/z3.h. +#define READYUV422_SVE_2X \ + "ld1b {z0.b}, p1/z, [%[src_y]] \n" \ + "ld1b {z2.h}, p1/z, [%[src_u]] \n" \ + "ld1b {z3.h}, p1/z, [%[src_v]] \n" \ + "incb %[src_y] \n" \ + "inch %[src_u] \n" \ + "inch %[src_v] \n" \ + "prfm pldl1keep, [%[src_y], 448] \n" \ + "prfm pldl1keep, [%[src_u], 128] \n" \ + "prfm pldl1keep, [%[src_v], 128] \n" \ + "trn2 z1.b, z0.b, z0.b \n" \ + "trn1 z0.b, z0.b, z0.b \n" + +// Read twice as much data from YUV, putting the even elements from the Y data +// in z0.h and odd elements in z1.h. +#define READYUV444_SVE_2X \ + "ld1b {z0.b}, p1/z, [%[src_y]] \n" \ + "ld1b {z2.b}, p1/z, [%[src_u]] \n" \ + "ld1b {z3.b}, p1/z, [%[src_v]] \n" \ + "incb %[src_y] \n" \ + "incb %[src_u] \n" \ + "incb %[src_v] \n" \ + "prfm pldl1keep, [%[src_y], 448] \n" \ + "prfm pldl1keep, [%[src_u], 128] \n" \ + "prfm pldl1keep, [%[src_v], 128] \n" \ + "trn2 z1.b, z0.b, z0.b \n" \ + "trn1 z0.b, z0.b, z0.b \n" + +// The U/V component multiplies do not need to be duplicated in I422, we just +// need to combine them with Y0/Y1 correctly. +#define I422TORGB_SVE_2X \ + "umulh z0.h, z24.h, z0.h \n" /* Y0 */ \ + "umulh z1.h, z24.h, z1.h \n" /* Y1 */ \ + "umullb z6.h, z30.b, z2.b \n" \ + "umullb z4.h, z28.b, z2.b \n" /* DB */ \ + "umullb z5.h, z29.b, z3.b \n" /* DR */ \ + "umlalb z6.h, z31.b, z3.b \n" /* DG */ \ + \ + "add z17.h, z0.h, z26.h \n" /* G0 */ \ + "add z21.h, z1.h, z26.h \n" /* G1 */ \ + "add z16.h, z0.h, z4.h \n" /* B0 */ \ + "add z20.h, z1.h, z4.h \n" /* B1 */ \ + "add z18.h, z0.h, z5.h \n" /* R0 */ \ + "add z22.h, z1.h, z5.h \n" /* R1 */ \ + "uqsub z17.h, z17.h, z6.h \n" /* G0 */ \ + "uqsub z21.h, z21.h, z6.h \n" /* G1 */ \ + "uqsub z16.h, z16.h, z25.h \n" /* B0 */ \ + "uqsub z20.h, z20.h, z25.h \n" /* B1 */ \ + "uqsub z18.h, z18.h, z27.h \n" /* R0 */ \ + "uqsub z22.h, z22.h, z27.h \n" /* R1 */ + +#define I444TORGB_SVE_2X \ + "umulh z0.h, z24.h, z0.h \n" /* Y0 */ \ + "umulh z1.h, z24.h, z1.h \n" /* Y1 */ \ + "umullb z6.h, z30.b, z2.b \n" \ + "umullt z7.h, z30.b, z2.b \n" \ + "umullb z4.h, z28.b, z2.b \n" /* DB */ \ + "umullt z2.h, z28.b, z2.b \n" /* DB */ \ + "umlalb z6.h, z31.b, z3.b \n" /* DG */ \ + "umlalt z7.h, z31.b, z3.b \n" /* DG */ \ + "umullb z5.h, z29.b, z3.b \n" /* DR */ \ + "umullt z3.h, z29.b, z3.b \n" /* DR */ \ + "add z17.h, z0.h, z26.h \n" /* G */ \ + "add z21.h, z1.h, z26.h \n" /* G */ \ + "add z16.h, z0.h, z4.h \n" /* B */ \ + "add z20.h, z1.h, z2.h \n" /* B */ \ + "add z18.h, z0.h, z5.h \n" /* R */ \ + "add z22.h, z1.h, z3.h \n" /* R */ \ + "uqsub z17.h, z17.h, z6.h \n" /* G */ \ + "uqsub z21.h, z21.h, z7.h \n" /* G */ \ + "uqsub z16.h, z16.h, z25.h \n" /* B */ \ + "uqsub z20.h, z20.h, z25.h \n" /* B */ \ + "uqsub z18.h, z18.h, z27.h \n" /* R */ \ + "uqsub z22.h, z22.h, z27.h \n" /* R */ + +#define RGBTOARGB8_SVE_2X \ + /* Inputs: B: z16.h, G: z17.h, R: z18.h, A: z19.b */ \ + "uqshrnb z16.b, z16.h, #6 \n" /* B0 */ \ + "uqshrnb z17.b, z17.h, #6 \n" /* G0 */ \ + "uqshrnb z18.b, z18.h, #6 \n" /* R0 */ \ + "uqshrnt z16.b, z20.h, #6 \n" /* B1 */ \ + "uqshrnt z17.b, z21.h, #6 \n" /* G1 */ \ + "uqshrnt z18.b, z22.h, #6 \n" /* R1 */ + +#define YUVTORGB_SVE_REGS \ + "z0", "z1", "z2", "z3", "z4", "z5", "z6", "z7", "z16", "z17", "z18", "z19", \ + "z20", "z22", "z23", "z24", "z25", "z26", "z27", "z28", "z29", "z30", \ + "z31", "p0", "p1", "p2", "p3" + +__arm_locally_streaming void I444ToARGBRow_SME( + const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width) { + // Streaming-SVE only, no use of ZA tile. + uint64_t vl; + asm volatile( + "cntb %[vl] \n" + "ptrue p0.b \n" // + YUVTORGB_SVE_SETUP + "dup z19.b, #255 \n" // A + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.b \n" + "1: \n" // + READYUV444_SVE_2X I444TORGB_SVE_2X RGBTOARGB8_SVE_2X + "subs %w[width], %w[width], %w[vl] \n" + "st4b {z16.b, z17.b, z18.b, z19.b}, p1, [%[dst_argb]] \n" + "incb %[dst_argb], all, mul #4 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "whilelt p1.b, wzr, %w[width] \n" // + READYUV444_SVE_2X I444TORGB_SVE_2X RGBTOARGB8_SVE_2X + "st4b {z16.b, z17.b, z18.b, z19.b}, p1, [%[dst_argb]] \n" + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_u] "+r"(src_u), // %[src_u] + [src_v] "+r"(src_v), // %[src_v] + [dst_argb] "+r"(dst_argb), // %[dst_argb] + [width] "+r"(width), // %[width] + [vl] "=&r"(vl) // %[vl] + : [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias) // %[kRGBCoeffBias] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + +__arm_locally_streaming void I422ToARGBRow_SME( + const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width) { + // Streaming-SVE only, no use of ZA tile. + uint64_t vl; + asm volatile( + "cntb %[vl] \n" + "ptrue p0.b \n" // + YUVTORGB_SVE_SETUP + "dup z19.b, #255 \n" // A0 + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.b \n" + "1: \n" // + READYUV422_SVE_2X I422TORGB_SVE_2X RGBTOARGB8_SVE_2X + "subs %w[width], %w[width], %w[vl] \n" + "st4b {z16.b, z17.b, z18.b, z19.b}, p1, [%[dst_argb]] \n" + "incb %[dst_argb], all, mul #4 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "whilelt p1.b, wzr, %w[width] \n" // + READYUV422_SVE_2X I422TORGB_SVE_2X RGBTOARGB8_SVE_2X + "st4b {z16.b, z17.b, z18.b, z19.b}, p1, [%[dst_argb]] \n" + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_u] "+r"(src_u), // %[src_u] + [src_v] "+r"(src_v), // %[src_v] + [dst_argb] "+r"(dst_argb), // %[dst_argb] + [width] "+r"(width), // %[width] + [vl] "=&r"(vl) // %[vl] + : [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias) // %[kRGBCoeffBias] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + +#endif // !defined(LIBYUV_DISABLE_SME) && defined(CLANG_HAS_SME) && + // defined(__aarch64__) + +#ifdef __cplusplus +} // extern "C" +} // namespace libyuv +#endif diff --git a/libfenrir/src/main/jni/animation/libyuv/source/row_sve.cc b/libfenrir/src/main/jni/animation/libyuv/source/row_sve.cc index a6da07b98..bfa49d9c2 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/row_sve.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/row_sve.cc @@ -78,6 +78,44 @@ extern "C" { "trn1 z0.b, z0.b, z0.b \n" /* YYYY */ \ "tbl z1.b, {z1.b}, z22.b \n" /* UVUV */ +#define READI210_SVE \ + "ld1h {z3.h}, p1/z, [%[src_y]] \n" \ + "lsl z0.h, z3.h, #6 \n" \ + "usra z0.h, z3.h, #4 \n" \ + "ld1h {z1.s}, p1/z, [%[src_u]] \n" \ + "ld1h {z2.s}, p1/z, [%[src_v]] \n" \ + "incb %[src_y] \n" \ + "inch %[src_u] \n" \ + "inch %[src_v] \n" \ + "prfm pldl1keep, [%[src_y], 448] \n" \ + "prfm pldl1keep, [%[src_u], 128] \n" \ + "prfm pldl1keep, [%[src_v], 128] \n" \ + "trn1 z1.h, z1.h, z1.h \n" \ + "trn1 z2.h, z2.h, z2.h \n" \ + "uqshrnb z1.b, z1.h, #2 \n" \ + "uqshrnb z2.b, z2.h, #2 \n" + +#define READP210_SVE \ + "ld1h {z0.h}, p1/z, [%[src_y]] \n" \ + "ld1h {z1.h}, p2/z, [%[src_uv]] \n" \ + "incb %[src_y] \n" \ + "incb %[src_uv] \n" \ + "prfm pldl1keep, [%[src_y], 448] \n" \ + "prfm pldl1keep, [%[src_uv], 256] \n" \ + "tbl z1.b, {z1.b}, z22.b \n" + +// We need different predicates for the UV components since we are reading +// 32-bit (pairs of UV) elements rather than 16-bit Y elements. +#define READP410_SVE \ + "ld1h {z0.h}, p1/z, [%[src_y]] \n" \ + "ld1w {z1.s}, p2/z, [%[src_uv]] \n" \ + "ld1w {z2.s}, p3/z, [%[src_uv], #1, mul vl] \n" \ + "incb %[src_y] \n" \ + "incb %[src_uv], all, mul #2 \n" \ + "prfm pldl1keep, [%[src_y], 448] \n" \ + "prfm pldl1keep, [%[src_uv], 256] \n" \ + "uzp2 z1.b, z1.b, z2.b \n" + #define READYUY2_SVE \ "ld1w {z0.s}, p2/z, [%[src_yuy2]] \n" /* YUYV */ \ "incb %[src_yuy2] \n" \ @@ -181,6 +219,15 @@ extern "C" { "uqshrnt z17.b, z21.h, #6 \n" /* G1 */ \ "uqshrnt z18.b, z22.h, #6 \n" /* R1 */ +#define RGBTOARGB8_SVE_TOP_2X \ + /* Inputs: B: z16.h, G: z17.h, R: z18.h */ \ + "uqshl z16.h, p0/m, z16.h, #2 \n" /* B0 */ \ + "uqshl z17.h, p0/m, z17.h, #2 \n" /* G0 */ \ + "uqshl z18.h, p0/m, z18.h, #2 \n" /* R0 */ \ + "uqshl z20.h, p0/m, z20.h, #2 \n" /* B1 */ \ + "uqshl z21.h, p0/m, z21.h, #2 \n" /* G1 */ \ + "uqshl z22.h, p0/m, z22.h, #2 \n" /* R1 */ + // Convert from 2.14 fixed point RGB to 8 bit RGBA, interleaving as AB and GR // pairs to allow us to use ST2 for storing rather than ST4. #define RGBTORGBA8_SVE \ @@ -194,6 +241,18 @@ extern "C" { "z20", "z22", "z23", "z24", "z25", "z26", "z27", "z28", "z29", "z30", \ "z31", "p0", "p1", "p2", "p3" +// Store AR30 elements +#define STOREAR30_SVE \ + "uqshl z16.h, p0/m, z16.h, #2 \n" /* bbbbbbbbbbxxxxxx */ \ + "uqshl z17.h, p0/m, z17.h, #2 \n" /* ggggggggggxxxxxx */ \ + "umin z18.h, p0/m, z18.h, z23.h \n" /* 00rrrrrrrrrrxxxx */ \ + "orr z18.h, z18.h, #0xc000 \n" /* 11rrrrrrrrrrxxxx */ \ + "sri z18.h, z17.h, #12 \n" /* 11rrrrrrrrrrgggg */ \ + "lsl z17.h, z17.h, #4 \n" /* ggggggxxxxxx0000 */ \ + "sri z17.h, z16.h, #6 \n" /* ggggggbbbbbbbbbb */ \ + "st2h {z17.h, z18.h}, p1, [%[dst_ar30]] \n" \ + "incb %[dst_ar30], all, mul #2 \n" + void I444ToARGBRow_SVE2(const uint8_t* src_y, const uint8_t* src_u, const uint8_t* src_v, @@ -332,6 +391,216 @@ void I422ToARGBRow_SVE2(const uint8_t* src_y, : "cc", "memory", YUVTORGB_SVE_REGS); } +void I422ToRGB24Row_SVE2(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width) { + uint64_t vl; + asm volatile( + "cntb %[vl] \n" + "ptrue p0.b \n" YUVTORGB_SVE_SETUP + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.b \n" + "1: \n" READYUV422_SVE_2X + I422TORGB_SVE_2X RGBTOARGB8_SVE_2X + "subs %w[width], %w[width], %w[vl] \n" + "st3b {z16.b, z17.b, z18.b}, p1, [%[dst_argb]] \n" + "incb %[dst_argb], all, mul #3 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "cnth %[vl] \n" + "whilelt p1.b, wzr, %w[width] \n" READYUV422_SVE_2X + I422TORGB_SVE_2X RGBTOARGB8_SVE_2X + "st3b {z16.b, z17.b, z18.b}, p1, [%[dst_argb]] \n" + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_u] "+r"(src_u), // %[src_u] + [src_v] "+r"(src_v), // %[src_v] + [dst_argb] "+r"(dst_argb), // %[dst_argb] + [width] "+r"(width), // %[width] + [vl] "=&r"(vl) // %[vl] + : [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias) // %[kRGBCoeffBias] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + +#define RGB8TORGB565_SVE_FROM_TOP_2X \ + "sri z18.h, z17.h, #5 \n" /* rrrrrgggggg00000 */ \ + "sri z22.h, z21.h, #5 \n" /* rrrrrgggggg00000 */ \ + "sri z18.h, z16.h, #11 \n" /* rrrrrggggggbbbbb */ \ + "sri z22.h, z20.h, #11 \n" /* rrrrrggggggbbbbb */ \ + "mov z19.d, z22.d \n" + +void I422ToRGB565Row_SVE2(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_rgb565, + const struct YuvConstants* yuvconstants, + int width) { + uint64_t vl; + asm volatile( + "cntb %[vl] \n" + "ptrue p0.b \n" YUVTORGB_SVE_SETUP + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.b \n" + "1: \n" READYUV422_SVE_2X + I422TORGB_SVE_2X RGBTOARGB8_SVE_TOP_2X + "subs %w[width], %w[width], %w[vl] \n" // + RGB8TORGB565_SVE_FROM_TOP_2X + "st2h {z18.h, z19.h}, p1, [%[dst]] \n" + "incb %[dst], all, mul #2 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "cnth %[vl] \n" + "whilelt p1.b, wzr, %w[width] \n" READYUV422_SVE_2X + I422TORGB_SVE_2X RGBTOARGB8_SVE_TOP_2X RGB8TORGB565_SVE_FROM_TOP_2X + "st2h {z18.h, z19.h}, p1, [%[dst]] \n" + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_u] "+r"(src_u), // %[src_u] + [src_v] "+r"(src_v), // %[src_v] + [dst] "+r"(dst_rgb565), // %[dst] + [width] "+r"(width), // %[width] + [vl] "=&r"(vl) // %[vl] + : [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias) // %[kRGBCoeffBias] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + +#define RGB8TOARGB1555_SVE_FROM_TOP_2X \ + "dup z0.h, #0x8000 \n" /* 1000000000000000 */ \ + "dup z1.h, #0x8000 \n" /* 1000000000000000 */ \ + "sri z0.h, z18.h, #1 \n" /* 1rrrrrxxxxxxxxxx */ \ + "sri z1.h, z22.h, #1 \n" /* 1rrrrrxxxxxxxxxx */ \ + "sri z0.h, z17.h, #6 \n" /* 1rrrrrgggggxxxxx */ \ + "sri z1.h, z21.h, #6 \n" /* 1rrrrrgggggxxxxx */ \ + "sri z0.h, z16.h, #11 \n" /* 1rrrrrgggggbbbbb */ \ + "sri z1.h, z20.h, #11 \n" /* 1rrrrrgggggbbbbb */ + +void I422ToARGB1555Row_SVE2(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_argb1555, + const struct YuvConstants* yuvconstants, + int width) { + uint64_t vl; + asm volatile( + "cntb %[vl] \n" + "ptrue p0.b \n" YUVTORGB_SVE_SETUP + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.b \n" + "1: \n" READYUV422_SVE_2X + I422TORGB_SVE_2X RGBTOARGB8_SVE_TOP_2X + "subs %w[width], %w[width], %w[vl] \n" // + RGB8TOARGB1555_SVE_FROM_TOP_2X + "st2h {z0.h, z1.h}, p1, [%[dst]] \n" + "incb %[dst], all, mul #2 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "cnth %[vl] \n" + "whilelt p1.b, wzr, %w[width] \n" READYUV422_SVE_2X + I422TORGB_SVE_2X RGBTOARGB8_SVE_TOP_2X RGB8TOARGB1555_SVE_FROM_TOP_2X + "st2h {z0.h, z1.h}, p1, [%[dst]] \n" + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_u] "+r"(src_u), // %[src_u] + [src_v] "+r"(src_v), // %[src_v] + [dst] "+r"(dst_argb1555), // %[dst] + [width] "+r"(width), // %[width] + [vl] "=&r"(vl) // %[vl] + : [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias) // %[kRGBCoeffBias] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + +#define RGB8TOARGB4444_SVE_FROM_TOP_2X \ + "dup z0.h, #0xf000 \n" /* 1111000000000000 */ \ + "dup z1.h, #0xf000 \n" /* 1111000000000000 */ \ + "sri z0.h, z18.h, #4 \n" /* 1111rrrrxxxxxxxx */ \ + "sri z1.h, z22.h, #4 \n" /* 1111rrrrxxxxxxxx */ \ + "sri z0.h, z17.h, #8 \n" /* 1111rrrrggggxxxx */ \ + "sri z1.h, z21.h, #8 \n" /* 1111rrrrggggxxxx */ \ + "sri z0.h, z16.h, #12 \n" /* 1111rrrrggggbbbb */ \ + "sri z1.h, z20.h, #12 \n" /* 1111rrrrggggbbbb */ + +void I422ToARGB4444Row_SVE2(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_argb4444, + const struct YuvConstants* yuvconstants, + int width) { + uint64_t vl; + asm volatile( + "cntb %[vl] \n" + "ptrue p0.b \n" YUVTORGB_SVE_SETUP + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.b \n" + "1: \n" READYUV422_SVE_2X + I422TORGB_SVE_2X RGBTOARGB8_SVE_TOP_2X + "subs %w[width], %w[width], %w[vl] \n" // + RGB8TOARGB4444_SVE_FROM_TOP_2X + "st2h {z0.h, z1.h}, p1, [%[dst]] \n" + "incb %[dst], all, mul #2 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "cnth %[vl] \n" + "whilelt p1.b, wzr, %w[width] \n" READYUV422_SVE_2X + I422TORGB_SVE_2X RGBTOARGB8_SVE_TOP_2X RGB8TOARGB4444_SVE_FROM_TOP_2X + "st2h {z0.h, z1.h}, p1, [%[dst]] \n" + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_u] "+r"(src_u), // %[src_u] + [src_v] "+r"(src_v), // %[src_v] + [dst] "+r"(dst_argb4444), // %[dst] + [width] "+r"(width), // %[width] + [vl] "=&r"(vl) // %[vl] + : [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias) // %[kRGBCoeffBias] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + void I422ToRGBARow_SVE2(const uint8_t* src_y, const uint8_t* src_u, const uint8_t* src_v, @@ -1401,6 +1670,436 @@ void ARGBToRAWRow_SVE2(const uint8_t* src_argb, uint8_t* dst_rgb, int width) { ARGBToXYZRow_SVE2(src_argb, dst_rgb, width, kARGBToRAWRowIndices); } +void DivideRow_16_SVE2(const uint16_t* src_y, + uint16_t* dst_y, + int scale, + int width) { + uint64_t vl; + asm volatile( + "cnth %x[vl] \n" + "dup z0.h, %w[scale] \n" + "subs %w[width], %w[width], %w[vl], lsl #1 \n" + "b.le 2f \n" + + // Run bulk of computation with the same predicates to avoid predicate + // generation overhead. + "ptrue p0.h \n" + "1: \n" + "ld1h {z1.h}, p0/z, [%[src]] \n" + "ld1h {z2.h}, p0/z, [%[src], #1, mul vl] \n" + "incb %[src], all, mul #2 \n" + "umulh z1.h, z1.h, z0.h \n" + "umulh z2.h, z2.h, z0.h \n" + "subs %w[width], %w[width], %w[vl], lsl #1 \n" + "st1h {z1.h}, p0, [%[dst]] \n" + "st1h {z2.h}, p0, [%[dst], #1, mul vl] \n" + "incb %[dst], all, mul #2 \n" + "b.gt 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl], lsl #1 \n" + "b.eq 99f \n" + + // Calculate a pair of predicates for the final iteration to deal with + // the tail. + "whilelt p0.h, wzr, %w[width] \n" + "whilelt p1.h, %w[vl], %w[width] \n" + "ld1h {z1.h}, p0/z, [%[src]] \n" + "ld1h {z2.h}, p1/z, [%[src], #1, mul vl] \n" + "umulh z1.h, z1.h, z0.h \n" + "umulh z2.h, z2.h, z0.h \n" + "st1h {z1.h}, p0, [%[dst]] \n" + "st1h {z2.h}, p1, [%[dst], #1, mul vl] \n" + + "99: \n" + : [src] "+r"(src_y), // %[src] + [dst] "+r"(dst_y), // %[dst] + [width] "+r"(width), // %[width] + [vl] "=&r"(vl) // %[vl] + : [scale] "r"(scale) // %[scale] + : "cc", "memory", "z0", "z1", "z2", "p0", "p1"); +} + +#define HALFFLOAT_SVE \ + "scvtf z0.s, p0/m, z0.s \n" \ + "scvtf z1.s, p0/m, z1.s \n" \ + "scvtf z2.s, p0/m, z2.s \n" \ + "scvtf z3.s, p0/m, z3.s \n" \ + "fmul z0.s, z0.s, z4.s \n" \ + "fmul z1.s, z1.s, z4.s \n" \ + "fmul z2.s, z2.s, z4.s \n" \ + "fmul z3.s, z3.s, z4.s \n" \ + "uqshrnb z0.h, z0.s, #13 \n" \ + "uqshrnb z1.h, z1.s, #13 \n" \ + "uqshrnb z2.h, z2.s, #13 \n" \ + "uqshrnb z3.h, z3.s, #13 \n" + +void HalfFloatRow_SVE2(const uint16_t* src, + uint16_t* dst, + float scale, + int width) { + uint64_t vl; + asm("cntw %x0" : "=r"(vl)); + asm volatile( + "mov z4.s, %s[scale] \n" + "subs %w[width], %w[width], %w[vl], lsl #2 \n" + "b.lt 2f \n" + + // Run bulk of computation with all-true predicates to avoid predicate + // generation overhead. + "ptrue p0.s \n" + "1: \n" + "ld1h {z0.s}, p0/z, [%[src]] \n" + "ld1h {z1.s}, p0/z, [%[src], #1, mul vl] \n" + "ld1h {z2.s}, p0/z, [%[src], #2, mul vl] \n" + "ld1h {z3.s}, p0/z, [%[src], #3, mul vl] \n" + "incb %[src], all, mul #2 \n" HALFFLOAT_SVE + "subs %w[width], %w[width], %w[vl], lsl #2 \n" + "st1h {z0.s}, p0, [%[dst]] \n" + "st1h {z1.s}, p0, [%[dst], #1, mul vl] \n" + "st1h {z2.s}, p0, [%[dst], #2, mul vl] \n" + "st1h {z3.s}, p0, [%[dst], #3, mul vl] \n" + "incb %[dst], all, mul #2 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl], lsl #2 \n" + "b.eq 99f \n" + + // Calculate predicates for the final iteration to deal with the tail. + "whilelt p0.s, wzr, %w[width] \n" + "whilelt p1.s, %w[vl], %w[width] \n" + "whilelt p2.s, %w[vl2], %w[width] \n" + "whilelt p3.s, %w[vl3], %w[width] \n" + "ld1h {z0.s}, p0/z, [%[src]] \n" + "ld1h {z1.s}, p1/z, [%[src], #1, mul vl] \n" + "ld1h {z2.s}, p2/z, [%[src], #2, mul vl] \n" + "ld1h {z3.s}, p3/z, [%[src], #3, mul vl] \n" HALFFLOAT_SVE + "st1h {z0.s}, p0, [%[dst]] \n" + "st1h {z1.s}, p1, [%[dst], #1, mul vl] \n" + "st1h {z2.s}, p2, [%[dst], #2, mul vl] \n" + "st1h {z3.s}, p3, [%[dst], #3, mul vl] \n" + + "99: \n" + : [src] "+r"(src), // %[src] + [dst] "+r"(dst), // %[dst] + [width] "+r"(width) // %[width] + : [vl] "r"(vl), // %[vl] + [vl2] "r"(vl * 2), // %[vl2] + [vl3] "r"(vl * 3), // %[vl3] + [scale] "w"(scale * 1.9259299444e-34f) // %[scale] + : "cc", "memory", "z0", "z1", "z2", "z3", "z4", "p0", "p1", "p2", "p3"); +} + +void HalfFloat1Row_SVE2(const uint16_t* src, + uint16_t* dst, + float scale, + int width) { + uint64_t vl; + asm volatile( + "cnth %x[vl] \n" + "subs %w[width], %w[width], %w[vl], lsl #1 \n" + "b.lt 2f \n" + + // Run bulk of computation with all-true predicates to avoid predicate + // generation overhead. + "ptrue p0.h \n" + "1: \n" + "ld1h {z0.h}, p0/z, [%[src]] \n" + "ld1h {z1.h}, p0/z, [%[src], #1, mul vl] \n" + "incb %[src], all, mul #2 \n" + "ucvtf z0.h, p0/m, z0.h \n" + "ucvtf z1.h, p0/m, z1.h \n" + "subs %w[width], %w[width], %w[vl], lsl #1 \n" + "st1h {z0.h}, p0, [%[dst]] \n" + "st1h {z1.h}, p0, [%[dst], #1, mul vl] \n" + "incb %[dst], all, mul #2 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl], lsl #1 \n" + "b.eq 99f \n" + + // Calculate predicates for the final iteration to deal with the tail. + "whilelt p0.h, wzr, %w[width] \n" + "whilelt p1.h, %w[vl], %w[width] \n" + "ld1h {z0.h}, p0/z, [%[src]] \n" + "ld1h {z1.h}, p1/z, [%[src], #1, mul vl] \n" + "ucvtf z0.h, p0/m, z0.h \n" + "ucvtf z1.h, p0/m, z1.h \n" + "st1h {z0.h}, p0, [%[dst]] \n" + "st1h {z1.h}, p1, [%[dst], #1, mul vl] \n" + + "99: \n" + : [src] "+r"(src), // %[src] + [dst] "+r"(dst), // %[dst] + [width] "+r"(width), // %[width] + [vl] "=&r"(vl) // %[vl] + : + : "cc", "memory", "z0", "z1", "p0", "p1"); +} + +void I210ToARGBRow_SVE2(const uint16_t* src_y, + const uint16_t* src_u, + const uint16_t* src_v, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width) { + uint64_t vl; + asm("cnth %0" : "=r"(vl)); + int width_last_y = width & (vl - 1); + width_last_y = width_last_y == 0 ? vl : width_last_y; + asm volatile( + "ptrue p0.b \n" YUVTORGB_SVE_SETUP + "dup z19.b, #255 \n" // A + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.h \n" + "1: \n" // + READI210_SVE I4XXTORGB_SVE RGBTOARGB8_SVE + "subs %w[width], %w[width], %w[vl] \n" + "st2h {z16.h, z17.h}, p1, [%[dst_argb]] \n" + "add %[dst_argb], %[dst_argb], %[vl], lsl #2 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "whilelt p1.h, wzr, %w[width_last_y] \n" // + READI210_SVE I4XXTORGB_SVE RGBTOARGB8_SVE + "st2h {z16.h, z17.h}, p1, [%[dst_argb]] \n" + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_u] "+r"(src_u), // %[src_u] + [src_v] "+r"(src_v), // %[src_v] + [dst_argb] "+r"(dst_argb), // %[dst_argb] + [width] "+r"(width) // %[width] + : [vl] "r"(vl), // %[vl] + [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias), // %[kRGBCoeffBias] + [width_last_y] "r"(width_last_y) // %[width_last_y] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + +// P210 has 10 bits in msb of 16 bit NV12 style layout. +void P210ToARGBRow_SVE2(const uint16_t* src_y, + const uint16_t* src_uv, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width) { + uint64_t vl; + asm("cnth %0" : "=r"(vl)); + int width_last_y = width & (vl - 1); + width_last_y = width_last_y == 0 ? vl : width_last_y; + int width_last_uv = width_last_y + (width_last_y & 1); + uint32_t nv_uv_start = 0x03010301U; + uint32_t nv_uv_step = 0x04040404U; + asm volatile( + "ptrue p0.b \n" YUVTORGB_SVE_SETUP + "index z22.s, %w[nv_uv_start], %w[nv_uv_step] \n" + "dup z19.b, #255 \n" // A + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.h \n" + "ptrue p2.h \n" + "1: \n" // + READP210_SVE NVTORGB_SVE RGBTOARGB8_SVE + "subs %w[width], %w[width], %w[vl] \n" + "st2h {z16.h, z17.h}, p1, [%[dst_argb]] \n" + "add %[dst_argb], %[dst_argb], %[vl], lsl #2 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "whilelt p1.h, wzr, %w[width_last_y] \n" + "whilelt p2.h, wzr, %w[width_last_uv] \n" // + READP210_SVE NVTORGB_SVE RGBTOARGB8_SVE + "st2h {z16.h, z17.h}, p1, [%[dst_argb]] \n" + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_uv] "+r"(src_uv), // %[src_uv] + [dst_argb] "+r"(dst_argb), // %[dst_argb] + [width] "+r"(width) // %[width] + : [vl] "r"(vl), // %[vl] + [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias), // %[kRGBCoeffBias] + [nv_uv_start] "r"(nv_uv_start), // %[nv_uv_start] + [nv_uv_step] "r"(nv_uv_step), // %[nv_uv_step] + [width_last_y] "r"(width_last_y), // %[width_last_y] + [width_last_uv] "r"(width_last_uv) // %[width_last_uv] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + +void P210ToAR30Row_SVE2(const uint16_t* src_y, + const uint16_t* src_uv, + uint8_t* dst_ar30, + const struct YuvConstants* yuvconstants, + int width) { + uint64_t vl; + asm("cnth %0" : "=r"(vl)); + int width_last_y = width & (vl - 1); + width_last_y = width_last_y == 0 ? vl : width_last_y; + int width_last_uv = width_last_y + (width_last_y & 1); + uint32_t nv_uv_start = 0x03010301U; + uint32_t nv_uv_step = 0x04040404U; + uint16_t limit = 0x3ff0; + asm volatile( + "ptrue p0.b \n" YUVTORGB_SVE_SETUP + "index z22.s, %w[nv_uv_start], %w[nv_uv_step] \n" + "dup z23.h, %w[limit] \n" + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.h \n" + "ptrue p2.h \n" + "1: \n" // + READP210_SVE NVTORGB_SVE + "subs %w[width], %w[width], %w[vl] \n" // + STOREAR30_SVE + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "whilelt p1.h, wzr, %w[width_last_y] \n" + "whilelt p2.h, wzr, %w[width_last_uv] \n" // + READP210_SVE NVTORGB_SVE STOREAR30_SVE + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_uv] "+r"(src_uv), // %[src_uv] + [dst_ar30] "+r"(dst_ar30), // %[dst_ar30] + [width] "+r"(width) // %[width] + : [vl] "r"(vl), // %[vl] + [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias), // %[kRGBCoeffBias] + [nv_uv_start] "r"(nv_uv_start), // %[nv_uv_start] + [nv_uv_step] "r"(nv_uv_step), // %[nv_uv_step] + [width_last_y] "r"(width_last_y), // %[width_last_y] + [width_last_uv] "r"(width_last_uv), // %[width_last_uv] + [limit] "r"(limit) // %[limit] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + +void P410ToARGBRow_SVE2(const uint16_t* src_y, + const uint16_t* src_uv, + uint8_t* dst_argb, + const struct YuvConstants* yuvconstants, + int width) { + uint64_t vl; + asm("cnth %0" : "=r"(vl)); + int width_last_y = width & (vl - 1); + width_last_y = width_last_y == 0 ? vl : width_last_y; + asm volatile( + "ptrue p0.b \n" YUVTORGB_SVE_SETUP + "dup z19.b, #255 \n" // A + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.h \n" + "ptrue p2.s \n" + "ptrue p3.s \n" + "1: \n" // + READP410_SVE NVTORGB_SVE RGBTOARGB8_SVE + "subs %w[width], %w[width], %w[vl] \n" + "st2h {z16.h, z17.h}, p1, [%[dst_argb]] \n" + "add %[dst_argb], %[dst_argb], %[vl], lsl #2 \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "whilelt p1.h, wzr, %w[width_last_y] \n" + "whilelt p2.s, wzr, %w[width_last_y] \n" + "cntw %x[vl] \n" + "whilelt p3.s, %w[vl], %w[width_last_y] \n" // + READP410_SVE NVTORGB_SVE RGBTOARGB8_SVE + "st2h {z16.h, z17.h}, p1, [%[dst_argb]] \n" + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_uv] "+r"(src_uv), // %[src_uv] + [dst_argb] "+r"(dst_argb), // %[dst_argb] + [width] "+r"(width) // %[width] + : [vl] "r"(vl), // %[vl] + [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias), // %[kRGBCoeffBias] + [width_last_y] "r"(width_last_y) // %[width_last_y] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + +void P410ToAR30Row_SVE2(const uint16_t* src_y, + const uint16_t* src_uv, + uint8_t* dst_ar30, + const struct YuvConstants* yuvconstants, + int width) { + uint64_t vl; + asm("cnth %0" : "=r"(vl)); + int width_last_y = width & (vl - 1); + width_last_y = width_last_y == 0 ? vl : width_last_y; + uint16_t limit = 0x3ff0; + asm volatile( + "ptrue p0.b \n" YUVTORGB_SVE_SETUP + "dup z23.h, %w[limit] \n" + "subs %w[width], %w[width], %w[vl] \n" + "b.lt 2f \n" + + // Run bulk of computation with an all-true predicate to avoid predicate + // generation overhead. + "ptrue p1.h \n" + "ptrue p2.s \n" + "ptrue p3.s \n" + "1: \n" // + READP410_SVE NVTORGB_SVE + "subs %w[width], %w[width], %w[vl] \n" // + STOREAR30_SVE + "b.ge 1b \n" + + "2: \n" + "adds %w[width], %w[width], %w[vl] \n" + "b.eq 99f \n" + + // Calculate a predicate for the final iteration to deal with the tail. + "whilelt p1.h, wzr, %w[width_last_y] \n" + "whilelt p2.s, wzr, %w[width_last_y] \n" + "cntw %x[vl] \n" + "whilelt p3.s, %w[vl], %w[width_last_y] \n" // + READP410_SVE NVTORGB_SVE STOREAR30_SVE + + "99: \n" + : [src_y] "+r"(src_y), // %[src_y] + [src_uv] "+r"(src_uv), // %[src_uv] + [dst_ar30] "+r"(dst_ar30), // %[dst_ar30] + [width] "+r"(width) // %[width] + : [vl] "r"(vl), // %[vl] + [kUVCoeff] "r"(&yuvconstants->kUVCoeff), // %[kUVCoeff] + [kRGBCoeffBias] "r"(&yuvconstants->kRGBCoeffBias), // %[kRGBCoeffBias] + [width_last_y] "r"(width_last_y), // %[width_last_y] + [limit] "r"(limit) // %[limit] + : "cc", "memory", YUVTORGB_SVE_REGS); +} + #endif // !defined(LIBYUV_DISABLE_SVE) && defined(__aarch64__) #ifdef __cplusplus diff --git a/libfenrir/src/main/jni/animation/libyuv/source/scale.cc b/libfenrir/src/main/jni/animation/libyuv/source/scale.cc index f4d1053e4..661224166 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/scale.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/scale.cc @@ -74,6 +74,13 @@ static void ScalePlaneDown2(int src_width, } } #endif +#if defined(HAS_SCALEROWDOWN2_SME) + if (TestCpuFlag(kCpuHasSME)) { + ScaleRowDown2 = filtering == kFilterNone ? ScaleRowDown2_SME + : filtering == kFilterLinear ? ScaleRowDown2Linear_SME + : ScaleRowDown2Box_SME; + } +#endif #if defined(HAS_SCALEROWDOWN2_SSSE3) if (TestCpuFlag(kCpuHasSSSE3)) { ScaleRowDown2 = diff --git a/libfenrir/src/main/jni/animation/libyuv/source/scale_argb.cc b/libfenrir/src/main/jni/animation/libyuv/source/scale_argb.cc index e95aa596f..0268b1afc 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/scale_argb.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/scale_argb.cc @@ -706,6 +706,11 @@ static int ScaleYUVToARGBBilinearUp(int src_width, I422ToARGBRow = I422ToARGBRow_SVE2; } #endif +#if defined(HAS_I422TOARGBROW_SME) + if (TestCpuFlag(kCpuHasSME)) { + I422ToARGBRow = I422ToARGBRow_SME; + } +#endif #if defined(HAS_I422TOARGBROW_MSA) if (TestCpuFlag(kCpuHasMSA)) { I422ToARGBRow = I422ToARGBRow_Any_MSA; diff --git a/libfenrir/src/main/jni/animation/libyuv/source/scale_neon64.cc b/libfenrir/src/main/jni/animation/libyuv/source/scale_neon64.cc index 2ad0c8152..c125c6c09 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/scale_neon64.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/scale_neon64.cc @@ -322,14 +322,11 @@ void ScaleRowDown34_1_Box_NEON(const uint8_t* src_ptr, static const uvec8 kShuf38 = {0, 3, 6, 8, 11, 14, 16, 19, 22, 24, 27, 30, 0, 0, 0, 0}; -static const uvec8 kShuf38_2 = {0, 16, 32, 2, 18, 33, 4, 20, - 34, 6, 22, 35, 0, 0, 0, 0}; -static const vec16 kMult38_Div6 = {65536 / 12, 65536 / 12, 65536 / 12, - 65536 / 12, 65536 / 12, 65536 / 12, - 65536 / 12, 65536 / 12}; -static const vec16 kMult38_Div9 = {65536 / 18, 65536 / 18, 65536 / 18, - 65536 / 18, 65536 / 18, 65536 / 18, - 65536 / 18, 65536 / 18}; +static const vec16 kMult38_Div664 = { + 65536 / 12, 65536 / 12, 65536 / 8, 65536 / 12, 65536 / 12, 65536 / 8, 0, 0}; +static const vec16 kMult38_Div996 = {65536 / 18, 65536 / 18, 65536 / 12, + 65536 / 18, 65536 / 18, 65536 / 12, + 0, 0}; // 32 -> 12 void ScaleRowDown38_NEON(const uint8_t* src_ptr, @@ -337,247 +334,163 @@ void ScaleRowDown38_NEON(const uint8_t* src_ptr, uint8_t* dst_ptr, int dst_width) { (void)src_stride; - asm volatile ( - "ld1 {v3.16b}, [%3] \n" - "1: \n" - "ld1 {v0.16b,v1.16b}, [%0], #32 \n" - "subs %w2, %w2, #12 \n" - "tbl v2.16b, {v0.16b,v1.16b}, v3.16b \n" - "prfm pldl1keep, [%0, 448] \n" // prefetch 7 lines ahead - "st1 {v2.8b}, [%1], #8 \n" - "st1 {v2.s}[2], [%1], #4 \n" - "b.gt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(dst_width) // %2 - : "r"(&kShuf38) // %3 + asm volatile( + "ld1 {v3.16b}, [%[kShuf38]] \n" + "subs %w[width], %w[width], #12 \n" + "b.eq 2f \n" + + "1: \n" + "ldp q0, q1, [%[src_ptr]], #32 \n" + "subs %w[width], %w[width], #12 \n" + "tbl v2.16b, {v0.16b, v1.16b}, v3.16b \n" + "prfm pldl1keep, [%[src_ptr], 448] \n" // prefetch 7 lines ahead + "str q2, [%[dst_ptr]] \n" + "add %[dst_ptr], %[dst_ptr], #12 \n" + "b.gt 1b \n" + + // Store exactly 12 bytes on the final iteration to avoid writing past + // the end of the array. + "2: \n" + "ldp q0, q1, [%[src_ptr]] \n" + "tbl v2.16b, {v0.16b, v1.16b}, v3.16b \n" + "st1 {v2.8b}, [%[dst_ptr]], #8 \n" + "st1 {v2.s}[2], [%[dst_ptr]] \n" + : [src_ptr] "+r"(src_ptr), // %[src_ptr] + [dst_ptr] "+r"(dst_ptr), // %[dst_ptr] + [width] "+r"(dst_width) // %[width] + : [kShuf38] "r"(&kShuf38) // %[kShuf38] : "memory", "cc", "v0", "v1", "v2", "v3"); } -// 32x3 -> 12x1 -void OMITFP ScaleRowDown38_3_Box_NEON(const uint8_t* src_ptr, - ptrdiff_t src_stride, - uint8_t* dst_ptr, - int dst_width) { - const uint8_t* src_ptr1 = src_ptr + src_stride * 2; - ptrdiff_t tmp_src_stride = src_stride; - - asm volatile ( - "ld1 {v29.8h}, [%5] \n" - "ld1 {v30.16b}, [%6] \n" - "ld1 {v31.8h}, [%7] \n" - "add %2, %2, %0 \n" - "1: \n" - - // 00 40 01 41 02 42 03 43 - // 10 50 11 51 12 52 13 53 - // 20 60 21 61 22 62 23 63 - // 30 70 31 71 32 72 33 73 - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" - "ld4 {v4.8b,v5.8b,v6.8b,v7.8b}, [%2], #32 \n" - "ld4 {v16.8b,v17.8b,v18.8b,v19.8b}, [%3], #32 \n" - "subs %w4, %w4, #12 \n" - - // Shuffle the input data around to get align the data - // so adjacent data can be added. 0,1 - 2,3 - 4,5 - 6,7 - // 00 10 01 11 02 12 03 13 - // 40 50 41 51 42 52 43 53 - "trn1 v20.8b, v0.8b, v1.8b \n" - "trn2 v21.8b, v0.8b, v1.8b \n" - "trn1 v22.8b, v4.8b, v5.8b \n" - "trn2 v23.8b, v4.8b, v5.8b \n" - "trn1 v24.8b, v16.8b, v17.8b \n" - "trn2 v25.8b, v16.8b, v17.8b \n" - - // 20 30 21 31 22 32 23 33 - // 60 70 61 71 62 72 63 73 - "trn1 v0.8b, v2.8b, v3.8b \n" - "trn2 v1.8b, v2.8b, v3.8b \n" - "trn1 v4.8b, v6.8b, v7.8b \n" - "trn2 v5.8b, v6.8b, v7.8b \n" - "trn1 v16.8b, v18.8b, v19.8b \n" - "trn2 v17.8b, v18.8b, v19.8b \n" - - // 00+10 01+11 02+12 03+13 - // 40+50 41+51 42+52 43+53 - "uaddlp v20.4h, v20.8b \n" - "uaddlp v21.4h, v21.8b \n" - "uaddlp v22.4h, v22.8b \n" - "uaddlp v23.4h, v23.8b \n" - "uaddlp v24.4h, v24.8b \n" - "uaddlp v25.4h, v25.8b \n" - - // 60+70 61+71 62+72 63+73 - "uaddlp v1.4h, v1.8b \n" - "uaddlp v5.4h, v5.8b \n" - "uaddlp v17.4h, v17.8b \n" - - // combine source lines - "add v20.4h, v20.4h, v22.4h \n" - "add v21.4h, v21.4h, v23.4h \n" - "add v20.4h, v20.4h, v24.4h \n" - "add v21.4h, v21.4h, v25.4h \n" - "add v2.4h, v1.4h, v5.4h \n" - "add v2.4h, v2.4h, v17.4h \n" - - // dst_ptr[3] = (s[6 + st * 0] + s[7 + st * 0] - // + s[6 + st * 1] + s[7 + st * 1] - // + s[6 + st * 2] + s[7 + st * 2]) / 6 - "sqrdmulh v2.8h, v2.8h, v29.8h \n" - "xtn v2.8b, v2.8h \n" - - // Shuffle 2,3 reg around so that 2 can be added to the - // 0,1 reg and 3 can be added to the 4,5 reg. This - // requires expanding from u8 to u16 as the 0,1 and 4,5 - // registers are already expanded. Then do transposes - // to get aligned. - // xx 20 xx 30 xx 21 xx 31 xx 22 xx 32 xx 23 xx 33 - "ushll v16.8h, v16.8b, #0 \n" - "uaddl v0.8h, v0.8b, v4.8b \n" - - // combine source lines - "add v0.8h, v0.8h, v16.8h \n" - - // xx 20 xx 21 xx 22 xx 23 - // xx 30 xx 31 xx 32 xx 33 - "trn1 v1.8h, v0.8h, v0.8h \n" - "trn2 v4.8h, v0.8h, v0.8h \n" - "xtn v0.4h, v1.4s \n" - "xtn v4.4h, v4.4s \n" - "prfm pldl1keep, [%0, 448] \n" // prefetch 7 lines ahead - - // 0+1+2, 3+4+5 - "add v20.8h, v20.8h, v0.8h \n" - "add v21.8h, v21.8h, v4.8h \n" - "prfm pldl1keep, [%2, 448] \n" - - // Need to divide, but can't downshift as the the value - // isn't a power of 2. So multiply by 65536 / n - // and take the upper 16 bits. - "sqrdmulh v0.8h, v20.8h, v31.8h \n" - "sqrdmulh v1.8h, v21.8h, v31.8h \n" - "prfm pldl1keep, [%3, 448] \n" - - // Align for table lookup, vtbl requires registers to be adjacent - "tbl v3.16b, {v0.16b, v1.16b, v2.16b}, v30.16b \n" +static const uvec8 kScaleRowDown38_3_BoxIndices1[] = { + 0, 1, 6, 7, 12, 13, 16, 17, 22, 23, 28, 29, 255, 255, 255, 255}; +static const uvec8 kScaleRowDown38_3_BoxIndices2[] = { + 2, 3, 8, 9, 14, 15, 18, 19, 24, 25, 30, 31, 255, 255, 255, 255}; +static const uvec8 kScaleRowDown38_3_BoxIndices3[] = { + 4, 5, 10, 11, 255, 255, 20, 21, 26, 27, 255, 255, 255, 255, 255, 255}; +static const uvec8 kScaleRowDown38_NarrowIndices[] = { + 0, 2, 4, 6, 8, 10, 16, 18, 20, 22, 24, 26, 255, 255, 255, 255}; - "st1 {v3.8b}, [%1], #8 \n" - "st1 {v3.s}[2], [%1], #4 \n" - "b.gt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(tmp_src_stride), // %2 - "+r"(src_ptr1), // %3 - "+r"(dst_width) // %4 - : "r"(&kMult38_Div6), // %5 - "r"(&kShuf38_2), // %6 - "r"(&kMult38_Div9) // %7 - : "memory", "cc", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16", - "v17", "v18", "v19", "v20", "v21", "v22", "v23", "v24", "v25", "v29", - "v30", "v31"); +void ScaleRowDown38_3_Box_NEON(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst_ptr, + int dst_width) { + const uint8_t* src_ptr1 = src_ptr + src_stride; + const uint8_t* src_ptr2 = src_ptr + src_stride * 2; + asm volatile( + "ld1 {v27.16b}, [%[tblArray1]] \n" + "ld1 {v28.16b}, [%[tblArray2]] \n" + "ld1 {v29.16b}, [%[tblArray3]] \n" + "ld1 {v31.16b}, [%[tblArray4]] \n" + "ld1 {v30.16b}, [%[div996]] \n" + + "1: \n" + "ldp q20, q0, [%[src_ptr]], #32 \n" + "ldp q21, q1, [%[src_ptr1]], #32 \n" + "ldp q22, q2, [%[src_ptr2]], #32 \n" + + "subs %w[width], %w[width], #12 \n" + + // Add across strided rows first. + "uaddl v23.8h, v20.8b, v21.8b \n" + "uaddl v3.8h, v0.8b, v1.8b \n" + "uaddl2 v24.8h, v20.16b, v21.16b \n" + "uaddl2 v4.8h, v0.16b, v1.16b \n" + + "uaddw v23.8h, v23.8h, v22.8b \n" + "uaddw v3.8h, v3.8h, v2.8b \n" + "uaddw2 v24.8h, v24.8h, v22.16b \n" // abcdefgh ... + "uaddw2 v4.8h, v4.8h, v2.16b \n" + + // Permute groups of {three,three,two} into separate vectors to sum. + "tbl v20.16b, {v23.16b, v24.16b}, v27.16b \n" // a d g ... + "tbl v0.16b, {v3.16b, v4.16b}, v27.16b \n" + "tbl v21.16b, {v23.16b, v24.16b}, v28.16b \n" // b e h ... + "tbl v1.16b, {v3.16b, v4.16b}, v28.16b \n" + "tbl v22.16b, {v23.16b, v24.16b}, v29.16b \n" // c f 0... + "tbl v2.16b, {v3.16b, v4.16b}, v29.16b \n" + + "add v23.8h, v20.8h, v21.8h \n" + "add v3.8h, v0.8h, v1.8h \n" + "add v24.8h, v23.8h, v22.8h \n" // a+b+c d+e+f g+h + "add v4.8h, v3.8h, v2.8h \n" + + "sqrdmulh v24.8h, v24.8h, v30.8h \n" // v /= {9,9,6} + "sqrdmulh v25.8h, v4.8h, v30.8h \n" + "tbl v21.16b, {v24.16b, v25.16b}, v31.16b \n" // Narrow. + "st1 {v21.d}[0], [%[dst_ptr]], #8 \n" + "st1 {v21.s}[2], [%[dst_ptr]], #4 \n" + "b.gt 1b \n" + : [src_ptr] "+r"(src_ptr), // %[src_ptr] + [dst_ptr] "+r"(dst_ptr), // %[dst_ptr] + [src_ptr1] "+r"(src_ptr1), // %[src_ptr1] + [src_ptr2] "+r"(src_ptr2), // %[src_ptr2] + [width] "+r"(dst_width) // %[width] + : [div996] "r"(&kMult38_Div996), // %[div996] + [tblArray1] "r"(kScaleRowDown38_3_BoxIndices1), // %[tblArray1] + [tblArray2] "r"(kScaleRowDown38_3_BoxIndices2), // %[tblArray2] + [tblArray3] "r"(kScaleRowDown38_3_BoxIndices3), // %[tblArray3] + [tblArray4] "r"(kScaleRowDown38_NarrowIndices) // %[tblArray4] + : "memory", "cc", "v0", "v1", "v2", "v3", "v4", "v20", "v21", "22", "23", + "24", "v27", "v28", "v29", "v30", "v31"); } -// 32x2 -> 12x1 +static const uvec8 kScaleRowDown38_2_BoxIndices1[] = { + 0, 1, 3, 4, 6, 7, 8, 9, 11, 12, 14, 15, 255, 255, 255, 255}; +static const uvec8 kScaleRowDown38_2_BoxIndices2[] = { + 2, 18, 5, 21, 255, 255, 10, 26, 13, 29, 255, 255, 255, 255, 255, 255}; + void ScaleRowDown38_2_Box_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, uint8_t* dst_ptr, int dst_width) { - // TODO(fbarchard): use src_stride directly for clang 3.5+. - ptrdiff_t tmp_src_stride = src_stride; - asm volatile ( - "ld1 {v30.8h}, [%4] \n" - "ld1 {v31.16b}, [%5] \n" - "add %2, %2, %0 \n" - "1: \n" - - // 00 40 01 41 02 42 03 43 - // 10 50 11 51 12 52 13 53 - // 20 60 21 61 22 62 23 63 - // 30 70 31 71 32 72 33 73 - "ld4 {v0.8b,v1.8b,v2.8b,v3.8b}, [%0], #32 \n" - "ld4 {v4.8b,v5.8b,v6.8b,v7.8b}, [%2], #32 \n" - "subs %w3, %w3, #12 \n" - - // Shuffle the input data around to get align the data - // so adjacent data can be added. 0,1 - 2,3 - 4,5 - 6,7 - // 00 10 01 11 02 12 03 13 - // 40 50 41 51 42 52 43 53 - "trn1 v16.8b, v0.8b, v1.8b \n" - "trn2 v17.8b, v0.8b, v1.8b \n" - "trn1 v18.8b, v4.8b, v5.8b \n" - "trn2 v19.8b, v4.8b, v5.8b \n" - - // 20 30 21 31 22 32 23 33 - // 60 70 61 71 62 72 63 73 - "trn1 v0.8b, v2.8b, v3.8b \n" - "trn2 v1.8b, v2.8b, v3.8b \n" - "trn1 v4.8b, v6.8b, v7.8b \n" - "trn2 v5.8b, v6.8b, v7.8b \n" - - // 00+10 01+11 02+12 03+13 - // 40+50 41+51 42+52 43+53 - "uaddlp v16.4h, v16.8b \n" - "uaddlp v17.4h, v17.8b \n" - "uaddlp v18.4h, v18.8b \n" - "uaddlp v19.4h, v19.8b \n" - - // 60+70 61+71 62+72 63+73 - "uaddlp v1.4h, v1.8b \n" - "uaddlp v5.4h, v5.8b \n" - - // combine source lines - "add v16.4h, v16.4h, v18.4h \n" - "add v17.4h, v17.4h, v19.4h \n" - "add v2.4h, v1.4h, v5.4h \n" - - // dst_ptr[3] = (s[6] + s[7] + s[6+st] + s[7+st]) / 4 - "uqrshrn v2.8b, v2.8h, #2 \n" - - // Shuffle 2,3 reg around so that 2 can be added to the - // 0,1 reg and 3 can be added to the 4,5 reg. This - // requires expanding from u8 to u16 as the 0,1 and 4,5 - // registers are already expanded. Then do transposes - // to get aligned. - // xx 20 xx 30 xx 21 xx 31 xx 22 xx 32 xx 23 xx 33 - - // combine source lines - "uaddl v0.8h, v0.8b, v4.8b \n" - - // xx 20 xx 21 xx 22 xx 23 - // xx 30 xx 31 xx 32 xx 33 - "trn1 v1.8h, v0.8h, v0.8h \n" - "trn2 v4.8h, v0.8h, v0.8h \n" - "xtn v0.4h, v1.4s \n" - "xtn v4.4h, v4.4s \n" - "prfm pldl1keep, [%0, 448] \n" // prefetch 7 lines ahead - - // 0+1+2, 3+4+5 - "add v16.8h, v16.8h, v0.8h \n" - "add v17.8h, v17.8h, v4.8h \n" - "prfm pldl1keep, [%2, 448] \n" - - // Need to divide, but can't downshift as the the value - // isn't a power of 2. So multiply by 65536 / n - // and take the upper 16 bits. - "sqrdmulh v0.8h, v16.8h, v30.8h \n" - "sqrdmulh v1.8h, v17.8h, v30.8h \n" - - // Align for table lookup, vtbl requires registers to - // be adjacent - - "tbl v3.16b, {v0.16b, v1.16b, v2.16b}, v31.16b \n" - - "st1 {v3.8b}, [%1], #8 \n" - "st1 {v3.s}[2], [%1], #4 \n" - "b.gt 1b \n" - : "+r"(src_ptr), // %0 - "+r"(dst_ptr), // %1 - "+r"(tmp_src_stride), // %2 - "+r"(dst_width) // %3 - : "r"(&kMult38_Div6), // %4 - "r"(&kShuf38_2) // %5 - : "memory", "cc", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v16", - "v17", "v18", "v19", "v30", "v31"); + const uint8_t* src_ptr1 = src_ptr + src_stride; + asm volatile( + "ld1 {v28.16b}, [%[tblArray1]] \n" + "ld1 {v29.16b}, [%[tblArray2]] \n" + "ld1 {v31.16b}, [%[tblArray3]] \n" + "ld1 {v30.8h}, [%[div664]] \n" + + "1: \n" + "ldp q20, q0, [%[src_ptr]], #32 \n" // abcdefgh ... + "ldp q21, q1, [%[src_ptr1]], #32 \n" // ijklmnop ... + "subs %w[width], %w[width], #12 \n" + + // Permute into groups of six values (three pairs) to be summed. + "tbl v22.16b, {v20.16b}, v28.16b \n" // abdegh ... + "tbl v2.16b, {v0.16b}, v28.16b \n" + "tbl v23.16b, {v21.16b}, v28.16b \n" // ijlmop ... + "tbl v3.16b, {v1.16b}, v28.16b \n" + "tbl v24.16b, {v20.16b, v21.16b}, v29.16b \n" // ckfn00 ... + "tbl v4.16b, {v0.16b, v1.16b}, v29.16b \n" + + "uaddlp v22.8h, v22.16b \n" // a+b d+e g+h ... + "uaddlp v2.8h, v2.16b \n" + "uaddlp v23.8h, v23.16b \n" // i+j l+m o+p ... + "uaddlp v3.8h, v3.16b \n" + "uaddlp v24.8h, v24.16b \n" // c+k f+n 0 ... + "uaddlp v4.8h, v4.16b \n" + "add v20.8h, v22.8h, v23.8h \n" + "add v0.8h, v2.8h, v3.8h \n" + "add v21.8h, v20.8h, v24.8h \n" // a+b+i+j+c+k ... + "add v1.8h, v0.8h, v4.8h \n" + + "sqrdmulh v21.8h, v21.8h, v30.8h \n" // v /= {6,6,4} + "sqrdmulh v22.8h, v1.8h, v30.8h \n" + "tbl v21.16b, {v21.16b, v22.16b}, v31.16b \n" // Narrow. + "st1 {v21.d}[0], [%[dst_ptr]], #8 \n" + "st1 {v21.s}[2], [%[dst_ptr]], #4 \n" + "b.gt 1b \n" + : [src_ptr] "+r"(src_ptr), // %[src_ptr] + [dst_ptr] "+r"(dst_ptr), // %[dst_ptr] + [src_ptr1] "+r"(src_ptr1), // %[src_ptr1] + [width] "+r"(dst_width) // %[width] + : [div664] "r"(&kMult38_Div664), // %[div664] + [tblArray1] "r"(kScaleRowDown38_2_BoxIndices1), // %[tblArray1] + [tblArray2] "r"(kScaleRowDown38_2_BoxIndices2), // %[tblArray2] + [tblArray3] "r"(kScaleRowDown38_NarrowIndices) // %[tblArray3] + : "memory", "cc", "v0", "v1", "v2", "v3", "v4", "v20", "v21", "v22", + "v23", "v24", "v28", "v29", "v30", "v31"); } void ScaleRowUp2_Linear_NEON(const uint8_t* src_ptr, @@ -1092,15 +1005,12 @@ void ScaleAddRow_NEON(const uint8_t* src_ptr, ); } -// TODO(Yang Zhang): Investigate less load instructions for -// the x/dx stepping -#define LOAD2_DATA8_LANE(n) \ - "lsr %5, %3, #16 \n" \ - "add %6, %1, %5 \n" \ - "add %3, %3, %4 \n" \ - "ld2 {v4.b, v5.b}[" #n "], [%6] \n" +#define SCALE_FILTER_COLS_STEP_ADDR \ + "lsr %[tmp_offset], %x[x], #16 \n" \ + "add %[tmp_ptr], %[src_ptr], %[tmp_offset] \n" \ + "add %x[x], %x[x], %x[dx] \n" -// The NEON version mimics this formula (from row_common.cc): +// The Neon version mimics this formula (from scale_common.cc): // #define BLENDER(a, b, f) (uint8_t)((int)(a) + // ((((int)((f)) * ((int)(b) - (int)(a))) + 0x8000) >> 16)) @@ -1110,65 +1020,69 @@ void ScaleFilterCols_NEON(uint8_t* dst_ptr, int x, int dx) { int dx_offset[4] = {0, 1, 2, 3}; - int* tmp = dx_offset; - const uint8_t* src_tmp = src_ptr; - int64_t x64 = (int64_t)x; // NOLINT - int64_t dx64 = (int64_t)dx; // NOLINT - asm volatile ( - "dup v0.4s, %w3 \n" // x - "dup v1.4s, %w4 \n" // dx - "ld1 {v2.4s}, [%5] \n" // 0 1 2 3 + int64_t tmp_offset; + uint8_t* tmp_ptr; + asm volatile( + "dup v0.4s, %w[x] \n" + "dup v1.4s, %w[dx] \n" + "ld1 {v2.4s}, [%[dx_offset]] \n" // 0 1 2 3 "shl v3.4s, v1.4s, #2 \n" // 4 * dx + "shl v22.4s, v1.4s, #3 \n" // 8 * dx + "mul v1.4s, v1.4s, v2.4s \n" - // x , x + 1 * dx, x + 2 * dx, x + 3 * dx + // x , x + 1 * dx, x + 2 * dx, x + 3 * dx "add v1.4s, v1.4s, v0.4s \n" - // x + 4 * dx, x + 5 * dx, x + 6 * dx, x + 7 * dx + // x + 4 * dx, x + 5 * dx, x + 6 * dx, x + 7 * dx "add v2.4s, v1.4s, v3.4s \n" - "shl v0.4s, v3.4s, #1 \n" // 8 * dx - "1: \n" - LOAD2_DATA8_LANE(0) - LOAD2_DATA8_LANE(1) - LOAD2_DATA8_LANE(2) - LOAD2_DATA8_LANE(3) - LOAD2_DATA8_LANE(4) - LOAD2_DATA8_LANE(5) - LOAD2_DATA8_LANE(6) - LOAD2_DATA8_LANE(7) - "mov v6.16b, v1.16b \n" - "mov v7.16b, v2.16b \n" - "uzp1 v6.8h, v6.8h, v7.8h \n" - "ushll v4.8h, v4.8b, #0 \n" - "ushll v5.8h, v5.8b, #0 \n" + + "movi v0.8h, #0 \n" + + // truncate to uint16_t + "trn1 v22.8h, v22.8h, v0.8h \n" + "trn1 v20.8h, v1.8h, v0.8h \n" + "trn1 v21.8h, v2.8h, v0.8h \n" + + "1: \n" SCALE_FILTER_COLS_STEP_ADDR + "ldr h6, [%[tmp_ptr]] \n" SCALE_FILTER_COLS_STEP_ADDR + "ld1 {v6.h}[1], [%[tmp_ptr]] \n" SCALE_FILTER_COLS_STEP_ADDR + "ld1 {v6.h}[2], [%[tmp_ptr]] \n" SCALE_FILTER_COLS_STEP_ADDR + "ld1 {v6.h}[3], [%[tmp_ptr]] \n" SCALE_FILTER_COLS_STEP_ADDR + "ld1 {v6.h}[4], [%[tmp_ptr]] \n" SCALE_FILTER_COLS_STEP_ADDR + "ld1 {v6.h}[5], [%[tmp_ptr]] \n" SCALE_FILTER_COLS_STEP_ADDR + "ld1 {v6.h}[6], [%[tmp_ptr]] \n" SCALE_FILTER_COLS_STEP_ADDR + "ld1 {v6.h}[7], [%[tmp_ptr]] \n" + + "subs %w[width], %w[width], #8 \n" // 8 processed per loop + "trn1 v4.16b, v6.16b, v0.16b \n" + "trn2 v5.16b, v6.16b, v0.16b \n" + "ssubl v16.4s, v5.4h, v4.4h \n" "ssubl2 v17.4s, v5.8h, v4.8h \n" - "ushll v7.4s, v6.4h, #0 \n" - "ushll2 v6.4s, v6.8h, #0 \n" - "mul v16.4s, v16.4s, v7.4s \n" - "mul v17.4s, v17.4s, v6.4s \n" + "mul v16.4s, v16.4s, v20.4s \n" + "mul v17.4s, v17.4s, v21.4s \n" "rshrn v6.4h, v16.4s, #16 \n" "rshrn2 v6.8h, v17.4s, #16 \n" "add v4.8h, v4.8h, v6.8h \n" "xtn v4.8b, v4.8h \n" - "st1 {v4.8b}, [%0], #8 \n" // store pixels - "add v1.4s, v1.4s, v0.4s \n" - "add v2.4s, v2.4s, v0.4s \n" - "subs %w2, %w2, #8 \n" // 8 processed per loop + "add v20.8h, v20.8h, v22.8h \n" + "add v21.8h, v21.8h, v22.8h \n" + + "st1 {v4.8b}, [%[dst_ptr]], #8 \n" // store pixels "b.gt 1b \n" - : "+r"(dst_ptr), // %0 - "+r"(src_ptr), // %1 - "+r"(dst_width), // %2 - "+r"(x64), // %3 - "+r"(dx64), // %4 - "+r"(tmp), // %5 - "+r"(src_tmp) // %6 - : - : "memory", "cc", "v0", "v1", "v2", "v3", - "v4", "v5", "v6", "v7", "v16", "v17" - ); + : [src_ptr] "+r"(src_ptr), // %[src_ptr] + [dst_ptr] "+r"(dst_ptr), // %[dst_ptr] + [width] "+r"(dst_width), // %[width] + [x] "+r"(x), // %[x] + [dx] "+r"(dx), // %[dx] + [tmp_offset] "=&r"(tmp_offset), // %[tmp_offset] + [tmp_ptr] "=&r"(tmp_ptr) // %[tmp_ptr] + : [dx_offset] "r"(dx_offset) // %[dx_offset] + : "memory", "cc", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v16", "v17", + "v20", "v21", "v22"); } -#undef LOAD2_DATA8_LANE +#undef SCALE_FILTER_COLS_STEP_ADDR void ScaleARGBRowDown2_NEON(const uint8_t* src_ptr, ptrdiff_t src_stride, diff --git a/libfenrir/src/main/jni/animation/libyuv/source/scale_sme.cc b/libfenrir/src/main/jni/animation/libyuv/source/scale_sme.cc new file mode 100644 index 000000000..fd364b316 --- /dev/null +++ b/libfenrir/src/main/jni/animation/libyuv/source/scale_sme.cc @@ -0,0 +1,292 @@ +/* + * Copyright 2024 The LibYuv Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "libyuv/scale_row.h" + +#ifdef __cplusplus +namespace libyuv { +extern "C" { +#endif + + +#if !defined(LIBYUV_DISABLE_SME) && defined(CLANG_HAS_SME) && \ + defined(__aarch64__) + +__arm_locally_streaming void ScaleRowDown2_SME(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width) { + // Streaming-SVE only, no use of ZA tile. + (void)src_stride; + int vl; + asm volatile( + "cntb %x[vl] \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "b.lt 2f \n" + + "1: \n" + "ptrue p0.b \n" + "ld2b {z0.b, z1.b}, p0/z, [%[src_ptr]] \n" + "incb %[src_ptr], all, mul #2 \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "st1b {z1.b}, p0, [%[dst_ptr]] \n" + "incb %[dst_ptr] \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[dst_width], %w[dst_width], %w[vl] \n" + "b.eq 99f \n" + + "whilelt p0.b, wzr, %w[dst_width] \n" + "ld2b {z0.b, z1.b}, p0/z, [%[src_ptr]] \n" + "st1b {z1.b}, p0, [%[dst_ptr]] \n" + + "99: \n" + : [src_ptr] "+r"(src_ptr), // %[src_ptr] + [dst_ptr] "+r"(dst), // %[dst_ptr] + [dst_width] "+r"(dst_width), // %[dst_width] + [vl] "=r"(vl) // %[vl] + : + : "memory", "cc", "z0", "z1", "p0"); +} + +__arm_locally_streaming void ScaleRowDown2Linear_SME(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width) { + // Streaming-SVE only, no use of ZA tile. + (void)src_stride; + int vl; + asm volatile( + "cntb %x[vl] \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "b.lt 2f \n" + + "1: \n" + "ptrue p0.b \n" + "ld2b {z0.b, z1.b}, p0/z, [%[src_ptr]] \n" + "incb %[src_ptr], all, mul #2 \n" + "urhadd z0.b, p0/m, z0.b, z1.b \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "st1b {z0.b}, p0, [%[dst_ptr]] \n" + "incb %[dst_ptr] \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[dst_width], %w[dst_width], %w[vl] \n" + "b.eq 99f \n" + + "whilelt p0.b, wzr, %w[dst_width] \n" + "ld2b {z0.b, z1.b}, p0/z, [%[src_ptr]] \n" + "urhadd z0.b, p0/m, z0.b, z1.b \n" + "st1b {z0.b}, p0, [%[dst_ptr]] \n" + + "99: \n" + : [src_ptr] "+r"(src_ptr), // %[src_ptr] + [dst_ptr] "+r"(dst), // %[dst_ptr] + [dst_width] "+r"(dst_width), // %[dst_width] + [vl] "=r"(vl) // %[vl] + : + : "memory", "cc", "z0", "z1", "p0"); +} + +#define SCALEROWDOWN2BOX_SVE \ + "ld2b {z0.b, z1.b}, p0/z, [%[src_ptr]] \n" \ + "ld2b {z2.b, z3.b}, p0/z, [%[src2_ptr]] \n" \ + "incb %[src_ptr], all, mul #2 \n" \ + "incb %[src2_ptr], all, mul #2 \n" \ + "uaddlb z4.h, z0.b, z1.b \n" \ + "uaddlt z5.h, z0.b, z1.b \n" \ + "uaddlb z6.h, z2.b, z3.b \n" \ + "uaddlt z7.h, z2.b, z3.b \n" \ + "add z4.h, z4.h, z6.h \n" \ + "add z5.h, z5.h, z7.h \n" \ + "rshrnb z0.b, z4.h, #2 \n" \ + "rshrnt z0.b, z5.h, #2 \n" \ + "subs %w[dst_width], %w[dst_width], %w[vl] \n" \ + "st1b {z0.b}, p0, [%[dst_ptr]] \n" \ + "incb %[dst_ptr] \n" + +__arm_locally_streaming void ScaleRowDown2Box_SME(const uint8_t* src_ptr, + ptrdiff_t src_stride, + uint8_t* dst, + int dst_width) { + // Streaming-SVE only, no use of ZA tile. + const uint8_t* src2_ptr = src_ptr + src_stride; + int vl; + asm volatile( + "cntb %x[vl] \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "b.lt 2f \n" + + "ptrue p0.b \n" + "1: \n" // + SCALEROWDOWN2BOX_SVE + "b.ge 1b \n" + + "2: \n" + "adds %w[dst_width], %w[dst_width], %w[vl] \n" + "b.eq 99f \n" + + "whilelt p0.b, wzr, %w[dst_width] \n" // + SCALEROWDOWN2BOX_SVE + + "99: \n" + : [src_ptr] "+r"(src_ptr), // %[src_ptr] + [src2_ptr] "+r"(src2_ptr), // %[src2_ptr] + [dst_ptr] "+r"(dst), // %[dst_ptr] + [dst_width] "+r"(dst_width), // %[dst_width] + [vl] "=r"(vl) // %[vl] + : + : "memory", "cc", "z0", "z1", "z2", "z3", "z4", "z5", "z6", "z7", "p0"); +} + +#undef SCALEROWDOWN2BOX_SVE + +__arm_locally_streaming void ScaleUVRowDown2_SME(const uint8_t* src_uv, + ptrdiff_t src_stride, + uint8_t* dst_uv, + int dst_width) { + // Streaming-SVE only, no use of ZA tile. + (void)src_stride; + int vl; + asm volatile( + "cnth %x[vl] \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "b.lt 2f \n" + + "1: \n" + "ptrue p0.b \n" + "ld2h {z0.h, z1.h}, p0/z, [%[src_uv]] \n" + "incb %[src_uv], all, mul #2 \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "st1h {z1.h}, p0, [%[dst_uv]] \n" + "incb %[dst_uv] \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[dst_width], %w[dst_width], %w[vl] \n" + "b.eq 99f \n" + + "whilelt p0.h, wzr, %w[dst_width] \n" + "ld2h {z0.h, z1.h}, p0/z, [%[src_uv]] \n" + "st1h {z1.h}, p0, [%[dst_uv]] \n" + + "99: \n" + : [src_uv] "+r"(src_uv), // %[src_uv] + [dst_uv] "+r"(dst_uv), // %[dst_uv] + [dst_width] "+r"(dst_width), // %[dst_width] + [vl] "=r"(vl) // %[vl] + : + : "memory", "cc", "z0", "z1", "p0"); +} + +__arm_locally_streaming void ScaleUVRowDown2Linear_SME(const uint8_t* src_uv, + ptrdiff_t src_stride, + uint8_t* dst_uv, + int dst_width) { + // Streaming-SVE only, no use of ZA tile. + (void)src_stride; + int vl; + asm volatile( + "cnth %x[vl] \n" + "ptrue p1.b \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "b.lt 2f \n" + + "ptrue p0.h \n" + "1: \n" + "ld2h {z0.h, z1.h}, p0/z, [%[src_uv]] \n" + "incb %[src_uv], all, mul #2 \n" + "urhadd z0.b, p1/m, z0.b, z1.b \n" + "st1h {z0.h}, p0, [%[dst_uv]] \n" + "incb %[dst_uv], all, mul #1 \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[dst_width], %w[dst_width], %w[vl] \n" + "b.eq 99f \n" + + "whilelt p0.h, wzr, %w[dst_width] \n" + "ld2h {z0.h, z1.h}, p0/z, [%[src_uv]] \n" + "urhadd z0.b, p1/m, z0.b, z1.b \n" + "st1h {z0.h}, p0, [%[dst_uv]] \n" + + "99: \n" + : [src_uv] "+r"(src_uv), // %[src_uv] + [dst_uv] "+r"(dst_uv), // %[dst_uv] + [dst_width] "+r"(dst_width), // %[dst_width] + [vl] "=r"(vl) // %[vl] + : + : "z0", "z1", "p0", "p1"); +} + +#define SCALEUVROWDOWN2BOX_SVE \ + "ld2h {z0.h, z1.h}, p0/z, [%[src_uv]] \n" \ + "ld2h {z2.h, z3.h}, p0/z, [%[src2_uv]] \n" \ + "incb %[src_uv], all, mul #2 \n" \ + "incb %[src2_uv], all, mul #2 \n" \ + "uaddlb z4.h, z0.b, z1.b \n" \ + "uaddlt z5.h, z0.b, z1.b \n" \ + "uaddlb z6.h, z2.b, z3.b \n" \ + "uaddlt z7.h, z2.b, z3.b \n" \ + "add z4.h, z4.h, z6.h \n" \ + "add z5.h, z5.h, z7.h \n" \ + "rshrnb z0.b, z4.h, #2 \n" \ + "rshrnt z0.b, z5.h, #2 \n" \ + "st1h {z0.h}, p0, [%[dst_uv]] \n" \ + "incb %[dst_uv], all, mul #1 \n" + +__arm_locally_streaming void ScaleUVRowDown2Box_SME(const uint8_t* src_uv, + ptrdiff_t src_stride, + uint8_t* dst_uv, + int dst_width) { + // Streaming-SVE only, no use of ZA tile. + const uint8_t* src2_uv = src_uv + src_stride; + int vl; + asm volatile( + "cnth %x[vl] \n" + "ptrue p1.b \n" + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "b.lt 2f \n" + + "ptrue p0.h \n" + "1: \n" // + SCALEUVROWDOWN2BOX_SVE + "subs %w[dst_width], %w[dst_width], %w[vl] \n" + "b.ge 1b \n" + + "2: \n" + "adds %w[dst_width], %w[dst_width], %w[vl] \n" + "b.eq 99f \n" + + "whilelt p0.h, wzr, %w[dst_width] \n" // + SCALEUVROWDOWN2BOX_SVE + + "99: \n" + : [src_uv] "+r"(src_uv), // %[src_uv] + [src2_uv] "+r"(src2_uv), // %[src2_uv] + [dst_uv] "+r"(dst_uv), // %[dst_uv] + [dst_width] "+r"(dst_width), // %[dst_width] + [vl] "=r"(vl) // %[vl] + : + : "z0", "z1", "z2", "z3", "z4", "z5", "z6", "z7", "p0", "p1"); +} + +#undef SCALEUVROWDOWN2BOX_SVE + +#endif // !defined(LIBYUV_DISABLE_SME) && defined(CLANG_HAS_SME) && + // defined(__aarch64__) + +#ifdef __cplusplus +} // extern "C" +} // namespace libyuv +#endif diff --git a/libfenrir/src/main/jni/animation/libyuv/source/scale_uv.cc b/libfenrir/src/main/jni/animation/libyuv/source/scale_uv.cc index 5c55aab51..9ef2e1387 100644 --- a/libfenrir/src/main/jni/animation/libyuv/source/scale_uv.cc +++ b/libfenrir/src/main/jni/animation/libyuv/source/scale_uv.cc @@ -104,14 +104,6 @@ static void ScaleUVDown2(int src_width, } } #endif -#if defined(HAS_SCALEUVROWDOWN2BOX_NEON) - if (TestCpuFlag(kCpuHasNEON) && filtering) { - ScaleUVRowDown2 = ScaleUVRowDown2Box_Any_NEON; - if (IS_ALIGNED(dst_width, 8)) { - ScaleUVRowDown2 = ScaleUVRowDown2Box_NEON; - } - } -#endif #if defined(HAS_SCALEUVROWDOWN2_NEON) if (TestCpuFlag(kCpuHasNEON)) { ScaleUVRowDown2 = @@ -128,6 +120,13 @@ static void ScaleUVDown2(int src_width, } } #endif +#if defined(HAS_SCALEUVROWDOWN2_SME) + if (TestCpuFlag(kCpuHasSME)) { + ScaleUVRowDown2 = filtering == kFilterNone ? ScaleUVRowDown2_SME + : filtering == kFilterLinear ? ScaleUVRowDown2Linear_SME + : ScaleUVRowDown2Box_SME; + } +#endif #if defined(HAS_SCALEUVROWDOWN2_RVV) if (TestCpuFlag(kCpuHasRVV)) { ScaleUVRowDown2 = @@ -242,6 +241,11 @@ static int ScaleUVDown4Box(int src_width, } } #endif +#if defined(HAS_SCALEUVROWDOWN2BOX_SME) + if (TestCpuFlag(kCpuHasSME)) { + ScaleUVRowDown2 = ScaleUVRowDown2Box_SME; + } +#endif #if defined(HAS_SCALEUVROWDOWN2BOX_RVV) if (TestCpuFlag(kCpuHasRVV)) { ScaleUVRowDown2 = ScaleUVRowDown2Box_RVV; diff --git a/libfenrir/src/main/jni/animation/lottie_jni.cpp b/libfenrir/src/main/jni/animation/lottie_jni.cpp deleted file mode 100644 index 56092daa3..000000000 --- a/libfenrir/src/main/jni/animation/lottie_jni.cpp +++ /dev/null @@ -1,337 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "fenrir_native.h" -#include "gif_builder.h" - -using namespace rlottie; - -class LottieInfo { -public: - std::unique_ptr animation; - std::string path; -}; - -std::string doDecompressResource(size_t length, char *bytes, bool &orig) { - orig = false; - std::string data; - if (length >= GZIP_HEADER_LENGTH && memcmp(bytes, GZIP_HEADER, GZIP_HEADER_LENGTH) == 0) { - z_stream zs; - memset(&zs, 0, sizeof(zs)); - - if (inflateInit2(&zs, 15 + 16) != Z_OK) { - return ""; - } - - zs.next_in = (Bytef *) bytes; - zs.avail_in = length; - - int ret; - std::vector outBuffer(32768); - - do { - zs.next_out = reinterpret_cast(outBuffer.data()); - zs.avail_out = outBuffer.size(); - ret = inflate(&zs, 0); - if (data.size() < zs.total_out) { - data.append(outBuffer.data(), zs.total_out - data.size()); - } - - } while (ret == Z_OK); - inflateEnd(&zs); - if (ret != Z_STREAM_END) { - return ""; - } - } else if (length >= MY_LZ4_HEADER_LENGTH && - memcmp(bytes, MY_LZ4_HEADER, MY_LZ4_HEADER_LENGTH) == 0) { - MY_LZ4HDR_PUSH hdr = {}; - memcpy(&hdr, bytes, sizeof(MY_LZ4HDR_PUSH)); - data.resize(hdr.size); - LZ4_decompress_safe(((const char *) bytes + sizeof(MY_LZ4HDR_PUSH)), (char *) data.data(), - (int) length - (int) sizeof(MY_LZ4HDR_PUSH), (int) hdr.size); - } else { - orig = true; - } - return data; -} - -extern "C" JNIEXPORT jlong -Java_dev_ragnarok_fenrir_module_rlottie_RLottieDrawable_create(JNIEnv *env, jobject, jstring src, - jintArray data, - jintArray colorReplacement, - jboolean useMoveColor) { - auto *info = new LottieInfo(); - internal::ColorReplace *colors = nullptr; - if (colorReplacement != nullptr) { - jint *arr = env->GetIntArrayElements(colorReplacement, nullptr); - if (arr != nullptr) { - jsize len = env->GetArrayLength(colorReplacement); - if (len % 2 == 0) { - colors = new internal::ColorReplace(useMoveColor); - for (int32_t a = 0; a < len / 2; a++) { - colors->colorMap[arr[a * 2]] = arr[a * 2 + 1]; - } - } - env->ReleaseIntArrayElements(colorReplacement, arr, 0); - } - } - - char const *srcString = SafeGetStringUTFChars(env, src, nullptr); - info->path = srcString; - if (srcString != nullptr) { - env->ReleaseStringUTFChars(src, srcString); - } - std::ifstream f; - f.open(info->path); - if (!f.is_open()) { - delete info; - return 0; - } - f.seekg(0, std::ios::end); - auto length = f.tellg(); - f.seekg(0, std::ios::beg); - if (length <= 0) { - f.close(); - delete info; - return 0; - } - auto *arr = new char[(size_t) length + 1]; - f.read(arr, length); - f.close(); - arr[length] = '\0'; - bool orig; - std::string jsonString = doDecompressResource(length, arr, orig); - if (orig) { - info->animation = rlottie::Animation::loadFromData(arr, colors); - } - delete[] arr; - if (!orig) { - if (jsonString.empty()) { - delete info; - return 0; - } - info->animation = rlottie::Animation::loadFromData(jsonString.c_str(), colors); - } - if (info->animation == nullptr) { - delete info; - return 0; - } - - jint *dataArr = env->GetIntArrayElements(data, nullptr); - if (dataArr != nullptr) { - dataArr[0] = (jint) info->animation->totalFrame(); - dataArr[1] = (jint) info->animation->frameRate(); - env->ReleaseIntArrayElements(data, dataArr, 0); - } - return (jlong) (intptr_t) info; -} - -extern "C" JNIEXPORT jlong -Java_dev_ragnarok_fenrir_module_rlottie_RLottieDrawable_createWithJson(JNIEnv *env, jobject, - jlong json, - jintArray data, - jintArray colorReplacement, - jboolean useMoveColor) { - internal::ColorReplace *colors = nullptr; - if (colorReplacement != nullptr) { - jint *arr = env->GetIntArrayElements(colorReplacement, nullptr); - if (arr != nullptr) { - jsize len = env->GetArrayLength(colorReplacement); - if (len % 2 == 0) { - colors = new internal::ColorReplace(useMoveColor); - for (int32_t a = 0; a < len / 2; a++) { - colors->colorMap[arr[a * 2]] = arr[a * 2 + 1]; - } - } - env->ReleaseIntArrayElements(colorReplacement, arr, 0); - } - } - - auto *info = new LottieInfo(); - auto u = ((std::vector *) (intptr_t) json); - bool orig; - std::string jsonString = doDecompressResource(u->size(), u->data(), orig); - if (orig) { - info->animation = rlottie::Animation::loadFromData(u->data(), colors); - } else { - info->animation = rlottie::Animation::loadFromData(jsonString.c_str(), colors); - } - if (info->animation == nullptr) { - delete info; - return 0; - } - - jint *dataArr = env->GetIntArrayElements(data, nullptr); - if (dataArr != nullptr) { - dataArr[0] = (int) info->animation->totalFrame(); - dataArr[1] = (int) info->animation->frameRate(); - env->ReleaseIntArrayElements(data, dataArr, 0); - } - return (jlong) (intptr_t) info; -} - -extern "C" JNIEXPORT void -Java_dev_ragnarok_fenrir_module_rlottie_RLottieDrawable_destroy(JNIEnv *, jobject, jlong ptr) { - if (!ptr) { - return; - } - auto *info = (LottieInfo *) (intptr_t) ptr; - delete info; -} - -extern "C" JNIEXPORT void -Java_dev_ragnarok_fenrir_module_rlottie_RLottieDrawable_setLayerColor(JNIEnv *env, jobject, - jlong ptr, - jstring layer, jint color) { - if (!ptr || layer == nullptr) { - return; - } - auto *info = (LottieInfo *) (intptr_t) ptr; - char const *layerString = SafeGetStringUTFChars(env, layer, nullptr); - Color v((float) ((color) & 0xff) / 255.0f, - (float) ((color >> 8) & 0xff) / - 255.0f, - (float) ((color >> 16) & 0xff) / - 255.0f); - - info->animation->setValue(layerString, v); - info->animation->setValue(layerString, v); - if (layerString != nullptr) { - env->ReleaseStringUTFChars(layer, layerString); - } -} - -extern "C" JNIEXPORT void -Java_dev_ragnarok_fenrir_module_rlottie_RLottieDrawable_replaceColors(JNIEnv *env, jobject, - jlong ptr, - jintArray colorReplacement) { - if (!ptr || colorReplacement == nullptr) { - return; - } - auto *info = (LottieInfo *) (intptr_t) ptr; - if (!info->animation->colorMap || info->animation->colorMap->useMoveColor) { - return; - } - jint *arr = env->GetIntArrayElements(colorReplacement, nullptr); - if (arr != nullptr) { - jsize len = env->GetArrayLength(colorReplacement); - if (len % 2 == 0) { - for (int32_t a = 0; a < len / 2; a++) { - info->animation->colorMap->colorMap[arr[a * 2]] = arr[a * 2 + 1]; - } - info->animation->resetCurrentFrame(); - } - env->ReleaseIntArrayElements(colorReplacement, arr, 0); - } -} - -extern "C" JNIEXPORT jint -Java_dev_ragnarok_fenrir_module_rlottie_RLottieDrawable_getFrame(JNIEnv *env, jobject, jlong ptr, - jint frame, - jobject bitmap, jint w, jint h, - jint stride, - jboolean clear) { - if (!ptr || !bitmap || w <= 0 || h <= 0) { - return 0; - } - auto *info = (LottieInfo *) (intptr_t) ptr; - - void *pixels; - if (AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0) { - Surface surface((uint32_t *) pixels, (size_t) w, (size_t) h, (size_t) stride); - info->animation->renderSync((size_t) frame, surface, clear); - AndroidBitmap_unlockPixels(env, bitmap); - } - return frame; -} - -class Lottie2Gif { -public: - static bool render(LottieInfo *player, jobject bitmap, int w, int h, int stride, int bgColor, - bool transparent, const std::string &gifName, int bitDepth, - bool dither, JNIEnv *env, jobject listener) { - void *pixels; - if (AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0) { - size_t frameCount = player->animation->totalFrame(); - uint32_t delay = 2; - GifBuilder builder(gifName, w, h, bgColor, delay, bitDepth, dither); - size_t start = 0, end = frameCount; - - if (listener != nullptr) { - jweak store_Wlistener = env->NewWeakGlobalRef(listener); - jclass clazz = env->GetObjectClass(store_Wlistener); - - jmethodID mth_update = env->GetMethodID(clazz, "onProgress", "(II)V"); - jmethodID mth_start = env->GetMethodID(clazz, "onStarted", "()V"); - jmethodID mth_end = env->GetMethodID(clazz, "onFinished", "()V"); - - env->CallVoidMethod(store_Wlistener, mth_start); - - for (size_t i = start; i < end; i++) { - rlottie::Surface surface((uint32_t *) pixels, (size_t) w, (size_t) h, - (size_t) stride); - player->animation->renderSync(i, surface, true); - builder.addFrame(surface.buffer(), surface.width(), surface.height(), - surface.bytesPerLine(), transparent, delay, bitDepth, dither); - - env->CallVoidMethod(store_Wlistener, mth_update, (jint) (i + 1), - (jint) frameCount); - } - - env->CallVoidMethod(store_Wlistener, mth_end); - } else { - for (size_t i = start; i < end; i++) { - rlottie::Surface surface((uint32_t *) pixels, (size_t) w, (size_t) h, - (size_t) stride); - player->animation->renderSync(i, surface, true); - builder.addFrame(surface.buffer(), surface.width(), surface.height(), - surface.bytesPerLine(), transparent, delay, bitDepth, dither); - } - } - - AndroidBitmap_unlockPixels(env, bitmap); - return true; - } - return false; - } - -}; - -extern "C" JNIEXPORT -jboolean -Java_dev_ragnarok_fenrir_module_rlottie_RLottie2Gif_lottie2gif(JNIEnv *env, jobject, jlong json, - jobject bitmap, jint w, jint h, - jint stride, jint bgColor, - jboolean transparent, - jstring gifName, - jint bitDepth, - jboolean dither, - jobject listener) { - if (!json) { - return false; - } - auto *info = new LottieInfo(); - auto u = ((std::vector *) (intptr_t) json); - bool orig; - std::string jsonString = doDecompressResource(u->size(), u->data(), orig); - if (orig) { - info->animation = rlottie::Animation::loadFromData(u->data(), nullptr); - } else { - info->animation = rlottie::Animation::loadFromData(jsonString.c_str(), nullptr); - } - if (info->animation == nullptr) { - delete info; - return 0; - } - - char const *name = SafeGetStringUTFChars(env, gifName, nullptr); - return Lottie2Gif::render(info, bitmap, w, h, stride, bgColor, (bool) transparent, name, - bitDepth, dither, env, listener); -} \ No newline at end of file diff --git a/libfenrir/src/main/jni/animation/rlottie/inc/colorreplace.h b/libfenrir/src/main/jni/animation/rlottie/inc/colorreplace.h deleted file mode 100644 index 0e4e463d3..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/inc/colorreplace.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef _ColorReplace_H_ -#define _ColorReplace_H_ -#include - -#define RLOTTIE_API - -namespace rlottie { - namespace internal { - class RLOTTIE_API ColorReplace { - public: - ColorReplace(bool useMoveColor = false) { - this->useMoveColor = useMoveColor; - } - ColorReplace(const std::map&colorMap, bool useMoveColor = false) { - this->colorMap = colorMap; - this->useMoveColor = useMoveColor; - } - std::mapcolorMap; - bool useMoveColor; - }; - } -} -#endif diff --git a/libfenrir/src/main/jni/animation/rlottie/inc/rlottie.h b/libfenrir/src/main/jni/animation/rlottie/inc/rlottie.h deleted file mode 100644 index 4cb41dfa3..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/inc/rlottie.h +++ /dev/null @@ -1,456 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef _RLOTTIE_H_ -#define _RLOTTIE_H_ - -#include -#include -#include -#include -#include "colorreplace.h" - -#define RLOTTIE_API - -class AnimationImpl; -struct LOTNode; -struct LOTLayerNode; - -namespace rlottie { - -/** - * @brief Configures rlottie model cache policy. - * - * Provides Library level control to configure model cache - * policy. Setting it to 0 will disable - * the cache as well as flush all the previously cached content. - * - * @param[in] cacheSize Maximum Model Cache size. - * - * @note to disable Caching configure with 0 size. - * @note to flush the current Cache content configure it with 0 and - * then reconfigure with the new size. - * - * @internal - */ - -struct Color { - Color() = default; - Color(float r, float g , float b):_r(r), _g(g), _b(b){} - float r() const {return _r;} - float g() const {return _g;} - float b() const {return _b;} -private: - float _r{0}; - float _g{0}; - float _b{0}; -}; - -struct Size { - Size() = default; - Size(float w, float h):_w(w), _h(h){} - float w() const {return _w;} - float h() const {return _h;} -private: - float _w{0}; - float _h{0}; -}; - -struct Point { - Point() = default; - Point(float x, float y):_x(x), _y(y){} - float x() const {return _x;} - float y() const {return _y;} -private: - float _x{0}; - float _y{0}; -}; - -struct FrameInfo { - explicit FrameInfo(uint32_t frame): _frameNo(frame){} - uint32_t curFrame() const {return _frameNo;} -private: - uint32_t _frameNo; -}; - -enum class Property { - FillColor, /*!< Color property of Fill object , value type is rlottie::Color */ - FillOpacity, /*!< Opacity property of Fill object , value type is float [ 0 .. 100] */ - StrokeColor, /*!< Color property of Stroke object , value type is rlottie::Color */ - StrokeOpacity, /*!< Opacity property of Stroke object , value type is float [ 0 .. 100] */ - StrokeWidth, /*!< stroke width property of Stroke object , value type is float */ - TrAnchor, /*!< Transform Anchor property of Layer and Group object , value type is rlottie::Point */ - TrPosition, /*!< Transform Position property of Layer and Group object , value type is rlottie::Point */ - TrScale, /*!< Transform Scale property of Layer and Group object , value type is rlottie::Size. range[0 ..100] */ - TrRotation, /*!< Transform Rotation property of Layer and Group object , value type is float. range[0 .. 360] in degrees*/ - TrOpacity, /*!< Transform Opacity property of Layer and Group object , value type is float [ 0 .. 100] */ - TrimStart, /*!< Trim Start property of Shape object , value type is float [ 0 .. 100] */ - TrimEnd /*!< Trim End property of Shape object , value type is rlottie::Point [ 0 .. 100] */ -}; - -struct Color_Type{}; -struct Point_Type{}; -struct Size_Type{}; -struct Float_Type{}; -template struct MapType; - -class RLOTTIE_API Surface { -public: - /** - * @brief Surface object constructor. - * - * @param[in] buffer surface buffer. - * @param[in] width surface width. - * @param[in] height surface height. - * @param[in] bytesPerLine number of bytes in a surface scanline. - * - * @note Default surface format is ARGB32_Premultiplied. - * - * @internal - */ - Surface(uint32_t *buffer, size_t width, size_t height, size_t bytesPerLine); - - /** - * @brief Sets the Draw Area available on the Surface. - * - * Lottie will use the draw region size to generate frame image - * and will update only the draw rgion of the surface. - * - * @param[in] x region area x position. - * @param[in] y region area y position. - * @param[in] width region area width. - * @param[in] height region area height. - * - * @note Default surface format is ARGB32_Premultiplied. - * @note Default draw region area is [ 0 , 0, surface width , surface height] - * - * @internal - */ - void setDrawRegion(size_t x, size_t y, size_t width, size_t height); - - /** - * @brief Returns width of the surface. - * - * @return surface width - * - * @internal - * - */ - size_t width() const {return mWidth;} - - /** - * @brief Returns height of the surface. - * - * @return surface height - * - * @internal - */ - size_t height() const {return mHeight;} - - /** - * @brief Returns number of bytes in the surface scanline. - * - * @return number of bytes in scanline. - * - * @internal - */ - size_t bytesPerLine() const {return mBytesPerLine;} - - /** - * @brief Returns buffer attached tp the surface. - * - * @return buffer attaced to the Surface. - * - * @internal - */ - uint32_t *buffer() const {return mBuffer;} - - /** - * @brief Returns drawable area width of the surface. - * - * @return drawable area width - * - * @note Default value is width() of the surface - * - * @internal - * - */ - size_t drawRegionWidth() const {return mDrawArea.w;} - - /** - * @brief Returns drawable area height of the surface. - * - * @return drawable area height - * - * @note Default value is height() of the surface - * - * @internal - */ - size_t drawRegionHeight() const {return mDrawArea.h;} - - /** - * @brief Returns drawable area's x position of the surface. - * - * @return drawable area's x potition. - * - * @note Default value is 0 - * - * @internal - */ - size_t drawRegionPosX() const {return mDrawArea.x;} - - /** - * @brief Returns drawable area's y position of the surface. - * - * @return drawable area's y potition. - * - * @note Default value is 0 - * - * @internal - */ - size_t drawRegionPosY() const {return mDrawArea.y;} - - /** - * @brief Default constructor. - */ - Surface() = default; -private: - uint32_t *mBuffer{nullptr}; - size_t mWidth{0}; - size_t mHeight{0}; - size_t mBytesPerLine{0}; - struct { - size_t x{0}; - size_t y{0}; - size_t w{0}; - size_t h{0}; - }mDrawArea; -}; - -using MarkerList = std::vector>; -/** - * @brief https://helpx.adobe.com/after-effects/using/layer-markers-composition-markers.html - * Markers exported form AE are used to describe a segmnet of an animation {comment/tag , startFrame, endFrame} - * Marker can be use to devide a resource in to separate animations by tagging the segment with comment string , - * start frame and duration of that segment. - */ - -using LayerInfoList = std::vector>; - - -class RLOTTIE_API Animation { -public: - /** - * @brief Constructs an animation object from JSON string data. - * - * @param[in] jsonData The JSON string data. - * @param[in] key the string that will be used to cache the JSON string data. - * @param[in] resourcePath the path will be used to search for external resource. - * @param[in] cachePolicy whether to cache or not the model data. - * use only when need to explicit disabl caching for a - * particular resource. To disable caching at library level - * use @see configureModelCacheSize() instead. - * - * @return Animation object that can render the contents of the - * Lottie resource represented by JSON string data. - * - * @internal - */ - static std::unique_ptr - loadFromData(const char* jsonData, internal::ColorReplace *colorReplacement = nullptr, const std::string &resourcePath = std::string()); - - /** - * @brief Returns default framerate of the Lottie resource. - * - * @return framerate of the Lottie resource - * - * @internal - * - */ - double frameRate() const; - - /** - * @brief Returns total number of frames present in the Lottie resource. - * - * @return frame count of the Lottie resource. - * - * @note frame number starts with 0. - * - * @internal - */ - size_t totalFrame() const; - - /** - * @brief Returns default viewport size of the Lottie resource. - * - * @param[out] width default width of the viewport. - * @param[out] height default height of the viewport. - * - * @internal - * - */ - void size(size_t &width, size_t &height) const; - - /** - * @brief Returns total animation duration of Lottie resource in second. - * it uses totalFrame() and frameRate() to calculate the duration. - * duration = totalFrame() / frameRate(). - * - * @return total animation duration in second. - * @retval 0 if the Lottie resource has no animation. - * - * @see totalFrame() - * @see frameRate() - * - * @internal - */ - double duration() const; - - /** - * @brief Returns frame number for a given position. - * this function helps to map the position value retuned - * by the animator to a frame number in side the Lottie resource. - * frame_number = lerp(start_frame, endframe, pos); - * - * @param[in] pos normalized position value [0 ... 1] - * - * @return frame numer maps to the position value [startFrame .... endFrame] - * - * @internal - */ - size_t frameAtPos(double pos); - - /** - * @brief Renders the content to surface synchronously. - * for performance use the async rendering @see render - * - * @param[in] frameNo Content corresponds to the @p frameNo needs to be drawn - * @param[in] surface Surface in which content will be drawn - * - * @internal - */ - void renderSync(size_t frameNo, Surface &surface, bool clear, bool* result = nullptr); - - /** - * @brief Returns root layer of the composition updated with - * content of the Lottie resource at frame number @p frameNo. - * - * @param[in] frameNo Content corresponds to the @p frameNo needs to be extracted. - * @param[in] width content viewbox width - * @param[in] height content viewbox height - * - * @return Root layer node. - * - * @internal - */ - const LOTLayerNode * renderTree(size_t frameNo, size_t width, size_t height) const; - - /** - * @brief Returns Composition Markers. - * - * - * @return returns MarkerList of the Composition. - * - * @see MarkerList - * @internal - */ - const MarkerList& markers() const; - - /** - * @brief Returns Layer information{name, inFrame, outFrame} of all the child layers of the composition. - * - * - * @return List of Layer Information of the Composition. - * - * @see LayerInfoList - * @internal - */ - const LayerInfoList& layers() const; - - /** - * @brief Sets property value for the specified {@link KeyPath}. This {@link KeyPath} can resolve - * to multiple contents. In that case, the callback's value will apply to all of them. - * - * Keypath should conatin object names separated by (.) and can handle globe(**) or wildchar(*). - * - * @usage - * To change fillcolor property of fill1 object in the layer1->group1->fill1 hirarchy to RED color - * - * player->setValue("layer1.group1.fill1", rlottie::Color(1, 0, 0); - * - * if all the color property inside group1 needs to be changed to GREEN color - * - * player->setValue("**.group1.**", rlottie::Color(0, 1, 0); - * - * @internal - */ - template - void setValue(const std::string &keypath, AnyValue value) - { - setValue(MapType>{}, prop, keypath, value); - } - - /** - * @brief default destructor - * - * @internal - */ - ~Animation(); - - internal::ColorReplace *colorMap{nullptr}; - void resetCurrentFrame(); -private: - void setValue(Color_Type, Property, const std::string &, Color); - void setValue(Float_Type, Property, const std::string &, float); - void setValue(Size_Type, Property, const std::string &, Size); - void setValue(Point_Type, Property, const std::string &, Point); - - void setValue(Color_Type, Property, const std::string &, std::function &&); - void setValue(Float_Type, Property, const std::string &, std::function &&); - void setValue(Size_Type, Property, const std::string &, std::function &&); - void setValue(Point_Type, Property, const std::string &, std::function &&); - /** - * @brief default constructor - * - * @internal - */ - Animation(); - - std::unique_ptr d; -}; - -//Map Property to Value type -template<> struct MapType>: Color_Type{}; -template<> struct MapType>: Color_Type{}; -template<> struct MapType>: Float_Type{}; -template<> struct MapType>: Float_Type{}; -template<> struct MapType>: Float_Type{}; -template<> struct MapType>: Float_Type{}; -template<> struct MapType>: Float_Type{}; -template<> struct MapType>: Point_Type{}; -template<> struct MapType>: Point_Type{}; -template<> struct MapType>: Size_Type{}; -template<> struct MapType>: Float_Type{}; -template<> struct MapType>: Point_Type{}; -} // namespace lotplayer - -#endif // _RLOTTIE_H_ diff --git a/libfenrir/src/main/jni/animation/rlottie/inc/rlottiecommon.h b/libfenrir/src/main/jni/animation/rlottie/inc/rlottiecommon.h deleted file mode 100644 index c5671a3b6..000000000 --- a/libfenrir/src/main/jni/animation/rlottie/inc/rlottiecommon.h +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. - - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef _RLOTTIE_COMMON_H_ -#define _RLOTTIE_COMMON_H_ - -#define RLOTTIE_API - -/** - * @defgroup Lottie_Animation Lottie_Animation - * - * Lottie Animation is a modern style vector based animation design. Its animation - * resource(within json format) could be generated by Adobe After Effect using - * bodymovin plugin. You can find a good examples in Lottie Community which - * shares many free resources(see: www.lottiefiles.com). - * - * This Lottie_Animation is a common engine to manipulate, control Lottie - * Animation from the Lottie resource - json file. It provides a scene-graph - * node tree per frames by user demand as well as rasterized frame images. - * - */ - -/** - * @ingroup Lottie_Animation - */ - -typedef enum -{ - BrushSolid = 0, - BrushGradient -} LOTBrushType; - -typedef enum -{ - FillEvenOdd = 0, - FillWinding -} LOTFillRule; - -typedef enum -{ - JoinMiter = 0, - JoinBevel, - JoinRound -} LOTJoinStyle; - -typedef enum -{ - CapFlat = 0, - CapSquare, - CapRound -} LOTCapStyle; - -typedef enum -{ - GradientLinear = 0, - GradientRadial -} LOTGradientType; - -typedef struct LOTGradientStop -{ - float pos; - unsigned char r, g, b, a; -} LOTGradientStop; - -typedef enum -{ - MaskAdd = 0, - MaskSubstract, - MaskIntersect, - MaskDifference -} LOTMaskType; - -typedef struct LOTMask { - struct { - const float *ptPtr; - size_t ptCount; - const char* elmPtr; - size_t elmCount; - } mPath; - LOTMaskType mMode; - unsigned char mAlpha; -}LOTMask; - -typedef enum -{ - MatteNone = 0, - MatteAlpha, - MatteAlphaInv, - MatteLuma, - MatteLumaInv -} LOTMatteType; - -typedef struct LOTMarker { - char *name; - size_t startframe; - size_t endframe; -} LOTMarker; - -typedef struct LOTMarkerList { - LOTMarker *ptr; - size_t size; -} LOTMarkerList; - -typedef struct LOTNode { - -#define ChangeFlagNone 0x0000 -#define ChangeFlagPath 0x0001 -#define ChangeFlagPaint 0x0010 -#define ChangeFlagAll (ChangeFlagPath & ChangeFlagPaint) - - struct { - const float *ptPtr; - size_t ptCount; - const char *elmPtr; - size_t elmCount; - } mPath; - - struct { - unsigned char r, g, b, a; - } mColor; - - struct { - unsigned char enable; - float width; - LOTCapStyle cap; - LOTJoinStyle join; - float miterLimit; - float *dashArray; - int dashArraySize; - } mStroke; - - struct { - LOTGradientType type; - LOTGradientStop *stopPtr; - size_t stopCount; - struct { - float x, y; - } start, end, center, focal; - float cradius; - float fradius; - } mGradient; - - struct { - unsigned char *data; - size_t width; - size_t height; - unsigned char mAlpha; - struct { - float m11; float m12; float m13; - float m21; float m22; float m23; - float m31; float m32; float m33; - } mMatrix; - } mImageInfo; - - int mFlag; - LOTBrushType mBrushType; - LOTFillRule mFillRule; - - const char *keypath; -} LOTNode; - - - -typedef struct LOTLayerNode { - - struct { - LOTMask *ptr; - size_t size; - } mMaskList; - - struct { - const float *ptPtr; - size_t ptCount; - const char *elmPtr; - size_t elmCount; - } mClipPath; - - struct { - struct LOTLayerNode **ptr; - size_t size; - } mLayerList; - - struct { - LOTNode **ptr; - size_t size; - } mNodeList; - - LOTMatteType mMatte; - int mVisible; - unsigned char mAlpha; - const char *keypath; - -} LOTLayerNode; - -/** - * @} - */ - -#endif // _RLOTTIE_COMMON_H_ diff --git a/libfenrir/src/main/jni/animation/rlottie/rlottie-master.zip b/libfenrir/src/main/jni/animation/rlottie/rlottie-master.zip deleted file mode 100644 index 033b9258afbe8824b2b19d6608311bfb7dec4935..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4303852 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 z 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

wKd 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

  • x3$eY= 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
    e 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

    7Q+dfc^{jcd?V9%h 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