From 5b815f9c160211101c5a08174d7bbf7274f53105 Mon Sep 17 00:00:00 2001 From: Koen Date: Mon, 25 Sep 2023 17:18:43 +0200 Subject: [PATCH] Initial source commit. --- .gitlab-ci.yml | 36 + .gitmodules | 63 + CONTRIBUTION.md | 60 + README.md | 205 ++ app/.gitignore | 1 + app/build.gradle | 207 ++ app/proguard-rules.pro | 21 + .../platformplayer/EncryptionProviderTests.kt | 62 + .../platformplayer/RequireMigrationTests.kt | 35 + .../com/futo/platformplayer/SignatureTests.kt | 68 + .../futo/platformplayer/StatePlatformTests.kt | 79 + app/src/main/AndroidManifest.xml | 187 ++ .../devportal/dependencies/FutoMainLogo.svg | 1 + app/src/main/assets/devportal/dev_bridge.js | 337 +++ app/src/main/assets/devportal/dev_test.html | 9 + app/src/main/assets/devportal/index.html | 897 +++++++ app/src/main/assets/devportal/plugin.d.ts | 364 +++ app/src/main/assets/scripts/polyfil.js | 177 ++ app/src/main/assets/scripts/source.js | 691 ++++++ app/src/main/ic_launcher-playstore.png | Bin 0 -> 41107 bytes .../platformplayer/Extensions_Coroutines.kt | 62 + .../platformplayer/Extensions_Formatting.kt | 327 +++ .../futo/platformplayer/Extensions_Network.kt | 275 +++ .../platformplayer/Extensions_Polycentric.kt | 38 + .../futo/platformplayer/Extensions_Syntax.kt | 15 + .../com/futo/platformplayer/Extensions_V8.kt | 131 + .../java/com/futo/platformplayer/Settings.kt | 606 +++++ .../com/futo/platformplayer/SettingsDev.kt | 334 +++ .../futo/platformplayer/SignatureProvider.kt | 56 + .../java/com/futo/platformplayer/UIDialogs.kt | 356 +++ .../futo/platformplayer/UISlideOverlays.kt | 419 ++++ .../java/com/futo/platformplayer/Utility.kt | 159 ++ .../activities/AddSourceActivity.kt | 221 ++ .../activities/AddSourceOptionsActivity.kt | 51 + .../activities/DeveloperActivity.kt | 36 + .../activities/ExceptionActivity.kt | 140 ++ .../activities/LoginActivity.kt | 117 + .../platformplayer/activities/MainActivity.kt | 906 +++++++ .../activities/ManageTabsActivity.kt | 99 + .../activities/PolycentricBackupActivity.kt | 175 ++ .../PolycentricCreateProfileActivity.kt | 93 + .../activities/PolycentricHomeActivity.kt | 94 + .../PolycentricImportProfileActivity.kt | 129 + .../activities/PolycentricProfileActivity.kt | 283 +++ .../activities/PolycentricWhyActivity.kt | 41 + .../activities/SettingsActivity.kt | 93 + .../platformplayer/activities/TestActivity.kt | 16 + .../api/http/ManagedHttpClient.kt | 276 +++ .../api/http/server/HttpBridge.kt | 9 + .../api/http/server/HttpContext.kt | 285 +++ .../api/http/server/HttpHeaders.kt | 25 + .../api/http/server/HttpResponse.kt | 115 + .../api/http/server/ManagedHttpServer.kt | 260 ++ .../exceptions/EmptyRequestException.kt | 6 + .../exceptions/KeepAliveTimeoutException.kt | 3 + .../server/handlers/HttpConstantHandler.kt | 14 + .../http/server/handlers/HttpFileHandler.kt | 107 + .../server/handlers/HttpFunctionHandler.kt | 10 + .../api/http/server/handlers/HttpHandler.kt | 24 + .../handlers/HttpOptionsAllowHandler.kt | 20 + .../http/server/handlers/HttpProxyHandler.kt | 95 + .../api/media/CachedPlatformClient.kt | 105 + .../api/media/IPlatformClient.kt | 155 ++ .../api/media/IPluginSourced.kt | 7 + .../api/media/LiveChatManager.kt | 223 ++ .../api/media/PlatformClientCapabilities.kt | 21 + .../api/media/PlatformClientPool.kt | 66 + .../platformplayer/api/media/PlatformID.kt | 53 + .../platformplayer/api/media/Serializer.kt | 9 + .../exceptions/APIRequestFailedException.kt | 4 + .../exceptions/AlreadyQueuedException.kt | 5 + .../ContentNotAvailableYetException.kt | 5 + .../exceptions/NoPlatformClientException.kt | 3 + .../api/media/exceptions/NotFoundException.kt | 4 + .../exceptions/UnknownPlatformException.kt | 4 + .../exceptions/search/NoNextPageException.kt | 5 + .../api/media/models/PlatformAuthorLink.kt | 40 + .../api/media/models/ResultCapabilities.kt | 102 + .../api/media/models/Thumbnails.kt | 45 + .../media/models/channels/IPlatformChannel.kt | 20 + .../models/channels/SerializedChannel.kt | 55 + .../media/models/comments/IPlatformComment.kt | 19 + .../media/models/comments/NoCommentsPager.kt | 12 + .../media/models/comments/PlatformComment.kt | 30 + .../comments/PolycentricPlatformComment.kt | 42 + .../api/media/models/contents/ContentType.kt | 29 + .../media/models/contents/IPlatformContent.kt | 18 + .../contents/IPlatformContentDetails.kt | 13 + .../contents/PlatformContentDeferred.kt | 15 + .../contents/PlatformContentPlaceholder.kt | 15 + .../models/live/ILiveChatWindowDescriptor.kt | 6 + .../models/live/ILiveEventChatMessage.kt | 8 + .../media/models/live/IPlatformLiveEvent.kt | 32 + .../api/media/models/live/LiveEventComment.kt | 42 + .../media/models/live/LiveEventDonation.kt | 50 + .../api/media/models/live/LiveEventEmojis.kt | 23 + .../api/media/models/live/LiveEventRaid.kt | 29 + .../api/media/models/live/LiveEventType.kt | 16 + .../media/models/live/LiveEventViewCount.kt | 23 + .../models/nested/IPlatformNestedContent.kt | 16 + .../media/models/playback/IPlaybackTracker.kt | 10 + .../models/playlists/IPlatformPlaylist.kt | 11 + .../playlists/IPlatformPlaylistDetails.kt | 12 + .../api/media/models/post/IPlatformPost.kt | 13 + .../media/models/post/IPlatformPostDetails.kt | 18 + .../api/media/models/post/TextType.kt | 19 + .../api/media/models/ratings/IRating.kt | 28 + .../models/ratings/RatingLikeDislikes.kt | 20 + .../api/media/models/ratings/RatingLikes.kt | 19 + .../api/media/models/ratings/RatingScaler.kt | 19 + .../api/media/models/ratings/RatingType.kt | 14 + .../models/streams/IVideoSourceDescriptor.kt | 8 + .../LocalVideoMuxedSourceDescriptor.kt | 10 + .../LocalVideoUnMuxedSourceDescriptor.kt | 11 + .../streams/VideoMuxedSourceDescriptor.kt | 10 + .../streams/VideoUnMuxedSourceDescriptor.kt | 12 + .../models/streams/sources/AudioUrlSource.kt | 46 + .../streams/sources/DashManifestSource.kt | 18 + .../streams/sources/HLSManifestSource.kt | 18 + .../models/streams/sources/IAudioSource.kt | 11 + .../models/streams/sources/IAudioUrlSource.kt | 5 + .../streams/sources/IDashManifestSource.kt | 5 + .../streams/sources/IHLSManifestSource.kt | 8 + .../models/streams/sources/IVideoSource.kt | 12 + .../models/streams/sources/IVideoUrlSource.kt | 5 + .../streams/sources/LocalAudioSource.kt | 48 + .../streams/sources/LocalSubtitleSource.kt | 40 + .../streams/sources/LocalVideoSource.kt | 52 + .../streams/sources/SubtitleRawSource.kt | 21 + .../models/streams/sources/VideoUrlSource.kt | 48 + .../sources/other/IStreamMetaDataSource.kt | 5 + .../streams/sources/other/StreamMetaData.kt | 9 + .../media/models/subtitles/ISubtitleSource.kt | 16 + .../api/media/models/video/IPlatformVideo.kt | 16 + .../models/video/IPlatformVideoDetails.kt | 28 + .../video/ISerializedVideoSourceDescriptor.kt | 27 + .../models/video/SerializedPlatformContent.kt | 25 + .../video/SerializedPlatformNestedContent.kt | 66 + .../models/video/SerializedPlatformPost.kt | 56 + .../models/video/SerializedPlatformVideo.kt | 58 + .../video/SerializedPlatformVideoDetails.kt | 81 + .../SerializedVideoMuxedSourceDescriptor.kt | 13 + .../SerializedVideoUnmuxedSourceDescriptor.kt | 15 + .../api/media/platforms/js/DevJSClient.kt | 189 ++ .../api/media/platforms/js/JSClient.kt | 593 +++++ .../api/media/platforms/js/SourceAuth.kt | 50 + .../platforms/js/SourcePluginAuthConfig.kt | 14 + .../media/platforms/js/SourcePluginConfig.kt | 138 ++ .../platforms/js/SourcePluginDescriptor.kt | 81 + .../api/media/platforms/js/internal/JSDocs.kt | 19 + .../platforms/js/internal/JSHttpClient.kt | 158 ++ .../media/platforms/js/models/IJSContent.kt | 30 + .../platforms/js/models/IJSContentDetails.kt | 22 + .../media/platforms/js/models/JSChannel.kt | 46 + .../platforms/js/models/JSChannelPager.kt | 16 + .../media/platforms/js/models/JSComment.kt | 65 + .../platforms/js/models/JSCommentPager.kt | 16 + .../media/platforms/js/models/JSContent.kt | 54 + .../platforms/js/models/JSContentPager.kt | 17 + .../js/models/JSLiveChatWindowDescriptor.kt | 25 + .../platforms/js/models/JSLiveEventPager.kt | 25 + .../js/models/JSNestedMediaContent.kt | 39 + .../api/media/platforms/js/models/JSPager.kt | 75 + .../platforms/js/models/JSPlaybackTracker.kt | 60 + .../media/platforms/js/models/JSPlaylist.kt | 19 + .../platforms/js/models/JSPlaylistDetails.kt | 37 + .../platforms/js/models/JSPlaylistPager.kt | 16 + .../api/media/platforms/js/models/JSPost.kt | 33 + .../platforms/js/models/JSPostDetails.kt | 59 + .../media/platforms/js/models/JSRequest.kt | 18 + .../platforms/js/models/JSRequestModifier.kt | 42 + .../platforms/js/models/JSSubtitleSource.kt | 64 + .../api/media/platforms/js/models/JSVideo.kt | 30 + .../platforms/js/models/JSVideoDetails.kt | 106 + .../media/platforms/js/models/JSVideoPager.kt | 15 + .../js/models/sources/JSAudioUrlSource.kt | 44 + .../sources/JSAudioWithMetadataSource.kt | 35 + .../js/models/sources/JSDashManifestSource.kt | 35 + .../sources/JSHLSManifestAudioSource.kt | 42 + .../js/models/sources/JSHLSManifestSource.kt | 35 + .../platforms/js/models/sources/JSSource.kt | 89 + .../sources/JSUnMuxVideoSourceDescriptor.kt | 29 + .../models/sources/JSVideoSourceDescriptor.kt | 40 + .../js/models/sources/JSVideoUrlSource.kt | 43 + .../sources/JSVideoWithMetadataSource.kt | 33 + .../api/media/structures/DedupContentPager.kt | 86 + .../api/media/structures/EmptyPager.kt | 19 + .../api/media/structures/IAsyncPager.kt | 12 + .../api/media/structures/INestedPager.kt | 8 + .../api/media/structures/IPager.kt | 10 + .../structures/IPlatformLiveEventPager.kt | 10 + .../api/media/structures/IRefreshPager.kt | 15 + .../api/media/structures/IReplacerPager.kt | 7 + .../api/media/structures/MultiAsyncPager.kt | 165 ++ .../structures/MultiChronoContentPager.kt | 25 + .../MultiDistributionChannelPager.kt | 48 + .../MultiDistributionContentAsyncPager.kt | 56 + .../MultiDistributionContentPager.kt | 47 + .../MultiDistributionContentParallelPager.kt | 49 + .../api/media/structures/MultiPager.kt | 141 ++ .../media/structures/MultiParallelPager.kt | 170 ++ .../api/media/structures/MultiRefreshPager.kt | 92 + .../api/media/structures/PlaceholderPager.kt | 25 + .../media/structures/PlatformContentPager.kt | 39 + .../structures/RefreshDedupContentPager.kt | 36 + .../RefreshDistributionContentPager.kt | 18 + .../api/media/structures/ReusablePager.kt | 98 + .../media/structures/SingleAsyncItemPager.kt | 113 + .../api/media/structures/SingleItemPager.kt | 46 + .../background/BackgroundWorker.kt | 114 + .../platformplayer/builders/DashBuilder.kt | 176 ++ .../platformplayer/builders/HlsBuilder.kt | 37 + .../platformplayer/builders/XMLBuilder.kt | 59 + .../cache/ChannelContentCache.kt | 153 ++ .../casting/AirPlayCastingDevice.kt | 305 +++ .../platformplayer/casting/CastingDevice.kt | 94 + .../casting/ChomecastCastingDevice.kt | 606 +++++ .../casting/FastCastCastingDevice.kt | 407 ++++ .../platformplayer/casting/StateCasting.kt | 758 ++++++ .../platformplayer/casting/models/FastCast.kt | 33 + .../constructs/BackgroundTaskHandler.kt | 70 + .../constructs/BatchedTaskHandler.kt | 73 + .../futo/platformplayer/constructs/Event.kt | 146 ++ .../platformplayer/constructs/TaskHandler.kt | 112 + .../futo/platformplayer/debug/Stopwatch.kt | 16 + .../developer/DeveloperEndpoints.kt | 434 ++++ .../dialogs/AutoUpdateDialog.kt | 208 ++ .../dialogs/AutomaticBackupDialog.kt | 88 + .../dialogs/AutomaticRestoreDialog.kt | 89 + .../dialogs/CastingAddDialog.kt | 138 ++ .../platformplayer/dialogs/ChangelogDialog.kt | 134 ++ .../platformplayer/dialogs/CommentDialog.kt | 104 + .../dialogs/ConnectCastingDialog.kt | 160 ++ .../dialogs/ConnectedCastingDialog.kt | 128 + .../platformplayer/dialogs/ImportDialog.kt | 210 ++ .../platformplayer/dialogs/MigrateDialog.kt | 223 ++ .../platformplayer/dialogs/ProgressDialog.kt | 62 + .../downloads/PlaylistDownloadDescriptor.kt | 8 + .../platformplayer/downloads/VideoDownload.kt | 593 +++++ .../platformplayer/downloads/VideoExport.kt | 261 ++ .../platformplayer/downloads/VideoLocal.kt | 116 + .../encryption/EncryptionProvider.kt | 69 + .../futo/platformplayer/engine/V8Plugin.kt | 265 ++ .../platformplayer/engine/V8PluginConfig.kt | 29 + .../engine/dev/V8RemoteObject.kt | 143 ++ .../engine/exceptions/NoInternetException.kt | 17 + .../engine/exceptions/PluginException.kt | 7 + .../engine/exceptions/ScriptAgeException.kt | 17 + .../exceptions/ScriptCompilationException.kt | 14 + .../engine/exceptions/ScriptException.kt | 17 + .../exceptions/ScriptExecutionException.kt | 17 + .../ScriptImplementationException.kt | 14 + .../exceptions/ScriptTimeoutException.kt | 13 + .../exceptions/ScriptUnavailableException.kt | 14 + .../exceptions/ScriptValidationException.kt | 3 + .../engine/internal/IV8Object.kt | 9 + .../engine/internal/V8BindObject.kt | 33 + .../engine/internal/V8Converter.kt | 28 + .../engine/packages/PackageBridge.kt | 65 + .../engine/packages/PackageDOMParser.kt | 153 ++ .../engine/packages/PackageHttp.kt | 474 ++++ .../engine/packages/PackageUtilities.kt | 29 + .../engine/packages/V8Package.kt | 25 + .../exceptions/ChannelException.kt | 19 + .../exceptions/MigrationException.kt | 5 + .../exceptions/ReconstructionException.kt | 5 + .../channel/tab/ChannelAboutFragment.kt | 155 ++ .../channel/tab/ChannelContentsFragment.kt | 344 +++ .../channel/tab/ChannelListFragment.kt | 149 ++ .../tab/ChannelMonetizationFragment.kt | 79 + .../channel/tab/ChannelStoreFragment.kt | 33 + .../channel/tab/IChannelTabFragment.kt | 7 + .../mainactivity/MainActivityFragment.kt | 52 + .../mainactivity/bottombar/BotFragment.kt | 7 + .../bottombar/MenuBottomBarFragment.kt | 367 +++ .../mainactivity/main/BrowserFragment.kt | 62 + .../fragment/mainactivity/main/BuyFragment.kt | 153 ++ .../mainactivity/main/ChannelFragment.kt | 425 ++++ .../mainactivity/main/ContentFeedView.kt | 202 ++ .../main/ContentSearchResultsFragment.kt | 289 +++ .../mainactivity/main/CreatorFeedView.kt | 51 + .../main/CreatorSearchResultsFragment.kt | 116 + .../mainactivity/main/CreatorsFragment.kt | 56 + .../mainactivity/main/DownloadsFragment.kt | 183 ++ .../fragment/mainactivity/main/FeedView.kt | 425 ++++ .../mainactivity/main/HistoryFragment.kt | 92 + .../mainactivity/main/HomeFragment.kt | 165 ++ .../main/ImportPlaylistsFragment.kt | 203 ++ .../main/ImportSubscriptionsFragment.kt | 201 ++ .../mainactivity/main/MainFragment.kt | 99 + .../mainactivity/main/PlaylistFragment.kt | 365 +++ .../main/PlaylistSearchResultsFragment.kt | 120 + .../mainactivity/main/PlaylistsFragment.kt | 180 ++ .../mainactivity/main/PostDetailFragment.kt | 710 ++++++ .../mainactivity/main/SourceDetailFragment.kt | 450 ++++ .../mainactivity/main/SourcesFragment.kt | 239 ++ .../main/SubscriptionsFeedFragment.kt | 302 +++ .../mainactivity/main/SuggestionsFragment.kt | 195 ++ .../mainactivity/main/VideoDetailFragment.kt | 470 ++++ .../mainactivity/main/VideoDetailView.kt | 2123 +++++++++++++++++ .../mainactivity/main/VideoListEditorView.kt | 139 ++ .../mainactivity/main/WatchLaterFragment.kt | 81 + .../mainactivity/topbar/AddTopBarFragment.kt | 49 + .../topbar/GeneralTopBarFragment.kt | 71 + .../topbar/ImportTopBarFragment.kt | 87 + .../topbar/NavigationTopBarFragment.kt | 98 + .../topbar/SearchTopBarFragment.kt | 222 ++ .../mainactivity/topbar/TopFragment.kt | 15 + .../helpers/DashManifestCreatorsUtils.kt | 228 ++ .../futo/platformplayer/helpers/FileHelper.kt | 12 + .../helpers/ManifestCreatorCache.kt | 64 + .../helpers/ProgressiveDashManifestCreator.kt | 78 + .../platformplayer/helpers/VideoHelper.kt | 156 ++ .../futo/platformplayer/images/GlideHelper.kt | 38 + .../images/GrayjayAppGlideModule.java | 18 + .../images/PolycentricModelLoader.java | 89 + .../listeners/OrientationManager.kt | 47 + .../logging/AndroidLogConsumer.kt | 27 + .../platformplayer/logging/FileLogConsumer.kt | 95 + .../com/futo/platformplayer/logging/Logger.kt | 89 + .../futo/platformplayer/logging/Logging.kt | 77 + .../models/CastingDeviceInfo.kt | 18 + .../futo/platformplayer/models/DiskUsage.kt | 8 + .../platformplayer/models/HistoryVideo.kt | 21 + .../platformplayer/models/ImageVariable.kt | 51 + .../models/PlatformVideoWithTime.kt | 5 + .../futo/platformplayer/models/Playlist.kt | 61 + .../models/PlaylistDownloaded.kt | 8 + .../futo/platformplayer/models/SearchType.kt | 7 + .../platformplayer/models/Subscription.kt | 31 + .../futo/platformplayer/models/Telemetry.kt | 16 + .../platformplayer/models/UrlVideoWithTime.kt | 3 + .../futo/platformplayer/others/Language.kt | 20 + .../others/LoginWebViewClient.kt | 183 ++ .../others/PlatformLinkMovementMethod.kt | 74 + .../polycentric/PolycentricCache.kt | 292 +++ .../receivers/AudioNoisyReceiver.kt | 19 + .../receivers/InstallReceiver.kt | 46 + .../receivers/MediaControlReceiver.kt | 69 + .../serializers/FlexibleBooleanSerializer.kt | 49 + .../serializers/IRatingSerializer.kt | 39 + .../serializers/OffsetDateTimeSerializer.kt | 42 + .../serializers/PlatformContentSerializer.kt | 37 + .../serializers/VideoDescriptorSerializer.kt | 19 + .../services/DownloadService.kt | 291 +++ .../services/ExportingService.kt | 225 ++ .../services/MediaPlaybackService.kt | 385 +++ .../states/StateAnnouncement.kt | 353 +++ .../futo/platformplayer/states/StateApp.kt | 578 +++++ .../futo/platformplayer/states/StateAssets.kt | 67 + .../futo/platformplayer/states/StateBackup.kt | 405 ++++ .../platformplayer/states/StateDeveloper.kt | 133 ++ .../platformplayer/states/StateDownloads.kt | 396 +++ .../futo/platformplayer/states/StateMeta.kt | 34 + .../platformplayer/states/StatePayment.kt | 37 + .../platformplayer/states/StatePlatform.kt | 875 +++++++ .../futo/platformplayer/states/StatePlayer.kt | 505 ++++ .../platformplayer/states/StatePlaylists.kt | 264 ++ .../platformplayer/states/StatePlugins.kt | 426 ++++ .../platformplayer/states/StatePolycentric.kt | 328 +++ .../futo/platformplayer/states/StateSaved.kt | 52 + .../states/StateSubscriptions.kt | 375 +++ .../platformplayer/states/StateTelemetry.kt | 77 + .../futo/platformplayer/states/StateUpdate.kt | 278 +++ .../stores/CastingDeviceInfoStorage.kt | 47 + .../stores/FragmentedStorage.kt | 229 ++ .../stores/FragmentedStorageDirectory.kt | 40 + .../stores/FragmentedStorageFile.kt | 73 + .../stores/FragmentedStorageFileJson.kt | 11 + .../stores/FragmentedStorageFileString.kt | 12 + .../futo/platformplayer/stores/MapStorage.kt | 31 + .../stores/PluginIconStorage.kt | 42 + .../stores/PluginScriptsDirectory.kt | 17 + .../platformplayer/stores/PluginStorage.kt | 15 + .../stores/SearchHistoryStorage.kt | 26 + .../stores/StringArrayStorage.kt | 44 + .../stores/StringHashSetStorage.kt | 50 + .../platformplayer/stores/StringStorage.kt | 26 + .../stores/SubscriptionStorage.kt | 32 + .../stores/WatchLaterStorage.kt | 15 + .../platformplayer/stores/v2/IStoreItem.kt | 5 + .../platformplayer/stores/v2/ManagedStore.kt | 500 ++++ .../stores/v2/ReconstructStore.kt | 33 + .../platformplayer/video/PlayerManager.kt | 108 + .../platformplayer/views/AnyAdapterView.kt | 87 + .../futo/platformplayer/views/FeedStyle.kt | 25 + .../com/futo/platformplayer/views/Loader.kt | 58 + .../views/adapters/AnyAdapter.kt | 181 ++ .../views/adapters/ChannelViewPagerAdapter.kt | 65 + .../views/adapters/CommentViewHolder.kt | 161 ++ .../adapters/ContentPreviewViewHolder.kt | 18 + .../views/adapters/DeviceAdapter.kt | 35 + .../views/adapters/DeviceViewHolder.kt | 133 ++ .../views/adapters/DisabledSourceAdapter.kt | 42 + .../views/adapters/DisabledSourceView.kt | 46 + .../adapters/DisabledSourceViewHolder.kt | 44 + .../views/adapters/EmptyPreviewViewHolder.kt | 23 + .../views/adapters/EnabledSourceAdapter.kt | 40 + .../views/adapters/EnabledSourceViewHolder.kt | 63 + .../views/adapters/HistoryListAdapter.kt | 109 + .../views/adapters/HistoryListViewHolder.kt | 103 + .../views/adapters/InsertedViewAdapter.kt | 70 + .../adapters/InsertedViewAdapterWithLoader.kt | 91 + .../views/adapters/InsertedViewHolder.kt | 46 + .../views/adapters/ItemMoveCallback.kt | 47 + .../views/adapters/PlaylistView.kt | 169 ++ .../views/adapters/PlaylistsAdapter.kt | 68 + .../views/adapters/PlaylistsViewHolder.kt | 58 + .../adapters/PreviewContentListAdapter.kt | 159 ++ .../views/adapters/PreviewNestedVideoView.kt | 151 ++ .../adapters/PreviewNestedVideoViewHolder.kt | 62 + .../adapters/PreviewPlaceholderViewHolder.kt | 52 + .../adapters/PreviewPlaylistViewHolder.kt | 37 + .../views/adapters/PreviewPostView.kt | 316 +++ .../views/adapters/PreviewPostViewHolder.kt | 37 + .../views/adapters/PreviewVideoView.kt | 320 +++ .../views/adapters/PreviewVideoViewHolder.kt | 46 + .../views/adapters/SearchSuggestionAdapter.kt | 37 + .../adapters/SearchSuggestionViewHolder.kt | 43 + .../views/adapters/SubscriptionAdapter.kt | 59 + .../views/adapters/SubscriptionViewHolder.kt | 97 + .../views/adapters/VideoListEditorAdapter.kt | 53 + .../adapters/VideoListEditorViewHolder.kt | 116 + .../adapters/VideoListHorizontalAdapter.kt | 36 + .../adapters/VideoListHorizontalViewHolder.kt | 62 + .../adapters/viewholders/CreatorViewHolder.kt | 64 + .../viewholders/ImportPlaylistsViewHolder.kt | 67 + .../ImportSubscriptionViewHolder.kt | 70 + .../viewholders/SubscriptionBarViewHolder.kt | 82 + .../adapters/viewholders/TabViewHolder.kt | 58 + .../viewholders/VideoDownloadViewHolder.kt | 78 + .../views/announcements/AnnouncementView.kt | 163 ++ .../views/behavior/GestureControlView.kt | 531 +++++ .../views/behavior/NonScrollingTextView.kt | 85 + .../behavior/TouchInterceptFrameLayout.kt | 55 + .../platformplayer/views/buttons/BigButton.kt | 136 ++ .../views/buttons/BigButtonGroup.kt | 35 + .../views/buttons/DescButton.kt | 48 + .../views/casting/CastButton.kt | 61 + .../platformplayer/views/casting/CastView.kt | 192 ++ .../views/comments/AddCommentView.kt | 56 + .../views/containers/DoubleTapLayout.kt | 41 + .../SingleViewTouchableMotionLayout.kt | 94 + .../views/fields/ButtonField.kt | 69 + .../views/fields/DropdownField.kt | 139 ++ .../futo/platformplayer/views/fields/Field.kt | 23 + .../platformplayer/views/fields/FieldForm.kt | 194 ++ .../platformplayer/views/fields/GroupField.kt | 141 ++ .../views/fields/ReadOnlyTextField.kt | 77 + .../views/fields/ToggleField.kt | 103 + .../views/items/ActiveDownloadItem.kt | 139 ++ .../views/items/PlaylistDownloadItem.kt | 25 + .../views/lists/VideoListEditorView.kt | 85 + .../livechat/LiveChatDonationListItem.kt | 117 + .../views/livechat/LiveChatDonationPill.kt | 66 + .../views/livechat/LiveChatListAdapter.kt | 66 + .../views/livechat/LiveChatListItem.kt | 25 + .../views/livechat/LiveChatMessageListItem.kt | 132 + .../views/others/BulletPointView.kt | 41 + .../platformplayer/views/others/Checkbox.kt | 31 + .../views/others/CircularProgressBar.kt | 90 + .../views/others/CreatorThumbnail.kt | 121 + .../views/others/ProgressBar.kt | 114 + .../views/others/RadioGroupView.kt | 69 + .../platformplayer/views/others/RadioView.kt | 60 + .../platformplayer/views/others/TagView.kt | 32 + .../platformplayer/views/others/TagsView.kt | 40 + .../platformplayer/views/others/Toggle.kt | 48 + .../views/others/ToggleTagView.kt | 47 + .../views/overlays/DescriptionOverlay.kt | 35 + .../views/overlays/LiveChatOverlay.kt | 417 ++++ .../views/overlays/LoaderOverlay.kt | 29 + .../views/overlays/OverlayTopbar.kt | 45 + .../views/overlays/QueueEditorOverlay.kt | 40 + .../views/overlays/RepliesOverlay.kt | 82 + .../overlays/slideup/SlideUpMenuButtonList.kt | 71 + .../overlays/slideup/SlideUpMenuFilters.kt | 131 + .../overlays/slideup/SlideUpMenuGroup.kt | 62 + .../views/overlays/slideup/SlideUpMenuItem.kt | 68 + .../overlays/slideup/SlideUpMenuOverlay.kt | 177 ++ .../overlays/slideup/SlideUpMenuRadioGroup.kt | 37 + .../overlays/slideup/SlideUpMenuTextInput.kt | 57 + .../overlays/slideup/SlideUpMenuTitle.kt | 27 + .../platformplayer/views/pills/PillButton.kt | 37 + .../views/pills/PillRatingLikesDislikes.kt | 145 ++ .../platformplayer/views/pills/RoundButton.kt | 61 + .../views/pills/RoundButtonGroup.kt | 91 + .../views/platform/PlatformIndicator.kt | 25 + .../views/platform/PlatformLinkView.kt | 55 + .../views/segments/CommentsList.kt | 189 ++ .../views/sources/SourceHeaderView.kt | 91 + .../views/sources/SourceInfoView.kt | 55 + .../sources/SourceUnderConstructionView.kt | 29 + .../views/subscriptions/SubscribeButton.kt | 123 + .../views/subscriptions/SubscriptionBar.kt | 67 + .../views/video/FutoThumbnailPlayer.kt | 118 + .../views/video/FutoVideoPlayer.kt | 452 ++++ .../views/video/FutoVideoPlayerBase.kt | 522 ++++ .../video/datasources/JSHttpDataSource.java | 854 +++++++ .../views/videometa/UpNextView.kt | 184 ++ .../protos/DeviceAuthMessage.proto | 82 + app/src/main/res/anim/slide_darken.xml | 4 + app/src/main/res/anim/slide_in_up.xml | 4 + app/src/main/res/anim/slide_lighten.xml | 4 + app/src/main/res/anim/slide_out_up.xml | 4 + app/src/main/res/animator/fade.xml | 9 + app/src/main/res/animator/fade_400.xml | 9 + app/src/main/res/animator/fade_800.xml | 9 + .../res/animator/rotation_1500_clockwise.xml | 8 + .../res/animator/rotation_2000_clockwise.xml | 8 + .../rotation_2500_counter_clockwise.xml | 8 + .../main/res/animator/toggle_background.xml | 10 + .../animator/toggle_background_reverse.xml | 10 + .../main/res/animator/toggle_foreground.xml | 10 + .../animator/toggle_foreground_container.xml | 6 + .../toggle_foreground_container_reverse.xml | 6 + .../animator/toggle_foreground_reverse.xml | 10 + .../main/res/drawable-v24/example_banner.jpg | Bin 0 -> 235230 bytes .../placeholder_channel_thumbnail.jpg | Bin 0 -> 3193 bytes .../placeholder_video_thumbnail.webp | Bin 0 -> 1336 bytes .../res/drawable/background_16_round_4dp.xml | 6 + .../res/drawable/background_1d_round_4dp.xml | 6 + .../res/drawable/background_30_round_4dp.xml | 6 + .../res/drawable/background_big_button.xml | 6 + .../drawable/background_big_button_red.xml | 6 + .../res/drawable/background_button_accent.xml | 6 + ...background_button_accent_straight_left.xml | 9 + .../res/drawable/background_button_black.xml | 6 + .../res/drawable/background_button_err.xml | 6 + .../background_button_err_round_4dp.xml | 6 + .../background_button_gray_straight_left.xml | 9 + .../res/drawable/background_button_pred.xml | 6 + .../drawable/background_button_primary.xml | 6 + .../background_button_primary_round.xml | 6 + .../background_button_primary_round_4dp.xml | 6 + ...ackground_button_primary_straight_left.xml | 9 + .../res/drawable/background_button_round.xml | 6 + .../background_button_round_green.xml | 6 + .../res/drawable/background_button_source.xml | 6 + .../background_button_transparent_round.xml | 6 + .../res/drawable/background_channel_round.xml | 6 + app/src/main/res/drawable/background_fade.xml | 7 + .../drawable/background_gesture_controls.xml | 6 + app/src/main/res/drawable/background_pill.xml | 7 + .../res/drawable/background_pill_black.xml | 9 + .../res/drawable/background_pill_toggled.xml | 8 + .../drawable/background_pill_transparent.xml | 5 + .../drawable/background_pill_untoggled.xml | 7 + .../drawable/background_radio_selected.xml | 7 + .../drawable/background_radio_unselected.xml | 9 + .../drawable/background_slide_up_option.xml | 7 + .../background_slide_up_option_selected.xml | 7 + .../res/drawable/background_small_button.xml | 7 + .../main/res/drawable/background_square.xml | 5 + .../main/res/drawable/background_store.xml | 6 + .../main/res/drawable/background_support.xml | 6 + .../background_thumbnail_duration.xml | 7 + .../drawable/background_thumbnail_live.xml | 7 + .../background_thumbnail_video_options.xml | 7 + .../background_up_next_toggle_active.xml | 9 + .../background_videodetail_description.xml | 7 + .../main/res/drawable/banner_placeholder.png | Bin 0 -> 768585 bytes app/src/main/res/drawable/bottom_gradient.xml | 21 + .../main/res/drawable/bottom_menu_border.xml | 9 + .../res/drawable/channel_icon_outline.xml | 3 + .../res/drawable/download_for_offline.xml | 9 + .../res/drawable/edit_text_background.xml | 8 + .../main/res/drawable/edit_text_cursor.xml | 5 + app/src/main/res/drawable/foreground.png | Bin 0 -> 104860 bytes app/src/main/res/drawable/ic_add.xml | 9 + app/src/main/res/drawable/ic_add_400.xml | 9 + .../res/drawable/ic_add_to_query_16dp.xml | 9 + .../main/res/drawable/ic_add_white_16dp.xml | 9 + .../main/res/drawable/ic_add_white_8dp.xml | 9 + app/src/main/res/drawable/ic_airplay.xml | 9 + .../main/res/drawable/ic_arrow_downward.xml | 9 + app/src/main/res/drawable/ic_arrow_right.xml | 10 + app/src/main/res/drawable/ic_aspect_ratio.xml | 9 + app/src/main/res/drawable/ic_back_nav.xml | 21 + .../res/drawable/ic_back_thin_white_16dp.xml | 21 + .../main/res/drawable/ic_back_white_24dp.xml | 21 + app/src/main/res/drawable/ic_block.xml | 9 + app/src/main/res/drawable/ic_brightness.xml | 9 + app/src/main/res/drawable/ic_brightness_1.xml | 9 + app/src/main/res/drawable/ic_cast.xml | 9 + .../main/res/drawable/ic_cast_white_20dp.xml | 10 + .../main/res/drawable/ic_cast_white_25dp.xml | 10 + .../main/res/drawable/ic_cast_white_32dp.xml | 10 + app/src/main/res/drawable/ic_chat.xml | 9 + app/src/main/res/drawable/ic_check.xml | 4 + .../main/res/drawable/ic_checkbox_checked.xml | 31 + .../res/drawable/ic_checkbox_unchecked.xml | 11 + app/src/main/res/drawable/ic_chromecast.xml | 9 + app/src/main/res/drawable/ic_clear_16dp.xml | 9 + app/src/main/res/drawable/ic_clock_white.xml | 9 + app/src/main/res/drawable/ic_close.xml | 9 + app/src/main/res/drawable/ic_close_thin.xml | 9 + app/src/main/res/drawable/ic_code.xml | 9 + app/src/main/res/drawable/ic_code_red.xml | 9 + app/src/main/res/drawable/ic_construction.xml | 9 + app/src/main/res/drawable/ic_copy.xml | 9 + app/src/main/res/drawable/ic_creators.xml | 9 + .../main/res/drawable/ic_creators_active.xml | 21 + .../main/res/drawable/ic_creators_filled.xml | 9 + .../res/drawable/ic_creators_inactive.xml | 9 + app/src/main/res/drawable/ic_download.xml | 9 + app/src/main/res/drawable/ic_download_off.xml | 9 + .../main/res/drawable/ic_dragdrop_white.xml | 9 + app/src/main/res/drawable/ic_edit.xml | 9 + app/src/main/res/drawable/ic_error.xml | 9 + app/src/main/res/drawable/ic_error_pred.xml | 9 + app/src/main/res/drawable/ic_expand.xml | 9 + app/src/main/res/drawable/ic_expand_18dp.xml | 4 + app/src/main/res/drawable/ic_expand_white.xml | 9 + app/src/main/res/drawable/ic_export.xml | 10 + app/src/main/res/drawable/ic_fast_forward.xml | 9 + .../res/drawable/ic_fast_forward_notif.xml | 9 + app/src/main/res/drawable/ic_fast_rewind.xml | 9 + .../res/drawable/ic_fast_rewind_notif.xml | 9 + app/src/main/res/drawable/ic_fastforward.xml | 16 + .../res/drawable/ic_fastforward_animated.xml | 13 + app/src/main/res/drawable/ic_fc.xml | 9 + .../res/drawable/ic_filter_settings_25dp.xml | 18 + app/src/main/res/drawable/ic_forum.xml | 9 + app/src/main/res/drawable/ic_futo_logo.xml | 23 + .../main/res/drawable/ic_futo_logo_text.xml | 10 + app/src/main/res/drawable/ic_help.xml | 10 + app/src/main/res/drawable/ic_history.xml | 9 + app/src/main/res/drawable/ic_history_29dp.xml | 9 + app/src/main/res/drawable/ic_home.xml | 9 + .../main/res/drawable/ic_home_active_25dp.xml | 9 + app/src/main/res/drawable/ic_home_filled.xml | 9 + .../res/drawable/ic_home_inactive_25dp.xml | 9 + app/src/main/res/drawable/ic_image.xml | 9 + app/src/main/res/drawable/ic_language.xml | 9 + app/src/main/res/drawable/ic_link.xml | 9 + app/src/main/res/drawable/ic_list.xml | 10 + app/src/main/res/drawable/ic_loader.xml | 39 + .../main/res/drawable/ic_loader_animated.xml | 12 + app/src/main/res/drawable/ic_lock.xml | 9 + app/src/main/res/drawable/ic_login.xml | 10 + app/src/main/res/drawable/ic_logout.xml | 10 + app/src/main/res/drawable/ic_menu.xml | 9 + app/src/main/res/drawable/ic_minimize.xml | 9 + app/src/main/res/drawable/ic_more.xml | 9 + .../res/drawable/ic_more_inactive_25dp.xml | 9 + app/src/main/res/drawable/ic_move_up.xml | 9 + app/src/main/res/drawable/ic_movie.xml | 9 + app/src/main/res/drawable/ic_music.xml | 9 + .../main/res/drawable/ic_no_internet_86dp.xml | 10 + app/src/main/res/drawable/ic_paid.xml | 9 + app/src/main/res/drawable/ic_paid_200.xml | 9 + app/src/main/res/drawable/ic_pause.xml | 9 + app/src/main/res/drawable/ic_pause_notif.xml | 9 + app/src/main/res/drawable/ic_pause_white.xml | 9 + app/src/main/res/drawable/ic_peertube.png | Bin 0 -> 7197 bytes app/src/main/res/drawable/ic_person.xml | 9 + app/src/main/res/drawable/ic_person_add.xml | 9 + .../res/drawable/ic_person_search_300w.xml | 9 + app/src/main/res/drawable/ic_pin.xml | 9 + app/src/main/res/drawable/ic_play.xml | 9 + app/src/main/res/drawable/ic_play_200w.xml | 9 + app/src/main/res/drawable/ic_play_notif.xml | 9 + .../main/res/drawable/ic_play_white_nopad.xml | 9 + app/src/main/res/drawable/ic_playlist.xml | 9 + app/src/main/res/drawable/ic_playlist_add.xml | 13 + .../main/res/drawable/ic_playlist_filled.xml | 9 + .../main/res/drawable/ic_playlist_select.xml | 13 + .../main/res/drawable/ic_playlists_active.xml | 13 + .../drawable/ic_playlists_inactive_25dp.xml | 9 + app/src/main/res/drawable/ic_qr.xml | 9 + app/src/main/res/drawable/ic_queue_16dp.xml | 13 + app/src/main/res/drawable/ic_queue_add.xml | 13 + app/src/main/res/drawable/ic_refresh.xml | 9 + app/src/main/res/drawable/ic_remove.xml | 9 + .../main/res/drawable/ic_remove_white_8dp.xml | 9 + app/src/main/res/drawable/ic_repeat.xml | 13 + app/src/main/res/drawable/ic_replay.xml | 9 + app/src/main/res/drawable/ic_rewind.xml | 16 + .../main/res/drawable/ic_rewind_animated.xml | 13 + app/src/main/res/drawable/ic_save.xml | 9 + app/src/main/res/drawable/ic_schedule.xml | 9 + .../res/drawable/ic_screen_lock_rotation.xml | 9 + .../main/res/drawable/ic_screen_rotation.xml | 9 + app/src/main/res/drawable/ic_screen_share.xml | 9 + app/src/main/res/drawable/ic_search.xml | 9 + app/src/main/res/drawable/ic_search_300w.xml | 9 + app/src/main/res/drawable/ic_search_thin.xml | 9 + .../res/drawable/ic_search_white_16dp.xml | 10 + .../res/drawable/ic_search_white_20dp.xml | 10 + .../res/drawable/ic_search_white_25dp.xml | 10 + app/src/main/res/drawable/ic_security.xml | 9 + .../main/res/drawable/ic_security_pred.xml | 9 + app/src/main/res/drawable/ic_security_red.xml | 9 + app/src/main/res/drawable/ic_settings.xml | 9 + .../main/res/drawable/ic_settings_29dp.xml | 9 + app/src/main/res/drawable/ic_share.xml | 9 + app/src/main/res/drawable/ic_shuffle.xml | 13 + .../main/res/drawable/ic_smart_display.xml | 9 + app/src/main/res/drawable/ic_sources.xml | 9 + .../main/res/drawable/ic_sources_active.xml | 9 + .../main/res/drawable/ic_sources_filled.xml | 9 + .../main/res/drawable/ic_sources_inactive.xml | 9 + app/src/main/res/drawable/ic_stop_notif.xml | 9 + app/src/main/res/drawable/ic_store.xml | 9 + app/src/main/res/drawable/ic_store_200.xml | 9 + .../main/res/drawable/ic_subscriptions.xml | 9 + .../drawable/ic_subscriptions_active_25dp.xml | 9 + .../res/drawable/ic_subscriptions_filled.xml | 9 + .../ic_subscriptions_inactive_25dp.xml | 9 + app/src/main/res/drawable/ic_thumb_down.xml | 9 + app/src/main/res/drawable/ic_thumb_up.xml | 9 + app/src/main/res/drawable/ic_trash.xml | 9 + app/src/main/res/drawable/ic_trash_18dp.xml | 9 + app/src/main/res/drawable/ic_tune_300.xml | 9 + app/src/main/res/drawable/ic_update.xml | 18 + .../main/res/drawable/ic_update_animated.xml | 6 + .../res/drawable/ic_update_fail_251dp.xml | 9 + .../res/drawable/ic_update_success_251dp.xml | 4 + app/src/main/res/drawable/ic_view_queue.xml | 9 + .../main/res/drawable/ic_visibility_off.xml | 9 + app/src/main/res/drawable/ic_volume_off.xml | 10 + app/src/main/res/drawable/ic_volume_up.xml | 10 + app/src/main/res/drawable/ic_volume_up_1.xml | 9 + .../main/res/drawable/ic_watchlist_add.xml | 13 + app/src/main/res/drawable/ic_web_white.xml | 13 + app/src/main/res/drawable/ic_wrench.xml | 9 + app/src/main/res/drawable/neopass.png | Bin 0 -> 25194 bytes app/src/main/res/drawable/neopass_button.png | Bin 0 -> 24412 bytes .../main/res/drawable/placeholder_profile.xml | 12 + app/src/main/res/drawable/player_progress.xml | 13 + .../res/drawable/player_seekbar_style.xml | 8 + app/src/main/res/drawable/player_thumb.xml | 10 + app/src/main/res/drawable/rounded_outline.xml | 5 + .../res/drawable/rounded_square_outline.xml | 5 + app/src/main/res/drawable/stripe.png | Bin 0 -> 1825 bytes app/src/main/res/drawable/tab_border.xml | 9 + app/src/main/res/drawable/toggle.xml | 22 + app/src/main/res/drawable/toggle_animated.xml | 15 + .../res/drawable/toggle_animated_reverse.xml | 15 + app/src/main/res/drawable/toggle_disabled.xml | 22 + app/src/main/res/drawable/toggle_enabled.xml | 22 + .../res/drawable/video_thumbnail_outline.xml | 3 + app/src/main/res/font/inter_black.ttf | Bin 0 -> 316372 bytes app/src/main/res/font/inter_bold.ttf | Bin 0 -> 316100 bytes app/src/main/res/font/inter_extra_bold.ttf | Bin 0 -> 316716 bytes app/src/main/res/font/inter_extra_light.ttf | Bin 0 -> 310808 bytes app/src/main/res/font/inter_light.ttf | Bin 0 -> 310420 bytes app/src/main/res/font/inter_medium.ttf | Bin 0 -> 314712 bytes app/src/main/res/font/inter_regular.ttf | Bin 0 -> 309828 bytes app/src/main/res/font/inter_semibold.ttf | Bin 0 -> 315756 bytes app/src/main/res/font/inter_thin.ttf | Bin 0 -> 310516 bytes .../main/res/layout/activity_add_source.xml | 195 ++ .../layout/activity_add_source_options.xml | 68 + app/src/main/res/layout/activity_dev.xml | 56 + .../main/res/layout/activity_exception.xml | 149 ++ app/src/main/res/layout/activity_login.xml | 11 + app/src/main/res/layout/activity_main.xml | 73 + .../main/res/layout/activity_manage_tabs.xml | 51 + app/src/main/res/layout/activity_pip.xml | 10 + .../layout/activity_polycentric_backup.xml | 77 + .../activity_polycentric_create_profile.xml | 96 + .../res/layout/activity_polycentric_home.xml | 77 + .../activity_polycentric_import_profile.xml | 97 + .../layout/activity_polycentric_profile.xml | 94 + .../res/layout/activity_polycentric_why.xml | 112 + app/src/main/res/layout/activity_settings.xml | 82 + app/src/main/res/layout/activity_test.xml | 13 + app/src/main/res/layout/big_button.xml | 54 + app/src/main/res/layout/big_button_group.xml | 24 + app/src/main/res/layout/button_round.xml | 32 + app/src/main/res/layout/button_subscribe.xml | 35 + .../res/layout/dialog_automatic_backup.xml | 130 + .../dialog_automatic_backup_restore.xml | 101 + .../main/res/layout/dialog_casting_add.xml | 103 + .../res/layout/dialog_casting_connect.xml | 134 ++ .../res/layout/dialog_casting_connected.xml | 161 ++ app/src/main/res/layout/dialog_changelog.xml | 129 + app/src/main/res/layout/dialog_comment.xml | 75 + app/src/main/res/layout/dialog_confirm.xml | 82 + app/src/main/res/layout/dialog_data.xml | 94 + app/src/main/res/layout/dialog_import.xml | 282 +++ app/src/main/res/layout/dialog_migrate.xml | 305 +++ .../main/res/layout/dialog_multi_button.xml | 71 + app/src/main/res/layout/dialog_progress.xml | 66 + .../main/res/layout/dialog_single_button.xml | 66 + app/src/main/res/layout/dialog_update.xml | 119 + app/src/main/res/layout/field_button.xml | 23 + app/src/main/res/layout/field_dropdown.xml | 34 + app/src/main/res/layout/field_form.xml | 9 + app/src/main/res/layout/field_group.xml | 36 + .../main/res/layout/field_readonly_text.xml | 22 + app/src/main/res/layout/field_toggle.xml | 35 + .../main/res/layout/fragment_add_top_bar.xml | 74 + app/src/main/res/layout/fragment_browser.xml | 14 + app/src/main/res/layout/fragment_buy.xml | 131 + app/src/main/res/layout/fragment_channel.xml | 177 ++ .../res/layout/fragment_channel_about.xml | 90 + .../main/res/layout/fragment_channel_list.xml | 6 + .../layout/fragment_channel_monetization.xml | 92 + .../res/layout/fragment_channel_store.xml | 16 + .../res/layout/fragment_channel_videos.xml | 14 + app/src/main/res/layout/fragment_creators.xml | 58 + .../main/res/layout/fragment_downloads.xml | 168 ++ app/src/main/res/layout/fragment_feed.xml | 106 + app/src/main/res/layout/fragment_history.xml | 106 + app/src/main/res/layout/fragment_import.xml | 76 + .../res/layout/fragment_import_top_bar.xml | 40 + .../layout/fragment_navigation_top_bar.xml | 52 + .../layout/fragment_overview_bottom_bar.xml | 52 + .../res/layout/fragment_overview_top_bar.xml | 87 + .../main/res/layout/fragment_playlists.xml | 151 ++ .../res/layout/fragment_search_top_bar.xml | 55 + .../res/layout/fragment_source_detail.xml | 75 + app/src/main/res/layout/fragment_sources.xml | 120 + .../res/layout/fragment_suggestion_list.xml | 14 + .../main/res/layout/fragment_video_detail.xml | 30 + .../res/layout/fragment_video_list_editor.xml | 225 ++ .../main/res/layout/fragview_post_detail.xml | 379 +++ .../main/res/layout/fragview_video_detail.xml | 611 +++++ app/src/main/res/layout/ic_sources_active.xml | 9 + .../main/res/layout/ic_sources_inactive.xml | 9 + .../main/res/layout/list_chat_donation.xml | 84 + app/src/main/res/layout/list_chat_message.xml | 58 + app/src/main/res/layout/list_comment.xml | 141 ++ app/src/main/res/layout/list_creator.xml | 73 + app/src/main/res/layout/list_device.xml | 148 ++ app/src/main/res/layout/list_donation.xml | 56 + app/src/main/res/layout/list_download.xml | 208 ++ app/src/main/res/layout/list_downloaded.xml | 227 ++ .../res/layout/list_downloaded_playlist.xml | 38 + app/src/main/res/layout/list_history.xml | 183 ++ .../main/res/layout/list_import_playlist.xml | 55 + .../res/layout/list_import_subscription.xml | 51 + .../res/layout/list_placeholder_preview.xml | 54 + .../res/layout/list_placeholder_thumbnail.xml | 49 + app/src/main/res/layout/list_playlist.xml | 184 ++ .../main/res/layout/list_playlist_feed.xml | 166 ++ .../res/layout/list_playlist_feed_preview.xml | 168 ++ app/src/main/res/layout/list_playlists.xml | 79 + app/src/main/res/layout/list_post_preview.xml | 280 +++ .../main/res/layout/list_post_thumbnail.xml | 202 ++ .../res/layout/list_search_suggestion.xml | 46 + .../res/layout/list_source_construction.xml | 83 + .../main/res/layout/list_source_disabled.xml | 83 + .../main/res/layout/list_source_enabled.xml | 95 + app/src/main/res/layout/list_subscription.xml | 53 + app/src/main/res/layout/list_tab.xml | 44 + .../main/res/layout/list_video_horizontal.xml | 125 + .../main/res/layout/list_video_preview.xml | 261 ++ .../res/layout/list_video_preview_nested.xml | 310 +++ .../main/res/layout/list_video_thumbnail.xml | 254 ++ .../layout/list_video_thumbnail_nested.xml | 306 +++ .../main/res/layout/overlay_description.xml | 42 + app/src/main/res/layout/overlay_livechat.xml | 284 +++ app/src/main/res/layout/overlay_loader.xml | 21 + app/src/main/res/layout/overlay_queue.xml | 25 + app/src/main/res/layout/overlay_replies.xml | 37 + .../main/res/layout/overlay_slide_up_menu.xml | 85 + .../overlay_slide_up_menu_button_list.xml | 11 + .../layout/overlay_slide_up_menu_group.xml | 24 + .../layout/overlay_slide_up_menu_option.xml | 43 + .../overlay_slide_up_menu_radio_group.xml | 26 + .../overlay_slide_up_menu_text_input.xml | 18 + .../layout/overlay_slide_up_menu_title.xml | 18 + app/src/main/res/layout/overlay_topbar.xml | 51 + app/src/main/res/layout/payment_checkout.xml | 327 +++ app/src/main/res/layout/payment_country.xml | 49 + app/src/main/res/layout/payment_currency.xml | 49 + .../res/layout/payment_currency_other.xml | 52 + app/src/main/res/layout/payment_method.xml | 50 + app/src/main/res/layout/payment_overlay.xml | 127 + .../main/res/layout/payment_postal_code.xml | 55 + app/src/main/res/layout/pill_button.xml | 34 + .../main/res/layout/rating_likesdislikes.xml | 47 + .../layout/spinner_dropdownitem_simple.xml | 13 + .../main/res/layout/spinner_item_simple.xml | 10 + .../main/res/layout/thumbnail_player_ui.xml | 121 + .../main/res/layout/thumbnail_video_view.xml | 26 + app/src/main/res/layout/toast_clickable.xml | 17 + app/src/main/res/layout/video_player_ui.xml | 160 ++ .../main/res/layout/video_player_ui_bar.xml | 34 + .../res/layout/video_player_ui_fullscreen.xml | 179 ++ app/src/main/res/layout/video_view.xml | 67 + app/src/main/res/layout/view_add_comment.xml | 23 + app/src/main/res/layout/view_announcement.xml | 119 + .../res/layout/view_bottom_menu_button.xml | 28 + .../layout/view_bottom_more_menu_button.xml | 34 + app/src/main/res/layout/view_bullet_point.xml | 21 + app/src/main/res/layout/view_cast.xml | 153 ++ .../main/res/layout/view_comments_list.xml | 13 + .../res/layout/view_creator_thumbnail.xml | 46 + app/src/main/res/layout/view_desc_button.xml | 44 + app/src/main/res/layout/view_filter_bar.xml | 8 + .../main/res/layout/view_gesture_controls.xml | 155 ++ app/src/main/res/layout/view_loader.xml | 15 + .../main/res/layout/view_platform_link.xml | 31 + app/src/main/res/layout/view_radio.xml | 24 + .../main/res/layout/view_source_header.xml | 147 ++ app/src/main/res/layout/view_source_info.xml | 52 + .../main/res/layout/view_subscription_bar.xml | 26 + .../res/layout/view_subscription_bar_icon.xml | 29 + app/src/main/res/layout/view_tag.xml | 24 + app/src/main/res/layout/view_toggle_tag.xml | 25 + app/src/main/res/layout/view_up_next.xml | 336 +++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2581 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 4577 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4783 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1582 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 2561 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2826 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 3770 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 6961 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6962 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6392 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 12324 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 11618 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9387 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 18523 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 17313 bytes .../res/values/announcement_view_attrs.xml | 6 + app/src/main/res/values/big_button_attrs.xml | 9 + .../main/res/values/bullet_point_attrs.xml | 9 + .../values/circular_progress_bar_attrs.xml | 6 + app/src/main/res/values/colors.xml | 41 + app/src/main/res/values/dimensions.xml | 5 + app/src/main/res/values/futo_video_player.xml | 8 + .../res/values/ic_launcher_background.xml | 4 + app/src/main/res/values/loader_attrs.xml | 6 + .../main/res/values/overlay_topbar_attrs.xml | 7 + app/src/main/res/values/pill_button_attrs.xml | 7 + .../main/res/values/progress_bar_attrs.xml | 12 + app/src/main/res/values/strings.xml | 256 ++ app/src/main/res/values/styles.xml | 36 + app/src/main/res/values/themes.xml | 72 + app/src/main/res/values/toggle_attrs.xml | 6 + .../touch_intercept_frame_layout_attrs.xml | 6 + app/src/main/res/values/view_attrs.xml | 8 + app/src/main/res/xml/activity_main_scene.xml | 35 + app/src/main/res/xml/backup_rules.xml | 13 + .../main/res/xml/data_extraction_rules.xml | 19 + app/src/main/res/xml/file_paths.xml | 5 + app/src/main/res/xml/file_provider_paths.xml | 6 + .../res/xml/fragview_videodetail_scene.xml | 27 + app/src/main/res/xml/videodetail_scene.xml | 210 ++ app/src/playstore/AndroidManifest.xml | 4 + app/src/playstore/assets/sources/peertube | 1 + app/src/playstore/res/raw/plugin_config.json | 7 + app/src/stable/AndroidManifest.xml | 44 + app/src/stable/assets/sources/kick | 1 + app/src/stable/assets/sources/nebula | 1 + app/src/stable/assets/sources/odysee | 1 + app/src/stable/assets/sources/patreon | 1 + app/src/stable/assets/sources/peertube | 1 + app/src/stable/assets/sources/rumble | 1 + app/src/stable/assets/sources/soundcloud | 1 + app/src/stable/assets/sources/twitch | 1 + app/src/stable/assets/sources/youtube | 1 + .../res/drawable/logo_subscribestar.png | Bin 0 -> 41764 bytes app/src/stable/res/raw/plugin_config.json | 19 + app/src/test/java/android/util/Log.java | 23 + .../BackgroundTaskHandlerTests.kt | 75 + .../platformplayer/BatchedTaskHandlerTests.kt | 80 + .../futo/platformplayer/DashBuilderTests.kt | 86 + .../DashManifestCreatorsUtilsTests.kt | 72 + .../ExtensionsCoroutinesTests.kt | 93 + .../ExtensionsFormattingTests.kt | 105 + .../platformplayer/ExtensionsNetworkTests.kt | 97 + .../com/futo/platformplayer/LoggerTests.kt | 78 + .../com/futo/platformplayer/PaymentTests.kt | 19 + .../ProgressiveDashManifestCreatorTest.kt | 140 ++ .../platformplayer/RequireMigrationTests.kt | 111 + .../futo/platformplayer/TaskHandlerTests.kt | 89 + .../com/futo/platformplayer/UtilityTests.kt | 36 + .../futo/platformplayer/XmlBuilderTests.kt | 47 + app/src/unstable/AndroidManifest.xml | 44 + app/src/unstable/assets/sources/kick | 1 + app/src/unstable/assets/sources/nebula | 1 + app/src/unstable/assets/sources/odysee | 1 + app/src/unstable/assets/sources/patreon | 1 + app/src/unstable/assets/sources/peertube | 1 + app/src/unstable/assets/sources/rumble | 1 + app/src/unstable/assets/sources/soundcloud | 1 + app/src/unstable/assets/sources/twitch | 1 + app/src/unstable/assets/sources/youtube | 1 + .../res/drawable/logo_subscribestar.png | Bin 0 -> 41764 bytes app/src/unstable/res/raw/plugin_config.json | 19 + build.gradle | 7 + dep/futopay | 1 + dep/polycentricandroid | 1 + deploy-playstore.sh | 27 + deploy-stable.sh | 37 + deploy-unstable.sh | 33 + docs/casting.md | 39 + docs/linking.md | 51 + docs/packages/packageHttp.md | 93 + docs/polycentric.md | 40 + gradle.properties | 23 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 185 ++ gradlew.bat | 89 + images/casting.jpg | Bin 0 -> 808085 bytes images/channel.jpg | Bin 0 -> 1244118 bytes images/creators.png | Bin 0 -> 219339 bytes images/downloads.jpg | Bin 0 -> 1064899 bytes images/history.jpg | Bin 0 -> 1342424 bytes images/playlist.jpg | Bin 0 -> 1788390 bytes images/playlists.jpg | Bin 0 -> 263530 bytes images/search-list.jpg | Bin 0 -> 1645606 bytes images/search-preview.jpg | Bin 0 -> 1637234 bytes images/search-suggestions.jpg | Bin 0 -> 338865 bytes images/settings.jpg | Bin 0 -> 367158 bytes images/source-install.png | Bin 0 -> 191909 bytes images/source-settings.jpg | Bin 0 -> 386561 bytes images/source.jpg | Bin 0 -> 318887 bytes images/sources-disabled.jpg | Bin 0 -> 348259 bytes images/sources.jpg | Bin 0 -> 318570 bytes images/subscriptions-list.png | Bin 0 -> 1481182 bytes images/subscriptions-preview.png | Bin 0 -> 1805381 bytes images/video-details.jpg | Bin 0 -> 1194393 bytes images/video.jpg | Bin 0 -> 1090496 bytes plugin-development.md | 416 ++++ pull-all-submodules.sh | 2 + scripts/clone-all-sources.sh | 11 + scripts/commit-and-push-all-sources.sh | 19 + scripts/pull-all-sources.sh | 12 + scripts/sign-script.sh | 22 + settings.gradle | 30 + 1031 files changed, 74881 insertions(+) create mode 100644 .gitlab-ci.yml create mode 100644 .gitmodules create mode 100644 CONTRIBUTION.md create mode 100644 README.md create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/futo/platformplayer/EncryptionProviderTests.kt create mode 100644 app/src/androidTest/java/com/futo/platformplayer/RequireMigrationTests.kt create mode 100644 app/src/androidTest/java/com/futo/platformplayer/SignatureTests.kt create mode 100644 app/src/androidTest/java/com/futo/platformplayer/StatePlatformTests.kt create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/assets/devportal/dependencies/FutoMainLogo.svg create mode 100644 app/src/main/assets/devportal/dev_bridge.js create mode 100644 app/src/main/assets/devportal/dev_test.html create mode 100644 app/src/main/assets/devportal/index.html create mode 100644 app/src/main/assets/devportal/plugin.d.ts create mode 100644 app/src/main/assets/scripts/polyfil.js create mode 100644 app/src/main/assets/scripts/source.js create mode 100644 app/src/main/ic_launcher-playstore.png create mode 100644 app/src/main/java/com/futo/platformplayer/Extensions_Coroutines.kt create mode 100644 app/src/main/java/com/futo/platformplayer/Extensions_Formatting.kt create mode 100644 app/src/main/java/com/futo/platformplayer/Extensions_Network.kt create mode 100644 app/src/main/java/com/futo/platformplayer/Extensions_Polycentric.kt create mode 100644 app/src/main/java/com/futo/platformplayer/Extensions_Syntax.kt create mode 100644 app/src/main/java/com/futo/platformplayer/Extensions_V8.kt create mode 100644 app/src/main/java/com/futo/platformplayer/Settings.kt create mode 100644 app/src/main/java/com/futo/platformplayer/SettingsDev.kt create mode 100644 app/src/main/java/com/futo/platformplayer/SignatureProvider.kt create mode 100644 app/src/main/java/com/futo/platformplayer/UIDialogs.kt create mode 100644 app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt create mode 100644 app/src/main/java/com/futo/platformplayer/Utility.kt create mode 100644 app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt create mode 100644 app/src/main/java/com/futo/platformplayer/activities/AddSourceOptionsActivity.kt create mode 100644 app/src/main/java/com/futo/platformplayer/activities/DeveloperActivity.kt create mode 100644 app/src/main/java/com/futo/platformplayer/activities/ExceptionActivity.kt create mode 100644 app/src/main/java/com/futo/platformplayer/activities/LoginActivity.kt create mode 100644 app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt create mode 100644 app/src/main/java/com/futo/platformplayer/activities/ManageTabsActivity.kt create mode 100644 app/src/main/java/com/futo/platformplayer/activities/PolycentricBackupActivity.kt create mode 100644 app/src/main/java/com/futo/platformplayer/activities/PolycentricCreateProfileActivity.kt create mode 100644 app/src/main/java/com/futo/platformplayer/activities/PolycentricHomeActivity.kt create mode 100644 app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt create mode 100644 app/src/main/java/com/futo/platformplayer/activities/PolycentricProfileActivity.kt create mode 100644 app/src/main/java/com/futo/platformplayer/activities/PolycentricWhyActivity.kt create mode 100644 app/src/main/java/com/futo/platformplayer/activities/SettingsActivity.kt create mode 100644 app/src/main/java/com/futo/platformplayer/activities/TestActivity.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/http/ManagedHttpClient.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/http/server/HttpBridge.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/http/server/HttpContext.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/http/server/HttpHeaders.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/http/server/HttpResponse.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/http/server/ManagedHttpServer.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/http/server/exceptions/EmptyRequestException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/http/server/exceptions/KeepAliveTimeoutException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpConstantHandler.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpFileHandler.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpFunctionHandler.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpHandler.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpOptionsAllowHandler.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpProxyHandler.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/CachedPlatformClient.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/IPlatformClient.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/IPluginSourced.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/LiveChatManager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/PlatformClientCapabilities.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/PlatformClientPool.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/PlatformID.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/Serializer.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/exceptions/APIRequestFailedException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/exceptions/AlreadyQueuedException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/exceptions/ContentNotAvailableYetException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/exceptions/NoPlatformClientException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/exceptions/NotFoundException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/exceptions/UnknownPlatformException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/exceptions/search/NoNextPageException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/PlatformAuthorLink.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/ResultCapabilities.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/Thumbnails.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/channels/IPlatformChannel.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/channels/SerializedChannel.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/comments/IPlatformComment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/comments/NoCommentsPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/comments/PlatformComment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/comments/PolycentricPlatformComment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/contents/ContentType.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/contents/IPlatformContent.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/contents/IPlatformContentDetails.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/contents/PlatformContentDeferred.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/contents/PlatformContentPlaceholder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/live/ILiveChatWindowDescriptor.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/live/ILiveEventChatMessage.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/live/IPlatformLiveEvent.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventComment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventDonation.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventEmojis.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventRaid.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventType.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventViewCount.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/nested/IPlatformNestedContent.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/playback/IPlaybackTracker.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/playlists/IPlatformPlaylist.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/playlists/IPlatformPlaylistDetails.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/post/IPlatformPost.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/post/IPlatformPostDetails.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/post/TextType.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/ratings/IRating.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingLikeDislikes.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingLikes.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingScaler.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingType.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/IVideoSourceDescriptor.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/LocalVideoMuxedSourceDescriptor.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/LocalVideoUnMuxedSourceDescriptor.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/VideoMuxedSourceDescriptor.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/VideoUnMuxedSourceDescriptor.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/AudioUrlSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/DashManifestSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/HLSManifestSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IAudioSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IAudioUrlSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IDashManifestSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IHLSManifestSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IVideoSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IVideoUrlSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalAudioSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalSubtitleSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalVideoSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/SubtitleRawSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/VideoUrlSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/other/IStreamMetaDataSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/other/StreamMetaData.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/subtitles/ISubtitleSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/video/IPlatformVideo.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/video/IPlatformVideoDetails.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/video/ISerializedVideoSourceDescriptor.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformContent.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformNestedContent.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformPost.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformVideo.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformVideoDetails.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedVideoMuxedSourceDescriptor.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedVideoUnmuxedSourceDescriptor.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourceAuth.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginAuthConfig.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSDocs.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/IJSContent.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/IJSContentDetails.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSChannel.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSChannelPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSComment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSCommentPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSContent.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSContentPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSLiveChatWindowDescriptor.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSLiveEventPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSNestedMediaContent.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaybackTracker.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaylist.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaylistDetails.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaylistPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPost.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPostDetails.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequest.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestModifier.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSSubtitleSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideo.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoDetails.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioUrlSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioWithMetadataSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestAudioSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSUnMuxVideoSourceDescriptor.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoSourceDescriptor.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoUrlSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoWithMetadataSource.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/DedupContentPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/EmptyPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/IAsyncPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/INestedPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/IPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/IPlatformLiveEventPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/IRefreshPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/IReplacerPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/MultiAsyncPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/MultiChronoContentPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/MultiDistributionChannelPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/MultiDistributionContentAsyncPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/MultiDistributionContentPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/MultiDistributionContentParallelPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/MultiPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/MultiParallelPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/MultiRefreshPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/PlaceholderPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/PlatformContentPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/RefreshDedupContentPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/RefreshDistributionContentPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/ReusablePager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/SingleAsyncItemPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/api/media/structures/SingleItemPager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/background/BackgroundWorker.kt create mode 100644 app/src/main/java/com/futo/platformplayer/builders/DashBuilder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/builders/HlsBuilder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/builders/XMLBuilder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/cache/ChannelContentCache.kt create mode 100644 app/src/main/java/com/futo/platformplayer/casting/AirPlayCastingDevice.kt create mode 100644 app/src/main/java/com/futo/platformplayer/casting/CastingDevice.kt create mode 100644 app/src/main/java/com/futo/platformplayer/casting/ChomecastCastingDevice.kt create mode 100644 app/src/main/java/com/futo/platformplayer/casting/FastCastCastingDevice.kt create mode 100644 app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt create mode 100644 app/src/main/java/com/futo/platformplayer/casting/models/FastCast.kt create mode 100644 app/src/main/java/com/futo/platformplayer/constructs/BackgroundTaskHandler.kt create mode 100644 app/src/main/java/com/futo/platformplayer/constructs/BatchedTaskHandler.kt create mode 100644 app/src/main/java/com/futo/platformplayer/constructs/Event.kt create mode 100644 app/src/main/java/com/futo/platformplayer/constructs/TaskHandler.kt create mode 100644 app/src/main/java/com/futo/platformplayer/debug/Stopwatch.kt create mode 100644 app/src/main/java/com/futo/platformplayer/developer/DeveloperEndpoints.kt create mode 100644 app/src/main/java/com/futo/platformplayer/dialogs/AutoUpdateDialog.kt create mode 100644 app/src/main/java/com/futo/platformplayer/dialogs/AutomaticBackupDialog.kt create mode 100644 app/src/main/java/com/futo/platformplayer/dialogs/AutomaticRestoreDialog.kt create mode 100644 app/src/main/java/com/futo/platformplayer/dialogs/CastingAddDialog.kt create mode 100644 app/src/main/java/com/futo/platformplayer/dialogs/ChangelogDialog.kt create mode 100644 app/src/main/java/com/futo/platformplayer/dialogs/CommentDialog.kt create mode 100644 app/src/main/java/com/futo/platformplayer/dialogs/ConnectCastingDialog.kt create mode 100644 app/src/main/java/com/futo/platformplayer/dialogs/ConnectedCastingDialog.kt create mode 100644 app/src/main/java/com/futo/platformplayer/dialogs/ImportDialog.kt create mode 100644 app/src/main/java/com/futo/platformplayer/dialogs/MigrateDialog.kt create mode 100644 app/src/main/java/com/futo/platformplayer/dialogs/ProgressDialog.kt create mode 100644 app/src/main/java/com/futo/platformplayer/downloads/PlaylistDownloadDescriptor.kt create mode 100644 app/src/main/java/com/futo/platformplayer/downloads/VideoDownload.kt create mode 100644 app/src/main/java/com/futo/platformplayer/downloads/VideoExport.kt create mode 100644 app/src/main/java/com/futo/platformplayer/downloads/VideoLocal.kt create mode 100644 app/src/main/java/com/futo/platformplayer/encryption/EncryptionProvider.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/V8PluginConfig.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/dev/V8RemoteObject.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/exceptions/NoInternetException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/exceptions/PluginException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptAgeException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptCompilationException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptExecutionException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptImplementationException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptTimeoutException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptUnavailableException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptValidationException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/internal/IV8Object.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/internal/V8BindObject.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/internal/V8Converter.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/packages/PackageDOMParser.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/packages/PackageUtilities.kt create mode 100644 app/src/main/java/com/futo/platformplayer/engine/packages/V8Package.kt create mode 100644 app/src/main/java/com/futo/platformplayer/exceptions/ChannelException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/exceptions/MigrationException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/exceptions/ReconstructionException.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelAboutFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelListFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelMonetizationFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelStoreFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/channel/tab/IChannelTabFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/MainActivityFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/BotFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/BrowserFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/BuyFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorFeedView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorSearchResultsFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorsFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/DownloadsFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HistoryFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportPlaylistsFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportSubscriptionsFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/MainFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistSearchResultsFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistsFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourcesFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SuggestionsFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoListEditorView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/WatchLaterFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/AddTopBarFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/GeneralTopBarFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/ImportTopBarFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/NavigationTopBarFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/SearchTopBarFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/TopFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/helpers/DashManifestCreatorsUtils.kt create mode 100644 app/src/main/java/com/futo/platformplayer/helpers/FileHelper.kt create mode 100644 app/src/main/java/com/futo/platformplayer/helpers/ManifestCreatorCache.kt create mode 100644 app/src/main/java/com/futo/platformplayer/helpers/ProgressiveDashManifestCreator.kt create mode 100644 app/src/main/java/com/futo/platformplayer/helpers/VideoHelper.kt create mode 100644 app/src/main/java/com/futo/platformplayer/images/GlideHelper.kt create mode 100644 app/src/main/java/com/futo/platformplayer/images/GrayjayAppGlideModule.java create mode 100644 app/src/main/java/com/futo/platformplayer/images/PolycentricModelLoader.java create mode 100644 app/src/main/java/com/futo/platformplayer/listeners/OrientationManager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/logging/AndroidLogConsumer.kt create mode 100644 app/src/main/java/com/futo/platformplayer/logging/FileLogConsumer.kt create mode 100644 app/src/main/java/com/futo/platformplayer/logging/Logger.kt create mode 100644 app/src/main/java/com/futo/platformplayer/logging/Logging.kt create mode 100644 app/src/main/java/com/futo/platformplayer/models/CastingDeviceInfo.kt create mode 100644 app/src/main/java/com/futo/platformplayer/models/DiskUsage.kt create mode 100644 app/src/main/java/com/futo/platformplayer/models/HistoryVideo.kt create mode 100644 app/src/main/java/com/futo/platformplayer/models/ImageVariable.kt create mode 100644 app/src/main/java/com/futo/platformplayer/models/PlatformVideoWithTime.kt create mode 100644 app/src/main/java/com/futo/platformplayer/models/Playlist.kt create mode 100644 app/src/main/java/com/futo/platformplayer/models/PlaylistDownloaded.kt create mode 100644 app/src/main/java/com/futo/platformplayer/models/SearchType.kt create mode 100644 app/src/main/java/com/futo/platformplayer/models/Subscription.kt create mode 100644 app/src/main/java/com/futo/platformplayer/models/Telemetry.kt create mode 100644 app/src/main/java/com/futo/platformplayer/models/UrlVideoWithTime.kt create mode 100644 app/src/main/java/com/futo/platformplayer/others/Language.kt create mode 100644 app/src/main/java/com/futo/platformplayer/others/LoginWebViewClient.kt create mode 100644 app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt create mode 100644 app/src/main/java/com/futo/platformplayer/polycentric/PolycentricCache.kt create mode 100644 app/src/main/java/com/futo/platformplayer/receivers/AudioNoisyReceiver.kt create mode 100644 app/src/main/java/com/futo/platformplayer/receivers/InstallReceiver.kt create mode 100644 app/src/main/java/com/futo/platformplayer/receivers/MediaControlReceiver.kt create mode 100644 app/src/main/java/com/futo/platformplayer/serializers/FlexibleBooleanSerializer.kt create mode 100644 app/src/main/java/com/futo/platformplayer/serializers/IRatingSerializer.kt create mode 100644 app/src/main/java/com/futo/platformplayer/serializers/OffsetDateTimeSerializer.kt create mode 100644 app/src/main/java/com/futo/platformplayer/serializers/PlatformContentSerializer.kt create mode 100644 app/src/main/java/com/futo/platformplayer/serializers/VideoDescriptorSerializer.kt create mode 100644 app/src/main/java/com/futo/platformplayer/services/DownloadService.kt create mode 100644 app/src/main/java/com/futo/platformplayer/services/ExportingService.kt create mode 100644 app/src/main/java/com/futo/platformplayer/services/MediaPlaybackService.kt create mode 100644 app/src/main/java/com/futo/platformplayer/states/StateAnnouncement.kt create mode 100644 app/src/main/java/com/futo/platformplayer/states/StateApp.kt create mode 100644 app/src/main/java/com/futo/platformplayer/states/StateAssets.kt create mode 100644 app/src/main/java/com/futo/platformplayer/states/StateBackup.kt create mode 100644 app/src/main/java/com/futo/platformplayer/states/StateDeveloper.kt create mode 100644 app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt create mode 100644 app/src/main/java/com/futo/platformplayer/states/StateMeta.kt create mode 100644 app/src/main/java/com/futo/platformplayer/states/StatePayment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt create mode 100644 app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt create mode 100644 app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt create mode 100644 app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt create mode 100644 app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt create mode 100644 app/src/main/java/com/futo/platformplayer/states/StateSaved.kt create mode 100644 app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt create mode 100644 app/src/main/java/com/futo/platformplayer/states/StateTelemetry.kt create mode 100644 app/src/main/java/com/futo/platformplayer/states/StateUpdate.kt create mode 100644 app/src/main/java/com/futo/platformplayer/stores/CastingDeviceInfoStorage.kt create mode 100644 app/src/main/java/com/futo/platformplayer/stores/FragmentedStorage.kt create mode 100644 app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageDirectory.kt create mode 100644 app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageFile.kt create mode 100644 app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageFileJson.kt create mode 100644 app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageFileString.kt create mode 100644 app/src/main/java/com/futo/platformplayer/stores/MapStorage.kt create mode 100644 app/src/main/java/com/futo/platformplayer/stores/PluginIconStorage.kt create mode 100644 app/src/main/java/com/futo/platformplayer/stores/PluginScriptsDirectory.kt create mode 100644 app/src/main/java/com/futo/platformplayer/stores/PluginStorage.kt create mode 100644 app/src/main/java/com/futo/platformplayer/stores/SearchHistoryStorage.kt create mode 100644 app/src/main/java/com/futo/platformplayer/stores/StringArrayStorage.kt create mode 100644 app/src/main/java/com/futo/platformplayer/stores/StringHashSetStorage.kt create mode 100644 app/src/main/java/com/futo/platformplayer/stores/StringStorage.kt create mode 100644 app/src/main/java/com/futo/platformplayer/stores/SubscriptionStorage.kt create mode 100644 app/src/main/java/com/futo/platformplayer/stores/WatchLaterStorage.kt create mode 100644 app/src/main/java/com/futo/platformplayer/stores/v2/IStoreItem.kt create mode 100644 app/src/main/java/com/futo/platformplayer/stores/v2/ManagedStore.kt create mode 100644 app/src/main/java/com/futo/platformplayer/stores/v2/ReconstructStore.kt create mode 100644 app/src/main/java/com/futo/platformplayer/video/PlayerManager.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/AnyAdapterView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/FeedStyle.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/Loader.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/AnyAdapter.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/ChannelViewPagerAdapter.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/CommentViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/ContentPreviewViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/DeviceAdapter.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/DeviceViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/DisabledSourceAdapter.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/DisabledSourceView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/DisabledSourceViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/EmptyPreviewViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/EnabledSourceAdapter.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/EnabledSourceViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/HistoryListAdapter.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/HistoryListViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/InsertedViewAdapter.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/InsertedViewAdapterWithLoader.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/InsertedViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/ItemMoveCallback.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistsAdapter.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistsViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/PreviewContentListAdapter.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/PreviewNestedVideoView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/PreviewNestedVideoViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/PreviewPlaceholderViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/PreviewPlaylistViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/PreviewPostView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/PreviewPostViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/PreviewVideoView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/PreviewVideoViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/SearchSuggestionAdapter.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/SearchSuggestionViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionAdapter.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorAdapter.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/VideoListHorizontalAdapter.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/VideoListHorizontalViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/ImportPlaylistsViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/ImportSubscriptionViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/SubscriptionBarViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/TabViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/VideoDownloadViewHolder.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/behavior/TouchInterceptFrameLayout.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/buttons/BigButton.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/buttons/BigButtonGroup.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/buttons/DescButton.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/casting/CastButton.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/comments/AddCommentView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/containers/DoubleTapLayout.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/containers/SingleViewTouchableMotionLayout.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/fields/ButtonField.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/fields/DropdownField.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/fields/Field.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/fields/FieldForm.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/fields/GroupField.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/fields/ReadOnlyTextField.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/items/ActiveDownloadItem.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/items/PlaylistDownloadItem.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/lists/VideoListEditorView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatDonationListItem.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatDonationPill.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatListAdapter.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatListItem.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatMessageListItem.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/others/BulletPointView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/others/Checkbox.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/others/CircularProgressBar.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/others/CreatorThumbnail.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/others/ProgressBar.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/others/RadioGroupView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/others/RadioView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/others/TagView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/others/TagsView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/others/Toggle.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/others/ToggleTagView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/overlays/DescriptionOverlay.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/overlays/LiveChatOverlay.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/overlays/LoaderOverlay.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/overlays/OverlayTopbar.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/overlays/QueueEditorOverlay.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/overlays/RepliesOverlay.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuButtonList.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuFilters.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuGroup.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuItem.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuOverlay.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuRadioGroup.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuTextInput.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuTitle.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/pills/PillButton.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/pills/PillRatingLikesDislikes.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/pills/RoundButton.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/pills/RoundButtonGroup.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/platform/PlatformIndicator.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/platform/PlatformLinkView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/segments/CommentsList.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/sources/SourceHeaderView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/sources/SourceInfoView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/sources/SourceUnderConstructionView.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/subscriptions/SubscribeButton.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/subscriptions/SubscriptionBar.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/video/FutoThumbnailPlayer.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/video/datasources/JSHttpDataSource.java create mode 100644 app/src/main/java/com/futo/platformplayer/views/videometa/UpNextView.kt create mode 100644 app/src/main/proto/com/futo/platformplayer/protos/DeviceAuthMessage.proto create mode 100644 app/src/main/res/anim/slide_darken.xml create mode 100644 app/src/main/res/anim/slide_in_up.xml create mode 100644 app/src/main/res/anim/slide_lighten.xml create mode 100644 app/src/main/res/anim/slide_out_up.xml create mode 100644 app/src/main/res/animator/fade.xml create mode 100644 app/src/main/res/animator/fade_400.xml create mode 100644 app/src/main/res/animator/fade_800.xml create mode 100644 app/src/main/res/animator/rotation_1500_clockwise.xml create mode 100644 app/src/main/res/animator/rotation_2000_clockwise.xml create mode 100644 app/src/main/res/animator/rotation_2500_counter_clockwise.xml create mode 100644 app/src/main/res/animator/toggle_background.xml create mode 100644 app/src/main/res/animator/toggle_background_reverse.xml create mode 100644 app/src/main/res/animator/toggle_foreground.xml create mode 100644 app/src/main/res/animator/toggle_foreground_container.xml create mode 100644 app/src/main/res/animator/toggle_foreground_container_reverse.xml create mode 100644 app/src/main/res/animator/toggle_foreground_reverse.xml create mode 100644 app/src/main/res/drawable-v24/example_banner.jpg create mode 100644 app/src/main/res/drawable-v24/placeholder_channel_thumbnail.jpg create mode 100644 app/src/main/res/drawable-v24/placeholder_video_thumbnail.webp create mode 100644 app/src/main/res/drawable/background_16_round_4dp.xml create mode 100644 app/src/main/res/drawable/background_1d_round_4dp.xml create mode 100644 app/src/main/res/drawable/background_30_round_4dp.xml create mode 100644 app/src/main/res/drawable/background_big_button.xml create mode 100644 app/src/main/res/drawable/background_big_button_red.xml create mode 100644 app/src/main/res/drawable/background_button_accent.xml create mode 100644 app/src/main/res/drawable/background_button_accent_straight_left.xml create mode 100644 app/src/main/res/drawable/background_button_black.xml create mode 100644 app/src/main/res/drawable/background_button_err.xml create mode 100644 app/src/main/res/drawable/background_button_err_round_4dp.xml create mode 100644 app/src/main/res/drawable/background_button_gray_straight_left.xml create mode 100644 app/src/main/res/drawable/background_button_pred.xml create mode 100644 app/src/main/res/drawable/background_button_primary.xml create mode 100644 app/src/main/res/drawable/background_button_primary_round.xml create mode 100644 app/src/main/res/drawable/background_button_primary_round_4dp.xml create mode 100644 app/src/main/res/drawable/background_button_primary_straight_left.xml create mode 100644 app/src/main/res/drawable/background_button_round.xml create mode 100644 app/src/main/res/drawable/background_button_round_green.xml create mode 100644 app/src/main/res/drawable/background_button_source.xml create mode 100644 app/src/main/res/drawable/background_button_transparent_round.xml create mode 100644 app/src/main/res/drawable/background_channel_round.xml create mode 100644 app/src/main/res/drawable/background_fade.xml create mode 100644 app/src/main/res/drawable/background_gesture_controls.xml create mode 100644 app/src/main/res/drawable/background_pill.xml create mode 100644 app/src/main/res/drawable/background_pill_black.xml create mode 100644 app/src/main/res/drawable/background_pill_toggled.xml create mode 100644 app/src/main/res/drawable/background_pill_transparent.xml create mode 100644 app/src/main/res/drawable/background_pill_untoggled.xml create mode 100644 app/src/main/res/drawable/background_radio_selected.xml create mode 100644 app/src/main/res/drawable/background_radio_unselected.xml create mode 100644 app/src/main/res/drawable/background_slide_up_option.xml create mode 100644 app/src/main/res/drawable/background_slide_up_option_selected.xml create mode 100644 app/src/main/res/drawable/background_small_button.xml create mode 100644 app/src/main/res/drawable/background_square.xml create mode 100644 app/src/main/res/drawable/background_store.xml create mode 100644 app/src/main/res/drawable/background_support.xml create mode 100644 app/src/main/res/drawable/background_thumbnail_duration.xml create mode 100644 app/src/main/res/drawable/background_thumbnail_live.xml create mode 100644 app/src/main/res/drawable/background_thumbnail_video_options.xml create mode 100644 app/src/main/res/drawable/background_up_next_toggle_active.xml create mode 100644 app/src/main/res/drawable/background_videodetail_description.xml create mode 100644 app/src/main/res/drawable/banner_placeholder.png create mode 100644 app/src/main/res/drawable/bottom_gradient.xml create mode 100644 app/src/main/res/drawable/bottom_menu_border.xml create mode 100644 app/src/main/res/drawable/channel_icon_outline.xml create mode 100644 app/src/main/res/drawable/download_for_offline.xml create mode 100644 app/src/main/res/drawable/edit_text_background.xml create mode 100644 app/src/main/res/drawable/edit_text_cursor.xml create mode 100644 app/src/main/res/drawable/foreground.png create mode 100644 app/src/main/res/drawable/ic_add.xml create mode 100644 app/src/main/res/drawable/ic_add_400.xml create mode 100644 app/src/main/res/drawable/ic_add_to_query_16dp.xml create mode 100644 app/src/main/res/drawable/ic_add_white_16dp.xml create mode 100644 app/src/main/res/drawable/ic_add_white_8dp.xml create mode 100644 app/src/main/res/drawable/ic_airplay.xml create mode 100644 app/src/main/res/drawable/ic_arrow_downward.xml create mode 100644 app/src/main/res/drawable/ic_arrow_right.xml create mode 100644 app/src/main/res/drawable/ic_aspect_ratio.xml create mode 100644 app/src/main/res/drawable/ic_back_nav.xml create mode 100644 app/src/main/res/drawable/ic_back_thin_white_16dp.xml create mode 100644 app/src/main/res/drawable/ic_back_white_24dp.xml create mode 100644 app/src/main/res/drawable/ic_block.xml create mode 100644 app/src/main/res/drawable/ic_brightness.xml create mode 100644 app/src/main/res/drawable/ic_brightness_1.xml create mode 100644 app/src/main/res/drawable/ic_cast.xml create mode 100644 app/src/main/res/drawable/ic_cast_white_20dp.xml create mode 100644 app/src/main/res/drawable/ic_cast_white_25dp.xml create mode 100644 app/src/main/res/drawable/ic_cast_white_32dp.xml create mode 100644 app/src/main/res/drawable/ic_chat.xml create mode 100644 app/src/main/res/drawable/ic_check.xml create mode 100644 app/src/main/res/drawable/ic_checkbox_checked.xml create mode 100644 app/src/main/res/drawable/ic_checkbox_unchecked.xml create mode 100644 app/src/main/res/drawable/ic_chromecast.xml create mode 100644 app/src/main/res/drawable/ic_clear_16dp.xml create mode 100644 app/src/main/res/drawable/ic_clock_white.xml create mode 100644 app/src/main/res/drawable/ic_close.xml create mode 100644 app/src/main/res/drawable/ic_close_thin.xml create mode 100644 app/src/main/res/drawable/ic_code.xml create mode 100644 app/src/main/res/drawable/ic_code_red.xml create mode 100644 app/src/main/res/drawable/ic_construction.xml create mode 100644 app/src/main/res/drawable/ic_copy.xml create mode 100644 app/src/main/res/drawable/ic_creators.xml create mode 100644 app/src/main/res/drawable/ic_creators_active.xml create mode 100644 app/src/main/res/drawable/ic_creators_filled.xml create mode 100644 app/src/main/res/drawable/ic_creators_inactive.xml create mode 100644 app/src/main/res/drawable/ic_download.xml create mode 100644 app/src/main/res/drawable/ic_download_off.xml create mode 100644 app/src/main/res/drawable/ic_dragdrop_white.xml create mode 100644 app/src/main/res/drawable/ic_edit.xml create mode 100644 app/src/main/res/drawable/ic_error.xml create mode 100644 app/src/main/res/drawable/ic_error_pred.xml create mode 100644 app/src/main/res/drawable/ic_expand.xml create mode 100644 app/src/main/res/drawable/ic_expand_18dp.xml create mode 100644 app/src/main/res/drawable/ic_expand_white.xml create mode 100644 app/src/main/res/drawable/ic_export.xml create mode 100644 app/src/main/res/drawable/ic_fast_forward.xml create mode 100644 app/src/main/res/drawable/ic_fast_forward_notif.xml create mode 100644 app/src/main/res/drawable/ic_fast_rewind.xml create mode 100644 app/src/main/res/drawable/ic_fast_rewind_notif.xml create mode 100644 app/src/main/res/drawable/ic_fastforward.xml create mode 100644 app/src/main/res/drawable/ic_fastforward_animated.xml create mode 100644 app/src/main/res/drawable/ic_fc.xml create mode 100644 app/src/main/res/drawable/ic_filter_settings_25dp.xml create mode 100644 app/src/main/res/drawable/ic_forum.xml create mode 100644 app/src/main/res/drawable/ic_futo_logo.xml create mode 100644 app/src/main/res/drawable/ic_futo_logo_text.xml create mode 100644 app/src/main/res/drawable/ic_help.xml create mode 100644 app/src/main/res/drawable/ic_history.xml create mode 100644 app/src/main/res/drawable/ic_history_29dp.xml create mode 100644 app/src/main/res/drawable/ic_home.xml create mode 100644 app/src/main/res/drawable/ic_home_active_25dp.xml create mode 100644 app/src/main/res/drawable/ic_home_filled.xml create mode 100644 app/src/main/res/drawable/ic_home_inactive_25dp.xml create mode 100644 app/src/main/res/drawable/ic_image.xml create mode 100644 app/src/main/res/drawable/ic_language.xml create mode 100644 app/src/main/res/drawable/ic_link.xml create mode 100644 app/src/main/res/drawable/ic_list.xml create mode 100644 app/src/main/res/drawable/ic_loader.xml create mode 100644 app/src/main/res/drawable/ic_loader_animated.xml create mode 100644 app/src/main/res/drawable/ic_lock.xml create mode 100644 app/src/main/res/drawable/ic_login.xml create mode 100644 app/src/main/res/drawable/ic_logout.xml create mode 100644 app/src/main/res/drawable/ic_menu.xml create mode 100644 app/src/main/res/drawable/ic_minimize.xml create mode 100644 app/src/main/res/drawable/ic_more.xml create mode 100644 app/src/main/res/drawable/ic_more_inactive_25dp.xml create mode 100644 app/src/main/res/drawable/ic_move_up.xml create mode 100644 app/src/main/res/drawable/ic_movie.xml create mode 100644 app/src/main/res/drawable/ic_music.xml create mode 100644 app/src/main/res/drawable/ic_no_internet_86dp.xml create mode 100644 app/src/main/res/drawable/ic_paid.xml create mode 100644 app/src/main/res/drawable/ic_paid_200.xml create mode 100644 app/src/main/res/drawable/ic_pause.xml create mode 100644 app/src/main/res/drawable/ic_pause_notif.xml create mode 100644 app/src/main/res/drawable/ic_pause_white.xml create mode 100644 app/src/main/res/drawable/ic_peertube.png create mode 100644 app/src/main/res/drawable/ic_person.xml create mode 100644 app/src/main/res/drawable/ic_person_add.xml create mode 100644 app/src/main/res/drawable/ic_person_search_300w.xml create mode 100644 app/src/main/res/drawable/ic_pin.xml create mode 100644 app/src/main/res/drawable/ic_play.xml create mode 100644 app/src/main/res/drawable/ic_play_200w.xml create mode 100644 app/src/main/res/drawable/ic_play_notif.xml create mode 100644 app/src/main/res/drawable/ic_play_white_nopad.xml create mode 100644 app/src/main/res/drawable/ic_playlist.xml create mode 100644 app/src/main/res/drawable/ic_playlist_add.xml create mode 100644 app/src/main/res/drawable/ic_playlist_filled.xml create mode 100644 app/src/main/res/drawable/ic_playlist_select.xml create mode 100644 app/src/main/res/drawable/ic_playlists_active.xml create mode 100644 app/src/main/res/drawable/ic_playlists_inactive_25dp.xml create mode 100644 app/src/main/res/drawable/ic_qr.xml create mode 100644 app/src/main/res/drawable/ic_queue_16dp.xml create mode 100644 app/src/main/res/drawable/ic_queue_add.xml create mode 100644 app/src/main/res/drawable/ic_refresh.xml create mode 100644 app/src/main/res/drawable/ic_remove.xml create mode 100644 app/src/main/res/drawable/ic_remove_white_8dp.xml create mode 100644 app/src/main/res/drawable/ic_repeat.xml create mode 100644 app/src/main/res/drawable/ic_replay.xml create mode 100644 app/src/main/res/drawable/ic_rewind.xml create mode 100644 app/src/main/res/drawable/ic_rewind_animated.xml create mode 100644 app/src/main/res/drawable/ic_save.xml create mode 100644 app/src/main/res/drawable/ic_schedule.xml create mode 100644 app/src/main/res/drawable/ic_screen_lock_rotation.xml create mode 100644 app/src/main/res/drawable/ic_screen_rotation.xml create mode 100644 app/src/main/res/drawable/ic_screen_share.xml create mode 100644 app/src/main/res/drawable/ic_search.xml create mode 100644 app/src/main/res/drawable/ic_search_300w.xml create mode 100644 app/src/main/res/drawable/ic_search_thin.xml create mode 100644 app/src/main/res/drawable/ic_search_white_16dp.xml create mode 100644 app/src/main/res/drawable/ic_search_white_20dp.xml create mode 100644 app/src/main/res/drawable/ic_search_white_25dp.xml create mode 100644 app/src/main/res/drawable/ic_security.xml create mode 100644 app/src/main/res/drawable/ic_security_pred.xml create mode 100644 app/src/main/res/drawable/ic_security_red.xml create mode 100644 app/src/main/res/drawable/ic_settings.xml create mode 100644 app/src/main/res/drawable/ic_settings_29dp.xml create mode 100644 app/src/main/res/drawable/ic_share.xml create mode 100644 app/src/main/res/drawable/ic_shuffle.xml create mode 100644 app/src/main/res/drawable/ic_smart_display.xml create mode 100644 app/src/main/res/drawable/ic_sources.xml create mode 100644 app/src/main/res/drawable/ic_sources_active.xml create mode 100644 app/src/main/res/drawable/ic_sources_filled.xml create mode 100644 app/src/main/res/drawable/ic_sources_inactive.xml create mode 100644 app/src/main/res/drawable/ic_stop_notif.xml create mode 100644 app/src/main/res/drawable/ic_store.xml create mode 100644 app/src/main/res/drawable/ic_store_200.xml create mode 100644 app/src/main/res/drawable/ic_subscriptions.xml create mode 100644 app/src/main/res/drawable/ic_subscriptions_active_25dp.xml create mode 100644 app/src/main/res/drawable/ic_subscriptions_filled.xml create mode 100644 app/src/main/res/drawable/ic_subscriptions_inactive_25dp.xml create mode 100644 app/src/main/res/drawable/ic_thumb_down.xml create mode 100644 app/src/main/res/drawable/ic_thumb_up.xml create mode 100644 app/src/main/res/drawable/ic_trash.xml create mode 100644 app/src/main/res/drawable/ic_trash_18dp.xml create mode 100644 app/src/main/res/drawable/ic_tune_300.xml create mode 100644 app/src/main/res/drawable/ic_update.xml create mode 100644 app/src/main/res/drawable/ic_update_animated.xml create mode 100644 app/src/main/res/drawable/ic_update_fail_251dp.xml create mode 100644 app/src/main/res/drawable/ic_update_success_251dp.xml create mode 100644 app/src/main/res/drawable/ic_view_queue.xml create mode 100644 app/src/main/res/drawable/ic_visibility_off.xml create mode 100644 app/src/main/res/drawable/ic_volume_off.xml create mode 100644 app/src/main/res/drawable/ic_volume_up.xml create mode 100644 app/src/main/res/drawable/ic_volume_up_1.xml create mode 100644 app/src/main/res/drawable/ic_watchlist_add.xml create mode 100644 app/src/main/res/drawable/ic_web_white.xml create mode 100644 app/src/main/res/drawable/ic_wrench.xml create mode 100644 app/src/main/res/drawable/neopass.png create mode 100644 app/src/main/res/drawable/neopass_button.png create mode 100644 app/src/main/res/drawable/placeholder_profile.xml create mode 100644 app/src/main/res/drawable/player_progress.xml create mode 100644 app/src/main/res/drawable/player_seekbar_style.xml create mode 100644 app/src/main/res/drawable/player_thumb.xml create mode 100644 app/src/main/res/drawable/rounded_outline.xml create mode 100644 app/src/main/res/drawable/rounded_square_outline.xml create mode 100644 app/src/main/res/drawable/stripe.png create mode 100644 app/src/main/res/drawable/tab_border.xml create mode 100644 app/src/main/res/drawable/toggle.xml create mode 100644 app/src/main/res/drawable/toggle_animated.xml create mode 100644 app/src/main/res/drawable/toggle_animated_reverse.xml create mode 100644 app/src/main/res/drawable/toggle_disabled.xml create mode 100644 app/src/main/res/drawable/toggle_enabled.xml create mode 100644 app/src/main/res/drawable/video_thumbnail_outline.xml create mode 100644 app/src/main/res/font/inter_black.ttf create mode 100644 app/src/main/res/font/inter_bold.ttf create mode 100644 app/src/main/res/font/inter_extra_bold.ttf create mode 100644 app/src/main/res/font/inter_extra_light.ttf create mode 100644 app/src/main/res/font/inter_light.ttf create mode 100644 app/src/main/res/font/inter_medium.ttf create mode 100644 app/src/main/res/font/inter_regular.ttf create mode 100644 app/src/main/res/font/inter_semibold.ttf create mode 100644 app/src/main/res/font/inter_thin.ttf create mode 100644 app/src/main/res/layout/activity_add_source.xml create mode 100644 app/src/main/res/layout/activity_add_source_options.xml create mode 100644 app/src/main/res/layout/activity_dev.xml create mode 100644 app/src/main/res/layout/activity_exception.xml create mode 100644 app/src/main/res/layout/activity_login.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/activity_manage_tabs.xml create mode 100644 app/src/main/res/layout/activity_pip.xml create mode 100644 app/src/main/res/layout/activity_polycentric_backup.xml create mode 100644 app/src/main/res/layout/activity_polycentric_create_profile.xml create mode 100644 app/src/main/res/layout/activity_polycentric_home.xml create mode 100644 app/src/main/res/layout/activity_polycentric_import_profile.xml create mode 100644 app/src/main/res/layout/activity_polycentric_profile.xml create mode 100644 app/src/main/res/layout/activity_polycentric_why.xml create mode 100644 app/src/main/res/layout/activity_settings.xml create mode 100644 app/src/main/res/layout/activity_test.xml create mode 100644 app/src/main/res/layout/big_button.xml create mode 100644 app/src/main/res/layout/big_button_group.xml create mode 100644 app/src/main/res/layout/button_round.xml create mode 100644 app/src/main/res/layout/button_subscribe.xml create mode 100644 app/src/main/res/layout/dialog_automatic_backup.xml create mode 100644 app/src/main/res/layout/dialog_automatic_backup_restore.xml create mode 100644 app/src/main/res/layout/dialog_casting_add.xml create mode 100644 app/src/main/res/layout/dialog_casting_connect.xml create mode 100644 app/src/main/res/layout/dialog_casting_connected.xml create mode 100644 app/src/main/res/layout/dialog_changelog.xml create mode 100644 app/src/main/res/layout/dialog_comment.xml create mode 100644 app/src/main/res/layout/dialog_confirm.xml create mode 100644 app/src/main/res/layout/dialog_data.xml create mode 100644 app/src/main/res/layout/dialog_import.xml create mode 100644 app/src/main/res/layout/dialog_migrate.xml create mode 100644 app/src/main/res/layout/dialog_multi_button.xml create mode 100644 app/src/main/res/layout/dialog_progress.xml create mode 100644 app/src/main/res/layout/dialog_single_button.xml create mode 100644 app/src/main/res/layout/dialog_update.xml create mode 100644 app/src/main/res/layout/field_button.xml create mode 100644 app/src/main/res/layout/field_dropdown.xml create mode 100644 app/src/main/res/layout/field_form.xml create mode 100644 app/src/main/res/layout/field_group.xml create mode 100644 app/src/main/res/layout/field_readonly_text.xml create mode 100644 app/src/main/res/layout/field_toggle.xml create mode 100644 app/src/main/res/layout/fragment_add_top_bar.xml create mode 100644 app/src/main/res/layout/fragment_browser.xml create mode 100644 app/src/main/res/layout/fragment_buy.xml create mode 100644 app/src/main/res/layout/fragment_channel.xml create mode 100644 app/src/main/res/layout/fragment_channel_about.xml create mode 100644 app/src/main/res/layout/fragment_channel_list.xml create mode 100644 app/src/main/res/layout/fragment_channel_monetization.xml create mode 100644 app/src/main/res/layout/fragment_channel_store.xml create mode 100644 app/src/main/res/layout/fragment_channel_videos.xml create mode 100644 app/src/main/res/layout/fragment_creators.xml create mode 100644 app/src/main/res/layout/fragment_downloads.xml create mode 100644 app/src/main/res/layout/fragment_feed.xml create mode 100644 app/src/main/res/layout/fragment_history.xml create mode 100644 app/src/main/res/layout/fragment_import.xml create mode 100644 app/src/main/res/layout/fragment_import_top_bar.xml create mode 100644 app/src/main/res/layout/fragment_navigation_top_bar.xml create mode 100644 app/src/main/res/layout/fragment_overview_bottom_bar.xml create mode 100644 app/src/main/res/layout/fragment_overview_top_bar.xml create mode 100644 app/src/main/res/layout/fragment_playlists.xml create mode 100644 app/src/main/res/layout/fragment_search_top_bar.xml create mode 100644 app/src/main/res/layout/fragment_source_detail.xml create mode 100644 app/src/main/res/layout/fragment_sources.xml create mode 100644 app/src/main/res/layout/fragment_suggestion_list.xml create mode 100644 app/src/main/res/layout/fragment_video_detail.xml create mode 100644 app/src/main/res/layout/fragment_video_list_editor.xml create mode 100644 app/src/main/res/layout/fragview_post_detail.xml create mode 100644 app/src/main/res/layout/fragview_video_detail.xml create mode 100644 app/src/main/res/layout/ic_sources_active.xml create mode 100644 app/src/main/res/layout/ic_sources_inactive.xml create mode 100644 app/src/main/res/layout/list_chat_donation.xml create mode 100644 app/src/main/res/layout/list_chat_message.xml create mode 100644 app/src/main/res/layout/list_comment.xml create mode 100644 app/src/main/res/layout/list_creator.xml create mode 100644 app/src/main/res/layout/list_device.xml create mode 100644 app/src/main/res/layout/list_donation.xml create mode 100644 app/src/main/res/layout/list_download.xml create mode 100644 app/src/main/res/layout/list_downloaded.xml create mode 100644 app/src/main/res/layout/list_downloaded_playlist.xml create mode 100644 app/src/main/res/layout/list_history.xml create mode 100644 app/src/main/res/layout/list_import_playlist.xml create mode 100644 app/src/main/res/layout/list_import_subscription.xml create mode 100644 app/src/main/res/layout/list_placeholder_preview.xml create mode 100644 app/src/main/res/layout/list_placeholder_thumbnail.xml create mode 100644 app/src/main/res/layout/list_playlist.xml create mode 100644 app/src/main/res/layout/list_playlist_feed.xml create mode 100644 app/src/main/res/layout/list_playlist_feed_preview.xml create mode 100644 app/src/main/res/layout/list_playlists.xml create mode 100644 app/src/main/res/layout/list_post_preview.xml create mode 100644 app/src/main/res/layout/list_post_thumbnail.xml create mode 100644 app/src/main/res/layout/list_search_suggestion.xml create mode 100644 app/src/main/res/layout/list_source_construction.xml create mode 100644 app/src/main/res/layout/list_source_disabled.xml create mode 100644 app/src/main/res/layout/list_source_enabled.xml create mode 100644 app/src/main/res/layout/list_subscription.xml create mode 100644 app/src/main/res/layout/list_tab.xml create mode 100644 app/src/main/res/layout/list_video_horizontal.xml create mode 100644 app/src/main/res/layout/list_video_preview.xml create mode 100644 app/src/main/res/layout/list_video_preview_nested.xml create mode 100644 app/src/main/res/layout/list_video_thumbnail.xml create mode 100644 app/src/main/res/layout/list_video_thumbnail_nested.xml create mode 100644 app/src/main/res/layout/overlay_description.xml create mode 100644 app/src/main/res/layout/overlay_livechat.xml create mode 100644 app/src/main/res/layout/overlay_loader.xml create mode 100644 app/src/main/res/layout/overlay_queue.xml create mode 100644 app/src/main/res/layout/overlay_replies.xml create mode 100644 app/src/main/res/layout/overlay_slide_up_menu.xml create mode 100644 app/src/main/res/layout/overlay_slide_up_menu_button_list.xml create mode 100644 app/src/main/res/layout/overlay_slide_up_menu_group.xml create mode 100644 app/src/main/res/layout/overlay_slide_up_menu_option.xml create mode 100644 app/src/main/res/layout/overlay_slide_up_menu_radio_group.xml create mode 100644 app/src/main/res/layout/overlay_slide_up_menu_text_input.xml create mode 100644 app/src/main/res/layout/overlay_slide_up_menu_title.xml create mode 100644 app/src/main/res/layout/overlay_topbar.xml create mode 100644 app/src/main/res/layout/payment_checkout.xml create mode 100644 app/src/main/res/layout/payment_country.xml create mode 100644 app/src/main/res/layout/payment_currency.xml create mode 100644 app/src/main/res/layout/payment_currency_other.xml create mode 100644 app/src/main/res/layout/payment_method.xml create mode 100644 app/src/main/res/layout/payment_overlay.xml create mode 100644 app/src/main/res/layout/payment_postal_code.xml create mode 100644 app/src/main/res/layout/pill_button.xml create mode 100644 app/src/main/res/layout/rating_likesdislikes.xml create mode 100644 app/src/main/res/layout/spinner_dropdownitem_simple.xml create mode 100644 app/src/main/res/layout/spinner_item_simple.xml create mode 100644 app/src/main/res/layout/thumbnail_player_ui.xml create mode 100644 app/src/main/res/layout/thumbnail_video_view.xml create mode 100644 app/src/main/res/layout/toast_clickable.xml create mode 100644 app/src/main/res/layout/video_player_ui.xml create mode 100644 app/src/main/res/layout/video_player_ui_bar.xml create mode 100644 app/src/main/res/layout/video_player_ui_fullscreen.xml create mode 100644 app/src/main/res/layout/video_view.xml create mode 100644 app/src/main/res/layout/view_add_comment.xml create mode 100644 app/src/main/res/layout/view_announcement.xml create mode 100644 app/src/main/res/layout/view_bottom_menu_button.xml create mode 100644 app/src/main/res/layout/view_bottom_more_menu_button.xml create mode 100644 app/src/main/res/layout/view_bullet_point.xml create mode 100644 app/src/main/res/layout/view_cast.xml create mode 100644 app/src/main/res/layout/view_comments_list.xml create mode 100644 app/src/main/res/layout/view_creator_thumbnail.xml create mode 100644 app/src/main/res/layout/view_desc_button.xml create mode 100644 app/src/main/res/layout/view_filter_bar.xml create mode 100644 app/src/main/res/layout/view_gesture_controls.xml create mode 100644 app/src/main/res/layout/view_loader.xml create mode 100644 app/src/main/res/layout/view_platform_link.xml create mode 100644 app/src/main/res/layout/view_radio.xml create mode 100644 app/src/main/res/layout/view_source_header.xml create mode 100644 app/src/main/res/layout/view_source_info.xml create mode 100644 app/src/main/res/layout/view_subscription_bar.xml create mode 100644 app/src/main/res/layout/view_subscription_bar_icon.xml create mode 100644 app/src/main/res/layout/view_tag.xml create mode 100644 app/src/main/res/layout/view_toggle_tag.xml create mode 100644 app/src/main/res/layout/view_up_next.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values/announcement_view_attrs.xml create mode 100644 app/src/main/res/values/big_button_attrs.xml create mode 100644 app/src/main/res/values/bullet_point_attrs.xml create mode 100644 app/src/main/res/values/circular_progress_bar_attrs.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimensions.xml create mode 100644 app/src/main/res/values/futo_video_player.xml create mode 100644 app/src/main/res/values/ic_launcher_background.xml create mode 100644 app/src/main/res/values/loader_attrs.xml create mode 100644 app/src/main/res/values/overlay_topbar_attrs.xml create mode 100644 app/src/main/res/values/pill_button_attrs.xml create mode 100644 app/src/main/res/values/progress_bar_attrs.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 app/src/main/res/values/toggle_attrs.xml create mode 100644 app/src/main/res/values/touch_intercept_frame_layout_attrs.xml create mode 100644 app/src/main/res/values/view_attrs.xml create mode 100644 app/src/main/res/xml/activity_main_scene.xml create mode 100644 app/src/main/res/xml/backup_rules.xml create mode 100644 app/src/main/res/xml/data_extraction_rules.xml create mode 100644 app/src/main/res/xml/file_paths.xml create mode 100644 app/src/main/res/xml/file_provider_paths.xml create mode 100644 app/src/main/res/xml/fragview_videodetail_scene.xml create mode 100644 app/src/main/res/xml/videodetail_scene.xml create mode 100644 app/src/playstore/AndroidManifest.xml create mode 160000 app/src/playstore/assets/sources/peertube create mode 100644 app/src/playstore/res/raw/plugin_config.json create mode 100644 app/src/stable/AndroidManifest.xml create mode 160000 app/src/stable/assets/sources/kick create mode 160000 app/src/stable/assets/sources/nebula create mode 160000 app/src/stable/assets/sources/odysee create mode 160000 app/src/stable/assets/sources/patreon create mode 160000 app/src/stable/assets/sources/peertube create mode 160000 app/src/stable/assets/sources/rumble create mode 160000 app/src/stable/assets/sources/soundcloud create mode 160000 app/src/stable/assets/sources/twitch create mode 160000 app/src/stable/assets/sources/youtube create mode 100644 app/src/stable/res/drawable/logo_subscribestar.png create mode 100644 app/src/stable/res/raw/plugin_config.json create mode 100644 app/src/test/java/android/util/Log.java create mode 100644 app/src/test/java/com/futo/platformplayer/BackgroundTaskHandlerTests.kt create mode 100644 app/src/test/java/com/futo/platformplayer/BatchedTaskHandlerTests.kt create mode 100644 app/src/test/java/com/futo/platformplayer/DashBuilderTests.kt create mode 100644 app/src/test/java/com/futo/platformplayer/DashManifestCreatorsUtilsTests.kt create mode 100644 app/src/test/java/com/futo/platformplayer/ExtensionsCoroutinesTests.kt create mode 100644 app/src/test/java/com/futo/platformplayer/ExtensionsFormattingTests.kt create mode 100644 app/src/test/java/com/futo/platformplayer/ExtensionsNetworkTests.kt create mode 100644 app/src/test/java/com/futo/platformplayer/LoggerTests.kt create mode 100644 app/src/test/java/com/futo/platformplayer/PaymentTests.kt create mode 100644 app/src/test/java/com/futo/platformplayer/ProgressiveDashManifestCreatorTest.kt create mode 100644 app/src/test/java/com/futo/platformplayer/RequireMigrationTests.kt create mode 100644 app/src/test/java/com/futo/platformplayer/TaskHandlerTests.kt create mode 100644 app/src/test/java/com/futo/platformplayer/UtilityTests.kt create mode 100644 app/src/test/java/com/futo/platformplayer/XmlBuilderTests.kt create mode 100644 app/src/unstable/AndroidManifest.xml create mode 160000 app/src/unstable/assets/sources/kick create mode 160000 app/src/unstable/assets/sources/nebula create mode 160000 app/src/unstable/assets/sources/odysee create mode 160000 app/src/unstable/assets/sources/patreon create mode 160000 app/src/unstable/assets/sources/peertube create mode 160000 app/src/unstable/assets/sources/rumble create mode 160000 app/src/unstable/assets/sources/soundcloud create mode 160000 app/src/unstable/assets/sources/twitch create mode 160000 app/src/unstable/assets/sources/youtube create mode 100644 app/src/unstable/res/drawable/logo_subscribestar.png create mode 100644 app/src/unstable/res/raw/plugin_config.json create mode 100644 build.gradle create mode 160000 dep/futopay create mode 160000 dep/polycentricandroid create mode 100644 deploy-playstore.sh create mode 100644 deploy-stable.sh create mode 100644 deploy-unstable.sh create mode 100644 docs/casting.md create mode 100644 docs/linking.md create mode 100644 docs/packages/packageHttp.md create mode 100644 docs/polycentric.md create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100755 images/casting.jpg create mode 100755 images/channel.jpg create mode 100644 images/creators.png create mode 100755 images/downloads.jpg create mode 100755 images/history.jpg create mode 100755 images/playlist.jpg create mode 100755 images/playlists.jpg create mode 100755 images/search-list.jpg create mode 100755 images/search-preview.jpg create mode 100755 images/search-suggestions.jpg create mode 100755 images/settings.jpg create mode 100644 images/source-install.png create mode 100755 images/source-settings.jpg create mode 100755 images/source.jpg create mode 100755 images/sources-disabled.jpg create mode 100755 images/sources.jpg create mode 100644 images/subscriptions-list.png create mode 100644 images/subscriptions-preview.png create mode 100755 images/video-details.jpg create mode 100755 images/video.jpg create mode 100644 plugin-development.md create mode 100755 pull-all-submodules.sh create mode 100644 scripts/clone-all-sources.sh create mode 100644 scripts/commit-and-push-all-sources.sh create mode 100644 scripts/pull-all-sources.sh create mode 100644 scripts/sign-script.sh create mode 100644 settings.gradle diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..eb9709af --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,36 @@ +variables: + GIT_SUBMODULE_STRATEGY: recursive + +stages: +- buildAndDeployApkUnstable +- buildAndDeployApkStable + +buildAndDeployApkUnstable: + stage: buildAndDeployApkUnstable + script: + - sh deploy-unstable.sh + only: + - tags + except: + - ^(dev) + when: manual + +buildAndDeployApkStable: + stage: buildAndDeployApkStable + script: + - sh deploy-stable.sh + only: + - tags + except: + - branches + when: manual + +buildAndDeployApkStable: + stage: buildAndDeployApkStable + script: + - sh deploy-playstore.sh + only: + - tags + except: + - branches + when: manual diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..3c5b4994 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,63 @@ +[submodule "dep/polycentricandroid"] + path = dep/polycentricandroid + url = ../polycentricandroid.git +[submodule "app/src/playstore/assets/sources/peertube"] + path = app/src/playstore/assets/sources/peertube + url = ../plugins/peertube.git +[submodule "app/src/stable/assets/sources/kick"] + path = app/src/stable/assets/sources/kick + url = ../plugins/kick.git +[submodule "app/src/stable/assets/sources/odysee"] + path = app/src/stable/assets/sources/odysee + url = ../plugins/odysee.git +[submodule "app/src/stable/assets/sources/nebula"] + path = app/src/stable/assets/sources/nebula + url = ../plugins/nebula.git +[submodule "app/src/stable/assets/sources/patreon"] + path = app/src/stable/assets/sources/patreon + url = ../plugins/patreon.git +[submodule "app/src/stable/assets/sources/peertube"] + path = app/src/stable/assets/sources/peertube + url = ../plugins/peertube.git +[submodule "app/src/stable/assets/sources/rumble"] + path = app/src/stable/assets/sources/rumble + url = ../plugins/rumble.git +[submodule "app/src/stable/assets/sources/soundcloud"] + path = app/src/stable/assets/sources/soundcloud + url = ../plugins/soundcloud.git +[submodule "app/src/stable/assets/sources/twitch"] + path = app/src/stable/assets/sources/twitch + url = ../plugins/twitch.git +[submodule "app/src/stable/assets/sources/youtube"] + path = app/src/stable/assets/sources/youtube + url = ../plugins/youtube.git +[submodule "app/src/unstable/assets/sources/kick"] + path = app/src/unstable/assets/sources/kick + url = ../plugins/kick.git +[submodule "app/src/unstable/assets/sources/nebula"] + path = app/src/unstable/assets/sources/nebula + url = ../plugins/nebula.git +[submodule "app/src/unstable/assets/sources/odysee"] + path = app/src/unstable/assets/sources/odysee + url = ../plugins/odysee.git +[submodule "app/src/unstable/assets/sources/patreon"] + path = app/src/unstable/assets/sources/patreon + url = ../plugins/patreon.git +[submodule "app/src/unstable/assets/sources/peertube"] + path = app/src/unstable/assets/sources/peertube + url = ../plugins/peertube.git +[submodule "app/src/unstable/assets/sources/rumble"] + path = app/src/unstable/assets/sources/rumble + url = ../plugins/rumble.git +[submodule "app/src/unstable/assets/sources/soundcloud"] + path = app/src/unstable/assets/sources/soundcloud + url = ../plugins/soundcloud.git +[submodule "app/src/unstable/assets/sources/twitch"] + path = app/src/unstable/assets/sources/twitch + url = ../plugins/twitch.git +[submodule "app/src/unstable/assets/sources/youtube"] + path = app/src/unstable/assets/sources/youtube + url = ../plugins/youtube.git +[submodule "dep/futopay"] + path = dep/futopay + url = ../futopayclientlibraries.git diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md new file mode 100644 index 00000000..dd7891bc --- /dev/null +++ b/CONTRIBUTION.md @@ -0,0 +1,60 @@ +# Contribution Guidelines + +## Table of Contents + +1. [Introduction](#introduction) +2. [Contributing to Official Plugins](#contributing-to-official-plugins) +3. [Creating Your Own Plugins](#creating-your-own-plugins) +4. [Contributing to Core](#contributing-to-core) + +--- + +## Introduction + +Thank you for your interest in contributing! This document outlines how you can contribute to the official plugins and also encourages you to write your own plugins. Please read this guide carefully to understand how you can collaborate with us. + +--- + +## Contributing to Official Plugins + +### License + +The official plugins for this project are licensed under GPLv3. Any contributions you make will also fall under the GPLv3 license. + +### How to Contribute + +1. Fork the repository containing the plugin. +2. Clone your fork. +3. Make your changes. +4. Commit and push your changes. +5. Open a pull request. + +### Guidelines + +- Ensure your code adheres to the existing style. +- Include documentation and unit tests (where applicable). + +--- + +## Creating Your Own Plugins + +We encourage developers to write their own plugins. Please refer to the "Getting Started" documentation to learn how to create a plugin for the app. + +### Guidelines + +- Your plugin's license must be compatible with the core application's license. +- We encourage you to make your plugin open-source, although it's not mandatory. + +--- + +## Contributing to Core + +**We are currently not accepting contributions to the core.** + +The core is currently licensed under the FUTO Temporary License (FTL). The licensing and ownership of contributions to the core are complex topics that we are still working on. We'll update these guidelines when we have more clarity. + +--- + +Thank you for reading the contribution guidelines. Happy contributing! + + diff --git a/README.md b/README.md new file mode 100644 index 00000000..263689a2 --- /dev/null +++ b/README.md @@ -0,0 +1,205 @@ +# PlatformPlayer + +The FUTO media app endeavours creating infrastructure for creators to have their content hosted by someone else but at the same time having creators retain full ownership of their content. We want creators to feel like they are publishing to the world, and we want multiple indexers competing with each other to do a good job connecting consumers to creators and their content. + +One part of the solution is to create an application that allows users to search through all available media websites and giving creators the tools for direct monetization of their content by allowing users to directly donate to the content creator. + +FUTO is an organization dedicated to developing, both through in-house engineering and investment, +technologies that frustrate centralization and industry consolidation. + + + + + + + + + + +
VideoVideo (details)
+ +## What does the app do? + +The FUTO media app is a player that exposes multiple video websites as sources in the app. These sources can be easily configured and third-party sources can also manually be added. This is done through the sources UI. + + + + + + + + + + +
Sources (all enabled)Sources (one disabled)
+ +Additional sources can also be installed. These sources are JavaScript sources, created and maintained by the community. + + + + + + + + + + +
Install a new sourceConfigure a source
+ +Once the sources are configured, the combined results will be shown throughout the app. The core features of the app will be highlighted below. + +### Searching + +When a user enters a search term into the search bar, the query is posted to the underlying platforms and a list of results that are ranked by relevance is returned. The search functionality of the app allows users to search multiple sources at once, allowing users to discover a wider range of content that is relevant to their interests. + + + + + + + + + + +
Search (list)Search (preview)
+ +### Channels + +Channels allow users to view the creators content, read more about them or support them by donating, purchasing from their store or buying a membership. The FUTO media app only links to other stores and the app does not play an intermediate role in the actual purchase process. This way, creators can directly monetize their own content in the way they like. + +Creators are able to configure their profile using NeoPass. + + + + + + + + +
Channel
+ +### Feed + +Subscriptions are a way for users to keep up with the latest videos and content from their favorite creators. The creators you are subscribed to are shown in the creators tab. In the future we will add both creator search and suggested creators. + + + + + + + + +
Creators
+ +When you subscribe to a creator, you'll be able to find new videos uploaded by them in the subscriptions tab. + + + + + + + + + + +
Subscriptions (list)Subscriptions (preview)
+ +Additionally there is also the "Home" feed which is based purely on recommendations by the underlying platforms. Also here we hope to offer user-picked recommendation engines in the future. + +## Settings + +The app offers a lot of settings customizing how the app looks and feels. An example of this is the background behaviour, do you wish to have it use picture in picture, background play or shut off entirely. Another example configuration option is choosing between list views or video previews. + + + + + + + + +
Settings
+ +### Playlists + +Playlists allow you to make a collection of videos that you can create and customize to your liking. When you add videos to a playlist, they're grouped together in a single location, making it easy for you to find and watch all of the videos in the playlist in sequence. + + + + + + + + + + +
PlaylistsPlaylist
+ +Playlists can also be downloaded in their entirety. + +### Downloads + +Both individual videos and playlists can be downloaded for local, offline playback. You can watch downloaded videos any time, even if you do not have an active internet connection. + + + + + + + + +
Downloads
+ +### Casting + +The app can also cast to a big screen using any of the supported protocols (FastCast, ChromeCast, AirPlay). Not all casting protocols support all features. As a rule of thumb feature-wise FastCast > ChromeCast > AirPlay. + +For more information about casting please click [here](./docs/casting.md). + + + + + + + + +
Casting
+ +### Commenting and rating + +The app can also cast to comment and rate. For more information about this please click [here](./docs/polycentric.md). + +### Creator Linking + +The app can also cast to link channels together. For more information about this please click [here](./docs/linking.md). + +### Migration and recommendations + +Sources have the ability to login, allowing you to use features that require credentials like importing your playlists, importing your subscriptions or have personalized recommendations. Some platforms may require a membership to work at all. + +In the future we hope to offer users the choice of their desired recommendation engine and have multiple competing recommendation engines for different audiences. + +## Building + +1. Download a copy of the repository. +2. Open the project in Android Studio: Once the repository is cloned, you can open it in Android Studio by selecting "Open an Existing Project" from the welcome screen and navigating to the directory where you cloned the repository. +3. Build the project: With the project open in Android Studio, you can build it by selecting "Build > Make Project" from the main menu. This will compile the code and generate an APK file that you can install on your device or emulator. +4. Run the project: To run the project, select "Run > Run 'app'" from the main menu. This will launch the app on your device or emulator, allowing you to test it and make any necessary changes. + +## Contributing + +Please see [CONTRIBUTION.md](./CONTRIBUTION.md). + +## CI/CD + +Tests will always run and are required to pass before a merge request is allowed to be merged. The build/deploy CI/CD steps will only be triggered by a tag on the master branch. + +### Making a new build + +Create a tag on the master branch, incrementing the last version number by 1 (for example `25` to `26`). + +Click on the CI/CD tab, you should now see the tests and build are in progress. If the build succeeds the last step will become available. The last step is a manual action which can be triggered by clicking the run button on the action. This action will deploy the build to all users using the app through auto-update. + + +## Documentation + +The documentation can be found [here](https://gitlab.futo.org/videostreaming/documents/-/wikis/API-Overview). diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 00000000..40ce21c2 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,207 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' + id 'org.jetbrains.kotlin.plugin.serialization' version '1.6.10' + id 'org.ajoberstar.grgit' version '1.7.2' + id 'com.google.protobuf' + id 'kotlin-parcelize' +} + +ext { + gitVersionName = grgit.describe() + gitVersionCode = gitVersionName.isInteger() ? gitVersionName.toInteger() : 1 +} + +println("Version Name: $gitVersionName") +println("Version Code: $gitVersionCode") + +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('/opt/key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + +protobuf { + protoc { + artifact = 'com.google.protobuf:protoc:3.22.3' + } + generateProtoTasks { + all().each { task -> + task.builtins { + java { + option "lite" + } + } + } + } +} + +android { + namespace 'com.futo.platformplayer' + compileSdk 33 + flavorDimensions "buildType" + productFlavors { + stable { + dimension "buildType" + applicationId "com.futo.platformplayer" + buildConfigField "boolean", "IS_UNSTABLE_BUILD", "false" + buildConfigField "boolean", "IS_PLAYSTORE_BUILD", "false" + resValue "string", "app_name", "Grayjay" + resValue "string", "authority", "com.futo.platformplayer" + } + unstable { + dimension "buildType" + applicationId "com.futo.platformplayer.d" + buildConfigField "boolean", "IS_UNSTABLE_BUILD", "true" + buildConfigField "boolean", "IS_PLAYSTORE_BUILD", "false" + resValue "string", "app_name", "Grayjay Unstable" + resValue "string", "authority", "com.futo.platformplayer.d" + getIsDefault().set(true) + } + playstore { + dimension "buildType" + applicationId "com.futo.platformplayer.playstore" + buildConfigField "boolean", "IS_UNSTABLE_BUILD", "false" + buildConfigField "boolean", "IS_PLAYSTORE_BUILD", "true" + resValue "string", "app_name", "Grayjay" + resValue "string", "authority", "com.futo.platformplayer.playstore" + } + } + + android.applicationVariants.all { variant -> + if (variant.flavorName == "unstable") { + variant.preBuildProvider.configure { + doFirst { + println("UNSTABLE BUILD") + } + } + } + + if (variant.flavorName == "stable") { + variant.preBuildProvider.configure { + doFirst { + println("STABLE BUILD") + } + } + } + + if (variant.flavorName == "playstore") { + variant.preBuildProvider.configure { + doFirst { + println("PLAYSTORE BUILD") + } + } + } + } + + defaultConfig { + minSdk 29 + targetSdk 33 + versionCode gitVersionCode + versionName gitVersionName + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } + + buildTypes { + release { + signingConfig signingConfigs.release + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + + splits { + abi { + enable true + reset() + + include "x86", "x86_64", "arm64-v8a", "armeabi-v7a" + universalApk true + } + } +} + +dependencies { + + //Core + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'com.google.android.material:material:1.5.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + + //Images + annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1' + implementation 'com.github.bumptech.glide:glide:4.15.1' + + //Async + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.2" + + //HTTP + implementation "com.squareup.okhttp3:okhttp:4.10.0" + + //JSON + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1" //Used for structured json + implementation 'com.google.code.gson:gson:2.10.1' //Used for complex/anonymous cases like during development conversions (eg. V8RemoteObject) + + //JS + implementation("com.caoccao.javet:javet-android:2.2.1") + + //Exoplayer + implementation 'com.google.android.exoplayer:exoplayer-core:2.18.7' + implementation 'com.google.android.exoplayer:exoplayer-dash:2.18.7' + implementation 'com.google.android.exoplayer:exoplayer-ui:2.18.7' + implementation 'com.google.android.exoplayer:exoplayer-hls:2.18.7' + implementation 'com.google.android.exoplayer:exoplayer-rtsp:2.18.7' + implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.18.7' + implementation 'com.google.android.exoplayer:exoplayer-transformer:2.18.7' + implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3' + implementation 'androidx.navigation:navigation-ui-ktx:2.5.3' + + //Other + implementation 'org.jmdns:jmdns:3.5.1' + implementation 'org.jsoup:jsoup:1.15.3' + implementation 'com.google.android.flexbox:flexbox:3.0.0' + implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' + implementation 'com.arthenica:ffmpeg-kit-full:5.1' + implementation 'org.jetbrains.kotlin:kotlin-reflect:1.7.20' + implementation 'com.github.dhaval2404:imagepicker:2.1' + implementation 'com.google.zxing:core:3.4.1' + implementation 'com.journeyapps:zxing-android-embedded:4.2.0' + implementation 'com.caverock:androidsvg-aar:1.4' + + //Protobuf + implementation 'com.google.protobuf:protobuf-javalite:3.22.3' + + implementation 'com.polycentric.core:app:1.0' + implementation 'com.futo.futopay:app:1.0' + implementation 'androidx.work:work-runtime-ktx:2.8.1' + implementation 'androidx.concurrent:concurrent-futures-ktx:1.1.0' + + //Payment + implementation 'com.stripe:stripe-android:20.28.3' + + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.2' + testImplementation "org.jetbrains.kotlin:kotlin-test:1.8.20" + testImplementation "org.xmlunit:xmlunit-core:2.9.1" + testImplementation "org.mockito:mockito-core:5.4.0" + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/futo/platformplayer/EncryptionProviderTests.kt b/app/src/androidTest/java/com/futo/platformplayer/EncryptionProviderTests.kt new file mode 100644 index 00000000..f4605efd --- /dev/null +++ b/app/src/androidTest/java/com/futo/platformplayer/EncryptionProviderTests.kt @@ -0,0 +1,62 @@ +package com.futo.platformplayer + +import com.futo.platformplayer.encryption.EncryptionProvider +import junit.framework.TestCase.assertEquals +import org.junit.Test + +class EncryptionProviderTests { + @Test + fun testEncryptDecrypt() { + val encryptionProvider = EncryptionProvider.instance + val plaintext = "This is a test string." + + // Encrypt the plaintext + val ciphertext = encryptionProvider.encrypt(plaintext) + + // Decrypt the ciphertext + val decrypted = encryptionProvider.decrypt(ciphertext) + + // The decrypted string should be equal to the original plaintext + assertEquals(plaintext, decrypted) + } + + + @Test + fun testEncryptDecryptBytes() { + val encryptionProvider = EncryptionProvider.instance + val bytes = "This is a test string.".toByteArray(); + + // Encrypt the plaintext + val ciphertext = encryptionProvider.encrypt(bytes) + + // Decrypt the ciphertext + val decrypted = encryptionProvider.decrypt(ciphertext) + + // The decrypted string should be equal to the original plaintext + assertArrayEquals(bytes, decrypted); + } + + @Test + fun testEncryptDecryptBytesPassword() { + val encryptionProvider = EncryptionProvider.instance + val bytes = "This is a test string.".toByteArray(); + val password = "1234".padStart(32, '9'); + + // Encrypt the plaintext + val ciphertext = encryptionProvider.encrypt(bytes, password) + + // Decrypt the ciphertext + val decrypted = encryptionProvider.decrypt(ciphertext, password) + + // The decrypted string should be equal to the original plaintext + assertArrayEquals(bytes, decrypted); + + } + + private fun assertArrayEquals(a: ByteArray, b: ByteArray) { + assertEquals(a.size, b.size); + for(i in 0 until a.size) { + assertEquals(a[i], b[i]); + } + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/futo/platformplayer/RequireMigrationTests.kt b/app/src/androidTest/java/com/futo/platformplayer/RequireMigrationTests.kt new file mode 100644 index 00000000..16c6da2f --- /dev/null +++ b/app/src/androidTest/java/com/futo/platformplayer/RequireMigrationTests.kt @@ -0,0 +1,35 @@ +package com.futo.platformplayer + +import android.util.Log +import androidx.test.platform.app.InstrumentationRegistry +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.Serializer +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.Thumbnail +import com.futo.platformplayer.api.media.models.Thumbnails +import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo +import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideoDetails +import com.futo.platformplayer.serializers.FlexibleBooleanSerializer +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.stores.FragmentedStorage +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import org.junit.Assert +import org.junit.Test +import java.time.OffsetDateTime + +class RequireMigrationTests { + /* THESE TESTS SIMPLY EXIST TO WARN THE DEVELOPER THAT THEIR CHANGE WILL CAUSE A MIGRATION FOR ALL USERS */ + private val serializedSettingsString = "{\"home\":{},\"search\":{},\"subscriptions\":{\"subscriptionsFeedStyle\":0,\"subscriptionsBackgroundUpdateInterval\":3},\"playback\":{\"autoRotate\":0},\"downloads\":{},\"browsing\":{},\"casting\":{},\"logging\":{},\"autoUpdate\":{\"check\":1},\"announcementSettings\":{},\"backup\":{},\"payment\":{\"paymentStatus\": \"Paid\"},\"info\":{}}"; + + @Test + fun testSettingsDeserializing() { + val context = InstrumentationRegistry.getInstrumentation().targetContext; + StateApp.instance.setGlobalContext(context, CoroutineScope(Dispatchers.Main)); + + Assert.assertNotNull(Json { ignoreUnknownKeys = true; this.isLenient = true }.decodeFromString(serializedSettingsString)); + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/futo/platformplayer/SignatureTests.kt b/app/src/androidTest/java/com/futo/platformplayer/SignatureTests.kt new file mode 100644 index 00000000..69150715 --- /dev/null +++ b/app/src/androidTest/java/com/futo/platformplayer/SignatureTests.kt @@ -0,0 +1,68 @@ +package com.futo.platformplayer + +import android.util.Log +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import org.junit.Test + +import org.junit.Assert.* + +class SignatureTests { + @Test + fun roundtripTest() { + val keys = SignatureProvider.generateKeyPair(); + Log.i(TAG, "public key: ${keys.publicKey}\nprivate key: ${keys.privateKey}"); + + val signature = SignatureProvider.sign("test", keys.privateKey); + assertTrue(SignatureProvider.verify("test", signature, keys.publicKey)); + } + + @Test + fun decodeTest() { + assertTrue(SignatureProvider.verify( + "//this is just an empty script", + "eLdlDIcmpTQmfpCumB5NQwFa0ZDNU8hkRB12/Lg+CdTwPrfTIylGeN6jpTmJrEivyLjj" + + "5qHWZeNmrHP++9XFwfwzcaXNspKU9YrL3+Bsy2WNnXfQDeB2t4AkzWYAEfm8/kEcK0Ov8dzy0KW" + + "lJsxmW+Oj3mFNVP6PV5ZQY1Gju6W8Jw0sGCxnbuhswtRDPwBKnZQUhlZEXPvbrcblW1q5fCESnf" + + "oiJ2MHR5epgHfAuMsoY9EAHVXuyrLvmbWADeVwC5jvWLAkJKw68rQmARqV5BBWkpqFEBQcg50CR" + + "vTXtPr8IDjW7yiJ6x9nTG3nokTJn3fj2D3hBEHttEG+KhTMlQ==", + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAucY14D6cl0fK5fHOTUfKMz1iQmfJMg" + + "Q+c4MfqlArGCv7YDTvazeQL9dsrCqlYx+o+AlYzbohGXYsYsJO474+Ia5VEcpCnMm6YPRhV0H+8bke" + + "lgE2vMB2MB54zIxVRVEA1CBPBrWle8qlBMmqXI8ndjQjIYZNZD0CN0ckOgLO3OX8+P6f+zYHbRINCXi" + + "T1L7DstJ4FacqE7b2+aNKiMogUoaq7H3dXxJXj32HMFZevrs8ZFxTvbIP4KkazRrdfnZPdWKXk9pv" + + "P8EI21RNKKr2NtVNJyRPxI1uWlvYtGeSLcNUioHshNRQ4SxRSG8p1VTBmUpS0cJoZSCmO/0W9doyzwI" + + "DAQAB")); + } + + @Test + fun testSignature() { + val somePlugin = SourcePluginConfig( + "Script", + "A plugin that adds Script as a source", + "FUTO", + "https://futo.org", + "https://futo.org", + "./Script.js", + 1, + "./script.png", + "394dba51-fa0c-450c-8f17-6a00df362218", + "eLdlDIcmpTQmfpCumB5NQwFa0ZDNU8hkRB12/Lg+CdTwPrfTIylGeN6jpTmJrEivyLjj" + + "5qHWZeNmrHP++9XFwfwzcaXNspKU9YrL3+Bsy2WNnXfQDeB2t4AkzWYAEfm8/kEcK0Ov8dzy0KW" + + "lJsxmW+Oj3mFNVP6PV5ZQY1Gju6W8Jw0sGCxnbuhswtRDPwBKnZQUhlZEXPvbrcblW1q5fCESnf" + + "oiJ2MHR5epgHfAuMsoY9EAHVXuyrLvmbWADeVwC5jvWLAkJKw68rQmARqV5BBWkpqFEBQcg50CR" + + "vTXtPr8IDjW7yiJ6x9nTG3nokTJn3fj2D3hBEHttEG+KhTMlQ==", + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAucY14D6cl0fK5fHOTUfKM" + + "z1iQmfJMgQ+c4MfqlArGCv7YDTvazeQL9dsrCqlYx+o+AlYzbohGXYsYsJO474+Ia5VEcpC" + + "nMm6YPRhV0H+8bkelgE2vMB2MB54zIxVRVEA1CBPBrWle8qlBMmqXI8ndjQjIYZNZD0CN0c" + + "kOgLO3OX8+P6f+zYHbRINCXiT1L7DstJ4FacqE7b2+aNKiMogUoaq7H3dXxJXj32HMFZevrs8ZF" + + "xTvbIP4KkazRrdfnZPdWKXk9pvP8EI21RNKKr2NtVNJyRPxI1uWlvYtGeSLcNUioHshNRQ4SxRSG8p1VTBmUpS0cJoZSCmO/0W9doyzwIDAQAB" + ); + val script = "//this is just an empty script"; + + assert(somePlugin.validate(script), { "Invalid signature" }); + + } + + companion object { + private const val TAG = "SignatureTests"; + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/futo/platformplayer/StatePlatformTests.kt b/app/src/androidTest/java/com/futo/platformplayer/StatePlatformTests.kt new file mode 100644 index 00000000..a006581a --- /dev/null +++ b/app/src/androidTest/java/com/futo/platformplayer/StatePlatformTests.kt @@ -0,0 +1,79 @@ +package com.futo.platformplayer + +//import androidx.test.platform.app.InstrumentationRegistry +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StateSubscriptions +import kotlinx.coroutines.runBlocking +import org.junit.Test +import kotlin.system.measureTimeMillis + +class StatePlatformTests { + + /* + @Test + fun testPlatformStateGetVideo(){ + runBlocking { + StatePlatform.instance.updateAvailableClients(InstrumentationRegistry.getInstrumentation().targetContext); + //StatePlatform.instance.selectClients(YoutubeClient.ID, OdyseeClient.ID); + var youtubeStreamVideoTask = StatePlatform.instance.getContentDetails("https://www.youtube.com/watch?v=bDgolMkLREA"); + val odyseeStreamVideoTask = StatePlatform.instance.getContentDetails("lbry://ads-and-tracking-is-getting-worse-on#e2d1a7334869dfb531c80823064debbb2e75dac5"); + val odyseeStreamVideoTask2 = StatePlatform.instance.getContentDetails("lbry://ads-and-tracking-is-getting-worse-on#e2d1a7334869dfb531c80823064debbb2e75dac5"); + + //Assert batching + assert(odyseeStreamVideoTask == odyseeStreamVideoTask2); + + val youtubeStreamVideo = youtubeStreamVideoTask.await(); + val odyseeStreamVideo = odyseeStreamVideoTask.await(); + + assert(youtubeStreamVideo.id.value == "bDgolMkLREA") + assert(odyseeStreamVideo.id.value == "bMPjhiYTR1GCPKaSGQkFyiVv1juJaY4PaG") + } + }*/ + + //TODO: Re-enable once getChannel requests are batched for non-subscribed channels. + /* + @Test + fun testPlatformStateGetChannelVideos(){ + val expectedChannelUrl = "https://www.youtube.com/channel/UCL81YHgzH8tcrFfOJJwlSQw"; + val expectedVideoId = "up2TjMuan6o" + val expectedVideoName= "bag cat"; + + runBlocking { + var youtubeChannelVideosTask = PlatformState.instance.getChannel(expectedChannelUrl); + var youtubeChannelVideosTask2 = PlatformState.instance.getChannel(expectedChannelUrl); + + //Assert batching + assert(youtubeChannelVideosTask == youtubeChannelVideosTask2); + + val youtubeStreamVideo = youtubeChannelVideosTask.await(); + + val page1Results = youtubeStreamVideo.videos.getResults(); + assert(page1Results.size > 0); + assert(page1Results.any { it.id.value == expectedVideoId }); + } + } + + @Test + fun testPlatformStateSubscription(){ + runBlocking { + StatePlatform.instance.updateAvailableClients(InstrumentationRegistry.getInstrumentation().targetContext); + //StatePlatform.instance.selectClients(YoutubeClient.ID, OdyseeClient.ID); + } + val expectedChannelUrl = "https://www.youtube.com/channel/UCL81YHgzH8tcrFfOJJwlSQw"; + val expectedVideoId = "up2TjMuan6o" + val expectedVideoName= "bag cat"; + + val channel = runBlocking { StatePlatform.instance.getChannel(expectedChannelUrl).await() }; + + val timeExplicit = measureTimeMillis { + runBlocking { + val stateExplicit = StateSubscriptions(); + stateExplicit.addSubscription(channel); + val channel = stateExplicit.getSubscription(expectedChannelUrl); + + assert(channel != null); + } + }; + System.out.println("Explicit Subscription update $timeExplicit ms"); + }*/ +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..2abba88c --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/devportal/dependencies/FutoMainLogo.svg b/app/src/main/assets/devportal/dependencies/FutoMainLogo.svg new file mode 100644 index 00000000..f7685471 --- /dev/null +++ b/app/src/main/assets/devportal/dependencies/FutoMainLogo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/assets/devportal/dev_bridge.js b/app/src/main/assets/devportal/dev_bridge.js new file mode 100644 index 00000000..57f0f276 --- /dev/null +++ b/app/src/main/assets/devportal/dev_bridge.js @@ -0,0 +1,337 @@ + +//These calls are purposely synchronized to emulate behavior within V8 +function syncGET(url, headers) { + if(!headers) headers = {}; + const req = new XMLHttpRequest(); + req.open("GET", url, false); + for (const [key, value] of Object.entries(headers)) + req.setRequestHeader(key, value); + req.send(null); + + if(req.status >= 200 && req.status < 300) + return req.response; + else + throw "Request [" + req.status + "]\n" + req.response; +} +function syncPOST(url, headers, body) { + if(!headers) headers = {}; + const req = new XMLHttpRequest(); + req.open("POST", url, false); + for (const [key, value] of Object.entries(headers)) + req.setRequestHeader(key, value); + req.send(body); + + if(req.status >= 200 && req.status < 300) + return req.response; + else + throw "Request [" + req.status + "]\n" + req.response; +} + + +class RemoteObject { + constructor(remoteObj) { + Object.assign(this, remoteObj); + + if(this.__methods) { + const me = this; + for(let i = 0; i < this.__methods.length; i++) { + const methodName = this.__methods[i]; + + this[methodName] = function() { + try{ + return remoteCall(me.__id, methodName, Array.from(arguments)); + } + catch(ex) { + if(ex.indexOf("[400]") > 0 && ex.indexOf("does not exist") > 0 && ex.indexOf(me.__id) > 0) { + deletePackage(me.__id); + } + else throw ex; + } + }; + } + } + if(this.__props) { + const me = this; + for(let i = 0; i < this.__props.length; i++) { + const propName = this.__props[i]; + + Object.defineProperty(this, propName, { + get() { + try{ + return remoteProp(me.__id, propName); + } + catch(ex) { + if(ex.indexOf("[400]") > 0 && ex.indexOf("does not exist") > 0 && ex.indexOf(me.__id) > 0) { + deletePackage(me.__id); + } + else throw ex; + } + } + }); + } + } + } +} +const excludedCallsFromLogs = ["isLoggedIn"]; +function remoteCall(objID, methodName, args) { + for(let i = 0; i < args.length; i++) { + let arg = args[i]; + if(typeof(arg) == "object") { + switch(arg.constructor.name) { + case "Uint8Array": + args[i] = [...arg] + break; + } + } + } + + + if(excludedCallsFromLogs.indexOf(methodName) < 0) + console.log("Remote Call on [" + objID + "]." + methodName + "(...)", args); + const result = pluginRemoteCall(objID, methodName, args); + return wrapRemoteObject(result); +} +function remoteProp(objID, propName) { + console.log("Remote Prop on [" + objID + "]." + propName); + const result = pluginRemoteProp(objID, propName); + return wrapRemoteObject(result); +} +function wrapRemoteObject(result) { + if(Array.isArray(result)) { + if(result.length == 0) + return []; + const firstItem = result[0]; + if(typeof firstItem === "object") + return result.map(x=>new RemoteObject(x)); + else + return result; + } + else if(typeof result === "object") + return new RemoteObject(result); + return result; +} + +//These override implementations by packages if enabled +var packageOverrides = { + domParser() { + return { + parseFromString(str) { + return new DOMParser().parseFromString(str, "text/html"); + } + } + } +}; +var packageOverridesEnabled = {}; +for(override in packageOverrides) + packageOverridesEnabled[override] = false; + + +var _loadedPackages = { + +}; +function clearPackages() { + _loadedPackages = {}; +} +function deletePackage(id) { + for(let key in _loadedPackages) { + if(_loadedPackages[key]?.__id == id) + _loadedPackages[key] = undefined; + } +} +function applyPackages(packages) { + _loadedPackages = {}; + for(let i = 0; i < packages.length; i++) { + const package = packages[i]; + delete window[package]; + Object.defineProperty(window, package, { + configurable: true, + get() { + if(!_loadedPackages[package]) { + if(packageOverridesEnabled[package]) { + _loadedPackages[package] = packageOverrides[package](); + console.log("LOADED EMULATED PACKAGE [" + package + "]", _loadedPackages[package]); + } + else { + _loadedPackages[package] = new RemoteObject(pluginGetPackage(package)); + console.log("LOADED REMOTE PACKAGE [" + package + "]", _loadedPackages[package]); + applyAdditionalOverrides(package, _loadedPackages[package]); + } + } + return _loadedPackages[package]; + } + }); + } +} +function applyAdditionalOverrides(packageName, package) { + switch(packageName) { + case "http": + console.log("Http override for socket"); + package.socket = (url, headers, auth) => { + console.warn("This uses an emulated socket connection directly from browser. Remoting websocket is not yet supported."); + if(auth) + throw "Socket override does not support auth yet (should work in-app)"; + + const obj = {}; + + obj.connect = function(listeners) { + obj.socket = new WebSocket(url); + obj.socket.addEventListener("open", (event) => { + obj.isOpen = true; + listeners.open && listeners.open(); + }); + obj.socket.addEventListener("message", (event) => listeners.message && listeners.message(event.data)); + obj.socket.addEventListener("error", (event) => listeners.failure && listeners.failure()); + obj.socket.addEventListener("closed", (event) => { + obj.isOpen = false; + listeners.closed && listeners.closed(event.code, event.reason); + }); + }; + obj.send = function(msg) { + if(obj.socket != null) + obj.socket.send(msg); + } + obj.close = function(code, reason) { + if(obj.socket != null) + obj.socket.close(code, reason); + } + return obj; + }; + break; + + } +} + +function reloadPackages() { + const packages = Object.keys(_loadedPackages); + applyPackages(packages); +} + +function httpGETBypass(url, headers, ct) { + return JSON.parse(syncPOST("/get?CT=" + ct, {}, JSON.stringify({ + url: url, + headers: headers + }))); +} +function pluginUpdateTestPlugin(config) { + return JSON.parse(syncPOST("/plugin/updateTestPlugin", {}, JSON.stringify(config))); +} +function pluginLoginTestPlugin() { + return syncGET("/plugin/loginTestPlugin", {}); +} +function pluginLogoutTestPlugin() { + return syncGET("/plugin/logoutTestPlugin", {}); +} +function pluginGetPackage(packageName) { + return JSON.parse(syncGET("/plugin/packageGet?variable=" + packageName, {})); +} +function pluginRemoteProp(objID, propName) { + return JSON.parse(syncGET("/plugin/remoteProp?id=" + objID + "&prop=" + propName, {})); +} +function pluginRemoteCall(objID, methodName, args) { + return JSON.parse(syncPOST("/plugin/remoteCall?id=" + objID + "&method=" + methodName, {}, JSON.stringify(args))); +} + +function pluginIsLoggedIn(cb, err) { + fetch("/plugin/isLoggedIn", { + timeout: 1000 + }) + .then(x => x.json()) + .then(x => cb(x)) + .catch(y => err && err(y)); +} + +function pluginGetWarnings(config) { + return JSON.parse(syncPOST("/plugin/getWarnings", {}, JSON.stringify(config))); +} + +function uploadDevPlugin(config) { + return JSON.parse(syncPOST("/plugin/loadDevPlugin", {}, JSON.stringify(config))); +} +function getDevLogs(lastIndex, cb) { + if(!lastIndex) + lastIndex = 0; + fetch("/plugin/getDevLogs?index=" + lastIndex, { + timeout: 1000 + }) + .then(x=>x.json()) + .then(y=> cb && cb(y)); +} +function sendFakeDevLog(devId, msg) { + return syncGET("/plugin/fakeDevLog?devId=" + devId + "&msg=" + msg, {}); +} + +var __DEV_SETTINGS = {}; +function setDevSettings(obj) { + __DEV_SETTINGS = obj; +} + +var liveChatIntervalId = null; +function testLiveChat(url, interval, verbose) { + if(!interval) + interval = 4000; + if(liveChatIntervalId) + clearInterval(liveChatIntervalId); + + let live = source.getLiveEvents(url); + liveChatIntervalId = setInterval(()=>{ + if(!live.hasMorePagers()) { + clearInterval(liveChatIntervalId); + console.log("END OF CHAT"); + } + live.nextPage(); + for(let event of live.results) { + if(verbose) { + if(event.type == 1) + console.log("Live Chat: [" + event.name + "]:" + event.message, event); + else if(event.type == 5) + console.log("Live Chat: DONATION (" + event.amount + ") [" + event.name + "]: " + event.message, event); + else if(event.type == 6) + console.log("Live Chat: MEMBER (" + event.amount + ") [" + event.name + "]: " + event.message, event); + else console.log("Live Chat: Ev", event); + } + else { + if(event.type == 1) + console.log("Live Chat: [" + event.name + "]:" + event.message); + else if(event.type == 5) + console.log("Live Chat: DONATION (" + event.amount + ") [" + event.name + "]: " + event.message); + else if(event.type == 6) + console.log("Live Chat: MEMBER (" + event.amount + ") [" + event.name + "]: " + event.message); + else console.log("Live Chat: Ev", event); + } + } + }, interval); +} + +function testPlaybackTracker(url, seconds, iterations, pauseAfter) { + let lastTime = (new Date()).getTime(); + const tracker = source.getPlaybackTracker(url); + if(!tracker) { + console.warn("No tracker available (null)"); + return; + } + + if(tracker.onInit) + tracker.onInit(seconds); + + let iteration = undefined; + iteration = function(itt) { + const diff = (new Date()).getTime() - lastTime; + const secCurrent = seconds + (diff / 1000); + + tracker.onProgress(secCurrent, true); + + if(itt > 0) + setTimeout(()=>{ + iteration(itt - 1); + }, tracker.nextRequest); + else + setTimeout(()=> { + const diff = (new Date()).getTime() - lastTime; + const secCurrent = seconds + (diff / 1000); + tracker.onProgress(secCurrent, false); + }, 850); + } + setTimeout(()=> { + iteration(iterations - 1); + }, tracker.nextRequest); +} \ No newline at end of file diff --git a/app/src/main/assets/devportal/dev_test.html b/app/src/main/assets/devportal/dev_test.html new file mode 100644 index 00000000..7105ad65 --- /dev/null +++ b/app/src/main/assets/devportal/dev_test.html @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/devportal/index.html b/app/src/main/assets/devportal/index.html new file mode 100644 index 00000000..17bce7d5 --- /dev/null +++ b/app/src/main/assets/devportal/index.html @@ -0,0 +1,897 @@ + + + + + + + + + + + + + + +
+ + +
+
+ +
+ + + + +
+ + Login + + + Logout + +
+ +
+
+ {{Plugin.currentPlugin.name}} +
+
+ Last updated: {{Plugin.lastLoadTime}} +
+
+ + mdi-refresh + +
+
+
+
+
+ + +
+ +
+ +
+ Loading via script tag might give issues reloading script, as it makes the script part of DOM, but does allow debugging via dev console. +
+
+
+
+ + + Load Plugin + +
+ + + Package Overrides + + +
+
+ Enabling a package override replaces the package with a browser implementation. + This generally improves speed, at the cost of test accuracy. +
+
+ +
+
+
+
+
+ + +
+

Past Plugins

+
+ {{pastPluginUrl}} +
+
+
+ + + +
+

Errors in Plugin

+ {{Plugin.currentPluginError}} +
+
+
+ + +
+

Your plugin is loaded

+ You can now use testing methods available on the webapp.

+ The information and warnings the user will see when installing the app can be viewed below. +
+
+
+ + +
+
+ +
+ Last updated: {{Plugin.lastLoadTime}} +
+
+

{{Plugin.currentPlugin.name}}

+
+ By + {{Plugin.currentPlugin.author}} + + + {{Plugin.currentPlugin.author}} + + +
+
+
+
+ {{Plugin.currentPlugin.description}} +
+
+
+
+ Version +
+
+ {{Plugin.currentPlugin.version}} +
+
+ + +
+
+
+ + + + Ref.js + + + Plugin.d.ts + + Reload + +
+
+

Warnings

+
+ These are the warnings a user will see when they attempt to install this plugin +
+ + +
+
+ security +
+
+
+ {{warning.first}} +
+
+ {{warning.second}} +
+
+
+
+
+
+
+
+ + + No Plugin Loaded + + + +
+ Load a plugin before doing testing. +
+
+
+
+ + + +
+ (Optional) + {{req.title}} +
+ +
+ {{req.description}} +
+
+ {{req.code}} +
+
+
+
+ {{parameter.name}} +
+
+ {{parameter.description}} +
+ +
+
+
+ + + + Test + + +
+
+
+

Results

+
+ Copy +
+
+ No test done yet +
+
{{Testing.lastResult}}
+
{{Testing.lastResultError}}
+
+
+ +
+ + + No Plugin Loaded + + + +
+ Load a plugin before doing integration testing. +
+
+
+ + +
+

Errors in Plugin

+ Its best to fix errors before doing any integration testing +
+
+
+ + + Integration Testing + + + +
+
+ Integration testing allows you to upload your loaded plugin onto your phone, and get logs below to find any exceptions in actual usage. +
+
+ Last Injected: {{Integration.lastInjectTime}}
+ Click Inject Plugin again to update to last version. +
+
+ Plugin is not yet injected. Click "Inject Plugin" to load the plugin on your phone. +
+
+
+ + + Inject Plugin + +
+ + + Device Logs + + + +
+
+
+ [{{line.type}}] +
+
{{line.log}}
+
+
+
+ + + Clear + +
+
+ +
+ + + Settings + + + +
+
+ +
+
+ > +
+
+
+ + + Save + +
+
+ +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/devportal/plugin.d.ts b/app/src/main/assets/devportal/plugin.d.ts new file mode 100644 index 00000000..75327d0b --- /dev/null +++ b/app/src/main/assets/devportal/plugin.d.ts @@ -0,0 +1,364 @@ + +declare class ScriptException extends Error { + constructor(type: string, msg: string); +} +declare class TimeoutException extends ScriptException { + constructor(msg: string); +} +declare class UnavailableException extends ScriptException { + constructor(msg: string); +} +declare class ScriptImplementationException extends ScriptException { + constructor(msg: string); +} + +declare class Thumbnails { + constructor(thumbnails: Thumbnail[]) +} +declare class Thumbnail { + constructor(url, quality) { + this.url = url ?? ""; //string + this.quality = quality ?? 0; //integer + } +} + +declare class PlatformID { + constructor(platform: string, id: string, pluginId: string, claimType: int = 0, claimFieldType: integer = -1); +} + +declare class ResultCapabilities { + constructor(types: string[], sorts: string[], filters: FilterGroup[]) +} +declare class FilterGroup { + constructor(name: string, filters: string[], isMultiSelect: boolean, id: string); +} +declare class FilterCapability { + constructor(name: string, value: string, id: string); +} + + +declare class PlatformAuthorLink { + constructor(id: PlatformID, name: string, url: string, thumbnail: string, subscribers: integer?); +} + +declare interface PlatformContentDef { + id: PlatformID, + name: string, + author: PlatformAuthorLink, + datetime: integer, + url: string +} +declare interface PlatformNestedMediaContentDef extends PlatformContentDef { + contentUrl: string, + contentName: string?, + contentDescription: string?, + contentProvider: string?, + contentThumbnails: Thumbnails +} +declare class PlatformNestedMediaContent { + constructor(obj: PlatformNestedMediaContentDef); +} + +declare interface PlatformVideoDef extends PlatformContentDef { + thumbnails: Thumbnails, + author: PlatformAuthorLink, + + duration: int, + viewCount: long, + isLive: boolean +} +declare interface PlatformContent {} + +declare class PlatformVideo implements PlatformContent { + constructor(obj: PlatformVideoDef); +} + + +declare interface PlatformVideoDetailsDef extends PlatformVideoDef { + description: string, + video: VideoSourceDescriptor, + live: SubtitleSource[], + rating: IRating +} +declare class PlatformVideoDetails extends PlatformVideo { + constructor(obj: PlatformVideoDetailsDef); +} + +declare class PlatformPostDef extends PlatformContentDef { + thumbnails: string[], + images: string[], + description: string +} +declare class PlatformPost extends PlatformContent { + constructor(obj: PlatformPostDef) +} + +declare class PlatformPostDetailsDef extends PlatformPostDef { + rating: IRating, + textType: int, + content: String +} +declare class PlatformPostDetails extends PlatformPost { + constructor(obj: PlatformPostDetailsDef); +} + + +//Sources +declare interface IVideoSourceDescriptor {} + +declare interface MuxVideoSourceDescriptorDef { + isUnMuxed: boolean, + videoSources: VideoSource[] +} +declare class MuxVideoSourceDescriptor implements IVideoSourceDescriptor { + constructor(obj: VideoSourceDescriptorDef); +} + +declare interface UnMuxVideoSourceDescriptorDef { + isUnMuxed: boolean, + videoSources: VideoSource[] +} +class UnMuxVideoSourceDescriptor implements IVideoSourceDescriptor { + constructor(videoSourcesOrObj: VideoSource[], audioSources: AudioSource[]); + constructor(videoSourcesOrObj: UnMuxVideoSourceDescriptorDef); +} + +declare interface IVideoSource { + +} +declare interface IAudioSource { + +} +interface VideoUrlSourceDef implements IVideoSource { + width: integer, + height: integer, + container: string, + codec: string, + name: string, + bitrate: integer, + duration: integer, + url: string +} +class VideoUrlSource { + constructor(obj: VideoUrlSourceDef); + + getRequestModifier(): RequestModifier?; +} +interface VideoUrlRangeSourceDef extends VideoUrlSource { + itagId: integer, + initStart: integer, + initEnd: integer, + indexStart: integer, + indexEnd: integer, +} +class VideoUrlRangeSource extends VideoUrlSource { + constructor(obj: YTVideoSourceDef); +} +interface AudioUrlSourceDef { + name: string, + bitrate: integer, + container: string, + codecs: string, + duration: integer, + url: string, + language: string +} +class AudioUrlSource implements IAudioSource { + constructor(obj: AudioUrlSourceDef); + + getRequestModifier(): RequestModifier?; +} +interface IRequest { + url: string, + headers: Map +} +interface IRequestModifierDef { + allowByteSkip: boolean +} +class RequestModifier { + constructor(obj: IRequestModifierDef) { } + + modifyRequest(url: string, headers: Map): IRequest; +} +interface AudioUrlRangeSourceDef extends AudioUrlSource { + itagId: integer, + initStart: integer, + initEnd: integer, + indexStart: integer, + indexEnd: integer, + audioChannels: integer +} +class AudioUrlRangeSource extends AudioUrlSource { + constructor(obj: AudioUrlRangeSourceDef); +} +interface HLSSourceDef { + name: string, + duration: integer, + url: string +} +class HLSSource implements IVideoSource { + constructor(obj: HLSSourceDef); +} +interface DashSourceDef { + name: string, + duration: integer, + url: string +} +class DashSource implements IVideoSource { + constructor(obj: DashSourceDef) +} + +//Channel +interface PlatformChannelDef { + id: PlatformID, + name: string, + thumbnail: string, + banner: string, + subscribers: integer, + description: string, + url: string, + links: Map? +} +class PlatformChannel { + constructor(obj: PlatformChannelDef); +} + +//Ratings +interface IRating { + type: integer +} +declare class RatingLikes implements IRating { + constructor(likes: integer); +} +declare class RatingLikesDislikes implements IRating { + constructor(likes: integer, dislikes: integer); +} +declare class RatingScaler implements IRating { + constructor(value: double); +} + +declare interface CommentDef { + contextUrl: string, + author: PlatformAuthorLink, + message: string, + rating: IRating, + date: long, + replyCount: int, + context: any +} +declare class PlatformComment { + constructor(obj: CommentDef); +} + + + +declare class LiveEventPager { + nextRequest = 4000; + + constructor(results: LiveEvent[], hasMore: boolean, context: any); + + hasMorePagers(): boolean + nextPage(): LiveEventPager; //Could be self +} + +class LiveEvent { + type: String +} +declare class LiveEventComment extends LiveEvent { + constructor(name: string, message: string, thumbnail: string?, colorName: string?, badges: string[]); +} +declare class LiveEventEmojis extends LiveEvent { + constructor(name: Map); +} +declare class LiveEventDonation extends LiveEvent { + constructor(amount: integer, name: string, message: string, thumbnail: string?, expire: Int, colorDonation: string?); +} +declare class LiveEventViewCount extends LiveEvent { + constructor(viewCount: integer); +} +declare class LiveEventRaid extends LiveEvent { + constructor(targetUrl: string, targetName: string, targetThumbnail: string); +} + + + +//Pagers +declare class ContentPager { + constructor(results: PlatformContent[], hasMore: boolean); + + hasMorePagers(): boolean + nextPage(): VideoPager; //Could be self +} +declare class VideoPager { + constructor(results: PlatformVideo[], hasMore: boolean); + + hasMorePagers(): boolean + nextPage(): VideoPager; //Could be self +} +declare class ChannelPager { + constructor(results: PlatformChannel[], hasMore: boolean); + + hasMorePagers(): boolean; + nextPage(): ChannelPager; //Could be self +} +declare class CommentPager { + constructor(results: PlatformComment[], hasMore: boolean); + + hasMorePagers(): boolean + nextPage(): CommentPager; //Could be self +} + +interface Map { + [Key: string]: T; +} + +//To override by plugin + +interface Source { + getHome(): VideoPager; + + enable(config: SourceConfig, settings: Any, savedState: string?); + disable(); + + saveState(): string; + + searchSuggestions(query: string): string[]; + search(query: string, type: string, order: string, filters): ContentPager; + getSearchCapabilities(): ResultCapabilities + + //Optional + searchChannelContents(channelUrl: string, query: string, type: string, order: string, filters): ContentPager; + //Optional + getSearchChannelContentsCapabilities(): ResultCapabilities; + + //Optional + getChannelUrlByClaim(claimType: int, values: Map) + + isChannelUrl(url: string): boolean; + getChannel(url: string): PlatformChannel; + + getChannelContents(url: string, type: string, order: string, filters): ContentPager; + getChannelCapabilities(): ResultCapabilities; + + isContentDetailsUrl(url: string): boolean; + getContentDetails(url: string): PlatformVideoDetails; + + getLiveEvents(url: string): LiveEventPager; + + //Optional + getComments(url: string): CommentPager; + //Optional + getSubComments(comment: PlatformComment): CommentPager; + + //Optional + getUserSubscriptions(): string[]; + //Optional + getUserPlaylists(): string[]; + + //Optional + isPlaylistUrl(url: string): boolean; + //Optional + getPlaylist(url): string[]; +} + +const source: Source; diff --git a/app/src/main/assets/scripts/polyfil.js b/app/src/main/assets/scripts/polyfil.js new file mode 100644 index 00000000..5536c29b --- /dev/null +++ b/app/src/main/assets/scripts/polyfil.js @@ -0,0 +1,177 @@ + +class URL { + constructor(url, base) { + let baseParts; + try { + baseParts = URL.parse(base); + } + catch (e) { + throw new Error('Invalid base URL'); + } + let urlParts = URL.parse(url); + if (urlParts.protocol) { + this._parts = { ...urlParts }; + } + else { + this._parts = { + protocol: baseParts.protocol, + username: baseParts.username, + password: baseParts.password, + hostname: baseParts.hostname, + port: baseParts.port, + path: urlParts.path || baseParts.path, + query: urlParts.query || baseParts.query, + hash: urlParts.hash, + }; + } + } + static init() { + this.URLRegExp = new RegExp('^' + this.patterns.protocol + '?' + this.patterns.authority + '?' + this.patterns.path + this.patterns.query + '?' + this.patterns.hash + '?'); + this.AuthorityRegExp = new RegExp('^' + this.patterns.authentication + '?' + this.patterns.hostname + this.patterns.port + '?$'); + } + static parse(url) { + const urlMatch = this.URLRegExp.exec(url); + if (urlMatch !== null) { + const authorityMatch = urlMatch[2] ? this.AuthorityRegExp.exec(urlMatch[2]) : [null, null, null, null, null]; + if (authorityMatch !== null) { + return { + protocol: urlMatch[1] || '', + username: authorityMatch[1] || '', + password: authorityMatch[2] || '', + hostname: authorityMatch[3] || '', + port: authorityMatch[4] || '', + path: urlMatch[3] || '', + query: urlMatch[4] || '', + hash: urlMatch[5] || '', + }; + } + } + throw new Error('Invalid URL'); + } + get hash() { + return this._parts.hash; + } + set hash(value) { + value = value.toString(); + if (value.length === 0) { + this._parts.hash = ''; + } + else { + if (value.charAt(0) !== '#') + value = '#' + value; + this._parts.hash = encodeURIComponent(value); + } + } + get host() { + return this.hostname + (this.port ? (':' + this.port) : ''); + } + set host(value) { + value = value.toString(); + const url = new URL('http://' + value); + this._parts.hostname = url.hostname; + this._parts.port = url.port; + } + get hostname() { + return this._parts.hostname; + } + set hostname(value) { + value = value.toString(); + this._parts.hostname = encodeURIComponent(value); + } + get href() { + const authentication = (this.username || this.password) ? (this.username + (this.password ? (':' + this.password) : '') + '@') : ''; + return this.protocol + '//' + authentication + this.host + this.pathname + this.search + this.hash; + } + set href(value) { + value = value.toString(); + const url = new URL(value); + this._parts = { ...url._parts }; + } + get origin() { + return this.protocol + '//' + this.host; + } + get password() { + return this._parts.password; + } + set password(value) { + value = value.toString(); + this._parts.password = encodeURIComponent(value); + } + get pathname() { + return this._parts.path ? this._parts.path : '/'; + } + set pathname(value) { + let chunks = value.toString().split('/').map(encodePathSegment); + if (chunks[0]) { + // ensure joined string starts with slash. + chunks.unshift(''); + } + this._parts.path = chunks.join('/'); + } + get port() { + return this._parts.port; + } + set port(value) { + let port = parseInt(value); + if (isNaN(port)) { + this._parts.port = '0'; + } + else { + this._parts.port = Math.max(0, port % (2 ** 16)).toString(); + } + } + get protocol() { + return this._parts.protocol + ':'; + } + set protocol(value) { + value = value.toString(); + if (value.length !== 0) { + if (value.charAt(value.length - 1) === ':') { + value = value.slice(0, -1); + } + this._parts.protocol = encodeURIComponent(value); + } + } + get search() { + return this._parts.query; + } + set search(value) { + value = value.toString(); + if (value.charAt(0) !== '?') + value = '?' + value; + this._parts.query = value; + } + get username() { + return this._parts.username; + } + set username(value) { + value = value.toString(); + this._parts.username = encodeURIComponent(value); + } + get searchParams() { + const searchParams = new URLSearchParams(this.search); + ['append', 'delete', 'set'].forEach((methodName) => { + const method = searchParams[methodName]; + searchParams[methodName] = (...args) => { + method.apply(searchParams, args); + this.search = searchParams.toString(); + }; + }); + return searchParams; + } + toString() { + return this.href; + } +} + +URL.patterns = { + protocol: '(?:([^:/?#]+):)', + authority: '(?://([^/?#]*))', + path: '([^?#]*)', + query: '(\\?[^#]*)', + hash: '(#.*)', + authentication: '(?:([^:]*)(?::([^@]*))?@)', + hostname: '([^:]+)', + port: '(?::(\\d+))', +}; +URL.init(); \ No newline at end of file diff --git a/app/src/main/assets/scripts/source.js b/app/src/main/assets/scripts/source.js new file mode 100644 index 00000000..901da475 --- /dev/null +++ b/app/src/main/assets/scripts/source.js @@ -0,0 +1,691 @@ +var IS_TESTING = false; + +let Type = { + Source: { + Dash: "DASH", + HLS: "HLS", + STATIC: "Static" + }, + Feed: { + Videos: "VIDEOS", + Streams: "STREAMS", + Mixed: "MIXED", + Live: "LIVE" + }, + Order: { + Chronological: "CHRONOLOGICAL" + }, + Date: { + LastHour: "LAST_HOUR", + Today: "TODAY", + LastWeek: "LAST_WEEK", + LastMonth: "LAST_MONTH", + LastYear: "LAST_YEAR" + }, + Duration: { + Short: "SHORT", + Medium: "MEDIUM", + Long: "LONG" + }, + Text: { + RAW: 0, + HTML: 1, + MARKUP: 2 + } +}; + +let Language = { + UNKNOWN: "Unknown", + ARABIC: "Arabic", + SPANISH: "Spanish", + FRENCH: "French", + HINDI: "Hindi", + INDONESIAN: "Indonesian", + KOREAN: "Korean", + PORTBRAZIL: "Portuguese Brazilian", + RUSSIAN: "Russian", + THAI: "Thai", + TURKISH: "Turkish", + VIETNAMESE: "Vietnamese", + ENGLISH: "English" +} + +class ScriptException extends Error { + constructor(type, msg) { + if(arguments.length == 1) { + super(arguments[0]); + this.plugin_type = "ScriptException"; + this.message = arguments[0]; + } + else { + super(msg); + this.plugin_type = type ?? ""; //string + this.msg = msg ?? ""; //string + } + } +} +class UnavailableException extends ScriptException { + constructor(msg) { + super("UnavailableException", msg); + } +} +class AgeException extends ScriptException { + constructor(msg) { + super("AgeException", msg); + } +} +class TimeoutException extends ScriptException { + constructor(msg) { + super(msg); + this.plugin_type = "ScriptTimeoutException"; + } +} +class ScriptImplementationException extends ScriptException { + constructor(msg) { + super(msg); + this.plugin_type = "ScriptImplementationException"; + } +} + +class Thumbnails { + constructor(thumbnails) { + this.sources = thumbnails ?? []; // Thumbnail[] + } +} +class Thumbnail { + constructor(url, quality) { + this.url = url ?? ""; //string + this.quality = quality ?? 0; //integer + } +} + +class PlatformID { + constructor(platform, id, pluginId, claimType, claimFieldType) { + this.platform = platform ?? ""; //string + this.pluginId = pluginId; //string + this.value = id; //string + this.claimType = claimType ?? 0; //int + this.claimFieldType = claimFieldType ?? -1; //int + } +} + +class ResultCapabilities { + constructor(types, sorts, filters) { + this.types = types ?? []; + this.sorts = sorts ?? []; + this.filters = filters ?? []; + } +} +class FilterGroup { + constructor(name, filters, isMultiSelect, id) { + if(!name) throw new ScriptException("No name for filter group"); + if(!filters) throw new ScriptException("No filter provided"); + + this.name = name + this.filters = filters + this.isMultiSelect = isMultiSelect; + this.id = id; + } +} +class FilterCapability { + constructor(name, value, id) { + if(!name) throw new ScriptException("No name for filter"); + if(!value) throw new ScriptException("No filter value"); + + this.name = name; + this.value = value; + this.id = id; + } +} + + +class PlatformAuthorLink { + constructor(id, name, url, thumbnail, subscribers) { + this.id = id ?? PlatformID(); //PlatformID + this.name = name ?? ""; //string + this.url = url ?? ""; //string + this.thumbnail = thumbnail; //string + if(subscribers) + this.subscribers = subscribers; + } +} +class PlatformContent { + constructor(obj, type) { + this.contentType = type; + obj = obj ?? {}; + this.id = obj.id ?? PlatformID(); //PlatformID + this.name = obj.name ?? ""; //string + this.thumbnails = obj.thumbnails; //Thumbnail[] + this.author = obj.author; //PlatformAuthorLink + this.datetime = obj.datetime ?? obj.uploadDate ?? 0; //OffsetDateTime (Long) + this.url = obj.url ?? ""; //String + } +} +class PlatformContentDetails { + constructor(type) { + this.contentType = type; + } +} +class PlatformNestedMediaContent extends PlatformContent { + constructor(obj) { + super(obj, 11); + obj = obj ?? {}; + this.contentUrl = obj.contentUrl ?? ""; + this.contentName = obj.contentName; + this.contentDescription = obj.contentDescription; + this.contentProvider = obj.contentProvider; + this.contentThumbnails = obj.contentThumbnails ?? new Thumbnails(); + } +} +class PlatformVideo extends PlatformContent { + constructor(obj) { + super(obj, 1); + obj = obj ?? {}; + this.plugin_type = "PlatformVideo"; + this.shareUrl = obj.shareUrl; + + this.duration = obj.duration ?? -1; //Long + this.viewCount = obj.viewCount ?? -1; //Long + + this.isLive = obj.isLive ?? false; //Boolean + } +} +class PlatformVideoDetails extends PlatformVideo { + constructor(obj) { + super(obj); + obj = obj ?? {}; + this.plugin_type = "PlatformVideoDetails"; + + this.description = obj.description ?? "";//String + this.video = obj.video ?? {}; //VideoSourceDescriptor + this.dash = obj.dash ?? null; //DashSource + this.hls = obj.hls ?? null; //HLSSource + this.live = obj.live ?? null; //VideoSource + + this.rating = obj.rating ?? null; //IRating + this.subtitles = obj.subtitles ?? []; + } +} + +class PlatformPost extends PlatformContent { + constructor(obj) { + super(obj, 2); + obj = obj ?? {}; + this.plugin_type = "PlatformPost"; + this.thumbnails = obj.thumbnails ?? []; + this.images = obj.images ?? []; + this.description = obj.description ?? ""; + } +} +class PlatformPostDetails extends PlatformPost { + constructor(obj) { + super(obj); + obj = obj ?? {}; + this.plugin_type = "PlatformPostDetails"; + this.rating = obj.rating ?? RatingLikes(-1); + this.textType = obj.textType ?? 0; + this.content = obj.content ?? ""; + } +} + +//Sources +class VideoSourceDescriptor { + constructor(obj) { + obj = obj ?? {}; + this.plugin_type = "MuxVideoSourceDescriptor"; + this.isUnMuxed = false; + + if(obj.constructor === Array) + this.videoSources = obj; + else + this.videoSources = obj.videoSources ?? []; + } +} +class UnMuxVideoSourceDescriptor { + constructor(videoSourcesOrObj, audioSources) { + videoSourcesOrObj = videoSourcesOrObj ?? {}; + this.plugin_type = "UnMuxVideoSourceDescriptor"; + this.isUnMuxed = true; + + if(videoSourcesOrObj.constructor === Array) { + this.videoSources = videoSourcesOrObj; + this.audioSources = audioSources; + } + else { + this.videoSources = videoSourcesOrObj.videoSources ?? []; + this.audioSources = videoSourcesOrObj.audioSources ?? []; + } + } +} + +class VideoUrlSource { + constructor(obj) { + obj = obj ?? {}; + this.plugin_type = "VideoUrlSource"; + this.width = obj.width ?? 0; + this.height = obj.height ?? 0; + this.container = obj.container ?? ""; + this.codec = obj.codec ?? ""; + this.name = obj.name ?? ""; + this.bitrate = obj.bitrate ?? 0; + this.duration = obj.duration ?? 0; + this.url = obj.url; + } +} +class VideoUrlRangeSource extends VideoUrlSource { + constructor(obj) { + super(obj); + this.plugin_type = "VideoUrlRangeSource"; + + this.itagId = obj.itagId ?? null; + this.initStart = obj.initStart ?? null; + this.initEnd = obj.initEnd ?? null; + this.indexStart = obj.indexStart ?? null; + this.indexEnd = obj.indexEnd ?? null; + } +} +class AudioUrlSource { + constructor(obj) { + obj = obj ?? {}; + this.plugin_type = "AudioUrlSource"; + this.name = obj.name ?? ""; + this.bitrate = obj.bitrate ?? 0; + this.container = obj.container ?? ""; + this.codec = obj.codec ?? ""; + this.duration = obj.duration ?? 0; + this.url = obj.url; + this.language = obj.language ?? Language.UNKNOWN; + } +} +class AudioUrlRangeSource extends AudioUrlSource { + constructor(obj) { + super(obj); + this.plugin_type = "AudioUrlRangeSource"; + + this.itagId = obj.itagId ?? null; + this.initStart = obj.initStart ?? null; + this.initEnd = obj.initEnd ?? null; + this.indexStart = obj.indexStart ?? null; + this.indexEnd = obj.indexEnd ?? null; + this.audioChannels = obj.audioChannels ?? 2; + } +} +class HLSSource { + constructor(obj) { + obj = obj ?? {}; + this.plugin_type = "HLSSource"; + this.name = obj.name ?? "HLS"; + this.duration = obj.duration ?? 0; + this.url = obj.url; + this.priority = obj.priority ?? false; + if(obj.language) + this.language = obj.language; + } +} +class DashSource { + constructor(obj) { + obj = obj ?? {}; + this.plugin_type = "DashSource"; + this.name = obj.name ?? "Dash"; + this.duration = obj.duration ?? 0; + this.url = obj.url; + if(obj.language) + this.language = obj.language; + } +} + +class RequestModifier { + constructor(obj) { + obj = obj ?? {}; + this.allowByteSkip = obj.allowByteSkip; + } +} + +//Channel +class PlatformChannel { + constructor(obj) { + obj = obj ?? {}; + this.plugin_type = "PlatformChannel"; + this.id = obj.id ?? ""; //string + this.name = obj.name ?? ""; //string + this.thumbnail = obj.thumbnail; //string + this.banner = obj.banner; //string + this.subscribers = obj.subscribers ?? 0; //integer + this.description = obj.description; //string + this.url = obj.url ?? ""; //string + this.urlAlternatives = obj.urlAlternatives ?? []; + this.links = obj.links ?? { } //Map + } +} + +//Playlist +class PlatformPlaylist extends PlatformContent { + constructor(obj) { + super(obj, 4); + this.plugin_type = "PlatformPlaylist"; + this.videoCount = obj.videoCount ?? 0; + this.thumbnail = obj.thumbnail; + } +} +class PlatformPlaylistDetails extends PlatformPlaylist { + constructor(obj) { + super(obj); + this.plugin_type = "PlatformPlaylistDetails"; + this.contents = obj.contents; + } +} + + +//Ratings +class RatingLikes { + constructor(likes) { + this.type = 1; + this.likes = likes; + } +} +class RatingLikesDislikes { + constructor(likes,dislikes) { + this.type = 2; + this.likes = likes; + this.dislikes = dislikes; + } +} +class RatingScaler { + constructor(value) { + this.type = 3; + this.value = value; + } +} + +class PlatformComment { + constructor(obj) { + this.plugin_type = "Comment"; + this.contextUrl = obj.contextUrl ?? ""; + this.author = obj.author ?? new PlatformAuthorLink(null, "", "", null); + this.message = obj.message ?? ""; + this.rating = obj.rating ?? new RatingLikes(0); + this.date = obj.date ?? 0; + this.replyCount = obj.replyCount ?? 0; + this.context = obj.context ?? {}; + } +} + +//Temporary backwards compat +class Comment extends PlatformComment { + constructor(obj) { + super(obj); + } +} + +class PlaybackTracker { + constructor(interval) { + this.nextRequest = interval ?? 10*1000; + } + setProgress(seconds) { + throw new ScriptImplementationException("Missing required setProgress(seconds) on PlaybackTracker"); + } +} + +class LiveEventPager { + constructor(results, hasMore, context) { + this.plugin_type = "LiveEventPager"; + this.results = results ?? []; + this.hasMore = hasMore ?? false; + this.context = context ?? {}; + this.nextRequest = 4000; + } + + hasMorePagers() { return this.hasMore; } + nextPage() { return new Pager([], false, this.context) } +} + +class LiveEvent { + constructor(type) { + this.type = type; + } +} +class LiveEventComment extends LiveEvent { + constructor(name, message, thumbnail, colorName, badges) { + super(1); + this.name = name; + this.message = message; + this.thumbnail = thumbnail; + this.colorName = colorName; + this.badges = badges; + } +} +class LiveEventEmojis extends LiveEvent { + constructor(emojis) { + super(4); + this.emojis = emojis; + } +} +class LiveEventDonation extends LiveEvent { + constructor(amount, name, message, thumbnail, expire, colorDonation) { + super(5); + this.amount = amount; + this.name = name; + this.message = message ?? ""; + this.thumbnail = thumbnail; + this.expire = expire; + this.colorDonation = colorDonation; + } +} +class LiveEventViewCount extends LiveEvent { + constructor(viewCount) { + super(10); + this.viewCount = viewCount; + } +} +class LiveEventRaid extends LiveEvent { + constructor(targetUrl, targetName, targetThumbnail) { + super(100); + this.targetUrl = targetUrl; + this.targetName = targetName; + this.targetThumbnail = targetThumbnail; + } +} + +//Pagers +class ContentPager { + constructor(results, hasMore, context) { + this.plugin_type = "ContentPager"; + this.results = results ?? []; + this.hasMore = hasMore ?? false; + this.context = context ?? {}; + } + + hasMorePagers() { return this.hasMore; } + nextPage() { return new ContentPager([], false, this.context) } +} +class VideoPager { + constructor(results, hasMore, context) { + this.plugin_type = "VideoPager"; + this.results = results ?? []; + this.hasMore = hasMore ?? false; + this.context = context ?? {}; + } + + hasMorePagers() { return this.hasMore; } + nextPage() { return new VideoPager([], false, this.context) } +} +class ChannelPager { + constructor(results, hasMore, context) { + this.plugin_type = "ChannelPager"; + this.results = results ?? []; + this.hasMore = hasMore ?? false; + this.context = context ?? {}; + } + + hasMorePagers() { return this.hasMore; } + nextPage() { return new Pager([], false, this.context) } +} +class PlaylistPager { + constructor(results, hasMore, context) { + this.plugin_type = "PlaylistPager"; + this.results = results ?? []; + this.hasMore = hasMore ?? false; + this.context = context ?? {}; + } + + hasMorePagers() { return this.hasMore; } + nextPage() { return new Pager([], false, this.context) } +} +class CommentPager { + constructor(results, hasMore, context) { + this.plugin_type = "CommentPager"; + this.results = results ?? []; + this.hasMore = hasMore ?? false; + this.context = context ?? {}; + } + + hasMorePagers() { return this.hasMore; } + nextPage() { return new Pager([], false, this.context) } +} + +function throwException(type, message) { + throw new Error("V8EXCEPTION:" + type + "-" + message); +} + +let plugin = { + config: {}, + settings: {} +}; + +//To override by plugin +const source = { + getHome() { return new ContentPager([], false, {}); }, + + enable(config){ }, + disable() {}, + + searchSuggestions(query){ return []; }, + getSearchCapabilities(){ return { types: [], sorts: [] }; }, + search(query, type, order, filters){ return new ContentPager([], false, {}); }, //TODO + //OPTIONAL getSearchChannelContentsCapabilities(){ return { types: [], sorts: [] }; }, + //OPTIONAL searchChannelContents(channelUrl, query, type, order, filters){ return new Pager([], false, {}); }, //TODO + + isChannelUrl(url){ return false; }, + getChannel(url){ return null; }, + getChannelCapabilities(){ return { types: [], sorts: [] }; }, + getChannelContents(url, type, order, filters) { return new ContentPager([], false, {}); }, + + isContentDetailsUrl(url){ return false; }, + getContentDetails(url){ }, //TODO + + //OPTIONAL getComments(url){ return new Pager([], false, {}); }, //TODO + //OPTIONAL getSubComments(comment){ return new Pager([], false, {}); }, //TODO + + //OPTIONAL getSubscriptionsUser(){ return []; }, + //OPTIONAL getPlaylistsUser(){ return []; } +}; + +function parseSettings(settings) { + if(!settings) + return {}; + let newSettings = {}; + for(let key in settings) { + if(typeof settings[key] == "string") + newSettings[key] = JSON.parse(settings[key]); + else + newSettings[key] = settings[key]; + } + return newSettings; +} + +function log(str) { + if(str) { + console.log(str); + if(typeof str == "string") + bridge.log(str); + else + bridge.log(JSON.stringify(str, null, 4)); + } +} + +function encodePathSegment(segment) { + return encodeURIComponent(segment).replace(/[!'()*]/g, function (c) { + return '%' + c.charCodeAt(0).toString(16); + }); +} + +class URLSearchParams { + constructor(init) { + this._entries = {}; + if (typeof init === 'string') { + if (init !== '') { + init = init.replace(/^\?/, ''); + const attributes = init.split('&'); + let attribute; + for (let i = 0; i < attributes.length; i++) { + attribute = attributes[i].split('='); + this.append(decodeURIComponent(attribute[0]), (attribute.length > 1) ? decodeURIComponent(attribute[1]) : ''); + } + } + } + else if (init instanceof URLSearchParams) { + init.forEach((value, name) => { + this.append(value, name); + }); + } + } + append(name, value) { + value = value.toString(); + if (name in this._entries) { + this._entries[name].push(value); + } + else { + this._entries[name] = [value]; + } + } + delete(name) { + delete this._entries[name]; + } + get(name) { + return (name in this._entries) ? this._entries[name][0] : null; + } + getAll(name) { + return (name in this._entries) ? this._entries[name].slice(0) : []; + } + has(name) { + return (name in this._entries); + } + set(name, value) { + this._entries[name] = [value.toString()]; + } + forEach(callback) { + let entries; + for (let name in this._entries) { + if (this._entries.hasOwnProperty(name)) { + entries = this._entries[name]; + for (let i = 0; i < entries.length; i++) { + callback.call(this, entries[i], name, this); + } + } + } + } + keys() { + const items = []; + this.forEach((value, name) => { items.push(name); }); + return createIterator(items); + } + values() { + const items = []; + this.forEach((value) => { items.push(value); }); + return createIterator(items); + } + entries() { + const items = []; + this.forEach((value, name) => { items.push([value, name]); }); + return createIterator(items); + } + toString() { + let searchString = ''; + this.forEach((value, name) => { + if (searchString.length > 0) + searchString += '&'; + searchString += encodeURIComponent(name) + '=' + encodeURIComponent(value); + }); + return searchString; + } +} diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..33de5798a35638c54dcfc808e100930da711ba95 GIT binary patch literal 41107 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 S_mQL~vd_-h_Nles<^Kbe?(KvC literal 0 HcmV?d00001 diff --git a/app/src/main/java/com/futo/platformplayer/Extensions_Coroutines.kt b/app/src/main/java/com/futo/platformplayer/Extensions_Coroutines.kt new file mode 100644 index 00000000..cb711565 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/Extensions_Coroutines.kt @@ -0,0 +1,62 @@ +package com.futo.platformplayer + +import android.util.Log +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.selects.select +import java.lang.IllegalArgumentException + +//Syntax sugaring +suspend inline fun Collection>.awaitFirst(): T?{ + val tasks = this; + if (tasks.isEmpty()) { + return null; + } + + var result: T? = null; + select { + tasks.forEach { def -> + def.onAwait { + result = it; + true; + } + } + } + return result; +} +suspend inline fun Collection>.awaitFirstDeferred(): Pair, T> { + if (isEmpty()) { + throw IllegalArgumentException("Cannot be called on empty list"); + } + + return select { + this@awaitFirstDeferred.onEach { deferred -> + deferred.onAwait { result -> + Pair(deferred, result) + } + } + } +} + +suspend inline fun Collection>.awaitFirstNotNullDeferred(): Pair, T>? { + if (isEmpty()) { + return null; + } + + val toAwait = this.toMutableList(); + while(toAwait.isNotEmpty()) { + val result = select { + toAwait.onEach { deferred -> + deferred.onAwait { result -> + Pair(deferred, result) + } + } + } + + if(result.second != null) { + return Pair(result.first, result.second!!); + } + + toAwait.remove(result.first); + } + return null; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/Extensions_Formatting.kt b/app/src/main/java/com/futo/platformplayer/Extensions_Formatting.kt new file mode 100644 index 00000000..f32c86d3 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/Extensions_Formatting.kt @@ -0,0 +1,327 @@ +package com.futo.platformplayer + +import android.text.Html +import android.text.Spanned +import androidx.core.text.HtmlCompat +import org.jsoup.Jsoup +import org.jsoup.nodes.Attributes +import org.jsoup.nodes.Element +import org.jsoup.nodes.Node +import org.jsoup.nodes.TextNode +import org.jsoup.parser.Tag +import java.text.DecimalFormat +import java.time.OffsetDateTime +import java.time.temporal.ChronoUnit +import kotlin.math.abs +import kotlin.time.toDuration + + +//Long +val countInKilo = 1000; +val countInMillion = countInKilo * 1000; +val countInBillion = countInMillion * 1000; + +fun Long.toHumanNumber(): String { + val v = Math.abs(this); + if(v >= countInBillion) + return "${Math.floor((this / countInBillion).toDouble()).toLong()}B" + if(v >= countInMillion) + return "${"%.2f".format((this.toDouble() / countInMillion)).trim('0').trim('.')}M" + if(v >= countInKilo) + return "${"%.2f".format((this.toDouble() / countInKilo)).trim('0').trim('.')}K" + + return "${this}"; +} + +val decimalDigits2 = DecimalFormat("#.##"); + +val countInKbit = 1000; +val countInMbit = countInKbit * 1000; +val countInGbit = countInMbit * 1000; + +fun Int.toHumanBitrate() = this.toLong().toHumanBitrate(); +fun Long.toHumanBitrate(): String{ + val v = Math.abs(this); + if(v >= countInGbit) + return "${this / countInGbit}gbps"; + else if(v >= countInMbit) + return "${this / countInMbit}mbps"; + else if(v >= countInKbit) + return "${this / countInKbit}kbps"; + + return "${this}bps"; +} +fun Int.toHumanBytesSpeed() = this.toLong().toHumanBytesSpeed(); +fun Long.toHumanBytesSpeed(): String{ + val v = Math.abs(this); + if(v >= countInGbit) + return "${decimalDigits2.format(this / countInGbit.toDouble())}GB/s"; + else if(v >= countInMbit) + return "${decimalDigits2.format(this / countInMbit.toDouble())}MB/s"; + else if(v >= countInKbit) + return "${decimalDigits2.format(this / countInKbit.toDouble())}KB/s"; + + return "${this}B/s"; +} + +fun Int.toHumanBytesSize() = this.toLong().toHumanBytesSize(); +fun Long.toHumanBytesSize(withDecimal: Boolean = true): String{ + val v = Math.abs(this); + if(withDecimal) { + if(v >= countInGbit) + return "${decimalDigits2.format(this / countInGbit.toDouble())}GB"; + else if(v >= countInMbit) + return "${decimalDigits2.format(this / countInMbit.toDouble())}MB"; + else if(v >= countInKbit) + return "${decimalDigits2.format(this / countInKbit.toDouble())}KB"; + + return "${this}B"; + } + else { + if(v >= countInGbit) + return "${(this / countInGbit.toDouble()).toInt()}GB"; + else if(v >= countInMbit) + return "${(this / countInMbit.toDouble()).toInt()}MB"; + else if(v >= countInKbit) + return "${(this / countInKbit.toDouble()).toInt()}KB"; + + return "${this}B"; + } +} + + +//OffestDateTime +val secondsInMinute = 60; +val secondsInHour = secondsInMinute * 60; +val secondsInDay = secondsInHour * 24; +val secondsInWeek = secondsInDay * 7; +val secondsInMonth = secondsInDay * 30; //Roughly +val secondsInYear = secondsInDay * 365; + +fun OffsetDateTime.getNowDiffMiliseconds(): Long { + return ChronoUnit.MILLIS.between(this, OffsetDateTime.now()); +} +fun OffsetDateTime.getNowDiffSeconds(): Long { + return ChronoUnit.SECONDS.between(this, OffsetDateTime.now()); +} +fun OffsetDateTime.getNowDiffMinutes(): Long { + return ChronoUnit.MINUTES.between(this, OffsetDateTime.now()); +} +fun OffsetDateTime.getNowDiffHours(): Long { + return ChronoUnit.HOURS.between(this, OffsetDateTime.now()); +} +fun OffsetDateTime.getNowDiffDays(): Long { + return ChronoUnit.DAYS.between(this, OffsetDateTime.now()); +} +fun OffsetDateTime.getNowDiffWeeks(): Long { + return ChronoUnit.WEEKS.between(this, OffsetDateTime.now()); +} +fun OffsetDateTime.getNowDiffMonths(): Long { + return ChronoUnit.MONTHS.between(this, OffsetDateTime.now()); +} +fun OffsetDateTime.getNowDiffYears(): Long { + return ChronoUnit.YEARS.between(this, OffsetDateTime.now()); +} + +fun OffsetDateTime.getDiffDays(otherDate: OffsetDateTime): Long { + return ChronoUnit.WEEKS.between(this, otherDate); +} + +fun OffsetDateTime.toHumanNowDiffStringMinDay(abs: Boolean = false) : String { + var value = getNowDiffSeconds(); + + if(abs) value = abs(value); + if (value >= 2 * secondsInDay) { + return "${toHumanNowDiffString(abs)} ago"; + } + + if (value >= 1 * secondsInDay) { + return "Yesterday"; + } + + return "Today"; +}; + +fun OffsetDateTime.toHumanNowDiffString(abs: Boolean = false) : String { + var value = getNowDiffSeconds(); + + var unit = "second"; + + if(abs) value = abs(value); + if(value >= secondsInYear) { + value = getNowDiffYears(); + if(abs) value = abs(value); + unit = "year"; + } + else if(value >= secondsInMonth) { + value = getNowDiffMonths(); + if(abs) value = abs(value); + value = Math.max(1, value); + unit = "month"; + } + else if(value >= secondsInWeek) { + value = getNowDiffWeeks(); + if(abs) value = abs(value); + unit = "week"; + } + else if(value >= secondsInDay) { + value = getNowDiffDays(); + if(abs) value = abs(value); + unit = "day"; + } + else if(value >= secondsInHour) { + value = getNowDiffHours(); + if(abs) value = abs(value); + unit = "hour"; + } + else if(value >= secondsInMinute) { + value = getNowDiffMinutes(); + if(abs) value = abs(value); + unit = "minute"; + } + + if(value != 1L) + unit += "s"; + + return "${value} ${unit}"; +}; + +fun Long.toHumanTime(isMs: Boolean): String { + var scaler = 1; + if(isMs) + scaler = 1000; + val v = Math.abs(this); + val hours = Math.max(v/(secondsInHour*scaler), 0); + val mins = Math.max((v % (secondsInHour*scaler)) / (secondsInMinute * scaler), 0); + val minsStr = mins.toString(); + val seconds = Math.max(((v % (secondsInHour*scaler)) % (secondsInMinute * scaler))/scaler, 0); + val secsStr = seconds.toString().padStart(2, '0'); + val prefix = if (this < 0) { "-" } else { "" }; + + if(hours > 0) + return "${prefix}${hours}:${minsStr.padStart(2, '0')}:${secsStr}" + else + return "${prefix}${minsStr}:${secsStr}" +} + +//TODO: Determine if below stuff should have its own proper class, seems a bit too complex for a utility method +fun String.fixHtmlWhitespace(): Spanned { + return Html.fromHtml(replace("\n", "
"), HtmlCompat.FROM_HTML_MODE_LEGACY); +} + +fun String.fixHtmlLinks(): Spanned { + //TODO: Properly fix whitespace handling. + val doc = Jsoup.parse(replace("\n", "
")); + for (n in doc.body().childNodes()) { + replaceLinks(n); + } + for (n in doc.body().childNodes()) { + replaceTimestamps(n); + } + + val modifiedDoc = doc.body().toString(); + return HtmlCompat.fromHtml(modifiedDoc, HtmlCompat.FROM_HTML_MODE_LEGACY); +} + +val timestampRegex = Regex("\\d+:\\d+(?::\\d+)?"); +private val urlRegex = Regex("https?://\\S+"); +private val linkTag = Tag.valueOf("a"); +private fun replaceTimestamps(node: Node) { + for (n in node.childNodes()) { + replaceTimestamps(n); + } + + if (node is TextNode) { + val text = node.text(); + var lastOffset = 0; + var lastNode = node; + + val matches = timestampRegex.findAll(text).toList(); + for (i in matches.indices) { + val match = matches[i]; + + val textBeforeNode = TextNode(text.substring(lastOffset, match.range.first)); + lastNode.after(textBeforeNode); + lastNode = textBeforeNode; + + val attributes = Attributes(); + attributes.add("href", match.value); + val linkNode = Element(linkTag, null, attributes); + linkNode.text(match.value); + lastNode.after(linkNode); + lastNode = linkNode; + + lastOffset = match.range.last + 1; + } + + if (lastOffset > 0) { + if (lastOffset < text.length) { + lastNode.after(TextNode(text.substring(lastOffset))); + } + + node.remove(); + } + } +} +private fun replaceLinks(node: Node) { + for (n in node.childNodes()) { + replaceLinks(n); + } + + if (node is Element && node.tag() == linkTag) { + node.text(node.text().trim()); + } + + if (node is TextNode) { + val text = node.text(); + var lastOffset = 0; + var lastNode = node; + + val matches = urlRegex.findAll(text).toList(); + for (i in matches.indices) { + val match = matches[i]; + + val textBeforeNode = TextNode(text.substring(lastOffset, match.range.first)); + lastNode.after(textBeforeNode); + lastNode = textBeforeNode; + + val attributes = Attributes(); + attributes.add("href", match.value); + val linkNode = Element(linkTag, null, attributes); + linkNode.text(match.value); + lastNode.after(linkNode); + lastNode = linkNode; + + lastOffset = match.range.last + 1; + } + + if (lastOffset > 0) { + if (lastOffset < text.length) { + lastNode.after(TextNode(text.substring(lastOffset))); + } + + node.remove(); + } + } +} + +fun ByteArray.toHexString(): String { + return this.joinToString("") { "%02x".format(it) } +} + +fun ByteArray.toHexString(size: Int): String { + return this.sliceArray(IntRange(0, size)).toHexString(); +} + +private val safeCharacters = HashSet(('a'..'z') + ('A'..'Z') + ('0'..'9') + listOf('-', '_')); +fun String.toSafeFileName(): String { + return this.map { if (it in safeCharacters) it else '_' }.joinToString(separator = "") +} + +fun String.matchesDomain(queryDomain: String): Boolean { + if(queryDomain.startsWith(".")) + //TODO: Should be safe, but double verify if can't be exploited + return this.endsWith(queryDomain) || this == queryDomain.trimStart('.') + else + return this == queryDomain; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/Extensions_Network.kt b/app/src/main/java/com/futo/platformplayer/Extensions_Network.kt new file mode 100644 index 00000000..f2310bfe --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/Extensions_Network.kt @@ -0,0 +1,275 @@ +package com.futo.platformplayer + +import com.google.common.base.CharMatcher +import java.net.Inet4Address +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.Socket +import java.nio.ByteBuffer + + +private const val IPV4_PART_COUNT = 4; +private const val IPV6_PART_COUNT = 8; +private const val IPV4_DELIMITER = '.'; +private const val IPV6_DELIMITER = ':'; +private val IPV4_DELIMITER_MATCHER = CharMatcher.`is`(IPV4_DELIMITER); +private val IPV6_DELIMITER_MATCHER = CharMatcher.`is`(IPV6_DELIMITER); +private val LOOPBACK4: Inet4Address? = "127.0.0.1".toInetAddress() as Inet4Address?; +private val ANY4: Inet4Address? = "0.0.0.0".toInetAddress() as Inet4Address?; + +fun String.toInetAddress(): InetAddress? { + val addr = ipStringToBytes(this) ?: return null; + return addr.toInetAddress(); +} + +private fun ipStringToBytes(ipStringParam: String): ByteArray? { + var ipString: String? = ipStringParam; + var hasColon = false; + var hasDot = false; + var percentIndex = -1; + + for (i in 0 until ipString!!.length) { + val c = ipString[i]; + if (c == '.') { + hasDot = true; + } else if (c == ':') { + if (hasDot) { + return null; + } + + hasColon = true; + } else if (c == '%') { + percentIndex = i; + break; + } else if (c.digitToIntOrNull(16) ?: -1 == -1) { + return null; + } + } + + // Now decide which address family to parse. + if (hasColon) { + if (hasDot) { + ipString = convertDottedQuadToHex(ipString) + if (ipString == null) { + return null; + } + } + if (percentIndex != -1) { + ipString = ipString.substring(0, percentIndex); + } + return textToNumericFormatV6(ipString); + } else if (hasDot) { + return if (percentIndex != -1) { + null // Scope IDs are not supported for IPV4 + } else textToNumericFormatV4(ipString); + } + return null +} + +private fun textToNumericFormatV4(ipString: String): ByteArray? { + if (IPV4_DELIMITER_MATCHER.countIn(ipString) + 1 != IPV4_PART_COUNT) { + return null; // Wrong number of parts + } + val bytes = ByteArray(IPV4_PART_COUNT); + var start = 0; + // Iterate through the parts of the ip string. + // Invariant: start is always the beginning of an octet. + for (i in 0 until IPV4_PART_COUNT) { + var end = ipString.indexOf(IPV4_DELIMITER, start); + if (end == -1) { + end = ipString.length; + } + try { + bytes[i] = parseOctet(ipString, start, end); + } catch (ex: java.lang.NumberFormatException) { + return null; + } + start = end + 1; + } + return bytes; +} + +private fun textToNumericFormatV6(ipString: String): ByteArray? { + // An address can have [2..8] colons. + val delimiterCount: Int = IPV6_DELIMITER_MATCHER.countIn(ipString); + if (delimiterCount < 2 || delimiterCount > IPV6_PART_COUNT) { + return null; + } + var partsSkipped: Int = IPV6_PART_COUNT - (delimiterCount + 1); // estimate; may be modified later + var hasSkip = false; + // Scan for the appearance of ::, to mark a skip-format IPV6 string and adjust the partsSkipped + // estimate. + for (i in 0 until ipString.length - 1) { + if (ipString[i] == IPV6_DELIMITER && ipString[i + 1] == IPV6_DELIMITER) { + if (hasSkip) { + return null; // Can't have more than one :: + } + hasSkip = true; + partsSkipped++; // :: means we skipped an extra part in between the two delimiters. + if (i == 0) { + partsSkipped++; // Begins with ::, so we skipped the part preceding the first : + } + if (i == ipString.length - 2) { + partsSkipped++; // Ends with ::, so we skipped the part after the last : + } + } + } + if (ipString[0] == IPV6_DELIMITER && ipString[1] != IPV6_DELIMITER) { + return null; // ^: requires ^:: + } + if (ipString[ipString.length - 1] == IPV6_DELIMITER && ipString[ipString.length - 2] != IPV6_DELIMITER) { + return null; // :$ requires ::$ + } + if (hasSkip && partsSkipped <= 0) { + return null // :: must expand to at least one '0' + } + if (!hasSkip && delimiterCount + 1 != IPV6_PART_COUNT) { + return null // Incorrect number of parts + } + val rawBytes: ByteBuffer = ByteBuffer.allocate(2 * IPV6_PART_COUNT) + try { + // Iterate through the parts of the ip string. + // Invariant: start is always the beginning of a hextet, or the second ':' of the skip + // sequence "::" + var start = 0 + if (ipString[0] == IPV6_DELIMITER) { + start = 1 + } + while (start < ipString.length) { + var end: Int = ipString.indexOf(IPV6_DELIMITER, start) + if (end == -1) { + end = ipString.length + } + if (ipString[start] == IPV6_DELIMITER) { + // expand zeroes + for (i in 0 until partsSkipped) { + rawBytes.putShort(0.toShort()) + } + } else { + rawBytes.putShort(parseHextet(ipString, start, end)) + } + start = end + 1 + } + } catch (ex: NumberFormatException) { + return null + } + return rawBytes.array() +} + +private fun parseHextet(ipString: String, start: Int, end: Int): Short { + // Note: we already verified that this string contains only hex digits. + val length = end - start + if (length <= 0 || length > 4) { + throw java.lang.NumberFormatException() + } + var hextet = 0 + for (i in start until end) { + hextet = hextet shl 4 + hextet = hextet or ipString[i].digitToIntOrNull(16)!! ?: -1 + } + return hextet.toShort() +} + +private fun parseOctet(ipString: String, start: Int, end: Int): Byte { + // Note: we already verified that this string contains only hex digits, but the string may still + // contain non-decimal characters. + val length = end - start + if (length <= 0 || length > 3) { + throw java.lang.NumberFormatException() + } + // Disallow leading zeroes, because no clear standard exists on + // whether these should be interpreted as decimal or octal. + if (length > 1 && ipString[start] == '0') { + throw java.lang.NumberFormatException() + } + var octet = 0 + for (i in start until end) { + octet *= 10 + val digit = ipString[i].digitToIntOrNull() ?: -1 + if (digit < 0) { + throw java.lang.NumberFormatException() + } + octet += digit + } + if (octet > 255) { + throw java.lang.NumberFormatException() + } + return octet.toByte() +} + +fun convertDottedQuadToHex(ipString: String): String? { + val lastColon = ipString.lastIndexOf(':'); + val initialPart = ipString.substring(0, lastColon + 1); + val dottedQuad = ipString.substring(lastColon + 1); + val quad: ByteArray = textToNumericFormatV4(dottedQuad) ?: return null; + val penultimate = Integer.toHexString(quad[0].toInt() and 0xff shl 8 or (quad[1].toInt() and 0xff)); + val ultimate = Integer.toHexString(quad[2].toInt() and 0xff shl 8 or (quad[3].toInt() and 0xff)); + return "$initialPart$penultimate:$ultimate"; +} + +private fun ByteArray.toInetAddress(): InetAddress { + return InetAddress.getByAddress(this); +} + +fun getConnectedSocket(addresses: List, port: Int): Socket? { + if (addresses.isEmpty()) { + return null; + } + + if (addresses.size == 1) { + try { + return Socket(addresses[0], port); + } catch (e: Throwable) { + //Ignored. + } + + return null; + } + + val sockets: ArrayList = arrayListOf(); + for (i in addresses.indices) { + sockets.add(Socket()); + } + + val syncObject = Object(); + var connectedSocket: Socket? = null; + val threads: ArrayList = arrayListOf(); + for (i in 0 until sockets.size) { + val address = addresses[i]; + val socket = sockets[i]; + val thread = Thread { + try { + synchronized(syncObject) { + if (connectedSocket != null) { + return@Thread; + } + } + + socket.connect(InetSocketAddress(address, port)); + + synchronized(syncObject) { + if (connectedSocket == null) { + connectedSocket = socket; + + for (j in 0 until sockets.size) { + if (i != j) { + sockets[j].close(); + } + } + } + } + } catch (e: Throwable) { + //Ignore + } + }; + + thread.start(); + threads.add(thread); + } + + for (thread in threads) { + thread.join(); + } + + return connectedSocket; +} diff --git a/app/src/main/java/com/futo/platformplayer/Extensions_Polycentric.kt b/app/src/main/java/com/futo/platformplayer/Extensions_Polycentric.kt new file mode 100644 index 00000000..6d6c076e --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/Extensions_Polycentric.kt @@ -0,0 +1,38 @@ +package com.futo.platformplayer + +import com.futo.platformplayer.states.StatePlatform +import userpackage.Protocol +import kotlin.math.abs +import kotlin.math.min + +fun Protocol.ImageBundle?.selectBestImage(desiredPixelCount: Int): Protocol.ImageManifest? { + if (this == null) { + return null + } + + val maximumFileSize = min(10 * desiredPixelCount, 5 * 1024 * 1024) + return imageManifestsList.mapNotNull { if (it.byteCount > maximumFileSize) null else it } + .minByOrNull { abs(it.width * it.height - desiredPixelCount) } +} + +fun Protocol.ImageBundle?.selectLowestResolutionImage(): Protocol.ImageManifest? { + if (this == null) { + return null + } + + val maximumFileSize = 5 * 1024 * 1024; + return imageManifestsList.filter { it.byteCount < maximumFileSize }.minByOrNull { abs(it.width * it.height) } +} + +fun Protocol.ImageBundle?.selectHighestResolutionImage(): Protocol.ImageManifest? { + if (this == null) { + return null + } + + val maximumFileSize = 5 * 1024 * 1024; + return imageManifestsList.filter { it.byteCount < maximumFileSize }.maxByOrNull { abs(it.width * it.height) } +} + +fun Protocol.Claim.resolveChannelUrl(): String? { + return StatePlatform.instance.resolveChannelUrlByClaimTemplates(this.claimType.toInt(), this.claimFieldsList.associate { Pair(it.key.toInt(), it.value) }) +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/Extensions_Syntax.kt b/app/src/main/java/com/futo/platformplayer/Extensions_Syntax.kt new file mode 100644 index 00000000..41d1822e --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/Extensions_Syntax.kt @@ -0,0 +1,15 @@ +package com.futo.platformplayer + +//Syntax sugaring +inline fun Any.assume(): T?{ + if(this is T) + return this; + else + return null; +} +inline fun Any.assume(cb: (T) -> R): R? { + val result = this.assume(); + if(result != null) + return cb(result); + return null; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt b/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt new file mode 100644 index 00000000..e1b643a4 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt @@ -0,0 +1,131 @@ +package com.futo.platformplayer + +import com.caoccao.javet.values.V8Value +import com.caoccao.javet.values.primitive.* +import com.caoccao.javet.values.reference.V8ValueArray +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.engine.exceptions.ScriptImplementationException + + +//V8 +fun V8Value?.orNull(handler: (V8Value)->R) : R? { + if(this == null) + return null; + if(this is V8ValueNull || this is V8ValueUndefined || (this is V8ValueDouble && this.isNaN)) + return null; + else + return handler(this); +} +fun V8Value?.orDefault(default: R, handler: (V8Value)->R): R { + if(this == null || this is V8ValueNull || this is V8ValueUndefined) + return default; + else + return handler(this); +} + +inline fun V8Value.expectOrThrow(config: IV8PluginConfig, contextName: String): T { + if(this !is T) + throw ScriptImplementationException(config, "Expected ${contextName} to be of type ${T::class.simpleName}, but found ${this::class.simpleName}"); + return this as T; +} + +//Singles +inline fun V8ValueObject.getOrThrowNullable(config: IV8PluginConfig, key: String, contextName: String): T? = getOrThrow(config, key, contextName, true); +inline fun V8ValueObject.getOrThrow(config: IV8PluginConfig, key: String, contextName: String, nullable: Boolean = false): T { + val value = this.get(key); + if(nullable) + return value.orNull { value.expectV8Variant(config, "${contextName}.${key}") } as T + else + return value.expectV8Variant(config, "${contextName}.${key}"); +} +inline fun V8ValueObject.getOrNull(config: IV8PluginConfig, key: String, contextName: String): T? { + val value = this.get(key); + return value.orNull { value.expectV8Variant(config, "${contextName}.${key}") }; +} +inline fun V8ValueObject.getOrDefault(config: IV8PluginConfig, key: String, contextName: String, default: T?): T? { + val value = this.get(key); + return value.orNull { value.expectV8Variant(config, "${contextName}.${key}") } ?: default; +} + +//Lists + +inline fun V8ValueObject.getOrThrowNullableList(config: IV8PluginConfig, key: String, contextName: String): List? = getOrThrowList(config, key, contextName, true); +inline fun V8ValueObject.getOrThrowList(config: IV8PluginConfig, key: String, contextName: String, nullable: Boolean = false): List { + val value = this.get(key); + val array = if(nullable) + value.orNull { value.expectV8Variant(config, "${contextName}.${key}") } + else + value.expectV8Variant(config, "${contextName}.${key}"); + if(array == null) + return listOf(); + + return array.expectV8Variants(config, contextName, false); +} +inline fun V8ValueObject.getOrNullList(config: IV8PluginConfig, key: String, contextName: String): List? { + val value = this.get(key); + val array = value.orNull { value.expectV8Variant(config, "${contextName}.${key}") } + ?: return null; + + return array.expectV8Variants(config, contextName, true); +} +inline fun V8ValueObject.getOrDefaultList(config: IV8PluginConfig, key: String, contextName: String, default: List?): List? { + val value = this.get(key); + val array = value.orNull { value.expectV8Variant(config, "${contextName}.${key}") } + ?: return default; + + return array.expectV8Variants(config, contextName, true); +} + +inline fun V8ValueArray.expectV8Variants(config: IV8PluginConfig, contextName: String, nullable: Boolean): List { + val array = this; + if(nullable) + return array.keys + .map { Pair(it, array.get(it)) } + .map { kv-> kv.second.orNull { it.expectV8Variant(config, contextName + "[${kv.first}]", ) } as T }; + else + return array.keys + .map { Pair(it, array.get(it)) } + .map { kv-> kv.second.orNull { it.expectV8Variant(config, contextName + "[${kv.first}]", ) } as T }; +} + +inline fun V8Value.expectV8Variant(config: IV8PluginConfig, contextName: String): T { + return when(T::class) { + String::class -> this.expectOrThrow(config, contextName).value as T; + Int::class -> { + if(this is V8ValueDouble) + return this.value.toInt() as T; + else if(this is V8ValueInteger) + return this.value.toInt() as T; + else if(this is V8ValueLong) + return this.value.toInt() as T; + else this.expectOrThrow(config, contextName).value as T + }; + Long::class -> { + if(this is V8ValueDouble) + return this.value.toLong() as T; + else if(this is V8ValueInteger) + return this.value.toLong() as T; + else + return this.expectOrThrow(config, contextName).value.toLong() as T + }; + V8ValueObject::class -> this.expectOrThrow(config, contextName) as T + V8ValueArray::class -> this.expectOrThrow(config, contextName) as T; + Boolean::class -> this.expectOrThrow(config, contextName).value as T; + Float::class -> this.expectOrThrow(config, contextName).value.toFloat() as T; + Double::class -> this.expectOrThrow(config, contextName).value as T; + HashMap::class -> this.expectOrThrow(config, contextName).let { V8ObjectToHashMap(it) } as T; + Map::class -> this.expectOrThrow(config, contextName).let { V8ObjectToHashMap(it) } as T; + List::class -> this.expectOrThrow(config, contextName).let { V8ArrayToStringList(it) } as T; + else -> throw NotImplementedError("Type ${T::class.simpleName} not implemented conversion"); + } +} +fun V8ArrayToStringList(obj: V8ValueArray): List = obj.keys.map { obj.getString(it) }; +fun V8ObjectToHashMap(obj: V8ValueObject?): HashMap { + if(obj == null) + return hashMapOf(); + val map = hashMapOf(); + for(prop in obj.ownPropertyNames.keys.map { obj.ownPropertyNames.get(it).toString() }) + map.put(prop, obj.getString(prop)); + return map; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt new file mode 100644 index 00000000..ea4e8384 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -0,0 +1,606 @@ +package com.futo.platformplayer + +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.webkit.CookieManager +import androidx.lifecycle.lifecycleScope +import com.futo.platformplayer.activities.* +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.fragment.mainactivity.bottombar.MenuBottomBarFragment +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.serializers.FlexibleBooleanSerializer +import com.futo.platformplayer.serializers.OffsetDateTimeSerializer +import com.futo.platformplayer.states.* +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.FragmentedStorageFileJson +import com.futo.platformplayer.views.FeedStyle +import com.futo.platformplayer.views.fields.DropdownFieldOptionsId +import com.futo.platformplayer.views.fields.FormField +import com.futo.platformplayer.views.fields.FieldForm +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import java.io.File +import java.time.OffsetDateTime + +@Serializable +data class MenuBottomBarSetting(val id: Int, var enabled: Boolean); + +@Serializable() +class Settings : FragmentedStorageFileJson() { + var didFirstStart: Boolean = false; + + @Serializable + val tabs: MutableList = MenuBottomBarFragment.buttonDefinitions.map { MenuBottomBarSetting(it.id, true) }.toMutableList() + + @Transient + val onTabsChanged = Event0(); + + @FormField( + "Manage Polycentric identity", FieldForm.BUTTON, + "Manage your Polycentric identity", -2 + ) + fun managePolycentricIdentity() { + SettingsActivity.getActivity()?.let { + if (StatePolycentric.instance.processHandle != null) { + it.startActivity(Intent(it, PolycentricProfileActivity::class.java)); + } else { + it.startActivity(Intent(it, PolycentricHomeActivity::class.java)); + } + } + } + + @FormField( + "Submit feedback", FieldForm.BUTTON, + "Give feedback on the application", -1 + ) + fun submitFeedback() { + try { + val i = Intent(Intent.ACTION_VIEW); + val subject = "Feedback Grayjay"; + val body = "Hey,\n\nI have some feedback on the Grayjay app.\nVersion information (version_name = ${BuildConfig.VERSION_NAME}, version_code = ${BuildConfig.VERSION_CODE}, flavor = ${BuildConfig.FLAVOR}, build_type = ${BuildConfig.BUILD_TYPE}})\n\n"; + val data = Uri.parse("mailto:grayjay@futo.org?subject=" + Uri.encode(subject) + "&body=" + Uri.encode(body)); + i.data = data; + + StateApp.withContext { it.startActivity(i); }; + } catch (e: Throwable) { + //Ignored + } + } + + @FormField( + "Manage Tabs", FieldForm.BUTTON, + "Change tabs visible on the home screen", -1 + ) + fun manageTabs() { + try { + SettingsActivity.getActivity()?.let { + it.startActivity(Intent(it, ManageTabsActivity::class.java)); + } + } catch (e: Throwable) { + //Ignored + } + } + + @FormField("Home", "group", "Configure how your Home tab works and feels", 1) + var home = HomeSettings(); + @Serializable + class HomeSettings { + @FormField("Feed Style", FieldForm.DROPDOWN, "", 5) + @DropdownFieldOptionsId(R.array.feed_style) + var homeFeedStyle: Int = 1; + + fun getHomeFeedStyle(): FeedStyle { + if(homeFeedStyle == 0) + return FeedStyle.PREVIEW; + else + return FeedStyle.THUMBNAIL; + } + } + + @FormField("Search", "group", "", 2) + var search = SearchSettings(); + @Serializable + class SearchSettings { + @FormField("Search History", FieldForm.TOGGLE, "", 4) + @Serializable(with = FlexibleBooleanSerializer::class) + var searchHistory: Boolean = true; + + + @FormField("Feed Style", FieldForm.DROPDOWN, "", 5) + @DropdownFieldOptionsId(R.array.feed_style) + var searchFeedStyle: Int = 1; + + + fun getSearchFeedStyle(): FeedStyle { + if(searchFeedStyle == 0) + return FeedStyle.PREVIEW; + else + return FeedStyle.THUMBNAIL; + } + } + + @FormField("Subscriptions", "group", "Configure how your Subscriptions works and feels", 3) + var subscriptions = SubscriptionsSettings(); + @Serializable + class SubscriptionsSettings { + @FormField("Feed Style", FieldForm.DROPDOWN, "", 5) + @DropdownFieldOptionsId(R.array.feed_style) + var subscriptionsFeedStyle: Int = 1; + + fun getSubscriptionsFeedStyle(): FeedStyle { + if(subscriptionsFeedStyle == 0) + return FeedStyle.PREVIEW; + else + return FeedStyle.THUMBNAIL; + } + + @FormField("Background Update", FieldForm.DROPDOWN, "Experimental background update for subscriptions cache (requires restart)", 6) + @DropdownFieldOptionsId(R.array.background_interval) + var subscriptionsBackgroundUpdateInterval: Int = 0; + + fun getSubscriptionsBackgroundIntervalMinutes(): Int = when(subscriptionsBackgroundUpdateInterval) { + 0 -> 0; + 1 -> 15; + 2 -> 60; + 3 -> 60 * 3; + 4 -> 60 * 6; + 5 -> 60 * 12; + 6 -> 60 * 24; + else -> 0 + }; + + + @FormField("Subscription Concurrency", FieldForm.DROPDOWN, "Specify how many threads are used to fetch channels (requires restart)", 7) + @DropdownFieldOptionsId(R.array.thread_count) + var subscriptionConcurrency: Int = 3; + + fun getSubscriptionsConcurrency() : Int { + return threadIndexToCount(subscriptionConcurrency); + } + } + + @FormField("Player", "group", "Change behavior of the player", 4) + var playback = PlaybackSettings(); + @Serializable + class PlaybackSettings { + @FormField("Primary Language", FieldForm.DROPDOWN, "", 0) + @DropdownFieldOptionsId(R.array.languages) + var primaryLanguage: Int = 0; + + fun getPrimaryLanguage(context: Context) = context.resources.getStringArray(R.array.languages)[primaryLanguage]; + + @FormField("Default Playback Speed", FieldForm.DROPDOWN, "", 1) + @DropdownFieldOptionsId(R.array.playback_speeds) + var defaultPlaybackSpeed: Int = 3; + fun getDefaultPlaybackSpeed(): Float = when(defaultPlaybackSpeed) { + 0 -> 0.25f; + 1 -> 0.5f; + 2 -> 0.75f; + 3 -> 1.0f; + 4 -> 1.25f; + 5 -> 1.5f; + 6 -> 1.75f; + 7 -> 2.0f; + 8 -> 2.25f; + else -> 1.0f; + }; + + @FormField("Preferred Quality", FieldForm.DROPDOWN, "", 2) + @DropdownFieldOptionsId(R.array.preferred_quality_array) + var preferredQuality: Int = 0; + + @FormField("Preferred Metered Quality", FieldForm.DROPDOWN, "", 2) + @DropdownFieldOptionsId(R.array.preferred_quality_array) + var preferredMeteredQuality: Int = 0; + fun getPreferredQualityPixelCount(): Int = preferedQualityToPixels(preferredQuality); + fun getPreferredMeteredQualityPixelCount(): Int = preferedQualityToPixels(preferredMeteredQuality); + fun getCurrentPreferredQualityPixelCount(): Int = if(!StateApp.instance.isCurrentMetered()) getPreferredQualityPixelCount() else getPreferredMeteredQualityPixelCount(); + + @FormField("Preferred Preview Quality", FieldForm.DROPDOWN, "", 3) + @DropdownFieldOptionsId(R.array.preferred_quality_array) + var preferredPreviewQuality: Int = 5; + fun getPreferredPreviewQualityPixelCount(): Int = preferedQualityToPixels(preferredPreviewQuality); + + @FormField("Auto-Rotate", FieldForm.DROPDOWN, "", 4) + @DropdownFieldOptionsId(R.array.system_enabled_disabled_array) + var autoRotate: Int = 2; + + fun isAutoRotate() = autoRotate == 1 || (autoRotate == 2 && StateApp.instance.getCurrentSystemAutoRotate()); + + @FormField("Background Behavior", FieldForm.DROPDOWN, "", 5) + @DropdownFieldOptionsId(R.array.player_background_behavior) + var backgroundPlay: Int = 2; + + fun isBackgroundContinue() = backgroundPlay == 1; + fun isBackgroundPictureInPicture() = backgroundPlay == 2; + + @FormField("Resume After Preview", FieldForm.DROPDOWN, "When watching a video in preview mode, resume at the position when opening the video", 4) + @DropdownFieldOptionsId(R.array.resume_after_preview) + var resumeAfterPreview: Int = 1; + + + @FormField("Live Chat Webview", FieldForm.TOGGLE, "Use the live chat web window when available over native implementation.", 5) + var useLiveChatWindow: Boolean = true; + + fun shouldResumePreview(previewedPosition: Long): Boolean{ + if(resumeAfterPreview == 2) + return true; + if(resumeAfterPreview == 1 && previewedPosition > 10) + return true; + return false; + } + } + + @FormField("Downloads", "group", "Configure downloading of videos", 5) + var downloads = Downloads(); + @Serializable + class Downloads { + + @FormField("Download when", FieldForm.DROPDOWN, "Configure when videos should be downloaded", 0) + @DropdownFieldOptionsId(R.array.when_download) + var whenDownload: Int = 0; + + fun shouldDownload(): Boolean { + return when (whenDownload) { + 0 -> !StateApp.instance.isCurrentMetered(); + 1 -> StateApp.instance.isNetworkState(StateApp.NetworkState.WIFI, StateApp.NetworkState.ETHERNET); + 2 -> true; + else -> false; + } + } + + @FormField("Default Video Quality", FieldForm.DROPDOWN, "", 2) + @DropdownFieldOptionsId(R.array.preferred_video_download) + var preferredVideoQuality: Int = 4; + fun getDefaultVideoQualityPixels(): Int = preferedQualityToPixels(preferredVideoQuality); + + @FormField("Default Audio Quality", FieldForm.DROPDOWN, "", 3) + @DropdownFieldOptionsId(R.array.preferred_audio_download) + var preferredAudioQuality: Int = 1; + fun isHighBitrateDefault(): Boolean = preferredAudioQuality > 0; + + @FormField("ByteRange Download", FieldForm.TOGGLE, "Attempt to utilize byte ranges, this can be combined with concurrency to bypass throttling", 4) + @Serializable(with = FlexibleBooleanSerializer::class) + var byteRangeDownload: Boolean = true; + + @FormField("ByteRange Concurrency", FieldForm.DROPDOWN, "Number of concurrent threads to multiply download speeds from throttled sources", 5) + @DropdownFieldOptionsId(R.array.thread_count) + var byteRangeConcurrency: Int = 3; + fun getByteRangeThreadCount(): Int { + return threadIndexToCount(byteRangeConcurrency); + } + } + + @FormField("Browsing", "group", "Configure browsing behavior", 6) + var browsing = Browsing(); + @Serializable + class Browsing { + @FormField("Enable Video Cache", FieldForm.TOGGLE, "A cache to quickly load previously fetched videos", 0) + @Serializable(with = FlexibleBooleanSerializer::class) + var videoCache: Boolean = true; + } + + @FormField("Casting", "group", "Configure casting", 7) + var casting = Casting(); + @Serializable + class Casting { + @FormField("Enabled", FieldForm.TOGGLE, "Enable casting", 0) + @Serializable(with = FlexibleBooleanSerializer::class) + var enabled: Boolean = true; + + + /*TODO: Should we have a different casting quality? + @FormField("Preferred Casting Quality", FieldForm.DROPDOWN, "", 3) + @DropdownFieldOptionsId(R.array.preferred_quality_array) + var preferredQuality: Int = 4; + fun getPreferredQualityPixelCount(): Int { + when (preferredQuality) { + 0 -> return 1280 * 720; + 1 -> return 3840 * 2160; + 2 -> return 1920 * 1080; + 3 -> return 1280 * 720; + 4 -> return 640 * 480; + else -> return 0; + } + }*/ + } + + + @FormField("Logging", FieldForm.GROUP, "", 8) + var logging = Logging(); + @Serializable + class Logging { + @FormField("Log Level", FieldForm.DROPDOWN, "", 0) + @DropdownFieldOptionsId(R.array.log_levels) + var logLevel: Int = 0; + + @FormField( + "Submit logs", FieldForm.BUTTON, + "Submit logs to help us narrow down issues", 1 + ) + fun submitLogs() { + StateApp.instance.scopeGetter().launch(Dispatchers.IO) { + try { + if (!Logger.submitLogs()) { + withContext(Dispatchers.Main) { + SettingsActivity.getActivity()?.let { UIDialogs.toast(it, "Please enable logging to submit logs") } + } + } + } catch (e: Throwable) { + Logger.e("Settings", "Failed to submit logs.", e); + } + } + } + } + + + + @FormField("Announcement", FieldForm.GROUP, "", 10) + var announcementSettings = AnnouncementSettings(); + @Serializable + class AnnouncementSettings { + @FormField( + "Reset announcements", FieldForm.BUTTON, + "Reset hidden announcements", 1 + ) + fun resetAnnouncements() { + StateAnnouncement.instance.resetAnnouncements(); + UIDialogs.toast("Announcements reset."); + } + } + + @FormField("Plugins", FieldForm.GROUP, "", 11) + @Transient + var plugins = Plugins(); + @Serializable + class Plugins { + + @FormField("Clear Cookies on Logout", FieldForm.TOGGLE, "Clears cookies when you log out, allowing you to change account.", 0) + var clearCookiesOnLogout: Boolean = true; + + @FormField( + "Clear Cookies", FieldForm.BUTTON, + "Clears in-app browser cookies, especially useful for fully logging out of plugins.", 1 + ) + fun clearCookies() { + val cookieManager: CookieManager = CookieManager.getInstance(); + cookieManager.removeAllCookies(null); + } + @FormField( + "Reinstall Embedded Plugins", FieldForm.BUTTON, + "Also removes any data related plugin like login or settings (may not clear browser cache)", 1 + ) + fun reinstallEmbedded() { + StateApp.instance.scopeOrNull!!.launch(Dispatchers.IO) { + try { + StatePlugins.instance.reinstallEmbeddedPlugins(StateApp.instance.context); + + withContext(Dispatchers.Main) { + StateApp.instance.contextOrNull?.let { + UIDialogs.toast(it, "Embedded plugins reinstalled, a reboot is recommended"); + }; + } + } catch (ex: Exception) { + withContext(Dispatchers.Main) { + StateApp.withContext { + UIDialogs.toast(it, "Failed: " + ex.message); + }; + } + } + } + } + } + + + @FormField("Auto Update", "group", "Configure the auto updater", 12) + var autoUpdate = AutoUpdate(); + @Serializable + class AutoUpdate { + @FormField("Check", FieldForm.DROPDOWN, "", 0) + @DropdownFieldOptionsId(R.array.auto_update_when_array) + var check: Int = 0; + + @FormField("Background download", FieldForm.DROPDOWN, "Configure if background download should be used", 1) + @DropdownFieldOptionsId(R.array.background_download) + var backgroundDownload: Int = 0; + + @FormField("Download when", FieldForm.DROPDOWN, "Configure when updates should be downloaded", 2) + @DropdownFieldOptionsId(R.array.when_download) + var whenDownload: Int = 0; + + fun shouldDownload(): Boolean { + return when (whenDownload) { + 0 -> !StateApp.instance.isCurrentMetered(); + 1 -> StateApp.instance.isNetworkState(StateApp.NetworkState.WIFI, StateApp.NetworkState.ETHERNET); + 2 -> true; + else -> false; + } + } + + fun isAutoUpdateEnabled(): Boolean { + return check == 0 && !BuildConfig.IS_PLAYSTORE_BUILD; + } + + @FormField( + "Manual check", FieldForm.BUTTON, + "Manually check for updates", 3 + ) + fun manualCheck() { + if (!BuildConfig.IS_PLAYSTORE_BUILD) { + SettingsActivity.getActivity()?.let { + StateUpdate.instance.checkForUpdates(it, true); + } + } else { + SettingsActivity.getActivity()?.let { + try { + it.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=${it.packageName}"))) + } catch (e: ActivityNotFoundException) { + UIDialogs.toast(it, "Failed to show store."); + } + } + } + } + + @FormField( + "View changelog", FieldForm.BUTTON, + "Review the current and past changelogs", 4 + ) + fun viewChangelog() { + UIDialogs.toast("Retrieving changelog"); + SettingsActivity.getActivity()?.let { + StateApp.instance.scopeGetter().launch(Dispatchers.IO) { + try { + val version = StateUpdate.instance.downloadVersionCode(ManagedHttpClient()) ?: return@launch; + Logger.i(TAG, "Version retrieved $version"); + + withContext(Dispatchers.Main) { + UIDialogs.showChangelogDialog(it, version); + } + } catch (e: Throwable) { + Logger.e("Settings", "Failed to submit logs.", e); + } + } + }; + } + + @FormField( + "Remove Cached Version", FieldForm.BUTTON, + "Remove the last downloaded version", 5 + ) + fun removeCachedVersion() { + StateApp.withContext { + val outputDirectory = File(it.filesDir, "autoupdate"); + if (!outputDirectory.exists()) { + UIDialogs.toast("Directory does not exist"); + return@withContext; + } + + File(outputDirectory, "last_version.apk").delete(); + File(outputDirectory, "last_version.txt").delete(); + UIDialogs.toast("Removed downloaded version"); + } + } + } + + @FormField("Backup", FieldForm.GROUP, "", 13) + var backup = Backup(); + @Serializable + class Backup { + @Serializable(with = OffsetDateTimeSerializer::class) + var lastAutoBackupTime: OffsetDateTime = OffsetDateTime.MIN; + var didAskAutoBackup: Boolean = false; + var autoBackupPassword: String? = null; + fun shouldAutomaticBackup() = autoBackupPassword != null; + + @FormField("Automatic Backup", FieldForm.READONLYTEXT, "", 0) + val automaticBackupText get() = if(!shouldAutomaticBackup()) "None" else "Every Day"; + + @FormField("Set Automatic Backup", FieldForm.BUTTON, "Configure daily backup in case of catastrophic failure. (Written to the external Grayjay directory)", 1) + fun configureAutomaticBackup() { + UIDialogs.showAutomaticBackupDialog(SettingsActivity.getActivity()!!); + } + @FormField("Restore Automatic Backup", FieldForm.BUTTON, "Restore a previous automatic backup", 2) + fun restoreAutomaticBackup() { + val activity = SettingsActivity.getActivity()!! + + if(!StateBackup.hasAutomaticBackup()) + UIDialogs.toast(activity, "You don't have any automatic backups", false); + else + UIDialogs.showAutomaticRestoreDialog(activity, activity.lifecycleScope); + } + + + @FormField("Export Data", FieldForm.BUTTON, "Creates a zip file with your data which can be imported by opening it with Grayjay", 3) + fun export() { + StateBackup.startExternalBackup(); + } + } + + @FormField("Payment", FieldForm.GROUP, "", 14) + var payment = Payment(); + @Serializable + class Payment { + @FormField("Payment Status", FieldForm.READONLYTEXT, "", 1) + val paymentStatus: String get() = if (StatePayment.instance.hasPaid) "Paid" else "Not Paid"; + + @FormField("Clear Payment", FieldForm.BUTTON, "Deletes license keys from app", 2) + fun clearPayment() { + StatePayment.instance.clearLicenses(); + SettingsActivity.getActivity()?.let { + UIDialogs.toast(it, "Licenses cleared, might require app restart"); + } + } + } + + @FormField("Info", FieldForm.GROUP, "", 15) + var info = Info(); + @Serializable + class Info { + @FormField("Version Code", FieldForm.READONLYTEXT, "", 1, "code") + var versionCode = BuildConfig.VERSION_CODE; + @FormField("Version Name", FieldForm.READONLYTEXT, "", 2) + var versionName = BuildConfig.VERSION_NAME; + @FormField("Version Type", FieldForm.READONLYTEXT, "", 3) + var versionType = BuildConfig.BUILD_TYPE; + } + + //region BOILERPLATE + override fun encode(): String { + return Json.encodeToString(this); + } + + companion object { + private const val TAG = "Settings"; + + private var _isFirst = true; + + val instance: Settings get() { + if(_isFirst) { + Logger.i(TAG, "Initial Settings fetch"); + _isFirst = false; + } + return FragmentedStorage.get(); + } + + fun replace(text: String) { + FragmentedStorage.replace(text, true); + } + + + private fun preferedQualityToPixels(q: Int): Int { + when (q) { + 0 -> return 1280 * 720; + 1 -> return 3840 * 2160; + 2 -> return 2560 * 1440; + 3 -> return 1920 * 1080; + 4 -> return 1280 * 720; + 5 -> return 854 * 480; + 6 -> return 640 * 360; + 7 -> return 426 * 240; + 8 -> return 256 * 144; + else -> return 0; + } + } + + + private fun threadIndexToCount(index: Int): Int { + return when(index) { + 0 -> 1; + 1 -> 2; + 2 -> 4; + 3 -> 6; + 4 -> 8; + 5 -> 10; + 6 -> 15; + else -> 1 + } + } + } + //endregion +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/SettingsDev.kt b/app/src/main/java/com/futo/platformplayer/SettingsDev.kt new file mode 100644 index 00000000..826a9c0f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/SettingsDev.kt @@ -0,0 +1,334 @@ +package com.futo.platformplayer + +import android.content.Context +import android.webkit.CookieManager +import com.caoccao.javet.values.primitive.V8ValueInteger +import com.caoccao.javet.values.primitive.V8ValueString +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.platforms.js.JSClient +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.api.media.platforms.js.SourcePluginDescriptor +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.engine.V8Plugin +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.serializers.FlexibleBooleanSerializer +import com.futo.platformplayer.states.StateAnnouncement +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StateDeveloper +import com.futo.platformplayer.states.StateDownloads +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.FragmentedStorageFileJson +import com.futo.platformplayer.views.fields.FieldForm +import com.futo.platformplayer.views.fields.FormField +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import java.util.stream.IntStream.range +import kotlin.system.measureTimeMillis + +@Serializable() +class SettingsDev : FragmentedStorageFileJson() { + + @FormField("Developer Mode", FieldForm.TOGGLE, "", 0) + @Serializable(with = FlexibleBooleanSerializer::class) + var developerMode: Boolean = false; + + @FormField("Development Server", FieldForm.GROUP, + "Settings related to development server, be careful as it may open your phone to security vulnerabilities", 1) + val devServerSettings: DeveloperServerFields = DeveloperServerFields(); + @Serializable + class DeveloperServerFields { + + @FormField("Start Server on boot", FieldForm.TOGGLE, "", 0) + @Serializable(with = FlexibleBooleanSerializer::class) + var devServerOnBoot: Boolean = false; + + @FormField("Start Server", FieldForm.BUTTON, + "Starts a DevServer on port 11337, may expose vulnerabilities.", 1) + fun startServer() { + StateDeveloper.instance.runServer(); + StateApp.instance.contextOrNull?.let { + UIDialogs.toast(it, "Dev Started", false); + }; + } + } + + @FormField("Experimental", FieldForm.GROUP, + "Settings related to development server, be careful as it may open your phone to security vulnerabilities", 2) + val experimentalSettings: ExperimentalFields = ExperimentalFields(); + @Serializable + class ExperimentalFields { + + @FormField("Background Subscription Testing", FieldForm.TOGGLE, "", 0) + @Serializable(with = FlexibleBooleanSerializer::class) + var backgroundSubscriptionFetching: Boolean = false; + } + + @FormField("Crash Me", FieldForm.BUTTON, + "Crashes the application on purpose", 2) + fun crashMe() { + throw java.lang.IllegalStateException("This is an uncaught exception triggered on purpose!"); + } + + @FormField("Delete Announcements", FieldForm.BUTTON, + "Delete all announcements", 2) + fun deleteAnnouncements() { + StateAnnouncement.instance.deleteAllAnnouncements(); + } + + @FormField("Clear Cookies", FieldForm.BUTTON, + "Clear all cook from the CookieManager", 2) + fun clearCookies() { + val cookieManager: CookieManager = CookieManager.getInstance() + cookieManager.removeAllCookies(null); + } + + @Contextual + @Transient + @FormField("V8 Benchmarks", FieldForm.GROUP, + "Various benchmarks using the integrated V8 engine", 3) + val v8Benchmarks: V8Benchmarks = V8Benchmarks(); + class V8Benchmarks { + @FormField( + "Test V8 Creation speed", FieldForm.BUTTON, + "Tests V8 creation times and running", 1 + ) + fun testV8Creation() { + var plugin: V8Plugin? = null; + StateApp.instance.scopeOrNull!!.launch(Dispatchers.IO) { + try { + val count = 1000; + val timeStart = System.currentTimeMillis(); + for (i in range(0, count)) { + val v8 = V8Plugin( + StateApp.instance.context, + SourcePluginConfig("Test", "", "", "", "", ""), + "var i = 0; function test() { i = i + 1; return i; }" + ); + + v8.start(); + if (v8.executeTyped("test()").value != 1) + throw java.lang.IllegalStateException("Test didn't properly respond"); + v8.stop(); + } + val timeEnd = System.currentTimeMillis(); + val resp = "Restarted V8 ${count} times in ${(timeEnd - timeStart)}ms, ${(timeEnd - timeStart) / count}ms per instance\n(initializing, calling function with value, destroying)" + Logger.i("SettingsDev", resp); + + withContext(Dispatchers.Main) { + StateApp.instance.contextOrNull?.let { + UIDialogs.toast(it, resp); + }; + } + } catch (ex: Exception) { + withContext(Dispatchers.Main) { + StateApp.withContext { + UIDialogs.toast(it, "Failed: " + ex.message); + }; + } + } finally { + plugin?.stop(); + } + } + } + + @FormField( + "Test V8 Communication speed", FieldForm.BUTTON, + "Tests V8 communication speeds", 2 + ) + fun testV8RunSpeeds() { + var plugin: V8Plugin? = null; + StateApp.instance.scope.launch(Dispatchers.IO) { + try { + val count = 10000; + var str = "012346789012346789012346789012346789012346789"; + val v8 = V8Plugin( + StateApp.instance.context, + SourcePluginConfig("Test"), + "function test(str) { return str; }" + ); + v8.start(); + val timeStart = System.currentTimeMillis(); + for (i in range(0, count)) { + if (v8.executeTyped("test(\"" + str + "\")").value != str) + throw java.lang.IllegalStateException("Test didn't properly respond"); + } + val timeEnd = System.currentTimeMillis(); + v8.stop(); + + val resp = "Ran V8 ${count} times in ${(timeEnd - timeStart)}ms, ${(timeEnd - timeStart) / count}ms per instance\n(passing a string[50] back and forth)"; + Logger.i("SettingsDev", resp); + withContext(Dispatchers.Main) { + StateApp.withContext { + UIDialogs.toast(it, resp); + }; + } + } catch (ex: Exception) { + withContext(Dispatchers.Main) { + StateApp.withContext { + UIDialogs.toast(it, "Failed: " + ex.message); + }; + } + } finally { + plugin?.stop(); + } + } + } + } + + @Contextual + @Transient + @FormField("V8 Script Testing", FieldForm.GROUP, "Various tests against a custom source", 4) + val v8ScriptTests: V8ScriptTests = V8ScriptTests(); + class V8ScriptTests { + @Contextual + private var _currentPlugin : JSClient? = null; + @FormField("Inject", FieldForm.BUTTON, "Injects a test source config (local) into V8", 1) + fun testV8Init() { + StateApp.instance.scope.launch(Dispatchers.IO) { + try { + _currentPlugin = + getTestPlugin("http://192.168.1.132/Public/FUTO/TestConfig.json"); + + withContext(Dispatchers.Main) { + UIDialogs.toast(StateApp.instance.context, "TestPlugin injected"); + } + } + catch(ex: Exception) { + toast(ex.message ?: ""); + } + } + } + @FormField("getHome", FieldForm.BUTTON, "Attempts to fetch 2 pages from getHome", 2) + fun testV8Home() { + runTestPlugin(_currentPlugin) { + var home: IPager? = null; + var resultPage1: String = ""; + var resultPage2: String = ""; + val page1Time = measureTimeMillis { + home = it.getHome(); + val results = home!!.getResults(); + resultPage1 = "Page1 Results=[${results.size}] HasMore=${home!!.hasMorePages()}\nResult[0]=${results.firstOrNull()?.name}"; + } + toast(resultPage1); + val page2Time = measureTimeMillis { + home!!.nextPage(); + val results = home!!.getResults(); + resultPage2 = "Page2 Results=[${results.size}] HasMore=${home!!.hasMorePages()}\nResult[0]=${results.firstOrNull()?.name}"; + } + toast(resultPage2); + toast("Page1: ${page1Time}ms, Page2: ${page2Time}ms"); + } + } + + private fun toast(str: String, isLong: Boolean = false) { + StateApp.instance.scope.launch(Dispatchers.Main) { + try { + UIDialogs.toast(StateApp.instance.context, str, isLong); + } catch (e: Throwable) { + Logger.e("SettingsDev", "Failed to show toast", e) + } + } + } + private fun runTestPlugin(plugin: JSClient?, handler: (JSClient) -> Unit) { + StateApp.instance.scope.launch(Dispatchers.IO) { + try { + if (plugin == null) + throw IllegalStateException("Test plugin not loaded, inject first"); + else + handler(plugin); + } catch (ex: Exception) { + Logger.e("ScriptTesting", ex.message ?: "", ex); + toast("Failed: " + ex.message, true); + } + } + } + private fun getTestPlugin(configUrl: String) : JSClient { + val configResp = + ManagedHttpClient().get(configUrl); + if (!configResp.isOk || configResp.body == null) + throw IllegalStateException("Failed to load config"); + val config = Json.decodeFromString(configResp.body.string()); + + val scriptResp = ManagedHttpClient().get(config.absoluteScriptUrl); + if (!scriptResp.isOk || scriptResp.body == null) + throw IllegalStateException("Failed to load script"); + val script = scriptResp.body.string(); + + val client = JSClient(StateApp.instance.context, SourcePluginDescriptor(config), null, script); + client.initialize(); + + return client; + } + } + + + @Contextual + @Transient + @FormField("Other", FieldForm.GROUP, "Others...", 5) + val otherTests: OtherTests = OtherTests(); + class OtherTests { + @FormField("Clear Downloads", FieldForm.BUTTON, "Deletes all ongoing downloads", 1) + fun clearDownloads() { + StateDownloads.instance.getDownloading().forEach { + StateDownloads.instance.removeDownload(it); + }; + } + @FormField("Clear All Downloaded", FieldForm.BUTTON, "Deletes all downloaded videos and related files", 2) + fun clearDownloaded() { + StateDownloads.instance.getDownloadedVideos().forEach { + StateDownloads.instance.deleteCachedVideo(it.id); + }; + } + @FormField("Delete Unresolved", FieldForm.BUTTON, "Deletes all unresolved source files", 3) + fun cleanupDownloads() { + StateDownloads.instance.cleanupDownloads(); + } + + @FormField("Fill storage till error", FieldForm.BUTTON, "Writes to disk till no space is left", 4) + fun fillStorage(context: Context, scope: CoroutineScope?) { + val gigabuffer = ByteArray(1024 * 1024 * 128); + var count: Long = 0; + + UIDialogs.toast("Starting filling up space.."); + + scope?.launch(Dispatchers.IO) { + try { + do { + Logger.i("Developer", "Total: ${count}, Storage: ${(count * gigabuffer.size).toHumanBytesSize()}") + val tempFile = StateApp.instance.getTempFile(); + tempFile.writeBytes(gigabuffer); + count++; + + if(count % 50 == 0L) { + StateApp.instance.scopeOrNull?.launch (Dispatchers.Main) { + UIDialogs.toast(context, "Filled up ${(count * gigabuffer.size).toHumanBytesSize()}"); + } + } + } while (true); + } catch (ex: Throwable) { + withContext(Dispatchers.Main) { + UIDialogs.toast("Total: ${count}, Storage: ${(count * gigabuffer.size).toHumanBytesSize()}\nError: ${ex.message}"); + UIDialogs.showGeneralErrorDialog(context, ex.message ?: "", ex); + } + } + } + } + } + + //region BOILERPLATE + override fun encode(): String { + return Json.encodeToString(this); + } + + companion object { + val instance: SettingsDev get() { + return FragmentedStorage.get(); + } + } + //endregion +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/SignatureProvider.kt b/app/src/main/java/com/futo/platformplayer/SignatureProvider.kt new file mode 100644 index 00000000..0c0c257f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/SignatureProvider.kt @@ -0,0 +1,56 @@ +package com.futo.platformplayer + +import java.security.KeyFactory +import java.security.KeyPairGenerator +import java.security.Signature +import java.security.spec.PKCS8EncodedKeySpec +import java.security.spec.X509EncodedKeySpec +import java.util.Base64 + +data class KeyPair(val privateKey: String, val publicKey: String); + +class SignatureProvider { + companion object { + fun sign(text: String, privateKey: String): String { + val privateKeyBytes = Base64.getDecoder().decode(privateKey); + val keySpec = PKCS8EncodedKeySpec(privateKeyBytes); + val keyFactory = KeyFactory.getInstance("RSA"); + val privateKeyObject = keyFactory.generatePrivate(keySpec); + + val signature = Signature.getInstance("SHA512withRSA"); + signature.initSign(privateKeyObject); + signature.update(text.toByteArray()); + val signatureBytes = signature.sign(); + + return Base64.getEncoder().encodeToString(signatureBytes); + } + + fun verify(text: String, signature: String, publicKey: String): Boolean { + val publicKeyBytes = Base64.getDecoder().decode(publicKey); + val keySpec = X509EncodedKeySpec(publicKeyBytes); + val keyFactory = KeyFactory.getInstance("RSA"); + val publicKeyObject = keyFactory.generatePublic(keySpec); + + val signatureBytes = Base64.getDecoder().decode(signature); + val verifySignature = Signature.getInstance("SHA512withRSA"); + verifySignature.initVerify(publicKeyObject); + verifySignature.update(text.toByteArray()); + + return verifySignature.verify(signatureBytes); + } + + fun generateKeyPair(): KeyPair { + val keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + val keyPair = keyPairGenerator.generateKeyPair(); + + val privateKeyBytes = keyPair.private.encoded; + val privateKey = Base64.getEncoder().encodeToString(privateKeyBytes); + + val publicKeyBytes = keyPair.public.encoded; + val publicKey = Base64.getEncoder().encodeToString(publicKeyBytes); + + return KeyPair(privateKey, publicKey); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/UIDialogs.kt b/app/src/main/java/com/futo/platformplayer/UIDialogs.kt new file mode 100644 index 00000000..0015cafb --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/UIDialogs.kt @@ -0,0 +1,356 @@ +package com.futo.platformplayer + +import android.app.AlertDialog +import android.content.Context +import android.graphics.Color +import android.util.TypedValue +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.widget.* +import androidx.core.content.ContextCompat +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.casting.StateCasting +import com.futo.platformplayer.dialogs.* +import com.futo.platformplayer.engine.exceptions.PluginException +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.stores.v2.ManagedStore +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import userpackage.Protocol +import java.io.File + +class UIDialogs { + companion object { + private val TAG = "Dialogs" + + private val _openDialogs = arrayListOf(); + + private fun registerDialogOpened(dialog: AlertDialog) { + _openDialogs.add(dialog); + } + + private fun registerDialogClosed(dialog: AlertDialog) { + _openDialogs.remove(dialog); + } + + fun dismissAllDialogs() { + for (openDialog in _openDialogs) { + openDialog.dismiss(); + } + + _openDialogs.clear(); + } + + fun showDialogProgress(context: Context, handler: ((ProgressDialog)->Unit)) { + val dialog = ProgressDialog(context, handler); + registerDialogOpened(dialog); + dialog.setOnDismissListener { registerDialogClosed(dialog) }; + dialog.show(); + } + + fun showDialogOk(context: Context, icon: Int, text: String, handler: (()->Unit)? = null) { + showDialog(context, icon, text, null, null, 0, Action("Ok", { handler?.invoke(); }, ActionStyle.PRIMARY)); + } + + fun multiShowDialog(context: Context, finally: (() -> Unit)?, vararg dialogDescriptor: Descriptor?) = multiShowDialog(context, dialogDescriptor.toList(), finally); + fun multiShowDialog(context: Context, vararg dialogDescriptor: Descriptor?) = multiShowDialog(context, dialogDescriptor.toList()); + fun multiShowDialog(context: Context, dialogDescriptor: List, finally: (()->Unit)? = null) { + if(dialogDescriptor.isEmpty()) { + if (finally != null) { + finally() + }; + return; + } + if(dialogDescriptor[0] == null) { + multiShowDialog(context, dialogDescriptor.drop(1), finally); + return; + } + val currentDialog = dialogDescriptor[0]!!; + if(!currentDialog.shouldShow()) { + multiShowDialog(context, dialogDescriptor.drop(1), finally); + return; + } + + showDialog(context, + currentDialog.icon, + currentDialog.text, + currentDialog.textDetails, + currentDialog.code, + currentDialog.defaultCloseAction, + *currentDialog.actions.map { + return@map Action(it.text, { + it.action(); + multiShowDialog(context, dialogDescriptor.drop(1), finally); + }, it.style); + }.toTypedArray()); + } + + + fun showAutomaticBackupDialog(context: Context) { + val dialog = AutomaticBackupDialog(context); + registerDialogOpened(dialog); + dialog.setOnDismissListener { registerDialogClosed(dialog) }; + dialog.show(); + } + fun showAutomaticRestoreDialog(context: Context, scope: CoroutineScope) { + val dialog = AutomaticRestoreDialog(context, scope); + registerDialogOpened(dialog); + dialog.setOnDismissListener { registerDialogClosed(dialog) }; + dialog.show(); + } + + fun showDialog(context: Context, icon: Int, text: String, textDetails: String? = null, code: String? = null, defaultCloseAction: Int, vararg actions: Action) { + val builder = AlertDialog.Builder(context); + val view = LayoutInflater.from(context).inflate(R.layout.dialog_multi_button, null); + builder.setView(view); + + val dialog = builder.create(); + registerDialogOpened(dialog); + + view.findViewById(R.id.dialog_icon).apply { + this.setImageResource(icon); + } + view.findViewById(R.id.dialog_text).apply { + this.text = text; + }; + view.findViewById(R.id.dialog_text_details).apply { + if(textDetails == null) + this.visibility = View.GONE; + else + this.text = textDetails; + }; + view.findViewById(R.id.dialog_text_code).apply { + if(code == null) + this.visibility = View.GONE; + else + this.text = code; + }; + view.findViewById(R.id.dialog_buttons).apply { + val buttons = actions.map { act -> + val buttonView = TextView(context); + val dp10 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, resources.displayMetrics).toInt(); + val dp28 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 28f, resources.displayMetrics).toInt(); + val dp14 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14.0f, resources.displayMetrics); + buttonView.layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { + if(actions.size > 1) + this.marginEnd = dp28; + }; + buttonView.setTextColor(Color.WHITE); + buttonView.textSize = 14f; + buttonView.typeface = resources.getFont(R.font.inter_regular); + buttonView.text = act.text; + buttonView.setOnClickListener { act.action(); dialog.dismiss(); }; + when(act.style) { + ActionStyle.PRIMARY -> buttonView.setBackgroundResource(R.drawable.background_button_primary); + ActionStyle.ACCENT -> buttonView.setBackgroundResource(R.drawable.background_button_accent); + ActionStyle.DANGEROUS -> buttonView.setBackgroundResource(R.drawable.background_button_pred); + ActionStyle.DANGEROUS_TEXT -> buttonView.setTextColor(ContextCompat.getColor(context, R.color.pastel_red)) + else -> buttonView.setTextColor(ContextCompat.getColor(context, R.color.colorPrimary)) + } + if(act.style != ActionStyle.NONE && act.style != ActionStyle.DANGEROUS_TEXT) + buttonView.setPadding(dp28, dp10, dp28, dp10); + else + buttonView.setPadding(dp10, dp10, dp10, dp10); + + return@map buttonView; + }; + if(actions.size <= 1) + this.gravity = Gravity.CENTER; + else + this.gravity = Gravity.END; + for(button in buttons) + this.addView(button); + }; + dialog.setOnCancelListener { + if(defaultCloseAction >= 0 && defaultCloseAction < actions.size) + actions[defaultCloseAction].action(); + } + dialog.setOnDismissListener { + registerDialogClosed(dialog); + } + dialog.show(); + } + + fun showGeneralErrorDialog(context: Context, msg: String, ex: Throwable? = null, button: String = "Ok", onOk: (()->Unit)? = null) { + showDialog(context, + R.drawable.ic_error_pred, + msg, (if(ex != null ) "${ex.message}" else ""), if(ex is PluginException) ex.code else null, + 0, + UIDialogs.Action(button, { + onOk?.invoke(); + }, UIDialogs.ActionStyle.PRIMARY) + ); + } + fun showGeneralRetryErrorDialog(context: Context, msg: String, ex: Throwable? = null, retryAction: (() -> Unit)? = null, closeAction: (() -> Unit)? = null) { + val pluginInfo = if(ex is PluginException) + "\nPlugin [${ex.config.name}]" else ""; + showDialog(context, + R.drawable.ic_error_pred, + "${msg}${pluginInfo}", (if(ex != null ) "${ex.message}" else ""), if(ex is PluginException) ex.code else null, + 0, + UIDialogs.Action("Retry", { + retryAction?.invoke(); + }, UIDialogs.ActionStyle.PRIMARY), + UIDialogs.Action("Close", { + closeAction?.invoke() + }, UIDialogs.ActionStyle.NONE) + ); + } + + fun showSingleButtonDialog(context: Context, icon: Int, text: String, buttonText: String, action: (() -> Unit)) { + val singleButtonAction = Action(buttonText, action) + showDialog(context, icon, text, null, null, -1, singleButtonAction) + } + + fun showDataRetryDialog(context: Context, reason: String? = null, retryAction: (() -> Unit)? = null, closeAction: (() -> Unit)? = null) { + val retryButtonAction = Action("Retry", retryAction ?: {}, ActionStyle.PRIMARY) + val closeButtonAction = Action("Close", closeAction ?: {}, ActionStyle.ACCENT) + showDialog(context, R.drawable.ic_no_internet_86dp, "Data Retry", reason, null, 0, retryButtonAction, closeButtonAction) + } + + + fun showConfirmationDialog(context: Context, text: String, action: () -> Unit, cancelAction: (() -> Unit)? = null) { + val confirmButtonAction = Action("Confirm", action, ActionStyle.PRIMARY) + val cancelButtonAction = Action("Cancel", cancelAction ?: {}, ActionStyle.ACCENT) + showDialog(context, R.drawable.ic_error, text, null, null, 1, cancelButtonAction, confirmButtonAction) + } + + fun showUpdateAvailableDialog(context: Context, lastVersion: Int) { + val dialog = AutoUpdateDialog(context); + registerDialogOpened(dialog); + dialog.setOnDismissListener { registerDialogClosed(dialog) }; + dialog.show(); + dialog.setMaxVersion(lastVersion); + } + + fun showChangelogDialog(context: Context, lastVersion: Int) { + val dialog = ChangelogDialog(context); + registerDialogOpened(dialog); + dialog.setOnDismissListener { registerDialogClosed(dialog) }; + dialog.show(); + dialog.setMaxVersion(lastVersion); + } + + fun showInstallDownloadedUpdateDialog(context: Context, apkFile: File) { + val dialog = AutoUpdateDialog(context); + registerDialogOpened(dialog); + dialog.setOnDismissListener { registerDialogClosed(dialog) }; + dialog.showPredownloaded(apkFile); + } + + fun showMigrateDialog(context: Context, store: ManagedStore<*>, onConcluded: ()->Unit) { + if(!store.hasMissingReconstructions()) + onConcluded(); + else + { + val dialog = MigrateDialog(context, store, onConcluded); + registerDialogOpened(dialog); + dialog.setOnDismissListener { registerDialogClosed(dialog) }; + dialog.show(); + } + } + + fun showImportDialog(context: Context, store: ManagedStore<*>, name: String, reconstructions: List, onConcluded: () -> Unit) { + val dialog = ImportDialog(context, store, name, reconstructions, onConcluded); + registerDialogOpened(dialog); + dialog.setOnDismissListener { registerDialogClosed(dialog) }; + dialog.show(); + } + + + fun showCastingDialog(context: Context) { + val d = StateCasting.instance.activeDevice; + if (d != null) { + val dialog = ConnectedCastingDialog(context); + registerDialogOpened(dialog); + dialog.setOnDismissListener { registerDialogClosed(dialog) }; + dialog.show(); + } else { + val dialog = ConnectCastingDialog(context); + registerDialogOpened(dialog); + dialog.setOnDismissListener { registerDialogClosed(dialog) }; + dialog.show(); + } + } + + fun showCastingAddDialog(context: Context) { + val dialog = CastingAddDialog(context); + registerDialogOpened(dialog); + dialog.setOnDismissListener { registerDialogClosed(dialog) }; + dialog.show(); + } + + fun toast(context : Context, text : String, long : Boolean = false) { + Toast.makeText(context, text, if(long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT).show(); + } + fun toast(text : String, long : Boolean = false) { + StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { + try { + StateApp.withContext { + Toast.makeText(it, text, if (long) Toast.LENGTH_LONG else Toast.LENGTH_SHORT).show(); + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to show toast.", e); + } + } + } + + fun showClickableToast(context: Context, text: String, onClick: () -> Unit, isLongDuration: Boolean = false) { + //TODO: Is not actually clickable... + val toastDuration = if (isLongDuration) Toast.LENGTH_LONG else Toast.LENGTH_SHORT + val toast = Toast(context) + + val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater + val customView = inflater.inflate(R.layout.toast_clickable, null) + val toastTextView: TextView = customView.findViewById(R.id.toast_text) + toastTextView.text = text + customView.setOnClickListener { + onClick() + } + + toast.view = customView + toast.duration = toastDuration + toast.show() + } + + fun showCommentDialog(context: Context, contextUrl: String, ref: Protocol.Reference, onCommentAdded: (comment: IPlatformComment) -> Unit) { + val dialog = CommentDialog(context, contextUrl, ref); + registerDialogOpened(dialog); + dialog.setOnDismissListener { registerDialogClosed(dialog) }; + dialog.onCommentAdded.subscribe { onCommentAdded(it); }; + dialog.show() + } + } + + class Descriptor(val icon: Int, val text: String, val textDetails: String? = null, val code: String? = null, val defaultCloseAction: Int, vararg acts: Action) { + var shouldShow: ()->Boolean = {true}; + val actions: List = acts.toList(); + + fun withCondition(shouldShow: () -> Boolean): Descriptor { + this.shouldShow = shouldShow; + return this; + } + } + class Action { + val text: String; + val action: ()->Unit; + val style: ActionStyle; + + constructor(text: String, action: ()->Unit, style: ActionStyle = ActionStyle.NONE) { + this.text = text; + this.action = action; + this.style = style; + } + } + enum class ActionStyle { + NONE, + PRIMARY, + ACCENT, + DANGEROUS, + DANGEROUS_TEXT + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt new file mode 100644 index 00000000..77b537a4 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt @@ -0,0 +1,419 @@ +package com.futo.platformplayer + +import android.content.ContentResolver +import android.view.View +import android.view.ViewGroup +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.api.http.server.handlers.HttpConstantHandler +import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor +import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource +import com.futo.platformplayer.api.media.models.streams.sources.SubtitleRawSource +import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails +import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo +import com.futo.platformplayer.downloads.VideoLocal +import com.futo.platformplayer.helpers.VideoHelper +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.Playlist +import com.futo.platformplayer.states.* +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuGroup +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay +import com.futo.platformplayer.views.pills.RoundButton +import com.futo.platformplayer.views.pills.RoundButtonGroup +import com.futo.platformplayer.views.overlays.slideup.* +import com.futo.platformplayer.views.video.FutoVideoPlayerBase +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File + +class UISlideOverlays { + companion object { + private const val TAG = "UISlideOverlays"; + + fun showOverlay(container: ViewGroup, title: String, okButton: String?, onOk: ()->Unit, vararg views: View) { + var menu = SlideUpMenuOverlay(container.context, container, title, okButton, true, *views); + + menu.onOK.subscribe { + menu.hide(); + onOk.invoke(); + }; + menu.show(); + } + + fun showDownloadVideoOverlay(contentResolver: ContentResolver, video: IPlatformVideoDetails, container: ViewGroup): SlideUpMenuOverlay? { + val items = arrayListOf(); + var menu: SlideUpMenuOverlay? = null; + + var descriptor = video.video; + if(video is VideoLocal) + descriptor = video.videoSerialized.video; + + + val requiresAudio = descriptor is VideoUnMuxedSourceDescriptor; + var selectedVideo: IVideoUrlSource? = null; + var selectedAudio: IAudioUrlSource? = null; + var selectedSubtitle: ISubtitleSource? = null; + + val videoSources = descriptor.videoSources; + val audioSources = if(descriptor is VideoUnMuxedSourceDescriptor) descriptor.audioSources else null; + val subtitleSources = video.subtitles; + + if(videoSources.size == 0 && (audioSources?.size ?: 0) == 0) { + UIDialogs.toast("No downloads available", false); + return null; + } + + items.add(SlideUpMenuGroup(container.context, "Video", videoSources, + listOf(listOf(SlideUpMenuItem(container.context, R.drawable.ic_movie, "None", "Audio Only", "none", { + selectedVideo = null; + menu?.selectOption(videoSources, "none"); + if(selectedAudio != null || !requiresAudio) + menu?.setOk("Download"); + }, false)) + + videoSources + .filter { it is IVideoUrlSource } + .map { + SlideUpMenuItem(container.context, R.drawable.ic_movie, it.name, "${it.width}x${it.height}", it, { + selectedVideo = it as IVideoUrlSource; + menu?.selectOption(videoSources, it); + if(selectedAudio != null || !requiresAudio) + menu?.setOk("Download"); + }, false) + }).flatten().toList() + )); + + if(Settings.instance.downloads.getDefaultVideoQualityPixels() > 0 && videoSources.size > 0) + selectedVideo = VideoHelper.selectBestVideoSource(videoSources.filter { it is IVideoUrlSource }.asIterable(), + Settings.instance.downloads.getDefaultVideoQualityPixels(), + FutoVideoPlayerBase.PREFERED_VIDEO_CONTAINERS) as IVideoUrlSource; + + + audioSources?.let { audioSources -> + items.add(SlideUpMenuGroup(container.context, "Audio", audioSources, audioSources + .filter { it is IAudioUrlSource } + .map { + SlideUpMenuItem(container.context, R.drawable.ic_music, it.name, "${it.bitrate}", it, { + selectedAudio = it as IAudioUrlSource; + menu?.selectOption(audioSources, it); + menu?.setOk("Download"); + }, false); + })); + val asources = audioSources; + val preferredAudioSource = VideoHelper.selectBestAudioSource(asources.asIterable(), + FutoVideoPlayerBase.PREFERED_AUDIO_CONTAINERS, + Settings.instance.playback.getPrimaryLanguage(container.context), + if(Settings.instance.downloads.isHighBitrateDefault()) 99999999 else 1); + menu?.selectOption(asources, preferredAudioSource); + + + selectedAudio = VideoHelper.selectBestAudioSource(audioSources.filter { it is IAudioUrlSource }.asIterable(), + FutoVideoPlayerBase.PREFERED_AUDIO_CONTAINERS, + Settings.instance.playback.getPrimaryLanguage(container.context), + if(Settings.instance.downloads.isHighBitrateDefault()) 9999999 else 1) as IAudioUrlSource?; + } + + items.add(SlideUpMenuGroup(container.context, "Subtitles", subtitleSources, subtitleSources + .map { + SlideUpMenuItem(container.context, R.drawable.ic_edit, it.name, "", it, { + if (selectedSubtitle == it) { + selectedSubtitle = null; + menu?.selectOption(subtitleSources, null); + } else { + selectedSubtitle = it; + menu?.selectOption(subtitleSources, it); + } + }, false); + })); + + menu = SlideUpMenuOverlay(container.context, container, "Download Video", null, true, items); + + if(selectedVideo != null) { + menu.selectOption(videoSources, selectedVideo); + } + if(selectedAudio != null) { + audioSources?.let { audioSources -> menu.selectOption(audioSources, selectedAudio); }; + } + if(selectedAudio != null || (!requiresAudio && selectedVideo != null)) { + menu.setOk("Download"); + } + + menu.onOK.subscribe { + menu.hide(); + val subtitleToDownload = selectedSubtitle; + if(selectedAudio != null || !requiresAudio) { + if (subtitleToDownload == null) { + StateDownloads.instance.download(video, selectedVideo, selectedAudio, null); + } else { + //TODO: Clean this up somewhere else, maybe pre-fetch instead of dup calls + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { + try { + val subtitleUri = subtitleToDownload.getSubtitlesURI(); + if (subtitleUri != null) { + var subtitles: String? = null; + if ("file" == subtitleUri.scheme) { + val inputStream = contentResolver.openInputStream(subtitleUri); + inputStream?.use { stream -> + val reader = stream.bufferedReader(); + subtitles = reader.use { it.readText() }; + } + } else if ("http" == subtitleUri.scheme || "https" == subtitleUri.scheme) { + val client = ManagedHttpClient(); + val subtitleResponse = client.get(subtitleUri.toString()); + if (!subtitleResponse.isOk) { + throw Exception("Cannot fetch subtitles from source '${subtitleUri}': ${subtitleResponse.code}"); + } + + subtitles = subtitleResponse.body?.toString() + ?: throw Exception("Subtitles are invalid '${subtitleUri}': ${subtitleResponse.code}"); + } else { + throw Exception("Unsuported scheme"); + } + + withContext(Dispatchers.Main) { + StateDownloads.instance.download(video, selectedVideo, selectedAudio, if (subtitles != null) SubtitleRawSource(subtitleToDownload.name, subtitleToDownload.format, subtitles!!) else null); + } + } else { + withContext(Dispatchers.Main) { + StateDownloads.instance.download(video, selectedVideo, selectedAudio, null); + } + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed download subtitles.", e); + } + } + } + } + }; + return menu.apply { show() }; + } + fun showDownloadVideoOverlay(video: IPlatformVideo, container: ViewGroup) { + showUnknownVideoDownload("Video", container) { px, bitrate -> + StateDownloads.instance.download(video, px, bitrate) + }; + } + fun showDownloadPlaylistOverlay(playlist: Playlist, container: ViewGroup) { + showUnknownVideoDownload("Video", container) { px, bitrate -> + StateDownloads.instance.download(playlist, px, bitrate); + }; + } + private fun showUnknownVideoDownload(toDownload: String, container: ViewGroup, cb: (Long?, Long?)->Unit) { + val items = arrayListOf(); + var menu: SlideUpMenuOverlay? = null; + + var targetPxSize: Long = 0; + var targetBitrate: Long = 0; + + val resolutions = listOf( + Triple("None", "None", -1), + Triple("480P", "720x480", 720*480), + Triple("720P", "1280x720", 1280*720), + Triple("1080P", "1920x1080", 1920*1080), + Triple("1440P", "2560x1440", 2560*1440), + Triple("2160P", "3840x2160", 3840*2160) + ); + + items.add(SlideUpMenuGroup(container.context, "Target Resolution", "Video", resolutions.map { + SlideUpMenuItem(container.context, R.drawable.ic_movie, it.first, it.second, it.third, { + targetPxSize = it.third; + menu?.selectOption("Video", it.third); + }, false) + })); + + items.add(SlideUpMenuGroup(container.context, "Target Bitrate", "Bitrate", listOf( + SlideUpMenuItem(container.context, R.drawable.ic_movie, "Low Bitrate", "", 1, { + targetBitrate = 1; + menu?.selectOption("Bitrate", 1); + menu?.setOk("Download"); + }, false), + SlideUpMenuItem(container.context, R.drawable.ic_movie, "High Bitrate", "", 9999999, { + targetBitrate = 9999999; + menu?.selectOption("Bitrate", 9999999); + menu?.setOk("Download"); + }, false) + ))); + + + menu = SlideUpMenuOverlay(container.context, container, "Download " + toDownload, null, true, items); + + if(Settings.instance.downloads.getDefaultVideoQualityPixels() != 0) { + val defTarget = Settings.instance.downloads.getDefaultVideoQualityPixels(); + if(defTarget == -1) { + targetPxSize = -1; + menu.selectOption("Video", (-1).toLong()); + } + else { + targetPxSize = resolutions.drop(1).minBy { Math.abs(defTarget - it.third) }.third; + menu.selectOption("Video", targetPxSize); + } + } + if(Settings.instance.downloads.isHighBitrateDefault()) { + targetBitrate = 9999999; + menu.selectOption("Bitrate", 9999999); + menu.setOk("Download"); + } + else { + targetBitrate = 1; + menu.selectOption("Bitrate", 1); + menu.setOk("Download"); + } + + menu.onOK.subscribe { + menu.hide(); + cb(if(targetPxSize > 0) targetPxSize else null, if(targetBitrate > 0) targetBitrate else null); + }; + menu.show(); + } + + fun showVideoOptionsOverlay(video: IPlatformVideo, container: ViewGroup, onVideoHidden: (()->Unit)? = null): SlideUpMenuOverlay { + val items = arrayListOf(); + val lastUpdated = StatePlaylists.instance.getLastUpdatedPlaylist(); + + if (lastUpdated != null) { + items.add( + SlideUpMenuGroup(container.context, "Recently Used Playlist", "recentlyusedplaylist", + SlideUpMenuItem(container.context, R.drawable.ic_playlist_add, lastUpdated.name, "${lastUpdated.videos.size} videos", "", + { + StatePlaylists.instance.addToPlaylist(lastUpdated.id, video); + StateDownloads.instance.checkForOutdatedPlaylists(); + })) + ); + } + + val allPlaylists = StatePlaylists.instance.getPlaylists(); + val queue = StatePlayer.instance.getQueue(); + val watchLater = StatePlaylists.instance.getWatchLater(); + items.add(SlideUpMenuGroup(container.context, "Actions", "actions", + SlideUpMenuItem(container.context, R.drawable.ic_visibility_off, "Hide", "Hide from Home", "hide", + { StateMeta.instance.addHiddenVideo(video.url); onVideoHidden?.invoke() }), + SlideUpMenuItem(container.context, R.drawable.ic_download, "Download", "Download the video", "download", + { showDownloadVideoOverlay(video, container); }, false) + )) + items.add( + SlideUpMenuGroup(container.context, "Add To", "addto", + SlideUpMenuItem(container.context, R.drawable.ic_queue_add, "Add to Queue", "${queue.size} videos", "queue", + { StatePlayer.instance.addToQueue(video); }), + SlideUpMenuItem(container.context, R.drawable.ic_watchlist_add, "Add to " + StatePlayer.TYPE_WATCHLATER + "", "${watchLater.size} videos", "watch later", + { StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video)); }) + )); + + val playlistItems = arrayListOf(); + for (playlist in allPlaylists) { + playlistItems.add(SlideUpMenuItem(container.context, R.drawable.ic_playlist_add, "Add to " + playlist.name + "", "${playlist.videos.size} videos", "", + { + StatePlaylists.instance.addToPlaylist(playlist.id, video); + StateDownloads.instance.checkForOutdatedPlaylists(); + })); + } + + if(playlistItems.size > 0) + items.add(SlideUpMenuGroup(container.context, "Playlists", "", playlistItems)); + + return SlideUpMenuOverlay(container.context, container, "Video Options", null, true, items).apply { show() }; + } + + + fun showAddToOverlay(video: IPlatformVideo, container: ViewGroup): SlideUpMenuOverlay { + + val items = arrayListOf(); + + val lastUpdated = StatePlaylists.instance.getLastUpdatedPlaylist(); + + if (lastUpdated != null) { + items.add( + SlideUpMenuGroup(container.context, "Recently Used Playlist", "recentlyusedplaylist", + SlideUpMenuItem(container.context, R.drawable.ic_playlist_add, lastUpdated.name, "${lastUpdated.videos.size} videos", "", + { + StatePlaylists.instance.addToPlaylist(lastUpdated.id, video); + StateDownloads.instance.checkForOutdatedPlaylists(); + })) + ); + } + + val allPlaylists = StatePlaylists.instance.getPlaylists().sortedByDescending { maxOf(it.datePlayed, it.dateUpdate, it.dateCreation) }; + val queue = StatePlayer.instance.getQueue(); + val watchLater = StatePlaylists.instance.getWatchLater(); + items.add( + SlideUpMenuGroup(container.context, "Other", "other", + SlideUpMenuItem(container.context, R.drawable.ic_queue_add, "Queue", "${queue.size} videos", "queue", + { StatePlayer.instance.addToQueue(video); }), + SlideUpMenuItem(container.context, R.drawable.ic_watchlist_add, StatePlayer.TYPE_WATCHLATER, "${watchLater.size} videos", "watch later", + { StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video)); }), + SlideUpMenuItem(container.context, R.drawable.ic_download, "Download", "Download the video", "download", + { showDownloadVideoOverlay(video, container); }, false)) + ); + + val playlistItems = arrayListOf(); + for (playlist in allPlaylists) { + playlistItems.add(SlideUpMenuItem(container.context, R.drawable.ic_playlist_add, playlist.name, "${playlist.videos.size} videos", "", + { + StatePlaylists.instance.addToPlaylist(playlist.id, video); + StateDownloads.instance.checkForOutdatedPlaylists(); + })); + } + + if(playlistItems.size > 0) + items.add(SlideUpMenuGroup(container.context, "Playlists", "", playlistItems)); + + return SlideUpMenuOverlay(container.context, container, "Add to", null, true, items).apply { show() }; + } + + fun showFiltersOverlay(lifecycleScope: CoroutineScope, container: ViewGroup, enabledClientsIds: List, filterValues: HashMap>): SlideUpMenuFilters { + val overlay = SlideUpMenuFilters(lifecycleScope, container, enabledClientsIds, filterValues); + overlay.show(); + return overlay; + } + + + fun showMoreButtonOverlay(container: ViewGroup, buttonGroup: RoundButtonGroup, ignoreTags: List = listOf(), onPinnedbuttons: ((List)->Unit)? = null): SlideUpMenuOverlay { + val visible = buttonGroup.getVisibleButtons().filter { !ignoreTags.contains(it.tagRef) }; + val hidden = buttonGroup.getInvisibleButtons().filter { !ignoreTags.contains(it.tagRef) }; + + val views = arrayOf(hidden + .map { btn -> SlideUpMenuItem(container.context, btn.iconResource, btn.text.text.toString(), "", "", { + btn.handler?.invoke(btn); + }, true) as View }.toTypedArray() ?: arrayOf(), + arrayOf(SlideUpMenuItem(container.context, R.drawable.ic_pin, "Change Pins", "Decide which buttons should be pinned", "", { + showOrderOverlay(container, "Select your pins in order", (visible + hidden).map { Pair(it.text.text.toString(), it.tagRef!!) }) { + val selected = it + .map { x -> visible.find { it.tagRef == x } ?: hidden.find { it.tagRef == x } } + .filter { it != null } + .map { it!! } + .toList(); + + onPinnedbuttons?.invoke(selected + (visible + hidden).filter { !selected.contains(it) }); + } + }, false)) + ).flatten().toTypedArray(); + + return SlideUpMenuOverlay(container.context, container, "More Options", null, true, *views).apply { show() }; + } + + fun showOrderOverlay(container: ViewGroup, title: String, options: List>, onOrdered: (List)->Unit) { + val selection: MutableList = mutableListOf(); + + var overlay: SlideUpMenuOverlay? = null; + + overlay = SlideUpMenuOverlay(container.context, container, title, "Save", true, + options.map { SlideUpMenuItem(container.context, R.drawable.ic_move_up, it.first, "", it.second, { + if(overlay!!.selectOption(null, it.second, true, true)) { + if(!selection.contains(it.second)) + selection.add(it.second); + } + else + selection.remove(it.second); + }, false) + }); + overlay.onOK.subscribe { + onOrdered.invoke(selection); + overlay.hide(); + }; + + overlay.show(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/Utility.kt b/app/src/main/java/com/futo/platformplayer/Utility.kt new file mode 100644 index 00000000..5357250d --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/Utility.kt @@ -0,0 +1,159 @@ +package com.futo.platformplayer + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.content.res.Resources +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.os.Build +import android.os.Looper +import android.os.OperationCanceledException +import android.util.DisplayMetrics +import android.util.TypedValue +import android.view.Display +import android.view.View +import android.view.WindowInsetsController +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.content.FileProvider +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.PlatformVideoWithTime +import com.futo.platformplayer.others.PlatformLinkMovementMethod +import com.futo.platformplayer.states.StatePlatform +import org.json.JSONArray +import org.json.JSONObject +import userpackage.Protocol +import java.io.File +import java.io.InputStream +import java.io.OutputStream +import java.util.* +import java.util.concurrent.ThreadLocalRandom +import kotlin.math.abs +import kotlin.math.min + +private val _allowedCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz "; +fun getRandomString(sizeOfRandomString: Int): String { + val random = Random(); + val sb = StringBuilder(sizeOfRandomString); + for (i in 0 until sizeOfRandomString) + sb.append(_allowedCharacters[random.nextInt(_allowedCharacters.length)]); + return sb.toString() +} + +fun getRandomStringRandomLength(minLength: Int, maxLength: Int): String { + if (maxLength == minLength) + return getRandomString(minLength); + return getRandomString(ThreadLocalRandom.current().nextInt(minLength, maxLength)); +} + +fun findNonRuntimeException(ex: Throwable?): Throwable? { + if(ex == null) + return null; + if(ex is java.lang.RuntimeException) + return findNonRuntimeException(ex.cause) + else + return ex; +} + +fun ensureNotMainThread() { + if (Looper.myLooper() == Looper.getMainLooper()) { + Logger.e("Utility", "Throwing exception because a function that should not be called on main thread, is called on main thread") + throw IllegalStateException("Cannot run on main thread") + } +} + +private val _regexHexColor = Regex("(#[a-fA-F0-9]{8})|(#[a-fA-F0-9]{6})|(#[a-fA-F0-9]{3})"); +fun String.isHexColor(): Boolean { + return _regexHexColor.matches(this); +} + +fun IPlatformVideo.withTimestamp(sec: Long) = PlatformVideoWithTime(this, sec); + + +fun loadBitmap(url: String): Bitmap { + try { + val client = ManagedHttpClient(); + val response = client.get(url); + if (response.isOk && response.body != null) { + val bitmapStream = response.body.byteStream(); + val bitmap = BitmapFactory.decodeStream(bitmapStream); + return bitmap; + } else { + throw Exception("Failed to find data at URL."); + } + } catch (e: Throwable) { + Logger.w("Utility", "Exception thrown while downloading bitmap.", e); + throw e; + } +} + +fun TextView.setPlatformPlayerLinkMovementMethod(context: Context) { + this.movementMethod = PlatformLinkMovementMethod(context); +} + +fun InputStream.copyToOutputStream(outputStream: OutputStream, isCancelled: (() -> Boolean)? = null) { + val buffer = ByteArray(16384); + var n: Int; + var total = 0; + + while (read(buffer).also { n = it } >= 0) { + if (isCancelled != null && isCancelled()) { + throw OperationCanceledException("Copy stream was cancelled."); + } + + total += n; + outputStream.write(buffer, 0, n); + } +} + +fun InputStream.copyToOutputStream(inputStreamLength: Long, outputStream: OutputStream, onProgress: (Float) -> Unit) { + val buffer = ByteArray(16384); + var n: Int; + var total = 0; + val inputStreamLengthFloat = inputStreamLength.toFloat(); + + while (read(buffer).also { n = it } >= 0) { + total += n; + outputStream.write(buffer, 0, n); + onProgress.invoke(total.toFloat() / inputStreamLengthFloat); + } +} + +fun Activity.setNavigationBarColorAndIcons() { + window.navigationBarColor = ContextCompat.getColor(this, android.R.color.black); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + window.insetsController?.setSystemBarsAppearance(0, WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS); + } else { + val decorView = window.decorView; + var systemUiVisibility = decorView.systemUiVisibility; + systemUiVisibility = systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv(); + decorView.systemUiVisibility = systemUiVisibility; + } +} + +fun Int.dp(resources: Resources): Int { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), resources.displayMetrics).toInt() +} + +fun Int.sp(resources: Resources): Int { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, this.toFloat(), resources.displayMetrics).toInt() +} + +fun File.share(context: Context) { + val uri = FileProvider.getUriForFile(context, context.resources.getString(R.string.authority), this); + + val shareIntent = Intent(); + shareIntent.action = Intent.ACTION_SEND; + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + shareIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); + shareIntent.setDataAndType(uri, context.contentResolver.getType(uri)); + shareIntent.putExtra(Intent.EXTRA_STREAM, uri); + + val chooserIntent = Intent.createChooser(shareIntent, "Share"); + chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(chooserIntent); +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt new file mode 100644 index 00000000..e63a9866 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt @@ -0,0 +1,221 @@ +package com.futo.platformplayer.activities + +import android.content.Intent +import android.graphics.drawable.Animatable +import android.os.Bundle +import android.view.View +import android.widget.* +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import com.futo.platformplayer.* +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StatePlugins +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.views.sources.SourceHeaderView +import com.futo.platformplayer.views.sources.SourceInfoView +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.serialization.SerializationException + +class AddSourceActivity : AppCompatActivity() { + private val TAG = "AddSourceActivity"; + + private lateinit var _buttonBack: ImageButton; + + private lateinit var _sourceHeader: SourceHeaderView; + + private lateinit var _sourcePermissions: LinearLayout; + private lateinit var _sourceWarnings: LinearLayout; + + private lateinit var _container: ScrollView; + private lateinit var _loader: ImageView; + + private lateinit var _buttonCancel: TextView; + private lateinit var _buttonInstall: LinearLayout; + + private var _isLoading: Boolean = false; + + private val _client = ManagedHttpClient(); + + private var _config : SourcePluginConfig? = null; + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + + if(!FragmentedStorage.isInitialized) + FragmentedStorage.initialize(filesDir); + if(StateApp.instance.scopeOrNull == null) + StateApp.instance.setGlobalContext(this, lifecycleScope); + + if(!StatePlatform.instance.hasClients) + lifecycleScope.launch { + StatePlatform.instance.updateAvailableClients(this@AddSourceActivity, false); + } + + setContentView(R.layout.activity_add_source); + setNavigationBarColorAndIcons(); + + _buttonBack = findViewById(R.id.button_back); + + _sourceHeader = findViewById(R.id.source_header); + + _sourcePermissions = findViewById(R.id.source_permissions); + _sourceWarnings = findViewById(R.id.source_warnings); + + _container = findViewById(R.id.configContainer); + _loader = findViewById(R.id.loader); + + _buttonCancel = findViewById(R.id.button_cancel); + _buttonInstall = findViewById(R.id.button_install); + + _buttonBack.setOnClickListener { + onBackPressed(); + }; + _buttonCancel.setOnClickListener { + onBackPressed(); + } + _buttonInstall.setOnClickListener { + _config?.let { + install(_config!!); + }; + }; + + setLoading(true); + + onNewIntent(intent); + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + var url = intent?.dataString; + + if(url == null) + UIDialogs.showDialog(this, R.drawable.ic_error, "No valid URL provided..", null, null, + 0, UIDialogs.Action("Ok", { finish() }, UIDialogs.ActionStyle.PRIMARY)); + else { + if(url.startsWith("vfuto://")) + url = "https://" + url.substring("vfuto://".length); + loadConfigUrl(url); + } + } + + fun clear() { + _sourceHeader.clear(); + _sourcePermissions.removeAllViews(); + _sourceWarnings.removeAllViews(); + } + + fun loadConfigUrl(url: String) { + setLoading(true); + + lifecycleScope.launch(Dispatchers.IO) { + try { + val configResp = _client.get(url); + if(!configResp.isOk) + throw IllegalStateException("Failed request with ${configResp.code}"); + val configJson = configResp.body?.string(); + if(configJson.isNullOrEmpty()) + throw IllegalStateException("No response"); + val config = SourcePluginConfig.fromJson(configJson, url); + + withContext(Dispatchers.Main) { + loadConfig(config); + } + } + catch(ex: SerializationException) { + Logger.e(TAG, "Failed decode config", ex); + withContext(Dispatchers.Main) { + UIDialogs.showDialog(this@AddSourceActivity, R.drawable.ic_error, + "Invalid Config Format", null, null, + 0, UIDialogs.Action("Ok", { finish() }, UIDialogs.ActionStyle.PRIMARY)); + }; + } + catch(ex: Exception) { + Logger.e(TAG, "Failed fetch config", ex); + withContext(Dispatchers.Main) { + UIDialogs.showGeneralErrorDialog(this@AddSourceActivity, "Failed to fetch configuration", ex); + }; + } + }; + } + + fun loadConfig(config: SourcePluginConfig) { + _config = config; + + _sourceHeader.loadConfig(config); + _sourcePermissions.removeAllViews(); + _sourceWarnings.removeAllViews(); + + if(!config.allowUrls.isEmpty()) + _sourcePermissions.addView( + SourceInfoView(this, + R.drawable.ic_language, + "URL Access", + "The plugin will have access to the following domains", + config.allowUrls, true) + ) + + if(config.allowEval) + _sourcePermissions.addView( + SourceInfoView(this, + R.drawable.ic_code, + "Eval Access", + "The plugin will have access to eval capability (remote injection)", + config.allowUrls, true) + ) + + val pastelRed = resources.getColor(R.color.pastel_red); + + for(warning in config.getWarnings()) + _sourceWarnings.addView( + SourceInfoView(this, + R.drawable.ic_security_pred, + warning.first, + warning.second) + .withDescriptionColor(pastelRed)); + + setLoading(false); + } + + fun install(config: SourcePluginConfig) { + StatePlugins.instance.installPlugin(this, lifecycleScope, config) { + if(it) + backToSources(); + } + } + + fun backToSources() { + this@AddSourceActivity.startActivity(MainActivity.getTabIntent(this, "Sources")); + finish(); + } + + + fun setLoading(loading: Boolean) { + _isLoading = loading; + if(loading) { + _container.visibility = View.GONE; + _loader.visibility = View.VISIBLE; + (_loader.drawable as Animatable?)?.start() + } + else { + _container.visibility = View.VISIBLE; + _loader.visibility = View.GONE; + (_loader.drawable as Animatable?)?.stop() + } + } + + override fun onResume() { + super.onResume(); + if(_isLoading) + (_loader.drawable as Animatable?)?.start() + } + override fun onPause() { + super.onPause() + (_loader.drawable as Animatable?)?.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/activities/AddSourceOptionsActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/AddSourceOptionsActivity.kt new file mode 100644 index 00000000..da4a563b --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/activities/AddSourceOptionsActivity.kt @@ -0,0 +1,51 @@ +package com.futo.platformplayer.activities + +import android.os.Bundle +import android.widget.* +import androidx.appcompat.app.AppCompatActivity +import com.futo.platformplayer.* +import com.futo.platformplayer.views.buttons.BigButton +import com.google.zxing.integration.android.IntentIntegrator +import com.journeyapps.barcodescanner.CaptureActivity + +class AddSourceOptionsActivity : AppCompatActivity() { + lateinit var _buttonBack: ImageButton; + + lateinit var _buttonQR: BigButton; + lateinit var _buttonURL: BigButton; + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_add_source_options); + setNavigationBarColorAndIcons(); + + _buttonBack = findViewById(R.id.button_back); + + _buttonQR = findViewById(R.id.option_qr); + _buttonURL = findViewById(R.id.option_url); + + _buttonBack.setOnClickListener { + finish(); + }; + + _buttonQR.onClick.subscribe { + val integrator = IntentIntegrator(this); + integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE) + integrator.setPrompt("Scan a QR Code") + integrator.setOrientationLocked(true); + integrator.setCameraId(0) + integrator.setBeepEnabled(false) + integrator.setBarcodeImageEnabled(true) + integrator.setCaptureActivity(QRCaptureActivity::class.java); + integrator.initiateScan() + } + _buttonURL.onClick.subscribe { + UIDialogs.toast(this, "Not implemented yet.."); + } + } + + + class QRCaptureActivity: CaptureActivity() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/activities/DeveloperActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/DeveloperActivity.kt new file mode 100644 index 00000000..53215522 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/activities/DeveloperActivity.kt @@ -0,0 +1,36 @@ +package com.futo.platformplayer.activities + +import android.os.Bundle +import android.widget.ImageButton +import androidx.appcompat.app.AppCompatActivity +import com.futo.platformplayer.* +import com.futo.platformplayer.views.fields.FieldForm + +class DeveloperActivity : AppCompatActivity() { + private lateinit var _form: FieldForm; + private lateinit var _buttonBack: ImageButton; + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_dev); + setNavigationBarColorAndIcons(); + + _buttonBack = findViewById(R.id.button_back); + _form = findViewById(R.id.settings_form); + + _form.fromObject(SettingsDev.instance); + _form.onChanged.subscribe { field, value -> + _form.setObjectValues(); + SettingsDev.instance.save(); + }; + + _buttonBack.setOnClickListener { + finish(); + } + } + + override fun finish() { + super.finish() + overridePendingTransition(R.anim.slide_lighten, R.anim.slide_out_up) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/activities/ExceptionActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/ExceptionActivity.kt new file mode 100644 index 00000000..55d222d1 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/activities/ExceptionActivity.kt @@ -0,0 +1,140 @@ +package com.futo.platformplayer.activities + +import android.content.Intent +import android.os.Bundle +import android.widget.LinearLayout +import android.widget.TextView +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import com.futo.platformplayer.* +import com.futo.platformplayer.logging.LogLevel +import com.futo.platformplayer.logging.Logging +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.BufferedWriter +import java.io.File +import java.io.FileWriter + +class ExceptionActivity : AppCompatActivity() { + private lateinit var _exText: TextView; + private lateinit var _buttonShare: LinearLayout; + private lateinit var _buttonSubmit: LinearLayout; + private lateinit var _buttonRestart: LinearLayout; + private lateinit var _buttonClose: LinearLayout; + private var _file: File? = null; + private var _submitted = false; + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_exception); + setNavigationBarColorAndIcons(); + + _exText = findViewById(R.id.ex_text); + _buttonShare = findViewById(R.id.button_share); + _buttonSubmit = findViewById(R.id.button_submit); + _buttonRestart = findViewById(R.id.button_restart); + _buttonClose = findViewById(R.id.button_close); + + val context = intent.getStringExtra(EXTRA_CONTEXT) ?: "Unknown Context"; + val stack = intent.getStringExtra(EXTRA_STACK) ?: "Something went wrong... missing stack trace?"; + + val exceptionString = "Version information (version_name = ${BuildConfig.VERSION_NAME}, version_code = ${BuildConfig.VERSION_CODE}, flavor = ${BuildConfig.FLAVOR}, build_type = ${BuildConfig.BUILD_TYPE})\n\n" + + Logging.buildLogString(LogLevel.ERROR, TAG, "Uncaught exception (\"$context\"): $stack"); + try { + val file = File(filesDir, "log.txt"); + if (!file.exists()) { + file.createNewFile(); + } + + BufferedWriter(FileWriter(file, true)).use { + it.appendLine(exceptionString); + }; + + _file = file + } catch (e: Throwable) { + //Ignored + } + + _exText.text = stack; + + _buttonSubmit.setOnClickListener { + submitFile(); + } + + _buttonShare.setOnClickListener { + share(exceptionString); + }; + + _buttonRestart.setOnClickListener { + startActivity(Intent(this, MainActivity::class.java)); + }; + _buttonClose.setOnClickListener { + finish(); + }; + } + + private fun submitFile() { + if (_submitted) { + Toast.makeText(this, "Logs already submitted.", Toast.LENGTH_LONG).show(); + return; + } + + val file = _file; + if (file == null) { + Toast.makeText(this, "No logs found.", Toast.LENGTH_LONG).show(); + return; + } + + lifecycleScope.launch(Dispatchers.IO) { + var id: String? = null; + + try { + id = Logging.submitLog(file); + } catch (e: Throwable) { + //Ignored + } + + withContext(Dispatchers.Main) { + if (id == null) { + try { + Toast.makeText(this@ExceptionActivity, "Failed automated share, share manually?", Toast.LENGTH_LONG).show(); + } catch (e: Throwable) { + //Ignored + } + } else { + _submitted = true; + file.delete(); + Toast.makeText(this@ExceptionActivity, "Shared $id", Toast.LENGTH_LONG).show(); + } + } + } + } + + private fun share(exceptionString: String) { + try { + val i = Intent(Intent.ACTION_SEND); + i.type = "text/plain"; + i.putExtra(Intent.EXTRA_EMAIL, arrayOf("grayjay@futo.org")); + i.putExtra(Intent.EXTRA_SUBJECT, "Unhandled exception in VS"); + i.putExtra(Intent.EXTRA_TEXT, exceptionString); + + startActivity(Intent.createChooser(i, "Send exception to developers...")); + } catch (e: Throwable) { + //Ignored + + } + } + + override fun finish() { + super.finish() + overridePendingTransition(R.anim.slide_lighten, R.anim.slide_out_up) + } + + companion object { + private const val TAG = "ExceptionActivity"; + val EXTRA_CONTEXT = "CONTEXT"; + val EXTRA_STACK = "STACK"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/activities/LoginActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/LoginActivity.kt new file mode 100644 index 00000000..f26317d1 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/activities/LoginActivity.kt @@ -0,0 +1,117 @@ +package com.futo.platformplayer.activities + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.webkit.CookieManager +import android.webkit.WebView +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.platforms.js.SourceAuth +import com.futo.platformplayer.api.media.platforms.js.SourcePluginAuthConfig +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.others.LoginWebViewClient +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +class LoginActivity : AppCompatActivity() { + private lateinit var _webView: WebView; + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login); + setNavigationBarColorAndIcons(); + + _webView = findViewById(R.id.web_view); + _webView.settings.javaScriptEnabled = true; + CookieManager.getInstance().setAcceptCookie(true); + + val config = if(intent.hasExtra("plugin")) + Json.decodeFromString(intent.getStringExtra("plugin")!!); + else null; + + val authConfig = if(config != null) + config.authentication ?: throw IllegalStateException("Plugin has no authentication support"); + else if(intent.hasExtra("auth")) + Json.decodeFromString(intent.getStringExtra("auth")!!); + else throw IllegalStateException("No valid configuration?"); + //TODO: Backwards compat removal? + + _webView.settings.userAgentString = authConfig.userAgent ?: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36"; + _webView.settings.useWideViewPort = true; + _webView.settings.loadWithOverviewMode = true; + + val webViewClient = if(config != null) LoginWebViewClient(config) else LoginWebViewClient(authConfig); + + webViewClient.onLogin.subscribe { auth -> + _callback?.let { + _callback = null; + it.invoke(auth); + } + finish(); + }; + var isFirstLoad = true; + webViewClient.onPageLoaded.subscribe { view, url -> + if(!isFirstLoad) + return@subscribe; + isFirstLoad = false; + + if(!authConfig.loginButton.isNullOrEmpty() && authConfig.loginButton.matches(REGEX_LOGIN_BUTTON)) { + Logger.i(TAG, "Clicking login button [${authConfig.loginButton}]"); + //TODO: Find most reliable way to wait for page js to finish + view?.evaluateJavascript("setTimeout(()=> document.querySelector(\"${authConfig.loginButton}\")?.click(), 1000)", {}); + } + } + //TODO: Required for some...TBD what to do with it. Clear on finish? + _webView.settings.domStorageEnabled = true; + + _webView.webViewClient = webViewClient; + _webView.loadUrl(authConfig.loginUrl); + } + + override fun finish() { + lifecycleScope.launch(Dispatchers.Main) { + _webView?.loadUrl("about:blank"); + } + _callback?.let { + _callback = null; + it.invoke(null); + } + super.finish(); + } + + companion object { + private val TAG = "LoginActivity"; + private val REGEX_LOGIN_BUTTON = Regex("[a-zA-Z\\-\\.#_ ]*"); + + private var _callback: ((SourceAuth?) -> Unit)? = null; + + fun getLoginIntent(context: Context, authConfig: SourcePluginAuthConfig): Intent { + val intent = Intent(context, LoginActivity::class.java); + intent.putExtra("auth", Json.encodeToString(authConfig)); + return intent; + } + fun getLoginIntent(context: Context, config: SourcePluginConfig): Intent { + val intent = Intent(context, LoginActivity::class.java); + intent.putExtra("plugin", Json.encodeToString(config)); + return intent; + } + + fun showLogin(context: Context, authConfig: SourcePluginAuthConfig, callback: ((SourceAuth?) -> Unit)? = null) { + if(_callback != null) _callback?.invoke(null); + _callback = callback; + context.startActivity(getLoginIntent(context, authConfig)); + } + fun showLogin(context: Context, config: SourcePluginConfig, callback: ((SourceAuth?) -> Unit)? = null) { + if(_callback != null) _callback?.invoke(null); + _callback = callback; + context.startActivity(getLoginIntent(context, config)); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt new file mode 100644 index 00000000..95ae8a9d --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt @@ -0,0 +1,906 @@ +package com.futo.platformplayer.activities + +import android.content.Context +import android.content.Intent +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK +import android.content.pm.ActivityInfo +import android.content.res.Configuration +import android.net.Uri +import android.os.Bundle +import android.util.TypedValue +import android.view.View +import android.widget.FrameLayout +import androidx.appcompat.app.AppCompatActivity +import androidx.constraintlayout.motion.widget.MotionLayout +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentContainerView +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.models.channels.SerializedChannel +import com.futo.platformplayer.casting.StateCasting +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.fragment.mainactivity.bottombar.MenuBottomBarFragment +import com.futo.platformplayer.fragment.mainactivity.main.* +import com.futo.platformplayer.fragment.mainactivity.topbar.AddTopBarFragment +import com.futo.platformplayer.fragment.mainactivity.topbar.GeneralTopBarFragment +import com.futo.platformplayer.fragment.mainactivity.topbar.ImportTopBarFragment +import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFragment +import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment +import com.futo.platformplayer.listeners.OrientationManager +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.UrlVideoWithTime +import com.futo.platformplayer.states.* +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.SubscriptionStorage +import com.futo.platformplayer.stores.v2.ManagedStore +import com.google.gson.JsonParser +import kotlinx.coroutines.* +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import java.io.File +import java.io.PrintWriter +import java.io.StringWriter +import java.lang.reflect.InvocationTargetException +import java.util.* + +class MainActivity : AppCompatActivity { + + //TODO: Move to dimensions + private val HEIGHT_MENU_DP = 48f; + private val HEIGHT_VIDEO_MINIMIZED_DP = 60f; + + //Containers + lateinit var rootView : MotionLayout; + + private lateinit var _overlayContainer: FrameLayout; + + //Segment Containers + private lateinit var _fragContainerTopBar: FragmentContainerView; + private lateinit var _fragContainerMain: FragmentContainerView; + private lateinit var _fragContainerBotBar: FragmentContainerView; + private lateinit var _fragContainerVideoDetail: FragmentContainerView; + private lateinit var _fragContainerOverlay: FrameLayout; + + //Frags TopBar + lateinit var _fragTopBarGeneral: GeneralTopBarFragment; + lateinit var _fragTopBarSearch: SearchTopBarFragment; + lateinit var _fragTopBarNavigation: NavigationTopBarFragment; + lateinit var _fragTopBarImport: ImportTopBarFragment; + lateinit var _fragTopBarAdd: AddTopBarFragment; + + //Frags BotBar + lateinit var _fragBotBarMenu: MenuBottomBarFragment; + + //Frags Main + lateinit var _fragMainHome: HomeFragment; + lateinit var _fragPostDetail: PostDetailFragment; + lateinit var _fragMainVideoSearchResults: ContentSearchResultsFragment; + lateinit var _fragMainCreatorSearchResults: CreatorSearchResultsFragment; + lateinit var _fragMainPlaylistSearchResults: PlaylistSearchResultsFragment; + lateinit var _fragMainSuggestions: SuggestionsFragment; + lateinit var _fragMainSubscriptions: CreatorsFragment; + lateinit var _fragMainSubscriptionsFeed: SubscriptionsFeedFragment; + lateinit var _fragMainChannel: ChannelFragment; + lateinit var _fragMainSources: SourcesFragment; + lateinit var _fragMainPlaylists: PlaylistsFragment; + lateinit var _fragMainPlaylist: PlaylistFragment; + lateinit var _fragWatchlist: WatchLaterFragment; + lateinit var _fragHistory: HistoryFragment; + lateinit var _fragSourceDetail: SourceDetailFragment; + lateinit var _fragDownloads: DownloadsFragment; + lateinit var _fragImportSubscriptions: ImportSubscriptionsFragment; + lateinit var _fragImportPlaylists: ImportPlaylistsFragment; + lateinit var _fragBuy: BuyFragment; + + lateinit var _fragBrowser: BrowserFragment; + + //Frags Overlay + lateinit var _fragVideoDetail: VideoDetailFragment; + + //State + private val _queue : Queue> = LinkedList(); + lateinit var fragCurrent : MainFragment private set; + private var _parameterCurrent: Any? = null; + + var fragBeforeOverlay : MainFragment? = null; private set; + + val onNavigated = Event1(); + + private lateinit var _orientationManager: OrientationManager; + var orientation: OrientationManager.Orientation = OrientationManager.Orientation.PORTRAIT + private set; + private var _isVisible = true; + private var _wasStopped = false; + + constructor() : super() { + Thread.setDefaultUncaughtExceptionHandler { _, throwable -> + val writer = StringWriter(); + + var excp = throwable; + Logger.e("Application", "Uncaught", excp); + + //Resolve invocation chains + while(excp is InvocationTargetException || excp is java.lang.RuntimeException) { + val before = excp; + + if(excp is InvocationTargetException) + excp = excp.targetException ?: excp.cause ?: excp; + else if(excp is java.lang.RuntimeException) + excp = excp.cause ?: excp; + + if(excp == before) + break; + } + writer.write((excp.message ?: "Empty error") + "\n\n"); + excp.printStackTrace(PrintWriter(writer)); + val message = writer.toString(); + Logger.e(TAG, message, excp); + + val exIntent = Intent(this, ExceptionActivity::class.java); + exIntent.addFlags(FLAG_ACTIVITY_NEW_TASK); + exIntent.putExtra(ExceptionActivity.EXTRA_STACK, message); + startActivity(exIntent); + + Runtime.getRuntime().exit(0); + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + StateApp.instance.setGlobalContext(this, lifecycleScope); + StateApp.instance.mainAppStarting(this); + + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + setNavigationBarColorAndIcons(); + + runBlocking { + StatePlatform.instance.updateAvailableClients(this@MainActivity); + } + + //Preload common files to memory + FragmentedStorage.get(); + FragmentedStorage.get(); + + rootView = findViewById(R.id.rootView); + _fragContainerTopBar = findViewById(R.id.fragment_top_bar); + _fragContainerMain = findViewById(R.id.fragment_main); + _fragContainerBotBar = findViewById(R.id.fragment_bottom_bar); + _fragContainerVideoDetail = findViewById(R.id.fragment_overlay); + _fragContainerOverlay = findViewById(R.id.fragment_overlay_container); + _overlayContainer = findViewById(R.id.overlay_container); + //_overlayContainer.visibility = View.GONE; + + //Initialize fragments + + //TopBars + _fragTopBarGeneral = GeneralTopBarFragment.newInstance(); + _fragTopBarSearch = SearchTopBarFragment.newInstance(); + _fragTopBarNavigation = NavigationTopBarFragment.newInstance(); + _fragTopBarImport = ImportTopBarFragment.newInstance(); + _fragTopBarAdd = AddTopBarFragment.newInstance(); + + //BotBars + _fragBotBarMenu = MenuBottomBarFragment.newInstance(); + + //Main + _fragMainHome = HomeFragment.newInstance(); + _fragMainSuggestions = SuggestionsFragment.newInstance(); + _fragMainVideoSearchResults = ContentSearchResultsFragment.newInstance(); + _fragMainCreatorSearchResults = CreatorSearchResultsFragment.newInstance(); + _fragMainPlaylistSearchResults = PlaylistSearchResultsFragment.newInstance(); + _fragMainSubscriptions = CreatorsFragment.newInstance(); + _fragMainChannel = ChannelFragment.newInstance(); + _fragMainSubscriptionsFeed = SubscriptionsFeedFragment.newInstance(); + _fragMainSources = SourcesFragment.newInstance(); + _fragMainPlaylists = PlaylistsFragment.newInstance(); + _fragMainPlaylist = PlaylistFragment.newInstance(); + _fragPostDetail = PostDetailFragment.newInstance(); + _fragWatchlist = WatchLaterFragment.newInstance(); + _fragHistory = HistoryFragment.newInstance(); + _fragSourceDetail = SourceDetailFragment.newInstance(); + _fragDownloads = DownloadsFragment(); + _fragImportSubscriptions = ImportSubscriptionsFragment.newInstance(); + _fragImportPlaylists = ImportPlaylistsFragment.newInstance(); + _fragBuy = BuyFragment.newInstance(); + + _fragBrowser = BrowserFragment.newInstance(); + + //Overlays + _fragVideoDetail = VideoDetailFragment.newInstance(); + //Overlay Init + _fragVideoDetail.onMinimize.subscribe { }; + _fragVideoDetail.onShownEvent.subscribe { + _fragMainHome.setPreviewsEnabled(false); + _fragMainVideoSearchResults.setPreviewsEnabled(false); + _fragMainSubscriptionsFeed.setPreviewsEnabled(false); + }; + + + _fragVideoDetail.onMinimize.subscribe { + updateSegmentPaddings(); + }; + _fragVideoDetail.onTransitioning.subscribe { + if(it || _fragVideoDetail.state != VideoDetailFragment.State.MINIMIZED) + _fragContainerOverlay.elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15f, resources.displayMetrics); + else + _fragContainerOverlay.elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics); + } + + _fragVideoDetail.onCloseEvent.subscribe { + _fragMainHome.setPreviewsEnabled(true); + _fragMainVideoSearchResults.setPreviewsEnabled(true); + _fragMainSubscriptionsFeed.setPreviewsEnabled(true); + _fragContainerVideoDetail.visibility = View.INVISIBLE; + updateSegmentPaddings(); + }; + + StatePlayer.instance.also { + it.onQueueChanged.subscribe { shouldSwapCurrentItem -> + if (!shouldSwapCurrentItem) { + return@subscribe; + } + + if(_fragVideoDetail.state == VideoDetailFragment.State.CLOSED) { + if (fragCurrent !is VideoDetailFragment) { + val toPlay = StatePlayer.instance.getCurrentQueueItem(); + navigate(_fragVideoDetail, toPlay); + + if (!StatePlayer.instance.queueFocused) + _fragVideoDetail.minimizeVideoDetail(); + } + } else { + val toPlay = StatePlayer.instance.getCurrentQueueItem() ?: return@subscribe; + Logger.i(TAG, "Queue changed _fragVideoDetail.currentUrl=${_fragVideoDetail.currentUrl} toPlay.url=${toPlay.url}") + if (_fragVideoDetail.currentUrl == null || _fragVideoDetail.currentUrl != toPlay.url) { + navigate(_fragVideoDetail, toPlay); + } + } + }; + } + + onNavigated.subscribe { + updateSegmentPaddings(); + } + + + //Set top bars + _fragMainHome.topBar = _fragTopBarGeneral; + _fragMainSubscriptions.topBar = _fragTopBarGeneral; + _fragMainSuggestions.topBar = _fragTopBarSearch; + _fragMainVideoSearchResults.topBar = _fragTopBarSearch; + _fragMainCreatorSearchResults.topBar = _fragTopBarSearch; + _fragMainPlaylistSearchResults.topBar = _fragTopBarSearch; + _fragMainChannel.topBar = _fragTopBarNavigation; + _fragMainSubscriptionsFeed.topBar = _fragTopBarGeneral; + _fragMainSources.topBar = _fragTopBarAdd; + _fragMainPlaylists.topBar = _fragTopBarGeneral; + _fragMainPlaylist.topBar = _fragTopBarNavigation; + _fragPostDetail.topBar = _fragTopBarNavigation; + _fragWatchlist.topBar = _fragTopBarNavigation; + _fragHistory.topBar = _fragTopBarNavigation; + _fragSourceDetail.topBar = _fragTopBarNavigation; + _fragDownloads.topBar = _fragTopBarGeneral; + _fragImportSubscriptions.topBar = _fragTopBarImport; + _fragImportPlaylists.topBar = _fragTopBarImport; + + _fragBrowser.topBar = _fragTopBarNavigation; + + fragCurrent = _fragMainHome; + + val defaultTab = Settings.instance.tabs.mapNotNull { + val buttonDefinition = MenuBottomBarFragment.buttonDefinitions.firstOrNull { bd -> it.id == bd.id }; + if (buttonDefinition == null) { + return@mapNotNull null; + } else { + return@mapNotNull Pair(it, buttonDefinition); + } + }.first { it.first.enabled }.second; + + supportFragmentManager.beginTransaction() + .replace(R.id.fragment_top_bar, _fragTopBarGeneral) + .replace(R.id.fragment_main, _fragMainHome) + .replace(R.id.fragment_bottom_bar, _fragBotBarMenu) + .replace(R.id.fragment_overlay, _fragVideoDetail) + .commitNow(); + + defaultTab.action(_fragBotBarMenu); + + _orientationManager = OrientationManager(this); + _orientationManager.onOrientationChanged.subscribe { + orientation = it; + Logger.i(TAG, "Orientation changed (Found ${it})"); + fragCurrent.onOrientationChanged(it); + if(_fragVideoDetail.state == VideoDetailFragment.State.MAXIMIZED) + _fragVideoDetail.onOrientationChanged(it); + }; + _orientationManager.enable(); + + StateSubscriptions.instance; + + fragCurrent.onShown(null, false); + + //Other stuff + rootView.progress = 0f; + + handleIntent(intent); + + if (Settings.instance.casting.enabled) { + StateCasting.instance.start(this); + } + + StatePlatform.instance.onDevSourceChanged.subscribe { + Logger.i(TAG, "onDevSourceChanged") + + lifecycleScope.launch(Dispatchers.Main) { + try { + if (!_isVisible) { + val bringUpIntent = Intent(this@MainActivity, MainActivity::class.java); + bringUpIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + bringUpIntent.action = "TAB"; + bringUpIntent.putExtra("TAB", "Sources"); + startActivity(bringUpIntent); + } else { + _fragVideoDetail.closeVideoDetails(); + navigate(_fragMainSources); + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to make sources front.", e); + } + } + }; + + StateApp.instance.mainAppStarted(this); + + //if(ContextCompat.checkSelfPermission(this, Manifest.permission.MANAGE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) + // ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.MANAGE_EXTERNAL_STORAGE), 123); + //else + StateApp.instance.mainAppStartedWithExternalFiles(this); + + //startActivity(Intent(this, TestActivity::class.java)); + } + + /* + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if(requestCode != 123) + return; + + if(grantResults.size == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) + StateApp.instance.mainAppStartedWithExternalFiles(this); + else { + UIDialogs.showDialog(this, R.drawable.ic_help, "File Permissions", "Grayjay requires file permissions for exporting downloads and automatic backups", null, 0, + UIDialogs.Action("Cancel", {}), + UIDialogs.Action("Configure", { + startActivity(Intent().apply { + action = android.provider.Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION; + data = Uri.fromParts("package", packageName, null) + }); + }, UIDialogs.ActionStyle.PRIMARY)); + } + UIDialogs.toast(this, "No external file permissions\nExporting and auto backups will not work"); + }*/ + + override fun onResume() { + super.onResume(); + Logger.i(TAG, "onResume") + + val curOrientation = _orientationManager.orientation; + + if(_fragVideoDetail.state == VideoDetailFragment.State.MAXIMIZED && _fragVideoDetail.lastOrientation != curOrientation) { + Logger.i(TAG, "Orientation mismatch (Found ${curOrientation})"); + orientation = curOrientation; + fragCurrent.onOrientationChanged(curOrientation); + if(_fragVideoDetail.state == VideoDetailFragment.State.MAXIMIZED) + _fragVideoDetail.onOrientationChanged(curOrientation); + } + + _isVisible = true; + val videoToOpen = StateSaved.instance.videoToOpen; + + if (_wasStopped) { + Logger.i(TAG, "_wasStopped is true"); + Logger.i(TAG, "set _wasStopped = false"); + _wasStopped = false; + + Logger.i(TAG, "onResume videoToOpen=$videoToOpen"); + + if (videoToOpen != null && _fragVideoDetail.state == VideoDetailFragment.State.CLOSED) { + if (StatePlatform.instance.hasEnabledVideoClient(videoToOpen.url)) { + navigate(_fragVideoDetail, UrlVideoWithTime(videoToOpen.url, videoToOpen.timeSeconds, false)); + _fragVideoDetail.maximizeVideoDetail(true); + } + + StateSaved.instance.setVideoToOpenNonBlocking(null); + } + } + } + + override fun onPause() { + super.onPause(); + Logger.i(TAG, "onPause") + _isVisible = false; + } + + override fun onStop() { + super.onStop() + Logger.i(TAG, "_wasStopped = true"); + _wasStopped = true; + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent); + handleIntent(intent); + } + + private fun handleIntent(intent: Intent?) { + if(intent == null) + return; + Logger.i(TAG, "handleIntent started by " + intent.action); + + + var targetData: String? = null; + + when(intent.action) { + Intent.ACTION_SEND -> { + targetData = intent.getStringExtra(Intent.EXTRA_STREAM) ?: intent.getStringExtra(Intent.EXTRA_TEXT); + Logger.i(TAG, "Share Received: " + targetData); + } + Intent.ACTION_VIEW -> { + targetData = intent.dataString + + if(!targetData.isNullOrEmpty()) { + Logger.i(TAG, "View Received: " + targetData); + } + } + "TAB" -> { + when(intent.getStringExtra("TAB")){ + "Sources" -> { + runBlocking { + StatePlatform.instance.updateAvailableClients(this@MainActivity, true) //Ideally this is not needed.. + navigate(_fragMainSources); + } + }; + } + } + } + + try { + if (targetData != null) { + when(intent.scheme) { + "grayjay" -> { + if(targetData.startsWith("grayjay://license/")) { + if(StatePayment.instance.setPaymentLicenseUrl(targetData)) + { + UIDialogs.showDialogOk(this, R.drawable.ic_check, "Your license key has been set!\nAn app restart might be required."); + + if(fragCurrent is BuyFragment) + closeSegment(fragCurrent); + } + else + UIDialogs.toast("Invalid license format"); + + } + else if(targetData.startsWith("grayjay://plugin/")) { + val intent = Intent(this, AddSourceActivity::class.java).apply { + data = Uri.parse(targetData.substring("grayjay://plugin/".length)); + }; + startActivity(intent); + } + } + "content" -> { + if(!handleContent(targetData, intent.type)) { + UIDialogs.showSingleButtonDialog( + this, + R.drawable.ic_play, + "Unknown content format [${targetData}]", + "Ok", + { }); + } + } + "file" -> { + if(!handleFile(targetData)) { + UIDialogs.showSingleButtonDialog( + this, + R.drawable.ic_play, + "Unknown file format [${targetData}]", + "Ok", + { }); + } + } + "polycentric" -> { + if(!handlePolycentric(targetData)) { + UIDialogs.showSingleButtonDialog( + this, + R.drawable.ic_play, + "Unknown Polycentric format [${targetData}]", + "Ok", + { }); + } + } + else -> { + if (!handleUrl(targetData)) { + UIDialogs.showSingleButtonDialog( + this, + R.drawable.ic_play, + "Unknown url format [${targetData}]", + "Ok", + { }); + } + } + } + } + } + catch(ex: Throwable) { + UIDialogs.showGeneralErrorDialog(this, "Failed to handle file", ex); + } + } + + fun handleUrl(url: String): Boolean { + Logger.i(TAG, "handleUrl(url=$url)") + + if (StatePlatform.instance.hasEnabledVideoClient(url)) { + navigate(_fragVideoDetail, url); + _fragVideoDetail.maximizeVideoDetail(true); + return true; + } else if(StatePlatform.instance.hasEnabledChannelClient(url)) { + navigate(_fragMainChannel, url); + + lifecycleScope.launch { + delay(100); + _fragVideoDetail.minimizeVideoDetail(); + }; + return true; + } + return false; + } + fun handleContent(file: String, mime: String? = null): Boolean { + Logger.i(TAG, "handleContent(url=$file)"); + + val data = readSharedContent(file); + if(file.lowercase().endsWith(".json") || mime == "application/json") { + var recon = String(data); + if(!recon.trim().startsWith("[")) + return handleUnknownJson(file, recon); + + val reconLines = Json.decodeFromString>(recon); + recon = reconLines.joinToString("\n"); + Logger.i(TAG, "Opened shared playlist reconstruction\n${recon}"); + handleReconstruction(recon); + return true; + } + else if(file.lowercase().endsWith(".zip") || mime == "application/zip") { + StateBackup.importZipBytes(this, lifecycleScope, data); + return true; + } + return false; + } + fun handleFile(file: String): Boolean { + Logger.i(TAG, "handleFile(url=$file)"); + if(file.lowercase().endsWith(".json")) { + val recon = String(readSharedFile(file)); + if(!recon.startsWith("[")) + return handleUnknownJson(file, recon); + + Logger.i(TAG, "Opened shared playlist reconstruction\n${recon}"); + handleReconstruction(recon); + return true; + } + else if(file.lowercase().endsWith(".zip")) { + StateBackup.importZipBytes(this, lifecycleScope, readSharedFile(file)); + return true; + } + return false; + } + fun handleReconstruction(recon: String) { + val type = ManagedStore.getReconstructionIdentifier(recon); + val store: ManagedStore<*> = when(type) { + "Playlist" -> StatePlaylists.instance.playlistStore + else -> { + UIDialogs.toast("Unknown reconstruction type ${type}", false); + return; + }; + }; + val name = when(type) { + "Playlist" -> recon.split("\n").filter { !it.startsWith(ManagedStore.RECONSTRUCTION_HEADER_OPERATOR) }.firstOrNull() ?: type; + else -> type + } + + + if(!type.isNullOrEmpty()) { + UIDialogs.showImportDialog(this, store, name, listOf(recon)) { + + } + } + } + + fun handleUnknownJson(name: String?, json: String): Boolean { + + val context = this; + + //TODO: Proper import selection + try { + val newPipeSubsParsed = JsonParser.parseString(json).asJsonObject; + if (!newPipeSubsParsed.has("subscriptions") || !newPipeSubsParsed["subscriptions"].isJsonArray) + return false;//throw IllegalArgumentException("Invalid NewPipe json structure found"); + + val jsonSubs = newPipeSubsParsed["subscriptions"] + val jsonSubsArray = jsonSubs.asJsonArray; + val jsonSubsArrayItt = jsonSubsArray.iterator(); + val subs = mutableListOf() + while(jsonSubsArrayItt.hasNext()) { + val jsonSubObj = jsonSubsArrayItt.next().asJsonObject; + + if(jsonSubObj.has("url")) + subs.add(jsonSubObj["url"].asString); + } + + navigate(_fragImportSubscriptions, subs); + } + catch(ex: Exception) { + Logger.e(TAG, ex.message, ex); + UIDialogs.showGeneralErrorDialog(context, "Failed to parse NewPipe Subscriptions", ex); + } + + /* + lifecycleScope.launch(Dispatchers.Main) { + UISlideOverlays.showOverlay(_overlayContainer, "Import Json", "", {}, + SlideUpMenuGroup(context, "What kind of json import is this?", "", + SlideUpMenuItem(context, 0, "NewPipe Subscriptions", "", "NewPipeSubs", { + })) + ); + }*/ + + + return true; + } + + + fun handlePolycentric(url: String): Boolean { + Logger.i(TAG, "handlePolycentric"); + startActivity(Intent(this, PolycentricImportProfileActivity::class.java).apply { putExtra("url", url) }) + return true; + } + private fun readSharedContent(contentPath: String): ByteArray { + return contentResolver.openInputStream(Uri.parse(contentPath))?.use { + return it.readBytes(); + } ?: throw IllegalStateException("Opened content was not accessible"); + } + + private fun readSharedFile(filePath: String): ByteArray { + val dataFile = File(filePath); + if(!dataFile.exists()) + throw IllegalArgumentException("Opened file does not exist or not permitted"); + val data = dataFile.readBytes(); + return data; + } + + override fun onBackPressed() { + Logger.i(TAG, "onBackPressed") + + if(_fragBotBarMenu.onBackPressed()) + return; + + if(_fragVideoDetail.state == VideoDetailFragment.State.MAXIMIZED && + _fragVideoDetail.onBackPressed()) + return; + + + if(!fragCurrent.onBackPressed()) + closeSegment(); + } + + override fun onUserLeaveHint() { + super.onUserLeaveHint(); + Logger.i(TAG, "onUserLeaveHint") + + if(_fragVideoDetail.state == VideoDetailFragment.State.MAXIMIZED || _fragVideoDetail.state == VideoDetailFragment.State.MINIMIZED) + _fragVideoDetail.onUserLeaveHint(); + } + + override fun onRestart() { + super.onRestart(); + Logger.i(TAG, "onRestart"); + + //Force Portrait on restart + Logger.i(TAG, "Restarted with state ${_fragVideoDetail.state}"); + if(_fragVideoDetail.state != VideoDetailFragment.State.MAXIMIZED) { + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + WindowCompat.setDecorFitsSystemWindows(window, true) + WindowInsetsControllerCompat(window, rootView).let { controller -> + controller.show(WindowInsetsCompat.Type.statusBars()); + controller.show(WindowInsetsCompat.Type.systemBars()) + } + _fragVideoDetail.onOrientationChanged(OrientationManager.Orientation.PORTRAIT); + } + + Logger.i(TAG, "onRestart5"); + } + + override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) { + super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig); + + val isStop: Boolean = lifecycle.currentState == Lifecycle.State.CREATED; + Logger.i(TAG, "onPictureInPictureModeChanged isInPictureInPictureMode=$isInPictureInPictureMode isStop=$isStop") + _fragVideoDetail?.onPictureInPictureModeChanged(isInPictureInPictureMode, isStop, newConfig); + Logger.i(TAG, "onPictureInPictureModeChanged Ready"); + } + + override fun onDestroy() { + super.onDestroy(); + Logger.i(TAG, "onDestroy") + + _orientationManager.disable(); + + StateApp.instance.mainAppDestroyed(this); + StateSaved.instance.setVideoToOpenBlocking(null); + } + + + /** + * Navigate takes a MainFragment, and makes them the current main visible view + * A parameter can be provided which becomes available in the onShow of said fragment + */ + fun navigate(segment: MainFragment, parameter: Any? = null, withHistory: Boolean = true, isBack: Boolean = false) { + Logger.i(TAG, "Navigate to $segment (parameter=$parameter, withHistory=$withHistory, isBack=$isBack)") + + if(segment != fragCurrent) { + + if(segment is VideoDetailFragment) { + if(_fragContainerVideoDetail.visibility != View.VISIBLE) + _fragContainerVideoDetail.visibility = View.VISIBLE; + when(segment.state) { + VideoDetailFragment.State.MINIMIZED -> segment.maximizeVideoDetail() + VideoDetailFragment.State.CLOSED -> segment.maximizeVideoDetail() + else -> {} + } + segment.onShown(parameter, isBack); + return; + } + + + fragCurrent.onHide(); + + if(segment.isMainView) { + var transaction = supportFragmentManager.beginTransaction(); + if (segment.topBar != null) { + if (segment.topBar != fragCurrent.topBar) { + transaction = transaction + .show(segment.topBar as Fragment) + .replace(R.id.fragment_top_bar, segment.topBar as Fragment); + fragCurrent.topBar?.onHide(); + } + } + else if(fragCurrent.topBar != null) + transaction.hide(fragCurrent.topBar as Fragment); + + transaction = transaction.replace(R.id.fragment_main, segment); + + val extraBottomDP = if(_fragVideoDetail.state == VideoDetailFragment.State.MINIMIZED) HEIGHT_VIDEO_MINIMIZED_DP else 0f + if (segment.hasBottomBar) { + if (!fragCurrent.hasBottomBar) + transaction = transaction.show(_fragBotBarMenu); + } + else { + if(fragCurrent.hasBottomBar) + transaction = transaction.hide(_fragBotBarMenu); + } + transaction.commitNow(); + } + else { + //Special cases + if(segment is VideoDetailFragment) { + _fragContainerVideoDetail.visibility = View.VISIBLE; + _fragVideoDetail.maximizeVideoDetail(); + } + + if(!segment.hasBottomBar) { + supportFragmentManager.beginTransaction() + .hide(_fragBotBarMenu) + .commitNow(); + } + } + + if(fragCurrent.isHistory && withHistory && _queue.lastOrNull() != fragCurrent) + _queue.add(Pair(fragCurrent, _parameterCurrent)); + + if(segment.isOverlay && !fragCurrent.isOverlay && withHistory)// && fragCurrent.isHistory) + fragBeforeOverlay = fragCurrent; + + + fragCurrent = segment; + _parameterCurrent = parameter; + } + + segment.topBar?.onShown(parameter); + segment.onShown(parameter, isBack); + onNavigated.emit(segment); + } + + /** + * Called when the current segment (main) should be closed, if already at a root view (tab), close application + * If called with a non-null fragment, it will only close if the current fragment is the provided one + */ + fun closeSegment(fragment: MainFragment? = null) { + if(fragment is VideoDetailFragment) { + fragment.onHide(); + return; + } + + if((fragment?.isOverlay ?: false) && fragBeforeOverlay != null) { + navigate(fragBeforeOverlay!!, null, false, true); + + } + else { + val last = _queue.lastOrNull(); + if (last != null) { + _queue.remove(last); + navigate(last.first, last.second, false, true); + } else + finish(); + } + } + + /** + * Provides the fragment instance for the provided fragment class + */ + inline fun getFragment() : T { + return when(T::class) { + HomeFragment::class -> _fragMainHome as T; + ContentSearchResultsFragment::class -> _fragMainVideoSearchResults as T; + CreatorSearchResultsFragment::class -> _fragMainCreatorSearchResults as T; + SuggestionsFragment::class -> _fragMainSuggestions as T; + VideoDetailFragment::class -> _fragVideoDetail as T; + MenuBottomBarFragment::class -> _fragBotBarMenu as T; + GeneralTopBarFragment::class -> _fragTopBarGeneral as T; + SearchTopBarFragment::class -> _fragTopBarSearch as T; + CreatorsFragment::class -> _fragMainSubscriptions as T; + SubscriptionsFeedFragment::class -> _fragMainSubscriptionsFeed as T; + PlaylistSearchResultsFragment::class -> _fragMainPlaylistSearchResults as T; + ChannelFragment::class -> _fragMainChannel as T; + SourcesFragment::class -> _fragMainSources as T; + PlaylistsFragment::class -> _fragMainPlaylists as T; + PlaylistFragment::class -> _fragMainPlaylist as T; + PostDetailFragment::class -> _fragPostDetail as T; + WatchLaterFragment::class -> _fragWatchlist as T; + HistoryFragment::class -> _fragHistory as T; + SourceDetailFragment::class -> _fragSourceDetail as T; + DownloadsFragment::class -> _fragDownloads as T; + ImportSubscriptionsFragment::class -> _fragImportSubscriptions as T; + ImportPlaylistsFragment::class -> _fragImportPlaylists as T; + BrowserFragment::class -> _fragBrowser as T; + BuyFragment::class -> _fragBuy as T; + else -> throw IllegalArgumentException("Fragment type ${T::class.java.name} is not available in MainActivity"); + } + } + + + private fun updateSegmentPaddings() { + var paddingBottom = 0f; + if(fragCurrent.hasBottomBar) + paddingBottom += HEIGHT_MENU_DP; + + _fragContainerOverlay.setPadding(0,0,0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom - HEIGHT_MENU_DP, resources.displayMetrics).toInt()); + + if(_fragVideoDetail.state == VideoDetailFragment.State.MINIMIZED) + paddingBottom += HEIGHT_VIDEO_MINIMIZED_DP; + + _fragContainerMain.setPadding(0,0,0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom, resources.displayMetrics).toInt()); + } + + companion object { + private val TAG = "MainActivity" + + fun getTabIntent(context: Context, tab: String) : Intent { + val sourcesIntent = Intent(context, MainActivity::class.java); + sourcesIntent.action = "TAB"; + sourcesIntent.putExtra("TAB", tab); + sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + return sourcesIntent; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/activities/ManageTabsActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/ManageTabsActivity.kt new file mode 100644 index 00000000..9d928f2c --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/activities/ManageTabsActivity.kt @@ -0,0 +1,99 @@ +package com.futo.platformplayer.activities + +import android.os.Bundle +import android.widget.ImageButton +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.MenuBottomBarSetting +import com.futo.platformplayer.R +import com.futo.platformplayer.Settings +import com.futo.platformplayer.fragment.mainactivity.bottombar.MenuBottomBarFragment +import com.futo.platformplayer.setNavigationBarColorAndIcons +import com.futo.platformplayer.views.AnyAdapterView +import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny +import com.futo.platformplayer.views.adapters.ItemMoveCallback +import com.futo.platformplayer.views.adapters.viewholders.TabViewHolder +import com.futo.platformplayer.views.adapters.viewholders.TabViewHolderData +import java.util.* + +class ManageTabsActivity : AppCompatActivity() { + private lateinit var _buttonBack: ImageButton; + private lateinit var _listTabs: AnyAdapterView; + private lateinit var _recyclerTabs: RecyclerView; + private lateinit var _touchHelper: ItemTouchHelper; + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_manage_tabs); + setNavigationBarColorAndIcons(); + + _buttonBack = findViewById(R.id.button_back); + + val callback = ItemMoveCallback(); + _touchHelper = ItemTouchHelper(callback); + _recyclerTabs = findViewById(R.id.recycler_tabs); + _touchHelper.attachToRecyclerView(_recyclerTabs); + + val itemsRemoved = Settings.instance.tabs.removeIf { MenuBottomBarFragment.buttonDefinitions.none { d -> it.id == d.id } } + + var itemsAdded = false + for (buttonDefinition in MenuBottomBarFragment.buttonDefinitions) { + if (Settings.instance.tabs.none { it.id == buttonDefinition.id }) { + Settings.instance.tabs.add(MenuBottomBarSetting(buttonDefinition.id, true)) + itemsAdded = true + } + } + + if (itemsAdded || itemsRemoved) { + Settings.instance.save() + } + + val items = Settings.instance.tabs.mapNotNull { + val buttonDefinition = MenuBottomBarFragment.buttonDefinitions.find { d -> it.id == d.id } ?: return@mapNotNull null + TabViewHolderData(buttonDefinition, it.enabled) + }; + + _listTabs = _recyclerTabs.asAny(items) { + it.onDragDrop.subscribe { vh -> + _touchHelper.startDrag(vh); + }; + it.onEnableChanged.subscribe { enabled -> + val d = it.data ?: return@subscribe + Settings.instance.tabs.find { def -> d.buttonDefinition.id == def.id }?.enabled = enabled + Settings.instance.onTabsChanged.emit() + Settings.instance.save() + }; + }; + + callback.onRowMoved.subscribe { fromPosition, toPosition -> + if (fromPosition < toPosition) { + for (i in fromPosition until toPosition) { + Collections.swap(items, i, i + 1) + Collections.swap(Settings.instance.tabs, i, i + 1) + } + + Settings.instance.onTabsChanged.emit() + Settings.instance.save() + } else { + for (i in fromPosition downTo toPosition + 1) { + Collections.swap(items, i, i - 1) + Collections.swap(Settings.instance.tabs, i, i - 1) + } + + Settings.instance.onTabsChanged.emit() + Settings.instance.save() + } + + _listTabs.adapter.notifyItemMoved(fromPosition, toPosition); + }; + + _buttonBack.setOnClickListener { + onBackPressed(); + }; + } + + companion object { + private const val TAG = "ManageTabsActivity"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/activities/PolycentricBackupActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/PolycentricBackupActivity.kt new file mode 100644 index 00000000..4413eb97 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricBackupActivity.kt @@ -0,0 +1,175 @@ +package com.futo.platformplayer.activities + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.Color +import android.os.Bundle +import android.util.TypedValue +import android.view.View +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import com.futo.platformplayer.R +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.setNavigationBarColorAndIcons +import com.futo.platformplayer.states.StatePolycentric +import com.futo.platformplayer.views.buttons.BigButton +import com.futo.polycentric.core.* +import com.google.zxing.BarcodeFormat +import com.google.zxing.MultiFormatWriter +import com.google.zxing.common.BitMatrix +import userpackage.Protocol +import userpackage.Protocol.ExportBundle +import userpackage.Protocol.URLInfo + +class PolycentricBackupActivity : AppCompatActivity() { + private lateinit var _buttonShare: BigButton; + private lateinit var _buttonCopy: BigButton; + private lateinit var _imageQR: ImageView; + private lateinit var _exportBundle: String; + private lateinit var _textQR: TextView; + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_polycentric_backup); + setNavigationBarColorAndIcons(); + + _buttonShare = findViewById(R.id.button_share); + _buttonCopy = findViewById(R.id.button_copy); + _imageQR = findViewById(R.id.image_qr); + _textQR = findViewById(R.id.text_qr); + findViewById(R.id.button_back).setOnClickListener { + finish(); + }; + + _exportBundle = createExportBundle(); + + try { + val dimension = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200f, resources.displayMetrics).toInt(); + val qrCodeBitmap = generateQRCode(_exportBundle, dimension, dimension); + _imageQR.setImageBitmap(qrCodeBitmap); + } catch (e: Exception) { + Logger.e(TAG, "Failed to generate QR code", e); + _imageQR.visibility = View.INVISIBLE; + _textQR.visibility = View.INVISIBLE; + } + + _buttonShare.onClick.subscribe { + val shareIntent = Intent(Intent.ACTION_SEND).apply { + type = "text/plain"; + putExtra(Intent.EXTRA_TEXT, _exportBundle); + } + startActivity(Intent.createChooser(shareIntent, "Share Text")); + }; + + _buttonCopy.onClick.subscribe { + val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager; + val clip = ClipData.newPlainText("Copied Text", _exportBundle); + clipboard.setPrimaryClip(clip); + }; + } + + private fun generateQRCode(content: String, width: Int, height: Int): Bitmap { + val bitMatrix = MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height); + return bitMatrixToBitmap(bitMatrix); + } + + private fun bitMatrixToBitmap(matrix: BitMatrix): Bitmap { + val width = matrix.width; + val height = matrix.height; + val bmp = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); + + for (x in 0 until width) { + for (y in 0 until height) { + bmp.setPixel(x, y, if (matrix[x, y]) Color.BLACK else Color.WHITE); + } + } + return bmp; + } + + private fun createExportBundle(): String { + val processHandle = StatePolycentric.instance.processHandle!!; + + val relevantContentTypes = listOf(ContentType.SERVER.value, ContentType.AVATAR.value, ContentType.USERNAME.value); + val crdtSetItems = arrayListOf>(); + val crdtItems = arrayListOf>(); + + Store.instance.enumerateSignedEvents(processHandle.system) { signedEvent -> + if (!relevantContentTypes.contains(signedEvent.event.contentType)) { + return@enumerateSignedEvents; + } + + val event = signedEvent.event; + event.lwwElementSet?.let { lwwElementSet -> + val foundIndex = crdtSetItems.indexOfFirst { pair -> + pair.second.contentType == event.contentType && pair.second.value.contentEquals(lwwElementSet.value) + } + + var found = false + if (foundIndex != -1) { + val foundPair = crdtSetItems[foundIndex] + if (foundPair.second.unixMilliseconds < lwwElementSet.unixMilliseconds) { + foundPair.second.operation = lwwElementSet.operation + foundPair.second.unixMilliseconds = lwwElementSet.unixMilliseconds + found = true + } + } + + if (!found) { + crdtSetItems.add(Pair(signedEvent, StorageTypeCRDTSetItem(event.contentType, lwwElementSet.value, lwwElementSet.unixMilliseconds, lwwElementSet.operation))) + } + } + + event.lwwElement?.let { lwwElement -> + val foundIndex = crdtItems.indexOfFirst { pair -> + pair.second.contentType == event.contentType + } + + var found = false + if (foundIndex != -1) { + val foundPair = crdtItems[foundIndex] + if (foundPair.second.unixMilliseconds < lwwElement.unixMilliseconds) { + foundPair.second.value = lwwElement.value + foundPair.second.unixMilliseconds = lwwElement.unixMilliseconds + found = true + } + } + + if (!found) { + crdtItems.add(Pair(signedEvent, StorageTypeCRDTItem(event.contentType, lwwElement.value, lwwElement.unixMilliseconds))) + } + } + }; + + val relevantEvents = arrayListOf(); + for (pair in crdtSetItems) { + relevantEvents.add(pair.first); + } + + for (pair in crdtItems) { + relevantEvents.add(pair.first); + } + + val exportBundle = ExportBundle.newBuilder() + .setKeyPair(processHandle.processSecret.system.toProto()) + .setEvents(Protocol.Events.newBuilder() + .addAllEvents(relevantEvents.map { it.toProto() }) + .build()) + .build(); + + val urlInfo = URLInfo.newBuilder() + .setUrlType(3) + .setBody(exportBundle.toByteString()) + .build(); + + return "polycentric://" + urlInfo.toByteArray().toBase64Url() + } + + companion object { + private const val TAG = "PolycentricBackupActivity"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/activities/PolycentricCreateProfileActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/PolycentricCreateProfileActivity.kt new file mode 100644 index 00000000..a8889ad9 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricCreateProfileActivity.kt @@ -0,0 +1,93 @@ +package com.futo.platformplayer.activities + +import android.content.Intent +import android.os.Bundle +import android.widget.EditText +import android.widget.ImageButton +import android.widget.LinearLayout +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import com.futo.platformplayer.R +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.setNavigationBarColorAndIcons +import com.futo.platformplayer.states.StatePolycentric +import com.futo.polycentric.core.ProcessHandle +import com.futo.polycentric.core.Store +import com.futo.polycentric.core.Synchronization +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class PolycentricCreateProfileActivity : AppCompatActivity() { + private lateinit var _buttonHelp: ImageButton; + private lateinit var _profileName: EditText; + private lateinit var _buttonCreate: LinearLayout; + private val TAG = "PolycentricCreateProfileActivity"; + + private var _creating = false; + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_polycentric_create_profile); + setNavigationBarColorAndIcons(); + + _buttonHelp = findViewById(R.id.button_help); + _profileName = findViewById(R.id.edit_profile_name); + _buttonCreate = findViewById(R.id.button_create_profile); + findViewById(R.id.button_back).setOnClickListener { + finish(); + }; + + _buttonHelp.setOnClickListener { + startActivity(Intent(this, PolycentricWhyActivity::class.java)); + }; + + _buttonCreate.setOnClickListener { + if (_creating) { + return@setOnClickListener; + } + + _creating = true; + + try { + val username = _profileName.text.toString(); + if (username.length < 3) { + UIDialogs.toast(this@PolycentricCreateProfileActivity, "Must be at least 3 characters long."); + return@setOnClickListener; + } + + lifecycleScope.launch(Dispatchers.IO) { + val processHandle: ProcessHandle; + + try { + processHandle = ProcessHandle.create(); + Store.instance.addProcessSecret(processHandle.processSecret); + processHandle.addServer("https://srv1-stg.polycentric.io"); + processHandle.setUsername(username); + StatePolycentric.instance.setProcessHandle(processHandle); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to create profile .", e); + return@launch; + } finally { + _creating = false; + } + + try { + processHandle.fullyBackfillServers(); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to fully backfill servers."); + } + + withContext(Dispatchers.Main) { + startActivity(Intent(this@PolycentricCreateProfileActivity, PolycentricProfileActivity::class.java)); + finish(); + } + } + } finally { + _creating = false; + } + }; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/activities/PolycentricHomeActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/PolycentricHomeActivity.kt new file mode 100644 index 00000000..2f691c80 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricHomeActivity.kt @@ -0,0 +1,94 @@ +package com.futo.platformplayer.activities + +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.util.TypedValue +import android.widget.ImageButton +import android.widget.LinearLayout +import androidx.appcompat.app.AppCompatActivity +import com.bumptech.glide.Glide +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.futo.platformplayer.R +import com.futo.platformplayer.dp +import com.futo.platformplayer.selectBestImage +import com.futo.platformplayer.setNavigationBarColorAndIcons +import com.futo.platformplayer.states.StatePolycentric +import com.futo.platformplayer.views.buttons.BigButton +import com.futo.polycentric.core.Store +import com.futo.polycentric.core.SystemState +import com.futo.polycentric.core.toURLInfoSystemLinkUrl + +class PolycentricHomeActivity : AppCompatActivity() { + private lateinit var _buttonHelp: ImageButton; + private lateinit var _buttonNewProfile: BigButton; + private lateinit var _buttonImportProfile: BigButton; + private lateinit var _layoutButtons: LinearLayout; + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_polycentric_home); + setNavigationBarColorAndIcons(); + + _buttonHelp = findViewById(R.id.button_help); + _buttonNewProfile = findViewById(R.id.button_new_profile); + _buttonImportProfile = findViewById(R.id.button_import_profile); + _layoutButtons = findViewById(R.id.layout_buttons); + findViewById(R.id.button_back).setOnClickListener { + finish(); + }; + + for (processHandle in StatePolycentric.instance.getProcessHandles()) { + val systemState = SystemState.fromStorageTypeSystemState(Store.instance.getSystemState(processHandle.system)); + val profileButton = BigButton(this); + profileButton.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply { + this.setMargins(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, resources.displayMetrics).toInt()); + }; + profileButton.withPrimaryText(systemState.username); + profileButton.withSecondaryText("Sign in to this identity"); + profileButton.onClick.subscribe { + StatePolycentric.instance.setProcessHandle(processHandle); + startActivity(Intent(this@PolycentricHomeActivity, PolycentricProfileActivity::class.java)); + finish(); + } + + val dp_32 = 32.dp(resources) + val avatarUrl = systemState.avatar.selectBestImage(dp_32 * dp_32)?.toURLInfoSystemLinkUrl(processHandle, systemState.servers.toList()); + Glide.with(profileButton) + .asBitmap() + .load(avatarUrl) + .placeholder(R.drawable.ic_loader) + .fallback(R.drawable.placeholder_profile) + .into(object : CustomTarget() { + override fun onResourceReady(resource: Bitmap, transition: Transition?) { + profileButton.withIcon(resource, true) + } + override fun onLoadCleared(placeholder: Drawable?) { + profileButton.withIcon(R.drawable.placeholder_profile) + } + }) + + _layoutButtons.addView(profileButton, 0); + } + + _buttonHelp.setOnClickListener { + startActivity(Intent(this, PolycentricWhyActivity::class.java)); + }; + + _buttonNewProfile.onClick.subscribe { + startActivity(Intent(this, PolycentricCreateProfileActivity::class.java)); + finish(); + }; + + _buttonImportProfile.onClick.subscribe { + startActivity(Intent(this, PolycentricImportProfileActivity::class.java)); + finish(); + } + } + + companion object { + private const val TAG = "PolycentricHomeActivity"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt new file mode 100644 index 00000000..2a92cbf1 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt @@ -0,0 +1,129 @@ +package com.futo.platformplayer.activities + +import android.content.Intent +import android.os.Bundle +import android.widget.EditText +import android.widget.ImageButton +import android.widget.LinearLayout +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import com.futo.platformplayer.R +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.setNavigationBarColorAndIcons +import com.futo.platformplayer.states.StatePolycentric +import com.futo.polycentric.core.* +import com.google.zxing.integration.android.IntentIntegrator +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import userpackage.Protocol +import userpackage.Protocol.ExportBundle + +class PolycentricImportProfileActivity : AppCompatActivity() { + private lateinit var _buttonHelp: ImageButton; + private lateinit var _buttonScanProfile: LinearLayout; + private lateinit var _buttonImportProfile: LinearLayout; + private lateinit var _editProfile: EditText; + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_polycentric_import_profile); + setNavigationBarColorAndIcons(); + + _buttonHelp = findViewById(R.id.button_help); + _buttonScanProfile = findViewById(R.id.button_scan_profile); + _buttonImportProfile = findViewById(R.id.button_import_profile); + _editProfile = findViewById(R.id.edit_profile); + findViewById(R.id.button_back).setOnClickListener { + finish(); + }; + + _buttonHelp.setOnClickListener { + startActivity(Intent(this, PolycentricWhyActivity::class.java)); + }; + + _buttonScanProfile.setOnClickListener { + val integrator = IntentIntegrator(this); + integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE); + integrator.setPrompt("Scan a QR code"); + integrator.initiateScan(); + }; + + _buttonImportProfile.setOnClickListener { + if (_editProfile.text.isEmpty()) { + UIDialogs.toast(this, "Text field does not contain any data"); + return@setOnClickListener; + } + + import(_editProfile.text.toString()); + }; + + val url = intent.getStringExtra("url"); + if (url != null) { + import(url); + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + val result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data) + if (result != null) { + if (result.contents != null) { + val scannedUrl = result.contents; + import(scannedUrl); + } + } else { + super.onActivityResult(requestCode, resultCode, data) + } + } + + private fun import(url: String) { + if (!url.startsWith("polycentric://")) { + UIDialogs.toast(this, "Not a valid URL"); + return; + } + + try { + val data = url.substring("polycentric://".length).base64UrlToByteArray(); + val urlInfo = Protocol.URLInfo.parseFrom(data); + if (urlInfo.urlType != 3L) { + throw Exception("Expected urlInfo struct of type ExportBundle") + } + + val exportBundle = ExportBundle.parseFrom(urlInfo.body); + val keyPair = KeyPair.fromProto(exportBundle.keyPair); + + val existingProcessSecret = Store.instance.getProcessSecret(keyPair.publicKey); + if (existingProcessSecret != null) { + UIDialogs.toast(this, "This profile is already imported"); + return; + } + + val processSecret = ProcessSecret(keyPair, Process.random()); + Store.instance.addProcessSecret(processSecret); + + val processHandle = processSecret.toProcessHandle(); + + for (e in exportBundle.events.eventsList) { + try { + val se = SignedEvent.fromProto(e); + Store.instance.putSignedEvent(se); + } catch (e: Throwable) { + Logger.w(TAG, "Ignored invalid event", e); + } + } + + StatePolycentric.instance.setProcessHandle(processHandle); + startActivity(Intent(this@PolycentricImportProfileActivity, PolycentricProfileActivity::class.java)); + finish(); + } catch (e: Throwable) { + Logger.w(TAG, "Failed to import profile", e); + UIDialogs.toast(this, "Failed to import profile: '${e.message}'"); + } + } + + companion object { + private const val TAG = "PolycentricImportProfileActivity"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/activities/PolycentricProfileActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/PolycentricProfileActivity.kt new file mode 100644 index 00000000..67c66aae --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricProfileActivity.kt @@ -0,0 +1,283 @@ +package com.futo.platformplayer.activities + +import android.app.Activity +import android.content.ContentResolver +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import android.os.Bundle +import android.webkit.MimeTypeMap +import android.widget.EditText +import android.widget.ImageButton +import android.widget.ImageView +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.dp +import com.futo.platformplayer.images.GlideHelper.Companion.crossfade +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.selectBestImage +import com.futo.platformplayer.setNavigationBarColorAndIcons +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StatePolycentric +import com.futo.platformplayer.views.buttons.BigButton +import com.futo.polycentric.core.Store +import com.futo.polycentric.core.Synchronization +import com.futo.polycentric.core.SystemState +import com.futo.polycentric.core.toURLInfoDataLink +import com.github.dhaval2404.imagepicker.ImagePicker +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import userpackage.Protocol +import java.io.ByteArrayOutputStream +import java.io.InputStream + +class PolycentricProfileActivity : AppCompatActivity() { + private lateinit var _buttonHelp: ImageButton; + private lateinit var _editName: EditText; + private lateinit var _buttonExport: BigButton; + private lateinit var _buttonLogout: BigButton; + private lateinit var _buttonDelete: BigButton; + private lateinit var _username: String; + private lateinit var _imagePolycentric: ImageView; + private var _avatarUri: Uri? = null; + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_polycentric_profile); + setNavigationBarColorAndIcons(); + + _buttonHelp = findViewById(R.id.button_help); + _imagePolycentric = findViewById(R.id.image_polycentric); + _editName = findViewById(R.id.edit_profile_name); + _buttonExport = findViewById(R.id.button_export); + _buttonLogout = findViewById(R.id.button_logout); + _buttonDelete = findViewById(R.id.button_delete); + findViewById(R.id.button_back).setOnClickListener { + saveIfRequired(); + finish(); + }; + + lifecycleScope.launch(Dispatchers.IO) { + try { + val processHandle = StatePolycentric.instance.processHandle!!; + Synchronization.fullyBackFillClient(processHandle, processHandle.system, "https://srv1-stg.polycentric.io"); + + withContext(Dispatchers.Main) { + updateUI(); + } + } catch (e: Throwable) { + withContext(Dispatchers.Main) { + UIDialogs.toast(this@PolycentricProfileActivity, "Failed to backfill client"); + } + } + } + + updateUI(); + + _imagePolycentric.setOnClickListener { + ImagePicker.with(this) + .cropSquare() + .maxResultSize(256, 256) + .start(); + } + + _buttonHelp.setOnClickListener { + startActivity(Intent(this, PolycentricWhyActivity::class.java)); + }; + + _buttonExport.onClick.subscribe { + startActivity(Intent(this, PolycentricBackupActivity::class.java)); + }; + + _buttonLogout.onClick.subscribe { + StatePolycentric.instance.setProcessHandle(null); + startActivity(Intent(this, PolycentricHomeActivity::class.java)); + finish(); + } + + _buttonDelete.onClick.subscribe { + UIDialogs.showConfirmationDialog(this, "Are you sure you want to remove this profile?", { + val processHandle = StatePolycentric.instance.processHandle; + if (processHandle == null) { + UIDialogs.toast(this, "No process handle set"); + return@showConfirmationDialog; + } + + StatePolycentric.instance.setProcessHandle(null); + Store.instance.removeProcessSecret(processHandle.system); + startActivity(Intent(this, PolycentricHomeActivity::class.java)); + finish(); + }); + } + } + + private fun saveIfRequired() { + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { + try { + var hasChanges = false; + val username = _editName.text.toString(); + if (username.length < 3) { + UIDialogs.toast(this@PolycentricProfileActivity, "Name must be at least 3 characters long"); + return@launch; + } + + val processHandle = StatePolycentric.instance.processHandle; + if (processHandle == null) { + UIDialogs.toast(this@PolycentricProfileActivity, "Process handle unset"); + return@launch; + } + + if (_username != username) { + _username = username; + processHandle.setUsername(username); + hasChanges = true; + } + + val avatarUri = _avatarUri; + if (avatarUri != null) { + val bytes = readBytesFromUri(applicationContext.contentResolver, avatarUri); + if (bytes == null) { + withContext(Dispatchers.Main) { + UIDialogs.toast(this@PolycentricProfileActivity, "Failed to read image"); + } + + return@launch; + } + + val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size); + val imageBundleBuilder = Protocol.ImageBundle.newBuilder(); + val resolutions = arrayListOf(256, 128, 32); + for (resolution in resolutions) { + val image = Bitmap.createScaledBitmap(bitmap, resolution, resolution, true) + + val outputStream = ByteArrayOutputStream() + val originalMimeType = getMimeType(applicationContext.contentResolver, avatarUri) ?: "image/png" + val compressFormat = when(originalMimeType) { + "image/png" -> Pair(Bitmap.CompressFormat.PNG, "image/png") + "image/jpeg" -> Pair(Bitmap.CompressFormat.JPEG, "image/jpeg") + else -> Pair(Bitmap.CompressFormat.PNG, "image/png") + } + image.compress(compressFormat.first, 100, outputStream) + val imageBytes = outputStream.toByteArray() + + val imageRanges = processHandle.publishBlob(imageBytes) + val imageManifest = Protocol.ImageManifest.newBuilder() + .setMime(compressFormat.second) + .setWidth(image.width.toLong()) + .setHeight(image.height.toLong()) + .setByteCount(imageBytes.size.toLong()) + .setProcess(processHandle.processSecret.process.toProto()) + .addAllSections(imageRanges.map { it.toProto() }) + .build() + + imageBundleBuilder.addImageManifests(imageManifest) + } + + processHandle.setAvatar(imageBundleBuilder.build()) + hasChanges = true; + + _avatarUri = null; + } + + if (hasChanges) { + try { + processHandle.fullyBackfillServers(); + withContext(Dispatchers.Main) { + UIDialogs.toast(this@PolycentricProfileActivity, "Changes have been saved"); + } + } catch (e: Throwable) { + Logger.w(TAG, "Failed to synchronize changes", e); + withContext(Dispatchers.Main) { + UIDialogs.toast(this@PolycentricProfileActivity, "Failed to synchronize changes"); + } + } + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to save polycentric profile.", e) + } + } + } + + override fun onBackPressed() { + saveIfRequired(); + super.onBackPressed(); + } + + private fun updateUI() { + val processHandle = StatePolycentric.instance.processHandle!!; + val systemState = SystemState.fromStorageTypeSystemState(Store.instance.getSystemState(processHandle.system)) + _username = systemState.username; + _editName.text.clear(); + _editName.text.append(_username); + + val dp_80 = 80.dp(resources) + val avatar = systemState.avatar.selectBestImage(dp_80 * dp_80); + + Glide.with(_imagePolycentric) + .load(avatar?.toURLInfoDataLink(processHandle.system.toProto(), processHandle.processSecret.process.toProto(), systemState.servers.toList())) + .placeholder(R.drawable.placeholder_profile) + .crossfade() + .into(_imagePolycentric) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data); + + if (resultCode == Activity.RESULT_OK) { + val uri: Uri = data?.data!! + _imagePolycentric.setImageURI(uri); + _avatarUri = uri; + } else if (resultCode == ImagePicker.RESULT_ERROR) { + UIDialogs.toast(this, ImagePicker.getError(data)); + } else { + UIDialogs.toast(this, "Image picker cancelled"); + } + } + + private fun getMimeType(contentResolver: ContentResolver, uri: Uri): String? { + var mimeType: String? = null; + + // Try to get MIME type from the content URI + mimeType = contentResolver.getType(uri); + + // If the MIME type couldn't be determined from the content URI, try using the file extension + if (mimeType == null) { + val fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString()); + mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.lowercase()); + } + + return mimeType; + } + + private fun readBytesFromUri(contentResolver: ContentResolver, uri: Uri): ByteArray? { + var inputStream: InputStream? = null; + val outputStream = ByteArrayOutputStream(); + + try { + inputStream = contentResolver.openInputStream(uri); + if (inputStream != null) { + val buffer = ByteArray(4096); + var bytesRead: Int; + while (inputStream.read(buffer).also { bytesRead = it } != -1) { + outputStream.write(buffer, 0, bytesRead); + } + return outputStream.toByteArray() + } + } catch (e: Exception) { + Logger.w(TAG, "Failed to read bytes from URI '${uri}'."); + } finally { + inputStream?.close(); + outputStream.close(); + } + return null + } + + companion object { + private const val TAG = "PolycentricProfileActivity"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/activities/PolycentricWhyActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/PolycentricWhyActivity.kt new file mode 100644 index 00000000..17c9acfd --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricWhyActivity.kt @@ -0,0 +1,41 @@ +package com.futo.platformplayer.activities + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.widget.ImageButton +import androidx.appcompat.app.AppCompatActivity +import com.futo.platformplayer.R +import com.futo.platformplayer.setNavigationBarColorAndIcons +import com.futo.platformplayer.views.buttons.BigButton + +class PolycentricWhyActivity : AppCompatActivity() { + private lateinit var _buttonVideo: BigButton; + private lateinit var _buttonTechnical: BigButton; + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_polycentric_why); + setNavigationBarColorAndIcons(); + + _buttonVideo = findViewById(R.id.button_video); + _buttonTechnical = findViewById(R.id.button_technical); + findViewById(R.id.button_back).setOnClickListener { + finish(); + }; + + _buttonVideo.onClick.subscribe { + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://www.youtube.com/watch?v=xYL96hb_p78")); + startActivity(browserIntent); + }; + + _buttonTechnical.onClick.subscribe { + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("https://docs.polycentric.io")); + startActivity(browserIntent); + }; + } + + companion object { + private const val TAG = "PolycentricWhyActivity"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/activities/SettingsActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/SettingsActivity.kt new file mode 100644 index 00000000..e3c4bed6 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/activities/SettingsActivity.kt @@ -0,0 +1,93 @@ +package com.futo.platformplayer.activities + +import android.annotation.SuppressLint +import android.content.Intent +import android.os.Bundle +import android.view.View +import android.widget.ImageButton +import android.widget.LinearLayout +import androidx.appcompat.app.AppCompatActivity +import com.futo.platformplayer.* +import com.futo.platformplayer.views.fields.FieldForm +import com.futo.platformplayer.views.fields.ReadOnlyTextField +import com.google.android.material.button.MaterialButton + +class SettingsActivity : AppCompatActivity() { + private lateinit var _form: FieldForm; + private lateinit var _buttonBack: ImageButton; + + private lateinit var _devSets: LinearLayout; + private lateinit var _buttonDev: MaterialButton; + + private var _isFinished = false; + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_settings); + setNavigationBarColorAndIcons(); + + _form = findViewById(R.id.settings_form); + _buttonBack = findViewById(R.id.button_back); + _buttonDev = findViewById(R.id.button_dev); + _devSets = findViewById(R.id.dev_settings); + + _form.fromObject(Settings.instance); + _form.onChanged.subscribe { field, value -> + _form.setObjectValues(); + Settings.instance.save(); + }; + _buttonBack.setOnClickListener { + finish(); + } + + _buttonDev.setOnClickListener { + startActivity(Intent(this, DeveloperActivity::class.java)); + } + + var devCounter = 0; + _form.findField("code")?.assume()?.setOnClickListener { + devCounter++; + if(devCounter > 5) { + devCounter = 0; + SettingsDev.instance.developerMode = true; + SettingsDev.instance.save(); + updateDevMode(); + UIDialogs.toast(this, "You are now in developer mode"); + } + }; + _lastActivity = this; + } + + override fun onResume() { + super.onResume() + updateDevMode(); + } + + fun updateDevMode() { + if(SettingsDev.instance.developerMode) + _devSets.visibility = View.VISIBLE; + else + _devSets.visibility = View.GONE; + } + + override fun finish() { + super.finish() + _isFinished = true; + if(_lastActivity == this) + _lastActivity = null; + overridePendingTransition(R.anim.slide_lighten, R.anim.slide_out_up) + } + + companion object { + //TODO: Temporary for solving Settings issues + @SuppressLint("StaticFieldLeak") + private var _lastActivity: SettingsActivity? = null; + + fun getActivity(): SettingsActivity? { + val act = _lastActivity; + if(act != null && !act._isFinished) + return act; + return null; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/activities/TestActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/TestActivity.kt new file mode 100644 index 00000000..608bda0a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/activities/TestActivity.kt @@ -0,0 +1,16 @@ +package com.futo.platformplayer.activities + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.futo.platformplayer.R + +class TestActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_test); + } + + companion object { + private const val TAG = "TestActivity"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/http/ManagedHttpClient.kt b/app/src/main/java/com/futo/platformplayer/api/http/ManagedHttpClient.kt new file mode 100644 index 00000000..22705451 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/http/ManagedHttpClient.kt @@ -0,0 +1,276 @@ +package com.futo.platformplayer.api.http + +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.ensureNotMainThread +import com.futo.platformplayer.logging.Logger +import okhttp3.Call +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.OkHttpClient +import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response +import okhttp3.ResponseBody +import okhttp3.WebSocket +import okhttp3.WebSocketListener +import java.util.Dictionary +import java.util.concurrent.TimeUnit +import kotlin.system.measureTimeMillis + +open class ManagedHttpClient { + protected val _builderTemplate: OkHttpClient.Builder; + + private var client: OkHttpClient; + + private var onBeforeRequest : ((Request) -> Unit)? = null; + private var onAfterRequest : ((Request, Response) -> Unit)? = null; + + var user_agent = "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0" + + constructor(builder: OkHttpClient.Builder = OkHttpClient.Builder()) { + _builderTemplate = builder; + client = builder.build(); + } + + open fun clone(): ManagedHttpClient { + val clonedClient = ManagedHttpClient(_builderTemplate); + clonedClient.user_agent = user_agent; + return clonedClient; + } + + fun tryHead(url: String): Map? { + try { + val result = head(url); + if(result.isOk) + return result.getHeadersFlat(); + else + return null; + } + catch(ex: Throwable) { + //Ignore + return null; + } + } + + fun socket(url: String, headers: MutableMap = HashMap(), listener: SocketListener): Socket { + + val requestBuilder: okhttp3.Request.Builder = okhttp3.Request.Builder() + .url(url); + if(user_agent != null && !user_agent.isEmpty() && !headers.any { it.key.lowercase() == "user-agent" }) + requestBuilder.addHeader("User-Agent", user_agent) + + for (pair in headers.entries) + requestBuilder.header(pair.key, pair.value); + + val request = requestBuilder.build(); + + val websocket = client.newWebSocket(request, object: WebSocketListener() { + override fun onOpen(webSocket: WebSocket, response: okhttp3.Response) { + super.onOpen(webSocket, response); + listener.open(); + } + override fun onMessage(webSocket: WebSocket, text: String) { + super.onMessage(webSocket, text) + listener.message(text); + } + override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { + super.onClosing(webSocket, code, reason); + listener.closing(code, reason); + } + override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { + super.onClosed(webSocket, code, reason); + listener.closed(code, reason); + } + override fun onFailure(webSocket: WebSocket, t: Throwable, response: okhttp3.Response?) { + super.onFailure(webSocket, t, response); + listener.failure(t); + } + }); + return Socket(websocket); + } + + fun get(url : String, headers : MutableMap = HashMap()) : Response { + return execute(Request(url, "GET", null, headers)); + } + + fun head(url : String, headers : MutableMap = HashMap()) : Response { + return execute(Request(url, "HEAD", null, headers)); + } + + fun post(url : String, headers : MutableMap = HashMap()) : Response { + return execute(Request(url, "POST", ByteArray(0), headers)); + } + fun post(url : String, body : String, headers : MutableMap = HashMap()) : Response { + return post(url, body.toByteArray(), headers); + } + fun post(url : String, body : ByteArray, headers : MutableMap = HashMap()) : Response { + return execute(Request(url, "POST", body, headers)); + } + + fun requestMethod(method: String, url : String, headers : MutableMap = HashMap()) : Response { + return execute(Request(url, method, null, headers)); + } + fun requestMethod(method: String, url : String, body: String?, headers : MutableMap = HashMap()) : Response { + return execute(Request(url, method, body?.toByteArray(), headers)); + } + + fun execute(request : Request) : Response { + ensureNotMainThread(); + + beforeRequest(request); + + Logger.v(TAG, "HTTP Request [${request.method}] ${request.url} - [${if(request.body != null) request.body.size else 0}]"); + + var requestBody: RequestBody? = null + if (request.body != null) { + val ct = request.getContentType(); + if(ct != null) + requestBody = request.body.toRequestBody(ct.toMediaTypeOrNull(), 0, request.body.size); + else + requestBody = request.body.toRequestBody(null, 0, request.body.size); + } + + val requestBuilder: okhttp3.Request.Builder = okhttp3.Request.Builder() + .method(request.method, requestBody) + .url(request.url); + if(user_agent != null && !user_agent.isEmpty() && !request.headers.any { it.key.lowercase() == "user-agent" }) + requestBuilder.addHeader("User-Agent", user_agent) + + for (pair in request.headers.entries) + requestBuilder.header(pair.key, pair.value); + + val response: okhttp3.Response; + val resp: Response; + + val time = measureTimeMillis { + val call = client.newCall(requestBuilder.build()); + request.onCallCreated?.emit(call); + response = call.execute() + resp = Response( + response.code, + response.request.url.toString(), + response.message, + response.headers.toMultimap(), + response.body + ) + } + if(true) + Logger.v(TAG, "HTTP Response [${request.method}] ${request.url} - [${time}ms]"); + + afterRequest(request, resp); + return resp; + } + + //Set Listeners + fun setOnBeforeRequest(listener : (Request)->Unit) { + this.onBeforeRequest = listener; + } + fun setOnAfterRequest(listener : (Request, Response)->Unit) { + this.onAfterRequest = listener; + } + + open fun beforeRequest(request: Request) { + onBeforeRequest?.invoke(request); + } + open fun afterRequest(request: Request, resp: Response) { + onAfterRequest?.invoke(request, resp); + } + + + class Request + { + val url : String; + val method : String; + val body : ByteArray?; + val headers : MutableMap; + + val onCallCreated = Event1(); + + constructor(url : String, method : String, body : ByteArray?, headers : MutableMap = HashMap()) { + this.url = url; + this.method = method; + this.body = body; + this.headers = headers; + } + + fun getContentType(): String? { + val ct = headers.keys.find { it.lowercase() == "content-type" }; + if(ct != null) + return headers[ct]; + return null; + } + } + + //TODO: Wrap ResponseBody into a non-library class? + class Response + { + val code : Int; + val url : String; + val message : String; + val headers : Map>; + val body : ResponseBody?; + + val isOk : Boolean get() = code >= 200 && code < 300; + + constructor(code : Int, url : String, msg : String, headers : Map>, body : ResponseBody?) { + this.code = code; + this.url = url; + this.message = msg; + this.headers = headers; + this.body = body; + } + + fun getHeader(key: String): List? { + for(header in headers) { + if (header.key.equals(key, ignoreCase = true)) { + return header.value; + }; + } + + return null; + } + + fun getHeaderFlat(key: String): String? { + for(header in headers) { + if (header.key.equals(key, ignoreCase = true)) { + return header.value.joinToString(", ") + }; + } + + return null; + } + + fun getHeadersFlat(): MutableMap { + val map = HashMap(); + for(header in headers) + map.put(header.key, header.value.joinToString(", ")); + return map; + } + } + + class Socket { + private val socket: WebSocket; + + constructor(socket: WebSocket) { + this.socket = socket; + } + + fun send(msg: String) { + socket.send(msg); + } + + fun close(code: Int, reason: String) { + socket.close(code, reason); + } + } + interface SocketListener { + fun open(); + fun message(msg: String); + fun closing(code: Int, reason: String); + fun closed(code: Int, reason: String); + fun failure(exception: Throwable); + } + + companion object { + val TAG = "ManagedHttpClient"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/HttpBridge.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/HttpBridge.kt new file mode 100644 index 00000000..5a5f0081 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/HttpBridge.kt @@ -0,0 +1,9 @@ +package com.futo.platformplayer.api.http.server + +@Target(AnnotationTarget.FIELD, AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class HttpGET(val path: String, val contentType: String = ""); + +@Target(AnnotationTarget.FIELD, AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class HttpPOST(val path: String, val contentType: String = ""); diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/HttpContext.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/HttpContext.kt new file mode 100644 index 00000000..1d0a525c --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/HttpContext.kt @@ -0,0 +1,285 @@ +package com.futo.platformplayer.api.http.server + +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.api.http.server.exceptions.EmptyRequestException +import com.futo.platformplayer.api.http.server.exceptions.KeepAliveTimeoutException +import com.futo.platformplayer.api.media.Serializer +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.io.BufferedReader +import java.io.OutputStream +import java.io.StringWriter +import java.net.SocketTimeoutException + +class HttpContext : AutoCloseable { + private val _stream: BufferedReader; + private var _responseStream: OutputStream? = null; + + var id: String? = null; + + var head: String = ""; + var headers: HttpHeaders = HttpHeaders(); + + var method: String = ""; + var path: String = ""; + var query = mutableMapOf(); + + var contentType: String? = null; + var contentLength: Long = 0; + + var keepAlive: Boolean = false; + var keepAliveTimeout: Int = 0; + var keepAliveMax: Int = 0; + + var _totalRead: Long = 0; + + var statusCode: Int = -1; + + private val _responseHeaders: HttpHeaders = HttpHeaders(); + + + constructor(stream: BufferedReader, responseStream: OutputStream? = null, requestId: String? = null, timeout: Int? = null) { + _stream = stream; + _responseStream = responseStream; + this.id = requestId; + + try { + head = stream.readLine() ?: throw EmptyRequestException("No head found"); + } + catch(ex: SocketTimeoutException) { + if((timeout ?: 0) > 0) + throw KeepAliveTimeoutException("Keep-Alive timedout", ex); + throw ex; + } + + val methodEndIndex = head.indexOf(' '); + val urlEndIndex = head.indexOf(' ', methodEndIndex + 1); + if (methodEndIndex == -1 || urlEndIndex == -1) { + Logger.w(TAG, "Skipped request, wrong format."); + throw IllegalStateException("Invalid request"); + } + + method = head.substring(0, methodEndIndex); + path = head.substring(methodEndIndex + 1, urlEndIndex); + + if (path.contains("?")) { + val queryPartIndex = path.indexOf("?"); + val queryParts = path.substring(queryPartIndex + 1).split("&"); + path = path.substring(0, queryPartIndex); + + for(queryPart in queryParts) { + val eqIndex = queryPart.indexOf("="); + if(eqIndex > 0) + query.put(queryPart.substring(0, eqIndex), queryPart.substring(eqIndex + 1)); + else + query.put(queryPart, ""); + } + } + + while (true) { + val line = stream.readLine(); + val headerEndIndex = line.indexOf(":"); + if (headerEndIndex == -1) + break; + + val headerKey = line.substring(0, headerEndIndex).lowercase() + val headerValue = line.substring(headerEndIndex + 1).trim(); + headers[headerKey] = headerValue; + + when(headerKey) { + "content-length" -> contentLength = headerValue.toLong(); + "content-type" -> contentType = headerValue; + "connection" -> keepAlive = headerValue.lowercase() == "keep-alive"; + "keep-alive" -> { + val keepAliveParams = headerValue.split(","); + for(keepAliveParam in keepAliveParams) { + val eqIndex = keepAliveParam.indexOf("="); + if(eqIndex > 0){ + when(keepAliveParam.substring(0, eqIndex)) { + "timeout" -> keepAliveTimeout = keepAliveParam.substring(eqIndex+1).toInt(); + "max" -> keepAliveTimeout = keepAliveParam.substring(eqIndex+1).toInt(); + } + } + } + } + } + if(line.isNullOrEmpty()) + break; + } + } + + fun getHttpHeaderString(): String { + val writer = StringWriter(); + writer.write(head + "\r\n"); + for(header in headers) { + writer.write("${header.key}: ${header.value}\r\n"); + } + writer.write("\r\n"); + return writer.toString(); + } + + fun getHeader(header: String) : String? { + return headers[header.lowercase()]; + } + fun setResponseHeaders(vararg respHeaders: Pair) { + for(header in respHeaders) + _responseHeaders.put(header.first, header.second); + } + fun setResponseHeaders(respHeaders: HttpHeaders) { + for(header in respHeaders) + _responseHeaders.put(header.key, header.value); + } + + inline fun respondJson(status: Int, body: T) { + respondCode(status, Json.encodeToString(body), "application/json"); + } + fun respondCode(status: Int, body: String = "", contentType: String = "text/plain") { + respondCode(status, HttpHeaders(Pair("Content-Type", contentType)), body); + } + fun respondCode(status: Int, headers: HttpHeaders, body: String? = null) { + val bytes = body?.toByteArray(Charsets.UTF_8); + if(body != null && headers.get("content-length").isNullOrEmpty()) + headers.put("content-length", bytes!!.size.toString()); + respond(status, headers) { responseStream -> + if(body != null) { + responseStream.write(bytes!!); + } + } + } + fun respond(status: Int, headers: HttpHeaders, writing: (OutputStream)->Unit) { + val responseStream = _responseStream ?: throw IllegalStateException("No response stream set"); + + val headersToRespond = headers.toMutableMap(); + + for(preHeader in _responseHeaders) + if(!headersToRespond.containsKey(preHeader.key)) + headersToRespond.put(preHeader.key, preHeader.value); + + if(keepAlive) { + headersToRespond.put("connection", "keep-alive"); + headersToRespond.put("keep-alive", "timeout=5, max=1000"); + } + + val responseHeader = HttpResponse(status, headers); + + responseStream.write(responseHeader.getHttpHeaderBytes()); + + if(method != "HEAD") { + writing(responseStream); + responseStream.flush(); + } + statusCode = status; + } + + fun readContentBytes(buffer: CharArray, length: Int) : Int { + val reading = Math.min(length, (contentLength - _totalRead).toInt()); + val read = _stream.read(buffer, 0, reading); + _totalRead += read; + + //TODO: Fix this properly + if(contentLength - _totalRead < 400 && read < length) { + _totalRead = contentLength; + } + return read; + } + fun readContentString() : String{ + val writer = StringWriter(); + var read = 0; + val buffer = CharArray(4096); + do { + read = readContentBytes(buffer, buffer.size); + writer.write(buffer, 0, read); + } while(read > 0); + return writer.toString(); + } + inline fun readContentJson() : T { + return Serializer.json.decodeFromString(readContentString()); + } + fun skipBody() { + if(contentLength > 0) + _stream.skip(contentLength - _totalRead); + } + + override fun close() { + if(!keepAlive) { + _stream?.close(); + _responseStream?.close(); + } + } + + companion object { + private val TAG = "HttpRequest"; + private val statusCodeMap = mapOf( + 100 to "Continue", + 101 to "Switching Protocols", + 102 to "Processing (WebDAV)", + 200 to "OK", + 201 to "Created", + 202 to "Accepted", + 203 to "Non-Authoritative Information", + 204 to "No Content", + 205 to "Reset Content", + 206 to "Partial Content", + 207 to "Multi-Status (WebDAV)", + 208 to "Already Reported (WebDAV)", + 226 to "IM Used", + 300 to "Multiple Choices", + 301 to "Moved Permanently", + 302 to "Found", + 303 to "See Other", + 304 to "Not Modified", + 305 to "Use Proxy", + 306 to "(Unused)", + 307 to "Temporary Redirect", + 308 to "Permanent Redirect (experimental)", + 400 to "Bad Request", + 401 to "Unauthorized", + 402 to "Payment Required", + 403 to "Forbidden", + 404 to "Not Found", + 405 to "Method Not Allowed", + 406 to "Not Acceptable", + 407 to "Proxy Authentication Required", + 408 to "Request Timeout", + 409 to "Conflict", + 410 to "Gone", + 411 to "Length Required", + 412 to "Precondition Failed", + 413 to "Request Entity Too Large", + 414 to "Request-URI Too Long", + 415 to "Unsupported Media Type", + 416 to "Requested Range Not Satisfiable", + 417 to "Expectation Failed", + 418 to "I'm a teapot (RFC 2324)", + 420 to "Enhance Your Calm (Twitter)", + 422 to "Unprocessable Entity (WebDAV)", + 423 to "Locked (WebDAV)", + 424 to "Failed Dependency (WebDAV)", + 425 to "Reserved for WebDAV", + 426 to "Upgrade Required", + 428 to "Precondition Required", + 429 to "Too Many Requests", + 431 to "Request Header Fields Too Large", + 444 to "No Response (Nginx)", + 449 to "Retry With (Microsoft)", + 450 to "Blocked by Windows Parental Controls (Microsoft)", + 451 to "Unavailable For Legal Reasons", + 499 to "Client Closed Request (Nginx)", + 500 to "Internal Server Error", + 501 to "Not Implemented", + 502 to "Bad Gateway", + 503 to "Service Unavailable", + 504 to "Gateway Timeout", + 505 to "HTTP Version Not Supported", + 506 to "Variant Also Negotiates (Experimental)", + 507 to "Insufficient Storage (WebDAV)", + 508 to "Loop Detected (WebDAV)", + 509 to "Bandwidth Limit Exceeded (Apache)", + 510 to "Not Extended", + 511 to "Network Authentication Required", + 598 to "Network read timeout error", + 599 to "Network connect timeout error", + ); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/HttpHeaders.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/HttpHeaders.kt new file mode 100644 index 00000000..40e4fbf6 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/HttpHeaders.kt @@ -0,0 +1,25 @@ +package com.futo.platformplayer.api.http.server + +class HttpHeaders : HashMap { + + constructor() : super(){} + constructor(vararg headers: Pair) : + super(headers.map{ Pair(it.first.lowercase(), it.second) }.toMap()) { } + constructor(headers: Map) : + super(headers.mapKeys { it.key.lowercase() }) { } + + override fun put(key: String, value: String): String? { + return super.put(key.lowercase(), value) + } + override fun get(key: String): String? { + return super.get(key.lowercase()); + } + + override fun containsKey(key: String): Boolean { + return super.containsKey(key.lowercase()) + } + + override fun clone() : HttpHeaders { + return HttpHeaders(this); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/HttpResponse.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/HttpResponse.kt new file mode 100644 index 00000000..01e51bed --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/HttpResponse.kt @@ -0,0 +1,115 @@ +package com.futo.platformplayer.api.http.server + +import java.io.InputStream +import java.io.StringWriter + +class HttpResponse : AutoCloseable { + private var _stream: InputStream? = null; + + var head: String = ""; + var headers: Map; + + var status: Int = 0; + + + constructor(status: Int, headers: Map) { + head = "HTTP/1.1 ${status} ${statusCodeMap.get(status)}"; + this.status = status; + this.headers = headers; + } + + fun getHttpHeaderString(): String { + val writer = StringWriter(); + writer.write(head + "\r\n"); + for(header in headers) { + writer.write("${header.key}: ${header.value}\r\n"); + } + writer.write("\r\n"); + return writer.toString(); + } + fun getHttpHeaderBytes(): ByteArray { + return getHttpHeaderString().toByteArray(Charsets.UTF_8); + } + + + override fun close() { + _stream?.close(); + } + + companion object { + private val REGEX_HEAD = Regex("(\\S+) (\\S+) (\\S+)"); + + + private val statusCodeMap = mapOf( + 100 to "Continue", + 101 to "Switching Protocols", + 102 to "Processing (WebDAV)", + 200 to "OK", + 201 to "Created", + 202 to "Accepted", + 203 to "Non-Authoritative Information", + 204 to "No Content", + 205 to "Reset Content", + 206 to "Partial Content", + 207 to "Multi-Status (WebDAV)", + 208 to "Already Reported (WebDAV)", + 226 to "IM Used", + 300 to "Multiple Choices", + 301 to "Moved Permanently", + 302 to "Found", + 303 to "See Other", + 304 to "Not Modified", + 305 to "Use Proxy", + 306 to "(Unused)", + 307 to "Temporary Redirect", + 308 to "Permanent Redirect (experimental)", + 400 to "Bad Request", + 401 to "Unauthorized", + 402 to "Payment Required", + 403 to "Forbidden", + 404 to "Not Found", + 405 to "Method Not Allowed", + 406 to "Not Acceptable", + 407 to "Proxy Authentication Required", + 408 to "Request Timeout", + 409 to "Conflict", + 410 to "Gone", + 411 to "Length Required", + 412 to "Precondition Failed", + 413 to "Request Entity Too Large", + 414 to "Request-URI Too Long", + 415 to "Unsupported Media Type", + 416 to "Requested Range Not Satisfiable", + 417 to "Expectation Failed", + 418 to "I'm a teapot (RFC 2324)", + 420 to "Enhance Your Calm (Twitter)", + 422 to "Unprocessable Entity (WebDAV)", + 423 to "Locked (WebDAV)", + 424 to "Failed Dependency (WebDAV)", + 425 to "Reserved for WebDAV", + 426 to "Upgrade Required", + 428 to "Precondition Required", + 429 to "Too Many Requests", + 431 to "Request Header Fields Too Large", + 444 to "No Response (Nginx)", + 449 to "Retry With (Microsoft)", + 450 to "Blocked by Windows Parental Controls (Microsoft)", + 451 to "Unavailable For Legal Reasons", + 499 to "Client Closed Request (Nginx)", + 500 to "Internal Server Error", + 501 to "Not Implemented", + 502 to "Bad Gateway", + 503 to "Service Unavailable", + 504 to "Gateway Timeout", + 505 to "HTTP Version Not Supported", + 506 to "Variant Also Negotiates (Experimental)", + 507 to "Insufficient Storage (WebDAV)", + 508 to "Loop Detected (WebDAV)", + 509 to "Bandwidth Limit Exceeded (Apache)", + 510 to "Not Extended", + 511 to "Network Authentication Required", + 598 to "Network read timeout error", + 599 to "Network connect timeout error", + ); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/ManagedHttpServer.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/ManagedHttpServer.kt new file mode 100644 index 00000000..09f898bf --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/ManagedHttpServer.kt @@ -0,0 +1,260 @@ +package com.futo.platformplayer.api.http.server + +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.api.http.server.exceptions.EmptyRequestException +import com.futo.platformplayer.api.http.server.handlers.HttpFuntionHandler +import com.futo.platformplayer.api.http.server.handlers.HttpHandler +import java.io.BufferedReader +import java.io.InputStreamReader +import java.io.OutputStream +import java.lang.reflect.Field +import java.lang.reflect.Method +import java.net.InetAddress +import java.net.NetworkInterface +import java.net.ServerSocket +import java.net.Socket +import java.util.* +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import java.util.stream.IntStream.range + +class ManagedHttpServer(private val _requestedPort: Int = 0) { + private val _client : ManagedHttpClient = ManagedHttpClient(); + private val _logVerbose: Boolean = false; + + var active : Boolean = false + private set; + private var _stopCount = 0; + var port = 0 + private set; + + private val _handlers = mutableListOf(); + private var _workerPool: ExecutorService? = null; + + @Synchronized + fun start() { + if (active) + return; + active = true; + _workerPool = Executors.newCachedThreadPool(); + + Thread { + try { + val socket = ServerSocket(_requestedPort); + port = socket.localPort; + + val stopCount = _stopCount; + while (_stopCount == stopCount) { + if(_logVerbose) + Logger.i(TAG, "Waiting for connection..."); + val s = socket.accept() ?: continue; + + try { + handleClientRequest(s); + } + catch(ex : Exception) { + Logger.e(TAG, "Client disconnected due to: " + ex.message, ex); + } + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to accept socket.", e); + stop(); + } + }.start(); + + Logger.i(TAG, "Started ${port}. \n" + getAddresses().map { it.hostAddress }.joinToString("\n")); + } + @Synchronized + fun stop() { + _stopCount++; + active = false; + _workerPool?.shutdown(); + _workerPool = null; + port = 0; + } + + private fun handleClientRequest(socket: Socket) { + _workerPool?.submit { + val requestReader = BufferedReader(InputStreamReader(socket.getInputStream())) + val responseStream = socket.getOutputStream(); + + val requestId = UUID.randomUUID().toString().substring(0, 5); + try { + keepAliveLoop(requestReader, responseStream, requestId) { req -> + req.use { httpContext -> + if(!httpContext.path.startsWith("/plugin/")) + Logger.i(TAG, "[${req.id}] ${httpContext.method}: ${httpContext.path}") + else + ;//Logger.v(TAG, "[${req.id}] ${httpContext.method}: ${httpContext.path}") + val handler = getHandler(httpContext.method, httpContext.path); + if (handler != null) { + handler.handle(httpContext); + } else { + Logger.i(TAG, "[${req.id}] 404 on ${httpContext.method}: ${httpContext.path}"); + httpContext.respondCode(404); + } + if(_logVerbose) + Logger.i(TAG, "[${req.id}] Responded [${req.statusCode}] ${httpContext.method}: ${httpContext.path}") + }; + } + } + catch(emptyRequest: EmptyRequestException) { + if(_logVerbose) + Logger.i(TAG, "[${requestId}] Request ended due to empty request: ${emptyRequest.message}"); + } + catch (e: Throwable) { + Logger.e(TAG, "Failed to handle client request.", e); + } + finally { + requestReader.close(); + responseStream.close(); + } + }; + } + + fun getHandler(method: String, path: String) : HttpHandler? { + synchronized(_handlers) { + //TODO: Support regex paths? + if(method == "HEAD") + return _handlers.firstOrNull { it.path == path && (it.allowHEAD || it.method == "HEAD") } + return _handlers.firstOrNull { it.method == method && it.path == path }; + } + } + fun addHandler(handler: HttpHandler, withHEAD: Boolean = false) : HttpHandler { + synchronized(_handlers) { + _handlers.add(handler); + handler.allowHEAD = withHEAD; + } + return handler; + } + fun removeHandler(method: String, path: String) { + synchronized(_handlers) { + val handler = getHandler(method, path); + if(handler != null) + _handlers.remove(handler); + } + } + fun removeAllHandlers(tag: String? = null) { + synchronized(_handlers) { + if(tag == null) + _handlers.clear(); + else + _handlers.removeIf { it.tag == tag }; + } + } + fun addBridgeHandlers(obj: Any, tag: String? = null) { + val tagToUse = tag ?: obj.javaClass.name; + val getMethods = obj::class.java.declaredMethods + .filter { it.getAnnotation(HttpGET::class.java) != null } + .map { Pair(it, it.getAnnotation(HttpGET::class.java)!!) } + .toList(); + val postMethods = obj::class.java.declaredMethods + .filter { it.getAnnotation(HttpPOST::class.java) != null } + .map { Pair(it, it.getAnnotation(HttpPOST::class.java)!!) } + .toList(); + + val getFields = obj::class.java.declaredFields + .filter { it.getAnnotation(HttpGET::class.java) != null && it.type == String::class.java } + .map { Pair(it, it.getAnnotation(HttpGET::class.java)!!) } + .toList(); + + for(getMethod in getMethods) + if(getMethod.first.parameterTypes.firstOrNull() == HttpContext::class.java && getMethod.first.parameterCount == 1) + addHandler(HttpFuntionHandler("GET", getMethod.second.path) { getMethod.first.invoke(obj, it) }).apply { + if(!getMethod.second.contentType.isEmpty()) + this.withContentType(getMethod.second.contentType); + }.withContentType(getMethod.second.contentType ?: ""); + for(postMethod in postMethods) + if(postMethod.first.parameterTypes.firstOrNull() == HttpContext::class.java && postMethod.first.parameterCount == 1) + addHandler(HttpFuntionHandler("POST", postMethod.second.path) { postMethod.first.invoke(obj, it) }).apply { + if(!postMethod.second.contentType.isEmpty()) + this.withContentType(postMethod.second.contentType); + }.withContentType(postMethod.second.contentType ?: ""); + + for(getField in getFields) { + getField.first.isAccessible = true; + addHandler(HttpFuntionHandler("GET", getField.second.path) { + val value = getField.first.get(obj) as String?; + if(value != null) { + val headers = HttpHeaders( + Pair("Content-Type", getField.second.contentType) + ); + it.respondCode(200, headers, value); + } + else + it.respondCode(204); + }).withContentType(getField.second.contentType ?: ""); + } + } + + private fun keepAliveLoop(requestReader: BufferedReader, responseStream: OutputStream, requestId: String, handler: (HttpContext)->Unit) { + val stopCount = _stopCount; + var keepAlive = false; + var requestsMax = 0; + var requestsTotal = 0; + do { + val req = HttpContext(requestReader, responseStream, requestId); + + //Handle Request + handler(req); + + requestsTotal++; + if(req.keepAlive) { + keepAlive = true; + if(req.keepAliveMax > 0) + requestsMax = req.keepAliveMax; + + req.skipBody(); + } else { + keepAlive = false; + } + } + while (keepAlive && (requestsMax == 0 || requestsTotal < requestsMax) && _stopCount == stopCount); + } + + fun getAddressByIP(addresses: List) : String = getAddress(addresses.map { it.address }.toList()); + fun getAddress(addresses: List = listOf()): String { + if(addresses.isEmpty()) + return getAddresses().first().hostAddress ?: ""; + else + //Matches the closest address to the list of provided addresses + return getAddresses().maxBy { + val availableAddress = it.address; + return@maxBy addresses.map { deviceAddress -> + var matches = 0; + for(index in range(0, Math.min(availableAddress.size, deviceAddress.size))) { + if(availableAddress[index] == deviceAddress[index]) + matches++; + else + break; + } + return@map matches; + }.max(); + }.hostAddress ?: ""; + } + private fun getAddresses(): List { + val addresses = arrayListOf(); + + try { + for (intf in NetworkInterface.getNetworkInterfaces()) { + for (addr in intf.inetAddresses) { + if (!addr.isLoopbackAddress) { + val ipString: String = addr.hostAddress; + val isIPv4 = ipString.indexOf(':') < 0; + if (!isIPv4) + continue; + addresses.add(addr); + } + } + } + } + catch (ignored: Exception) { } + + return addresses; + } + + companion object { + val TAG = "ManagedHttpServer"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/exceptions/EmptyRequestException.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/exceptions/EmptyRequestException.kt new file mode 100644 index 00000000..91182132 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/exceptions/EmptyRequestException.kt @@ -0,0 +1,6 @@ +package com.futo.platformplayer.api.http.server.exceptions + +import java.net.SocketTimeoutException +import java.util.concurrent.TimeoutException + +class EmptyRequestException(msg: String) : Exception(msg) {} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/exceptions/KeepAliveTimeoutException.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/exceptions/KeepAliveTimeoutException.kt new file mode 100644 index 00000000..211553e8 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/exceptions/KeepAliveTimeoutException.kt @@ -0,0 +1,3 @@ +package com.futo.platformplayer.api.http.server.exceptions + +class KeepAliveTimeoutException(msg: String, ex: Exception) : Exception(msg, ex) {} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpConstantHandler.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpConstantHandler.kt new file mode 100644 index 00000000..c868b2b7 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpConstantHandler.kt @@ -0,0 +1,14 @@ +package com.futo.platformplayer.api.http.server.handlers + +import com.futo.platformplayer.api.http.server.HttpContext + +class HttpConstantHandler(method: String, path: String, val content: String, val contentType: String? = null) : HttpHandler(method, path) { + override fun handle(httpContext: HttpContext) { + val headers = this.headers.clone(); + if(contentType != null) + headers["Content-Type"] = contentType; + headers["Content-Length"] = content.length.toString(); + + httpContext.respondCode(200, headers, content); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpFileHandler.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpFileHandler.kt new file mode 100644 index 00000000..89ad16c6 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpFileHandler.kt @@ -0,0 +1,107 @@ +package com.futo.platformplayer.api.http.server.handlers + +import com.futo.platformplayer.api.http.server.HttpContext +import com.futo.platformplayer.logging.Logger +import java.io.File +import java.nio.file.Files +import java.text.SimpleDateFormat +import java.util.* +import java.util.zip.GZIPOutputStream + +class HttpFileHandler(method: String, path: String, private val contentType: String, private val filePath: String, private val closeAfterRequest: Boolean = false): HttpHandler(method, path) { + override fun handle(httpContext: HttpContext) { + val requestHeaders = httpContext.headers; + val responseHeaders = this.headers.clone(); + responseHeaders["Content-Type"] = contentType; + + val file = File(filePath); + if (!file.exists()) { + throw Exception("File does not exist."); + } + + val lastModified = Files.getLastModifiedTime(file.toPath()) + responseHeaders["Last-Modified"] = httpDateFormat.format(Date(lastModified.toMillis())) + + val ifModifiedSince = requestHeaders["If-Modified-Since"]?.let { httpDateFormat.parse(it) } + if (ifModifiedSince != null && lastModified.toMillis() <= ifModifiedSince.time) { + httpContext.respondCode(304, headers) + return + } + + responseHeaders["Content-Disposition"] = "attachment; filename=\"${file.name.replace("\"", "\\\"")}\"" + + val acceptEncoding = requestHeaders["Accept-Encoding"] + val shouldGzip = acceptEncoding != null && acceptEncoding.split(',').any { it.trim().equals("gzip", ignoreCase = true) || it == "*" } + if (shouldGzip) { + responseHeaders["Content-Encoding"] = "gzip" + } + + val range = requestHeaders["Range"] + var start: Long + val end: Long + if (range != null && range.startsWith("bytes=")) { + val parts = range.substring(6).split("-") + start = parts[0].toLong() + end = parts.getOrNull(1)?.toLong() ?: (file.length() - 1) + responseHeaders["Content-Range"] = "bytes $start-$end/${file.length()}" + } else { + start = 0 + end = file.length() - 1 + } + + var totalBytesSent = 0 + val contentLength = end - start + 1 + Logger.i(TAG, "Sending $contentLength bytes (start: $start, end: $end, shouldGzip: $shouldGzip)") + responseHeaders["Content-Length"] = contentLength.toString() + + file.inputStream().use { inputStream -> + httpContext.respond(if (range == null) 200 else 206, responseHeaders) { responseStream -> + try { + val buffer = ByteArray(8192) + inputStream.skip(start) + + val outputStream = if (shouldGzip) GZIPOutputStream(responseStream) else responseStream + while (true) { + val expectedBytesRead = (end - start + 1).coerceAtMost(buffer.size.toLong()); + val bytesRead = inputStream.read(buffer); + if (bytesRead < 0) { + Logger.i(TAG, "End of file reached") + break; + } + + val bytesToSend = bytesRead.coerceAtMost(expectedBytesRead.toInt()); + outputStream.write(buffer, 0, bytesToSend) + + totalBytesSent += bytesToSend + Logger.v(TAG, "Sent bytes $start-${start + bytesToSend}, totalBytesSent=$totalBytesSent") + + start += bytesToSend.toLong() + if (start >= end) { + Logger.i(TAG, "Expected amount of bytes sent") + break + } + } + + Logger.i(TAG, "Finished sending file (segment)") + + if (shouldGzip) (outputStream as GZIPOutputStream).finish() + outputStream.flush() + } catch (e: Exception) { + httpContext.respondCode(500, headers) + } + } + + if (closeAfterRequest) { + httpContext.keepAlive = false; + } + } + } + + companion object { + private const val TAG = "HttpFileHandler" + + private val httpDateFormat = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US).apply { + timeZone = TimeZone.getTimeZone("GMT") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpFunctionHandler.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpFunctionHandler.kt new file mode 100644 index 00000000..758c8d33 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpFunctionHandler.kt @@ -0,0 +1,10 @@ +package com.futo.platformplayer.api.http.server.handlers + +import com.futo.platformplayer.api.http.server.HttpContext + +class HttpFuntionHandler(method: String, path: String, val handler: (HttpContext)->Unit) : HttpHandler(method, path) { + override fun handle(httpContext: HttpContext) { + httpContext.setResponseHeaders(this.headers); + handler(httpContext); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpHandler.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpHandler.kt new file mode 100644 index 00000000..509c1b40 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpHandler.kt @@ -0,0 +1,24 @@ +package com.futo.platformplayer.api.http.server.handlers + +import com.futo.platformplayer.api.http.server.HttpContext +import com.futo.platformplayer.api.http.server.HttpHeaders + + +abstract class HttpHandler(val method: String, val path: String) { + var tag: String? = null; + val headers = HttpHeaders() + var allowHEAD = false; + + abstract fun handle(httpContext: HttpContext); + + fun withHeader(key: String, value: String) : HttpHandler { + headers.put(key, value); + return this; + } + fun withContentType(contentType: String) = withHeader("Content-Type", contentType); + + fun withTag(tag: String) : HttpHandler { + this.tag = tag; + return this; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpOptionsAllowHandler.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpOptionsAllowHandler.kt new file mode 100644 index 00000000..af226aa6 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpOptionsAllowHandler.kt @@ -0,0 +1,20 @@ +package com.futo.platformplayer.api.http.server.handlers + +import com.futo.platformplayer.api.http.server.HttpContext + +class HttpOptionsAllowHandler(path: String) : HttpHandler("OPTIONS", path) { + override fun handle(httpContext: HttpContext) { + //Just allow whatever is requested + + val requestedOrigin = httpContext.headers.getOrDefault("Access-Control-Request-Origin", ""); + val requestedMethods = httpContext.headers.getOrDefault("Access-Control-Request-Method", ""); + val requestedHeaders = httpContext.headers.getOrDefault("Access-Control-Request-Headers", ""); + + val newHeaders = headers.clone(); + newHeaders.put("Allow", requestedMethods); + newHeaders.put("Access-Control-Allow-Methods", requestedMethods); + newHeaders.put("Access-Control-Allow-Headers", "*"); + + httpContext.respondCode(200, newHeaders); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpProxyHandler.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpProxyHandler.kt new file mode 100644 index 00000000..1756925c --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpProxyHandler.kt @@ -0,0 +1,95 @@ +package com.futo.platformplayer.api.http.server.handlers + +import android.net.Uri +import com.futo.platformplayer.api.http.server.HttpContext +import com.futo.platformplayer.api.http.server.HttpHeaders +import com.futo.platformplayer.api.http.ManagedHttpClient + +class HttpProxyHandler(method: String, path: String, val targetUrl: String): HttpHandler(method, path) { + var content: String? = null; + var contentType: String? = null; + + private val _ignoreRequestHeaders = mutableListOf(); + private val _injectRequestHeader = mutableListOf>(); + + private val _ignoreResponseHeaders = mutableListOf(); + + private var _injectHost = false; + private var _injectReferer = false; + + + private val _client = ManagedHttpClient(); + + override fun handle(context: HttpContext) { + val proxyHeaders = HashMap(); + for (header in context.headers.filter { !_ignoreRequestHeaders.contains(it.key.lowercase()) }) + proxyHeaders[header.key] = header.value; + for (injectHeader in _injectRequestHeader) + proxyHeaders[injectHeader.first] = injectHeader.second; + + val parsed = Uri.parse(targetUrl); + if(_injectHost) + proxyHeaders.put("Host", parsed.host!!); + if(_injectReferer) + proxyHeaders.put("Referer", targetUrl); + + val useMethod = if (method == "inherit") context.method else method; + //Logger.i(TAG, "Proxied Request ${useMethod}: ${targetUrl}"); + //Logger.i(TAG, "Headers:" + proxyHeaders.map { "${it.key}: ${it.value}" }.joinToString("\n")); + + val resp = when (useMethod) { + "GET" -> _client.get(targetUrl, proxyHeaders); + "POST" -> _client.post(targetUrl, content ?: "", proxyHeaders); + "HEAD" -> _client.head(targetUrl, proxyHeaders) + else -> _client.requestMethod(useMethod, targetUrl, proxyHeaders); + }; + + //Logger.i(TAG, "Proxied Response [${resp.code}]"); + val headersFiltered = HttpHeaders(resp.getHeadersFlat().filter { !_ignoreRequestHeaders.contains(it.key.lowercase()) }); + for(newHeader in headers) + headersFiltered.put(newHeader.key, newHeader.value); + + if(resp.body == null) + context.respondCode(resp.code, headersFiltered); + else { + resp.body.byteStream().use { inputStream -> + context.respond(resp.code, headersFiltered) { responseStream -> + val buffer = ByteArray(8192); + + var read: Int; + while (inputStream.read(buffer).also { read = it } >= 0) { + responseStream.write(buffer, 0, read); + } + }; + } + } + } + + fun withContent(body: String) : HttpProxyHandler { + this.content = body; + return this; + } + + fun withRequestHeader(header: String, value: String) : HttpProxyHandler { + _injectRequestHeader.add(Pair(header, value)); + return this; + } + fun withIgnoredRequestHeaders(ignored: List) : HttpProxyHandler { + _ignoreRequestHeaders.addAll(ignored.map { it.lowercase() }); + return this; + } + fun withIgnoredResponseHeaders(ignored: List) : HttpProxyHandler { + _ignoreResponseHeaders.addAll(ignored.map { it.lowercase() }); + return this; + } + fun withInjectedHost() : HttpProxyHandler { + _injectHost = true; + _ignoreRequestHeaders.add("host"); + return this; + } + fun withInjectedReferer() : HttpProxyHandler { + _injectReferer = true; + _ignoreRequestHeaders.add("referer"); + return this; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/CachedPlatformClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/CachedPlatformClient.kt new file mode 100644 index 00000000..66a86f8b --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/CachedPlatformClient.kt @@ -0,0 +1,105 @@ +package com.futo.platformplayer.api.media + +import androidx.collection.LruCache +import com.futo.platformplayer.api.media.models.ResultCapabilities +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails +import com.futo.platformplayer.api.media.models.live.ILiveChatWindowDescriptor +import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent +import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker +import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist +import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails +import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.models.ImageVariable +import com.futo.platformplayer.models.Playlist + +/** + * A temporary class that caches video results + * In future this should be part of a bigger system + */ +class CachedPlatformClient : IPlatformClient { + private val _client : IPlatformClient; + override val id: String get() = _client.id; + override val name: String get() = _client.name; + override val icon: ImageVariable? get() = _client.icon; + + private val _cache: LruCache; + + override val capabilities: PlatformClientCapabilities + get() = _client.capabilities; + + constructor(client : IPlatformClient, cacheSize : Int = 10 * 1024 * 1024) { + this._client = client; + this._cache = LruCache(cacheSize); + } + override fun initialize() { _client.initialize() } + override fun disable() { _client.disable() } + + override fun isContentDetailsUrl(url: String): Boolean = _client.isContentDetailsUrl(url); + override fun getContentDetails(url: String): IPlatformContentDetails { + var result = _cache.get(url); + if(result == null) { + result = _client.getContentDetails(url); + if (result != null) + _cache.put(url, result); + } + return result; + } + + override fun getPlaybackTracker(url: String): IPlaybackTracker? = _client.getPlaybackTracker(url); + + override fun isChannelUrl(url: String): Boolean = _client.isChannelUrl(url); + override fun getChannel(channelUrl: String): IPlatformChannel = _client.getChannel(channelUrl); + + override fun getChannelCapabilities(): ResultCapabilities = _client.getChannelCapabilities(); + override fun getChannelContents( + channelUrl: String, + type: String?, + order: String?, + filters: Map>? + ): IPager = _client.getChannelContents(channelUrl); + + override fun getChannelUrlByClaim(claimType: Int, claimValues: Map): String? = _client.getChannelUrlByClaim(claimType, claimValues) + + override fun searchSuggestions(query: String): Array = _client.searchSuggestions(query); + override fun getSearchCapabilities(): ResultCapabilities = _client.getSearchCapabilities(); + override fun search( + query: String, + type: String?, + order: String?, + filters: Map>? + ): IPager = _client.search(query, type, order, filters); + + override fun getSearchChannelContentsCapabilities(): ResultCapabilities = _client.getSearchChannelContentsCapabilities(); + override fun searchChannelContents( + channelUrl: String, + query: String, + type: String?, + order: String?, + filters: Map>? + ): IPager = _client.searchChannelContents(channelUrl, query, type, order, filters); + + override fun searchChannels(query: String) = _client.searchChannels(query); + + override fun getComments(url: String): IPager = _client.getComments(url); + override fun getSubComments(comment: IPlatformComment): IPager = _client.getSubComments(comment); + + override fun getLiveChatWindow(url: String): ILiveChatWindowDescriptor? = _client.getLiveChatWindow(url); + override fun getLiveEvents(url: String): IPager? = _client.getLiveEvents(url); + + override fun getHome(): IPager = _client.getHome(); + + override fun getUserSubscriptions(): Array { return arrayOf(); }; + + override fun searchPlaylists(query: String, type: String?, order: String?, filters: Map>?): IPager = _client.searchPlaylists(query, type, order, filters); + override fun isPlaylistUrl(url: String): Boolean = _client.isPlaylistUrl(url); + override fun getPlaylist(url: String): IPlatformPlaylistDetails = _client.getPlaylist(url); + override fun getUserPlaylists(): Array { return arrayOf(); }; + + override fun isClaimTypeSupported(claimType: Int): Boolean { + return _client.isClaimTypeSupported(claimType); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/IPlatformClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/IPlatformClient.kt new file mode 100644 index 00000000..0178cb5a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/IPlatformClient.kt @@ -0,0 +1,155 @@ +package com.futo.platformplayer.api.media + +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.ResultCapabilities +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails +import com.futo.platformplayer.api.media.models.live.ILiveChatWindowDescriptor +import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent +import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker +import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist +import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.models.ImageVariable +import com.futo.platformplayer.models.Playlist + +/** + * A client for a specific platform + */ +interface IPlatformClient { + val id: String; + val name: String; + + val icon: ImageVariable?; + + //Capabilities + val capabilities: PlatformClientCapabilities; + + fun initialize(); + fun disable(); + + /** + * Gets the home recommendations + */ + fun getHome(): IPager + + //Search + /** + * Gets search suggestion for the provided query string + */ + fun searchSuggestions(query: String): Array; + /** + * Describes what the plugin is capable on filtering/sorting search results + */ + fun getSearchCapabilities(): ResultCapabilities; + /** + * Searches for content and returns a search pager with results + */ + fun search(query: String, type: String? = null, order: String? = null, filters: Map>? = null): IPager; + + + + /** + * Describes what the plugin is capable on filtering/sorting search results on channels + */ + fun getSearchChannelContentsCapabilities(): ResultCapabilities; + /** + * Searches for content on a channel and returns a video pager + */ + fun searchChannelContents(channelUrl: String, query: String, type: String? = null, order: String? = null, filters: Map>? = null): IPager; + + + /** + * Searches for channels and returns a channel pager + */ + fun searchChannels(query: String): IPager; + + + //Video Pages + /** + * Determines if the provided url is a valid url for getting channel from this client + */ + fun isChannelUrl(url: String): Boolean; + /** + * Gets channel details, might also fetch videos which is then obtained by IPlatformChannel.getVideos. Otherwise might fall back to getChannelVideos + */ + fun getChannel(channelUrl: String): IPlatformChannel; + /** + * Describes what the plugin is capable on filtering/sorting channel results + */ + fun getChannelCapabilities(): ResultCapabilities; + /** + * Gets all videos of a channel, ideally in upload time descending + */ + fun getChannelContents(channelUrl: String, type: String? = null, order: String? = null, filters: Map>? = null): IPager; + + /** + * Gets the channel url associated with a claimType + */ + fun getChannelUrlByClaim(claimType: Int, claimValues: Map): String?; + + //Video + /** + * Determines if the provided url is a valid url for getting details from this client + */ + fun isContentDetailsUrl(url: String): Boolean; + /** + * Gets the video details for a given url, including video/audio streams + */ + fun getContentDetails(url: String): IPlatformContentDetails; + + /** + * Gets the playback tracker for a piece of content + */ + fun getPlaybackTracker(url: String): IPlaybackTracker?; + + + //Comments + /** + * Gets the comments underneath a video + */ + fun getComments(url: String): IPager; + /** + * Gets the replies to a comment + */ + fun getSubComments(comment: IPlatformComment): IPager; + + /** + * Gets the live events of a livestream + */ + fun getLiveChatWindow(url: String): ILiveChatWindowDescriptor?; + /** + * Gets the live events of a livestream + */ + fun getLiveEvents(url: String): IPager? + + + //Playlists + /** + * Search for Playlists and returns a Playlist pager + */ + fun searchPlaylists(query: String, type: String? = null, order: String? = null, filters: Map>? = null): IPager; + /** + * Gets a playlist from a url + */ + fun isPlaylistUrl(url: String): Boolean; + /** + * Gets a playlist from a url + */ + fun getPlaylist(url: String): IPlatformPlaylistDetails; + + //Migration + /** + * Retrieves the playlists of the currently logged in user + */ + fun getUserPlaylists(): Array; + /** + * Retrieves the subscriptions of the currently logged in user + */ + fun getUserSubscriptions(): Array; + + + fun isClaimTypeSupported(claimType: Int): Boolean; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/IPluginSourced.kt b/app/src/main/java/com/futo/platformplayer/api/media/IPluginSourced.kt new file mode 100644 index 00000000..0ad592f1 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/IPluginSourced.kt @@ -0,0 +1,7 @@ +package com.futo.platformplayer.api.media + +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig + +interface IPluginSourced { + val sourceConfig: SourcePluginConfig; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/LiveChatManager.kt b/app/src/main/java/com/futo/platformplayer/api/media/LiveChatManager.kt new file mode 100644 index 00000000..78c99bff --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/LiveChatManager.kt @@ -0,0 +1,223 @@ +package com.futo.platformplayer.api.media + +import android.graphics.BitmapFactory +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.graphics.drawable.PictureDrawable +import androidx.core.graphics.drawable.toBitmap +import com.caverock.androidsvg.SVG +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent +import com.futo.platformplayer.api.media.models.live.LiveEventComment +import com.futo.platformplayer.api.media.models.live.LiveEventDonation +import com.futo.platformplayer.api.media.models.live.LiveEventEmojis +import com.futo.platformplayer.api.media.platforms.js.models.JSLiveEventPager +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.constructs.BatchedTaskHandler +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.views.overlays.LiveChatOverlay +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class LiveChatManager { + private val _scope: CoroutineScope; + private val _emojiCache: EmojiCache = EmojiCache(); + private val _pager: IPager?; + + private val _history: ArrayList = arrayListOf(); + + private var _startCounter = 0; + + private val _followers: HashMap) -> Unit> = hashMapOf(); + + var viewCount: Long = 0 + private set; + + constructor(scope: CoroutineScope, pager: IPager, initialViewCount: Long = 0) { + _scope = scope; + _pager = pager; + viewCount = initialViewCount; + handleEvents(listOf(LiveEventComment("SYSTEM", null, "Live chat is still under construction. While it is mostly functional, the experience still needs to be improved.\n"))); + handleEvents(pager.getResults()); + } + + fun start() { + val counter = ++_startCounter; + startLoop(counter); + } + + fun stop() { + _startCounter++; + } + + fun getHistory(): List { + synchronized(_history) { + return _history.toList(); + } + } + + fun follow(tag: Any, eventHandler: (List) -> Unit) { + val before = synchronized(_history) { + _history.toList(); + }; + synchronized(_followers) { + _followers.put(tag, eventHandler); + } + eventHandler(before); + } + fun unfollow(tag: Any) { + synchronized(_followers) { + _followers.remove(tag); + } + } + + fun hasEmoji(emoji: String): Boolean { + return _emojiCache.hasEmoji(emoji); + } + fun getEmoji(emoji: String, handler: (Drawable?)->Unit) { + return _emojiCache.getEmojiDrawable(emoji, handler); + } + + private fun startLoop(counter: Int) { + _scope.launch(Dispatchers.IO) { + try { + while(_startCounter == counter) { + var nextInterval = 1000L; + try { + if(_pager == null || !_pager.hasMorePages()) + return@launch; + _pager.nextPage(); + val newEvents = _pager.getResults(); + if(_pager is JSLiveEventPager) + nextInterval = _pager.nextRequest.coerceAtLeast(800).toLong(); + + Logger.i(TAG, "New Live Events (${newEvents.size}) [${newEvents.map { it.type.name }.joinToString(", ")}]"); + + _scope.launch(Dispatchers.Main) { + try { + handleEvents(newEvents); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to handle new live events.", e); + } + } + } + catch(ex: Throwable) { + Logger.e(LiveChatOverlay.TAG, "Failed to load live events", ex); + } + delay(nextInterval); + } + } catch (e: Throwable) { + Logger.e(TAG, "Live events loop crashed.", e); + } + } + } + fun handleEvents(events: List) { + for(event in events) { + if(event is LiveEventEmojis) + _emojiCache.setEmojis(event); + } + synchronized(_history) { + _history.addAll(events); + } + val handlers = synchronized(_followers) { _followers.values.toList() }; + for(handler in handlers) { + try { + handler(events); + } + catch(ex: Throwable) { + Logger.e(TAG, "Failed to chat handle events on handler", ex); + } + } + } + + companion object { + val TAG = "LiveChatManager"; + } + + + class EmojiCache { + private val _cache_lock = Object(); + private val _cache_drawables = HashMap(); //TODO: Replace with LRUCache + private val _cache_urls = HashMap(); + + private val _client = ManagedHttpClient(); + private val _download_drawable = + BatchedTaskHandler(StateApp.instance.scope, { url -> + val req = _client.get(url); + if (req.isOk && req.body != null) { + val contentType = req.body.contentType(); + return@BatchedTaskHandler when (contentType?.toString()) { + //TODO: Get scaling to work with drawable (no bitmap conversion) + "image/svg+xml" -> { + val bitmap = PictureDrawable(SVG.getFromString(req.body.string()).renderToPicture(150, 150)).toBitmap(150,150,null); + return@BatchedTaskHandler BitmapDrawable(bitmap) + }; + //"image/svg+xml" -> PictureDrawable(SVG.getFromString(req.body.string()).renderToPicture(15, 15)); + else -> { + val bytes = req.body.bytes(); + BitmapDrawable(BitmapFactory.decodeByteArray(bytes, 0, bytes.size)) + } + } + } else { + Logger.w(TAG, "Failed to request emoji (${req.code}) [${req.url}]"); + return@BatchedTaskHandler null; + } + }, { url -> + synchronized(_cache_lock) { + return@synchronized _cache_drawables[url]; + } + }, { url, drawable -> + if (drawable != null) + synchronized(_cache_lock) { + _cache_drawables[url] = drawable; + } + }); + + fun setEmojis(emojis: LiveEventEmojis) { + synchronized(_cache_lock) { + for(emoji in emojis.emojis) { + _cache_urls[emoji.key] = emoji.value; + } + } + } + + fun hasEmoji(emoji: String): Boolean { + synchronized(_cache_lock) { + return _cache_urls.containsKey(emoji); + } + } + + fun getEmojiDrawable(emoji: String, cb: (drawable: Drawable?)->Unit) { + var drawable: Drawable? = null; + var url: String? = null; + synchronized(_cache_lock) { + url = _cache_urls[emoji]; + if(url != null) + drawable = _cache_drawables[url]; + } + if(drawable != null) + cb(drawable); + else if(url != null){ + Logger.i(TAG, "Requesting [${emoji}] (${url})"); + _download_drawable.execute(url!!).invokeOnCompletion { + if(it == null) { + Logger.i(TAG, "Found emoji [${emoji}]") + cb(synchronized(_cache_lock) { _cache_drawables[url] }); + } + else { + Logger.w(TAG, "Exception on emoji load [${emoji}]: ${it.message}", it); + } + } + } + } + fun getEmojiUrl(emoji: String): String? { + synchronized(_cache_lock) { + return _cache_urls[emoji]; + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/PlatformClientCapabilities.kt b/app/src/main/java/com/futo/platformplayer/api/media/PlatformClientCapabilities.kt new file mode 100644 index 00000000..4b15791e --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/PlatformClientCapabilities.kt @@ -0,0 +1,21 @@ +package com.futo.platformplayer.api.media + +data class PlatformClientCapabilities( + val hasChannelSearch: Boolean = false, + val hasGetComments: Boolean = false, + val hasGetUserSubscriptions: Boolean = false, + val hasSearchPlaylists: Boolean = false, + val hasGetPlaylist: Boolean = false, + val hasGetUserPlaylists: Boolean = false, + val hasSearchChannelContents: Boolean = false, + val hasSaveState: Boolean = false, + val hasGetPlaybackTracker: Boolean = false, + val hasGetChannelUrlByClaim: Boolean = false, + val hasGetChannelTemplateByClaimMap: Boolean = false, + val hasGetSearchCapabilities: Boolean = false, + val hasGetChannelCapabilities: Boolean = false, + val hasGetLiveEvents: Boolean = false, + val hasGetLiveChatWindow: Boolean = false +) { + +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/PlatformClientPool.kt b/app/src/main/java/com/futo/platformplayer/api/media/PlatformClientPool.kt new file mode 100644 index 00000000..ef1fc37c --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/PlatformClientPool.kt @@ -0,0 +1,66 @@ +package com.futo.platformplayer.api.media + +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.platforms.js.JSClient +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.logging.Logger + +class PlatformClientPool { + private val _parent: JSClient; + private val _pool: HashMap = hashMapOf(); + private var _poolCounter = 0; + + var isDead: Boolean = false + private set; + val onDead = Event2(); + + constructor(parentClient: IPlatformClient) { + if(parentClient !is JSClient) + throw IllegalArgumentException("Pooling only supported for JSClients right now"); + Logger.i(TAG, "Pool for ${parentClient.name} was started"); + + this._parent = parentClient; + parentClient.getUnderlyingPlugin().onStopped.subscribe { + Logger.i(TAG, "Pool for [${parentClient.name}] was killed"); + isDead = true; + onDead.emit(parentClient, this); + + for(clientPair in _pool) { + clientPair.key.disable(); + } + }; + } + + fun getClient(capacity: Int): IPlatformClient { + if(capacity < 1) + throw IllegalArgumentException("Capacity should be at least 1"); + val parentPlugin = _parent.getUnderlyingPlugin(); + if(parentPlugin._runtime?.isDead == true || parentPlugin._runtime?.isClosed == true) { + isDead = true; + onDead.emit(_parent, this); + } + + var reserved: JSClient?; + synchronized(_pool) { + _poolCounter++; + reserved = _pool.keys.find { !it.isBusy }; + if(reserved == null && _pool.size < capacity) { + Logger.i(TAG, "Started additional [${_parent.name}] client in pool (${_pool.size + 1}/${capacity})"); + reserved = _parent.getCopy(); + reserved?.initialize(); + _pool[reserved!!] = _poolCounter; + } + else + reserved = _pool.entries.toList().sortedBy { it.value }.first().key; + _pool[reserved!!] = _poolCounter; + } + return reserved!!; + } + + + companion object { + val TAG = "PlatformClientPool"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/PlatformID.kt b/app/src/main/java/com/futo/platformplayer/api/media/PlatformID.kt new file mode 100644 index 00000000..0617c3ee --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/PlatformID.kt @@ -0,0 +1,53 @@ +package com.futo.platformplayer.api.media + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.getOrDefault +import com.futo.platformplayer.getOrThrow +import com.futo.platformplayer.getOrThrowNullable +import com.futo.polycentric.core.combineHashCodes + +@kotlinx.serialization.Serializable +class PlatformID { + val platform: String; + val value: String?; + var pluginId: String? = null; + var claimType: Int = 0; + var claimFieldType: Int = -1; + + constructor(platform: String, id: String?, pluginId: String? = null, claimType: Int = 0, claimFieldType: Int = -1) { + this.platform = platform; + this.value = id; + this.pluginId = pluginId; + this.claimType = claimType; + this.claimFieldType = claimFieldType; + } + + override fun equals(other: Any?): Boolean { + if (other !is PlatformID) { + return false + } + + return platform == other.platform && value == other.value + } + + override fun hashCode(): Int { + return combineHashCodes(listOf(platform.hashCode(), value?.hashCode())) + } + + override fun toString(): String { + return "(platform: $platform, value: $value, pluginId: $pluginId, claimType: $claimType, claimFieldType: $claimFieldType)"; + } + + companion object { + fun fromV8(config: SourcePluginConfig, value: V8ValueObject): PlatformID { + val contextName = "PlatformID"; + return PlatformID( + value.getOrThrow(config, "platform", contextName), + value.getOrThrowNullable(config, "value", contextName), + config.id, + value.getOrDefault(config, "claimType", contextName, 0) ?: 0, + value.getOrDefault(config, "claimFieldType", contextName, -1) ?: -1); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/Serializer.kt b/app/src/main/java/com/futo/platformplayer/api/media/Serializer.kt new file mode 100644 index 00000000..40c3edfd --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/Serializer.kt @@ -0,0 +1,9 @@ +package com.futo.platformplayer.api.media + +import kotlinx.serialization.json.Json + +class Serializer { + companion object { + val json = Json { ignoreUnknownKeys = true; encodeDefaults = true; }; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/exceptions/APIRequestFailedException.kt b/app/src/main/java/com/futo/platformplayer/api/media/exceptions/APIRequestFailedException.kt new file mode 100644 index 00000000..0a7e8160 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/exceptions/APIRequestFailedException.kt @@ -0,0 +1,4 @@ +package com.futo.platformplayer.api.media.exceptions + +class APIRequestFailedException(msg : String) : IllegalStateException(msg) { +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/exceptions/AlreadyQueuedException.kt b/app/src/main/java/com/futo/platformplayer/api/media/exceptions/AlreadyQueuedException.kt new file mode 100644 index 00000000..a3af72b9 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/exceptions/AlreadyQueuedException.kt @@ -0,0 +1,5 @@ +package com.futo.platformplayer.api.media.exceptions + +class AlreadyQueuedException(message: String?) : Exception(message) { + +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/exceptions/ContentNotAvailableYetException.kt b/app/src/main/java/com/futo/platformplayer/api/media/exceptions/ContentNotAvailableYetException.kt new file mode 100644 index 00000000..fd3987a0 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/exceptions/ContentNotAvailableYetException.kt @@ -0,0 +1,5 @@ +package com.futo.platformplayer.api.media.exceptions + +class ContentNotAvailableYetException(message: String?, val availableWhen: String) : Exception(message) { + +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/exceptions/NoPlatformClientException.kt b/app/src/main/java/com/futo/platformplayer/api/media/exceptions/NoPlatformClientException.kt new file mode 100644 index 00000000..4761ba81 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/exceptions/NoPlatformClientException.kt @@ -0,0 +1,3 @@ +package com.futo.platformplayer.api.media.exceptions + +class NoPlatformClientException(s: String) : IllegalArgumentException("No enabled PlatformClient: $s") {} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/exceptions/NotFoundException.kt b/app/src/main/java/com/futo/platformplayer/api/media/exceptions/NotFoundException.kt new file mode 100644 index 00000000..dff1bdbc --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/exceptions/NotFoundException.kt @@ -0,0 +1,4 @@ +package com.futo.platformplayer.api.media.exceptions + +class NotFoundException(message: String?) : Exception(message) { +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/exceptions/UnknownPlatformException.kt b/app/src/main/java/com/futo/platformplayer/api/media/exceptions/UnknownPlatformException.kt new file mode 100644 index 00000000..9ab7d470 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/exceptions/UnknownPlatformException.kt @@ -0,0 +1,4 @@ +package com.futo.platformplayer.api.media.exceptions + +class UnknownPlatformException(s : String) : IllegalArgumentException("Unknown platform type:$s") { +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/exceptions/search/NoNextPageException.kt b/app/src/main/java/com/futo/platformplayer/api/media/exceptions/search/NoNextPageException.kt new file mode 100644 index 00000000..07a5d837 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/exceptions/search/NoNextPageException.kt @@ -0,0 +1,5 @@ +package com.futo.platformplayer.api.media.exceptions.search + +class NoNextPageException(s: String? = null) : IllegalStateException("No next page available:$s") { + +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/PlatformAuthorLink.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/PlatformAuthorLink.kt new file mode 100644 index 00000000..3104fe2c --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/PlatformAuthorLink.kt @@ -0,0 +1,40 @@ +package com.futo.platformplayer.api.media.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.getOrDefault +import com.futo.platformplayer.getOrThrow + +/** + * A link to a channel, often with its own name and thumbnail + */ +@kotlinx.serialization.Serializable +class PlatformAuthorLink { + val id: PlatformID; + val name: String; + val url: String; + val thumbnail: String?; + var subscribers: Long? = null; //Optional + + constructor(id: PlatformID, name: String, url: String, thumbnail: String? = null, subscribers: Long? = null) + { + this.id = id; + this.name = name; + this.url = url; + this.thumbnail = thumbnail; + this.subscribers = subscribers; + } + + companion object { + fun fromV8(config: SourcePluginConfig, value: V8ValueObject): PlatformAuthorLink { + val context = "AuthorLink" + return PlatformAuthorLink(PlatformID.fromV8(config, value.getOrThrow(config, "id", context, false)), + value.getOrThrow(config ,"name", context), + value.getOrThrow(config, "url", context), + value.getOrDefault(config, "thumbnail", context, null), + if(value.has("subscribers")) value.getOrThrow(config,"subscribers", context) else null + ); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/ResultCapabilities.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/ResultCapabilities.kt new file mode 100644 index 00000000..6fa88c25 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/ResultCapabilities.kt @@ -0,0 +1,102 @@ +package com.futo.platformplayer.api.media.models + +import com.caoccao.javet.values.V8Value +import com.caoccao.javet.values.primitive.V8ValueInteger +import com.caoccao.javet.values.reference.V8ValueArray +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.expectV8Variant +import com.futo.platformplayer.getOrDefault +import com.futo.platformplayer.getOrThrow + + +class ResultCapabilities( + val types: List = listOf(), + val sorts: List = listOf(), + val filters: List = listOf() +) { + + fun hasType(type: String): Boolean { + return types.contains(type); + } + fun hasSort(sort: String): Boolean { + return sorts.contains(sort); + } + + companion object { + const val TYPE_VIDEOS = "VIDEOS"; + const val TYPE_STREAMS = "STREAMS"; + const val TYPE_LIVE = "LIVE"; + const val TYPE_MIXED = "MIXED"; + + const val ORDER_CHONOLOGICAL = "CHRONOLOGICAL"; + + const val DATE_LAST_HOUR = "LAST_HOUR"; + const val DATE_TODAY = "TODAY"; + const val DATE_LAST_WEEK = "LAST_WEEK"; + const val DATE_LAST_MONTH = "LAST_MONTH"; + const val DATE_LAST_YEAR = "LAST_YEAR"; + + const val DURATION_SHORT = "SHORT"; + const val DURATION_MEDIUM = "MEDIUM"; + const val DURATION_LONG = "LONG"; + + fun fromV8(config: IV8PluginConfig, value: V8ValueObject): ResultCapabilities { + val contextName = "ResultCapabilities"; + return ResultCapabilities( + value.getOrThrow(config, "types", contextName).toArray().map { it.expectV8Variant(config, "Capabilities.types") }, + value.getOrThrow(config, "sorts", contextName).toArray().map { it.expectV8Variant(config, "Capabilities.sorts"); }, + value.getOrDefault(config, "filters", contextName, null) + ?.toArray() + ?.map { FilterGroup.fromV8(config, it as V8ValueObject) } + ?.toList() ?: listOf()); + } + } +} + + +@kotlinx.serialization.Serializable +class FilterGroup( + val name: String, + val filters: List = listOf(), + val isMultiSelect: Boolean, + val id: String? = null +) { + @kotlinx.serialization.Transient + val idOrName: String get() = id ?: name; + + companion object { + fun fromV8(config: IV8PluginConfig, value: V8ValueObject): FilterGroup { + return FilterGroup( + value.getString("name"), + value.getOrDefault(config, "filters", "FilterGroup", null) + ?.toArray() + ?.map { FilterCapability.fromV8(it as V8ValueObject) } + ?.toList() ?: listOf(), + value.getBoolean("isMultiSelect"), + value.getString("id")); + } + } +} + +@kotlinx.serialization.Serializable +class FilterCapability( + val name: String, + val value: String, + val id: String? = null) { + val idOrName: String get() = id ?: name; + + companion object { + fun fromV8(obj: V8ValueObject): FilterCapability { + val value = obj.get("value") as V8Value; + return FilterCapability( + obj.getString("name"), + if(value is V8ValueInteger) + value.value.toString() + else + value.toString(), + obj.getString("id") + ); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/Thumbnails.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/Thumbnails.kt new file mode 100644 index 00000000..a4ab6d8a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/Thumbnails.kt @@ -0,0 +1,45 @@ +package com.futo.platformplayer.api.media.models + +import com.caoccao.javet.values.reference.V8ValueArray +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow + +@kotlinx.serialization.Serializable +class Thumbnails { + val sources : Array; + + constructor() { sources = arrayOf(); } + constructor(thumbnails : Array) { + sources = thumbnails.filter {it.url != null} .sortedBy { it.quality }.toTypedArray(); + } + + fun getHQThumbnail() : String? { + return sources.lastOrNull()?.url; + } + fun getLQThumbnail() : String? { + return sources.firstOrNull()?.url; + } + fun hasMultiple() = sources.size > 1; + + + companion object { + fun fromV8(config: IV8PluginConfig, value: V8ValueObject): Thumbnails { + return Thumbnails((value.getOrThrow(config, "sources", "Thumbnails")) + .toArray() + .map { Thumbnail.fromV8(it as V8ValueObject) } + .toTypedArray()); + } + } +} +@kotlinx.serialization.Serializable +data class Thumbnail(val url : String?, val quality : Int = 0) { + + companion object { + fun fromV8(value: V8ValueObject): Thumbnail { + return Thumbnail( + value.getString("url"), + value.getInteger("quality")); + } + } +}; \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/channels/IPlatformChannel.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/channels/IPlatformChannel.kt new file mode 100644 index 00000000..e744be9b --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/channels/IPlatformChannel.kt @@ -0,0 +1,20 @@ +package com.futo.platformplayer.api.media.models.channels + +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.structures.IPager + +interface IPlatformChannel { + val id : PlatformID; + val name : String; + val thumbnail : String?; + val banner : String?; + val subscribers : Long; + val description: String?; + val url: String; + val links: Map; + val urlAlternatives: List; + + fun getContents(client: IPlatformClient): IPager; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/channels/SerializedChannel.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/channels/SerializedChannel.kt new file mode 100644 index 00000000..7c457a30 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/channels/SerializedChannel.kt @@ -0,0 +1,55 @@ +package com.futo.platformplayer.api.media.models.channels + +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.Serializer +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.structures.IPager +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +@kotlinx.serialization.Serializable +class SerializedChannel( + override val id: PlatformID, + override val name: String, + override val thumbnail: String?, + override val banner: String?, + override val subscribers: Long, + override val description: String?, + override val url: String, + override val links: Map, + override val urlAlternatives: List = listOf() +) : IPlatformChannel { + + fun toJson(): String { + return Json.encodeToString(this); + } + + fun fromJson(str: String): SerializedChannel { + return Serializer.json.decodeFromString(str); + } + fun fromJsonArray(str: String): Array { + return Serializer.json.decodeFromString>(str); + } + + override fun getContents(client: IPlatformClient): IPager { + TODO("Not yet implemented") + } + + companion object { + fun fromChannel(channel: IPlatformChannel): SerializedChannel { + return SerializedChannel( + channel.id, + channel.name, + channel.thumbnail, + channel.banner, + channel.subscribers, + channel.description, + channel.url, + channel.links, + channel.urlAlternatives + ); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/comments/IPlatformComment.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/comments/IPlatformComment.kt new file mode 100644 index 00000000..3cad3558 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/comments/IPlatformComment.kt @@ -0,0 +1,19 @@ +package com.futo.platformplayer.api.media.models.comments + +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.ratings.IRating +import com.futo.platformplayer.api.media.structures.IPager +import java.time.OffsetDateTime + +interface IPlatformComment { + val contextUrl: String; + val author : PlatformAuthorLink; + val message : String; + val rating : IRating; + val date : OffsetDateTime?; + + val replyCount : Int?; + + fun getReplies(client: IPlatformClient) : IPager?; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/comments/NoCommentsPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/comments/NoCommentsPager.kt new file mode 100644 index 00000000..a7a4f10d --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/comments/NoCommentsPager.kt @@ -0,0 +1,12 @@ +package com.futo.platformplayer.api.media.models.comments + +import com.futo.platformplayer.api.media.structures.IPager + +class NoCommentsPager : IPager { + + override fun hasMorePages(): Boolean = false; + override fun nextPage() { } + override fun getResults(): List { + return listOf(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/comments/PlatformComment.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/comments/PlatformComment.kt new file mode 100644 index 00000000..7de46eb3 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/comments/PlatformComment.kt @@ -0,0 +1,30 @@ +package com.futo.platformplayer.api.media.models.comments + +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.ratings.IRating +import com.futo.platformplayer.api.media.structures.IPager +import java.time.OffsetDateTime + +open class PlatformComment : IPlatformComment { + override val contextUrl: String; + override val author: PlatformAuthorLink; + override val message: String; + override val rating: IRating; + override val date: OffsetDateTime; + + override val replyCount: Int?; + + constructor(contextUrl: String, author: PlatformAuthorLink, msg: String, rating: IRating, date: OffsetDateTime, replyCount: Int? = null) { + this.contextUrl = contextUrl; + this.author = author; + this.message = msg; + this.rating = rating; + this.date = date; + this.replyCount = replyCount; + } + + override fun getReplies(client: IPlatformClient): IPager { + return NoCommentsPager(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/comments/PolycentricPlatformComment.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/comments/PolycentricPlatformComment.kt new file mode 100644 index 00000000..a07d10c9 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/comments/PolycentricPlatformComment.kt @@ -0,0 +1,42 @@ +package com.futo.platformplayer.api.media.models.comments + +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.ratings.IRating +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.polycentric.PolycentricCache +import com.futo.platformplayer.states.StatePolycentric +import com.futo.polycentric.core.Pointer +import com.futo.polycentric.core.SignedEvent +import userpackage.Protocol.Reference +import java.time.OffsetDateTime + +class PolycentricPlatformComment : IPlatformComment { + override val contextUrl: String; + override val author: PlatformAuthorLink; + override val message: String; + override val rating: IRating; + override val date: OffsetDateTime; + + override val replyCount: Int?; + + val reference: Reference; + + constructor(contextUrl: String, author: PlatformAuthorLink, msg: String, rating: IRating, date: OffsetDateTime, reference: Reference, replyCount: Int? = null) { + this.contextUrl = contextUrl; + this.author = author; + this.message = msg; + this.rating = rating; + this.date = date; + this.replyCount = replyCount; + this.reference = reference; + } + + override fun getReplies(client: IPlatformClient): IPager { + return NoCommentsPager(); + } + + fun cloneWithUpdatedReplyCount(replyCount: Int?): PolycentricPlatformComment { + return PolycentricPlatformComment(contextUrl, author, message, rating, date, reference, replyCount); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/contents/ContentType.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/contents/ContentType.kt new file mode 100644 index 00000000..4f93a377 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/contents/ContentType.kt @@ -0,0 +1,29 @@ +package com.futo.platformplayer.api.media.models.contents + +import com.futo.platformplayer.api.media.exceptions.UnknownPlatformException + +enum class ContentType(val value: Int) { + UNKNOWN(0), + MEDIA(1), + POST(2), + ARTICLE(3), + PLAYLIST(4), + + URL(9), + + NESTED_VIDEO(11), + + + PLACEHOLDER(90), + DEFERRED(91); + + companion object { + fun fromInt(value: Int): ContentType + { + val result = ContentType.values().firstOrNull { it.value == value }; + if(result == null) + throw UnknownPlatformException(value.toString()); + return result; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/contents/IPlatformContent.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/contents/IPlatformContent.kt new file mode 100644 index 00000000..554a4723 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/contents/IPlatformContent.kt @@ -0,0 +1,18 @@ +package com.futo.platformplayer.api.media.models.contents + +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import java.time.OffsetDateTime + +interface IPlatformContent { + val contentType: ContentType; + + val id: PlatformID; + val name: String; + val url: String; + val shareUrl: String; + + val datetime: OffsetDateTime?; + + val author: PlatformAuthorLink; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/contents/IPlatformContentDetails.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/contents/IPlatformContentDetails.kt new file mode 100644 index 00000000..cd422a9f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/contents/IPlatformContentDetails.kt @@ -0,0 +1,13 @@ +package com.futo.platformplayer.api.media.models.contents + +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker +import com.futo.platformplayer.api.media.structures.IPager + +interface IPlatformContentDetails: IPlatformContent { + + + fun getComments(client: IPlatformClient): IPager?; + fun getPlaybackTracker(): IPlaybackTracker?; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/contents/PlatformContentDeferred.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/contents/PlatformContentDeferred.kt new file mode 100644 index 00000000..c98c935c --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/contents/PlatformContentDeferred.kt @@ -0,0 +1,15 @@ +package com.futo.platformplayer.api.media.models.contents + +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import java.time.OffsetDateTime + +class PlatformContentDeferred(pluginId: String?): IPlatformContent { + override val contentType: ContentType = ContentType.DEFERRED; + override val id: PlatformID = PlatformID("", null, pluginId); + override val name: String = ""; + override val url: String = ""; + override val shareUrl: String = ""; + override val datetime: OffsetDateTime? = null; + override val author: PlatformAuthorLink = PlatformAuthorLink(PlatformID("", pluginId), "", "", null, null); +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/contents/PlatformContentPlaceholder.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/contents/PlatformContentPlaceholder.kt new file mode 100644 index 00000000..e8633aa8 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/contents/PlatformContentPlaceholder.kt @@ -0,0 +1,15 @@ +package com.futo.platformplayer.api.media.models.contents + +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import java.time.OffsetDateTime + +class PlatformContentPlaceholder(pluginId: String): IPlatformContent { + override val contentType: ContentType = ContentType.PLACEHOLDER; + override val id: PlatformID = PlatformID("", null, pluginId); + override val name: String = ""; + override val url: String = ""; + override val shareUrl: String = ""; + override val datetime: OffsetDateTime? = null; + override val author: PlatformAuthorLink = PlatformAuthorLink(PlatformID("", pluginId), "", "", null, null); +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/live/ILiveChatWindowDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/live/ILiveChatWindowDescriptor.kt new file mode 100644 index 00000000..ef5096fa --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/live/ILiveChatWindowDescriptor.kt @@ -0,0 +1,6 @@ +package com.futo.platformplayer.api.media.models.live + +interface ILiveChatWindowDescriptor { + val url: String; + val removeElements: List; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/live/ILiveEventChatMessage.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/live/ILiveEventChatMessage.kt new file mode 100644 index 00000000..c26b603a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/live/ILiveEventChatMessage.kt @@ -0,0 +1,8 @@ +package com.futo.platformplayer.api.media.models.live + +interface ILiveEventChatMessage: IPlatformLiveEvent { + + val name: String; + val thumbnail: String?; + val message: String; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/live/IPlatformLiveEvent.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/live/IPlatformLiveEvent.kt new file mode 100644 index 00000000..6ffaea35 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/live/IPlatformLiveEvent.kt @@ -0,0 +1,32 @@ +package com.futo.platformplayer.api.media.models.live + +import com.caoccao.javet.values.V8Value +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.ratings.IRating +import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes +import com.futo.platformplayer.api.media.models.ratings.RatingLikes +import com.futo.platformplayer.api.media.models.ratings.RatingScaler +import com.futo.platformplayer.api.media.models.ratings.RatingType +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow +import com.futo.platformplayer.orDefault + +interface IPlatformLiveEvent { + val type : LiveEventType; + + + companion object { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject, contextName: String = "Unknown") : IPlatformLiveEvent { + val contextName = "LiveEvent"; + val type = LiveEventType.fromInt(obj.getOrThrow(config, "type", contextName)); + return when(type) { + LiveEventType.COMMENT -> LiveEventComment.fromV8(config, obj); + LiveEventType.EMOJIS -> LiveEventEmojis.fromV8(config, obj); + LiveEventType.DONATION -> LiveEventDonation.fromV8(config, obj); + LiveEventType.VIEWCOUNT -> LiveEventViewCount.fromV8(config, obj); + LiveEventType.RAID -> LiveEventRaid.fromV8(config, obj); + else -> throw NotImplementedError("Unknown type ${type}"); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventComment.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventComment.kt new file mode 100644 index 00000000..28bbe15a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventComment.kt @@ -0,0 +1,42 @@ +package com.futo.platformplayer.api.media.models.live + +import com.caoccao.javet.values.reference.V8ValueArray +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.ratings.RatingLikes +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrDefault +import com.futo.platformplayer.getOrThrow + +class LiveEventComment: IPlatformLiveEvent, ILiveEventChatMessage { + override val type: LiveEventType = LiveEventType.COMMENT; + + override val name: String; + override val thumbnail: String?; + override val message: String; + + val colorName: String?; + val badges: List; + + constructor(name: String, thumbnail: String?, message: String, colorName: String? = null, badges: List? = null) { + this.name = name; + this.message = message; + this.thumbnail = thumbnail; + this.colorName = colorName; + this.badges = badges ?: listOf(); + } + + companion object { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : LiveEventComment { + val contextName = "LiveEventComment" + + val colorName = obj.getOrDefault(config, "colorName", contextName, null); + val badges = obj.getOrDefault>(config, "badges", contextName, null); + + return LiveEventComment( + obj.getOrThrow(config, "name", contextName), + obj.getOrThrow(config, "thumbnail", contextName, true), + obj.getOrThrow(config, "message", contextName), + colorName, badges); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventDonation.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventDonation.kt new file mode 100644 index 00000000..a4ac5d47 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventDonation.kt @@ -0,0 +1,50 @@ +package com.futo.platformplayer.api.media.models.live + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.ratings.RatingLikes +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrDefault +import com.futo.platformplayer.getOrThrow + +class LiveEventDonation: IPlatformLiveEvent, ILiveEventChatMessage { + override val type: LiveEventType = LiveEventType.DONATION; + + private val _creationTimestamp = System.currentTimeMillis(); + private var _hasExpired = false; + + override val name: String; + override val thumbnail: String?; + override val message: String; + val amount: String; + val colorDonation: String?; + + var expire: Int = 6000; + + + constructor(name: String, thumbnail: String?, message: String, amount: String, expire: Int = 6000, colorDonation: String? = null) { + this.name = name; + this.message = message; + this.thumbnail = thumbnail; + this.amount = amount; + this.expire = expire; + this.colorDonation = colorDonation; + } + + fun hasExpired(): Boolean { + _hasExpired = _hasExpired || (System.currentTimeMillis() - _creationTimestamp) > expire; + return _hasExpired; + } + + companion object { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : LiveEventDonation { + val contextName = "LiveEventDonation" + return LiveEventDonation( + obj.getOrThrow(config, "name", contextName), + obj.getOrThrow(config, "thumbnail", contextName, true), + obj.getOrThrow(config, "message", contextName), + obj.getOrThrow(config, "amount", contextName), + obj.getOrDefault(config, "expire", contextName, 6000) ?: 6000, + obj.getOrDefault(config, "colorDonation", contextName, null)); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventEmojis.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventEmojis.kt new file mode 100644 index 00000000..6e29bac5 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventEmojis.kt @@ -0,0 +1,23 @@ +package com.futo.platformplayer.api.media.models.live + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow + +class LiveEventEmojis: IPlatformLiveEvent { + override val type: LiveEventType = LiveEventType.EMOJIS; + + val emojis: HashMap; + + constructor(emojis: HashMap) { + this.emojis = emojis; + } + + companion object { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : LiveEventEmojis { + val contextName = "LiveEventEmojis" + return LiveEventEmojis( + obj.getOrThrow(config, "emojis", contextName)); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventRaid.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventRaid.kt new file mode 100644 index 00000000..ff5dd36f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventRaid.kt @@ -0,0 +1,29 @@ +package com.futo.platformplayer.api.media.models.live + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow + +class LiveEventRaid: IPlatformLiveEvent { + override val type: LiveEventType = LiveEventType.RAID; + + val targetName: String; + val targetThumbnail: String; + val targetUrl: String; + + constructor(name: String, url: String, thumbnail: String) { + this.targetName = name; + this.targetUrl = url; + this.targetThumbnail = thumbnail; + } + + companion object { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : LiveEventRaid { + val contextName = "LiveEventRaid" + return LiveEventRaid( + obj.getOrThrow(config, "targetName", contextName), + obj.getOrThrow(config, "targetUrl", contextName), + obj.getOrThrow(config, "targetThumbnail", contextName)); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventType.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventType.kt new file mode 100644 index 00000000..ddec2e0a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventType.kt @@ -0,0 +1,16 @@ +package com.futo.platformplayer.api.media.models.live + +enum class LiveEventType(val value : Int) { + UNKNOWN(0), + COMMENT(1), + EMOJIS(4), + DONATION(5), + VIEWCOUNT(10), + RAID(100); + + companion object{ + fun fromInt(value : Int) : LiveEventType{ + return LiveEventType.values().first { it.value == value }; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventViewCount.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventViewCount.kt new file mode 100644 index 00000000..adcfb883 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/live/LiveEventViewCount.kt @@ -0,0 +1,23 @@ +package com.futo.platformplayer.api.media.models.live + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow + +class LiveEventViewCount: IPlatformLiveEvent { + override val type: LiveEventType = LiveEventType.VIEWCOUNT; + + val viewCount: Int; + + constructor(viewCount: Int) { + this.viewCount = viewCount; + } + + companion object { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : LiveEventViewCount { + val contextName = "LiveEventViewCount" + return LiveEventViewCount( + obj.getOrThrow(config, "viewCount", contextName)); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/nested/IPlatformNestedContent.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/nested/IPlatformNestedContent.kt new file mode 100644 index 00000000..a44afc88 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/nested/IPlatformNestedContent.kt @@ -0,0 +1,16 @@ +package com.futo.platformplayer.api.media.models.nested + +import com.futo.platformplayer.api.media.models.Thumbnails +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.contents.IPlatformContent + +interface IPlatformNestedContent: IPlatformContent { + val nestedContentType: ContentType; + val contentUrl: String; + val contentName: String?; + val contentDescription: String?; + val contentProvider: String? + val contentThumbnails: Thumbnails; + val contentPlugin: String?; + val contentSupported: Boolean; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/playback/IPlaybackTracker.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/playback/IPlaybackTracker.kt new file mode 100644 index 00000000..59527d66 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/playback/IPlaybackTracker.kt @@ -0,0 +1,10 @@ +package com.futo.platformplayer.api.media.models.playback + +interface IPlaybackTracker { + val nextRequest: Int; + + fun shouldUpdate(): Boolean; + + fun onInit(seconds: Double); + fun onProgress(seconds: Double, isPlaying: Boolean); +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/playlists/IPlatformPlaylist.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/playlists/IPlatformPlaylist.kt new file mode 100644 index 00000000..d0470cfe --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/playlists/IPlatformPlaylist.kt @@ -0,0 +1,11 @@ +package com.futo.platformplayer.api.media.models.playlists + +import com.futo.platformplayer.api.media.models.Thumbnails +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.structures.IPager + +interface IPlatformPlaylist : IPlatformContent { + val thumbnail: String?; + val videoCount: Int; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/playlists/IPlatformPlaylistDetails.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/playlists/IPlatformPlaylistDetails.kt new file mode 100644 index 00000000..b7783668 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/playlists/IPlatformPlaylistDetails.kt @@ -0,0 +1,12 @@ +package com.futo.platformplayer.api.media.models.playlists + +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.models.Playlist + +interface IPlatformPlaylistDetails: IPlatformPlaylist { + //TODO: Determine if this should be IPlatformContent (probably not?) + val contents: IPager; + + fun toPlaylist(): Playlist; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/post/IPlatformPost.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/post/IPlatformPost.kt new file mode 100644 index 00000000..a5503c11 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/post/IPlatformPost.kt @@ -0,0 +1,13 @@ +package com.futo.platformplayer.api.media.models.post + +import com.futo.platformplayer.api.media.models.Thumbnails +import com.futo.platformplayer.api.media.models.contents.IPlatformContent + +/** + * A search result representing a video (overview data) + */ +interface IPlatformPost: IPlatformContent { + val description: String; + val thumbnails: List; + val images: List; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/post/IPlatformPostDetails.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/post/IPlatformPostDetails.kt new file mode 100644 index 00000000..985e8697 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/post/IPlatformPostDetails.kt @@ -0,0 +1,18 @@ +package com.futo.platformplayer.api.media.models.post + +import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails +import com.futo.platformplayer.api.media.models.ratings.IRating +import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor +import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource +import com.futo.platformplayer.api.media.models.video.IPlatformVideo + +/** + * A detailed video model with data including video/audio sources + */ +interface IPlatformPostDetails : IPlatformPost, IPlatformContentDetails { + val rating : IRating; + + val textType: TextType; + val content: String; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/post/TextType.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/post/TextType.kt new file mode 100644 index 00000000..8a2c20d4 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/post/TextType.kt @@ -0,0 +1,19 @@ +package com.futo.platformplayer.api.media.models.post + +import com.futo.platformplayer.api.media.exceptions.UnknownPlatformException + +enum class TextType(val value: Int) { + RAW(0), + HTML(1), + MARKUP(2); + + companion object { + fun fromInt(value: Int): TextType + { + val result = TextType.values().firstOrNull { it.value == value }; + if(result == null) + throw IllegalArgumentException("Unknown Texttype: $value"); + return result; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/IRating.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/IRating.kt new file mode 100644 index 00000000..4d560b9f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/IRating.kt @@ -0,0 +1,28 @@ +package com.futo.platformplayer.api.media.models.ratings + +import com.caoccao.javet.values.V8Value +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow +import com.futo.platformplayer.orDefault +import com.futo.platformplayer.serializers.IRatingSerializer + +@kotlinx.serialization.Serializable(with = IRatingSerializer::class) +interface IRating { + val type : RatingType; + + + companion object { + fun fromV8OrDefault(config: IV8PluginConfig, obj: V8Value?, default: IRating) = obj.orDefault(default) { fromV8(config, it as V8ValueObject) }; + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject, contextName: String = "Unknown") : IRating { + val contextName = "Rating"; + val type = RatingType.fromInt(obj.getOrThrow(config, "type", contextName)); + return when(type) { + RatingType.LIKES -> RatingLikes.fromV8(config, obj); + RatingType.LIKEDISLIKES -> RatingLikeDislikes.fromV8(config, obj); + RatingType.SCALE -> RatingScaler.fromV8(config, obj); + else -> throw NotImplementedError("Unknown type ${type}"); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingLikeDislikes.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingLikeDislikes.kt new file mode 100644 index 00000000..6d0e787b --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingLikeDislikes.kt @@ -0,0 +1,20 @@ +package com.futo.platformplayer.api.media.models.ratings + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow + +/** + * A rating that has both likes and dislikes + */ +@kotlinx.serialization.Serializable +class RatingLikeDislikes(val likes: Long, val dislikes: Long) : IRating { + + override val type: RatingType = RatingType.LIKEDISLIKES; + + companion object { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : RatingLikeDislikes { + return RatingLikeDislikes(obj.getOrThrow(config, "likes", "RatingLikeDislikes"), obj.getOrThrow(config, "dislikes", "RatingLikeDislikes")); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingLikes.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingLikes.kt new file mode 100644 index 00000000..e40169f2 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingLikes.kt @@ -0,0 +1,19 @@ +package com.futo.platformplayer.api.media.models.ratings + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow + +/** + * A rating that has just likes + */ +@kotlinx.serialization.Serializable +class RatingLikes(val likes: Long) : IRating { + override val type: RatingType = RatingType.LIKES; + + companion object { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : RatingLikes { + return RatingLikes(obj.getOrThrow(config, "likes", "RatingLikes")); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingScaler.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingScaler.kt new file mode 100644 index 00000000..7646cf24 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingScaler.kt @@ -0,0 +1,19 @@ +package com.futo.platformplayer.api.media.models.ratings + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow + +/** + * A rating that is based on a scaler (0..1) + */ +@kotlinx.serialization.Serializable +class RatingScaler(val value: Float) : IRating { + override val type: RatingType = RatingType.SCALE; + + companion object { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : RatingScaler { + return RatingScaler(obj.getOrThrow(config, "value", "RatingScaler")); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingType.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingType.kt new file mode 100644 index 00000000..eba21430 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/RatingType.kt @@ -0,0 +1,14 @@ +package com.futo.platformplayer.api.media.models.ratings + +enum class RatingType(val value : Int) { + UNKNOWN(0), + LIKES(1), + LIKEDISLIKES(2), + SCALE(3); + + companion object{ + fun fromInt(value : Int) : RatingType{ + return RatingType.values().first { it.value == value }; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/IVideoSourceDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/IVideoSourceDescriptor.kt new file mode 100644 index 00000000..d5c1d964 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/IVideoSourceDescriptor.kt @@ -0,0 +1,8 @@ +package com.futo.platformplayer.api.media.models.streams + +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource + +interface IVideoSourceDescriptor { + val isUnMuxed: Boolean; + val videoSources: Array; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/LocalVideoMuxedSourceDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/LocalVideoMuxedSourceDescriptor.kt new file mode 100644 index 00000000..b5309931 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/LocalVideoMuxedSourceDescriptor.kt @@ -0,0 +1,10 @@ +package com.futo.platformplayer.api.media.models.streams + +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource +import com.futo.platformplayer.downloads.VideoLocal + +class LocalVideoMuxedSourceDescriptor( + private val video: VideoLocal + ) : VideoMuxedSourceDescriptor() { + override val videoSources: Array get() = video.videoSource.toTypedArray(); +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/LocalVideoUnMuxedSourceDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/LocalVideoUnMuxedSourceDescriptor.kt new file mode 100644 index 00000000..b5d28c6f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/LocalVideoUnMuxedSourceDescriptor.kt @@ -0,0 +1,11 @@ +package com.futo.platformplayer.api.media.models.streams + +import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource +import com.futo.platformplayer.downloads.VideoLocal + + +class LocalVideoUnMuxedSourceDescriptor(private val video: VideoLocal) : VideoUnMuxedSourceDescriptor() { + override val videoSources: Array get() = video.videoSource.toTypedArray(); + override val audioSources: Array get() = video.audioSource.toTypedArray(); +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/VideoMuxedSourceDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/VideoMuxedSourceDescriptor.kt new file mode 100644 index 00000000..30d35fba --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/VideoMuxedSourceDescriptor.kt @@ -0,0 +1,10 @@ +package com.futo.platformplayer.api.media.models.streams + +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource + +@kotlinx.serialization.Serializable +abstract class VideoMuxedSourceDescriptor : IVideoSourceDescriptor { + override val isUnMuxed : Boolean = false; + + abstract override val videoSources : Array; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/VideoUnMuxedSourceDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/VideoUnMuxedSourceDescriptor.kt new file mode 100644 index 00000000..f298291d --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/VideoUnMuxedSourceDescriptor.kt @@ -0,0 +1,12 @@ +package com.futo.platformplayer.api.media.models.streams + +import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource + +@kotlinx.serialization.Serializable +abstract class VideoUnMuxedSourceDescriptor : IVideoSourceDescriptor { + override val isUnMuxed : Boolean = true; + + abstract override val videoSources : Array; + abstract val audioSources : Array; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/AudioUrlSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/AudioUrlSource.kt new file mode 100644 index 00000000..67548b89 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/AudioUrlSource.kt @@ -0,0 +1,46 @@ +package com.futo.platformplayer.api.media.models.streams.sources + +import com.futo.platformplayer.api.media.models.streams.sources.other.IStreamMetaDataSource +import com.futo.platformplayer.api.media.models.streams.sources.other.StreamMetaData +import com.futo.platformplayer.others.Language + +@kotlinx.serialization.Serializable +class AudioUrlSource( + override val name: String, + val url : String, + override val bitrate : Int, + override val container : String = "", + override val codec: String = "", + override val language: String = Language.UNKNOWN, + override val duration: Long? = null, + override var priority: Boolean = false +) : IAudioUrlSource, IStreamMetaDataSource{ + override var streamMetaData: StreamMetaData? = null; + + override fun getAudioUrl() : String { + return url; + } + + companion object { + fun fromUrlSource(source: IAudioUrlSource?): AudioUrlSource? { + if(source == null) + return null; + + val streamData = if(source is IStreamMetaDataSource) + source.streamMetaData else null; + + val ret = AudioUrlSource( + source.name, + source.getAudioUrl(), + source.bitrate, + source.container, + source.codec, + source.language, + source.duration + ); + ret.streamMetaData = streamData; + + return ret; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/DashManifestSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/DashManifestSource.kt new file mode 100644 index 00000000..3d117516 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/DashManifestSource.kt @@ -0,0 +1,18 @@ +package com.futo.platformplayer.api.media.models.streams.sources + +class DashManifestSource : IVideoSource, IDashManifestSource { + override val width : Int = 0; + override val height : Int = 0; + override val container : String = "Dash"; + override val codec: String = "Dash"; + override val name : String = "Dash"; + override val bitrate: Int? = null; + override val url : String; + override val duration: Long get() = 0; + + override var priority: Boolean = false; + + constructor(url : String) { + this.url = url; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/HLSManifestSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/HLSManifestSource.kt new file mode 100644 index 00000000..52304473 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/HLSManifestSource.kt @@ -0,0 +1,18 @@ +package com.futo.platformplayer.api.media.models.streams.sources + +class HLSManifestSource : IVideoSource, IHLSManifestSource { + override val width : Int = 0; + override val height : Int = 0; + override val container : String = "HLS"; + override val codec: String = "HLS"; + override val name : String = "HLS"; + override val bitrate : Int? = null; + override val url : String; + override val duration: Long = 0; + + override var priority: Boolean = false; + + constructor(url : String) { + this.url = url; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IAudioSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IAudioSource.kt new file mode 100644 index 00000000..eca17e47 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IAudioSource.kt @@ -0,0 +1,11 @@ +package com.futo.platformplayer.api.media.models.streams.sources + +interface IAudioSource { + val name : String; + val bitrate : Int; + val container : String; + val codec : String; + val language : String; + val duration : Long?; + val priority: Boolean; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IAudioUrlSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IAudioUrlSource.kt new file mode 100644 index 00000000..4a9ea42a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IAudioUrlSource.kt @@ -0,0 +1,5 @@ +package com.futo.platformplayer.api.media.models.streams.sources + +interface IAudioUrlSource : IAudioSource { + fun getAudioUrl(): String; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IDashManifestSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IDashManifestSource.kt new file mode 100644 index 00000000..c6086d7e --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IDashManifestSource.kt @@ -0,0 +1,5 @@ +package com.futo.platformplayer.api.media.models.streams.sources + +interface IDashManifestSource : IVideoSource { + val url : String; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IHLSManifestSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IHLSManifestSource.kt new file mode 100644 index 00000000..8993d044 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IHLSManifestSource.kt @@ -0,0 +1,8 @@ +package com.futo.platformplayer.api.media.models.streams.sources + +interface IHLSManifestSource : IVideoSource { + val url : String; +} +interface IHLSManifestAudioSource : IAudioSource { + val url : String; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IVideoSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IVideoSource.kt new file mode 100644 index 00000000..867c1ee5 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IVideoSource.kt @@ -0,0 +1,12 @@ +package com.futo.platformplayer.api.media.models.streams.sources + +interface IVideoSource { + val name : String; + val width : Int; + val height : Int; + val container : String; + val codec : String; + val bitrate : Int?; + val duration: Long; + val priority: Boolean; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IVideoUrlSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IVideoUrlSource.kt new file mode 100644 index 00000000..f5bc00d7 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IVideoUrlSource.kt @@ -0,0 +1,5 @@ +package com.futo.platformplayer.api.media.models.streams.sources + +interface IVideoUrlSource : IVideoSource { + fun getVideoUrl(): String; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalAudioSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalAudioSource.kt new file mode 100644 index 00000000..397c24a4 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalAudioSource.kt @@ -0,0 +1,48 @@ +package com.futo.platformplayer.api.media.models.streams.sources + +import com.futo.platformplayer.api.media.models.streams.sources.other.IStreamMetaDataSource +import com.futo.platformplayer.api.media.models.streams.sources.other.StreamMetaData +import com.futo.platformplayer.others.Language + +@kotlinx.serialization.Serializable +class LocalAudioSource : IAudioSource, IStreamMetaDataSource { + + override val name: String; + override val bitrate : Int; + override val container : String; + override val codec : String; + override val language: String; + override val duration: Long? = null; + + override var priority: Boolean = false; + + val filePath : String; + val fileSize: Long; + + //Only for particular videos + override var streamMetaData: StreamMetaData? = null; + + constructor(name: String?, filePath : String, fileSize: Long, bitrate : Int, container : String = "", codec: String = "", language: String = Language.UNKNOWN) { + this.name = name ?: "${container} ${bitrate}"; + this.bitrate = bitrate; + this.container = container; + this.codec = codec; + this.filePath = filePath; + this.language = language; + this.fileSize = fileSize; + } + + companion object { + fun fromSource(source: IAudioSource, path: String, fileSize: Long): LocalAudioSource { + return LocalAudioSource( + source.name, + path, + fileSize, + source.bitrate, + source.container, + source.codec, + source.language + ); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalSubtitleSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalSubtitleSource.kt new file mode 100644 index 00000000..0c866af4 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalSubtitleSource.kt @@ -0,0 +1,40 @@ +package com.futo.platformplayer.api.media.models.streams.sources + +import android.net.Uri +import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource +import java.io.File + +@kotlinx.serialization.Serializable +class LocalSubtitleSource : ISubtitleSource { + override val name: String; + override val url: String?; + override val format: String?; + override val hasFetch: Boolean get() = false; + + val filePath: String; + + constructor(name: String, format: String?, filePath: String) { + this.name = name; + this.format = format; + this.filePath = filePath; + this.url = Uri.fromFile(File(filePath)).toString(); + } + + override fun getSubtitles(): String? { + return null; + } + + override suspend fun getSubtitlesURI(): Uri? { + return null; + } + + companion object { + fun fromSource(source: SubtitleRawSource, path: String): LocalSubtitleSource { + return LocalSubtitleSource( + source.name, + source.format, + path + ); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalVideoSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalVideoSource.kt new file mode 100644 index 00000000..43455a50 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalVideoSource.kt @@ -0,0 +1,52 @@ +package com.futo.platformplayer.api.media.models.streams.sources + +import com.futo.platformplayer.api.media.models.streams.sources.other.IStreamMetaDataSource +import com.futo.platformplayer.api.media.models.streams.sources.other.StreamMetaData + +@kotlinx.serialization.Serializable +class LocalVideoSource : IVideoSource, IStreamMetaDataSource { + + override val width : Int; + override val height : Int; + override val container : String; + override val codec : String; + override val name : String; + override val bitrate : Int; + override val duration : Long; + + override var priority: Boolean = false; + + val filePath : String; + val fileSize : Long; + + //Only for particular videos + override var streamMetaData: StreamMetaData? = null; + + constructor(name : String, filePath : String, fileSize: Long, width : Int = 0, height : Int = 0, duration: Long = 0, container : String = "", codec : String = "", bitrate : Int = 0) { + this.name = name; + this.width = width; + this.height = height; + this.container = container; + this.codec = codec; + this.duration = duration; + this.filePath = filePath; + this.fileSize = fileSize; + this.bitrate = bitrate; + } + + companion object { + fun fromSource(source: IVideoSource, path: String, fileSize: Long): LocalVideoSource { + return LocalVideoSource( + source.name, + path, + fileSize, + source.width, + source.height, + source.duration, + source.container, + source.codec, + source.bitrate?:0 + ); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/SubtitleRawSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/SubtitleRawSource.kt new file mode 100644 index 00000000..f4dc29a2 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/SubtitleRawSource.kt @@ -0,0 +1,21 @@ +package com.futo.platformplayer.api.media.models.streams.sources + +import android.net.Uri +import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource + +@kotlinx.serialization.Serializable +class SubtitleRawSource( + override val name: String, + override val format: String?, + val _subtitles: String, + override val url: String? = null, + override val hasFetch: Boolean = true +) : ISubtitleSource { + override fun getSubtitles(): String? { + return _subtitles; + } + + override suspend fun getSubtitlesURI(): Uri? { + return null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/VideoUrlSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/VideoUrlSource.kt new file mode 100644 index 00000000..490b8d4c --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/VideoUrlSource.kt @@ -0,0 +1,48 @@ +package com.futo.platformplayer.api.media.models.streams.sources + +import com.futo.platformplayer.api.media.models.streams.sources.other.IStreamMetaDataSource +import com.futo.platformplayer.api.media.models.streams.sources.other.StreamMetaData + +@kotlinx.serialization.Serializable +open class VideoUrlSource( + override val name : String, + val url : String, + override val width : Int = 0, + override val height : Int = 0, + override val duration: Long = 0, + override val container : String = "", + override val codec : String = "", + override val bitrate : Int? = 0, + + override var priority: Boolean = false +) : IVideoUrlSource, IStreamMetaDataSource { + override var streamMetaData: StreamMetaData? = null; + + override fun getVideoUrl() : String { + return url; + } + + companion object { + fun fromUrlSource(source: IVideoUrlSource?): VideoUrlSource? { + if(source == null) + return null; + + val streamData = if(source is IStreamMetaDataSource) + source.streamMetaData else null; + + val ret = VideoUrlSource( + source.name, + source.getVideoUrl(), + source.width, + source.height, + source.duration, + source.container, + source.codec, + source.bitrate + ); + ret.streamMetaData = streamData; + + return ret; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/other/IStreamMetaDataSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/other/IStreamMetaDataSource.kt new file mode 100644 index 00000000..489f59fd --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/other/IStreamMetaDataSource.kt @@ -0,0 +1,5 @@ +package com.futo.platformplayer.api.media.models.streams.sources.other + +interface IStreamMetaDataSource { + val streamMetaData: StreamMetaData?; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/other/StreamMetaData.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/other/StreamMetaData.kt new file mode 100644 index 00000000..13545acb --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/other/StreamMetaData.kt @@ -0,0 +1,9 @@ +package com.futo.platformplayer.api.media.models.streams.sources.other + +@kotlinx.serialization.Serializable +data class StreamMetaData( + var fileInitStart: Int? = null, + var fileInitEnd: Int? = null, + var fileIndexStart: Int? = null, + var fileIndexEnd: Int? = null +) {} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/subtitles/ISubtitleSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/subtitles/ISubtitleSource.kt new file mode 100644 index 00000000..e210774d --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/subtitles/ISubtitleSource.kt @@ -0,0 +1,16 @@ +package com.futo.platformplayer.api.media.models.subtitles + +import android.net.Uri +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred + +interface ISubtitleSource { + val name: String; + val url: String?; + val format: String?; + val hasFetch: Boolean; + + fun getSubtitles(): String?; + + suspend fun getSubtitlesURI(): Uri?; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/video/IPlatformVideo.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/video/IPlatformVideo.kt new file mode 100644 index 00000000..69717eee --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/video/IPlatformVideo.kt @@ -0,0 +1,16 @@ +package com.futo.platformplayer.api.media.models.video + +import com.futo.platformplayer.api.media.models.Thumbnails +import com.futo.platformplayer.api.media.models.contents.IPlatformContent + +/** + * A search result representing a video (overview data) + */ +interface IPlatformVideo : IPlatformContent { + val thumbnails: Thumbnails; + + val duration: Long; + val viewCount: Long; + + val isLive : Boolean; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/video/IPlatformVideoDetails.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/video/IPlatformVideoDetails.kt new file mode 100644 index 00000000..433b44c4 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/video/IPlatformVideoDetails.kt @@ -0,0 +1,28 @@ +package com.futo.platformplayer.api.media.models.video + +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails +import com.futo.platformplayer.api.media.models.ratings.IRating +import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor +import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource +import com.futo.platformplayer.api.media.structures.IPager + +/** + * A detailed video model with data including video/audio sources + * TODO:TBD if it should be a derived of IPlatformSearchVideo (to cover identical fields) + */ +interface IPlatformVideoDetails : IPlatformVideo, IPlatformContentDetails { + val rating : IRating; + + val description : String; + + val video : IVideoSourceDescriptor; + val preview : IVideoSourceDescriptor?; + val live : IVideoSource?; + val dash: IDashManifestSource?; + val hls: IHLSManifestSource?; + + val subtitles: List; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/video/ISerializedVideoSourceDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/video/ISerializedVideoSourceDescriptor.kt new file mode 100644 index 00000000..a5498ac3 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/video/ISerializedVideoSourceDescriptor.kt @@ -0,0 +1,27 @@ +package com.futo.platformplayer.api.media.models.video + +import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor +import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor +import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.serializers.VideoDescriptorSerializer + +@kotlinx.serialization.Serializable(with = VideoDescriptorSerializer::class) +interface ISerializedVideoSourceDescriptor: IVideoSourceDescriptor { + + companion object { + fun fromDescriptor(descriptor: IVideoSourceDescriptor): ISerializedVideoSourceDescriptor { + val videoSources = descriptor.videoSources + .filter { it is IVideoUrlSource } + .map { VideoUrlSource.fromUrlSource(it as IVideoUrlSource)!! } + .toTypedArray(); + + if(descriptor !is VideoUnMuxedSourceDescriptor) + return SerializedVideoMuxedSourceDescriptor(videoSources); + else + return SerializedVideoNonMuxedSourceDescriptor(videoSources, descriptor.audioSources + .filter { it is IAudioUrlSource } + .map { AudioUrlSource.fromUrlSource(it as IAudioUrlSource)!! } + .toTypedArray()); + } + } +}; \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformContent.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformContent.kt new file mode 100644 index 00000000..30c6c9bd --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformContent.kt @@ -0,0 +1,25 @@ +package com.futo.platformplayer.api.media.models.video + +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.nested.IPlatformNestedContent +import com.futo.platformplayer.api.media.models.post.IPlatformPost +import com.futo.platformplayer.serializers.PlatformContentSerializer + +@kotlinx.serialization.Serializable(with = PlatformContentSerializer::class) +interface SerializedPlatformContent: IPlatformContent { + fun toJson() : String; + fun fromJson(str : String) : SerializedPlatformContent; + fun fromJsonArray(str : String) : Array; + + companion object { + fun fromContent(content : IPlatformContent) : SerializedPlatformContent { + return when(content.contentType) { + ContentType.MEDIA -> SerializedPlatformVideo.fromVideo(content as IPlatformVideo); + ContentType.NESTED_VIDEO -> SerializedPlatformNestedContent.fromNested(content as IPlatformNestedContent); + ContentType.POST -> SerializedPlatformPost.fromPost(content as IPlatformPost); + else -> throw NotImplementedError("Content type ${content.contentType} not implemented"); + }; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformNestedContent.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformNestedContent.kt new file mode 100644 index 00000000..6ab033a5 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformNestedContent.kt @@ -0,0 +1,66 @@ +package com.futo.platformplayer.api.media.models.video + +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.Serializer +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.Thumbnails +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.nested.IPlatformNestedContent +import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer +import com.futo.platformplayer.states.StatePlatform +import com.futo.polycentric.core.combineHashCodes +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.time.OffsetDateTime + +@kotlinx.serialization.Serializable +open class SerializedPlatformNestedContent( + override val id: PlatformID, + override val name: String, + override val author: PlatformAuthorLink, + @kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class) + override val datetime: OffsetDateTime?, + override val url: String, + override val shareUrl: String, + override val nestedContentType: ContentType, + override val contentUrl: String, + override val contentName: String?, + override val contentDescription: String?, + override val contentProvider: String?, + override val contentThumbnails: Thumbnails +) : IPlatformNestedContent, SerializedPlatformContent { + final override val contentType: ContentType get() = ContentType.MEDIA; + + override val contentPlugin: String? = StatePlatform.instance.getContentClientOrNull(contentUrl)?.id; + override val contentSupported: Boolean get() = contentPlugin != null; + + override fun toJson() : String { + return Json.encodeToString(this); + } + override fun fromJson(str : String) : SerializedPlatformNestedContent { + return Serializer.json.decodeFromString(str); + } + override fun fromJsonArray(str : String) : Array { + return Serializer.json.decodeFromString>(str); + } + + companion object { + fun fromNested(content: IPlatformNestedContent) : SerializedPlatformNestedContent { + return SerializedPlatformNestedContent( + content.id, + content.name, + content.author, + content.datetime, + content.url, + content.shareUrl, + content.nestedContentType, + content.contentUrl, + content.contentName, + content.contentDescription, + content.contentProvider, + content.contentThumbnails + ); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformPost.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformPost.kt new file mode 100644 index 00000000..8100fea7 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformPost.kt @@ -0,0 +1,56 @@ +package com.futo.platformplayer.api.media.models.video + +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.Serializer +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.Thumbnails +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.post.IPlatformPost +import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer +import com.futo.polycentric.core.combineHashCodes +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.time.OffsetDateTime + +@kotlinx.serialization.Serializable +open class SerializedPlatformPost( + override val id: PlatformID, + override val name: String, + override val url: String, + override val shareUrl: String, + @kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class) + override val datetime: OffsetDateTime?, + override val author: PlatformAuthorLink, + override val description: String, + override val thumbnails: List, + override val images: List +) : IPlatformPost, SerializedPlatformContent { + final override val contentType: ContentType get() = ContentType.POST; + + override fun toJson() : String { + return Json.encodeToString(this); + } + override fun fromJson(str : String) : SerializedPlatformVideo { + return Serializer.json.decodeFromString(str); + } + override fun fromJsonArray(str : String) : Array { + return Serializer.json.decodeFromString>(str); + } + + companion object { + fun fromPost(post: IPlatformPost) : SerializedPlatformPost { + return SerializedPlatformPost( + post.id, + post.name, + post.url, + post.shareUrl, + post.datetime, + post.author, + post.description, + post.thumbnails, + post.images + ); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformVideo.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformVideo.kt new file mode 100644 index 00000000..12dd9e78 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformVideo.kt @@ -0,0 +1,58 @@ +package com.futo.platformplayer.api.media.models.video + +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.Serializer +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.Thumbnails +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer +import com.futo.polycentric.core.combineHashCodes +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.time.OffsetDateTime + +@kotlinx.serialization.Serializable +open class SerializedPlatformVideo( + override val id: PlatformID, + override val name: String, + override val thumbnails: Thumbnails, + override val author: PlatformAuthorLink, + @kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class) + override val datetime: OffsetDateTime?, + override val url: String, + override val shareUrl: String, + + override val duration: Long, + override val viewCount: Long, +) : IPlatformVideo, SerializedPlatformContent { + final override val contentType: ContentType get() = ContentType.MEDIA; + + override val isLive: Boolean = false; + + override fun toJson() : String { + return Json.encodeToString(this); + } + override fun fromJson(str : String) : SerializedPlatformVideo { + return Serializer.json.decodeFromString(str); + } + override fun fromJsonArray(str : String) : Array { + return Serializer.json.decodeFromString>(str); + } + + companion object { + fun fromVideo(video: IPlatformVideo) : SerializedPlatformVideo { + return SerializedPlatformVideo( + video.id, + video.name, + video.thumbnails, + video.author, + video.datetime, + video.url, + video.shareUrl, + video.duration, + video.viewCount + ); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformVideoDetails.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformVideoDetails.kt new file mode 100644 index 00000000..39851441 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformVideoDetails.kt @@ -0,0 +1,81 @@ +package com.futo.platformplayer.api.media.models.video + +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.Serializer +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.Thumbnails +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker +import com.futo.platformplayer.api.media.models.ratings.IRating +import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.time.OffsetDateTime + +@kotlinx.serialization.Serializable +open class SerializedPlatformVideoDetails( + override val id: PlatformID, + override val name: String, + override val thumbnails: Thumbnails, + override val author: PlatformAuthorLink, + @kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class) + override val datetime: OffsetDateTime?, + override val url: String, + override val shareUrl: String, + + override val duration: Long, + override val viewCount: Long, + + override val rating: IRating, + override val description: String, + + override val video: ISerializedVideoSourceDescriptor, + override val preview: ISerializedVideoSourceDescriptor?, + + override val subtitles: List = listOf() +) : IPlatformVideo, IPlatformVideoDetails { + final override val contentType: ContentType get() = ContentType.MEDIA; + + override val isLive: Boolean get() = false; + + override val dash: IDashManifestSource? get() = null; + override val hls: IHLSManifestSource? get() = null; + override val live: IVideoSource? get() = null; + + fun toJson() : String { + return Json.encodeToString(this); + } + fun fromJson(str : String) : SerializedPlatformVideoDetails { + return Serializer.json.decodeFromString(str); + } + + override fun getComments(client: IPlatformClient): IPager? = null; + override fun getPlaybackTracker(): IPlaybackTracker? = null; + + companion object { + fun fromVideo(video : IPlatformVideoDetails, subtitleSources: List) : SerializedPlatformVideoDetails { + val descriptor = ISerializedVideoSourceDescriptor.fromDescriptor(video.video); + return SerializedPlatformVideoDetails( + video.id, + video.name, + video.thumbnails, + video.author, + video.datetime, + video.url, + video.shareUrl, + video.duration, + video.viewCount, + video.rating, + video.description, + descriptor, + video.preview?.let { descriptor }, + subtitleSources + ); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedVideoMuxedSourceDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedVideoMuxedSourceDescriptor.kt new file mode 100644 index 00000000..ad2d14ec --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedVideoMuxedSourceDescriptor.kt @@ -0,0 +1,13 @@ +package com.futo.platformplayer.api.media.models.video + +import com.futo.platformplayer.api.media.models.streams.VideoMuxedSourceDescriptor +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource +import com.futo.platformplayer.api.media.models.streams.sources.VideoUrlSource + +@kotlinx.serialization.Serializable +class SerializedVideoMuxedSourceDescriptor( + val _videoSources: Array +): VideoMuxedSourceDescriptor(), ISerializedVideoSourceDescriptor { + @kotlinx.serialization.Transient + override val videoSources: Array get() = _videoSources.map { it }.toTypedArray(); +}; \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedVideoUnmuxedSourceDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedVideoUnmuxedSourceDescriptor.kt new file mode 100644 index 00000000..f7a84c05 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedVideoUnmuxedSourceDescriptor.kt @@ -0,0 +1,15 @@ +package com.futo.platformplayer.api.media.models.video + +import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor +import com.futo.platformplayer.api.media.models.streams.sources.* + +@kotlinx.serialization.Serializable +class SerializedVideoNonMuxedSourceDescriptor( + val _videoSources: Array, + val _audioSources: Array +): VideoUnMuxedSourceDescriptor(), ISerializedVideoSourceDescriptor { + @kotlinx.serialization.Transient + override val videoSources: Array get() = _videoSources.map { it }.toTypedArray(); + @kotlinx.serialization.Transient + override val audioSources: Array get() = _audioSources.map { it }.toTypedArray(); +}; \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt new file mode 100644 index 00000000..f1167586 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt @@ -0,0 +1,189 @@ +package com.futo.platformplayer.api.media.platforms.js + +import android.content.Context +import com.futo.platformplayer.states.StateDeveloper +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails +import com.futo.platformplayer.api.media.structures.IPager +import java.util.* + +class DevJSClient : JSClient { + override val id: String + get() = StateDeveloper.DEV_ID; + + private val _devScript: String; + private var _auth: SourceAuth? = null; + + val devID: String; + + constructor(context: Context, config: SourcePluginConfig, script: String, auth: SourceAuth? = null, devID: String? = null): super(context, SourcePluginDescriptor(config, auth?.toEncrypted(), listOf("DEV")), null, script) { + _devScript = script; + _auth = auth; + this.devID = devID ?: UUID.randomUUID().toString().substring(0, 5); + } + constructor(context: Context, descriptor: SourcePluginDescriptor, script: String, auth: SourceAuth? = null, savedState: String? = null, devID: String? = null): super(context, descriptor, savedState, script) { + _devScript = script; + _auth = auth; + this.devID = devID ?: UUID.randomUUID().toString().substring(0, 5); + } + + fun setAuth(auth: SourceAuth? = null) { + _auth = auth; + } + fun recreate(context: Context): DevJSClient { + return DevJSClient(context, config, _devScript, _auth, devID); + } + + override fun getCopy(): JSClient { + return DevJSClient(_context, descriptor, _script, _auth, saveState(), devID); + } + + override fun initialize() { + return StateDeveloper.instance.handleDevCall(devID, "enable"){ + super.initialize(); + }; + } + override fun disable() { + return StateDeveloper.instance.handleDevCall(devID, "disable"){ + super.disable() + }; + } + + //Home + override fun getHome(): IPager { + return StateDeveloper.instance.handleDevCall(devID, "getHome"){ + DevPlatformVideoPager(devID, "homePager", super.getHome()); + }; + } + + //Search + override fun searchSuggestions(query: String): Array { + return StateDeveloper.instance.handleDevCall(devID, "searchSuggestions"){ + super.searchSuggestions(query); + }; + } + override fun search( + query: String, + type: String?, + order: String?, + filters: Map>? + ): IPager { + return StateDeveloper.instance.handleDevCall(devID, "search"){ + DevPlatformVideoPager(devID, "searchPager", super.search(query, type, order, filters)); + }; + } + + //Channel + override fun isChannelUrl(url: String): Boolean { + return StateDeveloper.instance.handleDevCall(devID, "isChannelUrl"){ + super.isChannelUrl(url); + }; + } + override fun getChannel(channelUrl: String): IPlatformChannel { + return StateDeveloper.instance.handleDevCall(devID, "getChannel"){ + super.getChannel(channelUrl); + }; + } + override fun getChannelContents( + channelUrl: String, + type: String?, + order: String?, + filters: Map>? + ): IPager { + return StateDeveloper.instance.handleDevCall(devID, "getChannelVideos"){ + DevPlatformVideoPager(devID, "channelPager", super.getChannelContents(channelUrl, type, order, filters)); + }; + } + + //Video + override fun isContentDetailsUrl(url: String): Boolean { + return StateDeveloper.instance.handleDevCall(devID, "isVideoDetailsUrl"){ + super.isContentDetailsUrl(url); + }; + } + override fun getContentDetails(url: String): IPlatformContentDetails { + return StateDeveloper.instance.handleDevCall(devID, "getVideoDetails"){ + super.getContentDetails(url); + }; + } + + //Comments + override fun getComments(url: String): IPager { + return StateDeveloper.instance.handleDevCall(devID, "getComments"){ + DevPlatformCommentPager(devID, "commentPager", super.getComments(url)); + }; + } + override fun getSubComments(comment: IPlatformComment): IPager { + return StateDeveloper.instance.handleDevCall(devID, "getSubComments"){ + DevPlatformCommentPager(devID, "subCommentPager", super.getSubComments(comment)); + }; + } + + override fun searchPlaylists( + query: String, + type: String?, + order: String?, + filters: Map>? + ): IPager { + return StateDeveloper.instance.handleDevCall(devID, "searchPlaylists"){ + DevPlatformVideoPager(devID, "searchPlaylists", super.searchPlaylists(query, type, order, filters)); + }; + } + + class DevPlatformVideoPager( + private val _devId: String, + private val _contextName: String, + private val _pager: IPager + ) : IPager { + + private var _morePagesWasFalse = false; + + override fun hasMorePages(): Boolean { + if(_morePagesWasFalse) + return false; + return StateDeveloper.instance.handleDevCall(_devId, "${_contextName}.hasMorePages", true) { + val result = _pager.hasMorePages(); + if(!result) + _morePagesWasFalse = true; + return@handleDevCall result; + } + } + + override fun nextPage() { + return StateDeveloper.instance.handleDevCall(_devId, "${_contextName}.nextPage") { + _pager.nextPage(); + } + } + + override fun getResults(): List { + return StateDeveloper.instance.handleDevCall(_devId, "${_contextName}.getResults", true) { + _pager.getResults(); + } + } + } + class DevPlatformCommentPager( + private val _devId: String, + private val _contextName: String, + private val _pager: IPager) : IPager { + + override fun hasMorePages(): Boolean { + return StateDeveloper.instance.handleDevCall(_devId, "${_contextName}.hasMorePages") { + _pager.hasMorePages(); + } + } + + override fun nextPage() { + return StateDeveloper.instance.handleDevCall(_devId, "${_contextName}.nextPage") { + _pager.nextPage(); + } + } + + override fun getResults(): List { + return StateDeveloper.instance.handleDevCall(_devId, "${_contextName}.getResults") { + _pager.getResults(); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt new file mode 100644 index 00000000..926ff063 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt @@ -0,0 +1,593 @@ +package com.futo.platformplayer.api.media.platforms.js + +import android.content.Context +import com.caoccao.javet.values.V8Value +import com.caoccao.javet.values.primitive.V8ValueBoolean +import com.caoccao.javet.values.primitive.V8ValueInteger +import com.caoccao.javet.values.primitive.V8ValueString +import com.caoccao.javet.values.reference.V8ValueArray +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StatePlugins +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.PlatformClientCapabilities +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.ResultCapabilities +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails +import com.futo.platformplayer.api.media.models.live.ILiveChatWindowDescriptor +import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent +import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker +import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails +import com.futo.platformplayer.api.media.platforms.js.internal.* +import com.futo.platformplayer.api.media.platforms.js.models.* +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.engine.V8Plugin +import com.futo.platformplayer.engine.exceptions.ScriptImplementationException +import com.futo.platformplayer.engine.exceptions.ScriptValidationException +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.ImageVariable +import com.futo.platformplayer.states.AnnouncementType +import com.futo.platformplayer.states.StateAnnouncement +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.time.OffsetDateTime +import kotlin.reflect.full.findAnnotations +import kotlin.reflect.jvm.kotlinFunction + +open class JSClient : IPlatformClient { + val config: SourcePluginConfig; + protected val _context: Context; + private val _plugin: V8Plugin; + private val plugin: V8Plugin get() = _plugin ?: throw IllegalStateException("Client not enabled"); + + var descriptor: SourcePluginDescriptor + private set; + + private val _client: JSHttpClient; + private val _clientAuth: JSHttpClient?; + private var _searchCapabilities: ResultCapabilities? = null; + private var _searchChannelContentsCapabilities: ResultCapabilities? = null; + private var _channelCapabilities: ResultCapabilities? = null; + + protected val _script: String; + + private var _initialized: Boolean = false; + private var _enabled: Boolean = false; + + private val _auth: SourceAuth?; + + private val _injectedSaveState: String?; + + override val id: String get() = config.id; + override val name: String get() = config.name; + override val icon: ImageVariable; + override var capabilities: PlatformClientCapabilities = PlatformClientCapabilities(); + + private val _busyLock = Object(); + private var _busyCounter = 0; + val isBusy: Boolean get() = _busyCounter > 0; + + val settings: HashMap get() = descriptor.settings; + + val flags: Array; + + var channelClaimTemplates: Map>? = null + private set; + + val isLoggedIn: Boolean get() = _auth != null; + val isEnabled: Boolean get() = _enabled; + + val enableInSearch get() = descriptor.appSettings.tabEnabled.enableSearch ?: true + val enableInHome get() = descriptor.appSettings.tabEnabled.enableHome ?: true + + val onDisabled = Event1(); + + constructor(context: Context, descriptor: SourcePluginDescriptor, saveState: String? = null) { + this._context = context; + this.config = descriptor.config; + icon = StatePlatform.instance.getPlatformIcon(config.id) ?: ImageVariable(config.absoluteIconUrl, null, null); + this.descriptor = descriptor; + _injectedSaveState = saveState; + _auth = descriptor.getAuth(); + flags = descriptor.flags.toTypedArray(); + + _client = JSHttpClient(this); + _clientAuth = JSHttpClient(this, _auth); + _plugin = V8Plugin(context, descriptor.config, null, _client, _clientAuth); + _plugin.withDependency(context, "scripts/polyfil.js"); + _plugin.withDependency(context, "scripts/source.js"); + + val script = StatePlugins.instance.getScript(descriptor.config.id); + if(script != null) { + _script = script; + _plugin.withScript(script); + } + else + throw IllegalStateException("Script for plugin [${descriptor.config.name}] was not available"); + } + constructor(context: Context, descriptor: SourcePluginDescriptor, saveState: String?, script: String) { + this._context = context; + this.config = descriptor.config; + icon = StatePlatform.instance.getPlatformIcon(config.id) ?: ImageVariable(config.absoluteIconUrl, null, null); + this.descriptor = descriptor; + _injectedSaveState = saveState; + _auth = descriptor.getAuth(); + flags = descriptor.flags.toTypedArray(); + + _client = JSHttpClient(this); + _clientAuth = JSHttpClient(this, _auth); + _plugin = V8Plugin(context, descriptor.config, script, _client, _clientAuth); + _plugin.withDependency(context, "scripts/polyfil.js"); + _plugin.withDependency(context, "scripts/source.js"); + _plugin.withScript(script); + _script = script; + } + + open fun getCopy(): JSClient { + return JSClient(_context, descriptor, saveState(), _script); + } + + fun getUnderlyingPlugin(): V8Plugin { + return _plugin; + } + + override fun initialize() { + Logger.i(TAG, "Plugin [${config.name}] initializing"); + plugin.start(); + plugin.execute("plugin.config = ${Json.encodeToString(config)}"); + plugin.execute("plugin.settings = parseSettings(${Json.encodeToString(descriptor.getSettingsWithDefaults())})"); + + descriptor.appSettings.loadDefaults(descriptor.config); + + _initialized = true; + + capabilities = PlatformClientCapabilities( + hasChannelSearch = plugin.executeBoolean("!!source.searchChannels") ?: false, + hasGetUserSubscriptions = plugin.executeBoolean("!!source.getUserSubscriptions") ?: false, + hasGetComments = plugin.executeBoolean("!!source.getComments") ?: false, + hasSearchPlaylists = (plugin.executeBoolean("!!source.searchPlaylists") ?: false), + hasGetPlaylist = (plugin.executeBoolean("!!source.getPlaylist") ?: false) && (plugin.executeBoolean("!!source.isPlaylistUrl") ?: false), + hasGetUserPlaylists = plugin.executeBoolean("!!source.getUserPlaylists") ?: false, + hasSearchChannelContents = plugin.executeBoolean("!!source.searchChannelContents") ?: false, + hasSaveState = plugin.executeBoolean("!!source.saveState") ?: false, + hasGetPlaybackTracker = plugin.executeBoolean("!!source.getPlaybackTracker") ?: false, + hasGetChannelUrlByClaim = plugin.executeBoolean("!!source.getChannelUrlByClaim") ?: false, + hasGetChannelTemplateByClaimMap = plugin.executeBoolean("!!source.getChannelTemplateByClaimMap") ?: false, + hasGetSearchCapabilities = plugin.executeBoolean("!!source.getSearchCapabilities") ?: false, + hasGetChannelCapabilities = plugin.executeBoolean("!!source.getChannelCapabilities") ?: false, + hasGetLiveEvents = plugin.executeBoolean("!!source.getLiveEvents") ?: false, + hasGetLiveChatWindow = plugin.executeBoolean("!!source.getLiveChatWindow") ?: false, + ); + + try { + if (capabilities.hasGetChannelTemplateByClaimMap) + getChannelTemplateByClaimMap(); + } + catch(ex: Throwable) { } + } + fun ensureEnabled() { + if(!_enabled) + enable(); + } + + @JSDocs(0, "source.enable()", "Called when the plugin is enabled/started") + fun enable() { + if(!_initialized) + initialize(); + plugin.execute("source.enable(${Json.encodeToString(config)}, parseSettings(${Json.encodeToString(descriptor.getSettingsWithDefaults())}), ${Json.encodeToString(_injectedSaveState)})"); + _enabled = true; + } + @JSDocs(1, "source.saveState()", "Provide a string that is passed to enable for quicker startup of multiple instances") + fun saveState(): String? { + ensureEnabled(); + if(!capabilities.hasSaveState) + return null; + val resp = plugin.executeTyped("source.saveState()").value; + return resp; + } + + @JSDocs(1, "source.disable()", "Called before the plugin is disabled/stopped") + override fun disable() { + Logger.i(TAG, "Disabling plugin [${name}] (Enabled: ${_enabled}, Initialized: ${_initialized})"); + if(_enabled) + ;//TODO: Disable? + _enabled = false; + if(_initialized) + _plugin.stop(); + _initialized = false; + + onDisabled.emit(this); + } + + @JSDocs(2, "source.getHome()", "Gets the HomeFeed of the platform") + override fun getHome(): IPager = isBusyWith { + ensureEnabled(); + return@isBusyWith JSContentPager(config, plugin, + plugin.executeTyped("source.getHome()")); + } + + @JSDocs(3, "source.searchSuggestions(query)", "Gets search suggestions for a given query") + @JSDocsParameter("query", "Query to complete suggestions for") + override fun searchSuggestions(query: String): Array = isBusyWith { + ensureEnabled(); + return@isBusyWith plugin.executeTyped("source.searchSuggestions(${Json.encodeToString(query)})") + .toArray() + .map { (it as V8ValueString).value } + .toTypedArray(); + } + @JSDocs(4, "source.getSearchCapabilities()", "Gets capabilities this plugin has for search contents") + override fun getSearchCapabilities(): ResultCapabilities { + if(!capabilities.hasGetSearchCapabilities) + return ResultCapabilities(listOf(ResultCapabilities.TYPE_MIXED)); + try { + if (_searchCapabilities != null) { + return _searchCapabilities!!; + } + + _searchCapabilities = ResultCapabilities.fromV8(config, plugin.executeTyped("source.getSearchCapabilities()")); + return _searchCapabilities!!; + } + catch(ex: Throwable) { + announcePluginUnhandledException("getSearchCapabilities", ex); + return ResultCapabilities(listOf(ResultCapabilities.TYPE_MIXED)); + } + } + @JSDocs(5, "source.search(query)", "Searches for contents on the platform") + @JSDocsParameter("query", "Query that search results should match") + @JSDocsParameter("type", "(optional) Type of contents to get from search ") + @JSDocsParameter("order", "(optional) Order in which contents should be returned") + @JSDocsParameter("filters", "(optional) Filters to apply on contents") + @JSDocsParameter("channelId", "(optional) Channel id to search in") + override fun search(query: String, type: String?, order: String?, filters: Map>?): IPager = isBusyWith { + ensureEnabled(); + return@isBusyWith JSContentPager(config, plugin, + plugin.executeTyped("source.search(${Json.encodeToString(query)}, ${Json.encodeToString(type)}, ${Json.encodeToString(order)}, ${Json.encodeToString(filters)})")); + } + + @JSDocs(4, "source.getSearchChannelContentsCapabilities()", "Gets capabilities this plugin has for search videos") + override fun getSearchChannelContentsCapabilities(): ResultCapabilities { + ensureEnabled(); + if (_searchChannelContentsCapabilities != null) + return _searchChannelContentsCapabilities!!; + + _searchChannelContentsCapabilities = ResultCapabilities.fromV8(config, plugin.executeTyped("source.getSearchChannelContentsCapabilities()")); + return _searchChannelContentsCapabilities!!; + } + @JSDocs(5, "source.searchChannelContents(query)", "Searches for videos on the platform") + @JSDocsParameter("channelUrl", "Channel url to search") + @JSDocsParameter("query", "Query that search results should match") + @JSDocsParameter("type", "(optional) Type of contents to get from search ") + @JSDocsParameter("order", "(optional) Order in which contents should be returned") + @JSDocsParameter("filters", "(optional) Filters to apply on contents") + override fun searchChannelContents(channelUrl: String, query: String, type: String?, order: String?, filters: Map>?): IPager = isBusyWith { + ensureEnabled(); + if(!capabilities.hasSearchChannelContents) + throw IllegalStateException("This plugin does not support channel search"); + + return@isBusyWith JSContentPager(config, plugin, + plugin.executeTyped("source.searchChannelContents(${Json.encodeToString(channelUrl)}, ${Json.encodeToString(query)}, ${Json.encodeToString(type)}, ${Json.encodeToString(order)}, ${Json.encodeToString(filters)})")); + } + + @JSOptional + @JSDocs(5, "source.searchChannels(query)", "Searches for channels on the platform") + @JSDocsParameter("query", "Query that channels should match") + override fun searchChannels(query: String): IPager = isBusyWith { + ensureEnabled(); + return@isBusyWith JSChannelPager(config, plugin, + plugin.executeTyped("source.searchChannels(${Json.encodeToString(query)})")); + } + + @JSDocs(6, "source.isChannelUrl(url)", "Validates if an channel url is for this platform") + @JSDocsParameter("url", "A channel url (May not be your platform)") + override fun isChannelUrl(url: String): Boolean { + try { + return plugin.executeTyped("source.isChannelUrl(${Json.encodeToString(url)})") + .value; + } + catch(ex: Throwable) { + announcePluginUnhandledException("isChannelUrl", ex); + return false; + } + } + @JSDocs(7, "source.getChannel(channelUrl)", "Gets a channel by its url") + @JSDocsParameter("channelUrl", "A channel url (this platform)") + override fun getChannel(channelUrl: String): IPlatformChannel = isBusyWith { + ensureEnabled(); + return@isBusyWith JSChannel(config, + plugin.executeTyped("source.getChannel(${Json.encodeToString(channelUrl)})")); + } + @JSDocs(8, "source.getChannelCapabilities()", "Gets capabilities this plugin has for channel contents") + override fun getChannelCapabilities(): ResultCapabilities { + if(!capabilities.hasGetChannelCapabilities) + return ResultCapabilities(listOf(ResultCapabilities.TYPE_MIXED)); + try { + if (_channelCapabilities != null) { + return _channelCapabilities!!; + } + + _channelCapabilities = ResultCapabilities.fromV8(config, plugin.executeTyped("source.getChannelCapabilities()")); + return _channelCapabilities!!; + } + catch(ex: Throwable) { + announcePluginUnhandledException("getChannelCapabilities", ex); + return ResultCapabilities(listOf(ResultCapabilities.TYPE_MIXED)); + } + } + @JSDocs(10, "source.getChannelContents(url, type, order, filters)", "Gets contents of a channel (reverse chronological order)") + @JSDocsParameter("channelUrl", "A channel url (this platform)") + @JSDocsParameter("type", "(optional) Type of contents to get from channel") + @JSDocsParameter("order", "(optional) Order in which contents should be returned") + @JSDocsParameter("filters", "(optional) Filters to apply on contents") + override fun getChannelContents(channelUrl: String, type: String?, order: String?, filters: Map>?): IPager = isBusyWith { + ensureEnabled(); + return@isBusyWith JSContentPager(config, plugin, + plugin.executeTyped("source.getChannelContents(${Json.encodeToString(channelUrl)}, ${Json.encodeToString(type)}, ${Json.encodeToString(order)}, ${Json.encodeToString(filters)})")); + } + + @JSOptional + @JSDocs(11, "source.getChannelUrlByClaim(claimType, claimValues)", "Gets the channel url that should be used to fetch a given polycentric claim") + @JSDocsParameter("claimType", "Polycentric claimtype id") + @JSDocsParameter("claimValues", "A map of values associated with the claim") + override fun getChannelUrlByClaim(claimType: Int, claimValues: Map): String? { + if(!capabilities.hasGetChannelUrlByClaim) + throw IllegalStateException("This plugin does not support channel url by claim"); + + val value = plugin.executeTyped("source.getChannelUrlByClaim(${claimType}, ${Json.encodeToString(claimValues)})"); + if(value !is V8ValueString) + return null; + return value.value; + } + @JSOptional + @JSDocs(12, "source.getChannelTemplateByClaimMap()", "Get a map for every supported claimtype mapping field to urls") + @JSDocsParameter("claimType", "Polycentric claimtype id") + @JSDocsParameter("claimValues", "A map of values associated with the claim") + fun getChannelTemplateByClaimMap(): Map>{ + if(!capabilities.hasGetChannelTemplateByClaimMap) + throw IllegalStateException("This plugin does not support channel template by claim map"); + + val value = plugin.executeTyped("source.getChannelTemplateByClaimMap()"); + if(value !is V8ValueObject) + return mapOf(); + + val claimTypes = mutableMapOf>(); + + val keys = value.ownPropertyNames; + for(key in keys.toArray()) { + if(key is V8ValueInteger) { + val map = value.get(key); + val mapKeys = map.ownPropertyNames; + + claimTypes[key.value] = mapKeys.toArray().filter { + it is V8ValueInteger + }.associate { + val mapKey = (it as V8ValueInteger).value; + return@associate Pair(mapKey, map.getString(mapKey)); + }; + } + } + channelClaimTemplates = claimTypes.toMap(); + return claimTypes; + } + + + @JSDocs(13, "source.isContentDetailsUrl(url)", "Validates if an content url is for this platform") + @JSDocsParameter("url", "A content url (May not be your platform)") + override fun isContentDetailsUrl(url: String): Boolean { + try { + return plugin.executeTyped("source.isContentDetailsUrl(${Json.encodeToString(url)})") + .value; + } + catch(ex: Throwable) { + announcePluginUnhandledException("isContentDetailsUrl", ex); + return false; + } + } + @JSDocs(14, "source.getContentDetails(url)", "Gets content details by its url") + @JSDocsParameter("url", "A content url (this platform)") + override fun getContentDetails(url: String): IPlatformContentDetails = isBusyWith { + ensureEnabled(); + return@isBusyWith IJSContentDetails.fromV8(config, + plugin.executeTyped("source.getContentDetails(${Json.encodeToString(url)})")); + } + + @JSOptional + @JSDocs(15, "source.getPlaybackTracker(url)", "Gets a playback tracker for given content url") + @JSDocsParameter("url", "A content url (this platform)") + override fun getPlaybackTracker(url: String): IPlaybackTracker? = isBusyWith { + if(!capabilities.hasGetPlaybackTracker) + return@isBusyWith null; + ensureEnabled(); + Logger.i(TAG, "JSClient.getPlaybackTracker(${url})"); + val tracker = plugin.executeTyped("source.getPlaybackTracker(${Json.encodeToString(url)})"); + if(tracker is V8ValueObject) + return@isBusyWith JSPlaybackTracker(config, tracker); + else + return@isBusyWith null; + } + + @JSDocs(16, "source.getComments(url)", "Gets comments for a content by its url") + @JSDocsParameter("url", "A content url (this platform)") + override fun getComments(url: String): IPager = isBusyWith { + ensureEnabled(); + return@isBusyWith JSCommentPager(config, plugin, + plugin.executeTyped("source.getComments(${Json.encodeToString(url)})")); + } + @JSDocs(17, "source.getSubComments(comment)", "Gets replies for a given comment") + @JSDocsParameter("comment", "Comment object that was returned by getComments") + override fun getSubComments(comment: IPlatformComment): IPager { + ensureEnabled(); + return comment.getReplies(this) ?: JSCommentPager(config, plugin, + plugin.executeTyped("source.getSubComments(${Json.encodeToString(comment as JSComment)})")); + } + + @JSDocs(16, "source.getLiveChatWindow(url)", "Gets live events for a livestream") + @JSDocsParameter("url", "Url of live stream") + override fun getLiveChatWindow(url: String): ILiveChatWindowDescriptor? = isBusyWith { + if(!capabilities.hasGetLiveChatWindow) + return@isBusyWith null; + ensureEnabled(); + return@isBusyWith JSLiveChatWindowDescriptor(config, + plugin.executeTyped("source.getLiveChatWindow(${Json.encodeToString(url)})")); + } + @JSDocs(16, "source.getLiveEvents(url)", "Gets live events for a livestream") + @JSDocsParameter("url", "Url of live stream") + override fun getLiveEvents(url: String): IPager? = isBusyWith { + if(!capabilities.hasGetLiveEvents) + return@isBusyWith null; + ensureEnabled(); + return@isBusyWith JSLiveEventPager(config, plugin, + plugin.executeTyped("source.getLiveEvents(${Json.encodeToString(url)})")); + } + @JSDocs(19, "source.searchPlaylists(query)", "Searches for playlists on the platform") + @JSDocsParameter("query", "Query that search results should match") + @JSDocsParameter("type", "(optional) Type of contents to get from search ") + @JSDocsParameter("order", "(optional) Order in which contents should be returned") + @JSDocsParameter("filters", "(optional) Filters to apply on contents") + @JSDocsParameter("channelId", "(optional) Channel id to search in") + override fun searchPlaylists(query: String, type: String?, order: String?, filters: Map>?): IPager = isBusyWith { + ensureEnabled(); + if(!capabilities.hasSearchPlaylists) + throw IllegalStateException("This plugin does not support playlist search"); + return@isBusyWith JSContentPager(config, plugin, plugin.executeTyped("source.searchPlaylists(${Json.encodeToString(query)}, ${Json.encodeToString(type)}, ${Json.encodeToString(order)}, ${Json.encodeToString(filters)})")); + } + @JSOptional + @JSDocs(20, "source.isPlaylistUrl(url)", "Validates if a playlist url is for this platform") + @JSDocsParameter("url", "Url of playlist") + override fun isPlaylistUrl(url: String): Boolean { + ensureEnabled(); + if (!capabilities.hasGetPlaylist) + return false; + return plugin.executeBoolean("source.isPlaylistUrl(${Json.encodeToString(url)})") ?: false; + } + @JSOptional + @JSDocs(21, "source.getPlaylist(url)", "Gets the playlist of the current user") + @JSDocsParameter("url", "Url of playlist") + override fun getPlaylist(url: String): IPlatformPlaylistDetails = isBusyWith { + ensureEnabled(); + return@isBusyWith JSPlaylistDetails(plugin, plugin.config as SourcePluginConfig, plugin.executeTyped("source.getPlaylist(${Json.encodeToString(url)})")); + } + + @JSOptional + @JSDocs(22, "source.getUserPlaylists()", "Gets the playlist of the current user") + override fun getUserPlaylists(): Array { + ensureEnabled(); + return plugin.executeTyped("source.getUserPlaylists()") + .toArray() + .map { (it as V8ValueString).value } + .toTypedArray(); + } + + @JSOptional + @JSDocs(23, "source.getUserSubscriptions()", "Gets the subscriptions of the current user") + override fun getUserSubscriptions(): Array { + ensureEnabled(); + return plugin.executeTyped("source.getUserSubscriptions()") + .toArray() + .map { (it as V8ValueString).value } + .toTypedArray(); + } + + fun validate() { + try { + plugin.start(); + + validateFunction("source.getHome"); + //validateFunction("source.getSearchCapabilities"); + validateFunction("source.search"); + validateFunction("source.isChannelUrl"); + validateFunction("source.getChannel"); + //validateFunction("source.getChannelCapabilities"); + validateFunction("source.getChannelContents"); + validateFunction("source.isContentDetailsUrl"); + validateFunction("source.getContentDetails"); + } + finally { + plugin.stop() + } + } + private fun validateFunction(funcName: String) { + if(plugin.executeBoolean("typeof ${funcName} == 'function'") != true) + throw ScriptValidationException("Validation\n[function ${funcName} not available]"); + } + + + fun validateUrlOrThrow(url: String) { + val allowed = config.isUrlAllowed(url); + if(!allowed) + throw ScriptImplementationException(config, "Attempted to access non-whitelisted url: ${url}"); + } + + override fun isClaimTypeSupported(claimType: Int): Boolean { + return capabilities.hasGetChannelTemplateByClaimMap && config.supportedClaimTypes.contains(claimType) + } + fun isClaimTemplateMapSupported(claimType: Int, map: Map): Boolean { + return capabilities.hasGetChannelTemplateByClaimMap && channelClaimTemplates?.let { + it.containsKey(claimType) && it[claimType]!!.any { map.containsKey(it.key) }; + } ?: false; + } + + fun resolveChannelUrlByClaimTemplates(claimType: Int, values: Map): String? { + return channelClaimTemplates?.let { + if(it.containsKey(claimType)) { + val templates = it[claimType]; + if(templates != null) + for(value in values.keys.sortedBy { it }) { + if(templates.containsKey(value)) { + return templates[value]!!.replace("{{CLAIMVALUE}}", values[value]!!); + } + } + } + return null; + }; + } + + + private fun isBusyWith(handle: ()->T): T { + try { + synchronized(_busyLock) { + _busyCounter++; + } + return handle(); + } + finally { + synchronized(_busyLock) { + _busyCounter--; + } + } + } + + private fun announcePluginUnhandledException(method: String, ex: Throwable) { + try { + StateAnnouncement.instance.registerAnnouncement("PluginUnhandled_${config.id}_${method}", + "Plugin ${config.name} encountered an error in [${method}]", + "${ex.message}\nPlease contact the plugin developer", + AnnouncementType.RECURRING, + OffsetDateTime.now()); + } + catch(_: Throwable) {} + } + + companion object { + val TAG = "JSClient"; + + fun getJSDocs(): List { + val docs = mutableListOf(); + val methods = JSClient::class.java.declaredMethods.filter { it.getAnnotation(JSDocs::class.java) != null } + for(method in methods.sortedBy { it.getAnnotation(JSDocs::class.java)?.order }) { + val doc = method.getAnnotation(JSDocs::class.java); + val parameters = method.kotlinFunction!!.findAnnotations(); + val isOptional = method.kotlinFunction!!.findAnnotations().isNotEmpty(); + + docs.add(JSCallDocs(method.name, doc.code, doc.description, parameters + .sortedBy { it.order } + .map{ JSParameterDocs(it.name, it.description) } + .toList(), isOptional)); + } + return docs; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourceAuth.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourceAuth.kt new file mode 100644 index 00000000..0bf0d96d --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourceAuth.kt @@ -0,0 +1,50 @@ +package com.futo.platformplayer.api.media.platforms.js + +import com.futo.platformplayer.encryption.EncryptionProvider +import com.futo.platformplayer.logging.Logger +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + + +data class SourceAuth(val cookieMap: HashMap>? = null, val headers: Map> = mapOf()) { + override fun toString(): String { + return "(headers: '$headers', cookieString: '$cookieMap')"; + } + + fun toEncrypted(): String{ + return EncryptionProvider.instance.encrypt(serialize()); + } + + private fun serialize(): String { + return Json.encodeToString(SerializedAuth(cookieMap, headers)); + } + + companion object { + val TAG = "SourceAuth"; + + fun fromEncrypted(encrypted: String?): SourceAuth? { + if(encrypted == null) + return null; + + val decrypted = EncryptionProvider.instance.decrypt(encrypted); + try { + return deserialize(decrypted); + } + catch(ex: Throwable) { + Logger.e(TAG, "Failed to deserialize authentication", ex); + return null; + } + } + + fun deserialize(str: String): SourceAuth { + val data = Json.decodeFromString(str); + return SourceAuth(data.cookieMap, data.headers); + } + } + + @Serializable + data class SerializedAuth(val cookieMap: HashMap>?, + val headers: Map> = mapOf()) +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginAuthConfig.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginAuthConfig.kt new file mode 100644 index 00000000..69abaa0b --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginAuthConfig.kt @@ -0,0 +1,14 @@ +package com.futo.platformplayer.api.media.platforms.js + +@kotlinx.serialization.Serializable +class SourcePluginAuthConfig( + val loginUrl: String, + val completionUrl: String? = null, + val allowedDomains: List? = null, + val headersToFind: List? = null, + val cookiesToFind: List? = null, + val cookiesExclOthers: Boolean = true, + val userAgent: String? = null, + val loginButton: String? = null, + val domainHeadersToFind: Map>? = null, +) { } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt new file mode 100644 index 00000000..1d9bd749 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt @@ -0,0 +1,138 @@ +package com.futo.platformplayer.api.media.platforms.js + +import android.net.Uri +import com.futo.platformplayer.SignatureProvider +import com.futo.platformplayer.api.media.Serializer +import com.futo.platformplayer.engine.IV8PluginConfig +import kotlinx.serialization.decodeFromString +import java.net.URL +import java.util.* + +@kotlinx.serialization.Serializable +class SourcePluginConfig( + override val name: String, + val description: String = "", + + //Author + val author: String = "", + val authorUrl: String = "", + + //Script + val repositoryUrl: String? = null, + val scriptUrl: String = "", + val version: Int = -1, + + val iconUrl: String? = null, + var id: String = UUID.randomUUID().toString(), + + val scriptSignature: String? = null, + val scriptPublicKey: String? = null, + + override val allowEval: Boolean = false, + override val allowUrls: List = listOf(), + override val packages: List = listOf(), + + val settings: List = listOf(), + + val authentication: SourcePluginAuthConfig? = null, + var sourceUrl: String? = null, + val constants: HashMap = hashMapOf(), + + //TODO: These should be vals...but prob for serialization reasons cannot be changed. + var enableInSearch: Boolean = true, + var enableInHome: Boolean = true, + var supportedClaimTypes: List = listOf() +) : IV8PluginConfig { + + val absoluteIconUrl: String? get() = resolveAbsoluteUrl(iconUrl, sourceUrl); + val absoluteScriptUrl: String get() = resolveAbsoluteUrl(scriptUrl, sourceUrl)!!; + + private fun resolveAbsoluteUrl(url: String?, sourceUrl: String?): String? { + if(url == null) + return null; + val uri = Uri.parse(url); + if(uri.isAbsolute) + return url; + else if(sourceUrl.isNullOrEmpty()) + return url; + // throw IllegalStateException("Attempted to get absolute script url from relative, without base url"); + else { + val sourceUrlParsed = URL(sourceUrl); + return URL(sourceUrlParsed, uri.path).toString(); + } + } + + private var _allowAnywhereVal: Boolean? = null; + private val _allowAnywhere: Boolean get() { + if(_allowAnywhereVal == null) + _allowAnywhereVal = allowUrls.any { it.lowercase() == "everywhere" }; + return _allowAnywhereVal!!; + }; + private var _allowUrlsLowerVal: List? = null; + private val _allowUrlsLower: List get() { + if(_allowUrlsLowerVal == null) + _allowUrlsLowerVal = allowUrls.map { it.lowercase() }; + return _allowUrlsLowerVal!!; + }; + + fun getWarnings(scriptToCheck: String? = null) : List> { + val list = mutableListOf>(); + + if(scriptPublicKey.isNullOrEmpty() || scriptSignature.isNullOrEmpty()) + list.add(Pair( + "Missing Signature", + "This plugin does not have a signature. This makes updating the plugin less safe as it makes it easier for a malicious actor besides the developer to update a malicious version.")); + else if(scriptToCheck != null && !this.validate(scriptToCheck)) + list.add(Pair( + "Invalid Signature", + "This plugin does not have a signature. This makes updating the plugin less safe as it makes it easier for a malicious actor besides the developer to update a malicious version.")); + if(allowEval) + list.add(Pair( + "Eval Access", + "Eval allows injection of unsure code, and should be avoided when possible.")); + if(allowUrls.any { it == "everywhere" }) + list.add(Pair( + "Unrestricted Web Access", + "This plugin requires access to all URLs, this may include malicious URLs.")); + + return list; + } + + fun validate(text: String): Boolean { + if(scriptPublicKey.isNullOrEmpty()) + throw IllegalStateException("No public key present"); + if(scriptSignature.isNullOrEmpty()) + throw IllegalStateException("No signature present"); + + return SignatureProvider.verify(text, scriptSignature, scriptPublicKey); + } + + fun isUrlAllowed(url: String): Boolean { + if(_allowAnywhere) + return true; + val uri = Uri.parse(url); + val host = uri.host?.lowercase() ?: ""; + return _allowUrlsLower.any { it == host }; + } + + companion object { + fun fromJson(json: String, sourceUrl: String? = null): SourcePluginConfig { + val obj = Serializer.json.decodeFromString(json); + if(obj.sourceUrl == null) + obj.sourceUrl = sourceUrl; + return obj; + } + } + + @kotlinx.serialization.Serializable + data class Setting( + val name: String, + val description: String, + val type: String, + val default: String? = null, + val variable: String? = null + ) { + @kotlinx.serialization.Transient + val variableOrName: String get() = variable ?: name; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt new file mode 100644 index 00000000..172ebb23 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt @@ -0,0 +1,81 @@ +package com.futo.platformplayer.api.media.platforms.js + +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.serializers.FlexibleBooleanSerializer +import com.futo.platformplayer.views.fields.FieldForm +import com.futo.platformplayer.views.fields.FormField +import kotlinx.serialization.Serializable + +@Serializable +class SourcePluginDescriptor { + val config: SourcePluginConfig; + var settings: HashMap = hashMapOf(); + + var appSettings: AppPluginSettings = AppPluginSettings(); + + var authEncrypted: String? + private set; + + val flags: List; + + @kotlinx.serialization.Transient + val onAuthChanged = Event0(); + + constructor(config :SourcePluginConfig, authEncrypted: String? = null) { + this.config = config; + this.authEncrypted = authEncrypted; + this.flags = listOf(); + } + constructor(config :SourcePluginConfig, authEncrypted: String? = null, flags: List) { + this.config = config; + this.authEncrypted = authEncrypted; + this.flags = flags; + } + + fun getSettingsWithDefaults(): HashMap { + val map = HashMap(settings); + for(field in config.settings) { + if(!map.containsKey(field.variableOrName) || map[field.variableOrName] == null) + map.put(field.variableOrName, field.default); + } + return map; + } + + + fun updateAuth(str: SourceAuth?) { + authEncrypted = str?.toEncrypted(); + onAuthChanged.emit(); + } + fun getAuth(): SourceAuth? { + return SourceAuth.fromEncrypted(authEncrypted); + } + + @Serializable + class AppPluginSettings { + + @FormField("Visibility", "group", "Enable where this plugin's content are visible.", 2) + var tabEnabled = TabEnabled(); + @Serializable + class TabEnabled { + @FormField("Home", FieldForm.TOGGLE, "Show content in home tab", 1) + var enableHome: Boolean? = null; + + + @FormField("Search", FieldForm.TOGGLE, "Show content in search results", 2) + var enableSearch: Boolean? = null; + } + + + + fun loadDefaults(config: SourcePluginConfig) { + if(tabEnabled.enableHome == null) + tabEnabled.enableHome = config.enableInHome ?: true; + if(tabEnabled.enableSearch == null) + tabEnabled.enableSearch = config.enableInSearch ?: true; + } + } + + companion object { + const val FLAG_EMBEDDED = "EMBEDDED"; + } +} diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSDocs.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSDocs.kt new file mode 100644 index 00000000..4c1dc22d --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSDocs.kt @@ -0,0 +1,19 @@ +package com.futo.platformplayer.api.media.platforms.js.internal + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class JSDocs(val order: Int, val code: String, val description: String) + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class JSOptional() + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +@Repeatable +annotation class JSDocsParameter(val name: String, val description: String, val order: Int = 0) + +@kotlinx.serialization.Serializable +data class JSCallDocs(val title: String, val code: String, val description: String, val parameters: List, val isOptional: Boolean = false); +@kotlinx.serialization.Serializable +data class JSParameterDocs(val name: String, val description: String); \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt new file mode 100644 index 00000000..6f1e3b1b --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt @@ -0,0 +1,158 @@ +package com.futo.platformplayer.api.media.platforms.js.internal + +import android.net.Uri +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.api.media.platforms.js.JSClient +import com.futo.platformplayer.api.media.platforms.js.SourceAuth +import com.futo.platformplayer.matchesDomain + +class JSHttpClient : ManagedHttpClient { + private val _jsClient: JSClient?; + private val _auth: SourceAuth?; + + var doUpdateCookies: Boolean = true; + var doApplyCookies: Boolean = true; + var doAllowNewCookies: Boolean = true; + val isLoggedIn: Boolean get() = _auth != null; + + private var _currentCookieMap: HashMap>?; + + constructor(jsClient: JSClient?, auth: SourceAuth? = null) : super() { + _jsClient = jsClient; + _auth = auth; + + if(!auth?.cookieMap.isNullOrEmpty()) { + _currentCookieMap = hashMapOf(); + for(domainCookies in auth!!.cookieMap!!) + _currentCookieMap!!.put(domainCookies.key, HashMap(domainCookies.value)); + } + else _currentCookieMap = null; + } + + override fun clone(): ManagedHttpClient { + val newClient = JSHttpClient(_jsClient, _auth); + newClient._currentCookieMap = if(_currentCookieMap != null) + HashMap(_currentCookieMap!!.toList().associate { Pair(it.first, HashMap(it.second)) }) + else + null; + return newClient; + } + + override fun beforeRequest(request: Request) { + val auth = _auth; + if (auth != null) { + val domain = Uri.parse(request.url).host!!.lowercase(); + + //TODO: Possibly add doApplyHeaders + for (header in auth.headers.filter { domain.matchesDomain(it.key) }.flatMap { it.value.entries }) + request.headers[header.key] = header.value; + + if(doApplyCookies) { + if (!_currentCookieMap.isNullOrEmpty()) { + val cookiesToApply = hashMapOf(); + synchronized(_currentCookieMap!!) { + for(cookie in _currentCookieMap!! + .filter { domain.matchesDomain(it.key) } + .flatMap { it.value.toList() }) + cookiesToApply[cookie.first] = cookie.second; + }; + + if(cookiesToApply.size > 0) { + val cookieString = cookiesToApply.map { it.key + "=" + it.value }.joinToString("; "); + request.headers["Cookie"] = cookieString; + } + //printTestCode(request.url, request.body, auth.headers, cookieString, request.headers.filter { !auth.headers.containsKey(it.key) }); + } + } + } + + _jsClient?.validateUrlOrThrow(request.url); + super.beforeRequest(request) + } + + override fun afterRequest(request: Request, resp: Response) { + super.afterRequest(request, resp) + + if(doUpdateCookies) { + val domain = Uri.parse(request.url).host!!.lowercase(); + val domainParts = domain!!.split("."); + val defaultCookieDomain = + "." + domainParts.drop(domainParts.size - 2).joinToString("."); + for (header in resp.headers) { + if (_currentCookieMap != null && header.key.lowercase() == "set-cookie") { + val newCookies = cookieStringToMap(header.value); + for (cookie in newCookies) { + val endIndex = cookie.value.indexOf(";"); + var cookieValue = cookie.value; + var domainToUse = domain; + + if (endIndex > 0) { + val cookieParts = cookie.value.split(";"); + if (cookieParts.size == 0) + continue; + cookieValue = cookieParts[0].trim(); + + val cookieVariables = cookieParts.drop(1).map { + val splitIndex = it.indexOf("="); + if (splitIndex < 0) + return@map Pair(it.trim().lowercase(), ""); + return@map Pair( + it.substring(0, splitIndex).lowercase().trim(), + it.substring(splitIndex + 1).trim() + ); + }.toMap(); + domainToUse = if (cookieVariables.containsKey("domain")) + cookieVariables["domain"]!!.lowercase(); + else defaultCookieDomain; + } + + val cookieMap = if (_currentCookieMap!!.containsKey(domainToUse)) + _currentCookieMap!![domainToUse]!!; + else { + val newMap = hashMapOf(); + _currentCookieMap!!.put(domainToUse, newMap) + newMap; + } + if(cookieMap.containsKey(cookie.key) || doAllowNewCookies) + cookieMap.put(cookie.key, cookieValue); + } + } + } + } + } + + + private fun cookieStringToMap(parts: List): Map { + val map = hashMapOf(); + for(cookie in parts) { + val cookieKey = cookie.substring(0, cookie.indexOf("=")); + val cookieVal = cookie.substring(cookie.indexOf("=") + 1); + map.put(cookieKey.trim(), cookieVal.trim()); + } + return map; + } + + //Prints out code for test reproduction.. + fun printTestCode(url: String, body: ByteArray?, headers: Map, cookieString: String, allHeaders: Map? = null) { + var code = "Code: \n"; + code += "\nurl = \"${url}\";"; + if(body != null) + code += "\nbody = \"${String(body).replace("\"", "\\\"")}\";"; + if(headers != null) + for(header in headers) { + code += "\nclient.Headers.Add(\"${header.key}\", \"${header.value}\");"; + } + if(cookieString != null) + code += "\nclient.Headers.Add(\"Cookie\", \"${cookieString}\");"; + + if(allHeaders != null) { + code += "\n//OTHER HEADERS:" + for (header in allHeaders) { + code += "\nclient.Headers.Add(\"${header.key}\", \"${header.value}\");"; + } + } + + Logger.i("Testing", code); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/IJSContent.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/IJSContent.kt new file mode 100644 index 00000000..2746b56c --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/IJSContent.kt @@ -0,0 +1,30 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.getOrDefault +import com.futo.platformplayer.getOrThrow + +interface IJSContent: IPlatformContent { + + companion object { + fun fromV8(config: SourcePluginConfig, obj: V8ValueObject): IPlatformContent { + val type: Int = obj.getOrThrow(config, "contentType", "ContentItem"); + val pluginType: String? = obj.getOrDefault(config, "plugin_type", "ContentItem", null); + + //TODO: Temporary workaround for intercepting details in lists + if(pluginType != null && pluginType.endsWith("Details")) + return IJSContentDetails.fromV8(config, obj); + + return when(ContentType.fromInt(type)) { + ContentType.MEDIA -> JSVideo(config, obj); + ContentType.POST -> JSPost(config, obj); + ContentType.NESTED_VIDEO -> JSNestedMediaContent(config, obj); + ContentType.PLAYLIST -> JSPlaylist(config, obj); + else -> throw NotImplementedError("Unknown content type ${type}"); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/IJSContentDetails.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/IJSContentDetails.kt new file mode 100644 index 00000000..fad34868 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/IJSContentDetails.kt @@ -0,0 +1,22 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.getOrThrow + +interface IJSContentDetails: IPlatformContent { + + companion object { + fun fromV8(config: SourcePluginConfig, obj: V8ValueObject): IPlatformContentDetails { + val type: Int = obj.getOrThrow(config, "contentType", "ContentDetails"); + return when(ContentType.fromInt(type)) { + ContentType.MEDIA -> JSVideoDetails(config, obj); + ContentType.POST -> JSPostDetails(config, obj); + else -> throw NotImplementedError("Unknown content type ${type}"); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSChannel.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSChannel.kt new file mode 100644 index 00000000..01436008 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSChannel.kt @@ -0,0 +1,46 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.getOrDefaultList +import com.futo.platformplayer.getOrThrow +import com.futo.platformplayer.getOrThrowNullable + +class JSChannel : IPlatformChannel { + private val _pluginConfig: SourcePluginConfig; + private val _channel : V8ValueObject; + + override val id: PlatformID; + override val name: String; + override val thumbnail: String?; + override val banner: String?; + override val subscribers: Long; + override val description: String?; + override val url: String; + override val links: Map; + override val urlAlternatives: List; + + constructor(config: SourcePluginConfig, obj: V8ValueObject) { + _pluginConfig = config; + _channel = obj; + val contextName = "PlatformChannel"; + id = PlatformID.fromV8(_pluginConfig, _channel.getOrThrow(config, "id", contextName)); + name = _channel.getOrThrow(config, "name", contextName); + thumbnail = _channel.getOrThrowNullable(config, "thumbnail", contextName); + banner = _channel.getOrThrowNullable(config, "banner", contextName); + subscribers = _channel.getOrThrow(config, "subscribers", contextName).toLong(); + description = _channel.getOrThrowNullable(config, "description", contextName); + url = _channel.getOrThrow(config, "url", contextName); + urlAlternatives = _channel.getOrDefaultList(config, "urlAlternatives", contextName, listOf()) ?: listOf(); + links = HashMap(); + } + + override fun getContents(client: IPlatformClient): IPager { + return client.getChannelContents(url); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSChannelPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSChannelPager.kt new file mode 100644 index 00000000..01253896 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSChannelPager.kt @@ -0,0 +1,16 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.engine.V8Plugin + +class JSChannelPager : JSPager, IPager { + + constructor(config: SourcePluginConfig, plugin: V8Plugin, pager: V8ValueObject) : super(config, plugin, pager) {} + + override fun convertResult(obj: V8ValueObject): PlatformAuthorLink { + return PlatformAuthorLink.fromV8(config, obj); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSComment.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSComment.kt new file mode 100644 index 00000000..04146582 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSComment.kt @@ -0,0 +1,65 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.models.ratings.IRating +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.engine.V8Plugin +import com.futo.platformplayer.getOrDefault +import com.futo.platformplayer.getOrThrow +import com.futo.platformplayer.getOrThrowNullable +import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer +import java.time.LocalDateTime +import java.time.OffsetDateTime +import java.time.ZoneOffset + +@kotlinx.serialization.Serializable +class JSComment : IPlatformComment { + @kotlinx.serialization.Transient + private var _hasGetReplies: Boolean = false; + + @kotlinx.serialization.Transient + private var _config: SourcePluginConfig? = null; + @kotlinx.serialization.Transient + private var _comment: V8ValueObject? = null; + @kotlinx.serialization.Transient + private var _plugin: V8Plugin? = null; + + override val contextUrl: String; + override val author: PlatformAuthorLink; + override val message: String; + override val rating: IRating; + @kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class) + override val date: OffsetDateTime?; + override val replyCount: Int?; + + val context: Map; + + + constructor(config: SourcePluginConfig, plugin: V8Plugin, obj: V8ValueObject) { + _config = config; + _comment = obj; + _plugin = plugin; + + val contextName = "Comment"; + contextUrl = _comment!!.getOrThrow(config, "contextUrl", contextName); + author = PlatformAuthorLink.fromV8(_config!!, _comment!!.getOrThrow(config, "author", contextName)); + message = _comment!!.getOrThrow(config, "message", contextName); + rating = IRating.fromV8(config, _comment!!.getOrThrow(config, "rating", contextName)); + date = _comment!!.getOrThrowNullable(config, "date", contextName)?.let { OffsetDateTime.of(LocalDateTime.ofEpochSecond(it.toLong(), 0, ZoneOffset.UTC), ZoneOffset.UTC) } + replyCount = _comment!!.getOrThrowNullable(config, "replyCount", contextName); + context = _comment!!.getOrDefault(config, "context", contextName, hashMapOf()) ?: hashMapOf(); + _hasGetReplies = _comment!!.has("getReplies"); + } + + override fun getReplies(client: IPlatformClient): IPager? { + if(!_hasGetReplies) + return null; + + val obj = _comment!!.invoke("getReplies", arrayOf()); + return JSCommentPager(_config!!, _plugin!!, obj); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSCommentPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSCommentPager.kt new file mode 100644 index 00000000..13d44fe9 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSCommentPager.kt @@ -0,0 +1,16 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.engine.V8Plugin + +class JSCommentPager : JSPager, IPager { + + constructor(config: SourcePluginConfig, plugin: V8Plugin, pager: V8ValueObject) : super(config, plugin, pager) { } + + override fun convertResult(obj: V8ValueObject): IPlatformComment { + return JSComment(config, plugin, obj); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSContent.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSContent.kt new file mode 100644 index 00000000..c79223ca --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSContent.kt @@ -0,0 +1,54 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.IPluginSourced +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.getOrDefault +import com.futo.platformplayer.getOrThrow +import java.time.LocalDateTime +import java.time.OffsetDateTime +import java.time.ZoneOffset + +open class JSContent : IPlatformContent, IPluginSourced { + protected val _pluginConfig: SourcePluginConfig; + protected val _content : V8ValueObject; + + protected val _hasGetDetails: Boolean; + + override val contentType: ContentType get() = ContentType.UNKNOWN; + + override val id: PlatformID; + override val name: String; + override val author: PlatformAuthorLink; + override val datetime: OffsetDateTime?; + + override val url: String; + override val shareUrl: String; + + override val sourceConfig: SourcePluginConfig get() = _pluginConfig; + + constructor(config: SourcePluginConfig, obj: V8ValueObject) { + _pluginConfig = config; + _content = obj; + + val contextName = "PlatformContent"; + + id = PlatformID.fromV8(_pluginConfig, _content.getOrThrow(config, "id", contextName)); + name = _content.getOrThrow(config, "name", contextName); + author = PlatformAuthorLink.fromV8(_pluginConfig, _content.getOrThrow(config, "author", contextName)); + + val datetimeInt = _content.getOrThrow(config, "datetime", contextName).toLong(); + if(datetimeInt == 0.toLong()) + datetime = null; + else + datetime = OffsetDateTime.of(LocalDateTime.ofEpochSecond(datetimeInt, 0, ZoneOffset.UTC), ZoneOffset.UTC); + url = _content.getOrThrow(config, "url", contextName); + shareUrl = _content.getOrDefault(config, "shareUrl", contextName, null) ?: url; + + _hasGetDetails = _content.has("getDetails"); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSContentPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSContentPager.kt new file mode 100644 index 00000000..f7a8fbbc --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSContentPager.kt @@ -0,0 +1,17 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.IPluginSourced +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.engine.V8Plugin + +class JSContentPager : JSPager, IPluginSourced { + override val sourceConfig: SourcePluginConfig get() = config; + + constructor(config: SourcePluginConfig, plugin: V8Plugin, pager: V8ValueObject) : super(config, plugin, pager) {} + + override fun convertResult(obj: V8ValueObject): IPlatformContent { + return IJSContent.fromV8(config, obj); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSLiveChatWindowDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSLiveChatWindowDescriptor.kt new file mode 100644 index 00000000..abb6e5e5 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSLiveChatWindowDescriptor.kt @@ -0,0 +1,25 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.live.ILiveChatWindowDescriptor +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.getOrDefault +import com.futo.platformplayer.getOrThrow +import java.time.LocalDateTime +import java.time.OffsetDateTime +import java.time.ZoneOffset + +class JSLiveChatWindowDescriptor: ILiveChatWindowDescriptor { + override val url: String; + override val removeElements: List; + + + constructor(config: SourcePluginConfig, obj: V8ValueObject) { + val contextName = "LiveChatWindowDescriptor"; + + url = obj.getOrThrow(config, "url", contextName); + removeElements = obj.getOrDefault(config, "removeElements", contextName, listOf()) ?: listOf(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSLiveEventPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSLiveEventPager.kt new file mode 100644 index 00000000..1d8ead85 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSLiveEventPager.kt @@ -0,0 +1,25 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.api.media.structures.IPlatformLiveEventPager +import com.futo.platformplayer.engine.V8Plugin +import com.futo.platformplayer.getOrThrow + +class JSLiveEventPager : JSPager, IPlatformLiveEventPager { + override var nextRequest: Int; + + constructor(config: SourcePluginConfig, plugin: V8Plugin, pager: V8ValueObject) : super(config, plugin, pager) { + nextRequest = pager.getOrThrow(config, "nextRequest", "LiveEventPager"); + } + + override fun nextPage() { + super.nextPage(); + nextRequest = pager.getOrThrow(config, "nextRequest", "LiveEventPager"); + } + + override fun convertResult(obj: V8ValueObject): IPlatformLiveEvent { + return IPlatformLiveEvent.fromV8(config, obj, "LiveEventPager"); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSNestedMediaContent.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSNestedMediaContent.kt new file mode 100644 index 00000000..eff81f18 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSNestedMediaContent.kt @@ -0,0 +1,39 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.Thumbnails +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.nested.IPlatformNestedContent +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.getOrDefault +import com.futo.platformplayer.getOrThrow +import com.futo.platformplayer.states.StatePlatform + +//TODO: Refactor into video-only +class JSNestedMediaContent: IPlatformNestedContent, JSContent { + + override val contentType: ContentType get() = ContentType.NESTED_VIDEO; + override val nestedContentType: ContentType get() = ContentType.MEDIA; + + override val contentUrl: String; + override val contentName: String?; + override val contentDescription: String?; + override val contentProvider: String?; + override val contentThumbnails: Thumbnails; + + override val contentPlugin: String?; + override val contentSupported: Boolean get() = contentPlugin != null && StatePlatform.instance.isClientEnabled(contentPlugin); + + constructor(config: SourcePluginConfig, obj: V8ValueObject): super(config, obj) { + val contextName = "PlatformNestedContent"; + + this.contentUrl = obj.getOrThrow(config, "contentUrl", contextName); + this.contentName = obj.getOrDefault(config, "contentName", contextName, null); + this.contentDescription = obj.getOrDefault(config, "contentName", contextName, null); + this.contentProvider = obj.getOrDefault(config, "contentName", contextName, null); + this.contentThumbnails = obj.getOrDefault(config, "contentThumbnails", contextName, null)?.let { + return@let Thumbnails.fromV8(config, it); + } ?: Thumbnails(); + this.contentPlugin = StatePlatform.instance.getContentClientOrNull(this.contentUrl)?.id; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPager.kt new file mode 100644 index 00000000..31b0a9e5 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPager.kt @@ -0,0 +1,75 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueArray +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.engine.V8Plugin +import com.futo.platformplayer.getOrThrow + +abstract class JSPager : IPager { + protected val plugin: V8Plugin; + protected val config: SourcePluginConfig; + protected var pager: V8ValueObject; + + private var _lastResults: List? = null; + private var _resultChanged: Boolean = true; + private var _hasMorePages: Boolean = false; + //private var _morePagesWasFalse: Boolean = false; + + val isAvailable get() = plugin._runtime?.let { !it.isClosed && !it.isDead } ?: false; + + constructor(config: SourcePluginConfig, plugin: V8Plugin, pager: V8ValueObject) { + this.plugin = plugin; + this.pager = pager; + this.config = config; + + _hasMorePages = pager.getOrThrow(config, "hasMore", "Pager"); + getResults(); + } + + fun getPluginConfig(): SourcePluginConfig { + return config; + } + + override fun hasMorePages(): Boolean { + return _hasMorePages; + } + + override fun nextPage() { + pager = plugin.catchScriptErrors("[${plugin.config.name}] JSPager", "pager.nextPage()") { + pager.invoke("nextPage", arrayOf()); + }; + _hasMorePages = pager.getOrThrow(config, "hasMore", "Pager"); + _resultChanged = true; + /* + try { + } + catch(ex: Throwable) { + Logger.e("JSPager", "[${plugin.config.name}] Failed to load next page", ex); + _lastResults = listOf(); + UIDialogs.toast("Failed to get more results for plugin [${plugin.config.name}]\n${ex.message}"); + }*/ + } + + override fun getResults(): List { + val previousResults = _lastResults?.let { + if(!_resultChanged) + return@let it; + else + null; + }; + if(previousResults != null) + return previousResults; + + val items = pager.getOrThrow(config, "results", "JSPager"); + val newResults = items.toArray() + .map { convertResult(it as V8ValueObject) } + .toList(); + _lastResults = newResults; + _resultChanged = false; + return newResults; + } + + abstract fun convertResult(obj: V8ValueObject): T; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaybackTracker.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaybackTracker.kt new file mode 100644 index 00000000..9c62e7db --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaybackTracker.kt @@ -0,0 +1,60 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.engine.exceptions.ScriptImplementationException +import com.futo.platformplayer.getOrThrow +import com.futo.platformplayer.logging.Logger + +class JSPlaybackTracker: IPlaybackTracker { + private val _config: IV8PluginConfig; + private val _obj: V8ValueObject; + + private var _hasCalledInit: Boolean = false; + private val _hasInit: Boolean; + + private var _lastRequest: Long = Long.MIN_VALUE; + + override var nextRequest: Int = 1000 + private set; + + constructor(config: IV8PluginConfig, obj: V8ValueObject) { + if(!obj.has("onProgress")) + throw ScriptImplementationException(config, "Missing onProgress on PlaybackTracker"); + if(!obj.has("nextRequest")) + throw ScriptImplementationException(config, "Missing nextRequest on PlaybackTracker"); + + this._config = config; + this._obj = obj; + this._hasInit = obj.has("onInit"); + } + + override fun onInit(seconds: Double) { + synchronized(_obj) { + if(_hasCalledInit) + return; + if (_hasInit) { + Logger.i("JSPlaybackTracker", "onInit (${seconds})"); + _obj.invokeVoid("onInit", seconds); + } + nextRequest = Math.max(100, _obj.getOrThrow(_config, "nextRequest", "PlaybackTracker", false)); + _hasCalledInit = true; + } + } + + override fun onProgress(seconds: Double, isPlaying: Boolean) { + synchronized(_obj) { + if(!_hasCalledInit && _hasInit) + onInit(seconds); + else { + Logger.i("JSPlaybackTracker", "onProgress (${seconds}, ${isPlaying})"); + _obj.invokeVoid("onProgress", Math.floor(seconds), isPlaying); + nextRequest = Math.max(100, _obj.getOrThrow(_config, "nextRequest", "PlaybackTracker", false)); + _lastRequest = System.currentTimeMillis(); + } + } + } + + override fun shouldUpdate(): Boolean = (_lastRequest < 0 || (System.currentTimeMillis() - _lastRequest) > nextRequest); +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaylist.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaylist.kt new file mode 100644 index 00000000..b47ae9ea --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaylist.kt @@ -0,0 +1,19 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.getOrDefault + +open class JSPlaylist : JSContent, IPlatformPlaylist { + override val contentType: ContentType get() = ContentType.PLAYLIST; + override val thumbnail: String?; + override val videoCount: Int; + + constructor(config: SourcePluginConfig, obj: V8ValueObject) : super(config, obj) { + val contextName = "Playlist"; + thumbnail = obj.getOrDefault(config, "thumbnail", contextName, null); + videoCount = obj.getOrDefault(config, "videoCount", contextName, 0)!!; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaylistDetails.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaylistDetails.kt new file mode 100644 index 00000000..97b3f29b --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaylistDetails.kt @@ -0,0 +1,37 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.engine.V8Plugin +import com.futo.platformplayer.getOrThrow +import com.futo.platformplayer.models.Playlist + +class JSPlaylistDetails: JSPlaylist, IPlatformPlaylistDetails { + override val contents: IPager; + + constructor(plugin: V8Plugin, config: SourcePluginConfig, obj: V8ValueObject): super(config, obj) { + contents = JSVideoPager(config, plugin, obj.getOrThrow(config, "contents", "PlaylistDetails")); + } + + override fun toPlaylist(): Playlist { + val videos = contents.getResults().toMutableList(); + + //Download all pages + var allowedEmptyCount = 2; + while(contents.hasMorePages()) { + contents.nextPage(); + if(!videos.addAll(contents.getResults())) { + allowedEmptyCount--; + if(allowedEmptyCount <= 0) + break; + } + else allowedEmptyCount = 2; + } + + return Playlist(id.toString(), name, videos.map { SerializedPlatformVideo.fromVideo(it)}); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaylistPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaylistPager.kt new file mode 100644 index 00000000..a0df057b --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaylistPager.kt @@ -0,0 +1,16 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.engine.V8Plugin + +class JSPlaylistPager : JSPager, IPager { + + constructor(config: SourcePluginConfig, plugin: V8Plugin, pager: V8ValueObject) : super(config, plugin, pager) {} + + override fun convertResult(obj: V8ValueObject): IPlatformPlaylist { + return JSPlaylist(config, obj); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPost.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPost.kt new file mode 100644 index 00000000..49838a36 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPost.kt @@ -0,0 +1,33 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.IPluginSourced +import com.futo.platformplayer.api.media.models.Thumbnails +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.post.IPlatformPost +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.getOrThrow +import com.futo.platformplayer.getOrThrowNullableList + +open class JSPost : JSContent, IPlatformPost, IPluginSourced { + final override val contentType: ContentType get() = ContentType.POST; + + final override val description: String; + final override val thumbnails: List; + final override val images: List; + + constructor(config: SourcePluginConfig, obj: V8ValueObject): super(config, obj) { + val contextName = "PlatformPost"; + + description = _content.getOrThrow(config, "description", contextName); + thumbnails = _content.getOrThrowNullableList(config, "thumbnails", contextName) + ?.map { + if(it != null) + Thumbnails.fromV8(config, it); + else + null; + } ?: listOf(); + + images = _content.getOrThrowNullableList(config, "images", contextName) ?: listOf(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPostDetails.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPostDetails.kt new file mode 100644 index 00000000..e8982889 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPostDetails.kt @@ -0,0 +1,59 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker +import com.futo.platformplayer.api.media.models.post.IPlatformPost +import com.futo.platformplayer.api.media.models.post.IPlatformPostDetails +import com.futo.platformplayer.api.media.models.post.TextType +import com.futo.platformplayer.api.media.models.ratings.IRating +import com.futo.platformplayer.api.media.models.ratings.RatingLikes +import com.futo.platformplayer.api.media.platforms.js.DevJSClient +import com.futo.platformplayer.api.media.platforms.js.JSClient +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.getOrDefault +import com.futo.platformplayer.states.StateDeveloper + +class JSPostDetails : JSPost, IPlatformPost, IPlatformPostDetails { + private val _hasGetComments: Boolean; + + override val rating: IRating; + + override val textType: TextType; + override val content: String; + + + + constructor(config: SourcePluginConfig, obj: V8ValueObject): super(config, obj) { + val contextName = "PlatformPostDetails"; + + rating = obj.getOrDefault(config, "rating", contextName, null)?.let { IRating.fromV8(config, it, contextName) } ?: RatingLikes(0); + textType = TextType.fromInt((obj.getOrDefault(config, "textType", contextName, null) ?: 0)); + content = obj.getOrDefault(config, "content", contextName, "") ?: ""; + + _hasGetComments = _content.has("getComments"); + } + + override fun getComments(client: IPlatformClient): IPager? { + if(!_hasGetComments || _content.isClosed) + return null; + + if(client is DevJSClient) + return StateDeveloper.instance.handleDevCall(client.devID, "videoDetail.getComments()") { + return@handleDevCall getCommentsJS(client); + } + else if(client is JSClient) + return getCommentsJS(client); + + return null; + } + override fun getPlaybackTracker(): IPlaybackTracker? = null; + + + private fun getCommentsJS(client: JSClient): JSCommentPager { + val commentPager = _content.invoke("getComments", arrayOf()); + return JSCommentPager(_pluginConfig, client.getUnderlyingPlugin(), commentPager); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequest.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequest.kt new file mode 100644 index 00000000..960982bf --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequest.kt @@ -0,0 +1,18 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow +import com.futo.platformplayer.logging.Logger + +@kotlinx.serialization.Serializable +class JSRequest : JSRequestModifier.IRequest { + override val url: String; + override val headers: Map; + + constructor(config: IV8PluginConfig, obj: V8ValueObject) { + val contextName = "ModifyRequestResponse"; + url = obj.getOrThrow(config, "url", contextName); + headers = obj.getOrThrow(config, "headers", contextName); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestModifier.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestModifier.kt new file mode 100644 index 00000000..0d71057a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSRequestModifier.kt @@ -0,0 +1,42 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.engine.V8Plugin +import com.futo.platformplayer.engine.exceptions.ScriptImplementationException +import com.futo.platformplayer.getOrNull + +class JSRequestModifier { + private val _config: IV8PluginConfig; + private var _modifier: V8ValueObject; + val allowByteSkip: Boolean; + + constructor(config: IV8PluginConfig, modifier: V8ValueObject) { + this._modifier = modifier; + this._config = config; + + allowByteSkip = modifier.getOrNull(config, "allowByteSkip", "JSRequestModifier") ?: true; + + if(!modifier.has("modifyRequest")) + throw ScriptImplementationException(config, "RequestModifier is missing modifyRequest", null); + } + + fun modifyRequest(url: String, headers: Map): IRequest { + if (_modifier.isClosed) { + return Request(url, headers); + } + + val result = V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSRequestModifier", "builder.modifyRequest()") { + _modifier.invoke("modifyRequest", url, headers); + }; + + return JSRequest(_config, result as V8ValueObject); + } + + interface IRequest { + val url: String; + val headers: Map; + } + + data class Request(override val url: String, override val headers: Map) : IRequest; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSSubtitleSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSSubtitleSource.kt new file mode 100644 index 00000000..bb4650f6 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSSubtitleSource.kt @@ -0,0 +1,64 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import android.net.Uri +import com.caoccao.javet.values.primitive.V8ValueString +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.getOrThrow +import com.futo.platformplayer.states.StateApp +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.File + +class JSSubtitleSource : ISubtitleSource { + private val _obj: V8ValueObject; + + private val _lockSub = Object(); + private var _fileSubtitle: File? = null; + + override val name: String; + override val url: String?; + override val format: String?; + override val hasFetch: Boolean; + + constructor(config: SourcePluginConfig, v8Value: V8ValueObject) { + _obj = v8Value; + + val context = "JSSubtitles"; + name = v8Value.getOrThrow(config, "name", context, false); + url = v8Value.getOrThrow(config, "url", context, true); + format = v8Value.getOrThrow(config, "format", context, true); + hasFetch = v8Value.has("getSubtitles"); + } + + override fun getSubtitles(): String { + if(!hasFetch) + throw IllegalStateException("This subtitle doesn't support getSubtitles.."); + val v8String = _obj.invoke("getSubtitles", arrayOf()); + return v8String.value; + } + + override suspend fun getSubtitlesURI(): Uri? { + if(_fileSubtitle != null) + return Uri.fromFile(_fileSubtitle); + if(!hasFetch) + return Uri.parse(url); + + return withContext(Dispatchers.IO) { + return@withContext synchronized(_lockSub) { + val subtitleText = getSubtitles(); + val subFile = StateApp.instance.getTempFile(); + subFile.writeText(subtitleText, Charsets.UTF_8); + _fileSubtitle = subFile; + return@synchronized Uri.fromFile(subFile); + }; + } + } + + companion object { + fun fromV8(config: SourcePluginConfig, value: V8ValueObject): JSSubtitleSource { + return JSSubtitleSource(config, value); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideo.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideo.kt new file mode 100644 index 00000000..ef0f2fee --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideo.kt @@ -0,0 +1,30 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.IPluginSourced +import com.futo.platformplayer.api.media.models.Thumbnails +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.getOrThrow + +open class JSVideo : JSContent, IPlatformVideo, IPluginSourced { + final override val contentType: ContentType get() = ContentType.MEDIA; + + final override val thumbnails: Thumbnails; + + final override val duration: Long; + final override val viewCount: Long; + + final override val isLive: Boolean; + + constructor(config: SourcePluginConfig, obj: V8ValueObject): super(config, obj) { + val contextName = "PlatformVideo"; + + thumbnails = Thumbnails.fromV8(config, _content.getOrThrow(config, "thumbnails", contextName)); + + duration = _content.getOrThrow(config, "duration", contextName).toLong(); + viewCount = _content.getOrThrow(config, "viewCount", contextName); + isLive = _content.getOrThrow(config, "isLive", contextName); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoDetails.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoDetails.kt new file mode 100644 index 00000000..6fbd07ed --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoDetails.kt @@ -0,0 +1,106 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.V8Value +import com.caoccao.javet.values.reference.V8ValueArray +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker +import com.futo.platformplayer.api.media.models.ratings.IRating +import com.futo.platformplayer.api.media.models.ratings.RatingLikes +import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor +import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource +import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails +import com.futo.platformplayer.api.media.platforms.js.DevJSClient +import com.futo.platformplayer.api.media.platforms.js.JSClient +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.api.media.platforms.js.models.sources.JSSource +import com.futo.platformplayer.api.media.platforms.js.models.sources.JSVideoSourceDescriptor +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.engine.V8Plugin +import com.futo.platformplayer.getOrDefault +import com.futo.platformplayer.getOrThrow +import com.futo.platformplayer.getOrThrowNullable +import com.futo.platformplayer.states.StateDeveloper + +class JSVideoDetails : JSVideo, IPlatformVideoDetails { + private val _hasGetComments: Boolean; + private val _hasGetPlaybackTracker: Boolean; + + //Details + override val description : String; + override val rating : IRating; + + override val video: IVideoSourceDescriptor; + override val preview : IVideoSourceDescriptor? = null; + + override val dash: IDashManifestSource?; + override val hls: IHLSManifestSource?; + + override val live: IVideoSource?; + + override val subtitles: List; + + + constructor(config: SourcePluginConfig, obj: V8ValueObject) : super(config, obj) { + val contextName = "VideoDetails"; + description = _content.getOrThrow(config, "description", contextName); + video = JSVideoSourceDescriptor.fromV8(config, _content.getOrThrow(config, "video", contextName)); + dash = JSSource.fromV8DashNullable(config, _content.getOrThrowNullable(config, "dash", contextName)); + hls = JSSource.fromV8HLSNullable(config, _content.getOrThrowNullable(config, "hls", contextName)); + live = JSSource.fromV8VideoNullable(config, _content.getOrThrowNullable(config, "live", contextName)); + rating = IRating.fromV8OrDefault(config, _content.getOrDefault(config, "rating", contextName, null), RatingLikes(0)); + + if(!_content.has("subtitles")) + subtitles = listOf(); + else { + val subArrs = _content.getOrThrowNullable(config, "subtitles", contextName); + if(subArrs != null) + subtitles = subArrs.keys.map { JSSubtitleSource.fromV8(config, subArrs.get(it)) }; + else + subtitles = listOf(); + } + + _hasGetComments = _content.has("getComments"); + _hasGetPlaybackTracker = _content.has("getPlaybackTracker"); + } + + override fun getPlaybackTracker(): IPlaybackTracker? { + if(!_hasGetPlaybackTracker || _content.isClosed) + return null; + if(_pluginConfig.id == StateDeveloper.DEV_ID) + return StateDeveloper.instance.handleDevCall(_pluginConfig.id, "videoDetail.getComments()") { + return@handleDevCall getPlaybackTrackerJS(); + } + else + return getPlaybackTrackerJS(); + } + private fun getPlaybackTrackerJS(): IPlaybackTracker? { + return V8Plugin.catchScriptErrors(_pluginConfig, "VideoDetails", "videoDetails.getPlaybackTracker()") { + val tracker = _content.invoke("getPlaybackTracker", arrayOf()) + ?: return@catchScriptErrors null; + if(tracker is V8ValueObject) + return@catchScriptErrors JSPlaybackTracker(_pluginConfig, tracker); + else + return@catchScriptErrors null; + }; + } + + override fun getComments(client: IPlatformClient): IPager? { + if(client !is JSClient || !_hasGetComments || _content.isClosed) + return null; + + if(client is DevJSClient) + return StateDeveloper.instance.handleDevCall(client.devID, "videoDetail.getComments()") { + return@handleDevCall getCommentsJS(client); + } + else + return getCommentsJS(client); + } + + private fun getCommentsJS(client: JSClient): JSCommentPager { + val commentPager = _content.invoke("getComments", arrayOf()); + return JSCommentPager(_pluginConfig, client.getUnderlyingPlugin(), commentPager); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoPager.kt new file mode 100644 index 00000000..1cc24a2d --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideoPager.kt @@ -0,0 +1,15 @@ +package com.futo.platformplayer.api.media.platforms.js.models + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.engine.V8Plugin + +class JSVideoPager : JSPager { + + constructor(config: SourcePluginConfig, plugin: V8Plugin, pager: V8ValueObject) : super(config, plugin, pager) {} + + override fun convertResult(obj: V8ValueObject): IPlatformVideo { + return JSVideo(config, obj); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioUrlSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioUrlSource.kt new file mode 100644 index 00000000..39d80032 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioUrlSource.kt @@ -0,0 +1,44 @@ +package com.futo.platformplayer.api.media.platforms.js.models.sources + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlSource +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrDefault +import com.futo.platformplayer.getOrThrow + +open class JSAudioUrlSource : IAudioUrlSource, JSSource { + override val name: String; + override val bitrate : Int; + override val container : String; + override val codec: String; + private val url : String; + + override val language: String; + + override val duration: Long?; + + override var priority: Boolean = false; + + constructor(config: IV8PluginConfig, obj: V8ValueObject) : super(TYPE_AUDIOURL, config, obj) { + val contextName = "AudioUrlSource"; + + bitrate = _obj.getOrThrow(config, "bitrate", contextName); + container = _obj.getOrThrow(config, "container", contextName); + codec = _obj.getOrThrow(config, "codec", contextName); + url = _obj.getOrThrow(config, "url", contextName); + language = _obj.getOrThrow(config, "language", contextName); + duration = _obj.getOrDefault(config, "duration", contextName, null); + + name = _obj.getOrDefault(config, "name", contextName, "${container} ${bitrate}") ?: "${container} ${bitrate}"; + + priority = if(_obj.has("priority")) obj.getOrThrow(config, "priority", contextName) else false; + } + + override fun getAudioUrl() : String { + return url; + } + + override fun toString(): String { + return "(name=$name, container=$container, bitrate=$bitrate, codec=$codec, url=$url, language=$language, duration=$duration)"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioWithMetadataSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioWithMetadataSource.kt new file mode 100644 index 00000000..9eafafee --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioWithMetadataSource.kt @@ -0,0 +1,35 @@ +package com.futo.platformplayer.api.media.platforms.js.models.sources + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.streams.sources.other.IStreamMetaDataSource +import com.futo.platformplayer.api.media.models.streams.sources.other.StreamMetaData +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrDefault + +class JSAudioUrlRangeSource : JSAudioUrlSource, IStreamMetaDataSource { + val hasItag: Boolean get() = itagId != null && initStart != null && initEnd != null && indexStart != null && indexEnd != null; + val itagId: Int?; + val initStart: Int?; + val initEnd: Int?; + + val indexStart: Int?; + val indexEnd: Int?; + val audioChannels: Int; + + override val streamMetaData get() = if(initStart != null + && initEnd != null + && indexStart != null + && indexEnd != null) + StreamMetaData(initStart, initEnd, indexStart, indexEnd) else null; + + constructor(config: IV8PluginConfig, obj: V8ValueObject) : super(config, obj) { + val contextName = "JSAudioUrlRangeSource"; + + itagId = _obj.getOrDefault(config, "itagId", contextName, null); + initStart = _obj.getOrDefault(config, "initStart", contextName, null); + initEnd = _obj.getOrDefault(config, "initEnd", contextName, null); + indexStart = _obj.getOrDefault(config, "indexStart", contextName, null); + indexEnd = _obj.getOrDefault(config, "indexEnd", contextName, null); + audioChannels = _obj.getOrDefault(config, "audioChannels", contextName, 2) ?: 2; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestSource.kt new file mode 100644 index 00000000..4f50cbf6 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestSource.kt @@ -0,0 +1,35 @@ +package com.futo.platformplayer.api.media.platforms.js.models.sources + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrNull +import com.futo.platformplayer.getOrThrow + +class JSDashManifestSource : IVideoUrlSource, IDashManifestSource, JSSource { + override val width : Int = 0; + override val height : Int = 0; + override val container : String = "application/dash+xml"; + override val codec : String = "Dash"; + override val name : String; + override val bitrate: Int? = null; + override val url : String; + override val duration: Long; + + override var priority: Boolean = false; + + constructor(config: IV8PluginConfig, obj: V8ValueObject) : super(TYPE_DASH, config, obj) { + val contextName = "DashSource"; + + name = _obj.getOrThrow(config, "name", contextName); + url = _obj.getOrThrow(config, "url", contextName); + duration = _obj.getOrThrow(config, "duration", contextName); + + priority = obj.getOrNull(config, "priority", contextName) ?: false; + } + + override fun getVideoUrl(): String { + return url; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestAudioSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestAudioSource.kt new file mode 100644 index 00000000..c006f2ec --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestAudioSource.kt @@ -0,0 +1,42 @@ +package com.futo.platformplayer.api.media.platforms.js.models.sources + +import com.caoccao.javet.values.V8Value +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlSource +import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestAudioSource +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrNull +import com.futo.platformplayer.getOrThrow +import com.futo.platformplayer.orNull + +class JSHLSManifestAudioSource : IAudioUrlSource, IHLSManifestAudioSource, JSSource { + override val container : String get() = "application/vnd.apple.mpegurl"; + override val codec: String = "HLS"; + override val name : String; + override val bitrate : Int = 0; + override val url : String; + override val duration: Long; + override val language: String; + + override var priority: Boolean = false; + + constructor(config: IV8PluginConfig, obj: V8ValueObject) : super(TYPE_HLS, config, obj) { + val contextName = "HLSAudioSource"; + + name = _obj.getOrThrow(config, "name", contextName); + url = _obj.getOrThrow(config, "url", contextName); + duration = _obj.getOrThrow(config, "duration", contextName).toLong(); + language = _obj.getOrThrow(config, "language", contextName); + + priority = obj.getOrNull(config, "priority", contextName) ?: false; + } + + override fun getAudioUrl(): String { + return url; + } + + companion object { + fun fromV8HLSNullable(config: IV8PluginConfig, obj: V8Value?) : JSHLSManifestAudioSource? = obj.orNull { fromV8HLS(config, it as V8ValueObject) }; + fun fromV8HLS(config: IV8PluginConfig, obj: V8ValueObject) : JSHLSManifestAudioSource = JSHLSManifestAudioSource(config, obj); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestSource.kt new file mode 100644 index 00000000..27ba3352 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestSource.kt @@ -0,0 +1,35 @@ +package com.futo.platformplayer.api.media.platforms.js.models.sources + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrNull +import com.futo.platformplayer.getOrThrow + +class JSHLSManifestSource : IVideoUrlSource, IHLSManifestSource, JSSource { + override val width : Int = 0; + override val height : Int = 0; + override val container : String get() = "application/vnd.apple.mpegurl"; + override val codec: String = "HLS"; + override val name : String; + override val bitrate : Int? = null; + override val url : String; + override val duration: Long; + + override var priority: Boolean = false; + + constructor(config: IV8PluginConfig, obj: V8ValueObject) : super(TYPE_HLS, config, obj) { + val contextName = "HLSSource"; + + name = _obj.getOrThrow(config, "name", contextName); + url = _obj.getOrThrow(config, "url", contextName); + duration = _obj.getOrThrow(config, "duration", contextName).toLong(); + + priority = obj.getOrNull(config, "priority", contextName) ?: false; + } + + override fun getVideoUrl(): String { + return url; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt new file mode 100644 index 00000000..c4cae894 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt @@ -0,0 +1,89 @@ +package com.futo.platformplayer.api.media.platforms.js.models.sources + +import com.caoccao.javet.values.V8Value +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource +import com.futo.platformplayer.api.media.platforms.js.models.JSRequestModifier +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.engine.V8Plugin +import com.futo.platformplayer.orNull +import com.futo.platformplayer.views.video.datasources.JSHttpDataSource +import com.google.android.exoplayer2.upstream.DefaultHttpDataSource +import com.google.android.exoplayer2.upstream.HttpDataSource + +abstract class JSSource { + protected val _config: IV8PluginConfig; + protected val _obj: V8ValueObject; + private val _hasRequestModifier: Boolean; + + val type : String; + + constructor(type: String, config: IV8PluginConfig, obj: V8ValueObject) { + this._config = config; + this._obj = obj; + this.type = type; + + _hasRequestModifier = obj.has("getRequestModifier"); + } + + fun getRequestModifier(): JSRequestModifier? { + if (!_hasRequestModifier || _obj.isClosed) { + return null; + } + + val result = V8Plugin.catchScriptErrors(_config, "[${_config.name}] JSVideoUrlSource", "obj.getRequestModifier()") { + _obj.invoke("getRequestModifier", arrayOf()); + }; + + if (result !is V8ValueObject) { + return null; + } + + return JSRequestModifier(_config, result) + } + + fun getHttpDataSourceFactory(): HttpDataSource.Factory { + val requestModifier = getRequestModifier(); + return if (requestModifier != null) { + JSHttpDataSource.Factory().setRequestModifier(requestModifier); + } else { + DefaultHttpDataSource.Factory(); + } + } + + companion object { + const val TYPE_AUDIOURL = "AudioUrlSource"; + const val TYPE_VIDEOURL = "VideoUrlSource"; + const val TYPE_AUDIO_WITH_METADATA = "AudioUrlRangeSource"; + const val TYPE_VIDEO_WITH_METADATA = "VideoUrlRangeSource"; + const val TYPE_DASH = "DashSource"; + const val TYPE_HLS = "HLSSource"; + + fun fromV8VideoNullable(config: IV8PluginConfig, obj: V8Value?) : IVideoSource? = obj.orNull { fromV8Video(config, it as V8ValueObject) }; + fun fromV8Video(config: IV8PluginConfig, obj: V8ValueObject) : IVideoSource { + val type = obj.getString("plugin_type"); + return when(type) { + TYPE_VIDEOURL -> JSVideoUrlSource(config, obj); + TYPE_VIDEO_WITH_METADATA -> JSVideoUrlRangeSource(config, obj); + TYPE_HLS -> fromV8HLS(config, obj); + TYPE_DASH -> fromV8Dash(config, obj); + else -> throw NotImplementedError("Unknown type ${type}"); + } + } + fun fromV8DashNullable(config: IV8PluginConfig, obj: V8Value?) : JSDashManifestSource? = obj.orNull { fromV8Dash(config, it as V8ValueObject) }; + fun fromV8Dash(config: IV8PluginConfig, obj: V8ValueObject) : JSDashManifestSource = JSDashManifestSource(config, obj); + fun fromV8HLSNullable(config: IV8PluginConfig, obj: V8Value?) : JSHLSManifestSource? = obj.orNull { fromV8HLS(config, it as V8ValueObject) }; + fun fromV8HLS(config: IV8PluginConfig, obj: V8ValueObject) : JSHLSManifestSource = JSHLSManifestSource(config, obj); + + fun fromV8Audio(config: IV8PluginConfig, obj: V8ValueObject) : IAudioSource { + val type = obj.getString("plugin_type"); + return when(type) { + TYPE_HLS -> JSHLSManifestAudioSource.fromV8HLS(config, obj); + TYPE_AUDIOURL -> JSAudioUrlSource(config, obj); + TYPE_AUDIO_WITH_METADATA -> JSAudioUrlRangeSource(config, obj); + else -> throw NotImplementedError("Unknown type ${type}"); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSUnMuxVideoSourceDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSUnMuxVideoSourceDescriptor.kt new file mode 100644 index 00000000..08d4911d --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSUnMuxVideoSourceDescriptor.kt @@ -0,0 +1,29 @@ +package com.futo.platformplayer.api.media.platforms.js.models.sources + +import com.caoccao.javet.values.reference.V8ValueArray +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor +import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow + +class JSUnMuxVideoSourceDescriptor: VideoUnMuxedSourceDescriptor { + protected val _obj: V8ValueObject; + + override val isUnMuxed: Boolean; + override val videoSources: Array; + override val audioSources: Array; + + constructor(config: IV8PluginConfig, obj: V8ValueObject) { + this._obj = obj; + val contextName = "UnMuxVideoSource" + this.isUnMuxed = obj.getOrThrow(config, "isUnMuxed", contextName); + this.videoSources = obj.getOrThrow(config, "videoSources", contextName).toArray() + .map { JSSource.fromV8Video(config, it as V8ValueObject) } + .toTypedArray(); + this.audioSources = obj.getOrThrow(config, "audioSources", contextName).toArray() + .map { JSSource.fromV8Audio(config, it as V8ValueObject) } + .toTypedArray(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoSourceDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoSourceDescriptor.kt new file mode 100644 index 00000000..463100b0 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoSourceDescriptor.kt @@ -0,0 +1,40 @@ +package com.futo.platformplayer.api.media.platforms.js.models.sources + +import com.caoccao.javet.values.reference.V8ValueArray +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor +import com.futo.platformplayer.api.media.models.streams.VideoMuxedSourceDescriptor +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow + +class JSVideoSourceDescriptor: VideoMuxedSourceDescriptor { + protected val _obj: V8ValueObject; + + override val isUnMuxed: Boolean; + override val videoSources: Array; + + constructor(config: IV8PluginConfig, obj: V8ValueObject) { + this._obj = obj; + val contextName = "VideoSourceDescriptor"; + this.isUnMuxed = obj.getOrThrow(config, "isUnMuxed", contextName); + this.videoSources = obj.getOrThrow(config, "videoSources", contextName).toArray() + .map { JSSource.fromV8Video(config, it as V8ValueObject) } + .toTypedArray(); + } + + companion object { + const val TYPE_MUXED = "MuxVideoSourceDescriptor"; + const val TYPE_UNMUXED = "UnMuxVideoSourceDescriptor"; + + + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : IVideoSourceDescriptor { + val type = obj.getString("plugin_type") + return when(type) { + TYPE_MUXED -> JSVideoSourceDescriptor(config, obj); + TYPE_UNMUXED -> JSUnMuxVideoSourceDescriptor(config, obj); + else -> throw NotImplementedError("Unknown type: ${type}"); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoUrlSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoUrlSource.kt new file mode 100644 index 00000000..a4fbf0e8 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoUrlSource.kt @@ -0,0 +1,43 @@ +package com.futo.platformplayer.api.media.platforms.js.models.sources + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrNull +import com.futo.platformplayer.getOrThrow + +open class JSVideoUrlSource : IVideoUrlSource, JSSource { + override val width : Int; + override val height : Int; + override val container : String; + override val codec: String; + override val name : String; + override val bitrate : Int; + override val duration: Long; + private val url : String; + + override var priority: Boolean = false; + + constructor(config: IV8PluginConfig, obj: V8ValueObject): super(TYPE_VIDEOURL, config, obj) { + val contextName = "JSVideoUrlSource"; + + width = _obj.getOrThrow(config, "width", contextName); + height = _obj.getOrThrow(config, "height", contextName); + container = _obj.getOrThrow(config, "container", contextName); + codec = _obj.getOrThrow(config, "codec", contextName); + name = _obj.getOrThrow(config, "name", contextName); + bitrate = _obj.getOrThrow(config, "bitrate", contextName); + duration = _obj.getOrThrow(config, "duration", contextName).toLong(); + url = _obj.getOrThrow(config, "url", contextName); + + priority = obj.getOrNull(config, "priority", contextName) ?: false; + } + + override fun getVideoUrl() : String { + return url; + } + + override fun toString(): String { + return "(width=$width, height=$height, container=$container, codec=$codec, name=$name, bitrate=$bitrate, duration=$duration, url=$url)" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoWithMetadataSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoWithMetadataSource.kt new file mode 100644 index 00000000..90b6edee --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoWithMetadataSource.kt @@ -0,0 +1,33 @@ +package com.futo.platformplayer.api.media.platforms.js.models.sources + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.streams.sources.other.IStreamMetaDataSource +import com.futo.platformplayer.api.media.models.streams.sources.other.StreamMetaData +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrDefault + +class JSVideoUrlRangeSource : JSVideoUrlSource, IStreamMetaDataSource { + val hasItag: Boolean get() = itagId != null && initStart != null && initEnd != null && indexStart != null && indexEnd != null; + val itagId: Int?; + val initStart: Int?; + val initEnd: Int?; + + val indexStart: Int?; + val indexEnd: Int?; + + override val streamMetaData get() = if(initStart != null + && initEnd != null + && indexStart != null + && indexEnd != null) + StreamMetaData(initStart, initEnd, indexStart, indexEnd) else null; + + constructor(config: IV8PluginConfig, obj: V8ValueObject) : super(config, obj) { + val contextName = "JSVideoUrlRangeSource"; + + itagId = _obj.getOrDefault(config, "itagId", contextName, null); + initStart = _obj.getOrDefault(config, "initStart", contextName, null); + initEnd = _obj.getOrDefault(config, "initEnd", contextName, null); + indexStart = _obj.getOrDefault(config, "indexStart", contextName, null); + indexEnd = _obj.getOrDefault(config, "indexEnd", contextName, null); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/DedupContentPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/DedupContentPager.kt new file mode 100644 index 00000000..033f737e --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/DedupContentPager.kt @@ -0,0 +1,86 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.contents.PlatformContentPlaceholder +import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.getDiffDays +import com.futo.platformplayer.getNowDiffDays +import com.futo.platformplayer.logging.Logger +import com.futo.polycentric.core.combineHashCodes +import kotlin.math.abs + +//TODO: If common pattern, create ModifierPager that implements all this composition +class DedupContentPager : IPager, IAsyncPager, IReplacerPager { + private val _basePager: IPager; + private val _pastResults: ArrayList = arrayListOf(); + private var _currentResults: List; + + private val _preferredPlatform: List; + + override val onReplaced = Event2(); + + constructor(basePager: IPager, preferredPlatform: List? = null) { + _preferredPlatform = preferredPlatform ?: listOf(); + _basePager = basePager; + _currentResults = dedupResults(_basePager.getResults()); + } + + override fun hasMorePages(): Boolean = _basePager.hasMorePages(); + override fun nextPage() { + _basePager.nextPage() + _currentResults = dedupResults(_basePager.getResults()); + } + + override suspend fun nextPageAsync() { + if(_basePager is IAsyncPager<*>) + _basePager.nextPageAsync(); + else + _basePager.nextPage(); + _currentResults = dedupResults(_basePager.getResults()); + } + override fun getResults(): List = _currentResults; + + private fun dedupResults(results: List): List { + val resultsToRemove = arrayListOf(); + + for(result in results) { + if(resultsToRemove.contains(result) || result is PlatformContentPlaceholder) + continue; + + //TODO: Map allocation can prob be simplified to just index based. + val sameItems = results.filter { isSameItem(result, it) }; + val platformItemMap = sameItems.groupBy { it.id.pluginId }.mapValues { (_, items) -> items.first() } + val bestPlatform = _preferredPlatform.map { it.lowercase() }.firstOrNull { platformItemMap.containsKey(it) } + val bestItem = platformItemMap[bestPlatform] ?: sameItems.first() + + resultsToRemove.addAll(sameItems.filter { it != bestItem }); + } + val toReturn = results.filter { !resultsToRemove.contains(it) }.mapNotNull { item -> + val olderItemIndex = _pastResults.indexOfFirst { isSameItem(item, it) }; + if(olderItemIndex >= 0) { + val olderItem = _pastResults[olderItemIndex]; + val olderItemPriority = _preferredPlatform.indexOf(olderItem.id.pluginId); + val newItemPriority = _preferredPlatform.indexOf(item.id.pluginId); + if(newItemPriority < olderItemPriority) { + _pastResults[olderItemIndex] = item; + onReplaced.emit(olderItem, item); + } + return@mapNotNull null; + } + else + return@mapNotNull item; + }; + _pastResults.addAll(toReturn); + return toReturn; + } + private fun isSameItem(item: IPlatformContent, item2: IPlatformContent): Boolean { + return item.name == item2.name && (item.datetime == null || item2.datetime == null || abs(item.datetime!!.getDiffDays(item2.datetime!!)) < 2); + } + private fun calculateHash(item: IPlatformContent): Int { + return combineHashCodes(listOf(item.name.hashCode(), item.datetime?.hashCode())); + } + + companion object { + private const val TAG = "DedupContentPager"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/EmptyPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/EmptyPager.kt new file mode 100644 index 00000000..31d1f0fb --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/EmptyPager.kt @@ -0,0 +1,19 @@ +package com.futo.platformplayer.api.media.structures + +/** + * A pager without results + */ +open class EmptyPager : IPager { + override fun hasMorePages(): Boolean { + return false; + } + + override fun nextPage() { + + } + + override fun getResults(): List { + return listOf(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/IAsyncPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/IAsyncPager.kt new file mode 100644 index 00000000..9a9ebd65 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/IAsyncPager.kt @@ -0,0 +1,12 @@ +package com.futo.platformplayer.api.media.structures + +import kotlinx.coroutines.CoroutineScope + +/** + * A Pager interface that implements a suspended manner of nextPage + */ +interface IAsyncPager { + fun hasMorePages() : Boolean; + suspend fun nextPageAsync(); + fun getResults() : List; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/INestedPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/INestedPager.kt new file mode 100644 index 00000000..42e1d1eb --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/INestedPager.kt @@ -0,0 +1,8 @@ +package com.futo.platformplayer.api.media.structures + +/** + * Interface extension for some pagers that allow you to find nested pagers if needed + */ +interface INestedPager { + fun findPager(query: (IPager) -> Boolean): IPager?; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/IPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/IPager.kt new file mode 100644 index 00000000..77d49acf --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/IPager.kt @@ -0,0 +1,10 @@ +package com.futo.platformplayer.api.media.structures + +/** + * Base pager used for all paging in the app, often wrapped by various other pagers to modified behavior + */ +interface IPager { + fun hasMorePages() : Boolean; + fun nextPage(); + fun getResults() : List; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/IPlatformLiveEventPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/IPlatformLiveEventPager.kt new file mode 100644 index 00000000..3048a6e9 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/IPlatformLiveEventPager.kt @@ -0,0 +1,10 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent + +/** + * A special pager intended for live chat implementation. Extended if required based on the JS implementations + */ +interface IPlatformLiveEventPager: IPager { + val nextRequest: Int; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/IRefreshPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/IRefreshPager.kt new file mode 100644 index 00000000..390895bd --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/IRefreshPager.kt @@ -0,0 +1,15 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.constructs.Event1 + +/** + * A RefreshPager represents a pager that can be modified overtime (eg. By getting more results later, by recreating the pager) + * When the onPagerChanged event is emitted, a new pager instance is passed, or requested via getCurrentPager + */ +interface IRefreshPager { + val onPagerChanged: Event1>; + val onPagerError: Event1; + + fun getCurrentPager(): IPager; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/IReplacerPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/IReplacerPager.kt new file mode 100644 index 00000000..613bc255 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/IReplacerPager.kt @@ -0,0 +1,7 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.constructs.Event2 + +interface IReplacerPager { + val onReplaced: Event2; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiAsyncPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiAsyncPager.kt new file mode 100644 index 00000000..8ab8e5d5 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiAsyncPager.kt @@ -0,0 +1,165 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.api.media.exceptions.search.NoNextPageException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import java.util.stream.IntStream +import kotlin.system.measureTimeMillis + +/** + * Async MultiPager is a multipager that calls all pagers in a deferred (promised) manner. + * Unlike its normal counterpart which waits for results as they are needed. + * The benefit is that if multiple pagers need to request a new page, its done in parallel and awaited together. + * The downside is that pager results cannot be consumed based on their contents, as their contents may still be unknown. + * (eg. example Chronological pagers cannot be done without reordering the results every refresh, which causes bad UX) + */ +abstract class MultiAsyncPager : IPager, IAsyncPager { + protected val _pagerLock = Object(); + + protected val _pagers : MutableList>; + protected val _subSinglePagers : MutableList>; + protected val _failedPagers: ArrayList> = arrayListOf(); + + private val _pageSize : Int = 9; + + private var _didInitialize = false; + + private var _currentResults : List = listOf(); + private var _currentResultExceptions: Map, Throwable> = mapOf(); + + val allowFailure: Boolean; + + val totalPagers: Int get() = _pagers.size; + + constructor(pagers : List>, allowFailure: Boolean = false) { + this.allowFailure = allowFailure; + _pagers = pagers.toMutableList(); + _subSinglePagers = _pagers.map { SingleAsyncItemPager(it) }.toMutableList(); + } + suspend fun initialize() { + withContext(Dispatchers.IO) { + _currentResults = loadNextPage(this, true); + } + _didInitialize = true; + } + + override fun hasMorePages(): Boolean { + synchronized(_pagerLock) { + return _subSinglePagers.any { it.hasMoreItems() } || _pagers.any { it.hasMorePages() } + } + } + override fun nextPage() { + Logger.i(TAG, "Load next page"); + if(!_didInitialize) + throw IllegalStateException("Call initialize on MultiVideoPager before using it"); + runBlocking { loadNextPage(this) }; + Logger.i(TAG, "New results: ${_currentResults.size}"); + } + + override suspend fun nextPageAsync() { + Logger.i(TAG, "Load next page (async)"); + if(!_didInitialize) + throw IllegalStateException("Call initialize on MultiVideoPager before using it"); + withContext(Dispatchers.IO) { + } + Logger.i(TAG, "New results: ${_currentResults.size}"); + } + + override fun getResults(): List { + if(!_didInitialize) + throw IllegalStateException("Call initialize on MultiVideoPager before using it"); + return _currentResults; + } + fun getResultExceptions(): Map, Throwable> { + if(!_didInitialize) + throw IllegalStateException("Call initialize on MultiVideoPager before using it"); + return _currentResultExceptions; + } + + @Synchronized + private fun loadNextPage(scope: CoroutineScope, isInitial: Boolean = false) : List { + synchronized(_pagerLock) { + if (_subSinglePagers.size == 0) + return listOf(); + } + if(!isInitial && !hasMorePages()) + throw NoNextPageException(); + + val results = ArrayList>(); + val exceptions: MutableMap, Throwable> = mutableMapOf(); + for(i in IntStream.range(0, _pageSize)) { + val validPagers = synchronized(_pagerLock) { + _subSinglePagers.filter { !_failedPagers.contains(it.getPager()) && (it.hasMoreItems() || it.getPager().hasMorePages()) } + }; + val options: ArrayList> = arrayListOf(); + for (pager in validPagers) { + val item: Deferred? = if (allowFailure) { + try { + pager.getCurrentItem(scope); + } catch (ex: NoNextPageException) { + //TODO: This should never happen, has to be fixed later + Logger.i(TAG, "Expected item from pager but no page found?"); + null; + } catch (ex: Throwable) { + Logger.e(TAG, "Failed to fetch page for pager, exception: ${ex.message}", ex); + _failedPagers.add(pager.getPager()); + exceptions.put(pager.getPager(), ex); + null; + } + } else { + try { + pager.getCurrentItem(scope); + } catch (ex: NoNextPageException) { + //TODO: This should never happen, has to be fixed later + Logger.i(TAG, "Expected item from pager but no page found?"); + null; + } + }; + if (item != null) + options.add(SelectionOption(pager, item)); + } + + if (options.size == 0) + break; + val bestIndex = selectItemIndex(options.toTypedArray()); + if (bestIndex >= 0) { + + val consumed = options[bestIndex].pager.consumeItem(scope); + if (consumed != null) + results.add(consumed); + } + } + + _currentResults = results.mapNotNull { convertItem(it) }; + + _currentResultExceptions = exceptions; + return _currentResults; + } + + protected abstract fun convertItem(def: Deferred): R?; + + protected abstract fun selectItemIndex(options : Array>) : Int; + + protected class SelectionOption(val pager : SingleAsyncItemPager, val item : Deferred?); + + fun setExceptions(exs: Map, Throwable>) { + _currentResultExceptions = exs; + } + fun findPager(query: (IPager)->Boolean): IPager<*>? { + for(pager in _pagers) { + if(query(pager)) + return pager; + if(pager is MultiAsyncPager<*,*>) + return pager.findPager(query as (IPager) -> Boolean); + } + return null; + } + + companion object { + val TAG = "MultiPager"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiChronoContentPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiChronoContentPager.kt new file mode 100644 index 00000000..cc799cc9 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiChronoContentPager.kt @@ -0,0 +1,25 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import java.util.stream.IntStream + +/** + * A PlatformContent MultiPager that orders the results of a page based on the datetime of a content item + */ +class MultiChronoContentPager : MultiPager { + constructor(pagers : Array>, allowFailure: Boolean = false) : super(pagers.map { it }.toList(), allowFailure) {} + + @Synchronized + override fun selectItemIndex(options: Array>): Int { + if(options.size == 0) + return -1; + var bestIndex = 0; + for(i in IntStream.range(1, options.size)) { + val best = options[bestIndex].item!!; + val cur = options[i].item!!; + if(best.datetime == null || (cur.datetime != null && cur.datetime!! > best.datetime!!)) + bestIndex = i; + } + return bestIndex; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiDistributionChannelPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiDistributionChannelPager.kt new file mode 100644 index 00000000..a262af30 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiDistributionChannelPager.kt @@ -0,0 +1,48 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import java.util.stream.IntStream + +/** + * A Channel MultiPager that returns results based on a specified distribution + * TODO: Merge all basic distribution pagers + */ +class MultiDistributionChannelPager : MultiPager { + + private val dist : HashMap, Float>; + private val distConsumed : HashMap, Float>; + + constructor(pagers : Map, Float>) : super(pagers.keys.toMutableList()) { + val distTotal = pagers.values.sum(); + dist = HashMap(); + + //Convert distribution values to inverted percentages + for(kv in pagers) + dist[kv.key] = 1f - (kv.value / distTotal); + distConsumed = HashMap(); + for(kv in dist) + distConsumed[kv.key] = 0f; + } + + @Synchronized + override fun selectItemIndex(options: Array>): Int { + if(options.size == 0) + return -1; + var bestIndex = 0; + var bestConsumed = distConsumed[options[0].pager.getPager()]!! + dist[options[0].pager.getPager()]!!; + for(i in IntStream.range(1, options.size)) { + val pager = options[i].pager.getPager(); + val valueAfterAdd = distConsumed[pager]!! + dist[pager]!!; + + if(valueAfterAdd < bestConsumed) { + bestIndex = i; + bestConsumed = valueAfterAdd; + } + } + distConsumed[options[bestIndex].pager.getPager()] = bestConsumed; + return bestIndex; + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiDistributionContentAsyncPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiDistributionContentAsyncPager.kt new file mode 100644 index 00000000..25f79331 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiDistributionContentAsyncPager.kt @@ -0,0 +1,56 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.contents.PlatformContentDeferred +import kotlinx.coroutines.Deferred +import java.util.stream.IntStream + + +/** + * A Content AsyncMultiPager that returns results based on a specified distribution + * Unlike its non-async counterpart, this one uses parallel nextPage requests + */ +class MultiDistributionContentAsyncPager : MultiAsyncPager { + + private val dist : HashMap, Float>; + private val distConsumed : HashMap, Float>; + + constructor(pagers: Map, Float>) : super(pagers.keys.toMutableList()) { + val distTotal = pagers.values.sum(); + dist = HashMap(); + + //Convert distribution values to inverted percentages + for(kv in pagers) + dist[kv.key] = 1f - (kv.value / distTotal); + distConsumed = HashMap(); + for(kv in dist) + distConsumed[kv.key] = 0f; + } + + override fun convertItem(def: Deferred): IPlatformContent? { + if(def.isCompleted) + return def.getCompleted(); + else + return PlatformContentDeferred(null); + } + + @Synchronized + override fun selectItemIndex(options: Array>): Int { + if(options.size == 0) + return -1; + + var bestIndex = 0; + var bestConsumed = distConsumed[options[0].pager.getPager()]!! + dist[options[0].pager.getPager()]!!; + for(i in IntStream.range(1, options.size)) { + val pager = options[i].pager.getPager(); + val valueAfterAdd = distConsumed[pager]!! + dist[pager]!!; + + if(valueAfterAdd < bestConsumed) { + bestIndex = i; + bestConsumed = valueAfterAdd; + } + } + distConsumed[options[bestIndex].pager.getPager()] = bestConsumed; + return bestIndex; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiDistributionContentPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiDistributionContentPager.kt new file mode 100644 index 00000000..e86c9ba6 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiDistributionContentPager.kt @@ -0,0 +1,47 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import java.util.stream.IntStream + +/** + * A Content MultiPager that returns results based on a specified distribution + * TODO: Merge all basic distribution pagers + */ +class MultiDistributionContentPager : MultiPager { + + private val dist : HashMap, Float>; + private val distConsumed : HashMap, Float>; + + constructor(pagers : Map, Float>) : super(pagers.keys.toMutableList()) { + val distTotal = pagers.values.sum(); + dist = HashMap(); + + //Convert distribution values to inverted percentages + for(kv in pagers) + dist[kv.key] = 1f - (kv.value / distTotal); + distConsumed = HashMap(); + for(kv in dist) + distConsumed[kv.key] = 0f; + } + + @Synchronized + override fun selectItemIndex(options: Array>): Int { + if(options.size == 0) + return -1; + var bestIndex = 0; + var bestConsumed = distConsumed[options[0].pager.getPager()]!! + dist[options[0].pager.getPager()]!!; + for(i in IntStream.range(1, options.size)) { + val pager = options[i].pager.getPager(); + val valueAfterAdd = distConsumed[pager]!! + dist[pager]!!; + + if(valueAfterAdd < bestConsumed) { + bestIndex = i; + bestConsumed = valueAfterAdd; + } + } + distConsumed[options[bestIndex].pager.getPager()] = bestConsumed; + return bestIndex; + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiDistributionContentParallelPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiDistributionContentParallelPager.kt new file mode 100644 index 00000000..4cfdd764 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiDistributionContentParallelPager.kt @@ -0,0 +1,49 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import java.util.stream.IntStream + + +/** + * A Content AsyncMultiPager that returns results based on a specified distribution + * Unlike its non-async counterpart, this one uses parallel nextPage requests + */ +class MultiDistributionContentParallelPager : MultiParallelPager { + + private val dist : HashMap, Float>; + private val distConsumed : HashMap, Float>; + + constructor(pagers: Map, Float>) : super(pagers.keys.toMutableList()) { + val distTotal = pagers.values.sum(); + dist = HashMap(); + + //Convert distribution values to inverted percentages + for(kv in pagers) + dist[kv.key] = 1f - (kv.value / distTotal); + distConsumed = HashMap(); + for(kv in dist) + distConsumed[kv.key] = 0f; + } + + @Synchronized + override fun selectItemIndex(options: Array>): Int { + if(options.size == 0) + return -1; + + var bestIndex = 0; + var bestConsumed = distConsumed[options[0].pager.getPager()]!! + dist[options[0].pager.getPager()]!!; + for(i in IntStream.range(1, options.size)) { + val pager = options[i].pager.getPager(); + val valueAfterAdd = distConsumed[pager]!! + dist[pager]!!; + + if(valueAfterAdd < bestConsumed) { + bestIndex = i; + bestConsumed = valueAfterAdd; + } + } + distConsumed[options[bestIndex].pager.getPager()] = bestConsumed; + return bestIndex; + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiPager.kt new file mode 100644 index 00000000..0917805b --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiPager.kt @@ -0,0 +1,141 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.api.media.exceptions.search.NoNextPageException +import java.util.stream.IntStream + +/** + * A MultiPager combines several pagers of the same types, and merges them in some manner. + * Implementations of this abstract class require to implement which item is the next one, by choosing between each provided pager + * (eg. Implementation of MultiPager is MultiChronoContentPager which orders multiple pager contents by their datetime) + */ +abstract class MultiPager : IPager { + protected val _pagerLock = Object(); + + protected val _pagers : MutableList>; + protected val _subSinglePagers : MutableList>; + protected val _failedPagers: ArrayList> = arrayListOf(); + + private val _pageSize : Int = 9; + + private var _didInitialize = false; + + private var _currentResults : List = listOf(); + private var _currentResultExceptions: Map, Throwable> = mapOf(); + + val allowFailure: Boolean; + + val totalPagers: Int get() = _pagers.size; + + constructor(pagers : List>, allowFailure: Boolean = false) { + this.allowFailure = allowFailure; + _pagers = pagers.toMutableList(); + _subSinglePagers = _pagers.map { SingleItemPager(it) }.toMutableList(); + } + fun initialize() { + _currentResults = loadNextPage(true); + _didInitialize = true; + } + + override fun hasMorePages(): Boolean { + synchronized(_pagerLock) { + return _subSinglePagers.any { it.hasMoreItems() } || _pagers.any { it.hasMorePages() } + } + } + override fun nextPage() { + Logger.i(TAG, "Load next page"); + if(!_didInitialize) + throw IllegalStateException("Call initialize on MultiVideoPager before using it"); + loadNextPage(); + Logger.i(TAG, "New results: ${_currentResults.size}"); + } + override fun getResults(): List { + if(!_didInitialize) + throw IllegalStateException("Call initialize on MultiVideoPager before using it"); + return _currentResults; + } + fun getResultExceptions(): Map, Throwable> { + if(!_didInitialize) + throw IllegalStateException("Call initialize on MultiVideoPager before using it"); + return _currentResultExceptions; + } + + @Synchronized + private fun loadNextPage(isInitial: Boolean = false) : List { + synchronized(_pagerLock) { + if (_subSinglePagers.size == 0) + return listOf(); + } + if(!isInitial && !hasMorePages()) + throw NoNextPageException(); + + val results = ArrayList(); + val exceptions: MutableMap, Throwable> = mutableMapOf(); + for(i in IntStream.range(0, _pageSize)) { + val validPagers = synchronized(_pagerLock) { + _subSinglePagers.filter { !_failedPagers.contains(it.getPager()) && (it.hasMoreItems() || it.getPager().hasMorePages()) } + }; + val options: ArrayList> = arrayListOf(); + for (pager in validPagers) { + val item: T? = if (allowFailure) { + try { + pager.getCurrentItem(); + } catch (ex: NoNextPageException) { + //TODO: This should never happen, has to be fixed later + Logger.i(TAG, "Expected item from pager but no page found?"); + null; + } catch (ex: Throwable) { + Logger.e(TAG, "Failed to fetch page for pager, exception: ${ex.message}", ex); + _failedPagers.add(pager.getPager()); + exceptions.put(pager.getPager(), ex); + null; + } + } else { + try { + pager.getCurrentItem(); + } catch (ex: NoNextPageException) { + //TODO: This should never happen, has to be fixed later + Logger.i(TAG, "Expected item from pager but no page found?"); + null; + } + }; + if (item != null) + options.add(SelectionOption(pager, item)); + } + + if (options.size == 0) + break; + val bestIndex = selectItemIndex(options.toTypedArray()); + if (bestIndex >= 0) { + + val consumed = options[bestIndex].pager.consumeItem(); + if (consumed != null) + results.add(consumed); + } + } + _currentResults = results; + _currentResultExceptions = exceptions; + return _currentResults; + } + + protected abstract fun selectItemIndex(options : Array>) : Int; + + protected class SelectionOption(val pager : SingleItemPager, val item : T?); + + fun setExceptions(exs: Map, Throwable>) { + _currentResultExceptions = exs; + } + fun findPager(query: (IPager)->Boolean): IPager<*>? { + for(pager in _pagers) { + if(query(pager)) + return pager; + if(pager is MultiPager<*>) + return pager.findPager(query as (IPager) -> Boolean); + } + return null; + } + + companion object { + val TAG = "MultiPager"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiParallelPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiParallelPager.kt new file mode 100644 index 00000000..9c782e29 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiParallelPager.kt @@ -0,0 +1,170 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.api.media.exceptions.search.NoNextPageException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import java.util.stream.IntStream +import kotlin.system.measureTimeMillis + +/** + * Async MultiPager is a multipager that calls all pagers in a deferred (promised) manner. + * Unlike its normal counterpart which waits for results as they are needed. + * The benefit is that if multiple pagers need to request a new page, its done in parallel and awaited together. + * The downside is that pager results cannot be consumed based on their contents, as their contents may still be unknown. + * (eg. example Chronological pagers cannot be done without reordering the results every refresh, which causes bad UX) + */ +abstract class MultiParallelPager : IPager, IAsyncPager { + protected val _pagerLock = Object(); + + protected val _pagers : MutableList>; + protected val _subSinglePagers : MutableList>; + protected val _failedPagers: ArrayList> = arrayListOf(); + + private val _pageSize : Int = 9; + + private var _didInitialize = false; + + private var _currentResults : List = listOf(); + private var _currentResultExceptions: Map, Throwable> = mapOf(); + + val allowFailure: Boolean; + + val totalPagers: Int get() = _pagers.size; + + constructor(pagers : List>, allowFailure: Boolean = false) { + this.allowFailure = allowFailure; + _pagers = pagers.toMutableList(); + _subSinglePagers = _pagers.map { SingleAsyncItemPager(it) }.toMutableList(); + } + suspend fun initialize() { + withContext(Dispatchers.IO) { + _currentResults = loadNextPage(this, true); + } + _didInitialize = true; + } + + override fun hasMorePages(): Boolean { + synchronized(_pagerLock) { + return _subSinglePagers.any { it.hasMoreItems() } || _pagers.any { it.hasMorePages() } + } + } + + override fun nextPage() { + Logger.i(TAG, "Load next page"); + if(!_didInitialize) + throw IllegalStateException("Call initialize on MultiVideoPager before using it"); + runBlocking { loadNextPage(this) }; + Logger.i(TAG, "New results: ${_currentResults.size}"); + } + + override suspend fun nextPageAsync() { + Logger.i(TAG, "Load next page (async)"); + if(!_didInitialize) + throw IllegalStateException("Call initialize on MultiVideoPager before using it"); + withContext(Dispatchers.IO) { + loadNextPage(this); + } + Logger.i(TAG, "New results: ${_currentResults.size}"); + } + + override fun getResults(): List { + if(!_didInitialize) + throw IllegalStateException("Call initialize on MultiVideoPager before using it"); + return _currentResults; + } + fun getResultExceptions(): Map, Throwable> { + if(!_didInitialize) + throw IllegalStateException("Call initialize on MultiVideoPager before using it"); + return _currentResultExceptions; + } + + private suspend fun loadNextPage(scope: CoroutineScope, isInitial: Boolean = false) : List { + synchronized(_pagerLock) { + if (_subSinglePagers.size == 0) + return listOf(); + } + + if(!isInitial && !hasMorePages()) + throw NoNextPageException(); + + val results = ArrayList>(); + val exceptions: MutableMap, Throwable> = mutableMapOf(); + val timeForPage = measureTimeMillis { + for(i in IntStream.range(0, _pageSize)) { + val validPagers = synchronized(_pagerLock) { + _subSinglePagers.filter { !_failedPagers.contains(it.getPager()) && (it.hasMoreItems() || it.getPager().hasMorePages()) } + }; + val options: ArrayList> = arrayListOf(); + for (pager in validPagers) { + val item: Deferred? = if (allowFailure) { + try { + pager.getCurrentItem(scope); + } catch (ex: NoNextPageException) { + //TODO: This should never happen, has to be fixed later + Logger.i(TAG, "Expected item from pager but no page found?"); + null; + } catch (ex: Throwable) { + Logger.e(TAG, "Failed to fetch page for pager, exception: ${ex.message}", ex); + _failedPagers.add(pager.getPager()); + exceptions.put(pager.getPager(), ex); + null; + } + } else { + try { + pager.getCurrentItem(scope); + } catch (ex: NoNextPageException) { + //TODO: This should never happen, has to be fixed later + Logger.i(TAG, "Expected item from pager but no page found?"); + null; + } + }; + if (item != null) + options.add(SelectionOption(pager, item)); + } + + if (options.size == 0) + break; + val bestIndex = selectItemIndex(options.toTypedArray()); + if (bestIndex >= 0) { + + val consumed = options[bestIndex].pager.consumeItem(scope); + if (consumed != null) + results.add(consumed); + } + } + } + Logger.i(TAG, "Pager prepare in ${timeForPage}ms"); + val timeAwait = measureTimeMillis { + _currentResults = results.map { it.await() }.mapNotNull { it }; + }; + Logger.i(TAG, "Pager load in ${timeAwait}ms"); + + _currentResultExceptions = exceptions; + return _currentResults; + } + + protected abstract fun selectItemIndex(options : Array>) : Int; + + protected class SelectionOption(val pager : SingleAsyncItemPager, val item : Deferred?); + + fun setExceptions(exs: Map, Throwable>) { + _currentResultExceptions = exs; + } + fun findPager(query: (IPager)->Boolean): IPager<*>? { + for(pager in _pagers) { + if(query(pager)) + return pager; + if(pager is MultiParallelPager<*>) + return pager.findPager(query as (IPager) -> Boolean); + } + return null; + } + + companion object { + val TAG = "MultiPager"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiRefreshPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiRefreshPager.kt new file mode 100644 index 00000000..af18e817 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiRefreshPager.kt @@ -0,0 +1,92 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.api.media.structures.ReusablePager.Companion.asReusable +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.logging.Logger +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.runBlocking + +/** + * Refresh pager is a managed multiple pagers of which some are promised/deferred, and optionally inserts placeholdesr for not yet finished promised pagers. + * RefreshMultiPager has no inherit logic on how the pagers are read, and solely manages awaiting pagers, and "refreshing" them when new ones become available. + * The abstract recreatePager method is intended to implement the exact pager used that is given to then consumer. + * (Eg. RefreshDistributionContentPager returns the pagers as a equal distribution from each pager) + */ +abstract class MultiRefreshPager: IRefreshPager, IPager { + override val onPagerChanged: Event1> = Event1(); + override val onPagerError: Event1 = Event1(); + + private val _pagersReusable: MutableList>; + private var _currentPager: IPager; + private val _addPlaceholders = false; + private val _totalPagers: Int; + private val _placeHolderPagersPaired: Map?>, IPager>; + + private val _pending: MutableList?>>; + + constructor(pagers: List>, pendingPagers: List?>>, placeholderPagers: List>? = null) { + _pagersReusable = pagers.map { ReusablePager(it) }.toMutableList(); + _totalPagers = pagers.size + pendingPagers.size; + _placeHolderPagersPaired = placeholderPagers?.take(pendingPagers.size)?.mapIndexed { i, pager -> + return@mapIndexed Pair(pendingPagers[i], pager); + }?.toMap() ?: mapOf(); + _pending = pendingPagers.toMutableList(); + + for(pendingPager in pendingPagers) + pendingPager.invokeOnCompletion { error -> + synchronized(_pending) { + _pending.remove(pendingPager); + } + if(error != null) + onPagerError.emit(error); + else + updatePager(pendingPager.getCompleted()); + } + synchronized(_pagersReusable) { + _currentPager = recreatePager(getCurrentSubPagers()); + + if(_currentPager is MultiParallelPager<*>) + runBlocking { (_currentPager as MultiParallelPager).initialize(); }; + else if(_currentPager is MultiPager<*>) + (_currentPager as MultiPager).initialize(); + + onPagerChanged.emit(_currentPager); + } + } + + abstract fun recreatePager(pagers: List>): IPager; + + override fun hasMorePages(): Boolean = synchronized(_pagersReusable){ _currentPager.hasMorePages() }; + override fun nextPage() = synchronized(_pagersReusable){ _currentPager.nextPage() }; + override fun getResults(): List = synchronized(_pagersReusable){ _currentPager.getResults() }; + + private fun updatePager(pagerToAdd: IPager?) { + if(pagerToAdd == null) + return; + synchronized(_pagersReusable) { + Logger.i("RefreshMultiDistributionContentPager", "Received new pager for RefreshPager") + _pagersReusable.add(pagerToAdd.asReusable()); + + _currentPager = recreatePager(getCurrentSubPagers()); + + if(_currentPager is MultiParallelPager<*>) + runBlocking { (_currentPager as MultiParallelPager).initialize(); }; + else if(_currentPager is MultiPager<*>) + (_currentPager as MultiPager).initialize(); + + onPagerChanged.emit(_currentPager); + } + } + + private fun getCurrentSubPagers(): List> { + val reusableWindows = _pagersReusable.map { it.getWindow() as IPager }; + val placeholderWindows = synchronized(_pending) { + _placeHolderPagersPaired.filter { _pending.contains(it.key) }.values + } + return reusableWindows + placeholderWindows; + } + + override fun getCurrentPager(): IPager { + return synchronized(_pagersReusable) { _currentPager }; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/PlaceholderPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/PlaceholderPager.kt new file mode 100644 index 00000000..c0bd8d6e --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/PlaceholderPager.kt @@ -0,0 +1,25 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.api.media.models.contents.IPlatformContent + +/** + * A placeholder pager simply generates PlatformContent by some creator function. + */ +class PlaceholderPager : IPager { + private val _creator: ()->IPlatformContent; + private val _pageSize: Int; + + constructor(pageSize: Int, placeholderCreator: ()->IPlatformContent) { + _creator = placeholderCreator; + _pageSize = pageSize; + } + + override fun nextPage() {}; + override fun getResults(): List { + val pages = ArrayList(); + for(item in 1.._pageSize) + pages.add(_creator()); + return pages; + } + override fun hasMorePages(): Boolean = true; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/PlatformContentPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/PlatformContentPager.kt new file mode 100644 index 00000000..12127a88 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/PlatformContentPager.kt @@ -0,0 +1,39 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import kotlin.streams.toList + +/** + * Old wrapper for Platform Content + * Marked for deletion (?) + */ +class PlatformContentPager : IPager { + private val _items : List; + private var _page = 0; + private val _pageSize : Int; + private var _currentItems : List; + + constructor(items : List, itemsPerPage : Int = 20) { + _items = items; + _pageSize = itemsPerPage; + _currentItems = items.take(itemsPerPage).toList(); + } + + override fun hasMorePages(): Boolean { + return _items.size > (_page + 1) * _pageSize; + } + + override fun nextPage() { + _page++; + _currentItems = _items.stream() + .skip((_page * _pageSize).toLong()) + .toList() + .take(_pageSize) + .toList(); + } + + override fun getResults(): List { + return _currentItems; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/RefreshDedupContentPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/RefreshDedupContentPager.kt new file mode 100644 index 00000000..f57473ab --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/RefreshDedupContentPager.kt @@ -0,0 +1,36 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.constructs.Event1 + +class RefreshDedupContentPager: IRefreshPager, IPager { + private val _basePager: MultiRefreshPager; + private var _currentPage: IPager; + + override val onPagerChanged = Event1>(); + override val onPagerError = Event1(); + + + constructor(refreshPager: MultiRefreshPager, preferredPlatform: List? = null) : super() { + _basePager = refreshPager; + _currentPage = DedupContentPager(_basePager.getCurrentPager(), preferredPlatform); + _basePager.onPagerError.subscribe(onPagerError::emit); + _basePager.onPagerChanged.subscribe { + _currentPage = DedupContentPager(it, preferredPlatform); + onPagerChanged.emit(_currentPage); + }; + } + + override fun getCurrentPager(): IPager = _currentPage; + override fun hasMorePages(): Boolean { + return _basePager.hasMorePages(); + } + + override fun nextPage() { + return _basePager.nextPage(); + } + + override fun getResults(): List { + return _basePager.getResults(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/RefreshDistributionContentPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/RefreshDistributionContentPager.kt new file mode 100644 index 00000000..abce481f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/RefreshDistributionContentPager.kt @@ -0,0 +1,18 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import kotlinx.coroutines.Deferred + +/** + * A RefreshMultiPager that simply returns all respective pagers in equal distribution, optionally inserting PlaceholderPager results as provided for their respective promised pagers + * (Eg. Pager A is completed, Pager [B,C,D] are promised/deferred. placeholderPagers [1,2,3] will map B=>1, C=>2, D=>3 until promised pagers are completed) + * Uses wrapped MultiDistributionContentAsyncPager for inidivual pagers. + */ +class RefreshDistributionContentPager(pagers: List>, pendingPagers: List?>>, placeholderPagers: List>? = null) + : MultiRefreshPager(pagers, pendingPagers, placeholderPagers) { + + override fun recreatePager(pagers: List>): IPager { + return MultiDistributionContentParallelPager(pagers.associateWith { 1f }); + //return MultiDistributionContentPager(pagers.associateWith { 1f }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/ReusablePager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/ReusablePager.kt new file mode 100644 index 00000000..45f6aea5 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/ReusablePager.kt @@ -0,0 +1,98 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.logging.Logger + +/** + * A wrapper pager that stores previous pages of results, and provides child-pagers that read from the source. + * This allows for a pager to be re-used in various scenarios where previous results need to be reloaded. + * (Eg. Subscriptions feed uses it to batch requests, and respond multiple pagers with the same source without duplicate requests) + * A "Window" is effectively a pager that just reads previous results from the shared results, but when the end is reached, it will call nextPage on the parent if possible for new results. + * This allows multiple Windows to exist of the same pager, without messing with position, or duplicate requests + */ +class ReusablePager: INestedPager, IPager { + private val _pager: IPager; + val previousResults = arrayListOf(); + + constructor(subPager: IPager) { + this._pager = subPager; + synchronized(previousResults) { + previousResults.addAll(subPager.getResults()); + } + } + + override fun findPager(query: (IPager) -> Boolean): IPager? { + if(query(_pager)) + return _pager; + else if(_pager is INestedPager<*>) + return (_pager as INestedPager).findPager(query); + return null; + } + + override fun hasMorePages(): Boolean { + return _pager.hasMorePages(); + } + + override fun nextPage() { + _pager.nextPage(); + } + + override fun getResults(): List { + val results = _pager.getResults(); + synchronized(previousResults) { + previousResults.addAll(results); + } + return previousResults; + } + + fun getWindow(): Window { + return Window(this); + } + + + class Window: IPager, INestedPager { + private val _parent: ReusablePager; + private var _position: Int = 0; + private var _read: Int = 0; + + private var _currentResults: List; + + + constructor(parent: ReusablePager) { + _parent = parent; + synchronized(_parent.previousResults) { + _currentResults = _parent.previousResults.toList(); + _read += _currentResults.size; + } + } + + override fun hasMorePages(): Boolean { + return _parent.previousResults.size > _read || _parent.hasMorePages(); + } + + override fun nextPage() { + synchronized(_parent.previousResults) { + if(_parent.previousResults.size <= _read) { + _parent.nextPage(); + _parent.getResults(); + } + _currentResults = _parent.previousResults.drop(_read).toList(); + _read += _currentResults.size; + } + } + + override fun getResults(): List { + return _currentResults; + } + + override fun findPager(query: (IPager) -> Boolean): IPager? { + return _parent.findPager(query); + } + + } + + companion object { + fun IPager.asReusable(): ReusablePager { + return ReusablePager(this); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/SingleAsyncItemPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/SingleAsyncItemPager.kt new file mode 100644 index 00000000..4c0bc621 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/SingleAsyncItemPager.kt @@ -0,0 +1,113 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateApp +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlin.system.measureTimeMillis + +/** + * SingleItemPagers are used to wrap any IPager and consume items 1 at a time. + * Often used by MultiPagers to add items to a page one at a time from multiple pagers + * Unlike its non-async counterpart, It returns a Deferred item which is either constant, or a future page response if a nextPage has to be called on the base pager. + */ +class SingleAsyncItemPager { + private val _pager : IPager; + private var _currentResultPos : Int; + private var _currentPagerStartPos: Int = 0; + private var _currentPagerEndPos: Int = 0; + + private var _requestedPageItems: ArrayList?> = arrayListOf(); + + private var _isRequesting = false; + + constructor(pager: IPager) { + _pager = pager; + val results = _pager.getResults() + for(result in results) + _requestedPageItems.add(CompletableDeferred(result)); + _currentResultPos = 0; + _currentPagerEndPos = results.size; + } + + fun getPager() : IPager = _pager; + + fun hasMoreItems() : Boolean = _currentResultPos < _currentPagerEndPos; + + @Synchronized + fun getCurrentItem(scope: CoroutineScope) : Deferred? { + synchronized(_requestedPageItems) { + if (_currentResultPos >= _requestedPageItems.size) { + val startPos = fillDeferredUntil(_currentResultPos); + if(!_pager.hasMorePages()) { + completeRemainder { it?.complete(null) }; + } + if(_isRequesting) + return _requestedPageItems[_currentResultPos]; + _isRequesting = true; + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { + try { + Logger.i("SingleAsyncItemPager", "Started Pager"); + val timeForPage = measureTimeMillis { _pager.nextPage() }; + val newResults = _pager.getResults(); + Logger.i("SingleAsyncItemPager", "Finished Pager (${timeForPage}ms)"); + _currentPagerStartPos = _currentPagerEndPos; + _currentPagerEndPos = _currentPagerStartPos + newResults.size; + synchronized(_requestedPageItems) { + fillDeferredUntil(_currentPagerEndPos) + for (i in newResults.indices) + _requestedPageItems[_currentPagerStartPos + i]!!.complete(newResults[i]); + completeRemainder { + it?.complete(null); + }; + } + } + catch(ex: Throwable) { + Logger.e("SingleAsyncItemPager", "Pager exception", ex); + synchronized(_requestedPageItems) { + fillDeferredUntil(_currentPagerEndPos); + + completeRemainder { + it?.completeExceptionally(ex); + }; + } + } + finally { + synchronized(_requestedPageItems) { + _isRequesting = false; + } + } + } + + return _requestedPageItems[_currentResultPos]; + } + if (_requestedPageItems.size > _currentResultPos) + return _requestedPageItems[_currentResultPos]; + else return null; + } + } + + @Synchronized + fun consumeItem(scope: CoroutineScope) : Deferred? { + val result = getCurrentItem(scope); + _currentResultPos++; + return result; + } + + private fun fillDeferredUntil(i: Int): Int { + val startPos = _requestedPageItems.size; + for(i in _requestedPageItems.size..i) { + _requestedPageItems.add(CompletableDeferred()); + } + return startPos; + } + private fun completeRemainder(completer: (CompletableDeferred?)->Unit) { + synchronized(_requestedPageItems) { + for(i in _currentPagerEndPos until _requestedPageItems.size) + completer(_requestedPageItems[i]); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/SingleItemPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/SingleItemPager.kt new file mode 100644 index 00000000..30b3bd53 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/SingleItemPager.kt @@ -0,0 +1,46 @@ +package com.futo.platformplayer.api.media.structures + +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import kotlinx.coroutines.Deferred + +/** + * SingleItemPagers are used to wrap any IPager and consume items 1 at a time. + * Often used by MultiPagers to add items to a page one at a time from multiple pagers + */ +class SingleItemPager { + private val _pager : IPager; + private var _currentResult : List; + private var _currentResultPos : Int; + + + + + constructor(pager: IPager) { + _pager = pager; + _currentResult = _pager.getResults(); + _currentResultPos = 0; + } + + fun getPager() : IPager = _pager; + + fun hasMoreItems() : Boolean = _currentResultPos < _currentResult.size; + + @Synchronized + fun getCurrentItem() : T? { + if(_currentResultPos >= _currentResult.size) { + _pager.nextPage(); + _currentResult = _pager.getResults(); + _currentResultPos = 0; + } + if(_currentResult.size > _currentResultPos) + return _currentResult[_currentResultPos]; + else return null; + } + + @Synchronized + fun consumeItem() : T? { + val result = getCurrentItem(); + _currentResultPos++; + return result; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/background/BackgroundWorker.kt b/app/src/main/java/com/futo/platformplayer/background/BackgroundWorker.kt new file mode 100644 index 00000000..c98646dd --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/background/BackgroundWorker.kt @@ -0,0 +1,114 @@ +package com.futo.platformplayer.background + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import androidx.concurrent.futures.CallbackToFutureAdapter +import androidx.concurrent.futures.ResolvableFuture +import androidx.core.app.NotificationCompat +import androidx.work.CoroutineWorker +import androidx.work.ListenableWorker +import androidx.work.WorkerParameters +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.getNowDiffSeconds +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.Subscription +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StateSubscriptions +import com.futo.platformplayer.views.adapters.viewholders.TabViewHolder +import com.google.common.util.concurrent.ListenableFuture +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import java.time.OffsetDateTime + +class BackgroundWorker(private val appContext: Context, workerParams: WorkerParameters) : + CoroutineWorker(appContext, workerParams) { + override suspend fun doWork(): Result { + if(StateApp.instance.isMainActive) { + Logger.i("BackgroundWorker", "CANCELLED"); + return Result.success(); + } + var exception: Throwable? = null; + + StateApp.instance.startBackground(appContext, true, true) { + Logger.i("BackgroundWorker", "STARTED"); + val notificationManager = appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager; + val notificationChannel = NotificationChannel("backgroundWork", "Background Work", + NotificationManager.IMPORTANCE_HIGH).apply { + this.enableVibration(false); + this.setSound(null, null); + }; + notificationManager.createNotificationChannel(notificationChannel); + try { + doSubscriptionUpdating(notificationManager, notificationChannel); + } + catch(ex: Throwable) { + exception = ex; + Logger.e("BackgroundWorker", "FAILED: ${ex.message}", ex); + notificationManager.notify(14, NotificationCompat.Builder(appContext, notificationChannel.id) + .setSmallIcon(com.futo.platformplayer.R.drawable.foreground) + .setContentTitle("Grayjay") + .setContentText("Failed subscriptions update\n${ex.message}") + .setChannelId(notificationChannel.id).build()); + } + + } + + return if(exception == null) + Result.success() + else + Result.failure(); + } + + + suspend fun doSubscriptionUpdating(manager: NotificationManager, notificationChannel: NotificationChannel) { + val notif = NotificationCompat.Builder(appContext, notificationChannel.id) + .setSmallIcon(com.futo.platformplayer.R.drawable.foreground) + .setContentTitle("Grayjay") + .setContentText("Updating subscriptions...") + .setChannelId(notificationChannel.id) + .setProgress(1, 0, true); + + manager.notify(12, notif.build()); + + var lastNotifUpdate = OffsetDateTime.now(); + + val newSubChanges = hashSetOf(); + val newItems = mutableListOf(); + withContext(Dispatchers.IO) { + StateSubscriptions.instance.getSubscriptionsFeedWithExceptions(true, false,this, { progress, total -> + Logger.i("BackgroundWorker", "SUBSCRIPTION PROGRESS: ${progress}/${total}"); + + synchronized(manager) { + if (lastNotifUpdate.getNowDiffSeconds() > 1) { + notif.setContentText("Subscriptions (${progress}/${total})"); + notif.setProgress(total, progress, false); + manager.notify(12, notif.build()); + lastNotifUpdate = OffsetDateTime.now(); + } + } + }, { sub, content -> + synchronized(newSubChanges) { + if(!newSubChanges.contains(sub)) + newSubChanges.add(sub); + newItems.add(content); + } + }); + } + + manager.cancel(12); + + if(newItems.size > 0) + manager.notify(13, NotificationCompat.Builder(appContext, notificationChannel.id) + .setSmallIcon(com.futo.platformplayer.R.drawable.foreground) + .setContentTitle("Grayjay") + .setContentText("${newItems.size} new content from ${newSubChanges.size} creators") + .setChannelId(notificationChannel.id).build()); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/builders/DashBuilder.kt b/app/src/main/java/com/futo/platformplayer/builders/DashBuilder.kt new file mode 100644 index 00000000..3e1fcccd --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/builders/DashBuilder.kt @@ -0,0 +1,176 @@ +package com.futo.platformplayer.builders + +import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource +import com.futo.platformplayer.api.media.models.streams.sources.other.IStreamMetaDataSource +import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource + +class DashBuilder : XMLBuilder { + + constructor(durationS: Long, profile: String) { + writeXmlHeader(); + writeTag("MPD", mapOf( + Pair("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"), + Pair("xmlns", "urn:mpeg:dash:schema:mpd:2011"), + Pair("xsi:schemaLocation", "urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd"), + Pair("type", "static"), + Pair("mediaPresentationDuration", "PT${durationS}S"), + Pair("minBufferTime", "PT2S"), + Pair("profiles", profile) + ), false); + + //Temporary...always Period wrapped + writeTag("Period", mapOf(), false); + } + + //AdaptationSets + fun withAdaptationSet(parameters: Map, writeBody: (DashBuilder)->Unit) { + tag("AdaptationSet", parameters, { + writeBody(it as DashBuilder); + }); + } + + //Representation + fun withRepresentation(id: String, parameters: Map, writeBody: (DashBuilder)->Unit) { + val modParas = parameters.toMutableMap(); + modParas.put("id", id); + tag("Representation", modParas) { + writeBody(it as DashBuilder); + }; + } + fun withRepresentationOnDemand(id: String, audioSource: IAudioSource, audioUrl: String) { + if(audioSource !is IStreamMetaDataSource) + throw NotImplementedError("Currently onDemand dash only works with IStreamMetaDataSource"); + if (audioSource.streamMetaData == null) + throw Exception("Stream metadata information missing, the video will need to be redownloaded to be casted") + + withRepresentation(id, mapOf( + Pair("mimeType", audioSource.container), + Pair("codecs", audioSource.codec), + Pair("startWithSAP", "1"), + Pair("bandwidth", "100000") + ) + ) { + it.withSegmentBase( + audioUrl, + audioSource.streamMetaData!!.fileInitStart!!.toLong(), + audioSource.streamMetaData!!.fileInitEnd!!.toLong(), + audioSource.streamMetaData!!.fileIndexStart!!.toLong(), + audioSource.streamMetaData!!.fileIndexEnd!!.toLong() + ) + } + } + fun withRepresentationOnDemand(id: String, videoSource: IVideoSource, videoUrl: String) { + if(videoSource !is IStreamMetaDataSource || videoSource.streamMetaData == null) + throw NotImplementedError("Currently onDemand dash only works with IStreamMetaDataSource"); + if (videoSource.streamMetaData == null) + throw Exception("Stream metadata information missing, the video will need to be redownloaded to be casted") + + withRepresentation(id, mapOf( + Pair("mimeType", videoSource.container), + Pair("codecs", videoSource.codec), + Pair("width", videoSource.width.toString()), + Pair("height", videoSource.height.toString()), + Pair("startWithSAP", "1"), + Pair("bandwidth", "100000") + ) + ) { + it.withSegmentBase( + videoUrl, + videoSource.streamMetaData!!.fileInitStart!!.toLong(), + videoSource.streamMetaData!!.fileInitEnd!!.toLong(), + videoSource.streamMetaData!!.fileIndexStart!!.toLong(), + videoSource.streamMetaData!!.fileIndexEnd!!.toLong() + ) + } + } + + fun withRepresentationOnDemand(id: String, subtitleSource: ISubtitleSource, subtitleUrl: String) { + withRepresentation(id, mapOf( + Pair("mimeType", subtitleSource.format ?: "text/vtt"), + Pair("startWithSAP", "1"), + Pair("bandwidth", "1000") + )) { + it.withBaseURL(subtitleUrl) + } + } + + fun withBaseURL(url: String) { + valueTag("BaseURL", url) + } + + //Segments + fun withSegmentBase(url: String, initStart: Long, initEnd: Long, segStart: Long, segEnd: Long) { + valueTag("BaseURL", url); + + tag("SegmentBase", mapOf(Pair("indexRange", "${segStart}-${segEnd}"))) { + tagClosed("Initialization", Pair("sourceURL", url), Pair("range", "${initStart}-${initEnd}")); + } + } + + + override fun build() : String { + writeCloseTag("Period"); + writeCloseTag("MPD"); + + return super.build(); + } + + + companion object{ + val PROFILE_MAIN = "urn:mpeg:dash:profile:isoff-main:2011"; + val PROFILE_ON_DEMAND = "urn:mpeg:dash:profile:isoff-on-demand:2011"; + + fun generateOnDemandDash(vidSource: IVideoSource?, vidUrl: String?, audioSource: IAudioSource?, audioUrl: String?, subtitleSource: ISubtitleSource?, subtitleUrl: String?) : String { + val duration = vidSource?.duration ?: audioSource?.duration; + if (duration == null) { + throw Exception("Either video or audio source needs to be set."); + } + + val dashBuilder = DashBuilder(duration, PROFILE_ON_DEMAND); + + //Audio + if(audioSource != null && audioUrl != null) { + dashBuilder.withAdaptationSet(mapOf( + Pair("mimeType", audioSource.container), + Pair("codecs", audioSource.codec), + Pair("subsegmentAlignment", "true"), + Pair("subsegmentStartsWithSAP", "1") + )) { + //TODO: Verify if & really should be replaced like this? + it.withRepresentationOnDemand("1", audioSource, audioUrl.replace("&", "&")); + } + } + // Subtitles + if (subtitleSource != null && subtitleUrl != null) { + dashBuilder.withAdaptationSet( + mapOf( + Pair("mimeType", subtitleSource.format ?: "text/vtt"), + Pair("lang", "en"), + Pair("default", "true") + ) + ) { + //TODO: Verify if & really should be replaced like this? + it.withRepresentationOnDemand("1", subtitleSource, subtitleUrl.replace("&", "&")) + } + } + //Video + if (vidSource != null && vidUrl != null) { + dashBuilder.withAdaptationSet( + mapOf( + Pair("mimeType", vidSource.container), + Pair("codecs", vidSource.codec), + Pair("subsegmentAlignment", "true"), + Pair("subsegmentStartsWithSAP", "1") + ) + ) { + it.withRepresentationOnDemand("1", vidSource, vidUrl.replace("&", "&")); + } + } + + return dashBuilder.build(); + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/builders/HlsBuilder.kt b/app/src/main/java/com/futo/platformplayer/builders/HlsBuilder.kt new file mode 100644 index 00000000..2744e8a0 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/builders/HlsBuilder.kt @@ -0,0 +1,37 @@ +package com.futo.platformplayer.builders + +import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource +import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource +import java.io.PrintWriter +import java.io.StringWriter + +class HlsBuilder { + companion object{ + fun generateOnDemandHLS(vidSource: IVideoSource, vidUrl: String, audioSource: IAudioSource?, audioUrl: String?, subtitleSource: ISubtitleSource?, subtitleUrl: String?): String { + val hlsBuilder = StringWriter() + PrintWriter(hlsBuilder).use { writer -> + writer.println("#EXTM3U") + + // Audio + if (audioSource != null && audioUrl != null) { + val audioFormat = audioSource.container.substringAfter("/") + writer.println("#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audio\",LANGUAGE=\"en\",NAME=\"English\",AUTOSELECT=YES,DEFAULT=YES,URI=\"${audioUrl.replace("&", "&")}\",FORMAT=\"$audioFormat\"") + } + + // Subtitles + if (subtitleSource != null && subtitleUrl != null) { + val subtitleFormat = subtitleSource.format ?: "text/vtt" + writer.println("#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",LANGUAGE=\"en\",NAME=\"English\",AUTOSELECT=YES,DEFAULT=YES,URI=\"${subtitleUrl.replace("&", "&")}\",FORMAT=\"$subtitleFormat\"") + } + + // Video + val videoFormat = vidSource.container.substringAfter("/") + writer.println("#EXT-X-STREAM-INF:BANDWIDTH=100000,CODECS=\"${vidSource.codec}\",RESOLUTION=${vidSource.width}x${vidSource.height}${if (audioSource != null) ",AUDIO=\"audio\"" else ""}${if (subtitleSource != null) ",SUBTITLES=\"subs\"" else ""},FORMAT=\"$videoFormat\"") + writer.println(vidUrl.replace("&", "&")) + } + + return hlsBuilder.toString() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/builders/XMLBuilder.kt b/app/src/main/java/com/futo/platformplayer/builders/XMLBuilder.kt new file mode 100644 index 00000000..16dc4170 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/builders/XMLBuilder.kt @@ -0,0 +1,59 @@ +package com.futo.platformplayer.builders + +import java.io.StringWriter + +open class XMLBuilder { + protected val writer = StringWriter(); + private var _indentation = 0; + + fun writeXmlHeader(version: String = "1.0", encoding: String = "UTF-8") { + writer.write("\n"); + } + fun tagClosed(tagName: String, vararg parameters: Pair) { tagClosed(tagName, parameters.toMap()) } + fun tagClosed(tagName: String, parameters: Map) { + writeTag(tagName, parameters, true); + } + fun tag(tagName: String, parameters: Map, fill: (XMLBuilder)->Unit) { + writeTag(tagName, parameters, false); + fill(this); + writeCloseTag(tagName); + } + fun valueTag(tagName: String, value: String){ valueTag(tagName, mapOf(), value); } + fun valueTag(tagName: String, parameters: Map, value: String) { + writeTag(tagName, parameters, false, false); + writer.write(value); + writeCloseTag(tagName, false); + } + fun value(value: String) { + writeIndentation(_indentation); + writer.write(value + "\n"); + } + + protected fun writeTag(tagName: String, parameters: Map = mapOf(), closed: Boolean = true, withNewLine: Boolean = true) { + writeIndentation(_indentation) + writer.write("<${tagName}"); + for(parameter in parameters) + writer.write(" ${parameter.key}=\"${parameter.value}\""); + + if(closed) + writer.write("/>"); + else { + writer.write(">"); + _indentation++; + } + if(withNewLine) + writer.write("\n"); + } + protected fun writeCloseTag(tagName: String, withIndentation: Boolean = true) { + _indentation--; + if(withIndentation) + writeIndentation(_indentation); + writer.write("\n"); + } + protected fun writeIndentation(level: Int) { + writer.write("".padStart(level * 3, ' ')); + } + open fun build() : String { + return writer.toString(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/cache/ChannelContentCache.kt b/app/src/main/java/com/futo/platformplayer/cache/ChannelContentCache.kt new file mode 100644 index 00000000..d25333c9 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/cache/ChannelContentCache.kt @@ -0,0 +1,153 @@ +package com.futo.platformplayer.cache + +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.video.SerializedPlatformContent +import com.futo.platformplayer.api.media.structures.DedupContentPager +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.api.media.structures.PlatformContentPager +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.polycentric.PolycentricCache +import com.futo.platformplayer.resolveChannelUrl +import com.futo.platformplayer.serializers.PlatformContentSerializer +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StateSubscriptions +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.toSafeFileName +import com.futo.polycentric.core.toUrl +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class ChannelContentCache { + val _channelCacheDir = FragmentedStorage.getOrCreateDirectory("channelCache"); + val _channelContents = HashMap(_channelCacheDir.listFiles() + .filter { it.isDirectory } + .associate { Pair(it.name, FragmentedStorage.storeJson(_channelCacheDir, it.name, PlatformContentSerializer()) + .withoutBackup() + .load()) }); + + fun getChannelCachePager(channelUrl: String): PlatformContentPager { + val validID = channelUrl.toSafeFileName(); + + val validStores = _channelContents + .filter { it.key == validID } + .map { it.value }; + + val items = validStores.flatMap { it.getItems() } + .sortedByDescending { it.datetime }; + return PlatformContentPager(items, Math.min(150, items.size)); + } + fun getSubscriptionCachePager(): DedupContentPager { + val subs = StateSubscriptions.instance.getSubscriptions(); + val allUrls = subs.map { + val otherUrls = PolycentricCache.instance.getCachedProfile(it.channel.url)?.profile?.ownedClaims?.mapNotNull { c -> c.claim.resolveChannelUrl() } ?: listOf(); + if(!otherUrls.contains(it.channel.url)) + return@map listOf(listOf(it.channel.url), otherUrls).flatten(); + else + return@map otherUrls; + }.flatten().distinct(); + val validSubIds = allUrls.map { it.toSafeFileName() }.toHashSet(); + + val validStores = _channelContents + .filter { validSubIds.contains(it.key) } + .map { it.value }; + + val items = validStores.flatMap { it.getItems() } + .sortedByDescending { it.datetime }; + + return DedupContentPager(PlatformContentPager(items, Math.min(150, items.size)), StatePlatform.instance.getEnabledClients().map { it.id }); + } + + fun cacheVideos(contents: List): List { + return contents.filter { cacheContent(it) }; + } + fun cacheContent(content: IPlatformContent, doUpdate: Boolean = false): Boolean { + if(content.author.url.isEmpty()) + return false; + + val channelId = content.author.url.toSafeFileName(); + val store = synchronized(_channelContents) { + var channelStore = _channelContents.get(channelId); + if(channelStore == null) { + Logger.i(TAG, "New Subscription Cache for channel ${content.author.name}"); + channelStore = FragmentedStorage.storeJson(_channelCacheDir, channelId, PlatformContentSerializer()).load(); + _channelContents.put(channelId, channelStore); + } + return@synchronized channelStore; + } + val serialized = SerializedPlatformContent.fromContent(content); + val existing = store.findItems { it.url == content.url }; + + if(existing.isEmpty() || doUpdate) { + if(existing.isNotEmpty()) + existing.forEach { store.delete(it) }; + + store.save(serialized); + } + + return existing.isEmpty(); + } + + companion object { + private val TAG = "ChannelCache"; + + private val _lock = Object(); + private var _instance: ChannelContentCache? = null; + val instance: ChannelContentCache get() { + synchronized(_lock) { + if(_instance == null) + _instance = ChannelContentCache(); + return _instance!!; + } + } + + fun cachePagerResults(scope: CoroutineScope, pager: IPager, onNewCacheHit: ((IPlatformContent)->Unit)? = null): IPager { + return ChannelVideoCachePager(pager, scope, onNewCacheHit); + } + } + + class ChannelVideoCachePager(val pager: IPager, private val scope: CoroutineScope, private val onNewCacheItem: ((IPlatformContent)->Unit)? = null): IPager { + + init { + val results = pager.getResults(); + + Logger.i(TAG, "Caching ${results.size} subscription initial results"); + scope.launch(Dispatchers.IO) { + try { + val newCacheItems = instance.cacheVideos(results); + if(onNewCacheItem != null) + newCacheItems.forEach { onNewCacheItem!!(it) } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to cache videos.", e); + } + } + } + + override fun hasMorePages(): Boolean { + return pager.hasMorePages(); + } + + override fun nextPage() { + pager.nextPage(); + val results = pager.getResults(); + + Logger.i(TAG, "Caching ${results.size} subscription results"); + scope.launch(Dispatchers.IO) { + try { + val newCacheItems = instance.cacheVideos(results); + if(onNewCacheItem != null) + newCacheItems.forEach { onNewCacheItem!!(it) } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to cache videos.", e); + } + } + } + + override fun getResults(): List { + val results = pager.getResults(); + + return results; + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/casting/AirPlayCastingDevice.kt b/app/src/main/java/com/futo/platformplayer/casting/AirPlayCastingDevice.kt new file mode 100644 index 00000000..e8a8a573 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/casting/AirPlayCastingDevice.kt @@ -0,0 +1,305 @@ +package com.futo.platformplayer.casting + +import android.os.Looper +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.casting.models.FastCastSetVolumeMessage +import com.futo.platformplayer.getConnectedSocket +import com.futo.platformplayer.models.CastingDeviceInfo +import com.futo.platformplayer.toInetAddress +import kotlinx.coroutines.* +import java.net.InetAddress +import java.util.UUID + +class AirPlayCastingDevice : CastingDevice { + //See for more info: https://nto.github.io/AirPlay + + override val protocol: CastProtocolType get() = CastProtocolType.AIRPLAY; + override val isReady: Boolean get() = name != null && addresses != null && addresses?.isNotEmpty() == true && port != 0; + override var usedRemoteAddress: InetAddress? = null; + override var localAddress: InetAddress? = null; + override val canSetVolume: Boolean get() = false; + + var addresses: Array? = null; + var port: Int = 0; + + private var _scopeIO: CoroutineScope? = null; + private var _started: Boolean = false; + private var _sessionId: String? = null; + private val _client = ManagedHttpClient(); + + constructor(name: String, addresses: Array, port: Int) : super() { + this.name = name; + this.addresses = addresses; + this.port = port; + } + + constructor(deviceInfo: CastingDeviceInfo) : super() { + this.name = deviceInfo.name; + this.addresses = deviceInfo.addresses.map { a -> a.toInetAddress() }.filterNotNull().toTypedArray(); + this.port = deviceInfo.port; + } + + override fun getAddresses(): List { + return addresses?.toList() ?: listOf(); + } + + override fun loadVideo(streamType: String, contentType: String, contentId: String, resumePosition: Double, duration: Double) { + if (invokeInIOScopeIfRequired({ loadVideo(streamType, contentType, contentId, resumePosition, duration) })) { + return; + } + + Logger.i(FastCastCastingDevice.TAG, "Start streaming (streamType: $streamType, contentType: $contentType, contentId: $contentId, resumePosition: $resumePosition, duration: $duration)"); + + time = resumePosition; + if (resumePosition > 0.0) { + val pos = resumePosition / duration; + Logger.i(TAG, "resumePosition: $resumePosition, duration: ${duration}, pos: $pos") + post("play", "text/parameters", "Content-Location: $contentId\r\nStart-Position: $pos"); + } else { + post("play", "text/parameters", "Content-Location: $contentId\r\nStart-Position: 0"); + } + } + + override fun loadContent(contentType: String, content: String, resumePosition: Double, duration: Double) { + throw NotImplementedError(); + } + + override fun seekVideo(timeSeconds: Double) { + if (invokeInIOScopeIfRequired({ seekVideo(timeSeconds) })) { + return; + } + + post("scrub?position=${timeSeconds}"); + } + + override fun resumeVideo() { + if (invokeInIOScopeIfRequired(::resumeVideo)) { + return; + } + + isPlaying = true; + post("rate?value=1.000000"); + } + + override fun pauseVideo() { + if (invokeInIOScopeIfRequired(::pauseVideo)) { + return; + } + + isPlaying = false; + post("rate?value=0.000000"); + } + + override fun stopVideo() { + if (invokeInIOScopeIfRequired(::stopVideo)) { + return; + } + + post("stop"); + } + + override fun stopCasting() { + if (invokeInIOScopeIfRequired(::stopCasting)) { + return; + } + + post("stop"); + stop(); + } + + override fun start() { + val adrs = addresses ?: return; + if (_started) { + return; + } + + _started = true; + _scopeIO?.cancel(); + _scopeIO = CoroutineScope(Dispatchers.IO); + + Logger.i(TAG, "Starting..."); + + _scopeIO?.launch { + try { + connectionState = CastConnectionState.CONNECTING; + + while (_scopeIO?.isActive == true) { + try { + val connectedSocket = getConnectedSocket(adrs.toList(), port); + if (connectedSocket == null) { + delay(3000); + continue; + } + + usedRemoteAddress = connectedSocket.inetAddress; + localAddress = connectedSocket.localAddress; + connectedSocket.close(); + _sessionId = UUID.randomUUID().toString(); + break; + } catch (e: Throwable) { + Logger.w(TAG, "Failed to get setup initial connection to AirPlay device.", e) + } + } + + while (_scopeIO?.isActive == true) { + try { + val progressInfo = getProgress(); + if (progressInfo == null) { + connectionState = CastConnectionState.CONNECTING; + Logger.i(TAG, "Failed to retrieve progress from AirPlay device."); + delay(1000); + continue; + } + + connectionState = CastConnectionState.CONNECTED; + delay(1000); + + val progressIndex = progressInfo.lowercase().indexOf("position: "); + if (progressIndex == -1) { + continue; + } + + val progress = progressInfo.substring(progressIndex + "position: ".length).toDoubleOrNull() ?: continue; + + time = progress; + } catch (e: Throwable) { + Logger.w(TAG, "Failed to get server info from AirPlay device.", e) + } + } + } catch (e: Throwable) { + Logger.w(TAG, "Failed to setup AirPlay device connection.", e) + } + }; + + Logger.i(TAG, "Started."); + } + + override fun stop() { + Logger.i(TAG, "Stopping..."); + connectionState = CastConnectionState.DISCONNECTED; + + usedRemoteAddress = null; + localAddress = null; + _started = false; + _scopeIO?.cancel(); + _scopeIO = null; + } + + override fun getDeviceInfo(): CastingDeviceInfo { + return CastingDeviceInfo(name!!, CastProtocolType.AIRPLAY, addresses!!.filter { a -> a.hostAddress != null }.map { a -> a.hostAddress!! }.toTypedArray(), port); + } + + private fun getProgress(): String? { + val info = get("scrub"); + Logger.i(TAG, "Progress: ${info ?: "null"}"); + return info; + } + + private fun getPlaybackInfo(): String? { + val playbackInfo = get("playback-info"); + Logger.i(TAG, "Playback info: ${playbackInfo ?: "null"}"); + return playbackInfo; + } + + private fun getServerInfo(): String? { + val serverInfo = get("server-info"); + Logger.i(TAG, "Server info: ${serverInfo ?: "null"}"); + return serverInfo; + } + + private fun post(path: String): Boolean { + try { + val sessionId = _sessionId ?: return false; + + val headers = hashMapOf( + "X-Apple-Device-ID" to "0xdc2b61a0ce79", + "User-Agent" to "MediaControl/1.0", + "Content-Length" to "0", + "X-Apple-Session-ID" to sessionId + ); + + val url = "http://${usedRemoteAddress}:${port}/${path}"; + + Logger.i(TAG, "POST $url"); + val response = _client.post(url, headers); + if (!response.isOk) { + return false; + } + + return true; + } catch (e: Throwable) { + Logger.w(TAG, "Failed to POST $path"); + return false; + } + } + + private fun post(path: String, contentType: String, body: String): Boolean { + try { + val sessionId = _sessionId ?: return false; + + val headers = hashMapOf( + "X-Apple-Device-ID" to "0xdc2b61a0ce79", + "User-Agent" to "MediaControl/1.0", + "X-Apple-Session-ID" to sessionId, + "Content-Type" to contentType + ); + + val url = "http://${usedRemoteAddress}:${port}/${path}"; + + Logger.i(TAG, "POST $url:\n$body"); + val response = _client.post(url, body, headers); + if (!response.isOk) { + return false; + } + + return true; + } catch (e: Throwable) { + Logger.w(TAG, "Failed to POST $path $body"); + return false; + } + } + + private fun get(path: String): String? { + val sessionId = _sessionId ?: return null; + + try { + val headers = hashMapOf( + "X-Apple-Device-ID" to "0xdc2b61a0ce79", + "Content-Length" to "0", + "User-Agent" to "MediaControl/1.0", + "X-Apple-Session-ID" to sessionId + ); + + val url = "http://${usedRemoteAddress}:${port}/${path}"; + + Logger.i(TAG, "GET $url"); + val response = _client.get(url, headers); + if (!response.isOk) { + return null; + } + + if (response.body == null) { + return null; + } + + return response.body.string(); + } catch (e: Throwable) { + Logger.w(TAG, "Failed to GET $path"); + return null; + } + } + + private fun invokeInIOScopeIfRequired(action: () -> Unit): Boolean { + if(Looper.getMainLooper().thread == Thread.currentThread()) { + _scopeIO?.launch { action(); } + return true; + } + + return false; + } + + companion object { + val TAG = "AirPlayCastingDevice"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/casting/CastingDevice.kt b/app/src/main/java/com/futo/platformplayer/casting/CastingDevice.kt new file mode 100644 index 00000000..66a655be --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/casting/CastingDevice.kt @@ -0,0 +1,94 @@ +package com.futo.platformplayer.casting + +import android.content.Context +import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.getNowDiffMiliseconds +import com.futo.platformplayer.models.CastingDeviceInfo +import java.net.InetAddress +import java.time.OffsetDateTime + +enum class CastConnectionState { + DISCONNECTED, + CONNECTING, + CONNECTED +} + +enum class CastProtocolType { + CHROMECAST, + AIRPLAY, + FASTCAST +} + +abstract class CastingDevice { + abstract val protocol: CastProtocolType; + abstract val isReady: Boolean; + abstract var usedRemoteAddress: InetAddress?; + abstract var localAddress: InetAddress?; + abstract val canSetVolume: Boolean; + + var name: String? = null; + var isPlaying: Boolean = false + set(value) { + val changed = value != field; + field = value; + if (changed) { + onPlayChanged.emit(value); + } + }; + var timeReceivedAt: OffsetDateTime = OffsetDateTime.now() + private set; + var time: Double = 0.0 + set(value) { + val changed = value != field; + field = value; + if (changed) { + timeReceivedAt = OffsetDateTime.now(); + onTimeChanged.emit(value); + } + }; + var volume: Double = 1.0 + set(value) { + val changed = value != field; + field = value; + if (changed) { + onVolumeChanged.emit(value); + } + }; + val expectedCurrentTime: Double + get() { + val diff = timeReceivedAt.getNowDiffMiliseconds().toDouble() / 1000.0; + return time + diff; + }; + var connectionState: CastConnectionState = CastConnectionState.DISCONNECTED + set(value) { + val changed = value != field; + field = value; + + if (changed) { + onConnectionStateChanged.emit(value); + } + }; + + var onConnectionStateChanged = Event1(); + var onPlayChanged = Event1(); + var onTimeChanged = Event1(); + var onVolumeChanged = Event1(); + + abstract fun stopCasting(); + + abstract fun seekVideo(timeSeconds: Double); + abstract fun stopVideo(); + abstract fun pauseVideo(); + abstract fun resumeVideo(); + abstract fun loadVideo(streamType: String, contentType: String, contentId: String, resumePosition: Double, duration: Double); + abstract fun loadContent(contentType: String, content: String, resumePosition: Double, duration: Double); + open fun changeVolume(volume: Double) { throw NotImplementedError() } + + abstract fun start(); + abstract fun stop(); + + abstract fun getDeviceInfo(): CastingDeviceInfo; + + abstract fun getAddresses(): List; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/casting/ChomecastCastingDevice.kt b/app/src/main/java/com/futo/platformplayer/casting/ChomecastCastingDevice.kt new file mode 100644 index 00000000..69b447e1 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/casting/ChomecastCastingDevice.kt @@ -0,0 +1,606 @@ +package com.futo.platformplayer.casting + +import android.os.Looper +import android.util.Log +import com.futo.platformplayer.casting.models.FastCastSetVolumeMessage +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.getConnectedSocket +import com.futo.platformplayer.models.CastingDeviceInfo +import com.futo.platformplayer.protos.DeviceAuthMessageOuterClass +import com.futo.platformplayer.toHexString +import com.futo.platformplayer.toInetAddress +import kotlinx.coroutines.* +import org.json.JSONObject +import java.io.DataInputStream +import java.io.DataOutputStream +import java.io.IOException +import java.net.InetAddress +import java.security.cert.X509Certificate +import javax.net.ssl.SSLContext +import javax.net.ssl.SSLSocket +import javax.net.ssl.TrustManager +import javax.net.ssl.X509TrustManager + +class ChromecastCastingDevice : CastingDevice { + //See for more info: https://developers.google.com/cast/docs/media/messages + + override val protocol: CastProtocolType get() = CastProtocolType.CHROMECAST; + override val isReady: Boolean get() = name != null && addresses != null && addresses?.isNotEmpty() == true && port != 0; + override var usedRemoteAddress: InetAddress? = null; + override var localAddress: InetAddress? = null; + override val canSetVolume: Boolean get() = true; + + var addresses: Array? = null; + var port: Int = 0; + + private var _streamType: String? = null; + private var _contentType: String? = null; + private var _contentId: String? = null; + + private var _socket: SSLSocket? = null; + private var _outputStream: DataOutputStream? = null; + private var _inputStream: DataInputStream? = null; + private var _scopeIO: CoroutineScope? = null; + private var _requestId = 1; + private var _started: Boolean = false; + private var _sessionId: String? = null; + private var _transportId: String? = null; + private var _launching = false; + private var _mediaSessionId: Int? = null; + + constructor(name: String, addresses: Array, port: Int) : super() { + this.name = name; + this.addresses = addresses; + this.port = port; + } + + constructor(deviceInfo: CastingDeviceInfo) : super() { + this.name = deviceInfo.name; + this.addresses = deviceInfo.addresses.map { a -> a.toInetAddress() }.filterNotNull().toTypedArray(); + this.port = deviceInfo.port; + } + + override fun getAddresses(): List { + return addresses?.toList() ?: listOf(); + } + + override fun loadVideo(streamType: String, contentType: String, contentId: String, resumePosition: Double, duration: Double) { + if (invokeInIOScopeIfRequired({ loadVideo(streamType, contentType, contentId, resumePosition, duration) })) { + return; + } + + Logger.i(FastCastCastingDevice.TAG, "Start streaming (streamType: $streamType, contentType: $contentType, contentId: $contentId, resumePosition: $resumePosition, duration: $duration)"); + + time = resumePosition; + _streamType = streamType; + _contentType = contentType; + _contentId = contentId; + + playVideo(); + } + + override fun loadContent(contentType: String, content: String, resumePosition: Double, duration: Double) { + //TODO: Can maybe be implemented by sending data:contentType,base64... + throw NotImplementedError(); + } + + private fun connectMediaChannel(transportId: String) { + val connectObject = JSONObject(); + connectObject.put("type", "CONNECT"); + connectObject.put("connType", 0); + sendChannelMessage("sender-0", transportId, "urn:x-cast:com.google.cast.tp.connection", connectObject.toString()); + } + + private fun requestMediaStatus() { + val transportId = _transportId ?: return; + + val loadObject = JSONObject(); + loadObject.put("type", "GET_STATUS"); + loadObject.put("requestId", _requestId++); + sendChannelMessage("sender-0", transportId, "urn:x-cast:com.google.cast.media", loadObject.toString()); + } + + private fun playVideo() { + val transportId = _transportId ?: return; + val contentId = _contentId ?: return; + val streamType = _streamType ?: return; + val contentType = _contentType ?: return; + + val loadObject = JSONObject(); + loadObject.put("type", "LOAD"); + + val mediaObject = JSONObject(); + mediaObject.put("contentId", contentId); + mediaObject.put("streamType", streamType); + mediaObject.put("contentType", contentType); + + if (time > 0.0) { + val seekTime = time; + loadObject.put("currentTime", seekTime); + } + + loadObject.put("media", mediaObject); + loadObject.put("requestId", _requestId++); + + + //TODO: This replace is necessary to get rid of backward slashes added by the JSON Object serializer + val json = loadObject.toString().replace("\\/","/"); + sendChannelMessage("sender-0", transportId, "urn:x-cast:com.google.cast.media", json); + } + + override fun changeVolume(volume: Double) { + if (invokeInIOScopeIfRequired({ changeVolume(volume) })) { + return; + } + + this.volume = volume + val setVolumeObject = JSONObject(); + setVolumeObject.put("type", "SET_VOLUME"); + + val volumeObject = JSONObject(); + volumeObject.put("level", volume) + setVolumeObject.put("volume", volumeObject); + + setVolumeObject.put("requestId", _requestId++); + sendChannelMessage("sender-0", "receiver-0", "urn:x-cast:com.google.cast.receiver", setVolumeObject.toString()); + } + + override fun seekVideo(timeSeconds: Double) { + if (invokeInIOScopeIfRequired({ seekVideo(timeSeconds) })) { + return; + } + + val transportId = _transportId ?: return; + val mediaSessionId = _mediaSessionId ?: return; + + val loadObject = JSONObject(); + loadObject.put("type", "SEEK"); + loadObject.put("mediaSessionId", mediaSessionId); + loadObject.put("requestId", _requestId++); + loadObject.put("currentTime", timeSeconds); + sendChannelMessage("sender-0", transportId, "urn:x-cast:com.google.cast.media", loadObject.toString()); + } + + override fun resumeVideo() { + if (invokeInIOScopeIfRequired(::resumeVideo)) { + return; + } + + val transportId = _transportId ?: return; + val mediaSessionId = _mediaSessionId ?: return; + + val loadObject = JSONObject(); + loadObject.put("type", "PLAY"); + loadObject.put("mediaSessionId", mediaSessionId); + loadObject.put("requestId", _requestId++); + sendChannelMessage("sender-0", transportId, "urn:x-cast:com.google.cast.media", loadObject.toString()); + } + + override fun pauseVideo() { + if (invokeInIOScopeIfRequired(::pauseVideo)) { + return; + } + + val transportId = _transportId ?: return; + val mediaSessionId = _mediaSessionId ?: return; + + val loadObject = JSONObject(); + loadObject.put("type", "PAUSE"); + loadObject.put("mediaSessionId", mediaSessionId); + loadObject.put("requestId", _requestId++); + sendChannelMessage("sender-0", transportId, "urn:x-cast:com.google.cast.media", loadObject.toString()); + } + + override fun stopVideo() { + if (invokeInIOScopeIfRequired(::stopVideo)) { + return; + } + + val transportId = _transportId ?: return; + val mediaSessionId = _mediaSessionId ?: return; + _contentId = null; + _contentType = null; + _streamType = null; + + val loadObject = JSONObject(); + loadObject.put("type", "STOP"); + loadObject.put("mediaSessionId", mediaSessionId); + loadObject.put("requestId", _requestId++); + sendChannelMessage("sender-0", transportId, "urn:x-cast:com.google.cast.media", loadObject.toString()); + } + + private fun launchPlayer() { + if (invokeInIOScopeIfRequired(::launchPlayer)) { + return; + } + + val launchObject = JSONObject(); + launchObject.put("type", "LAUNCH"); + launchObject.put("appId", "CC1AD845"); + launchObject.put("requestId", _requestId++); + sendChannelMessage("sender-0", "receiver-0", "urn:x-cast:com.google.cast.receiver", launchObject.toString()); + } + + private fun getStatus() { + if (invokeInIOScopeIfRequired(::getStatus)) { + return; + } + + val launchObject = JSONObject(); + launchObject.put("type", "GET_STATUS"); + launchObject.put("requestId", _requestId++); + sendChannelMessage("sender-0", "receiver-0", "urn:x-cast:com.google.cast.receiver", launchObject.toString()); + } + + private fun invokeInIOScopeIfRequired(action: () -> Unit): Boolean { + if(Looper.getMainLooper().thread == Thread.currentThread()) { + _scopeIO?.launch { action(); } + return true; + } + + return false; + } + + override fun stopCasting() { + if (invokeInIOScopeIfRequired(::stopCasting)) { + return; + } + + val sessionId = _sessionId; + if (sessionId != null) { + val launchObject = JSONObject(); + launchObject.put("type", "STOP"); + launchObject.put("sessionId", sessionId); + launchObject.put("requestId", _requestId++); + sendChannelMessage("sender-0", "receiver-0", "urn:x-cast:com.google.cast.receiver", launchObject.toString()); + + _contentId = null; + _contentType = null; + _streamType = null; + _sessionId = null; + _transportId = null; + } + + Logger.i(TAG, "Stopping active device because stopCasting was called.") + stop(); + } + + override fun start() { + val adrs = addresses ?: return; + if (_started) { + return; + } + + _started = true; + _sessionId = null; + _mediaSessionId = null; + + Logger.i(TAG, "Starting..."); + + _launching = true; + + _scopeIO?.cancel(); + Logger.i(TAG, "Cancelled previous scopeIO because a new one is starting.") + _scopeIO = CoroutineScope(Dispatchers.IO); + + Thread { + connectionState = CastConnectionState.CONNECTING; + + while (_scopeIO?.isActive == true) { + try { + val connectedSocket = getConnectedSocket(adrs.toList(), port); + if (connectedSocket == null) { + Thread.sleep(3000); + continue; + } + + usedRemoteAddress = connectedSocket.inetAddress; + localAddress = connectedSocket.localAddress; + connectedSocket.close(); + break; + } catch (e: Throwable) { + Logger.w(TAG, "Failed to get setup initial connection to ChromeCast device.", e) + } + } + + val sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustAllCerts, null); + + val factory = sslContext.socketFactory; + + //Connection loop + while (_scopeIO?.isActive == true) { + Logger.i(TAG, "Connecting to Chromecast."); + connectionState = CastConnectionState.CONNECTING; + + try { + _socket = factory.createSocket(usedRemoteAddress, port) as SSLSocket; + _socket?.startHandshake(); + Logger.i(TAG, "Successfully connected to Chromecast at $usedRemoteAddress:$port"); + + try { + _outputStream = DataOutputStream(_socket?.outputStream); + _inputStream = DataInputStream(_socket?.inputStream); + } catch (e: Throwable) { + Logger.i(TAG, "Failed to authenticate to Chromecast.", e); + } + } catch (e: IOException) { + _socket?.close(); + Logger.i(TAG, "Failed to connect to Chromecast.", e); + + connectionState = CastConnectionState.CONNECTING; + Thread.sleep(3000); + continue; + } + + localAddress = _socket?.localAddress; + + try { + val connectObject = JSONObject(); + connectObject.put("type", "CONNECT"); + connectObject.put("connType", 0); + sendChannelMessage("sender-0", "receiver-0", "urn:x-cast:com.google.cast.tp.connection", connectObject.toString()); + } catch (e: Throwable) { + Logger.i(TAG, "Failed to send connect message to Chromecast.", e); + _socket?.close(); + + connectionState = CastConnectionState.CONNECTING; + Thread.sleep(3000); + continue; + } + + getStatus(); + + val buffer = ByteArray(4096); + + Logger.i(TAG, "Started receiving."); + while (_scopeIO?.isActive == true) { + try { + val inputStream = _inputStream ?: break; + Log.d(TAG, "Receiving next packet..."); + val b1 = inputStream.readUnsignedByte(); + val b2 = inputStream.readUnsignedByte(); + val b3 = inputStream.readUnsignedByte(); + val b4 = inputStream.readUnsignedByte(); + val size = ((b1.toLong() shl 24) or (b2.toLong() shl 16) or (b3.toLong() shl 8) or b4.toLong()).toInt(); + if (size > buffer.size) { + Logger.w(TAG, "Skipping packet that is too large $size bytes.") + inputStream.skip(size.toLong()); + continue; + } + + Log.d(TAG, "Received header indicating $size bytes. Waiting for message."); + inputStream.read(buffer, 0, size); + + //TODO: In the future perhaps this size-1 will cause issues, why is there a 0 on the end? + val messageBytes = buffer.sliceArray(IntRange(0, size - 1)); + Log.d(TAG, "Received $size bytes: ${messageBytes.toHexString()}."); + val message = DeviceAuthMessageOuterClass.CastMessage.parseFrom(messageBytes); + if (message.namespace != "urn:x-cast:com.google.cast.tp.heartbeat") { + Logger.i(TAG, "Received message: $message"); + } + + try { + handleMessage(message); + } catch (e:Throwable) { + Logger.w(TAG, "Failed to handle message.", e); + } + } catch (e: java.net.SocketException) { + Logger.e(TAG, "Socket exception while receiving.", e); + break; + } catch (e: Throwable) { + Logger.e(TAG, "Exception while receiving.", e); + break; + } + } + _socket?.close(); + Logger.i(TAG, "Socket disconnected."); + + connectionState = CastConnectionState.CONNECTING; + Thread.sleep(3000); + } + + Logger.i(TAG, "Stopped connection loop."); + connectionState = CastConnectionState.DISCONNECTED; + }.start(); + + //Start ping loop + Thread { + Logger.i(TAG, "Started ping loop.") + + val pingObject = JSONObject(); + pingObject.put("type", "PING"); + + while (_scopeIO?.isActive == true) { + try { + sendChannelMessage("sender-0", "receiver-0", "urn:x-cast:com.google.cast.tp.heartbeat", pingObject.toString()); + Thread.sleep(5000); + } catch (e: Throwable) { + + } + } + + Logger.i(TAG, "Stopped ping loop."); + }.start(); + + Logger.i(TAG, "Started."); + } + + private fun sendChannelMessage(sourceId: String, destinationId: String, namespace: String, json: String) { + try { + val castMessage = DeviceAuthMessageOuterClass.CastMessage.newBuilder() + .setProtocolVersion(DeviceAuthMessageOuterClass.CastMessage.ProtocolVersion.CASTV2_1_0) + .setSourceId(sourceId) + .setDestinationId(destinationId) + .setNamespace(namespace) + .setPayloadType(DeviceAuthMessageOuterClass.CastMessage.PayloadType.STRING) + .setPayloadUtf8(json) + .build(); + + sendMessage(castMessage.toByteArray()); + + if (namespace != "urn:x-cast:com.google.cast.tp.heartbeat") { + //Log.d(TAG, "Sent channel message: $castMessage"); + } + } catch (e: Throwable) { + Logger.w(TAG, "Failed to send channel message (sourceId: $sourceId, destinationId: $destinationId, namespace: $namespace, json: $json)", e); + } + } + + private fun handleMessage(message: DeviceAuthMessageOuterClass.CastMessage) { + if (message.payloadType == DeviceAuthMessageOuterClass.CastMessage.PayloadType.STRING) { + val jsonObject = JSONObject(message.payloadUtf8); + val type = jsonObject.getString("type"); + if (type == "RECEIVER_STATUS") { + val status = jsonObject.getJSONObject("status"); + + var sessionIsRunning = false; + if (status.has("applications")) { + val applications = status.getJSONArray("applications"); + + for (i in 0 until applications.length()) { + val applicationUpdate = applications.getJSONObject(i); + + val appId = applicationUpdate.getString("appId"); + Logger.i(TAG, "Status update received appId (appId: $appId)"); + + if (appId == "CC1AD845") { + sessionIsRunning = true; + + if (_sessionId == null) { + connectionState = CastConnectionState.CONNECTED; + _sessionId = applicationUpdate.getString("sessionId"); + + val transportId = applicationUpdate.getString("transportId"); + connectMediaChannel(transportId); + Logger.i(TAG, "Connected to media channel $transportId"); + _transportId = transportId; + + requestMediaStatus(); + playVideo(); + } + } + } + } + + if (!sessionIsRunning) { + _sessionId = null; + _mediaSessionId = null; + time = 0.0; + _transportId = null; + Logger.w(TAG, "Session not found."); + + if (_launching) { + Logger.i(TAG, "Player not found, launching."); + launchPlayer(); + } else { + Logger.i(TAG, "Player not found, disconnecting."); + stop(); + } + } else { + _launching = false; + } + + val volume = status.getJSONObject("volume"); + val volumeControlType = volume.getString("controlType"); + val volumeLevel = volume.getString("level").toDouble(); + val volumeMuted = volume.getBoolean("muted"); + val volumeStepInterval = volume.getString("stepInterval").toFloat(); + this.volume = if (volumeMuted) 0.0 else volumeLevel; + + Logger.i(TAG, "Status update received volume (level: $volumeLevel, muted: $volumeMuted)"); + } else if (type == "MEDIA_STATUS") { + val statuses = jsonObject.getJSONArray("status"); + for (i in 0 until statuses.length()) { + val status = statuses.getJSONObject(i); + _mediaSessionId = status.getInt("mediaSessionId"); + + val playerState = status.getString("playerState"); + val currentTime = status.getDouble("currentTime"); + + isPlaying = playerState == "PLAYING"; + if (isPlaying) { + time = currentTime; + } + + val playbackRate = status.getInt("playbackRate"); + Logger.i(TAG, "Media update received (mediaSessionId: $_mediaSessionId, playedState: $playerState, currentTime: $currentTime, playbackRate: $playbackRate)"); + + if (_contentType == null) { + stopVideo(); + } + } + } else if (type == "CLOSE") { + if (message.sourceId == "receiver-0") { + Logger.i(TAG, "Close received."); + stop(); + } + } + } else { + throw Exception("Payload type ${message.payloadType} is not implemented."); + } + } + + private fun sendMessage(data: ByteArray) { + val outputStream = _outputStream; + if (outputStream == null) { + Logger.w(TAG, "Failed to send ${data.size} bytes, output stream is null."); + return; + } + + val serializedSizeBE = ByteArray(4); + serializedSizeBE[0] = (data.size shr 24 and 0xff).toByte(); + serializedSizeBE[1] = (data.size shr 16 and 0xff).toByte(); + serializedSizeBE[2] = (data.size shr 8 and 0xff).toByte(); + serializedSizeBE[3] = (data.size and 0xff).toByte(); + outputStream.write(serializedSizeBE); + outputStream.write(data); + + //Log.d(TAG, "Sent ${data.size} bytes."); + } + + override fun stop() { + Logger.i(TAG, "Stopping..."); + usedRemoteAddress = null; + localAddress = null; + _started = false; + + val socket = _socket; + val scopeIO = _scopeIO; + + if (scopeIO != null && socket != null) { + Logger.i(TAG, "Cancelling scopeIO with open socket.") + + scopeIO.launch { + socket.close(); + connectionState = CastConnectionState.DISCONNECTED; + scopeIO.cancel(); + Logger.i(TAG, "Cancelled scopeIO with open socket.") + } + } else { + scopeIO?.cancel(); + Logger.i(TAG, "Cancelled scopeIO without open socket.") + } + + _scopeIO = null; + _socket = null; + _outputStream = null; + _inputStream = null; + _mediaSessionId = null; + connectionState = CastConnectionState.DISCONNECTED; + } + + override fun getDeviceInfo(): CastingDeviceInfo { + return CastingDeviceInfo(name!!, CastProtocolType.CHROMECAST, addresses!!.filter { a -> a.hostAddress != null }.map { a -> a.hostAddress!! }.toTypedArray(), port); + } + + companion object { + val TAG = "ChromecastCastingDevice"; + + val trustAllCerts: Array = arrayOf(object : X509TrustManager { + override fun checkClientTrusted(chain: Array?, authType: String?) { } + override fun checkServerTrusted(chain: Array?, authType: String?) { } + override fun getAcceptedIssuers(): Array { return emptyArray(); } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/casting/FastCastCastingDevice.kt b/app/src/main/java/com/futo/platformplayer/casting/FastCastCastingDevice.kt new file mode 100644 index 00000000..da4f8fbf --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/casting/FastCastCastingDevice.kt @@ -0,0 +1,407 @@ +package com.futo.platformplayer.casting + +import android.os.Looper +import android.util.Log +import com.futo.platformplayer.casting.models.* +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.getConnectedSocket +import com.futo.platformplayer.models.CastingDeviceInfo +import com.futo.platformplayer.toHexString +import com.futo.platformplayer.toInetAddress +import kotlinx.coroutines.* +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.io.DataInputStream +import java.io.DataOutputStream +import java.io.IOException +import java.net.InetAddress +import java.net.Socket + +enum class Opcode(val value: Byte) { + NONE(0), + PLAY(1), + PAUSE(2), + RESUME(3), + STOP(4), + SEEK(5), + PLAYBACK_UPDATE(6), + VOLUME_UPDATE(7), + SET_VOLUME(8) +} + +class FastCastCastingDevice : CastingDevice { + //See for more info: TODO + + override val protocol: CastProtocolType get() = CastProtocolType.FASTCAST; + override val isReady: Boolean get() = name != null && addresses != null && addresses?.isNotEmpty() == true && port != 0; + override var usedRemoteAddress: InetAddress? = null; + override var localAddress: InetAddress? = null; + override val canSetVolume: Boolean get() = true; + + var addresses: Array? = null; + var port: Int = 0; + + private var _socket: Socket? = null; + private var _outputStream: DataOutputStream? = null; + private var _inputStream: DataInputStream? = null; + private var _scopeIO: CoroutineScope? = null; + private var _started: Boolean = false; + + constructor(name: String, addresses: Array, port: Int) : super() { + this.name = name; + this.addresses = addresses; + this.port = port; + } + + constructor(deviceInfo: CastingDeviceInfo) : super() { + this.name = deviceInfo.name; + this.addresses = deviceInfo.addresses.map { a -> a.toInetAddress() }.filterNotNull().toTypedArray(); + this.port = deviceInfo.port; + } + + override fun getAddresses(): List { + return addresses?.toList() ?: listOf(); + } + + override fun loadVideo(streamType: String, contentType: String, contentId: String, resumePosition: Double, duration: Double) { + if (invokeInIOScopeIfRequired({ loadVideo(streamType, contentType, contentId, resumePosition, duration) })) { + return; + } + + Logger.i(TAG, "Start streaming (streamType: $streamType, contentType: $contentType, contentId: $contentId, resumePosition: $resumePosition, duration: $duration)"); + + time = resumePosition; + sendMessage(Opcode.PLAY, FastCastPlayMessage( + container = contentType, + url = contentId, + time = resumePosition.toInt() + )); + } + + override fun loadContent(contentType: String, content: String, resumePosition: Double, duration: Double) { + if (invokeInIOScopeIfRequired({ loadContent(contentType, content, resumePosition, duration) })) { + return; + } + + Logger.i(TAG, "Start streaming content (contentType: $contentType, resumePosition: $resumePosition, duration: $duration)"); + + time = resumePosition; + sendMessage(Opcode.PLAY, FastCastPlayMessage( + container = contentType, + content = content, + time = resumePosition.toInt() + )); + } + + override fun changeVolume(volume: Double) { + if (invokeInIOScopeIfRequired({ changeVolume(volume) })) { + return; + } + + this.volume = volume + sendMessage(Opcode.SET_VOLUME, FastCastSetVolumeMessage(volume)) + } + + override fun seekVideo(timeSeconds: Double) { + if (invokeInIOScopeIfRequired({ seekVideo(timeSeconds) })) { + return; + } + + sendMessage(Opcode.SEEK, FastCastSeekMessage( + time = timeSeconds.toInt() + )); + } + + override fun resumeVideo() { + if (invokeInIOScopeIfRequired(::resumeVideo)) { + return; + } + + sendMessage(Opcode.RESUME); + } + + override fun pauseVideo() { + if (invokeInIOScopeIfRequired(::pauseVideo)) { + return; + } + + sendMessage(Opcode.PAUSE); + } + + override fun stopVideo() { + if (invokeInIOScopeIfRequired(::stopVideo)) { + return; + } + + sendMessage(Opcode.STOP); + } + + private fun invokeInIOScopeIfRequired(action: () -> Unit): Boolean { + if(Looper.getMainLooper().thread == Thread.currentThread()) { + _scopeIO?.launch { action(); } + return true; + } + + return false; + } + + override fun stopCasting() { + if (invokeInIOScopeIfRequired(::stopCasting)) { + return; + } + + stopVideo(); + + Logger.i(TAG, "Stopping active device because stopCasting was called.") + stop(); + } + + override fun start() { + val adrs = addresses ?: return; + if (_started) { + return; + } + + _started = true; + Logger.i(TAG, "Starting..."); + + _scopeIO?.cancel(); + Logger.i(TAG, "Cancelled previous scopeIO because a new one is starting.") + _scopeIO = CoroutineScope(Dispatchers.IO); + + Thread { + connectionState = CastConnectionState.CONNECTING; + + while (_scopeIO?.isActive == true) { + try { + val connectedSocket = getConnectedSocket(adrs.toList(), port); + if (connectedSocket == null) { + Thread.sleep(3000); + continue; + } + + usedRemoteAddress = connectedSocket.inetAddress; + localAddress = connectedSocket.localAddress; + connectedSocket.close(); + break; + } catch (e: Throwable) { + Logger.w(ChromecastCastingDevice.TAG, "Failed to get setup initial connection to FastCast device.", e) + } + } + + //Connection loop + while (_scopeIO?.isActive == true) { + Logger.i(TAG, "Connecting to FastCast."); + connectionState = CastConnectionState.CONNECTING; + + try { + _socket = Socket(usedRemoteAddress, port); + Logger.i(TAG, "Successfully connected to FastCast at $usedRemoteAddress:$port"); + + try { + _outputStream = DataOutputStream(_socket?.outputStream); + _inputStream = DataInputStream(_socket?.inputStream); + } catch (e: Throwable) { + Logger.i(TAG, "Failed to authenticate to FastCast.", e); + } + } catch (e: IOException) { + _socket?.close(); + Logger.i(TAG, "Failed to connect to FastCast.", e); + + connectionState = CastConnectionState.CONNECTING; + Thread.sleep(3000); + continue; + } + + localAddress = _socket?.localAddress; + connectionState = CastConnectionState.CONNECTED; + + val buffer = ByteArray(4096); + + Logger.i(TAG, "Started receiving."); + while (_scopeIO?.isActive == true) { + try { + val inputStream = _inputStream ?: break; + Log.d(TAG, "Receiving next packet..."); + val b1 = inputStream.readUnsignedByte(); + val b2 = inputStream.readUnsignedByte(); + val b3 = inputStream.readUnsignedByte(); + val b4 = inputStream.readUnsignedByte(); + val size = ((b4.toLong() shl 24) or (b3.toLong() shl 16) or (b2.toLong() shl 8) or b1.toLong()).toInt(); + if (size > buffer.size) { + Logger.w(TAG, "Skipping packet that is too large $size bytes.") + inputStream.skip(size.toLong()); + continue; + } + + Log.d(TAG, "Received header indicating $size bytes. Waiting for message."); + inputStream.read(buffer, 0, size); + + val messageBytes = buffer.sliceArray(IntRange(0, size)); + Log.d(TAG, "Received $size bytes: ${messageBytes.toHexString()}."); + + val opcode = messageBytes[0]; + var json: String? = null; + if (size > 1) { + json = messageBytes.sliceArray(IntRange(1, size - 1)).decodeToString(); + } + + try { + handleMessage(Opcode.values().first { it.value == opcode }, json); + } catch (e:Throwable) { + Logger.w(TAG, "Failed to handle message.", e); + } + } catch (e: java.net.SocketException) { + Logger.e(TAG, "Socket exception while receiving.", e); + break; + } catch (e: Throwable) { + Logger.e(TAG, "Exception while receiving.", e); + break; + } + } + _socket?.close(); + Logger.i(TAG, "Socket disconnected."); + + connectionState = CastConnectionState.CONNECTING; + Thread.sleep(3000); + } + + Logger.i(TAG, "Stopped connection loop."); + connectionState = CastConnectionState.DISCONNECTED; + }.start(); + + Logger.i(TAG, "Started."); + } + + private fun handleMessage(opcode: Opcode, json: String? = null) { + when (opcode) { + Opcode.PLAYBACK_UPDATE -> { + if (json == null) { + Logger.w(TAG, "Got playback update without JSON, ignoring."); + return; + } + + val playbackUpdate = Json.decodeFromString(json); + time = playbackUpdate.time.toDouble(); + isPlaying = when (playbackUpdate.state) { + 1 -> true + else -> false + } + } + Opcode.VOLUME_UPDATE -> { + if (json == null) { + Logger.w(TAG, "Got volume update without JSON, ignoring."); + return; + } + + val volumeUpdate = Json.decodeFromString(json); + volume = volumeUpdate.volume; + } + else -> { } + } + } + + private fun sendMessage(opcode: Opcode) { + try { + val size = 1; + val outputStream = _outputStream; + if (outputStream == null) { + Logger.w(TAG, "Failed to send $size bytes, output stream is null."); + return; + } + + val serializedSizeLE = ByteArray(4); + serializedSizeLE[0] = (size and 0xff).toByte(); + serializedSizeLE[1] = (size shr 8 and 0xff).toByte(); + serializedSizeLE[2] = (size shr 16 and 0xff).toByte(); + serializedSizeLE[3] = (size shr 24 and 0xff).toByte(); + outputStream.write(serializedSizeLE); + + val opcodeBytes = ByteArray(1); + opcodeBytes[0] = opcode.value; + outputStream.write(opcodeBytes); + + Log.d(TAG, "Sent $size bytes."); + } catch (e: Throwable) { + Logger.i(TAG, "Failed to send message.", e); + } + } + + private inline fun sendMessage(opcode: Opcode, message: T) { + try { + val data: ByteArray; + var jsonString: String? = null; + if (message != null) { + jsonString = Json.encodeToString(message); + data = jsonString.encodeToByteArray(); + } else { + data = ByteArray(0); + } + + val size = 1 + data.size; + val outputStream = _outputStream; + if (outputStream == null) { + Logger.w(TAG, "Failed to send $size bytes, output stream is null."); + return; + } + + val serializedSizeLE = ByteArray(4); + serializedSizeLE[0] = (size and 0xff).toByte(); + serializedSizeLE[1] = (size shr 8 and 0xff).toByte(); + serializedSizeLE[2] = (size shr 16 and 0xff).toByte(); + serializedSizeLE[3] = (size shr 24 and 0xff).toByte(); + outputStream.write(serializedSizeLE); + + val opcodeBytes = ByteArray(1); + opcodeBytes[0] = opcode.value; + outputStream.write(opcodeBytes); + + if (data.isNotEmpty()) { + outputStream.write(data); + } + + Log.d(TAG, "Sent $size bytes: '$jsonString'."); + } catch (e: Throwable) { + Logger.i(TAG, "Failed to send message.", e); + } + } + + override fun stop() { + Logger.i(TAG, "Stopping..."); + usedRemoteAddress = null; + localAddress = null; + _started = false; + + val socket = _socket; + val scopeIO = _scopeIO; + + if (scopeIO != null && socket != null) { + Logger.i(TAG, "Cancelling scopeIO with open socket.") + + scopeIO.launch { + socket.close(); + connectionState = CastConnectionState.DISCONNECTED; + scopeIO.cancel(); + Logger.i(TAG, "Cancelled scopeIO with open socket.") + } + } else { + scopeIO?.cancel(); + Logger.i(TAG, "Cancelled scopeIO without open socket.") + } + + _scopeIO = null; + _socket = null; + _outputStream = null; + _inputStream = null; + connectionState = CastConnectionState.DISCONNECTED; + } + + override fun getDeviceInfo(): CastingDeviceInfo { + return CastingDeviceInfo(name!!, CastProtocolType.FASTCAST, addresses!!.filter { a -> a.hostAddress != null }.map { a -> a.hostAddress!! }.toTypedArray(), port); + } + + companion object { + val TAG = "FastCastCastingDevice"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt b/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt new file mode 100644 index 00000000..63b9dfe3 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt @@ -0,0 +1,758 @@ +package com.futo.platformplayer.casting + +import android.content.ContentResolver +import android.content.Context +import android.os.Looper +import com.futo.platformplayer.* +import com.futo.platformplayer.api.http.server.ManagedHttpServer +import com.futo.platformplayer.api.http.server.handlers.* +import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource +import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails +import com.futo.platformplayer.builders.DashBuilder +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.CastingDeviceInfo +import com.futo.platformplayer.states.StateApp +import kotlinx.coroutines.* +import java.net.InetAddress +import java.util.* +import javax.jmdns.JmDNS +import javax.jmdns.ServiceEvent +import javax.jmdns.ServiceListener +import kotlin.collections.HashMap +import com.futo.platformplayer.stores.CastingDeviceInfoStorage +import com.futo.platformplayer.stores.FragmentedStorage +import javax.jmdns.ServiceTypeListener + +class StateCasting { + private val _scopeIO = CoroutineScope(Dispatchers.IO); + private val _scopeMain = CoroutineScope(Dispatchers.Main); + private lateinit var _jmDNS: JmDNS; + private val _storage: CastingDeviceInfoStorage = FragmentedStorage.get(); + + private val _castServer = ManagedHttpServer(9999); + private var _started = false; + + var devices: HashMap = hashMapOf(); + var rememberedDevices: ArrayList = arrayListOf(); + val onDeviceAdded = Event1(); + val onDeviceChanged = Event1(); + val onDeviceRemoved = Event1(); + val onActiveDeviceConnectionStateChanged = Event2(); + val onActiveDevicePlayChanged = Event1(); + val onActiveDeviceTimeChanged = Event1(); + var activeDevice: CastingDevice? = null; + + val isCasting: Boolean get() = activeDevice != null; + + private val _chromecastServiceListener = object : ServiceListener { + override fun serviceAdded(event: ServiceEvent) { + Logger.i(TAG, "ChromeCast service added: " + event.info); + addOrUpdateDevice(event); + } + + override fun serviceRemoved(event: ServiceEvent) { + Logger.i(TAG, "ChromeCast service removed: " + event.info); + synchronized(devices) { + val device = devices[event.info.name]; + if (device != null) { + onDeviceRemoved.emit(device); + } + } + } + + override fun serviceResolved(event: ServiceEvent) { + Logger.i(TAG, "ChromeCast service resolved: " + event.info); + addOrUpdateDevice(event); + } + + fun addOrUpdateDevice(event: ServiceEvent) { + addOrUpdateChromeCastDevice(event.info.name, event.info.inetAddresses, event.info.port); + } + } + + private val _airPlayServiceListener = object : ServiceListener { + override fun serviceAdded(event: ServiceEvent) { + Logger.i(TAG, "AirPlay service added: " + event.info); + addOrUpdateDevice(event); + } + + override fun serviceRemoved(event: ServiceEvent) { + Logger.i(TAG, "AirPlay service removed: " + event.info); + synchronized(devices) { + val device = devices[event.info.name]; + if (device != null) { + onDeviceRemoved.emit(device); + } + } + } + + override fun serviceResolved(event: ServiceEvent) { + Logger.i(TAG, "AirPlay service resolved: " + event.info); + addOrUpdateDevice(event); + } + + fun addOrUpdateDevice(event: ServiceEvent) { + addOrUpdateAirPlayDevice(event.info.name, event.info.inetAddresses, event.info.port); + } + } + + private val _fastCastServiceListener = object : ServiceListener { + override fun serviceAdded(event: ServiceEvent) { + Logger.i(TAG, "FastCast service added: " + event.info); + addOrUpdateDevice(event); + } + + override fun serviceRemoved(event: ServiceEvent) { + Logger.i(TAG, "FastCast service removed: " + event.info); + synchronized(devices) { + val device = devices[event.info.name]; + if (device != null) { + onDeviceRemoved.emit(device); + } + } + } + + override fun serviceResolved(event: ServiceEvent) { + Logger.i(TAG, "FastCast service resolved: " + event.info); + addOrUpdateDevice(event); + } + + fun addOrUpdateDevice(event: ServiceEvent) { + addOrUpdateFastCastDevice(event.info.name, event.info.inetAddresses, event.info.port); + } + } + + private val _serviceTypeListener = object : ServiceTypeListener { + override fun serviceTypeAdded(event: ServiceEvent?) { + if (event == null) { + return; + } + + Logger.i(TAG, "Service type added (name: ${event.name}, type: ${event.type})"); + } + + override fun subTypeForServiceTypeAdded(event: ServiceEvent?) { + if (event == null) { + return; + } + + Logger.i(TAG, "Sub type for service type added (name: ${event.name}, type: ${event.type})"); + } + } + + fun onStop() { + val ad = activeDevice ?: return; + Logger.i(TAG, "Stopping active device because of onStop."); + ad.stop(); + } + + @Synchronized + fun start(context: Context) { + if (_started) + return; + _started = true; + + Logger.i(TAG, "CastingService starting..."); + + rememberedDevices.clear(); + rememberedDevices.addAll(_storage.deviceInfos.map { deviceFromCastingDeviceInfo(it) }); + + _scopeIO.launch { + try { + _jmDNS = JmDNS.create(InetAddress.getLocalHost()); + _jmDNS.addServiceListener("_googlecast._tcp.local.", _chromecastServiceListener); + _jmDNS.addServiceListener("_airplay._tcp.local.", _airPlayServiceListener); + _jmDNS.addServiceListener("_fastcast._tcp.local.", _fastCastServiceListener); + + if (BuildConfig.DEBUG) { + _jmDNS.addServiceTypeListener(_serviceTypeListener); + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to start casting service.", e); + } + } + _castServer.start(); + enableDeveloper(context.contentResolver, true); + + Logger.i(TAG, "CastingService started."); + } + + @Synchronized + fun stop() { + if (!_started) + return; + + _started = false; + + Logger.i(TAG, "CastingService stopping.") + + _scopeIO.launch { + try { + _jmDNS.removeServiceListener("_googlecast._tcp.local.", _chromecastServiceListener); + _jmDNS.removeServiceListener("_airplay._tcp", _airPlayServiceListener); + + if (BuildConfig.DEBUG) { + _jmDNS.removeServiceTypeListener(_serviceTypeListener); + } + + _jmDNS.close(); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to stop mDNS.", e); + } + } + + _scopeIO.cancel(); + _scopeMain.cancel(); + + Logger.i(TAG, "Stopping active device because StateCasting is being stopped.") + val d = activeDevice; + activeDevice = null; + d?.stop(); + + _castServer.stop(); + _castServer.removeAllHandlers(); + + Logger.i(TAG, "CastingService stopped.") + } + + @Synchronized + fun connectDevice(device: CastingDevice) { + if (activeDevice == device) + return; + + val ad = activeDevice; + if (ad != null) { + Logger.i(TAG, "Stopping previous device because a new one is being connected.") + ad.onPlayChanged.clear(); + ad.onTimeChanged.clear(); + ad.onConnectionStateChanged.clear(); + ad.stop(); + } + + device.onConnectionStateChanged.subscribe { castConnectionState -> + Logger.i(TAG, "Active device connection state changed: $castConnectionState"); + + if (castConnectionState == CastConnectionState.DISCONNECTED) { + Logger.i(TAG, "Clearing events: $castConnectionState"); + + device.onPlayChanged.clear(); + device.onTimeChanged.clear(); + device.onConnectionStateChanged.clear(); + activeDevice = null; + } + + invokeInMainScopeIfRequired { + StateApp.withContext(false) { context -> + context.let { + when (castConnectionState) { + CastConnectionState.CONNECTED -> UIDialogs.toast(it, "Connected to device") + CastConnectionState.CONNECTING -> UIDialogs.toast(it, "Connecting to device...") + CastConnectionState.DISCONNECTED -> UIDialogs.toast(it, "Disconnected from device") + } + } + }; + onActiveDeviceConnectionStateChanged.emit(device, castConnectionState); + }; + }; + device.onPlayChanged.subscribe { + invokeInMainScopeIfRequired { onActiveDevicePlayChanged.emit(it) }; + } + device.onTimeChanged.subscribe { + invokeInMainScopeIfRequired { onActiveDeviceTimeChanged.emit(it) }; + }; + + addRememberedDevice(device); + Logger.i(TAG, "Device added to active discovery. Active discovery now contains ${_storage.getDevicesCount()} devices.") + + try { + device.start(); + } catch (e: Throwable) { + Logger.w(TAG, "Failed to connect to device."); + device.onConnectionStateChanged.clear(); + device.onPlayChanged.clear(); + device.onTimeChanged.clear(); + return; + } + + activeDevice = device; + Logger.i(TAG, "Connect to device ${device.name}"); + } + + fun addRememberedDevice(deviceInfo: CastingDeviceInfo) { + val device = deviceFromCastingDeviceInfo(deviceInfo); + addRememberedDevice(device); + } + + fun addRememberedDevice(device: CastingDevice) { + if (_storage.addDevice(device.getDeviceInfo())) { + rememberedDevices.add(device); + } + } + + fun removeRememberedDevice(device: CastingDevice) { + val name = device.name ?: return; + _storage.removeDevice(name); + rememberedDevices.remove(device); + } + + private fun invokeInMainScopeIfRequired(action: () -> Unit){ + if(Looper.getMainLooper().thread != Thread.currentThread()) { + _scopeMain.launch { action(); } + return; + } + + action(); + } + + fun castIfAvailable(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoSource?, audioSource: IAudioSource?, subtitleSource: ISubtitleSource?, ms: Long = -1): Boolean { + val ad = activeDevice ?: return false; + if (ad.connectionState != CastConnectionState.CONNECTED) { + return false; + } + + val resumePosition = if (ms > 0L) (ms.toDouble() / 1000.0) else 0.0; + + var sourceCount = 0; + if (videoSource != null) sourceCount++; + if (audioSource != null) sourceCount++; + if (subtitleSource != null) sourceCount++; + + if (sourceCount < 1) { + throw Exception("At least one source should be specified."); + } + + if (sourceCount > 1) { + if (ad is AirPlayCastingDevice) { + StateApp.withContext(false) { context -> UIDialogs.toast(context, "AirPlay does not support DASH. Try ChromeCast or FastCast for casting this video."); }; + ad.stopCasting(); + return false; + } + + if (videoSource is LocalVideoSource || audioSource is LocalAudioSource || subtitleSource is LocalSubtitleSource) { + castLocalDash(video, videoSource as LocalVideoSource?, audioSource as LocalAudioSource?, subtitleSource as LocalSubtitleSource?, resumePosition); + } else { + StateApp.instance.scope.launch(Dispatchers.IO) { + try { + if (ad is FastCastCastingDevice) { + castDashDirect(contentResolver, video, videoSource as IVideoUrlSource?, audioSource as IAudioUrlSource?, subtitleSource, resumePosition); + } else { + castDashIndirect(contentResolver, video, videoSource as IVideoUrlSource?, audioSource as IAudioUrlSource?, subtitleSource, resumePosition); + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to start casting DASH videoSource=${videoSource} audioSource=${audioSource}.", e); + } + } + } + } else { + if (videoSource is IVideoUrlSource) { + ad.loadVideo("BUFFERED", videoSource.container, videoSource.getVideoUrl(), resumePosition, video.duration.toDouble()); + } else if (audioSource is IAudioUrlSource) { + ad.loadVideo("BUFFERED", audioSource.container, audioSource.getAudioUrl(), resumePosition, video.duration.toDouble()); + } else if (videoSource is LocalVideoSource) { + castLocalVideo(video, videoSource, resumePosition); + } else if (audioSource is LocalAudioSource) { + castLocalAudio(video, audioSource, resumePosition); + } else { + throw Exception("Unhandled source type videoSource=$videoSource audioSource=$audioSource subtitleSource=$subtitleSource"); + } + } + + return true; + } + + fun resumeVideo(): Boolean { + val ad = activeDevice ?: return false; + ad.resumeVideo(); + return true; + } + + fun pauseVideo(): Boolean { + val ad = activeDevice ?: return false; + ad.pauseVideo(); + return true; + } + + fun stopVideo(): Boolean { + val ad = activeDevice ?: return false; + ad.stopVideo(); + return true; + } + + fun videoSeekTo(timeSeconds: Double): Boolean { + val ad = activeDevice ?: return false; + ad.seekVideo(timeSeconds); + return true; + } + + private fun castLocalVideo(video: IPlatformVideoDetails, videoSource: LocalVideoSource, resumePosition: Double) : List { + val ad = activeDevice ?: return listOf(); + + val url = "http://${ad.localAddress}:${_castServer.port}"; + val id = UUID.randomUUID(); + val videoPath = "/video-${id}" + val videoUrl = url + videoPath; + + _castServer.addHandler( + HttpFileHandler("GET", videoPath, videoSource.container, videoSource.filePath) + .withHeader("Access-Control-Allow-Origin", "*"), true + ).withTag("cast"); + + Logger.i(TAG, "Casting local video (videoUrl: $videoUrl)."); + ad.loadVideo("BUFFERED", videoSource.container, videoUrl, resumePosition, video.duration.toDouble()); + + return listOf(videoUrl); + } + + private fun castLocalAudio(video: IPlatformVideoDetails, audioSource: LocalAudioSource, resumePosition: Double) : List { + val ad = activeDevice ?: return listOf(); + + val url = "http://${ad.localAddress}:${_castServer.port}"; + val id = UUID.randomUUID(); + val audioPath = "/audio-${id}" + val audioUrl = url + audioPath; + + _castServer.addHandler( + HttpFileHandler("GET", audioPath, audioSource.container, audioSource.filePath) + .withHeader("Access-Control-Allow-Origin", "*"), true + ).withTag("cast"); + + Logger.i(TAG, "Casting local audio (audioUrl: $audioUrl)."); + ad.loadVideo("BUFFERED", audioSource.container, audioUrl, resumePosition, video.duration.toDouble()); + + return listOf(audioUrl); + } + + + private fun castLocalDash(video: IPlatformVideoDetails, videoSource: LocalVideoSource?, audioSource: LocalAudioSource?, subtitleSource: LocalSubtitleSource?, resumePosition: Double) : List { + val ad = activeDevice ?: return listOf(); + + val url = "http://${ad.localAddress}:${_castServer.port}"; + val id = UUID.randomUUID(); + + val dashPath = "/dash-${id}" + val videoPath = "/video-${id}" + val audioPath = "/audio-${id}" + val subtitlePath = "/subtitle-${id}" + + val dashUrl = url + dashPath; + val videoUrl = url + videoPath; + val audioUrl = url + audioPath; + val subtitleUrl = url + subtitlePath; + + _castServer.addHandler( + HttpConstantHandler("GET", dashPath, DashBuilder.generateOnDemandDash(videoSource, videoUrl, audioSource, audioUrl, subtitleSource, subtitleUrl), + "application/dash+xml") + .withHeader("Access-Control-Allow-Origin", "*"), true + ).withTag("cast"); + if (videoSource != null) { + _castServer.addHandler( + HttpFileHandler("GET", videoPath, videoSource.container, videoSource.filePath) + .withHeader("Access-Control-Allow-Origin", "*"), true + ).withTag("cast"); + _castServer.addHandler( + HttpOptionsAllowHandler(videoPath) + .withHeader("Access-Control-Allow-Origin", "*") + .withHeader("Connection", "keep-alive")) + .withTag("cast"); + } + if (audioSource != null) { + _castServer.addHandler( + HttpFileHandler("GET", audioPath, audioSource.container, audioSource.filePath) + .withHeader("Access-Control-Allow-Origin", "*"), true + ).withTag("cast"); + _castServer.addHandler( + HttpOptionsAllowHandler(audioPath) + .withHeader("Access-Control-Allow-Origin", "*") + .withHeader("Connection", "keep-alive")) + .withTag("cast"); + } + if (subtitleSource != null) { + _castServer.addHandler( + HttpFileHandler("GET", subtitlePath, subtitleSource.format ?: "text/vtt", subtitleSource.filePath, true) + .withHeader("Access-Control-Allow-Origin", "*"), true + ).withTag("cast"); + _castServer.addHandler( + HttpOptionsAllowHandler(subtitlePath) + .withHeader("Access-Control-Allow-Origin", "*") + .withHeader("Connection", "keep-alive")) + .withTag("cast"); + } + + Logger.i(TAG, "added new castLocalDash handlers (dashPath: $dashPath, videoPath: $videoPath, audioPath: $audioPath, subtitlePath: $subtitlePath)."); + ad.loadVideo("BUFFERED", "application/dash+xml", dashUrl, resumePosition, video.duration.toDouble()); + + return listOf(dashUrl, videoUrl, audioUrl, subtitleUrl); + } + + private suspend fun castDashDirect(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoUrlSource?, audioSource: IAudioUrlSource?, subtitleSource: ISubtitleSource?, resumePosition: Double) : List { + val ad = activeDevice ?: return listOf(); + + val url = "http://${ad.localAddress}:${_castServer.port}"; + val id = UUID.randomUUID(); + val subtitlePath = "/subtitle-${id}"; + + val videoUrl = videoSource?.getVideoUrl(); + val audioUrl = audioSource?.getAudioUrl(); + + val subtitlesUri = if (subtitleSource != null) withContext(Dispatchers.IO) { + return@withContext subtitleSource.getSubtitlesURI(); + } else null; + + var subtitlesUrl: String? = null; + if (subtitlesUri != null) { + if(subtitlesUri.scheme == "file") { + var content: String? = null; + val inputStream = contentResolver.openInputStream(subtitlesUri); + inputStream?.use { stream -> + val reader = stream.bufferedReader(); + content = reader.use { it.readText() }; + } + + if (content != null) { + _castServer.addHandler( + HttpConstantHandler("GET", subtitlePath, content!!, subtitleSource?.format ?: "text/vtt") + .withHeader("Access-Control-Allow-Origin", "*"), true + ).withTag("cast"); + } + + subtitlesUrl = url + subtitlePath; + } else { + subtitlesUrl = subtitlesUri.toString(); + } + } + + val content = DashBuilder.generateOnDemandDash(videoSource, videoUrl, audioSource, audioUrl, subtitleSource, subtitlesUrl); + + Logger.i(TAG, "Direct dash cast to casting device (videoUrl: $videoUrl, audioUrl: $audioUrl)."); + ad.loadContent("application/dash+xml", content, resumePosition, video.duration.toDouble()); + + return listOf(videoSource?.getVideoUrl() ?: "", audioSource?.getAudioUrl() ?: ""); + } + + private suspend fun castDashIndirect(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoUrlSource?, audioSource: IAudioUrlSource?, subtitleSource: ISubtitleSource?, resumePosition: Double) : List { + val ad = activeDevice ?: return listOf(); + val proxyStreams = ad !is FastCastCastingDevice; + + val url = "http://${ad.localAddress}:${_castServer.port}"; + Logger.i(TAG, "DASH url: $url"); + + val id = UUID.randomUUID(); + + val dashPath = "/dash-${id}" + val videoPath = "/video-${id}" + val audioPath = "/audio-${id}" + val subtitlePath = "/subtitle-${id}" + + val dashUrl = url + dashPath; + val videoUrl = if(proxyStreams) url + videoPath else videoSource?.getVideoUrl(); + val audioUrl = if(proxyStreams) url + audioPath else audioSource?.getAudioUrl(); + + val subtitlesUri = if (subtitleSource != null) withContext(Dispatchers.IO) { + return@withContext subtitleSource.getSubtitlesURI(); + } else null; + + //_castServer.removeAllHandlers("cast"); + //Logger.i(TAG, "removed all old castDash handlers."); + + var subtitlesUrl: String? = null; + if (subtitlesUri != null) { + if(subtitlesUri.scheme == "file") { + var content: String? = null; + val inputStream = contentResolver.openInputStream(subtitlesUri); + inputStream?.use { stream -> + val reader = stream.bufferedReader(); + content = reader.use { it.readText() }; + } + + if (content != null) { + _castServer.addHandler( + HttpConstantHandler("GET", subtitlePath, content!!, subtitleSource?.format ?: "text/vtt") + .withHeader("Access-Control-Allow-Origin", "*"), true + ).withTag("cast"); + } + + subtitlesUrl = url + subtitlePath; + } else { + subtitlesUrl = subtitlesUri.toString(); + } + } + + _castServer.addHandler( + HttpConstantHandler("GET", dashPath, DashBuilder.generateOnDemandDash(videoSource, videoUrl, audioSource, audioUrl, subtitleSource, subtitlesUrl), + "application/dash+xml") + .withHeader("Access-Control-Allow-Origin", "*"), true + ).withTag("cast"); + if (videoSource != null) { + _castServer.addHandler( + HttpProxyHandler("GET", videoPath, videoSource.getVideoUrl()) + .withInjectedHost() + .withHeader("Access-Control-Allow-Origin", "*"), true + ).withTag("cast"); + _castServer.addHandler( + HttpOptionsAllowHandler(videoPath) + .withHeader("Access-Control-Allow-Origin", "*") + .withHeader("Connection", "keep-alive")) + .withTag("cast"); + } + if (audioSource != null) { + _castServer.addHandler( + HttpProxyHandler("GET", audioPath, audioSource.getAudioUrl()) + .withInjectedHost() + .withHeader("Access-Control-Allow-Origin", "*"), true + ).withTag("cast"); + _castServer.addHandler( + HttpOptionsAllowHandler(audioPath) + .withHeader("Access-Control-Allow-Origin", "*") + .withHeader("Connection", "keep-alivcontexte")) + .withTag("cast"); + } + + Logger.i(TAG, "added new castDash handlers (dashPath: $dashPath, videoPath: $videoPath, audioPath: $audioPath)."); + ad.loadVideo("BUFFERED", "application/dash+xml", dashUrl, resumePosition, video.duration.toDouble()); + + return listOf(dashUrl, videoUrl ?: "", audioUrl ?: "", subtitlesUrl ?: "", videoSource?.getVideoUrl() ?: "", audioSource?.getAudioUrl() ?: "", subtitlesUri.toString()); + } + + private fun deviceFromCastingDeviceInfo(deviceInfo: CastingDeviceInfo): CastingDevice { + return when (deviceInfo.type) { + CastProtocolType.CHROMECAST -> { + ChromecastCastingDevice(deviceInfo); + } + CastProtocolType.AIRPLAY -> { + AirPlayCastingDevice(deviceInfo); + } + CastProtocolType.FASTCAST -> { + FastCastCastingDevice(deviceInfo); + } + else -> throw Exception("${deviceInfo.type} is not a valid casting protocol") + } + } + + private fun addOrUpdateChromeCastDevice(name: String, addresses: Array, port: Int) { + return addOrUpdateCastDevice(name, + deviceFactory = { ChromecastCastingDevice(name, addresses, port) }, + deviceUpdater = { d -> + if (d.isReady) { + return@addOrUpdateCastDevice false; + } + + val changed = addresses.contentEquals(d.addresses) || d.name != name || d.port != port; + if (changed) { + d.name = name; + d.addresses = addresses; + d.port = port; + } + + return@addOrUpdateCastDevice changed; + } + ); + } + + private fun addOrUpdateAirPlayDevice(name: String, addresses: Array, port: Int) { + return addOrUpdateCastDevice(name, + deviceFactory = { AirPlayCastingDevice(name, addresses, port) }, + deviceUpdater = { d -> + if (d.isReady) { + return@addOrUpdateCastDevice false; + } + + val changed = addresses.contentEquals(addresses) || d.name != name || d.port != port; + if (changed) { + d.name = name; + d.port = port; + d.addresses = addresses; + } + + return@addOrUpdateCastDevice changed; + } + ); + } + + private fun addOrUpdateFastCastDevice(name: String, addresses: Array, port: Int) { + return addOrUpdateCastDevice(name, + deviceFactory = { FastCastCastingDevice(name, addresses, port) }, + deviceUpdater = { d -> + if (d.isReady) { + return@addOrUpdateCastDevice false; + } + + val changed = addresses.contentEquals(addresses) || d.name != name || d.port != port; + if (changed) { + d.name = name; + d.port = port; + d.addresses = addresses; + } + + return@addOrUpdateCastDevice changed; + } + ); + } + + private inline fun addOrUpdateCastDevice(name: String, deviceFactory: () -> TCastDevice, deviceUpdater: (device: TCastDevice) -> Boolean) where TCastDevice : CastingDevice { + var invokeEvents: (() -> Unit)? = null; + + synchronized(devices) { + val device = devices[name]; + if (device != null) { + if (device !is TCastDevice) { + Logger.w(TAG, "Device name conflict between device types. Ignoring device."); + } else { + val changed = deviceUpdater(device as TCastDevice); + if (changed) { + invokeEvents = { + onDeviceChanged.emit(device); + } + } else { + + } + } + } else { + val newDevice = deviceFactory(); + devices[name] = newDevice; + + invokeEvents = { + onDeviceAdded.emit(newDevice); + }; + } + } + + invokeEvents?.let { _scopeMain.launch { it(); }; }; + } + + fun enableDeveloper(contentResolver: ContentResolver, enableDev: Boolean){ + _castServer.removeAllHandlers("dev"); + if(enableDev) { + _castServer.addHandler(HttpFuntionHandler("GET", "/dashPlayer") { context -> + if (context.query.containsKey("dashUrl")) { + val dashUrl = context.query["dashUrl"]; + val html = "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
"; + context.respondCode(200, html, "text/html"); + } + }).withTag("dev"); + } + } + + companion object { + val instance: StateCasting = StateCasting(); + + private val TAG = "StateCasting"; + } +} + diff --git a/app/src/main/java/com/futo/platformplayer/casting/models/FastCast.kt b/app/src/main/java/com/futo/platformplayer/casting/models/FastCast.kt new file mode 100644 index 00000000..5b8e8272 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/casting/models/FastCast.kt @@ -0,0 +1,33 @@ +package com.futo.platformplayer.casting.models + +import kotlinx.serialization.Serializable + +@kotlinx.serialization.Serializable +data class FastCastPlayMessage( + val container: String, + val url: String? = null, + val content: String? = null, + val time: Int? = null +) { } + +@kotlinx.serialization.Serializable +data class FastCastSeekMessage( + val time: Int +) { } + +@kotlinx.serialization.Serializable +data class FastCastPlaybackUpdateMessage( + val time: Int, + val state: Int +) { } + + +@Serializable +data class FastCastVolumeUpdateMessage( + val volume: Double +) + +@Serializable +data class FastCastSetVolumeMessage( + val volume: Double +) \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/constructs/BackgroundTaskHandler.kt b/app/src/main/java/com/futo/platformplayer/constructs/BackgroundTaskHandler.kt new file mode 100644 index 00000000..86676267 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/constructs/BackgroundTaskHandler.kt @@ -0,0 +1,70 @@ +package com.futo.platformplayer.constructs + +import com.futo.platformplayer.logging.Logger +import kotlinx.coroutines.* + +class BackgroundTaskHandler { + private val TAG = "BackgroundTaskHandler" + + var onError = Event1(); + + private val _scope: CoroutineScope; + private val _dispatcher: CoroutineDispatcher; + private var _idGenerator = 0; + private val _task: (() -> Unit); + private val _lockObject = Object(); + + constructor(scope: CoroutineScope, task: (() -> Unit), dispatcher: CoroutineDispatcher = Dispatchers.IO) { + _task = task; + _scope = scope; + _dispatcher = dispatcher; + } + + inline fun exception(noinline cb : (T)->Unit) : BackgroundTaskHandler { + onError.subscribeConditional { + if(it is T) { + cb(it); + return@subscribeConditional true; + } + + return@subscribeConditional false; + } + return this; + } + + @Synchronized + fun run() { + val id = ++_idGenerator; + + _scope.launch(_dispatcher) { + synchronized (_lockObject) { + if (id != _idGenerator) + return@launch; + + try { + _task.invoke(); + if (id != _idGenerator) + return@launch; + } catch (e: Throwable) { + if (id != _idGenerator) + return@launch; + + if (!onError.emit(e)) { + Logger.e(TAG, "Uncaught exception handled by BackgroundTaskHandler.", e); + } + } + } + } + } + + @Synchronized + fun cancel() { + _idGenerator++; + } + + @Synchronized + fun dispose() { + cancel(); + onError.clear(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/constructs/BatchedTaskHandler.kt b/app/src/main/java/com/futo/platformplayer/constructs/BatchedTaskHandler.kt new file mode 100644 index 00000000..a6464301 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/constructs/BatchedTaskHandler.kt @@ -0,0 +1,73 @@ +package com.futo.platformplayer.constructs + +import com.futo.platformplayer.states.StateApp +import kotlinx.coroutines.* + +class BatchedTaskHandler { + + private val _batchLock = Object(); + //TODO: Determine Deferred/Async vs CompletableFuture (JVM8) + private val _batchRequest = HashMap>(); + + private val _scope: CoroutineScope; + private val _task: suspend ((parameter: TParameter) -> TResult); + private val _taskGetCache: ((parameter: TParameter) -> TResult?)?; + private val _taskSetCache: ((para: TParameter, result: TResult) -> Unit)?; + + constructor(scope: CoroutineScope, task: suspend ((parameter: TParameter) -> TResult), taskGetCache: ((parameter: TParameter) -> TResult?)? = null, taskSetCache: ((para: TParameter, result: TResult) -> Unit)? = null) { + _task = task; + _scope = scope; + _taskGetCache = taskGetCache; + _taskSetCache = taskSetCache; + if((_taskGetCache != null) != (_taskSetCache != null)) + throw IllegalArgumentException("Neither or both getCache/setCache need to be provided"); + } + + fun execute(para : TParameter) : Deferred { + var result: TResult? = null; + var taskResult: Deferred? = null; + + synchronized(_batchLock) { + result = _taskGetCache?.invoke(para); + if(result == null) { + taskResult = _batchRequest[para]; + if(taskResult?.isCancelled ?: false) { + _batchRequest.remove(para); + taskResult = null; + } + } + + //Cached + if(result != null) + //TODO: Replace with some kind of constant Deferred + return _scope.async { result as TResult } + //Already requesting + if(taskResult != null) + return taskResult as Deferred; + + //No ongoing task, then execute the search + //TODO: Replace GlobalScope with _scope after preventing cancel on exception + val task = GlobalScope.async { + val res: TResult; + try { + res = _task.invoke(para); + result = res; + } catch(ex : Throwable) { + synchronized (_batchLock) { + _batchRequest.remove(para); + } + + throw ex.fillInStackTrace(); + } + + synchronized(_batchLock) { + _batchRequest.remove(para); + _taskSetCache?.invoke(para, res); + return@async res; + } + }; + _batchRequest[para] = task; + return task; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/constructs/Event.kt b/app/src/main/java/com/futo/platformplayer/constructs/Event.kt new file mode 100644 index 00000000..84802a6d --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/constructs/Event.kt @@ -0,0 +1,146 @@ +package com.futo.platformplayer.constructs + +interface IEvent { + +} +abstract class EventBase: IEvent { + + protected val _conditionalListeners = mutableListOf>(); + protected val _listeners = mutableListOf>(); + + fun subscribeConditional(listener: ConditionalHandler) { + synchronized(_conditionalListeners) { + _conditionalListeners.add(TaggedHandler(listener)); + } + } + + fun subscribeConditional(tag: Any?, listener: ConditionalHandler) { + synchronized(_conditionalListeners) { + _conditionalListeners.add(TaggedHandler(listener, tag)); + } + } + + fun subscribe(listener : Handler) { + synchronized(_listeners) { + _listeners.add(TaggedHandler(listener)); + } + } + + fun subscribe(tag: Any?, listener: Handler) { + synchronized(_listeners) { + _listeners.add(TaggedHandler(listener, tag)); + } + } + + fun remove(tag: Any) { + synchronized(_conditionalListeners) { + _conditionalListeners.removeIf { it.tag == tag }; + } + + synchronized(_listeners) { + _listeners.removeIf { it.tag == tag }; + } + } + + fun clear() { + synchronized(_conditionalListeners) { + _conditionalListeners.clear(); + } + + synchronized(_listeners) { + _listeners.clear(); + } + } + + class TaggedHandler { + val tag: Any?; + val handler: T; + + constructor(handler: T, tag: Any? = null) { + this.tag = tag; + this.handler = handler; + } + } +} + +class Event0() : EventBase<(()->Unit), (()->Boolean)>() { + fun emit() : Boolean { + var handled: Boolean; + synchronized(_listeners) { + handled = _listeners.isNotEmpty(); + } + + synchronized(_conditionalListeners) { + for (conditional in _conditionalListeners) + handled = handled || conditional.handler.invoke(); + } + + synchronized(_listeners) { + for (handler in _listeners) + handler.handler.invoke(); + } + + return handled; + } +} +class Event1() : EventBase<((T1)->Unit), ((T1)->Boolean)>() { + fun emit(value : T1): Boolean { + var handled: Boolean; + synchronized(_listeners) { + handled = _listeners.isNotEmpty(); + } + + synchronized(_conditionalListeners) { + for (conditional in _conditionalListeners) + handled = handled || conditional.handler.invoke(value); + } + + synchronized(_listeners) { + for (handler in _listeners) + handler.handler.invoke(value); + } + + return handled; + } +} +class Event2() : EventBase<((T1, T2)->Unit), ((T1, T2)->Boolean)>() { + fun emit(value1 : T1, value2 : T2): Boolean { + var handled: Boolean; + synchronized(_listeners) { + handled = _listeners.isNotEmpty(); + } + + synchronized(_conditionalListeners) { + for (conditional in _conditionalListeners) + handled = handled || conditional.handler.invoke(value1, value2); + } + + synchronized(_listeners) { + for (handler in _listeners) + handler.handler.invoke(value1, value2); + } + + return handled; + } +} + +class Event3() : EventBase<((T1, T2, T3)->Unit), ((T1, T2, T3)->Boolean)>() { + fun emit(value1 : T1, value2 : T2, value3 : T3): Boolean { + var handled: Boolean; + synchronized(_listeners) { + handled = _listeners.isNotEmpty(); + } + + synchronized(_conditionalListeners) { + for (conditional in _conditionalListeners) + handled = handled || conditional.handler.invoke(value1, value2, value3); + } + + synchronized(_listeners) { + for (handler in _listeners) + handler.handler.invoke(value1, value2, value3); + } + + return handled; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/constructs/TaskHandler.kt b/app/src/main/java/com/futo/platformplayer/constructs/TaskHandler.kt new file mode 100644 index 00000000..9307fff3 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/constructs/TaskHandler.kt @@ -0,0 +1,112 @@ +package com.futo.platformplayer.constructs + +import android.util.Log +import com.futo.platformplayer.logging.Logger +import kotlinx.coroutines.* + +class TaskHandler { + private val TAG = "TaskHandler" + + var onSuccess = Event1(); + var onError = Event2(); + + private val _scope: ()->CoroutineScope; + private val _dispatcher: CoroutineDispatcher; + private var _idGenerator = 0; + private val _task: suspend ((parameter: TParameter) -> TResult); + + constructor(claz : Class, scope: ()->CoroutineScope) { + _task = { claz.newInstance() }; + _scope = scope; + _dispatcher = Dispatchers.IO; + } + constructor(scope: ()->CoroutineScope, task: suspend ((parameter: TParameter) -> TResult), dispatcher: CoroutineDispatcher = Dispatchers.IO) { + _task = task; + _scope = scope; + _dispatcher = dispatcher; + } + + inline fun success(noinline cb : (TResult)->Unit) : TaskHandler { + onSuccess.subscribe(cb); + return this; + } + + inline fun exception(noinline cb : (T)->Unit) : TaskHandler { + onError.subscribeConditional { ex, para -> + if(ex is T) { + cb(ex); + return@subscribeConditional true; + } + return@subscribeConditional false; + } + return this; + } + inline fun exceptionWithParameter(noinline cb : (T, TParameter)->Unit) : TaskHandler { + onError.subscribeConditional { ex, para -> + if(ex is T) { + cb(ex, para); + return@subscribeConditional true; + } + + return@subscribeConditional false; + } + return this; + } + + @Synchronized + fun run(parameter: TParameter) { + val id = ++_idGenerator; + + _scope().launch(_dispatcher) { + if (id != _idGenerator) + return@launch; + + try { + val result = _task.invoke(parameter); + if (id != _idGenerator) + return@launch; + + withContext(Dispatchers.Main) { + if (id != _idGenerator) + return@withContext; + + try { + onSuccess.emit(result); + } + catch (e: Throwable) { + Logger.w(TAG, "Handled exception in TaskHandler onSuccess.", e); + onError.emit(e, parameter); + } + } + } + catch (e: Throwable) { + Log.i("TaskHandler", "TaskHandler.run in exception: " + e.message); + if (id != _idGenerator) + return@launch; + + withContext(Dispatchers.Main) { + if (id != _idGenerator) + return@withContext; + + if (!onError.emit(e, parameter)) { + Logger.e(TAG, "Uncaught exception handled by TaskHandler.", e); + } else { + Logger.w(TAG, "Handled exception in TaskHandler invoke.", e); + } + } + } + } + } + + @Synchronized + fun cancel() { + _idGenerator++; + } + + @Synchronized + fun dispose() { + cancel(); + onSuccess.clear(); + onError.clear(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/debug/Stopwatch.kt b/app/src/main/java/com/futo/platformplayer/debug/Stopwatch.kt new file mode 100644 index 00000000..a10c8da2 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/debug/Stopwatch.kt @@ -0,0 +1,16 @@ +package com.futo.platformplayer.debug + +import com.google.android.exoplayer2.util.Log + +class Stopwatch { + var startTime = System.nanoTime() + + fun logAndNext(tag: String, message: String): Long { + val now = System.nanoTime() + val diff = now - startTime + val diffMs = diff / 1000000.0 + Log.d(tag, "STOPWATCH $message ${diffMs}ms") + startTime = now + return diff + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/developer/DeveloperEndpoints.kt b/app/src/main/java/com/futo/platformplayer/developer/DeveloperEndpoints.kt new file mode 100644 index 00000000..808662b9 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/developer/DeveloperEndpoints.kt @@ -0,0 +1,434 @@ +package com.futo.platformplayer.developer + +import android.content.Context +import com.futo.platformplayer.activities.LoginActivity +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.api.http.server.HttpContext +import com.futo.platformplayer.api.http.server.HttpGET +import com.futo.platformplayer.api.http.server.HttpPOST +import com.futo.platformplayer.api.media.platforms.js.JSClient +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient +import com.futo.platformplayer.engine.V8Plugin +import com.futo.platformplayer.engine.dev.V8RemoteObject +import com.futo.platformplayer.engine.dev.V8RemoteObject.Companion.gsonStandard +import com.futo.platformplayer.engine.dev.V8RemoteObject.Companion.serialize +import com.futo.platformplayer.engine.packages.PackageHttp +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StateAssets +import com.futo.platformplayer.states.StateDeveloper +import com.futo.platformplayer.states.StatePlatform +import com.google.gson.JsonArray +import com.google.gson.JsonParser +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.util.UUID +import kotlin.reflect.jvm.jvmErasure + +class DeveloperEndpoints(private val context: Context) { + private val TAG = "DeveloperEndpoints"; + private val _client = ManagedHttpClient(); + private var _testPlugin: V8Plugin? = null; + private val testPluginOrThrow: V8Plugin get() = _testPlugin ?: throw IllegalStateException("Attempted to use test plugin without plugin"); + private val _testPluginVariables: HashMap = hashMapOf(); + + private inline fun createRemoteObjectArray(objs: Iterable): List { + val remotes = mutableListOf(); + for(obj in objs) + remotes.add(createRemoteObject(obj)!!); + return remotes; + } + private inline fun createRemoteObject(obj: T): V8RemoteObject? { + if(obj == null) + return null; + + val id = UUID.randomUUID().toString(); + val robj = V8RemoteObject(id, obj as Any); + if(robj.requiresRegistration) { + synchronized(_testPluginVariables) { + _testPluginVariables.put(id, robj); + } + } + return robj; + } + private inline fun getRemoteObjectOrCreate(obj: T): V8RemoteObject? { + if(obj == null) + return null; + + var instance: V8RemoteObject? = getRemoteObjectByInstance(obj as Any); + if(instance == null) + instance = createRemoteObject(obj); + return instance!!; + } + private fun getRemoteObject(id: String): V8RemoteObject { + synchronized(_testPluginVariables) { + if(!_testPluginVariables.containsKey(id)) + throw IllegalArgumentException("Remote object [${id}] does not exist"); + return _testPluginVariables[id]!!; + } + } + private fun getRemoteObjectByInstance(obj: Any): V8RemoteObject? { + + synchronized(_testPluginVariables) { + return _testPluginVariables.values.firstOrNull { it.obj == obj }; + } + } + + + //Files + @HttpGET("/dev", "text/html") + val devTestHtml = StateAssets.readAsset(context, "devportal/index.html", true); + @HttpGET("/source.js", "application/javascript") + val devSourceJS = StateAssets.readAsset(context, "scripts/source.js", true); + @HttpGET("/dev_bridge.js", "application/javascript") + val devBridgeJS = StateAssets.readAsset(context, "devportal/dev_bridge.js", true); + @HttpGET("/source_docs.json", "application/json") + val devSourceDocsJson = Json.encodeToString(JSClient.getJSDocs()); + @HttpGET("/source_docs.js", "application/javascript") + val devSourceDocsJS = "const sourceDocs = $devSourceDocsJson"; + + //Dependencies + //@HttpGET("/dependencies/vue.js", "application/javascript") + //val depVue = StateAssets.readAsset(context, "devportal/dependencies/vue.js", true); + //@HttpGET("/dependencies/vuetify.js", "application/javascript") + //val depVuetify = StateAssets.readAsset(context, "devportal/dependencies/vuetify.js", true); + //@HttpGET("/dependencies/vuetify.min.css", "text/css") + //val depVuetifyCss = StateAssets.readAsset(context, "devportal/dependencies/vuetify.min.css", true); + @HttpGET("/dependencies/FutoMainLogo.svg", "image/svg+xml") + val depFutoLogo = StateAssets.readAsset(context, "devportal/dependencies/FutoMainLogo.svg", true); + + @HttpGET("/reference_plugin.d.ts", "text/plain") + fun devSourceTSWithRefs(httpContext: HttpContext) { + val builder = StringBuilder(); + + builder.appendLine("//Reference Scriptfile"); + builder.appendLine("//Intended exclusively for auto-complete in your IDE, not for execution"); + + builder.appendLine(StateAssets.readAsset(context, "devportal/plugin.d.ts", true)); + + httpContext.respondCode(200, builder.toString(), "text/plain"); + } + + @HttpGET("/reference_autocomplete.js", "application/javascript") + fun devSourceJSWithRefs(httpContext: HttpContext) { + val builder = StringBuilder(); + + builder.appendLine("//Reference Scriptfile"); + builder.appendLine("//Intended exclusively for auto-complete in your IDE, not for execution"); + + builder.appendLine(StateAssets.readAsset(context, "scripts/source.js", true)); + + for(pack in testPluginOrThrow.getPackages()) { + builder.appendLine(); + builder.appendLine("//Package ${pack.name} (variable: ${pack.variableName})"); + val props = V8RemoteObject.getV8Properties(pack::class); + val funcs = V8RemoteObject.getV8Functions(pack::class); + + if(!pack.variableName.isNullOrEmpty() && (props.isNotEmpty() || funcs.isNotEmpty())) { + builder.appendLine("let ${pack.variableName} = {"); + + val lastProp = props.lastOrNull(); + for(prop in props) { + builder.appendLine(" /**"); + builder.appendLine(" * @return {${prop.returnType.jvmErasure.simpleName}}"); + builder.appendLine(" **/"); + builder.append(" ${prop.name}: null"); + if(prop != lastProp || funcs.isNotEmpty()) + builder.append(",\n"); + else + builder.append(("\n")); + builder.appendLine(); + } + + val lastFunc = funcs.lastOrNull(); + for(func in funcs) { + builder.appendLine(" /**"); + for(para in func.parameters.subList(1, func.parameters.size)) + builder.appendLine(" * @param {${para.type.jvmErasure.simpleName}} ${para.name}"); + builder.appendLine(" * @return {${func.returnType.jvmErasure.simpleName}}"); + builder.appendLine(" **/"); + builder.append(" ${func.name}: function("); + + val lastPara = func.parameters.lastOrNull(); + for(para in func.parameters.subList(1, func.parameters.size)) { + builder.append("${para.name}"); + if(para != lastPara) + builder.append(", "); + } + builder.append(") {}"); + if(func != lastFunc || funcs.isNotEmpty()) + builder.append(",\n"); + else + builder.append(("\n")); + builder.appendLine(); + } + + builder.appendLine("}"); + } + } + httpContext.respondCode(200, builder.toString(), "application/javascript"); + } + + + + @HttpPOST("/plugin/getWarnings") + fun plugin_getWarnings(context: HttpContext) { + val config = context.readContentJson() + context.respondJson(200, config.getWarnings()) + } + + //Testing + @HttpPOST("/plugin/updateTestPlugin") + fun pluginUpdateTestPlugin(context: HttpContext) { + val config = context.readContentJson() + try { + _testPluginVariables.clear(); + _testPlugin = V8Plugin(StateApp.instance.context, config); + context.respondJson(200, testPluginOrThrow.getPackageVariables()); + } + catch(ex: Throwable) { + context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain") + } + } + @HttpPOST("/plugin/cleanTestPlugin") + fun pluginCleanTestPlugin(context: HttpContext) { + try { + _testPluginVariables.clear(); + context.respondCode(200); + } + catch(ex: Throwable) { + context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain") + } + } + @HttpGET("/plugin/loginTestPlugin") + fun pluginLoginTestPlugin(context: HttpContext) { + val config = _testPlugin?.config as SourcePluginConfig; + try { + val authConfig = config.authentication; + if(authConfig == null) { + context.respondCode(403, "This plugin doesn't support auth"); + return; + } + LoginActivity.showLogin(StateApp.instance.context, config) { + _testPluginVariables.clear(); + _testPlugin = V8Plugin(StateApp.instance.context, config, null, JSHttpClient(null), JSHttpClient(null, it)); + + }; + context.respondCode(200, "Login started"); + } + catch(ex: Throwable) { + context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain") + } + } + @HttpGET("/plugin/logoutTestPlugin") + fun pluginLogoutTestPlugin(context: HttpContext) { + val config = _testPlugin?.config as SourcePluginConfig; + try { + _testPluginVariables.clear(); + _testPlugin = V8Plugin(StateApp.instance.context, config, null); + context.respondCode(200, "Logged out"); + } + catch(ex: Throwable) { + context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain") + } + } + + @HttpGET("/plugin/isLoggedIn") + fun pluginIsLoggedIn(context: HttpContext) { + try { + val isLoggedIn = _testPlugin?.httpClientAuth is JSHttpClient && (_testPlugin?.httpClientAuth as JSHttpClient).isLoggedIn; + context.respondCode(200, if(isLoggedIn) "true" else "false", "application/json"); + } + catch(ex: Throwable) { + context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain") + } + } + + @HttpGET("/plugin/packageGet") + fun pluginPackageGet(context: HttpContext) { + val variableName = context.query.get("variable") + try { + if(variableName.isNullOrEmpty()) { + context.respondCode(400, "Missing variable name"); + return; + } + val pack = testPluginOrThrow.getPackageByVariableName(variableName); + context.respondCode(200, getRemoteObjectOrCreate(pack)?.serialize() ?: "null", "application/json"); + } + catch(ex: Throwable) { + //Logger.e("Developer Endpoints", "Failed to fetch packageGet:", ex); + context.respondCode(500, ex::class.simpleName + ":" + ex.message ?: "", "text/plain") + } + } + @HttpPOST("/plugin/remoteCall") + fun pluginRemoteCall(context: HttpContext) { + try { + val parameters = context.readContentString(); + val objId = context.query.get("id") + val method = context.query.get("method") + + if(objId.isNullOrEmpty()) { + context.respondCode(400, "Missing object id"); + return; + } + if(method.isNullOrEmpty()) { + context.respondCode(400, "Missing method"); + return; + } + val remoteObj = getRemoteObject(objId); + val paras = JsonParser.parseString(parameters); + if(!paras.isJsonArray) + throw IllegalArgumentException("Expected json array as body"); + if(method != "isLoggedIn") + Logger.i(TAG, "Remote Call [${objId}].${method}(...)"); + val callResult = remoteObj.call(method, paras as JsonArray); + val json = wrapRemoteResult(callResult, false); + context.respondCode(200, json, "application/json"); + } + catch(ilEx: IllegalArgumentException) { + if(ilEx.message?.contains("does not exist") ?: false) { + context.respondCode(400, ilEx.message ?: "", "text/plain"); + } + else { + Logger.e("DeveloperEndpoints", ilEx.message, ilEx); + context.respondCode(500, ilEx::class.simpleName + ":" + ilEx.message ?: "", "text/plain") + } + } + catch(ex: Throwable) { + Logger.e("DeveloperEndpoints", ex.message, ex); + context.respondCode(500, ex::class.simpleName + ":" + ex.message ?: "", "text/plain") + } + } + @HttpGET("/plugin/remoteProp") + fun pluginRemoteProp(context: HttpContext) { + val objId = context.query.get("id") + val prop = context.query.get("prop") + try { + if(objId.isNullOrEmpty()) { + context.respondCode(400, "Missing variable name"); + return; + } + if(prop.isNullOrEmpty()) { + context.respondCode(400, "Missing prop name"); + return; + } + val remoteObj = getRemoteObject(objId); + Logger.i(TAG, "Remote Prop [${objId}].${prop}(...)"); + + //TODO: Determine if we should get existing or always create new + val callResult = remoteObj.prop(prop); + val json = wrapRemoteResult(callResult, true); + context.respondCode(200, json, "application/json"); + } + catch(ilEx: IllegalArgumentException) { + if(ilEx.message?.contains("does not exist") ?: false) { + context.respondCode(400, ilEx.message ?: "", "text/plain"); + } + else { + Logger.e("DeveloperEndpoints", ilEx.message, ilEx); + context.respondCode(500, ilEx::class.simpleName + ":" + ilEx.message ?: "", "text/plain") + } + } + catch(ex: Throwable) { + Logger.e("DeveloperEndpoints", ex.message, ex); + context.respondCode(500, ex::class.simpleName + ":" + ex.message ?: "", "text/plain") + } + } + + private fun wrapRemoteResult(callResult: Any?, useCached: Boolean = false): String { + return if(callResult == null) + "null"; + else if(callResult.javaClass.isPrimitive || callResult.javaClass == String::class.java) + gsonStandard.toJson(callResult); + else if(callResult is Iterable<*> && callResult.count() == 0) + return "[]"; + else if(callResult is Iterable<*>) { + val firstItemType = callResult.first()!!.javaClass; + if(firstItemType.isPrimitive || firstItemType == String::class.java) + return gsonStandard.toJson(callResult); + else + createRemoteObjectArray(callResult).serialize(); + } + else if(useCached) + getRemoteObjectOrCreate(callResult)?.serialize() ?: "null"; + else + createRemoteObject(callResult)?.serialize() ?: "null"; + } + + + + //Integration + @HttpPOST("/plugin/loadDevPlugin") + fun pluginLoadDevPlugin(context: HttpContext) { + val config = context.readContentJson() + try { + val script = _client.get(config.absoluteScriptUrl!!); + if(!script.isOk) + throw IllegalStateException("URL ${config.scriptUrl} return code ${script.code}"); + if(script.body == null) + throw IllegalStateException("URL ${config.scriptUrl} return no body"); + + val id = StatePlatform.instance.injectDevPlugin(config, script.body.string()); + context.respondJson(200, id); + } + catch(ex: Exception) { + Logger.e("DeveloperEndpoints", ex.message, ex); + context.respondCode(500, ex::class.simpleName + ":" + ex.message ?: "", "text/plain") + } + } + + @HttpGET("/plugin/getDevLogs") + fun pluginGetDevLogs(context: HttpContext) { + try { + val index = context.query.getOrDefault("index", "0").toInt(); + context.respondJson(200, StateDeveloper.instance.getLogs(index)); + } + catch(ex: Exception) { + context.respondCode(500, ex.message ?: "", "text/plain") + } + } + @HttpGET("/plugin/fakeDevLog") + fun pluginFakeDevLog(context: HttpContext) { + try { + val type = context.query.getOrDefault("type", "INFO"); + val devId = context.query.getOrDefault("devId", ""); + val msg = context.query.getOrDefault("msg", ""); + when(type) { + "INFO" -> StateDeveloper.instance.logDevInfo(devId, msg); + "EXCEPTION" -> StateDeveloper.instance.logDevException(devId, msg); + } + context.respondCode(200); + } + catch(ex: Exception) { + context.respondCode(500, ex.message ?: "", "text/plain") + } + } + + //Internal calls + @HttpPOST("/get") + fun get(context: HttpContext) { + try{ + val body = context.readContentJson(); + if(body.url == null) + throw IllegalStateException("Missing url"); + + val resp = _client.get(body.url!!, body.headers); + + context.respondCode(200, + Json.encodeToString(PackageHttp.BridgeHttpResponse(resp.code, resp.body?.string())), + context.query.getOrDefault("CT", "text/plain")); + } + catch(ex: Exception) { + context.respondCode(500, ex.message ?: "", "text/plain"); + } + } + + @kotlinx.serialization.Serializable + class BridgeHttpRequest() { + var url: String? = null; + var headers: MutableMap = HashMap(); + var contentType: String = ""; + var body: String? = null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/AutoUpdateDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/AutoUpdateDialog.kt new file mode 100644 index 00000000..452b290f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/dialogs/AutoUpdateDialog.kt @@ -0,0 +1,208 @@ +package com.futo.platformplayer.dialogs + +import android.app.AlertDialog +import android.app.PendingIntent.* +import android.content.Context +import android.content.Intent +import android.content.pm.PackageInstaller +import android.graphics.drawable.Animatable +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager +import android.widget.Button +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.* +import com.futo.platformplayer.receivers.InstallReceiver +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateUpdate +import kotlinx.coroutines.* +import java.io.File +import java.io.InputStream + +class AutoUpdateDialog(context: Context?) : AlertDialog(context) { + companion object { + private val TAG = "AutoUpdateDialog"; + } + + private lateinit var _buttonNever: Button; + private lateinit var _buttonClose: Button; + private lateinit var _buttonUpdate: LinearLayout; + private lateinit var _text: TextView; + private lateinit var _textProgress: TextView; + private lateinit var _updateSpinner: ImageView; + private lateinit var _buttonShowChangelog: Button; + private var _maxVersion: Int = 0; + + private var _updating: Boolean = false; + private var _apkFile: File? = null; + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(LayoutInflater.from(context).inflate(R.layout.dialog_update, null)); + + _buttonNever = findViewById(R.id.button_never); + _buttonClose = findViewById(R.id.button_close); + _buttonUpdate = findViewById(R.id.button_update); + _text = findViewById(R.id.text_dialog); + _textProgress = findViewById(R.id.text_progress); + _updateSpinner = findViewById(R.id.update_spinner); + _buttonShowChangelog = findViewById(R.id.button_show_changelog); + + _buttonNever.setOnClickListener { + Settings.instance.autoUpdate.check = 1; + Settings.instance.save(); + dismiss(); + }; + + _buttonClose.setOnClickListener { + dismiss(); + }; + + _buttonShowChangelog.setOnClickListener { + dismiss(); + UIDialogs.showChangelogDialog(context, _maxVersion); + }; + + _buttonUpdate.setOnClickListener { + if (_updating) { + return@setOnClickListener; + } + + _updating = true; + update(); + }; + } + + fun showPredownloaded(apkFile: File) { + _apkFile = apkFile; + super.show() + } + + override fun dismiss() { + super.dismiss() + InstallReceiver.onReceiveResult.clear(); + Logger.i(TAG, "Cleared InstallReceiver.onReceiveResult handler.") + } + + private fun update() { + _buttonShowChangelog.visibility = Button.GONE; + _buttonNever.visibility = Button.GONE; + _buttonClose.visibility = Button.GONE; + _buttonUpdate.visibility = Button.GONE; + setCancelable(false); + setCanceledOnTouchOutside(false); + window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + _text.text = context.resources.getText(R.string.downloading_update); + (_updateSpinner?.drawable as Animatable?)?.start(); + + GlobalScope.launch(Dispatchers.IO) { + var inputStream: InputStream? = null; + try { + val apkFile = _apkFile; + if (apkFile != null) { + inputStream = apkFile.inputStream(); + val dataLength = apkFile.length(); + install(inputStream, dataLength); + } else { + val client = ManagedHttpClient(); + val response = client.get(StateUpdate.APK_URL); + if (response.isOk && response.body != null) { + inputStream = response.body.byteStream(); + val dataLength = response.body.contentLength(); + install(inputStream, dataLength); + } else { + throw Exception("Failed to download latest version of app."); + } + } + } catch (e: Throwable) { + Logger.w(TAG, "Exception thrown while downloading and installing latest version of app.", e); + withContext(Dispatchers.Main) { + onReceiveResult("Failed to download update."); + } + } finally { + inputStream?.close(); + } + } + } + + private suspend fun install(inputStream: InputStream, dataLength: Long) { + var lastProgressText = ""; + var session: PackageInstaller.Session? = null; + + try { + Logger.i(TAG, "Hooked InstallReceiver.onReceiveResult.") + InstallReceiver.onReceiveResult.subscribe(this) { message -> onReceiveResult(message); }; + + val packageInstaller: PackageInstaller = context.packageManager.packageInstaller; + val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL); + val sessionId = packageInstaller.createSession(params); + session = packageInstaller.openSession(sessionId) + + session.openWrite("package", 0, dataLength).use { sessionStream -> + inputStream.copyToOutputStream(dataLength, sessionStream) { progress -> + val progressText = "${(progress * 100.0f).toInt()}%"; + if (lastProgressText != progressText) { + lastProgressText = progressText; + + //TODO: Use proper scope + GlobalScope.launch(Dispatchers.Main) { + _textProgress.text = progressText; + }; + } + } + + session.fsync(sessionStream); + }; + + val intent = Intent(context, InstallReceiver::class.java); + val pendingIntent = getBroadcast(context, 0, intent, FLAG_MUTABLE or FLAG_UPDATE_CURRENT); + val statusReceiver = pendingIntent.intentSender; + + session.commit(statusReceiver); + session.close(); + + withContext(Dispatchers.Main) { + _textProgress.text = ""; + _text.text = context.resources.getText(R.string.installing_update); + } + } catch (e: Throwable) { + Logger.w(TAG, "Exception thrown while downloading and installing latest version of app.", e); + session?.abandon(); + withContext(Dispatchers.Main) { + onReceiveResult("Failed to download update."); + } + } + finally { + withContext(Dispatchers.Main) { + window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + } + + private fun onReceiveResult(result: String?) { + InstallReceiver.onReceiveResult.remove(this); + Logger.i(TAG, "Cleared InstallReceiver.onReceiveResult handler."); + + setCancelable(true); + setCanceledOnTouchOutside(true); + _buttonClose.visibility = View.VISIBLE; + (_updateSpinner?.drawable as Animatable?)?.stop(); + + if (result == null || result.isBlank()) { + _updateSpinner.setImageResource(R.drawable.ic_update_success_251dp); + _text.text = context.resources.getText(R.string.success); + } else { + _updateSpinner.setImageResource(R.drawable.ic_update_fail_251dp); + _text.text = "${context.resources.getText(R.string.failed_to_update_with_error)}: '${result}'."; + } + } + + fun setMaxVersion(version: Int) { + _maxVersion = version; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/AutomaticBackupDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/AutomaticBackupDialog.kt new file mode 100644 index 00000000..29a8a3c2 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/dialogs/AutomaticBackupDialog.kt @@ -0,0 +1,88 @@ +package com.futo.platformplayer.dialogs + +import android.app.AlertDialog +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.WindowManager +import android.view.inputmethod.InputMethodManager +import android.widget.* +import com.futo.platformplayer.R +import com.futo.platformplayer.Settings +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateBackup +import com.google.android.material.button.MaterialButton + + +class AutomaticBackupDialog(context: Context) : AlertDialog(context) { + private lateinit var _buttonStart: LinearLayout; + private lateinit var _buttonStop: LinearLayout; + private lateinit var _buttonCancel: ImageButton; + + private lateinit var _editPassword: EditText; + + private lateinit var _inputMethodManager: InputMethodManager; + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(LayoutInflater.from(context).inflate(R.layout.dialog_automatic_backup, null)); + + _buttonCancel = findViewById(R.id.button_cancel); + _buttonStop = findViewById(R.id.button_stop); + _buttonStart = findViewById(R.id.button_start); + _editPassword = findViewById(R.id.edit_password); + + _inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager; + + _buttonCancel.setOnClickListener { + clearFocus(); + dismiss(); + }; + _buttonStop.setOnClickListener { + clearFocus(); + dismiss(); + Settings.instance.backup.autoBackupPassword = null; + Settings.instance.backup.didAskAutoBackup = true; + Settings.instance.save(); + + UIDialogs.toast(context, "AutoBackup disabled"); + } + + _buttonStart.setOnClickListener { + val pbytes = _editPassword.text.toString().toByteArray(); + if(pbytes.size < 4 || pbytes.size > 32) { + UIDialogs.toast(context, "Password needs to be atleast 4 bytes long and smaller than 32 bytes", false); + return@setOnClickListener; + } + clearFocus(); + dismiss(); + Logger.i(TAG, "Set AutoBackupPassword"); + Settings.instance.backup.autoBackupPassword = _editPassword.text.toString(); + Settings.instance.backup.didAskAutoBackup = true; + Settings.instance.save(); + + UIDialogs.toast(context, "AutoBackup enabled"); + + try { + StateBackup.startAutomaticBackup(true); + } + catch(ex: Throwable) { + Logger.e(TAG, "Forced automatic backup failed", ex); + UIDialogs.toast(context, "Automatic backup failed due to:\n" + ex.message); + } + }; + + window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + } + + private fun clearFocus() { + _editPassword.clearFocus(); + currentFocus?.let { _inputMethodManager.hideSoftInputFromWindow(it.windowToken, 0) }; + } + + companion object { + private val TAG = "AutomaticBackupDialog"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/AutomaticRestoreDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/AutomaticRestoreDialog.kt new file mode 100644 index 00000000..80842f24 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/dialogs/AutomaticRestoreDialog.kt @@ -0,0 +1,89 @@ +package com.futo.platformplayer.dialogs + +import android.app.AlertDialog +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.WindowManager +import android.view.inputmethod.InputMethodManager +import android.widget.* +import com.futo.platformplayer.R +import com.futo.platformplayer.Settings +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment +import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.dp +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.selectBestImage +import com.futo.platformplayer.states.StateAnnouncement +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StateBackup +import com.futo.platformplayer.states.StatePolycentric +import com.futo.polycentric.core.* +import com.google.android.material.button.MaterialButton +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import userpackage.Protocol +import java.time.OffsetDateTime + + +class AutomaticRestoreDialog(context: Context, val scope: CoroutineScope) : AlertDialog(context) { + private lateinit var _buttonStart: LinearLayout; + private lateinit var _buttonCancel: MaterialButton; + + private lateinit var _editPassword: EditText; + + private lateinit var _inputMethodManager: InputMethodManager; + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(LayoutInflater.from(context).inflate(R.layout.dialog_automatic_backup_restore, null)); + + _buttonCancel = findViewById(R.id.button_cancel); + _buttonStart = findViewById(R.id.button_start); + _editPassword = findViewById(R.id.edit_password); + + _inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager; + + _buttonCancel.setOnClickListener { + clearFocus(); + dismiss(); + }; + + _buttonStart.setOnClickListener { + val pbytes = _editPassword.text.toString().toByteArray(); + if(pbytes.size < 4 || pbytes.size > 32) { + UIDialogs.toast(context, "Password needs to be atleast 4 bytes long and less than 32 bytes", false); + return@setOnClickListener; + } + clearFocus(); + + try { + StateBackup.restoreAutomaticBackup(context, scope, _editPassword.text.toString(), true); + dismiss(); + } + catch(ex: Throwable) { + Logger.e(TAG, "Failed to restore automatic backup", ex); + //UIDialogs.toast(context, "Restore failed due to:\n" + ex.message); + UIDialogs.showGeneralErrorDialog(context, "Restore failed", ex); + } + }; + + window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + } + + private fun clearFocus() { + _editPassword.clearFocus(); + currentFocus?.let { _inputMethodManager.hideSoftInputFromWindow(it.windowToken, 0) }; + } + + companion object { + private val TAG = "AutomaticRestoreDialog"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/CastingAddDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/CastingAddDialog.kt new file mode 100644 index 00000000..9966e40a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/dialogs/CastingAddDialog.kt @@ -0,0 +1,138 @@ +package com.futo.platformplayer.dialogs + +import android.app.AlertDialog +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager +import android.widget.* +import com.futo.platformplayer.R +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.casting.CastProtocolType +import com.futo.platformplayer.casting.StateCasting +import com.futo.platformplayer.models.CastingDeviceInfo +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.toInetAddress +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + + +class CastingAddDialog(context: Context?) : AlertDialog(context) { + private lateinit var _spinnerType: Spinner; + private lateinit var _editName: EditText; + private lateinit var _editIP: EditText; + private lateinit var _editPort: EditText; + private lateinit var _textError: TextView; + private lateinit var _buttonCancel: Button; + private lateinit var _buttonConfirm: LinearLayout; + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(LayoutInflater.from(context).inflate(R.layout.dialog_casting_add, null)); + + _spinnerType = findViewById(R.id.spinner_type); + _editName = findViewById(R.id.edit_name); + _editIP = findViewById(R.id.edit_ip); + _editPort = findViewById(R.id.edit_port); + _textError = findViewById(R.id.text_error); + _buttonCancel = findViewById(R.id.button_cancel); + _buttonConfirm = findViewById(R.id.button_confirm); + + ArrayAdapter.createFromResource(context, R.array.casting_device_type_array, R.layout.spinner_item_simple).also { adapter -> + adapter.setDropDownViewResource(R.layout.spinner_dropdownitem_simple); + _spinnerType.adapter = adapter; + }; + + _buttonCancel.setOnClickListener { + performDismiss(); + } + + _spinnerType.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>, view: View?, pos: Int, id: Long) { + _editPort.text.clear(); + _editPort.text.append(when (_spinnerType.selectedItemPosition) { + 0 -> "46899" //FastCast + 1 -> "8009" //ChromeCast + else -> "" + }); + } + override fun onNothingSelected(parent: AdapterView<*>?) = Unit + }; + + _buttonConfirm.setOnClickListener { + val castProtocolType: CastProtocolType = when (_spinnerType.selectedItemPosition) { + 0 -> CastProtocolType.FASTCAST + 1 -> CastProtocolType.CHROMECAST + 2 -> CastProtocolType.AIRPLAY + else -> { + _textError.text = "Device type is invalid expected values like FastCast or ChromeCast."; + _textError.visibility = View.VISIBLE; + return@setOnClickListener; + } + }; + + val name = _editName.text.toString().trim(); + if (name.isNullOrBlank()) { + _textError.text = "Name can not be empty."; + _textError.visibility = View.VISIBLE; + return@setOnClickListener; + } + + val ip = _editIP.text.toString().trim(); + if (ip.isNullOrBlank()) { + _textError.text = "IP can not be empty."; + _textError.visibility = View.VISIBLE; + return@setOnClickListener; + } + + val address = ip.toInetAddress(); + if (address == null) { + _textError.text = "IP address is invalid, expected an IPv4 or IPv6 address."; + _textError.visibility = View.VISIBLE; + return@setOnClickListener; + } + + val port: UShort? = _editPort.text.toString().trim().toUShortOrNull(); + if (port == null) { + _textError.text = "Port number is invalid, expected a number between 0 and 65535."; + _textError.visibility = View.VISIBLE; + return@setOnClickListener; + } + + _textError.visibility = View.GONE; + val castingDeviceInfo = CastingDeviceInfo(name, castProtocolType, arrayOf(ip), port.toInt()); + StateCasting.instance.addRememberedDevice(castingDeviceInfo); + performDismiss(); + }; + } + + override fun show() { + super.show(); + + _spinnerType.setSelection(0); + _editPort.text.clear(); + _editPort.text.append("46899"); + _editIP.text.clear(); + _editName.text.clear(); + _textError.visibility = View.GONE; + + window?.apply { + clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) + clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) + setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) + }; + } + + private fun performDismiss(shouldShowCastingDialog: Boolean = true) { + if (shouldShowCastingDialog) { + UIDialogs.showCastingDialog(context); + } + + dismiss(); + } + + companion object { + private val TAG = "CastingAddDialog"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/ChangelogDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/ChangelogDialog.kt new file mode 100644 index 00000000..38f4f65a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/dialogs/ChangelogDialog.kt @@ -0,0 +1,134 @@ +package com.futo.platformplayer.dialogs + +import android.app.AlertDialog +import android.app.PendingIntent.* +import android.content.Context +import android.content.Intent +import android.content.pm.PackageInstaller +import android.graphics.drawable.Animatable +import android.os.Bundle +import android.text.method.ScrollingMovementMethod +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager +import android.widget.Button +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.lifecycle.lifecycleScope +import com.futo.platformplayer.* +import com.futo.platformplayer.receivers.InstallReceiver +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.fragment.mainactivity.main.ChannelFragment +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StateUpdate +import kotlinx.coroutines.* +import java.io.File +import java.io.InputStream + +class ChangelogDialog(context: Context?) : AlertDialog(context) { + companion object { + private val TAG = "ChangelogDialog"; + } + + private lateinit var _textVersion: TextView; + private lateinit var _textChangelog: TextView; + private lateinit var _buttonPrevious: Button; + private lateinit var _buttonNext: Button; + private lateinit var _buttonClose: Button; + private lateinit var _buttonUpdate: LinearLayout; + private lateinit var _imageSpinner: ImageView; + private var _isLoading: Boolean = false; + private var _version: Int = 0; + private var _maxVersion: Int = 0; + private var _managedHttpClient = ManagedHttpClient(); + + private val _taskDownloadChangelog = TaskHandler(StateApp.instance.scopeGetter, { version -> StateUpdate.instance.downloadChangelog(_managedHttpClient, version) }) + .success { setChangelog(it); } + .exception { + Logger.w(TAG, "Failed to load changelog.", it); + setChangelog(null); + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(LayoutInflater.from(context).inflate(R.layout.dialog_changelog, null)); + + _textVersion = findViewById(R.id.text_version); + _textChangelog = findViewById(R.id.text_changelog); + _buttonPrevious = findViewById(R.id.button_previous); + _buttonNext = findViewById(R.id.button_next); + _buttonClose = findViewById(R.id.button_close); + _buttonUpdate = findViewById(R.id.button_update); + _imageSpinner = findViewById(R.id.image_spinner); + + _textChangelog.movementMethod = ScrollingMovementMethod(); + + _buttonPrevious.setOnClickListener { + setVersion(Math.max(0, _version - 1)); + }; + + _buttonNext.setOnClickListener { + setVersion(Math.min(_maxVersion, _version + 1)); + }; + + _buttonClose.setOnClickListener { + dismiss(); + }; + + _buttonUpdate.setOnClickListener { + UIDialogs.showUpdateAvailableDialog(context, _maxVersion); + dismiss(); + }; + } + + override fun dismiss() { + _taskDownloadChangelog.cancel(); + super.dismiss() + } + + fun setMaxVersion(version: Int) { + _maxVersion = version; + setVersion(version); + + val currentVersion = BuildConfig.VERSION_CODE; + _buttonUpdate.visibility = if (currentVersion == _maxVersion) View.GONE else View.VISIBLE; + } + + private fun setVersion(version: Int) { + if (_version == version) { + return; + } + + _version = version; + _buttonPrevious.visibility = if (_version == 0) View.GONE else View.VISIBLE; + _buttonNext.visibility = if (_version == _maxVersion) View.GONE else View.VISIBLE; + _textVersion.text = version.toString(); + setIsLoading(true); + _taskDownloadChangelog.run(_version); + } + + private fun setChangelog(text: String?) { + _textChangelog.text = text ?: "There is no changelog available for this version."; + setIsLoading(false); + } + + private fun setIsLoading(isLoading: Boolean) { + if (isLoading) { + _imageSpinner.visibility = View.VISIBLE; + _textChangelog.visibility = View.GONE; + (_imageSpinner.drawable as Animatable?)?.start(); + } else { + (_imageSpinner.drawable as Animatable?)?.stop(); + _imageSpinner.visibility = View.GONE; + _textChangelog.visibility = View.VISIBLE; + } + + _isLoading = false; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/CommentDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/CommentDialog.kt new file mode 100644 index 00000000..1d401348 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/dialogs/CommentDialog.kt @@ -0,0 +1,104 @@ +package com.futo.platformplayer.dialogs + +import android.app.AlertDialog +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.WindowManager +import android.view.inputmethod.InputMethodManager +import android.widget.* +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment +import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.dp +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.selectBestImage +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StatePolycentric +import com.futo.polycentric.core.* +import com.google.android.material.button.MaterialButton +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import userpackage.Protocol +import java.time.OffsetDateTime + + +class CommentDialog(context: Context?, val contextUrl: String, val ref: Protocol.Reference) : AlertDialog(context) { + private lateinit var _buttonCreate: LinearLayout; + private lateinit var _buttonCancel: MaterialButton; + private lateinit var _editComment: EditText; + private lateinit var _inputMethodManager: InputMethodManager; + + val onCommentAdded = Event1(); + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(LayoutInflater.from(context).inflate(R.layout.dialog_comment, null)); + + _buttonCancel = findViewById(R.id.button_cancel); + _buttonCreate = findViewById(R.id.button_create); + _editComment = findViewById(R.id.edit_comment); + + _inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager; + + _buttonCancel.setOnClickListener { + clearFocus(); + dismiss(); + }; + + _buttonCreate.setOnClickListener { + clearFocus(); + + val comment = _editComment.text.toString(); + val processHandle = StatePolycentric.instance.processHandle!! + val eventPointer = processHandle.post(comment, null, ref) + + StateApp.instance.scopeGetter().launch(Dispatchers.IO) { + try { + processHandle.fullyBackfillServers() + } catch (e: Throwable) { + Logger.e(TAG, "Failed to backfill servers.", e); + } + } + val systemState = SystemState.fromStorageTypeSystemState(Store.instance.getSystemState(processHandle.system)) + val dp_25 = 25.dp(context.resources) + onCommentAdded.emit(PolycentricPlatformComment( + contextUrl = contextUrl, + author = PlatformAuthorLink( + id = PlatformID("polycentric", processHandle.system.systemToURLInfoSystemLinkUrl(systemState.servers.toList()), null, ClaimType.POLYCENTRIC.value.toInt()), + name = systemState.username, + url = processHandle.system.systemToURLInfoSystemLinkUrl(systemState.servers.toList()), + thumbnail = systemState.avatar.selectBestImage(dp_25 * dp_25)?.toURLInfoSystemLinkUrl(processHandle, systemState.servers.toList()), + subscribers = null + ), + msg = comment, + rating = RatingLikeDislikes(0, 0), + date = OffsetDateTime.now(), + reference = eventPointer.toReference() + )); + + dismiss(); + }; + + window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + focus(); + } + + private fun focus() { + _editComment.requestFocus(); + window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + } + + private fun clearFocus() { + _editComment.clearFocus(); + currentFocus?.let { _inputMethodManager.hideSoftInputFromWindow(it.windowToken, 0) }; + } + + companion object { + private val TAG = "CommentDialog"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/ConnectCastingDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/ConnectCastingDialog.kt new file mode 100644 index 00000000..dca38091 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/dialogs/ConnectCastingDialog.kt @@ -0,0 +1,160 @@ +package com.futo.platformplayer.dialogs + +import android.app.AlertDialog +import android.content.Context +import android.graphics.drawable.Animatable +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.widget.Button +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.R +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.casting.CastConnectionState +import com.futo.platformplayer.casting.CastingDevice +import com.futo.platformplayer.casting.StateCasting +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.views.adapters.DeviceAdapter +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import java.util.UUID + +class ConnectCastingDialog(context: Context?) : AlertDialog(context) { + private lateinit var _imageLoader: ImageView; + private lateinit var _buttonClose: Button; + private lateinit var _buttonAdd: Button; + private lateinit var _textNoDevicesFound: TextView; + private lateinit var _textNoDevicesRemembered: TextView; + private lateinit var _recyclerDevices: RecyclerView; + private lateinit var _recyclerRememberedDevices: RecyclerView; + private lateinit var _adapter: DeviceAdapter; + private lateinit var _rememberedAdapter: DeviceAdapter; + private val _devices: ArrayList = arrayListOf(); + private val _rememberedDevices: ArrayList = arrayListOf(); + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(LayoutInflater.from(context).inflate(R.layout.dialog_casting_connect, null)); + + _imageLoader = findViewById(R.id.image_loader); + _buttonClose = findViewById(R.id.button_close); + _buttonAdd = findViewById(R.id.button_add); + _recyclerDevices = findViewById(R.id.recycler_devices); + _recyclerRememberedDevices = findViewById(R.id.recycler_remembered_devices); + _textNoDevicesFound = findViewById(R.id.text_no_devices_found); + _textNoDevicesRemembered = findViewById(R.id.text_no_devices_remembered); + + _adapter = DeviceAdapter(_devices, false); + _recyclerDevices.adapter = _adapter; + _recyclerDevices.layoutManager = LinearLayoutManager(context); + + _rememberedAdapter = DeviceAdapter(_rememberedDevices, true); + _rememberedAdapter.onRemove.subscribe { d -> + if (StateCasting.instance.activeDevice == d) { + d.stopCasting(); + } + + StateCasting.instance.removeRememberedDevice(d); + val index = _rememberedDevices.indexOf(d); + if (index != -1) { + _rememberedDevices.removeAt(index); + _rememberedAdapter.notifyItemRemoved(index); + } + + _textNoDevicesRemembered.visibility = if (_rememberedDevices.isEmpty()) View.VISIBLE else View.GONE; + _recyclerRememberedDevices.visibility = if (_rememberedDevices.isNotEmpty()) View.VISIBLE else View.GONE; + }; + _recyclerRememberedDevices.adapter = _rememberedAdapter; + _recyclerRememberedDevices.layoutManager = LinearLayoutManager(context); + + _buttonClose.setOnClickListener { dismiss(); }; + _buttonAdd.setOnClickListener { + UIDialogs.showCastingAddDialog(context); + dismiss(); + }; + } + + override fun show() { + super.show(); + Logger.i(TAG, "Dialog shown."); + + (_imageLoader.drawable as Animatable?)?.start(); + + _devices.clear(); + synchronized (StateCasting.instance.devices) { + _devices.addAll(StateCasting.instance.devices.values); + } + + _rememberedDevices.clear(); + synchronized (StateCasting.instance.rememberedDevices) { + _rememberedDevices.addAll(StateCasting.instance.rememberedDevices); + } + + _textNoDevicesFound.visibility = if (_devices.isEmpty()) View.VISIBLE else View.GONE; + _recyclerDevices.visibility = if (_devices.isNotEmpty()) View.VISIBLE else View.GONE; + _textNoDevicesRemembered.visibility = if (_rememberedDevices.isEmpty()) View.VISIBLE else View.GONE; + _recyclerRememberedDevices.visibility = if (_rememberedDevices.isNotEmpty()) View.VISIBLE else View.GONE; + + StateCasting.instance.onDeviceAdded.subscribe(this) { d -> + _devices.add(d); + _adapter.notifyItemInserted(_devices.size - 1); + _textNoDevicesFound.visibility = View.GONE; + _recyclerDevices.visibility = View.VISIBLE; + }; + + StateCasting.instance.onDeviceChanged.subscribe(this) { d -> + val index = _devices.indexOf(d); + if (index == -1) { + return@subscribe; + } + + _devices[index] = d; + _adapter.notifyItemChanged(index); + }; + + StateCasting.instance.onDeviceRemoved.subscribe(this) { d -> + val index = _devices.indexOf(d); + if (index == -1) { + return@subscribe; + } + + _devices.removeAt(index); + _adapter.notifyItemRemoved(index); + _textNoDevicesFound.visibility = if (_devices.isEmpty()) View.VISIBLE else View.GONE; + _recyclerDevices.visibility = if (_devices.isNotEmpty()) View.VISIBLE else View.GONE; + }; + + StateCasting.instance.onActiveDeviceConnectionStateChanged.subscribe(this) { _, connectionState -> + if (connectionState != CastConnectionState.CONNECTED) { + return@subscribe; + } + + StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { + dismiss(); + }; + }; + + _adapter.notifyDataSetChanged(); + _rememberedAdapter.notifyDataSetChanged(); + } + + override fun dismiss() { + super.dismiss(); + + (_imageLoader.drawable as Animatable?)?.stop(); + + StateCasting.instance.onDeviceAdded.remove(this); + StateCasting.instance.onDeviceChanged.remove(this); + StateCasting.instance.onDeviceRemoved.remove(this); + StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this); + } + + companion object { + private val TAG = "CastingDialog"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/ConnectedCastingDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/ConnectedCastingDialog.kt new file mode 100644 index 00000000..878a14d9 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/dialogs/ConnectedCastingDialog.kt @@ -0,0 +1,128 @@ +package com.futo.platformplayer.dialogs + +import android.app.AlertDialog +import android.content.Context +import android.graphics.drawable.Animatable +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.widget.Button +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.R +import com.futo.platformplayer.casting.* +import com.futo.platformplayer.states.StateApp +import com.google.android.material.slider.Slider +import com.google.android.material.slider.Slider.OnChangeListener +import com.google.android.material.slider.Slider.OnSliderTouchListener +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + +class ConnectedCastingDialog(context: Context?) : AlertDialog(context) { + private lateinit var _buttonClose: Button; + private lateinit var _imageLoader: ImageView; + private lateinit var _imageDevice: ImageView; + private lateinit var _textName: TextView; + private lateinit var _textType: TextView; + private lateinit var _buttonDisconnect: LinearLayout; + private lateinit var _sliderVolume: Slider; + private lateinit var _layoutVolumeAdjustable: LinearLayout; + private lateinit var _layoutVolumeFixed: LinearLayout; + private var _device: CastingDevice? = null; + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(LayoutInflater.from(context).inflate(R.layout.dialog_casting_connected, null)); + + _imageLoader = findViewById(R.id.image_loader); + _buttonClose = findViewById(R.id.button_close); + _imageDevice = findViewById(R.id.image_device); + _textName = findViewById(R.id.text_name); + _textType = findViewById(R.id.text_type); + _buttonDisconnect = findViewById(R.id.button_disconnect); + _sliderVolume = findViewById(R.id.slider_volume); + _layoutVolumeAdjustable = findViewById(R.id.layout_volume_adjustable); + _layoutVolumeFixed = findViewById(R.id.layout_volume_fixed); + + _buttonClose.setOnClickListener { dismiss(); }; + _buttonDisconnect.setOnClickListener { + StateCasting.instance.activeDevice?.stopCasting(); + dismiss(); + }; + + _sliderVolume.addOnChangeListener(OnChangeListener { _, value, _ -> StateCasting.instance.activeDevice?.changeVolume(value.toDouble()); }); + + setLoading(false); + updateDevice(); + } + + override fun show() { + super.show(); + Logger.i(TAG, "Dialog shown."); + + _device?.onVolumeChanged?.remove(this); + _device?.onVolumeChanged?.subscribe { + _sliderVolume.value = it.toFloat(); + }; + + _device = StateCasting.instance.activeDevice; + val d = _device; + val isConnected = d != null && d.connectionState == CastConnectionState.CONNECTED; + setLoading(!isConnected); + StateCasting.instance.onActiveDeviceConnectionStateChanged.subscribe(this) { _, connectionState -> + StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { setLoading(connectionState != CastConnectionState.CONNECTED); }; + }; + + updateDevice(); + } + + override fun dismiss() { + super.dismiss(); + _device?.onVolumeChanged?.remove(this); + _device = null; + StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this); + } + + private fun updateDevice() { + val d = StateCasting.instance.activeDevice ?: return; + + if (d is ChromecastCastingDevice) { + _imageDevice.setImageResource(R.drawable.ic_chromecast); + _textType.text = "Chromecast"; + } else if (d is AirPlayCastingDevice) { + _imageDevice.setImageResource(R.drawable.ic_airplay); + _textType.text = "AirPlay"; + } else if (d is FastCastCastingDevice) { + _imageDevice.setImageResource(R.drawable.ic_fc); + _textType.text = "FastCast"; + } + + _textName.text = d.name; + _sliderVolume.value = d.volume.toFloat(); + + if (d.canSetVolume) { + _layoutVolumeAdjustable.visibility = View.VISIBLE; + _layoutVolumeFixed.visibility = View.GONE; + } else { + _layoutVolumeAdjustable.visibility = View.GONE; + _layoutVolumeFixed.visibility = View.VISIBLE; + } + } + + private fun setLoading(isLoading: Boolean) { + if (isLoading) { + _imageLoader.visibility = View.VISIBLE; + (_imageLoader.drawable as Animatable?)?.start(); + } else { + (_imageLoader.drawable as Animatable?)?.stop(); + _imageLoader.visibility = View.GONE; + } + } + + companion object { + private val TAG = "CastingDialog"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/ImportDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/ImportDialog.kt new file mode 100644 index 00000000..62305de1 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/dialogs/ImportDialog.kt @@ -0,0 +1,210 @@ +package com.futo.platformplayer.dialogs + +import android.app.AlertDialog +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.Animatable +import android.os.Bundle +import android.text.Spannable +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.text.method.ScrollingMovementMethod +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager +import android.widget.Button +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.stores.v2.ManagedStore +import kotlinx.coroutines.* + +class ImportDialog : AlertDialog { + companion object { + private val TAG = "ImportDialog"; + } + private val _context: Context; + + private lateinit var _buttonCancel: Button; + private lateinit var _buttonImport: LinearLayout; + + private lateinit var _buttonCancelImport: Button; + private lateinit var _buttonOk: LinearLayout; + private lateinit var _buttonRetry: Button; + + private lateinit var _import_name_text: TextView; + private lateinit var _import_type_text: TextView; + + private lateinit var _import_result_restored_text: TextView; + private lateinit var _import_result_failed_text: TextView; + private lateinit var _import_result_fplugin_text: TextView; + private lateinit var _import_result_failed_count_text: TextView; + + private lateinit var _uiChoiceTop: FrameLayout; + private lateinit var _uiProgressTop: FrameLayout; + + private lateinit var _uiChoiceBot: LinearLayout; + private lateinit var _uiResultBot: LinearLayout; + + private lateinit var _textProgress: TextView; + private lateinit var _updateSpinner: ImageView; + + private var _isImport: Boolean = false; + + private val _store: ManagedStore<*>; + private val _onConcluded: ()->Unit; + + private val _name: String; + private val _toImport: List; + + + constructor(context: Context, importStore: ManagedStore<*>, name: String, toReconstruct: List, onConcluded: ()->Unit): super(context) { + _context = context; + _store = importStore; + _onConcluded = onConcluded; + _name = name; + _toImport = ArrayList(toReconstruct); + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(LayoutInflater.from(context).inflate(R.layout.dialog_import, null)); + + _buttonCancel = findViewById(R.id.button_cancel); + _buttonImport = findViewById(R.id.button_import); + + _buttonOk = findViewById(R.id.button_ok); + _buttonCancelImport = findViewById(R.id.button_cancel_import); + _buttonRetry = findViewById(R.id.button_retry); + + _import_type_text = findViewById(R.id.import_type_text); + _import_name_text = findViewById(R.id.import_name_text); + + _import_result_restored_text = findViewById(R.id.import_result_restored_text); + _import_result_failed_text = findViewById(R.id.import_result_failed_text); + _import_result_fplugin_text = findViewById(R.id.import_result_fplugin_text); + _import_result_failed_count_text = findViewById(R.id.import_result_failed_count_text); + + _uiChoiceTop = findViewById(R.id.dialog_ui_choice_top); + _uiProgressTop = findViewById(R.id.dialog_ui_progress_top); + + _uiChoiceBot = findViewById(R.id.dialog_ui_bottom_choice); + _uiResultBot = findViewById(R.id.dialog_ui_bottom_result) + + _textProgress = findViewById(R.id.text_progress); + _updateSpinner = findViewById(R.id.update_spinner); + + val toMigrateCount = _store.getMissingReconstructionCount(); + _import_type_text.text = _store.name; + _import_name_text.text = _name; + + _import_result_failed_text.movementMethod = ScrollingMovementMethod.getInstance() + + _buttonCancel.setOnClickListener { + dismiss(); + }; + _buttonImport.setOnClickListener { + if (_isImport) + return@setOnClickListener; + _isImport = true; + import(); + }; + + _buttonRetry.setOnClickListener { + import(); + }; + } + + override fun dismiss() { + super.dismiss(); + _onConcluded.invoke(); + } + + private fun import() { + _uiChoiceTop.visibility = View.GONE; + _uiChoiceBot.visibility = View.GONE; + _uiResultBot.visibility = View.GONE; + _uiProgressTop.visibility = View.VISIBLE; + _textProgress.text = "0/${_store.getMissingReconstructionCount()}"; + + setCancelable(false); + setCanceledOnTouchOutside(false); + window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + _updateSpinner.drawable?.assume()?.start(); + + val scope = StateApp.instance.scopeOrNull; + scope?.launch(Dispatchers.IO) { + try { + val migrationResult = _store.importReconstructions(_toImport) { finished, total -> + scope.launch(Dispatchers.Main) { + _textProgress.text = "${finished}/${total}"; + } + }; + + withContext(Dispatchers.Main) { + try { + val realFailures = migrationResult.exceptions.filter { it !is NoPlatformClientException }; + val pluginFailures = migrationResult.exceptions.filter { it is NoPlatformClientException }; + + _import_result_restored_text.text = "Imported ${migrationResult.success} items"; + _import_result_fplugin_text.visibility = View.GONE; + + if(realFailures.isNotEmpty() || migrationResult.messages.isNotEmpty()) { + val messagesText = migrationResult.messages.map { it }.joinToString("\n") + (if(migrationResult.messages.isNotEmpty()) "\n" else ""); + val errorText = realFailures.map { it.message }.joinToString("\n"); + val spannable = SpannableString(messagesText + errorText); + spannable.setSpan(ForegroundColorSpan(Color.WHITE), 0, messagesText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + spannable.setSpan(ForegroundColorSpan(Color.RED), messagesText.length, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + _import_result_failed_text.text = spannable + _import_result_failed_text.visibility = View.VISIBLE; + } + else + _import_result_failed_text.visibility = View.GONE; + + if (realFailures.isEmpty()) { + _import_result_failed_count_text.visibility = View.GONE; + _buttonCancelImport.visibility = View.GONE; + _buttonRetry.visibility = View.GONE; + } else { + _import_result_failed_count_text.visibility = View.VISIBLE; + _import_result_failed_count_text.text = "(${migrationResult.exceptions.size} failed)" + _buttonCancelImport.visibility = View.VISIBLE; + _buttonRetry.visibility = View.VISIBLE; + } + + if(pluginFailures.isEmpty()) { + _import_result_fplugin_text.visibility = View.GONE; + } else { + _import_result_fplugin_text.visibility = View.VISIBLE; + _import_result_fplugin_text.text = "Plugin not enabled for ${pluginFailures} items"; + } + + _buttonCancelImport.setOnClickListener { + dismiss(); + }; + _buttonOk.setOnClickListener { + if(migrationResult.exceptions.size > 0) + UIDialogs.toast(_context, "${migrationResult.exceptions.size} items will be invisible\nWe will ask again next boot"); + dismiss(); + } + + _uiProgressTop.visibility = View.GONE; + _uiChoiceTop.visibility = View.VISIBLE; + _uiResultBot.visibility = View.VISIBLE; + } catch (e: Throwable) { + Logger.e(TAG, "Failed to update import UI.", e) + } + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to import reconstruction.", e) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/MigrateDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/MigrateDialog.kt new file mode 100644 index 00000000..e933a425 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/dialogs/MigrateDialog.kt @@ -0,0 +1,223 @@ +package com.futo.platformplayer.dialogs + +import android.app.AlertDialog +import android.app.PendingIntent.* +import android.content.Context +import android.content.Intent +import android.content.pm.PackageInstaller +import android.graphics.Color +import android.graphics.drawable.Animatable +import android.os.Bundle +import android.text.Spannable +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.text.method.ScrollingMovementMethod +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager +import android.widget.Button +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.* +import com.futo.platformplayer.receivers.InstallReceiver +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StateUpdate +import com.futo.platformplayer.stores.v2.ManagedStore +import kotlinx.coroutines.* + +class MigrateDialog : AlertDialog { + companion object { + private val TAG = "MigrateDialog"; + } + private val _context: Context; + + private lateinit var _buttonIgnore: Button; + private lateinit var _buttonDelete: Button; + private lateinit var _buttonRestore: LinearLayout; + + private lateinit var _buttonDeleteFailed: Button; + private lateinit var _buttonOk: LinearLayout; + private lateinit var _buttonRetry: Button; + + private lateinit var _migrate_type_text: TextView; + private lateinit var _migrate_count_text: TextView; + + private lateinit var _migrate_result_restored_text: TextView; + private lateinit var _migrate_result_failed_text: TextView; + private lateinit var _migrate_result_fplugin_text: TextView; + private lateinit var _migrate_result_failed_count_text: TextView; + + private lateinit var _uiChoiceTop: FrameLayout; + private lateinit var _uiProgressTop: FrameLayout; + + private lateinit var _uiChoiceBot: LinearLayout; + private lateinit var _uiResultBot: LinearLayout; + + private lateinit var _textProgress: TextView; + private lateinit var _updateSpinner: ImageView; + + private var _isRestoring: Boolean = false; + + private val _store: ManagedStore<*>; + private val _onConcluded: ()->Unit; + + + constructor(context: Context, toMigrate: ManagedStore<*>, onConcluded: ()->Unit): super(context) { + _context = context; + _store = toMigrate; + _onConcluded = onConcluded; + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(LayoutInflater.from(context).inflate(R.layout.dialog_migrate, null)); + + _buttonIgnore = findViewById(R.id.button_ignore); + _buttonDelete = findViewById(R.id.button_delete); + _buttonRestore = findViewById(R.id.button_restore); + + _buttonOk = findViewById(R.id.button_ok); + _buttonRetry = findViewById(R.id.button_retry); + _buttonDeleteFailed = findViewById(R.id.button_delete_failed); + + _migrate_type_text = findViewById(R.id.migrate_type_text); + _migrate_count_text = findViewById(R.id.migrate_count_text); + + _migrate_result_restored_text = findViewById(R.id.migrate_result_restored_text); + _migrate_result_failed_text = findViewById(R.id.migrate_result_failed_text); + _migrate_result_fplugin_text = findViewById(R.id.migrate_result_fplugin_text); + _migrate_result_failed_count_text = findViewById(R.id.migrate_result_failed_count_text); + + _uiChoiceTop = findViewById(R.id.dialog_ui_choice_top); + _uiProgressTop = findViewById(R.id.dialog_ui_progress_top); + + _uiChoiceBot = findViewById(R.id.dialog_ui_bottom_choice); + _uiResultBot = findViewById(R.id.dialog_ui_bottom_result) + + _textProgress = findViewById(R.id.text_progress); + _updateSpinner = findViewById(R.id.update_spinner); + + val toMigrateCount = _store.getMissingReconstructionCount(); + _migrate_type_text.text = _store.name; + _migrate_count_text.text = "${toMigrateCount} items"; + + _migrate_result_failed_text.movementMethod = ScrollingMovementMethod.getInstance() + + _buttonIgnore.setOnClickListener { + UIDialogs.toast(_context, "${toMigrateCount} items will be invisible\nWe will ask again next boot"); + dismiss(); + }; + _buttonDelete.setOnClickListener { + _store.deleteMissing(); + UIDialogs.toast(_context, "Deleted ${toMigrateCount} failed items"); + dismiss(); + }; + _buttonRestore.setOnClickListener { + if (_isRestoring) + return@setOnClickListener; + _isRestoring = true; + restore(); + }; + + _buttonRetry.setOnClickListener { + restore(); + }; + } + + override fun dismiss() { + super.dismiss(); + _onConcluded.invoke(); + } + + private fun restore() { + _uiChoiceTop.visibility = View.GONE; + _uiChoiceBot.visibility = View.GONE; + _uiResultBot.visibility = View.GONE; + _uiProgressTop.visibility = View.VISIBLE; + + _textProgress.text = "0/${_store.getMissingReconstructionCount()}"; + + setCancelable(false); + setCanceledOnTouchOutside(false); + window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + _updateSpinner.drawable?.assume()?.start(); + + val scope = StateApp.instance.scopeOrNull; + scope?.launch(Dispatchers.IO) { + try { + val migrationResult = _store.reconstructMissing { finished, total -> + scope.launch(Dispatchers.Main) { + _textProgress.text = "${finished}/${total}"; + } + }; + + withContext(Dispatchers.Main) { + try { + val realFailures = migrationResult.exceptions.filter { it !is NoPlatformClientException }; + val pluginFailures = migrationResult.exceptions.filter { it is NoPlatformClientException }; + + _migrate_result_restored_text.text = "Restored ${migrationResult.success} items"; + _migrate_result_fplugin_text.visibility = View.GONE; + + if(realFailures.isNotEmpty() || migrationResult.messages.isNotEmpty()) { + val messagesText = migrationResult.messages.map { it }.joinToString("\n") + (if(migrationResult.messages.isNotEmpty()) "\n" else ""); + val errorText = realFailures.map { it.message }.joinToString("\n"); + val spannable = SpannableString(messagesText + errorText); + spannable.setSpan(ForegroundColorSpan(Color.WHITE), 0, messagesText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + spannable.setSpan(ForegroundColorSpan(Color.RED), messagesText.length, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + _migrate_result_failed_text.text = spannable + _migrate_result_failed_text.visibility = View.VISIBLE; + } + else + _migrate_result_failed_text.visibility = View.GONE; + + + if (realFailures.isEmpty()) { + _migrate_result_failed_count_text.visibility = View.GONE; + _buttonDeleteFailed.visibility = View.GONE; + _buttonRetry.visibility = View.GONE; + } else { + _migrate_result_failed_count_text.visibility = View.VISIBLE; + _migrate_result_failed_count_text.text = "(${migrationResult.exceptions.size} failed)" + _buttonDeleteFailed.visibility = View.VISIBLE; + _buttonRetry.visibility = View.VISIBLE; + } + + if(pluginFailures.isEmpty()) { + _migrate_result_fplugin_text.visibility = View.GONE; + } else { + _migrate_result_fplugin_text.visibility = View.VISIBLE; + _migrate_result_fplugin_text.text = "Plugin not enabled for ${pluginFailures} items"; + } + + _buttonDeleteFailed.setOnClickListener { + _store.deleteMissing(); + UIDialogs.toast(_context, "Deleted ${realFailures} failed items", false); + dismiss(); + }; + _buttonOk.setOnClickListener { + if(migrationResult.exceptions.size > 0) + UIDialogs.toast(_context, "${migrationResult.exceptions.size} items will be invisible\nWe will ask again next boot"); + dismiss(); + } + + _uiProgressTop.visibility = View.GONE; + _uiChoiceTop.visibility = View.VISIBLE; + _uiResultBot.visibility = View.VISIBLE; + } catch (e: Throwable) { + Logger.e(TAG, "Failed to update import UI.", e) + } + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to import reconstruction.", e) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/ProgressDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/ProgressDialog.kt new file mode 100644 index 00000000..fea26588 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/dialogs/ProgressDialog.kt @@ -0,0 +1,62 @@ +package com.futo.platformplayer.dialogs + +import android.app.AlertDialog +import android.app.PendingIntent.* +import android.content.Context +import android.content.Intent +import android.content.pm.PackageInstaller +import android.graphics.drawable.Animatable +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.widget.Button +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.receivers.InstallReceiver +import com.futo.platformplayer.R +import com.futo.platformplayer.Settings +import com.futo.platformplayer.activities.MainActivity +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.states.StateApp +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class ProgressDialog : AlertDialog { + companion object { + private val TAG = "AutoUpdateDialog"; + } + + private lateinit var _text: TextView; + private lateinit var _textProgress: TextView; + private lateinit var _updateSpinner: ImageView; + + private val _handler: ((ProgressDialog) -> Unit); + + constructor(context: Context, act: ((ProgressDialog) -> Unit)) : super(context) { + _handler = act; + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(LayoutInflater.from(context).inflate(R.layout.dialog_progress, null)); + + _text = findViewById(R.id.text_dialog); + _textProgress = findViewById(R.id.text_progress); + _updateSpinner = findViewById(R.id.update_spinner); + setCancelable(false); + setCanceledOnTouchOutside(false); + _text.text = ""; + (_updateSpinner?.drawable as Animatable?)?.start(); + + _handler(this); + } + + fun setProgress(progress: Float) { setProgress(progress.toDouble()); } + fun setProgress(progress: Double) { _textProgress.text = "${Math.floor((progress * 100)).toInt()}%" } + fun setProgress(percentage: String) { _textProgress.text = percentage; } + fun setText(str: String) { _text.text = str; } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/downloads/PlaylistDownloadDescriptor.kt b/app/src/main/java/com/futo/platformplayer/downloads/PlaylistDownloadDescriptor.kt new file mode 100644 index 00000000..cbbd195a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/downloads/PlaylistDownloadDescriptor.kt @@ -0,0 +1,8 @@ +package com.futo.platformplayer.downloads + +@kotlinx.serialization.Serializable +data class PlaylistDownloadDescriptor( + val id: String, + val targetPxCount: Long?, + val targetBitrate: Long? +); \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/downloads/VideoDownload.kt b/app/src/main/java/com/futo/platformplayer/downloads/VideoDownload.kt new file mode 100644 index 00000000..16e4dfbb --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/downloads/VideoDownload.kt @@ -0,0 +1,593 @@ +package com.futo.platformplayer.downloads + +import com.futo.platformplayer.Settings +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateDownloads +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.api.media.models.streams.sources.other.IStreamMetaDataSource +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails +import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo +import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideoDetails +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.helpers.FileHelper.Companion.sanitizeFileName +import com.futo.platformplayer.helpers.VideoHelper +import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer +import com.futo.platformplayer.serializers.OffsetDateTimeSerializer +import com.futo.platformplayer.toHumanBitrate +import com.futo.platformplayer.toHumanBytesSpeed +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.time.OffsetDateTime +import java.util.concurrent.CancellationException +import java.util.concurrent.ForkJoinPool +import java.util.concurrent.ForkJoinTask +import java.util.concurrent.ThreadLocalRandom + +@kotlinx.serialization.Serializable +class VideoDownload { + var state: State = State.QUEUED; + + var video: SerializedPlatformVideo? = null; + var videoDetails: SerializedPlatformVideoDetails? = null; + + @kotlinx.serialization.Transient + val videoEither: IPlatformVideo get() = videoDetails ?: video ?: throw IllegalStateException("Missing video?"); + + @kotlinx.serialization.Transient + val id: PlatformID get() = videoEither.id + @kotlinx.serialization.Transient + val name: String get() = videoEither.name; + @kotlinx.serialization.Transient + val thumbnail: String? get() = videoDetails?.thumbnails?.getHQThumbnail() ?: video?.thumbnails?.getHQThumbnail(); + + var targetPixelCount: Long? = null; + var targetBitrate: Long? = null; + var videoSource: VideoUrlSource?; + var audioSource: AudioUrlSource?; + var subtitleSource: SubtitleRawSource?; + @kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class) + var prepareTime: OffsetDateTime? = null; + + var progress: Double = 0.0; + var isCancelled = false; + + var downloadSpeedVideo: Long = 0; + var downloadSpeedAudio: Long = 0; + val downloadSpeed: Long get() = downloadSpeedVideo + downloadSpeedAudio; + + var error: String? = null; + + var videoFilePath: String? = null; + var videoFileName: String? = null; + var videoFileSize: Long? = null; + + var audioFilePath: String? = null; + var audioFileName: String? = null; + var audioFileSize: Long? = null; + + var subtitleFilePath: String? = null; + var subtitleFileName: String? = null; + + var groupType: String? = null; + var groupID: String? = null; + + @kotlinx.serialization.Transient + val onStateChanged = Event1(); + @kotlinx.serialization.Transient + val onProgressChanged = Event1(); + + + fun changeState(newState: State) { + state = newState; + onStateChanged.emit(newState); + } + + constructor(video: IPlatformVideo, targetPixelCount: Long? = null, targetBitrate: Long? = null) { + this.video = SerializedPlatformVideo.fromVideo(video); + this.videoSource = null; + this.audioSource = null; + this.subtitleSource = null; + this.targetPixelCount = targetPixelCount; + this.targetBitrate = targetBitrate; + } + constructor(video: IPlatformVideoDetails, videoSource: IVideoUrlSource?, audioSource: IAudioUrlSource?, subtitleSource: SubtitleRawSource?) { + this.video = SerializedPlatformVideo.fromVideo(video); + this.videoDetails = SerializedPlatformVideoDetails.fromVideo(video, if (subtitleSource != null) listOf(subtitleSource) else listOf()); + this.videoSource = VideoUrlSource.fromUrlSource(videoSource); + this.audioSource = AudioUrlSource.fromUrlSource(audioSource); + this.subtitleSource = subtitleSource; + this.prepareTime = OffsetDateTime.now(); + } + + fun withGroup(groupType: String, groupID: String): VideoDownload { + this.groupType = groupType; + this.groupID = groupID; + return this; + } + + fun getDownloadInfo() : String { + val videoInfo = if(videoSource != null) + "${videoSource!!.width}x${videoSource!!.height} (${videoSource!!.container})" + else if(targetPixelCount != null && targetPixelCount!! > 0) { + val guessWidth = ((4 * Math.sqrt(targetPixelCount!!.toDouble())) / 3).toInt(); + val guessHeight = ((3 * Math.sqrt(targetPixelCount!!.toDouble())) / 4).toInt(); + "${guessWidth}x${guessHeight}" + } + else null; + val audioInfo = if(audioSource != null) + audioSource!!.bitrate.toHumanBitrate(); + else if(targetBitrate != null && targetBitrate!! > 0) + targetBitrate!!.toHumanBitrate(); + else null; + + val items = arrayOf(videoInfo, audioInfo).filter { it != null }; + + return items.joinToString(" • "); + } + + suspend fun prepare() { + Logger.i(TAG, "VideoDownload Prepare [${name}]"); + if(video == null && videoDetails == null) + throw IllegalStateException("Missing information for download to complete"); + if(targetPixelCount == null && targetBitrate == null && videoSource == null && audioSource == null) + throw IllegalStateException("No sources or query values set"); + + //Fetch full video object and determine source + if(video != null && videoDetails == null) { + val original = StatePlatform.instance.getContentDetails(video!!.url).await(); + if(original !is IPlatformVideoDetails) + throw IllegalStateException("Original content is not media?"); + + videoDetails = SerializedPlatformVideoDetails.fromVideo(original, if (subtitleSource != null) listOf(subtitleSource!!) else listOf()); + if(videoSource == null && targetPixelCount != null) { + val vsource = VideoHelper.selectBestVideoSource(videoDetails!!.video, targetPixelCount!!.toInt(), arrayOf()) + ?: throw IllegalStateException("Could not find a valid video source for video"); + if(vsource is IVideoUrlSource) + videoSource = VideoUrlSource.fromUrlSource(vsource); + else + throw IllegalStateException("Download video source is not a url source"); + } + + if(audioSource == null && targetBitrate != null) { + val asource = VideoHelper.selectBestAudioSource(videoDetails!!.video, arrayOf(), null, targetPixelCount) + ?: if(videoSource != null ) null + else throw IllegalStateException("Could not find a valid audio source for video"); + if(asource == null) + audioSource = null; + else if(asource is IAudioUrlSource) + audioSource = AudioUrlSource.fromUrlSource(asource); + else + throw IllegalStateException("Download audio source is not a url source"); + } + } + } + suspend fun download(client: ManagedHttpClient, onProgress: ((Double) -> Unit)? = null) = coroutineScope { + Logger.i(TAG, "VideoDownload Download [${name}]"); + if(videoDetails == null || (videoSource == null && audioSource == null)) + throw IllegalStateException("Missing information for download to complete"); + val downloadDir = StateDownloads.instance.getDownloadsDirectory(); + + if(videoDetails!!.id.value == null) + throw IllegalStateException("Video has no id"); + + if(isCancelled) throw CancellationException("Download got cancelled"); + + if(videoSource != null) { + videoFileName = "${videoDetails!!.id.value!!} [${videoSource!!.width}x${videoSource!!.height}].${videoContainerToExtension(videoSource!!.container)}".sanitizeFileName(); + videoFilePath = File(downloadDir, videoFileName!!).absolutePath; + } + if(audioSource != null) { + audioFileName = "${videoDetails!!.id.value!!} [${audioSource!!.bitrate}].${audioContainerToExtension(audioSource!!.container)}".sanitizeFileName(); + audioFilePath = File(downloadDir, audioFileName!!).absolutePath; + } + if(subtitleSource != null) { + subtitleFileName = "${videoDetails!!.id.value!!} [${subtitleSource!!.name}].${subtitleContainerToExtension(subtitleSource!!.format)}".sanitizeFileName(); + subtitleFilePath = File(downloadDir, subtitleFileName!!).absolutePath; + } + val progressLock = Object(); + val sourcesToDownload = mutableListOf>(); + + var lastVideoLength: Long = 0; + var lastVideoRead: Long = 0; + var lastAudioLength: Long = 0; + var lastAudioRead: Long = 0; + + if(videoSource != null) { + sourcesToDownload.add(async { + Logger.i(TAG, "Started downloading video"); + videoFileSize = downloadSource("Video", client, videoSource!!.getVideoUrl(), File(downloadDir, videoFileName!!)) { length, totalRead, speed -> + synchronized(progressLock) { + lastVideoLength = length; + lastVideoRead = totalRead; + downloadSpeedVideo = speed; + if(videoFileSize == null) + videoFileSize = lastVideoLength; + + val totalLength = lastVideoLength + lastAudioLength; + val total = lastVideoRead + lastAudioRead; + if(totalLength > 0) { + val percentage = (total / totalLength.toDouble()); + onProgress?.invoke(percentage); + progress = percentage; + onProgressChanged.emit(percentage); + } + } + } + }); + } + if(audioSource != null) { + sourcesToDownload.add(async { + Logger.i(TAG, "Started downloading audio"); + audioFileSize = downloadSource("Audio", client, audioSource!!.getAudioUrl(), File(downloadDir, audioFileName!!)) { length, totalRead, speed -> + synchronized(progressLock) { + lastAudioLength = length; + lastAudioRead = totalRead; + downloadSpeedAudio = speed; + if(audioFileSize == null) + audioFileSize = lastAudioLength; + + val totalLength = lastVideoLength + lastAudioLength; + val total = lastVideoRead + lastAudioRead; + if(totalLength > 0) { + val percentage = (total / totalLength.toDouble()); + onProgress?.invoke(percentage); + progress = percentage; + onProgressChanged.emit(percentage); + } + } + } + }); + } + if (subtitleSource != null) { + sourcesToDownload.add(async { + File(downloadDir, subtitleFileName!!).writeText(subtitleSource!!._subtitles) + }); + } + + try { + awaitAll(*sourcesToDownload.toTypedArray()); + } + catch(runtimeEx: RuntimeException) { + if(runtimeEx.cause != null) + throw runtimeEx.cause!!; + else + throw runtimeEx; + } + catch(ex: Throwable) { + throw ex; + } + } + private fun downloadSource(name: String, client: ManagedHttpClient, videoUrl: String, targetFile: File, onProgress: (Long, Long, Long) -> Unit): Long { + if(targetFile.exists()) + targetFile.delete(); + + targetFile.createNewFile(); + + var sourceLength: Long? = null; + + val fileStream = FileOutputStream(targetFile); + + try{ + val head = client.tryHead(videoUrl); + if(Settings.instance.downloads.byteRangeDownload && head?.containsKey("accept-ranges") == true && head.containsKey("content-length")) + { + val concurrency = Settings.instance.downloads.getByteRangeThreadCount(); + Logger.i(TAG, "Download ${name} ByteRange Parallel (${concurrency})"); + sourceLength = head["content-length"]!!.toLong(); + onProgress(sourceLength, 0, 0); + downloadSource_Ranges(name, client, fileStream, videoUrl, sourceLength, 1024*512, concurrency, onProgress); + } + else { + Logger.i(TAG, "Download ${name} Sequential"); + sourceLength = downloadSource_Sequential(client, fileStream, videoUrl, onProgress); + } + + Logger.i(TAG, "${name} downloadSource Finished"); + } + catch(ioex: IOException) { + if(targetFile.exists() ?: false) + targetFile.delete(); + if(ioex.message?.contains("ENOSPC") ?: false) + throw Exception("Not enough space on device", ioex); + else + throw ioex; + } + catch(ex: Throwable) { + if(targetFile.exists() ?: false) + targetFile.delete(); + throw ex; + } + finally { + fileStream?.close(); + } + return sourceLength!!; + } + private fun downloadSource_Sequential(client: ManagedHttpClient, fileStream: FileOutputStream, url: String, onProgress: (Long, Long, Long) -> Unit): Long { + val progressRate: Int = 4096 * 25; + var lastProgressCount: Int = 0; + val speedRate: Int = 4096 * 25; + var readSinceLastSpeedTest: Long = 0; + var timeSinceLastSpeedTest: Long = System.currentTimeMillis(); + + var lastSpeed: Long = 0; + + val result = client.get(url); + if (!result.isOk) + throw IllegalStateException("Failed to download source. Web[${result.code}] Error"); + if (result.body == null) + throw IllegalStateException("Failed to download source. Web[${result.code}] No response"); + + val sourceLength = result.body.contentLength(); + val sourceStream = result.body.byteStream(); + + var totalRead: Long = 0; + var read = 0; + + val buffer = ByteArray(4096); + + do { + read = sourceStream.read(buffer); + if (read < 0) + break; + + fileStream.write(buffer, 0, read); + + totalRead += read; + + readSinceLastSpeedTest += read; + if (totalRead / progressRate > lastProgressCount) { + onProgress(sourceLength, totalRead, lastSpeed); + lastProgressCount++; + } + if (readSinceLastSpeedTest > speedRate) { + val lastSpeedTime = timeSinceLastSpeedTest; + timeSinceLastSpeedTest = System.currentTimeMillis(); + val timeSince = timeSinceLastSpeedTest - lastSpeedTime; + if (timeSince > 0) + lastSpeed = (readSinceLastSpeedTest / (timeSince / 1000.0)).toLong(); + readSinceLastSpeedTest = 0; + } + + if (isCancelled) + throw IllegalStateException("Cancelled"); + } while (read > 0); + + lastSpeed = 0; + onProgress(sourceLength, totalRead, 0); + return sourceLength; + } + private fun downloadSource_Ranges(name: String, client: ManagedHttpClient, fileStream: FileOutputStream, url: String, sourceLength: Long, rangeSize: Int, concurrency: Int = 1, onProgress: (Long, Long, Long) -> Unit) { + val progressRate: Int = 4096 * 5; + var lastProgressCount: Int = 0; + val speedRate: Int = 4096 * 5; + var readSinceLastSpeedTest: Long = 0; + var timeSinceLastSpeedTest: Long = System.currentTimeMillis(); + + var lastSpeed: Long = 0; + + var reqCount = -1; + var totalRead: Long = 0; + + val pool = ForkJoinPool(concurrency); + + while(totalRead < sourceLength) { + reqCount++; + + Logger.i(TAG, "Download ${name} Batch #${reqCount} [${concurrency}] (${lastSpeed.toHumanBytesSpeed()})"); + + val byteRangeResults = requestByteRangeParallel(client, pool, url, sourceLength, concurrency, totalRead, + rangeSize, 1024 * 64); + + for(byteRange in byteRangeResults) { + val read = ((byteRange.third - byteRange.second) + 1).toInt(); + + fileStream.write(byteRange.first, 0, read); + + totalRead += read; + readSinceLastSpeedTest += read; + } + + if(readSinceLastSpeedTest > speedRate) { + val lastSpeedTime = timeSinceLastSpeedTest; + timeSinceLastSpeedTest = System.currentTimeMillis(); + val timeSince = timeSinceLastSpeedTest - lastSpeedTime; + if(timeSince > 0) + lastSpeed = (readSinceLastSpeedTest / (timeSince / 1000.0)).toLong(); + readSinceLastSpeedTest = 0; + } + if(totalRead / progressRate > lastProgressCount) { + onProgress(sourceLength, totalRead, lastSpeed); + lastProgressCount++; + } + + if(isCancelled) + throw IllegalStateException("Cancelled"); + } + onProgress(sourceLength, totalRead, 0); + } + + private fun requestByteRangeParallel(client: ManagedHttpClient, pool: ForkJoinPool, url: String, totalLength: Long, concurrency: Int, rangePosition: Long, rangeSize: Int, rangeVariance: Int = -1): List> { + val tasks = mutableListOf>>(); + var readPosition = rangePosition; + for(i in 0 until concurrency) { + if(readPosition >= totalLength - 1) + continue; + + val toRead = rangeSize + (if(rangeVariance >= 1) ThreadLocalRandom.current().nextInt(rangeVariance * -1, rangeVariance) else 0); + val rangeStart = readPosition; + val rangeEnd = if(rangeStart + toRead > totalLength) + totalLength - 1; + else readPosition + toRead; + + tasks.add(pool.submit> { + return@submit requestByteRange(client, url, rangeStart, rangeEnd); + }); + readPosition = rangeEnd + 1; + } + + return tasks.map { it.get() }; + } + private fun requestByteRange(client: ManagedHttpClient, url: String, rangeStart: Long, rangeEnd: Long): Triple { + val toRead = rangeEnd - rangeStart; + val req = client.get(url, mutableMapOf(Pair("Range", "bytes=${rangeStart}-${rangeEnd}"))); + if(!req.isOk) + throw IllegalStateException("Range request failed Code [${req.code}] due to: ${req.message}"); + if(req.body == null) + throw IllegalStateException("Range request failed, No body"); + val read = req.body.contentLength(); + + if(read < toRead) + throw IllegalStateException("Byte-Range request attempted to provide less (${read} < ${toRead})"); + + return Triple(req.body.bytes(), rangeStart, rangeEnd); + } + + fun validate() { + Logger.i(TAG, "VideoDownload Validate [${name}]"); + if(videoSource != null) { + if(videoFilePath == null) + throw IllegalStateException("Missing video file name after download"); + val expectedFile = File(videoFilePath!!); + if(!expectedFile.exists()) + throw IllegalStateException("Video file missing after download"); + if(expectedFile.length() != videoFileSize) + throw IllegalStateException("Expected size [${videoFileSize}], but found ${expectedFile.length()}"); + } + if(audioSource != null) { + if(audioFilePath == null) + throw IllegalStateException("Missing audio file name after download"); + val expectedFile = File(audioFilePath!!); + if(!expectedFile.exists()) + throw IllegalStateException("Audio file missing after download"); + if(expectedFile.length() != audioFileSize) + throw IllegalStateException("Expected size [${audioFileSize}], but found ${expectedFile.length()}"); + } + if(subtitleSource != null) { + if(subtitleFilePath == null) + throw IllegalStateException("Missing subtitle file name after download"); + val expectedFile = File(subtitleFilePath!!); + if(!expectedFile.exists()) + throw IllegalStateException("Subtitle file missing after download"); + } + } + fun complete() { + Logger.i(TAG, "VideoDownload Complete [${name}]"); + val existing = StateDownloads.instance.getCachedVideo(id); + val localVideoSource = videoFilePath?.let { LocalVideoSource.fromSource(videoSource!!, it, videoFileSize ?: 0) }; + val localAudioSource = audioFilePath?.let { LocalAudioSource.fromSource(audioSource!!, it, audioFileSize ?: 0) }; + val localSubtitleSource = subtitleFilePath?.let { LocalSubtitleSource.fromSource(subtitleSource!!, it) }; + + if(localVideoSource != null && videoSource != null && videoSource is IStreamMetaDataSource) + localVideoSource.streamMetaData = (videoSource as IStreamMetaDataSource).streamMetaData; + + if(localAudioSource != null && audioSource != null && audioSource is IStreamMetaDataSource) + localAudioSource.streamMetaData = (audioSource as IStreamMetaDataSource).streamMetaData; + + if(existing != null) { + existing.videoSerialized = videoDetails!!; + if(localVideoSource != null) { + val newVideos = ArrayList(existing.videoSource); + newVideos.add(localVideoSource); + existing.videoSource = newVideos; + } + if(localAudioSource != null) { + val newAudios = ArrayList(existing.audioSource); + newAudios.add(localAudioSource); + existing.audioSource = newAudios; + } + if (localSubtitleSource != null) { + val newSubtitles = ArrayList(existing.subtitlesSources); + newSubtitles.add(localSubtitleSource); + existing.subtitlesSources = newSubtitles; + } + StateDownloads.instance.updateCachedVideo(existing); + } + else { + val newVideo = VideoLocal(videoDetails!!); + if(localVideoSource != null) + newVideo.videoSource.add(localVideoSource); + if(localAudioSource != null) + newVideo.audioSource.add(localAudioSource); + if (localSubtitleSource != null) + newVideo.subtitlesSources.add(localSubtitleSource); + newVideo.groupID = groupID; + newVideo.groupType = groupType; + StateDownloads.instance.updateCachedVideo(newVideo); + } + } + + enum class State { + QUEUED, + PREPARING, + DOWNLOADING, + VALIDATING, + FINALIZING, + COMPLETED, + ERROR; + + override fun toString(): String { + val lowercase = super.toString().lowercase(); + if(lowercase.length == 0) + return lowercase; + return lowercase[0].uppercase() + lowercase.substring(1); + } + } + + companion object { + const val TAG = "VideoDownload"; + const val GROUP_PLAYLIST = "Playlist"; + + fun videoContainerToExtension(container: String): String? { + if (container.contains("video/mp4")) + return "mp4"; + else if (container.contains("application/x-mpegURL")) + return "m3u8"; + else if (container.contains("video/3gpp")) + return "3gp"; + else if (container.contains("video/quicktime")) + return "mov"; + else if (container.contains("video/webm")) + return "webm"; + else if (container.contains("video/x-matroska")) + return "mkv"; + else + return "video"; + } + + fun audioContainerToExtension(container: String): String { + if (container.contains("audio/mp4")) + return "mp4a"; + else if (container.contains("audio/mpeg")) + return "mpga"; + else if (container.contains("audio/mp3")) + return "mp3"; + else if (container.contains("audio/webm")) + return "webma"; + else + return "audio"; + } + + fun subtitleContainerToExtension(container: String?): String { + if (container == null) + return "subtitle"; + + if (container.contains("text/vtt")) + return "vtt"; + else if (container.contains("text/plain")) + return "srt"; + else if (container.contains("application/x-subrip")) + return "srt"; + else + return "subtitle"; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/downloads/VideoExport.kt b/app/src/main/java/com/futo/platformplayer/downloads/VideoExport.kt new file mode 100644 index 00000000..987dd8c6 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/downloads/VideoExport.kt @@ -0,0 +1,261 @@ +package com.futo.platformplayer.downloads + +import android.os.Environment +import com.arthenica.ffmpegkit.* +import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.toHumanBitrate +import kotlinx.coroutines.* +import java.io.* +import java.util.concurrent.CancellationException +import java.util.concurrent.Executors +import kotlin.coroutines.resumeWithException + +@kotlinx.serialization.Serializable +class VideoExport { + var state: State = State.QUEUED; + + var videoLocal: VideoLocal; + var videoSource: LocalVideoSource?; + var audioSource: LocalAudioSource?; + var subtitleSource: LocalSubtitleSource?; + + var progress: Double = 0.0; + var isCancelled = false; + + var error: String? = null; + + @kotlinx.serialization.Transient + val onStateChanged = Event1(); + @kotlinx.serialization.Transient + val onProgressChanged = Event1(); + + fun changeState(newState: State) { + state = newState; + onStateChanged.emit(newState); + } + + constructor(videoLocal: VideoLocal, videoSource: LocalVideoSource?, audioSource: LocalAudioSource?, subtitleSource: LocalSubtitleSource?) { + this.videoLocal = videoLocal; + this.videoSource = videoSource; + this.audioSource = audioSource; + this.subtitleSource = subtitleSource; + } + + suspend fun export(onProgress: ((Double) -> Unit)? = null): File = coroutineScope { + if(isCancelled) throw CancellationException("Export got cancelled"); + + val v = videoSource; + val a = audioSource; + val s = subtitleSource; + + var sourceCount = 0; + if (v != null) sourceCount++; + if (a != null) sourceCount++; + if (s != null) sourceCount++; + + var outputFile: File? = null; + val moviesRoot = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES); + val musicRoot = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC); + val moviesGrayjay = File(moviesRoot, "Grayjay"); + val musicGrayjay = File(musicRoot, "Grayjay"); + if(!moviesGrayjay.exists()) + moviesGrayjay.mkdirs(); + if(!musicGrayjay.exists()) + musicGrayjay.mkdirs(); + + if (sourceCount > 1) { + val outputFileName = toSafeFileName(videoLocal.name) + ".mp4"// + VideoDownload.videoContainerToExtension(v.container); + val f = File(moviesGrayjay, outputFileName); + + Logger.i(TAG, "Combining video and audio through FFMPEG."); + combine(a?.filePath, v?.filePath, s?.filePath, f.absolutePath, videoLocal.duration.toDouble()) { progress -> onProgress?.invoke(progress) }; + outputFile = f; + } else if (v != null) { + val outputFileName = toSafeFileName(videoLocal.name) + "." + VideoDownload.videoContainerToExtension(v.container); + val f = File(moviesGrayjay, outputFileName); + Logger.i(TAG, "Copying video."); + copy(v.filePath, f.absolutePath) { progress -> onProgress?.invoke(progress) }; + outputFile = f; + } else if (a != null) { + val outputFileName = toSafeFileName(videoLocal.name) + "." + VideoDownload.audioContainerToExtension(a.container); + val f = File(musicGrayjay, outputFileName); + Logger.i(TAG, "Copying audio."); + copy(a.filePath, f.absolutePath) { progress -> onProgress?.invoke(progress) }; + outputFile = f; + } else { + throw Exception("Cannot export when no audio or video source is set."); + } + + onProgressChanged.emit(100.0); + return@coroutineScope outputFile; + } + + private fun toSafeFileName(input: String): String { + val safeCharacters = ('a'..'z') + ('A'..'Z') + ('0'..'9') + listOf('-', '_') + return input.map { if (it in safeCharacters) it else '_' }.joinToString(separator = "") + } + + private suspend fun combine(inputPathAudio: String?, inputPathVideo: String?, inputPathSubtitles: String?, outputPath: String, duration: Double, onProgress: ((Double) -> Unit)? = null) = withContext(Dispatchers.IO) { + suspendCancellableCoroutine { continuation -> + //ffmpeg -i a.mp4 -i b.m4a -scodec mov_text -i c.vtt -map 0:v -map 1:a -map 2 -c:v copy -c:a copy -c:s mov_text output.mp4 + + val cmdBuilder = StringBuilder("-y") + var counter = 0 + + if (inputPathVideo != null) { + cmdBuilder.append(" -i $inputPathVideo") + } + if (inputPathAudio != null) { + cmdBuilder.append(" -i $inputPathAudio") + } + if (inputPathSubtitles != null) { + val subtitleExtension = File(inputPathSubtitles).extension + + val codec = when (subtitleExtension.lowercase()) { + "srt" -> "mov_text" + "vtt" -> "webvtt" + else -> throw Exception("Unsupported subtitle format: $subtitleExtension") + } + + cmdBuilder.append(" -scodec $codec -i $inputPathSubtitles") + } + + if (inputPathVideo != null) { + cmdBuilder.append(" -map ${counter++}:v") + } + if (inputPathAudio != null) { + cmdBuilder.append(" -map ${counter++}:a") + } + + if (inputPathSubtitles != null) { + cmdBuilder.append(" -map ${counter++}") + } + + if (inputPathVideo != null) { + cmdBuilder.append(" -c:v copy") + } + if (inputPathAudio != null) { + cmdBuilder.append(" -c:a copy") + } + if (inputPathAudio != null) { + cmdBuilder.append(" -c:s mov_text") + } + + cmdBuilder.append(" $outputPath") + + val cmd = cmdBuilder.toString() + Logger.i(TAG, "Used command: $cmd"); + + val statisticsCallback = StatisticsCallback { statistics -> + val time = statistics.time.toDouble() / 1000.0 + val progressPercentage = (time / duration) + onProgress?.invoke(progressPercentage) + } + + val executorService = Executors.newSingleThreadExecutor() + val session = FFmpegKit.executeAsync(cmd, + { session -> + if (ReturnCode.isSuccess(session.returnCode)) { + continuation.resumeWith(Result.success(Unit)) + } else { + val errorMessage = if (ReturnCode.isCancel(session.returnCode)) { + "Command cancelled" + } else { + "Command failed with state '${session.state}' and return code ${session.returnCode}, stack trace ${session.failStackTrace}" + } + continuation.resumeWithException(RuntimeException(errorMessage)) + } + }, + LogCallback { Logger.v(TAG, it.message) }, + statisticsCallback, + executorService + ) + + continuation.invokeOnCancellation { + session.cancel() + } + } + } + + private suspend fun copy(fromPath: String, toPath: String, bufferSize: Int = 8192, onProgress: ((Double) -> Unit)? = null) { + withContext(Dispatchers.IO) { + var inputStream: FileInputStream? = null + var outputStream: FileOutputStream? = null + + try { + val srcFile = File(fromPath) + if (!srcFile.exists()) { + throw IOException("Source file not found.") + } + + val dstFile = File(toPath) + val parentDir = dstFile.parentFile ?: throw IOException("Non existent parent dir.") + + if (!parentDir.exists()) { + if (!parentDir.mkdirs()) { + throw IOException("Failed to create destination directory.") + } + } + + inputStream = FileInputStream(srcFile) + outputStream = FileOutputStream(dstFile) + + val buffer = ByteArray(bufferSize) + val totalBytes = srcFile.length() + var bytesCopied: Long = 0 + + var bytesRead: Int + while (inputStream.read(buffer).also { bytesRead = it } != -1) { + outputStream.write(buffer, 0, bytesRead) + bytesCopied += bytesRead.toLong() + + onProgress?.let { + withContext(Dispatchers.Main) { + it(bytesCopied / totalBytes.toDouble()) + } + } + } + } catch (e: Exception) { + throw IOException("Error occurred while copying file: ${e.message}", e) + } finally { + inputStream?.close() + outputStream?.close() + } + } + } + + fun getExportInfo() : String { + val tokens = ArrayList(); + val v = videoSource; + if (v != null) { + tokens.add("${v.width}x${v.height} (${v.container})"); + } + + val a = audioSource; + if (a != null) { + tokens.add(a.bitrate.toHumanBitrate()); + } + + return tokens.joinToString(" • "); + } + + enum class State { + QUEUED, + EXPORTING, + COMPLETED, + ERROR; + + override fun toString(): String { + val lowercase = super.toString().lowercase(); + if(lowercase.length == 0) + return lowercase; + return lowercase[0].uppercase() + lowercase.substring(1); + } + } + + companion object { + private const val TAG = "VideoExport" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/downloads/VideoLocal.kt b/app/src/main/java/com/futo/platformplayer/downloads/VideoLocal.kt new file mode 100644 index 00000000..bf8cb601 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/downloads/VideoLocal.kt @@ -0,0 +1,116 @@ +package com.futo.platformplayer.downloads + +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.Thumbnails +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker +import com.futo.platformplayer.api.media.models.ratings.IRating +import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor +import com.futo.platformplayer.api.media.models.streams.LocalVideoMuxedSourceDescriptor +import com.futo.platformplayer.api.media.models.streams.LocalVideoUnMuxedSourceDescriptor +import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource +import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails +import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideoDetails +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.stores.v2.IStoreItem +import java.io.File +import java.time.OffsetDateTime + +//TODO: Better name +@kotlinx.serialization.Serializable +class VideoLocal: IPlatformVideoDetails, IStoreItem { + var videoSerialized: SerializedPlatformVideoDetails; + + var groupType: String? = null; + var groupID: String? = null; + + var videoSource: ArrayList = arrayListOf(); + var audioSource: ArrayList = arrayListOf(); + var subtitlesSources: ArrayList = arrayListOf(); + + override val contentType: ContentType get() = ContentType.MEDIA; + override val id: PlatformID get() = videoSerialized.id; + override val name: String get() = videoSerialized.name; + override val description: String get() = videoSerialized.description; + + override val thumbnails: Thumbnails get() = videoSerialized.thumbnails; + override val author: PlatformAuthorLink get() = videoSerialized.author; + + override val datetime: OffsetDateTime? get() = videoSerialized.datetime; + + override val url: String get() = videoSerialized.url; + override val shareUrl: String get() = videoSerialized.shareUrl; + + @kotlinx.serialization.Transient + override val video: IVideoSourceDescriptor get() = if(!audioSource.isEmpty()) + LocalVideoUnMuxedSourceDescriptor(this) + else + LocalVideoMuxedSourceDescriptor(this); + override val preview: IVideoSourceDescriptor? get() = videoSerialized.preview; + + override val live: IVideoSource? get() = videoSerialized.live; + override val dash: IDashManifestSource? get() = videoSerialized.dash; + override val hls: IHLSManifestSource? get() = videoSerialized.hls; + + override val duration: Long get() = videoSerialized.duration; + override val viewCount: Long get() = videoSerialized.viewCount; + + override val rating: IRating get() = videoSerialized.rating; + + override val isLive: Boolean get() = videoSerialized.isLive; + + //TODO: Offline subtitles + override val subtitles: List = listOf(); + + constructor(video: SerializedPlatformVideoDetails) { + this.videoSerialized = video; + } + constructor(video: IPlatformVideoDetails, subtitleSources: List) { + this.videoSerialized = SerializedPlatformVideoDetails.fromVideo(video, subtitleSources); + } + + override fun getComments(client: IPlatformClient): IPager? = null; + override fun getPlaybackTracker(): IPlaybackTracker? = null; + + fun toPlatformVideo() : IPlatformVideoDetails { + throw NotImplementedError(); + } + + fun getSimilarVideo(targetPixelCount: Int): LocalVideoSource? { + return videoSource.filter { + val px = it.height * it.width; + val diff = Math.abs(px - targetPixelCount); + val max = Math.max(targetPixelCount, px); + return@filter (diff.toFloat() / max) < 0.15f; + }.firstOrNull(); + } + fun getSimilarAudio(targetBitrate: Int): LocalAudioSource? { + return audioSource.filter { + val diff = Math.abs(it.bitrate - targetBitrate); + val max = Math.max(it.bitrate, targetBitrate); + return@filter (diff.toFloat() / max) < 0.15f; + }.firstOrNull(); + } + + override fun onDelete() { + for(srcFile in videoSource) { + val file = File(srcFile.filePath); + if (file.exists()) + file.delete(); + } + for(srcFile in audioSource) { + val file = File(srcFile.filePath); + if (file.exists()) + file.delete(); + } + for(srcFile in subtitlesSources) { + val file = File(srcFile.filePath); + if (file.exists()) + file.delete(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/encryption/EncryptionProvider.kt b/app/src/main/java/com/futo/platformplayer/encryption/EncryptionProvider.kt new file mode 100644 index 00000000..86d1d885 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/encryption/EncryptionProvider.kt @@ -0,0 +1,69 @@ +package com.futo.platformplayer.encryption + +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import android.util.Base64 +import java.security.Key +import java.security.KeyStore +import javax.crypto.Cipher +import javax.crypto.KeyGenerator +import javax.crypto.spec.GCMParameterSpec +import javax.crypto.spec.SecretKeySpec + +class EncryptionProvider { + private val _keyStore: KeyStore; + private val secretKey: Key? get() = _keyStore.getKey(KEY_ALIAS, null); + + constructor() { + _keyStore = KeyStore.getInstance(AndroidKeyStore); + _keyStore.load(null); + + if (!_keyStore.containsAlias(KEY_ALIAS)) { + val keyGenerator: KeyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, AndroidKeyStore) + keyGenerator.init(KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .setRandomizedEncryptionRequired(false) + .build()); + + keyGenerator.generateKey(); + } + } + + fun encrypt(decrypted: String, password: String? = null): String { + val encodedBytes = encrypt(decrypted.toByteArray(), password); + val encrypted = Base64.encodeToString(encodedBytes, Base64.DEFAULT); + return encrypted; + } + fun encrypt(decrypted: ByteArray, password: String? = null): ByteArray { + val c: Cipher = Cipher.getInstance(AES_MODE); + val keyToUse = if(password == null) secretKey else SecretKeySpec(password.toByteArray(), "AES"); + c.init(Cipher.ENCRYPT_MODE, keyToUse, GCMParameterSpec(128, FIXED_IV)); + val encodedBytes: ByteArray = c.doFinal(decrypted); + return encodedBytes; + } + + fun decrypt(encrypted: String, password: String? = null): String { + val c = Cipher.getInstance(AES_MODE); + val keyToUse = if(password == null) secretKey else SecretKeySpec(password.toByteArray(), "AES"); + c.init(Cipher.DECRYPT_MODE, keyToUse, GCMParameterSpec(128, FIXED_IV)); + val decrypted = String(c.doFinal(Base64.decode(encrypted, Base64.DEFAULT))); + return decrypted; + } + fun decrypt(encrypted: ByteArray, password: String? = null): ByteArray { + val c = Cipher.getInstance(AES_MODE); + val keyToUse = if(password == null) secretKey else SecretKeySpec(password.toByteArray(), "AES"); + c.init(Cipher.DECRYPT_MODE, keyToUse, GCMParameterSpec(128, FIXED_IV)); + return c.doFinal(encrypted); + } + + companion object { + val instance: EncryptionProvider = EncryptionProvider(); + + private val FIXED_IV = byteArrayOf(12, 43, 127, 2, 99, 22, 6, 78, 24, 53, 8, 101); + private const val AndroidKeyStore = "AndroidKeyStore"; + private const val KEY_ALIAS = "FUTOMedia_Key"; + private const val AES_MODE = "AES/GCM/NoPadding"; + private val TAG = "EncryptionProvider"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt new file mode 100644 index 00000000..e0b3bce1 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt @@ -0,0 +1,265 @@ +package com.futo.platformplayer.engine + +import android.content.Context +import com.caoccao.javet.exceptions.JavetCompilationException +import com.caoccao.javet.exceptions.JavetExecutionException +import com.caoccao.javet.interop.V8Host +import com.caoccao.javet.interop.V8Runtime +import com.caoccao.javet.values.V8Value +import com.caoccao.javet.values.primitive.V8ValueBoolean +import com.caoccao.javet.values.primitive.V8ValueInteger +import com.caoccao.javet.values.primitive.V8ValueString +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.* +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.engine.exceptions.* +import com.futo.platformplayer.engine.internal.V8Converter +import com.futo.platformplayer.engine.packages.* +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateAssets +import kotlinx.coroutines.* + +class V8Plugin { + val config: IV8PluginConfig; + private val _client: ManagedHttpClient; + private val _clientAuth: ManagedHttpClient; + + val httpClient: ManagedHttpClient get() = _client; + val httpClientAuth: ManagedHttpClient get() = _clientAuth; + + var _runtime : V8Runtime? = null; + + private val _deps : LinkedHashMap = LinkedHashMap(); + private val _depsPackages : MutableList = mutableListOf(); + private var _script : String? = null; + + val onStopped = Event1(); + + constructor(context: Context, config: IV8PluginConfig, script: String? = null, client: ManagedHttpClient = ManagedHttpClient(), clientAuth: ManagedHttpClient = ManagedHttpClient()) { + this._client = client; + this._clientAuth = clientAuth; + this.config = config; + this._script = script; + withDependency(PackageBridge(this, config)); + + for(pack in config.packages) + withDependency(getPackage(context, pack)); + } + + fun withDependency(context: Context, assetPath: String) : V8Plugin { + if(!_deps.containsKey(assetPath)) + _deps.put(assetPath, getAssetFile(context, assetPath)); + return this; + } + fun withDependency(name: String, script: String) : V8Plugin { + if(!_deps.containsKey(name)) + _deps.put(name, script); + return this; + } + fun withDependency(v8Package: V8Package) : V8Plugin { + _depsPackages.add(v8Package); + return this; + } + fun withScript(script: String) : V8Plugin { + _script = script; + return this; + } + + fun getPackages(): List { + return _depsPackages.toList(); + } + fun getPackageVariables(): List { + return _depsPackages.filter { it.variableName != null }.map { it.variableName!! }.toList(); + } + fun getPackageByVariableName(varName: String): V8Package? { + return _depsPackages.firstOrNull { it.variableName == varName }; + } + + fun start() { + val script = _script ?: throw IllegalStateException("Attempted to start V8 without script"); + synchronized(this) { + if (_runtime != null) + return; + + val host = V8Host.getV8Instance(); + val options = host.jsRuntimeType.getRuntimeOptions(); + _runtime = host.createV8Runtime(options); + if (!host.isIsolateCreated) + throw IllegalStateException("Isolate not created"); + + //Setup bridge + _runtime?.let { + it.converter = V8Converter(); + + for (pack in _depsPackages) { + if (pack.variableName != null) + it.createV8ValueObject().use { v8valueObject -> + it.globalObject.set(pack.variableName, v8valueObject); + v8valueObject.bind(pack); + }; + catchScriptErrors("Package Dep[${pack.name}]") { + for (packScript in pack.getScripts()) + it.getExecutor(packScript).executeVoid(); + } + } + + //Load deps + for (dep in _deps) + catchScriptErrors("Dep[${dep.key}]") { + it.getExecutor(dep.value).executeVoid() + }; + + + if (config.allowEval) + it.allowEval(true); + + //Load plugin + catchScriptErrors("Plugin[${config.name}]") { + it.getExecutor(script).executeVoid() + }; + } + } + } + fun stop(){ + Logger.i(TAG, "Stopping plugin [${config.name}]"); + synchronized(this) { + _runtime?.let { + _runtime = null; + if(!it.isClosed && !it.isDead) + it.close(); + }; + } + onStopped.emit(this); + } + + fun execute(js: String) : V8Value { + return executeTyped(js); + } + fun executeTyped(js: String) : T { + val runtime = _runtime ?: throw IllegalStateException("JSPlugin not started yet"); + return catchScriptErrors("Plugin[${config.name}]", js) { runtime.getExecutor(js).execute() }; + } + fun executeBoolean(js: String) : Boolean? = catchScriptErrors("Plugin[${config.name}]") { executeTyped(js).value }; + fun executeString(js: String) : String? = catchScriptErrors("Plugin[${config.name}]") { executeTyped(js).value }; + fun executeInteger(js: String) : Int? = catchScriptErrors("Plugin[${config.name}]") { executeTyped(js).value }; + + private fun getPackage(context: Context, packageName: String): V8Package { + //TODO: Auto get all package types? + return when(packageName) { + "DOMParser" -> PackageDOMParser(context, this) + "Http" -> PackageHttp(this, config) + "Utilities" -> PackageUtilities(this, config) + else -> throw ScriptCompilationException(config, "Unknown package [${packageName}] required for plugin ${config.name}"); + }; + } + + fun catchScriptErrors(context: String, code: String? = null, handle: ()->T): T { + return catchScriptErrors(this.config, context, code, handle); + } + + companion object { + private val REGEX_EX_FALLBACK = Regex(".*throw.*?[\"](.*)[\"].*"); + private val REGEX_EX_FALLBACK2 = Regex(".*throw.*?['](.*)['].*"); + + val TAG = "V8Plugin"; + + fun catchScriptErrors(config: IV8PluginConfig, context: String, code: String? = null, handle: ()->T): T { + var codeStripped = code; + if(codeStripped != null) { //TODO: Improve code stripped + if (codeStripped.contains("(") && codeStripped.contains(")")) + { + val start = codeStripped.indexOf("("); + val end = codeStripped.lastIndexOf(")"); + codeStripped = codeStripped.substring(0, start) + "(...)" + codeStripped.substring(end + 1); + } + } + try { + val result = handle(); + + if(result is V8ValueObject) { + val type = result.getString("plugin_type"); + if(type != null && type.endsWith("Exception")) + Companion.throwExceptionFromV8( + config, + result.getOrThrow(config, "plugin_type", "V8Plugin"), + result.getOrThrow(config, "message", "V8Plugin"), + null, + null, + codeStripped + ); + } + + + return result; + } + catch(scriptEx: JavetCompilationException) { + throw ScriptCompilationException(config, "Compilation: ${scriptEx.message}\n(${scriptEx.scriptingError.lineNumber})[${scriptEx.scriptingError.startColumn}-${scriptEx.scriptingError.endColumn}]: ${scriptEx.scriptingError.sourceLine}", null, codeStripped); + } + catch(executeEx: JavetExecutionException) { + val exMessage = extractJSExceptionMessage(executeEx); + + if(executeEx.scriptingError?.context?.containsKey("plugin_type") == true) + throwExceptionFromV8( + config, + executeEx.scriptingError.context["plugin_type"].toString(), + (exMessage ?: ""), + executeEx, + executeEx.scriptingError?.stack, + codeStripped + ); + + throw ScriptExecutionException(config, "${exMessage}", null, executeEx.scriptingError?.stack, codeStripped); + } + catch(ex: Exception) { + throw ex; + } + } + + private fun throwExceptionFromV8(config: IV8PluginConfig, pluginType: String, msg: String, innerEx: Exception? = null, stack: String? = null, code: String? = null) { + when(pluginType) { + "ScriptException" -> throw ScriptException(config, msg, innerEx, stack, code); + "AgeException" -> throw ScriptAgeException(config, msg, innerEx, stack, code); + "UnavailableException" -> throw ScriptUnavailableException(config, msg, innerEx, stack, code); + "ScriptExecutionException" -> throw ScriptExecutionException(config, msg, innerEx, stack, code); + "ScriptCompilationException" -> throw ScriptCompilationException(config, msg, innerEx, code); + "ScriptImplementationException" -> throw ScriptImplementationException(config, msg, innerEx, null, code); + "ScriptTimeoutException" -> throw ScriptTimeoutException(config, msg, innerEx); + "NoInternetException" -> throw NoInternetException(config, msg, innerEx, stack, code); + else -> throw ScriptExecutionException(config, msg, innerEx, stack, code); + } + } + + private fun extractJSExceptionMessage(ex: JavetExecutionException) : String? { + val lineInfo = " (${ex.scriptingError.lineNumber})[${ex.scriptingError.startColumn}-${ex.scriptingError.endColumn}]"; + + if(ex.message == null || ex.message == "") { + if(!ex.scriptingError?.message.isNullOrEmpty()) + return ex.scriptingError?.message!! + lineInfo; + else if(!ex.scriptingError?.sourceLine?.isNullOrEmpty()!!) { + val source = ex.scriptingError.sourceLine; + val matchReg1 = REGEX_EX_FALLBACK.matchEntire(source); + val matchReg2 = REGEX_EX_FALLBACK2.matchEntire(source); + if(matchReg1 != null) + return matchReg1.groupValues[1] + lineInfo; + if(matchReg2 != null) + return matchReg2.groupValues[1] + lineInfo; + } + } + else if(!ex.scriptingError?.detailedMessage.isNullOrEmpty()) + return ex.scriptingError.detailedMessage + lineInfo; + else if(!ex.scriptingError?.message.isNullOrEmpty()) + return ex.scriptingError.message + lineInfo; + return ex.message + lineInfo; + } + + private fun getAssetFile(context: Context, path: String) : String { + return StateAssets.readAsset(context, path) ?: throw java.lang.IllegalStateException("script ${path} not found"); + } + } + + + /** + * Methods available for scripts (bridge object) + */ +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/V8PluginConfig.kt b/app/src/main/java/com/futo/platformplayer/engine/V8PluginConfig.kt new file mode 100644 index 00000000..df6ad4bc --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/V8PluginConfig.kt @@ -0,0 +1,29 @@ +package com.futo.platformplayer.engine + +interface IV8PluginConfig { + val name: String; + val allowEval: Boolean; + val allowUrls: List; + val packages: List; +} + +@kotlinx.serialization.Serializable +class V8PluginConfig : IV8PluginConfig { + override val name: String; + override val allowEval: Boolean; + override val allowUrls: List; + override val packages: List; + + constructor() { + name = "Unknown"; + allowEval = false; + allowUrls = listOf(); + packages = listOf(); + } + constructor(name: String, allowEval: Boolean, allowUrls: List, packages: List = listOf()) { + this.name = name; + this.allowEval = allowEval; + this.allowUrls = allowUrls; + this.packages = packages; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/dev/V8RemoteObject.kt b/app/src/main/java/com/futo/platformplayer/engine/dev/V8RemoteObject.kt new file mode 100644 index 00000000..9aa0c6cc --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/dev/V8RemoteObject.kt @@ -0,0 +1,143 @@ +package com.futo.platformplayer.engine.dev + +import com.caoccao.javet.annotations.V8Function +import com.caoccao.javet.annotations.V8Property +import com.futo.platformplayer.logging.Logger +import com.google.gson.GsonBuilder +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonNull +import com.google.gson.JsonObject +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSerializer +import java.lang.reflect.Type +import java.util.stream.IntStream.range +import kotlin.reflect.* +import kotlin.reflect.full.declaredFunctions +import kotlin.reflect.full.hasAnnotation +import kotlin.reflect.full.instanceParameter +import kotlin.reflect.jvm.javaMethod + + +/** + * Serializable object wrapper that communicates complex V8 objects to a format understood by the dev portal + * It allows plugins in development to communicate with package logic on the phone + * It does this by embedding object ids and function names into the object, which dev portal can then intercept and call via special endpoints + */ +class V8RemoteObject { + private val _id: String; + private val _class: KClass<*>; + val obj: Any; + + val requiresRegistration: Boolean; + + constructor(id: String, obj: Any) { + this._id = id; + this._class = obj::class; + this.obj = obj; + this.requiresRegistration = getV8Functions(_class).isNotEmpty() || getV8Properties(_class).isNotEmpty(); + } + + fun prop(propName: String): Any? { + val propMethod = getV8Property(_class, propName); + return propMethod.call(obj); + } + fun call(methodName: String, array: JsonArray): Any? { + val propMethod = getV8Function(_class, methodName); + + val map = mutableMapOf(); + var instanceParaCount = 0; + for(i in range(0, propMethod.parameters.size)) { + val para = propMethod.parameters[i]; + if(para == propMethod.instanceParameter) { + map.put(para, obj); + instanceParaCount++; + } + else if(i - instanceParaCount < array.size()) + map.put(para, gsonStandard.fromJson(array.get(i - instanceParaCount), propMethod.javaMethod!!.parameterTypes[i - instanceParaCount])); + } + + return propMethod.callBy(map) + } + + + fun serialize(): String { + return _gson.toJson(this); + } + + class Serializer : JsonSerializer { + override fun serialize(src: V8RemoteObject?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { + try { + if (src == null) + return JsonNull.INSTANCE; + if (!src.requiresRegistration) + return gsonStandard.toJsonTree(src.obj, src.obj.javaClass); + else { + val obj = _gson.toJsonTree(src.obj) as JsonObject; + obj.addProperty("__id", src._id); + + val methodsArray = JsonArray(); + for (method in getV8Functions(src._class)) + methodsArray.add(method.name); + obj.add("__methods", methodsArray); + + val propsArray = JsonArray(); + for (method in getV8Properties(src._class)) + propsArray.add(method.name); + obj.add("__props", propsArray); + + return obj; + } + } + catch(ex: StackOverflowError) { + val msg = "Recursive structure for class [${src?._class?.simpleName}], can't serialize..: ${ex.message}"; + Logger.e("V8RemoteObject", msg); + throw IllegalArgumentException(msg); + } + } + } + + companion object { + val gsonStandard = GsonBuilder() + .serializeNulls() + .setPrettyPrinting() + .create(); + private val _gson = GsonBuilder() + .registerTypeAdapter(V8RemoteObject::class.java, Serializer()) + .create(); + + private val _classV8Functions: HashMap, List>> = hashMapOf(); + private val _classV8Props: HashMap, List>> = hashMapOf(); + + + fun getV8Functions(clazz: KClass<*>): List> { + if(!_classV8Functions.containsKey(clazz)) + _classV8Functions.put(clazz, clazz.declaredFunctions.filter { it.hasAnnotation() }.toList()); + return _classV8Functions.get(clazz)!!; + } + fun getV8Function(clazz: KClass<*>, name: String): KFunction<*> { + val functions = getV8Functions(clazz); + val method = functions.firstOrNull { it.name == name }; + if(method == null) + throw IllegalArgumentException("Non-existent property ${name}"); + return method; + } + fun getV8Properties(clazz: KClass<*>): List> { + if(!_classV8Props.containsKey(clazz)) + _classV8Props.put(clazz, clazz.declaredFunctions.filter { it.hasAnnotation() }.toList()); + return _classV8Props.get(clazz)!!; + } + fun getV8Property(clazz: KClass<*>, name: String): KFunction<*> { + val props = getV8Properties(clazz); + val method = props.firstOrNull { it.name == name }; + if(method == null) + throw IllegalArgumentException("Non-existent property ${name}"); + return method; + } + + + fun List.serialize() : String { + return _gson.toJson(this); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/NoInternetException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/NoInternetException.kt new file mode 100644 index 00000000..bce39025 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/NoInternetException.kt @@ -0,0 +1,17 @@ +package com.futo.platformplayer.engine.exceptions + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow + +open class NoInternetException(config: IV8PluginConfig, error: String, ex: Exception? = null, stack: String? = null, code: String? = null) : ScriptException(config, error, ex, stack, code) { + + + + + companion object { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : NoInternetException { + return NoInternetException(config, obj.getOrThrow(config, "message", "NoInternetException")); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/PluginException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/PluginException.kt new file mode 100644 index 00000000..09941166 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/PluginException.kt @@ -0,0 +1,7 @@ +package com.futo.platformplayer.engine.exceptions + +import com.futo.platformplayer.engine.IV8PluginConfig + +open class PluginException(val config: IV8PluginConfig, msg: String, ex: Exception? = null, val code: String? = null): Exception("[${config.name}] " + msg, ex) { + +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptAgeException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptAgeException.kt new file mode 100644 index 00000000..ef1ca13f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptAgeException.kt @@ -0,0 +1,17 @@ +package com.futo.platformplayer.engine.exceptions + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow + +open class ScriptAgeException(config: IV8PluginConfig, error: String, ex: Exception? = null, stack: String? = null, code: String? = null) : ScriptException(config, error, ex, stack, code) { + + + + + companion object { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptException { + return ScriptException(config, obj.getOrThrow(config, "message", "ScriptAgeException")); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptCompilationException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptCompilationException.kt new file mode 100644 index 00000000..2db245d3 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptCompilationException.kt @@ -0,0 +1,14 @@ +package com.futo.platformplayer.engine.exceptions + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow + +class ScriptCompilationException(config: IV8PluginConfig, error: String, ex: Exception? = null, code: String? = null) : PluginException(config, error, ex, code) { + + companion object { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptCompilationException { + return ScriptCompilationException(config, obj.getOrThrow(config, "message", "ScriptCompilationException")); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptException.kt new file mode 100644 index 00000000..cf038a23 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptException.kt @@ -0,0 +1,17 @@ +package com.futo.platformplayer.engine.exceptions + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow + +open class ScriptException(config: IV8PluginConfig, error: String, ex: Exception? = null, stack: String? = null, code: String? = null) : ScriptExecutionException(config, error, ex, stack, code) { + + + + + companion object { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptException { + return ScriptException(config, obj.getOrThrow(config, "message", "ScriptException")); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptExecutionException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptExecutionException.kt new file mode 100644 index 00000000..28b9b0e9 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptExecutionException.kt @@ -0,0 +1,17 @@ +package com.futo.platformplayer.engine.exceptions + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow + +open class ScriptExecutionException(config: IV8PluginConfig, error: String, ex: Exception? = null, val stack: String? = null, code: String? = null) : PluginException(config, error, ex, code) { + + + + + companion object { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptExecutionException { + return ScriptExecutionException(config, obj.getOrThrow(config, "message", "ScriptExecutionException")); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptImplementationException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptImplementationException.kt new file mode 100644 index 00000000..dd2aaf7a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptImplementationException.kt @@ -0,0 +1,14 @@ +package com.futo.platformplayer.engine.exceptions + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow + +class ScriptImplementationException(config: IV8PluginConfig, error: String, ex: Exception? = null, var pluginId: String? = null, code: String? = null) : PluginException(config, error, ex, code) { + + companion object { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptImplementationException { + return ScriptImplementationException(config, obj.getOrThrow(config, "message", "ScriptImplementationException")); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptTimeoutException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptTimeoutException.kt new file mode 100644 index 00000000..6f883854 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptTimeoutException.kt @@ -0,0 +1,13 @@ +package com.futo.platformplayer.engine.exceptions + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow + +class ScriptTimeoutException(config: IV8PluginConfig, error: String, ex: Exception? = null) : ScriptException(config, error, ex) { + companion object { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptTimeoutException { + return ScriptTimeoutException(config, obj.getOrThrow(config, "message", "ScriptException")); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptUnavailableException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptUnavailableException.kt new file mode 100644 index 00000000..5d331b8b --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptUnavailableException.kt @@ -0,0 +1,14 @@ +package com.futo.platformplayer.engine.exceptions + +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.getOrThrow + +class ScriptUnavailableException(config: IV8PluginConfig, error: String, ex: Exception? = null, stack: String? = null, code: String? = null) : ScriptException(config, error, ex, stack, code) { + + companion object { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : ScriptException { + return ScriptUnavailableException(config, obj.getOrThrow(config, "message", "ScriptUnavailableException")); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptValidationException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptValidationException.kt new file mode 100644 index 00000000..a0c27edf --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/ScriptValidationException.kt @@ -0,0 +1,3 @@ +package com.futo.platformplayer.engine.exceptions + +class ScriptValidationException(error: String, ex: Exception? = null) : Exception(error, ex); \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/internal/IV8Object.kt b/app/src/main/java/com/futo/platformplayer/engine/internal/IV8Object.kt new file mode 100644 index 00000000..27390d67 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/internal/IV8Object.kt @@ -0,0 +1,9 @@ +package com.futo.platformplayer.engine.internal + +import com.caoccao.javet.interop.V8Runtime +import com.caoccao.javet.values.V8Value + + +interface IV8Convertable { + fun toV8(runtime: V8Runtime) : V8Value?; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/internal/V8BindObject.kt b/app/src/main/java/com/futo/platformplayer/engine/internal/V8BindObject.kt new file mode 100644 index 00000000..4e861b72 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/internal/V8BindObject.kt @@ -0,0 +1,33 @@ +package com.futo.platformplayer.engine.internal + +import com.caoccao.javet.annotations.V8Function +import com.caoccao.javet.interop.V8Runtime +import com.caoccao.javet.values.V8Value +import com.caoccao.javet.values.reference.V8ValueObject + +open class V8BindObject : IV8Convertable { + protected var _runtimeObj: V8ValueObject? = null; + protected var _isDisposed: Boolean = false + private set; + + + override fun toV8(runtime: V8Runtime): V8Value? { + synchronized(this) { + if(_runtimeObj != null) + return _runtimeObj; + + val v8Obj = runtime.createV8ValueObject(); + v8Obj.bind(this); + _runtimeObj = v8Obj; + return v8Obj; + } + } + + @V8Function + open fun dispose() { + if(!_isDisposed) { + //_runtimeObj?.v8Runtime?.v8Internal?.removeReference(_runtimeObj); + //_isDisposed = true; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/internal/V8Converter.kt b/app/src/main/java/com/futo/platformplayer/engine/internal/V8Converter.kt new file mode 100644 index 00000000..62b6c149 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/internal/V8Converter.kt @@ -0,0 +1,28 @@ +package com.futo.platformplayer.engine.internal + +import com.caoccao.javet.annotations.V8Convert +import com.caoccao.javet.interop.V8Runtime +import com.caoccao.javet.interop.converters.JavetObjectConverter +import com.caoccao.javet.interop.converters.JavetProxyConverter +import com.caoccao.javet.values.V8Value +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.V8Plugin + + +class V8Converter : JavetObjectConverter() { + + + override fun toV8Value(v8Runtime: V8Runtime, obj: Any?, depth: Int): T? { + if (obj == null) + return null; + + val value: V8Value? = super.toV8Value(v8Runtime, obj, depth) + if (value != null && !value.isUndefined) + return value as T; + if (obj != null) { + if (obj is IV8Convertable) + return obj.toV8(v8Runtime) as T; + } + return null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt new file mode 100644 index 00000000..0760a609 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt @@ -0,0 +1,65 @@ +package com.futo.platformplayer.engine.packages + +import com.caoccao.javet.annotations.V8Function +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateDeveloper +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.engine.V8Plugin +import com.futo.platformplayer.states.StateApp +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class PackageBridge : V8Package { + @Transient + private val _config: IV8PluginConfig; + @Transient + private val _client: ManagedHttpClient + @Transient + private val _clientAuth: ManagedHttpClient + + override val name: String get() = "Bridge"; + override val variableName: String get() = "bridge"; + + constructor(plugin: V8Plugin, config: IV8PluginConfig): super(plugin) { + _config = config; + _client = plugin.httpClient; + _clientAuth = plugin.httpClientAuth; + } + + @V8Function + fun toast(str: String) { + StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { + try { + UIDialogs.toast(str); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to show toast.", e); + } + } + } + @V8Function + fun log(str: String?) { + Logger.i(_config.name, str ?: "null"); + if(_config is SourcePluginConfig && _config.id == StateDeveloper.DEV_ID) + StateDeveloper.instance.logDevInfo(StateDeveloper.instance.currentDevID ?: "", str ?: "null"); + } + + @V8Function + fun throwTest(str: String) { + throw IllegalStateException(str); + } + + @V8Function + fun isLoggedIn(): Boolean { + if (_clientAuth is JSHttpClient) + return _clientAuth.isLoggedIn; + return false; + } + + companion object { + private const val TAG = "PackageBridge"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageDOMParser.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageDOMParser.kt new file mode 100644 index 00000000..9c5cacbd --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageDOMParser.kt @@ -0,0 +1,153 @@ +package com.futo.platformplayer.engine.packages + +import android.content.Context +import android.util.Log +import com.caoccao.javet.annotations.V8Allow +import com.caoccao.javet.annotations.V8Convert +import com.caoccao.javet.annotations.V8Function +import com.caoccao.javet.annotations.V8Property +import com.caoccao.javet.enums.V8ConversionMode +import com.caoccao.javet.enums.V8ProxyMode +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.engine.V8Plugin +import com.futo.platformplayer.engine.dev.V8RemoteObject +import com.futo.platformplayer.engine.internal.V8BindObject +import org.jsoup.Jsoup +import org.jsoup.nodes.Element + + +class PackageDOMParser : V8Package { + override val name: String get() = "DOMParser"; + override val variableName: String = "domParser"; + + constructor(context: Context, v8Plugin: V8Plugin): super(v8Plugin) { + //v8Plugin.withDependency(context, "/scripts/some/package/path"); + } + + @V8Function + fun parseFromString(html: String): DOMNode { + val dom = DOMNode.parse(this, html); + return dom; + } + + @V8Convert(mode = V8ConversionMode.AllowOnly, proxyMode = V8ProxyMode.Class) + class DOMNode: V8BindObject { + @Transient + private val _children: ArrayList = arrayListOf(); + + @Transient + private val _element: Element; + @Transient + private val _package: PackageDOMParser; + + @V8Property + fun nodeType(): String = _element.tagName(); + @V8Property + fun childNodes(): List { + val results = _element.children().map { DOMNode(_package, it) }.toList(); + if(results != null) + _children.addAll(results); + return results; + } + @V8Property + fun firstChild(): DOMNode? { + val result = _element.firstElementChild()?.let { DOMNode(_package, it) }; + if(result != null) + _children.add(result); + return result; + } + @V8Property + fun lastChild(): DOMNode? { + val result = _element.firstElementChild()?.let { DOMNode(_package, it) }; + if(result != null) + _children.add(result); + return result; + } + @V8Property + fun parentNode(): DOMNode? { + val result = _element.parent()?.let { DOMNode(_package, it) }; + if(result != null) + _children.add(result); + return result; + } + @V8Property + fun attributes(): Map = _element.attributes().dataset(); + @V8Property + fun innerHTML(): String = _element.html(); + @V8Property + fun outerHTML(): String = _element.outerHtml(); + @V8Property + fun textContent(): String = _element.text(); + @V8Property + fun text(): String = _element.text().ifEmpty { data() }; + @V8Property + fun data(): String = _element.data(); + + @V8Property + fun classList(): List = _element.classNames().toList() + + @V8Property + fun className(): String = _element.className(); + + + constructor(parser: PackageDOMParser, element: Element) { + _package = parser; + _element = element; + } + + @V8Function + fun getAttribute(key: String): String { + return _element.attr(key); + } + @V8Function + fun getElementById(id: String): DOMNode? { + val node = _element.getElementById(id)?.let { DOMNode(_package, it) }; + if(node != null) + _children.add(node); + return node; + } + @V8Function + fun getElementsByClassName(className: String): List { + val results = _element.getElementsByClass(className).map { DOMNode(_package, it) }.toList(); + _children.addAll(results); + return results; + } + @V8Function + fun getElementsByTagName(tagName: String): List { + val results = _element.getElementsByTag(tagName).map { DOMNode(_package, it) }.toList(); + _children.addAll(results); + return results; + } + @V8Function + fun getElementsByName(name: String): List { + val results = _element.getElementsByAttributeValue("name", name).map { DOMNode(_package, it) }.toList(); + _children.addAll(results); + return results; + } + + @V8Function + fun querySelector(query: String): DOMNode? { + val result = _element.selectFirst(query) ?: return null; + return DOMNode(_package, result); + } + @V8Function + fun querySelectorAll(query: String): List { + val results = _element.select(query) ?: return listOf(); + return results.map { DOMNode(_package, it) }; + } + + @V8Function + override fun dispose() { + for(child in _children) + child.dispose(); + _children.clear(); + super.dispose(); + } + + companion object { + fun parse(parser: PackageDOMParser, str: String): DOMNode { + return DOMNode(parser, Jsoup.parse(str)); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt new file mode 100644 index 00000000..36372af4 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt @@ -0,0 +1,474 @@ +package com.futo.platformplayer.engine.packages + +import com.caoccao.javet.annotations.V8Convert +import com.caoccao.javet.annotations.V8Function +import com.caoccao.javet.annotations.V8Property +import com.caoccao.javet.enums.V8ConversionMode +import com.caoccao.javet.enums.V8ProxyMode +import com.caoccao.javet.interop.V8Runtime +import com.caoccao.javet.values.V8Value +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.engine.V8Plugin +import com.futo.platformplayer.engine.internal.IV8Convertable +import com.futo.platformplayer.engine.internal.V8BindObject +import com.futo.platformplayer.getOrThrow +import kotlinx.coroutines.CoroutineScope +import java.net.SocketTimeoutException +import kotlin.streams.toList + +class PackageHttp: V8Package { + @Transient + private val _config: IV8PluginConfig; + @Transient + private val _client: ManagedHttpClient + @Transient + private val _clientAuth: ManagedHttpClient + @Transient + private val _packageClient: PackageHttpClient; + @Transient + private val _packageClientAuth: PackageHttpClient + + + override val name: String get() = "Http"; + override val variableName: String get() = "http"; + + + constructor(plugin: V8Plugin, config: IV8PluginConfig): super(plugin) { + _config = config; + _client = plugin.httpClient; + _clientAuth = plugin.httpClientAuth; + _packageClient = PackageHttpClient(this, _client); + _packageClientAuth = PackageHttpClient(this, _clientAuth); + } + + @V8Function + fun newClient(withAuth: Boolean): PackageHttpClient { + return PackageHttpClient(this, if(withAuth) _clientAuth.clone() else _client.clone()); + } + @V8Function + fun getDefaultClient(withAuth: Boolean): PackageHttpClient { + return if(withAuth) _packageClientAuth else _packageClient; + } + + @V8Function + fun batch(): BatchBuilder { + return BatchBuilder(this); + } + + @V8Function + fun request(method: String, url: String, headers: MutableMap = HashMap(), useAuth: Boolean = false) : BridgeHttpResponse { + return if(useAuth) + _packageClientAuth.request(method, url, headers) + else + _packageClient.request(method, url, headers); + } + + @V8Function + fun requestWithBody(method: String, url: String, body:String, headers: MutableMap = HashMap(), useAuth: Boolean = false) : BridgeHttpResponse { + return if(useAuth) + _packageClientAuth.requestWithBody(method, url, body, headers) + else + _packageClient.requestWithBody(method, url, body, headers); + } + @V8Function + fun GET(url: String, headers: MutableMap = HashMap(), useAuth: Boolean = false) : BridgeHttpResponse { + return if(useAuth) + _packageClientAuth.GET(url, headers) + else + _packageClient.GET(url, headers); + } + @V8Function + fun POST(url: String, body: String, headers: MutableMap = HashMap(), useAuth: Boolean = false) : BridgeHttpResponse { + return if(useAuth) + _packageClientAuth.POST(url, body, headers) + else + _packageClient.POST(url, body, headers); + } + + @V8Function + fun socket(url: String, headers: Map? = null, useAuth: Boolean = false): SocketResult { + return if(useAuth) + _packageClientAuth.socket(url, headers) + else + _packageClient.socket(url, headers); + } + + private fun logExceptions(handle: ()->T): T { + try { + return handle(); + } + catch(ex: Exception) { + Logger.e("Plugin[${_config.name}]", ex.message, ex); + throw ex; + } + } + + @kotlinx.serialization.Serializable + class BridgeHttpResponse(val code: Int, val body: String?, val headers: Map>? = null) : IV8Convertable { + val isOk = code >= 200 && code < 300; + + override fun toV8(runtime: V8Runtime): V8Value? { + val obj = runtime.createV8ValueObject(); + obj.set("code", code); + obj.set("body", body); + obj.set("headers", headers); + obj.set("isOk", isOk); + return obj; + } + } + + //TODO: This object is currently re-wrapped each modification, this is due to an issue passing the same object back and forth, should be fixed in future. + @V8Convert(mode = V8ConversionMode.AllowOnly, proxyMode = V8ProxyMode.Class) + class BatchBuilder(private val _package: PackageHttp, existingRequests: MutableList> = mutableListOf()): V8BindObject() { + @Transient + private val _reqs = existingRequests; + + @V8Function + fun request(method: String, url: String, headers: MutableMap = HashMap(), useAuth: Boolean = false) : BatchBuilder { + return clientRequest(_package.getDefaultClient(useAuth), method, url, headers); + } + @V8Function + fun requestWithBody(method: String, url: String, body:String, headers: MutableMap = HashMap(), useAuth: Boolean = false) : BatchBuilder { + return clientRequestWithBody(_package.getDefaultClient(useAuth), method, url, body, headers); + } + @V8Function + fun GET(url: String, headers: MutableMap = HashMap(), useAuth: Boolean = false) : BatchBuilder + = clientGET(_package.getDefaultClient(useAuth), url, headers); + @V8Function + fun POST(url: String, body: String, headers: MutableMap = HashMap(), useAuth: Boolean = false) : BatchBuilder + = clientPOST(_package.getDefaultClient(useAuth), url, body, headers); + + //Client-specific + + @V8Function + fun clientRequest(client: PackageHttpClient, method: String, url: String, headers: MutableMap = HashMap()) : BatchBuilder { + _reqs.add(Pair(client, RequestDescriptor(method, url, headers))); + return BatchBuilder(_package, _reqs); + } + @V8Function + fun clientRequestWithBody(client: PackageHttpClient, method: String, url: String, body:String, headers: MutableMap = HashMap()) : BatchBuilder { + _reqs.add(Pair(client, RequestDescriptor(method, url, headers, body))); + return BatchBuilder(_package, _reqs); + } + @V8Function + fun clientGET(client: PackageHttpClient, url: String, headers: MutableMap = HashMap()) : BatchBuilder + = clientRequest(client, "GET", url, headers); + @V8Function + fun clientPOST(client: PackageHttpClient, url: String, body: String, headers: MutableMap = HashMap()) : BatchBuilder + = clientRequestWithBody(client, "POST", url, body, headers); + + + //Finalizer + @V8Function + fun execute(): List { + return _reqs.parallelStream().map { + if(it.second.body != null) + return@map it.first.requestWithBody(it.second.method, it.second.url, it.second.body!!, it.second.headers); + else + return@map it.first.request(it.second.method, it.second.url, it.second.headers); + }.toList(); + } + } + + + + @V8Convert(mode = V8ConversionMode.AllowOnly, proxyMode = V8ProxyMode.Class) + class PackageHttpClient : V8BindObject { + + @Transient + private val _package: PackageHttp; + @Transient + private val _client: ManagedHttpClient; + + @Transient + private val _defaultHeaders = mutableMapOf(); + + constructor(pack: PackageHttp, baseClient: ManagedHttpClient): super() { + _package = pack; + _client = baseClient; + } + + @V8Function + fun setDefaultHeaders(defaultHeaders: Map): PackageHttpClient { + for(pair in defaultHeaders) + _defaultHeaders[pair.key] = pair.value; + return this; + } + @V8Function + fun setDoApplyCookies(apply: Boolean): PackageHttpClient { + if(_client is JSHttpClient) + _client.doApplyCookies = apply; + return this; + } + @V8Function + fun setDoUpdateCookies(update: Boolean): PackageHttpClient { + if(_client is JSHttpClient) + _client.doUpdateCookies = update; + return this; + } + @V8Function + fun setDoAllowNewCookies(allow: Boolean): PackageHttpClient { + if(_client is JSHttpClient) + _client.doAllowNewCookies = allow; + return this; + } + + @V8Function + fun request(method: String, url: String, headers: MutableMap = HashMap()) : BridgeHttpResponse { + applyDefaultHeaders(headers); + return logExceptions { + return@logExceptions catchHttp { + val client = _client; + logRequest(method, url, headers, null); + val resp = client.requestMethod(method, url, headers); + val responseBody = resp.body?.string(); + logResponse(method, url, resp.code, resp.headers, responseBody); + return@catchHttp BridgeHttpResponse(resp.code, responseBody, sanitizeResponseHeaders(resp.headers)); + } + }; + } + @V8Function + fun requestWithBody(method: String, url: String, body:String, headers: MutableMap = HashMap()) : BridgeHttpResponse { + applyDefaultHeaders(headers); + return logExceptions { + catchHttp { + val client = _client; + logRequest(method, url, headers, body); + val resp = client.requestMethod(method, url, body, headers); + val responseBody = resp.body?.string(); + logResponse(method, url, resp.code, resp.headers, responseBody); + return@catchHttp BridgeHttpResponse(resp.code, responseBody, sanitizeResponseHeaders(resp.headers)); + } + }; + } + + @V8Function + fun GET(url: String, headers: MutableMap = HashMap()) : BridgeHttpResponse { + applyDefaultHeaders(headers); + return logExceptions { + catchHttp { + val client = _client; + logRequest("GET", url, headers, null); + val resp = client.get(url, headers); + val responseBody = resp.body?.string(); + logResponse("GET", url, resp.code, resp.headers, responseBody); + return@catchHttp BridgeHttpResponse(resp.code, responseBody, sanitizeResponseHeaders(resp.headers)); + } + }; + } + @V8Function + fun POST(url: String, body: String, headers: MutableMap = HashMap()) : BridgeHttpResponse { + applyDefaultHeaders(headers); + return logExceptions { + catchHttp { + val client = _client; + logRequest("POST", url, headers, body); + val resp = client.post(url, body, headers); + val responseBody = resp.body?.string(); + logResponse("POST", url, resp.code, resp.headers, responseBody); + return@catchHttp BridgeHttpResponse(resp.code, responseBody, sanitizeResponseHeaders(resp.headers)); + } + }; + } + + @V8Function + fun socket(url: String, headers: Map? = null): SocketResult { + val socketHeaders = headers?.toMutableMap() ?: HashMap(); + applyDefaultHeaders(socketHeaders); + return SocketResult(this, _client, url, socketHeaders ?: HashMap()); + } + + private fun applyDefaultHeaders(headerMap: MutableMap) { + synchronized(_defaultHeaders) { + for(toApply in _defaultHeaders) + if(!headerMap.containsKey(toApply.key)) + headerMap[toApply.key] = toApply.value; + } + } + + private fun sanitizeResponseHeaders(headers: Map>?): Map> { + val result = mutableMapOf>() + headers?.forEach { (header, values) -> + val lowerCaseHeader = header.lowercase() + if (WHITELISTED_RESPONSE_HEADERS.contains(lowerCaseHeader)) { + result[lowerCaseHeader] = values + } + } + return result + } + + private fun logRequest(method: String, url: String, headers: Map = HashMap(), body: String?) { + return; + + Logger.v(TAG) { + val stringBuilder = StringBuilder(); + stringBuilder.appendLine("HTTP request (useAuth = )"); + stringBuilder.appendLine("$method $url"); + + for (pair in headers) { + stringBuilder.appendLine("${pair.key}: ${pair.value}"); + } + + if (body != null) { + stringBuilder.appendLine(); + stringBuilder.appendLine(body); + } + + return@v stringBuilder.toString(); + }; + } + + private fun logResponse(method: String, url: String, responseCode: Int? = null, responseHeaders: Map> = HashMap(), responseBody: String? = null) { + return; + + Logger.v(TAG) { + val stringBuilder = StringBuilder(); + if (responseCode != null) { + stringBuilder.appendLine("HTTP response (${responseCode})"); + stringBuilder.appendLine("$method $url"); + + for (pair in responseHeaders) { + if (pair.key.equals("authorization", ignoreCase = true) || pair.key.equals("set-cookie", ignoreCase = true)) { + stringBuilder.appendLine("${pair.key}: @CENSOREDVALUE@"); + } else { + stringBuilder.appendLine("${pair.key}: ${pair.value.joinToString("; ")}"); + } + } + + if (responseBody != null) { + stringBuilder.appendLine(); + stringBuilder.appendLine(responseBody); + } + } else { + stringBuilder.appendLine("No response"); + } + + return@v stringBuilder.toString(); + }; + } + + fun logExceptions(handle: ()->T): T { + try { + return handle(); + } + catch(ex: Exception) { + Logger.e("Plugin[${_package._config.name}]", ex.message, ex); + throw ex; + } + } + + private fun catchHttp(handle: ()->BridgeHttpResponse): BridgeHttpResponse { + try{ + return handle(); + } + //Forward timeouts + catch(ex: SocketTimeoutException) { + return BridgeHttpResponse(408, null); + } + } + } + + @V8Convert(mode = V8ConversionMode.AllowOnly, proxyMode = V8ProxyMode.Class) + class SocketResult: V8BindObject { + private var _isOpen = false; + private var _socket: ManagedHttpClient.Socket? = null; + + private var _listeners: V8ValueObject? = null; + + private val _packageClient: PackageHttpClient; + private val _client: ManagedHttpClient; + private val _url: String; + private val _headers: Map; + + constructor(pack: PackageHttpClient, client: ManagedHttpClient, url: String, headers: Map) { + _packageClient = pack; + _client = client; + _url = url; + _headers = headers; + } + + @V8Property + fun isOpen(): Boolean = _isOpen; //TODO + + @V8Function + fun connect(socketObj: V8ValueObject) { + val hasOpen = socketObj.has("open"); + val hasMessage = socketObj.has("message"); + val hasClosing = socketObj.has("closing"); + val hasClosed = socketObj.has("closed"); + val hasFailure = socketObj.has("failure"); + + //socketObj.setWeak(); //We have to manage this lifecycle + _listeners = socketObj; + + _socket = _packageClient.logExceptions { + val client = _client; + return@logExceptions client.socket(_url, _headers.toMutableMap(), object: ManagedHttpClient.SocketListener { + override fun open() { + Logger.i(TAG, "Websocket opened: " + _url); + _isOpen = true; + if(hasOpen) + _listeners?.invokeVoid("open", arrayOf()); + } + override fun message(msg: String) { + if(hasMessage) { + try { + _listeners?.invokeVoid("message", msg); + } + catch(ex: Throwable) {} + } + } + override fun closing(code: Int, reason: String) { + if(hasClosing) + _listeners?.invokeVoid("closing", code, reason); + } + override fun closed(code: Int, reason: String) { + _isOpen = false; + if(hasClosed) + _listeners?.invokeVoid("closed", code, reason); + } + override fun failure(exception: Throwable) { + _isOpen = false; + Logger.e(TAG, "Websocket failure: ${exception.message} (${_url})", exception); + if(hasFailure) + _listeners?.invokeVoid("failure", exception.message); + } + }); + }; + } + + @V8Function + fun send(msg: String) { + _socket?.send(msg); + } + } + + data class RequestDescriptor( + val method: String, + val url: String, + val headers: MutableMap, + val body: String? = null, + val contentType: String? = null + ) + + private fun catchHttp(handle: ()->BridgeHttpResponse): BridgeHttpResponse { + try{ + return handle(); + } + //Forward timeouts + catch(ex: SocketTimeoutException) { + return BridgeHttpResponse(408, null); + } + } + + + + companion object { + private const val TAG = "PackageHttp"; + private val WHITELISTED_RESPONSE_HEADERS = listOf("content-type", "date", "content-length", "last-modified", "etag", "cache-control", "content-encoding", "content-disposition", "connection") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageUtilities.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageUtilities.kt new file mode 100644 index 00000000..98e64dbe --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageUtilities.kt @@ -0,0 +1,29 @@ +package com.futo.platformplayer.engine.packages + +import android.util.Base64 +import com.caoccao.javet.annotations.V8Function +import com.futo.platformplayer.engine.IV8PluginConfig +import com.futo.platformplayer.engine.V8Plugin +import java.util.UUID + +class PackageUtilities : V8Package { + @Transient + private val _config: IV8PluginConfig; + + override val name: String get() = "Utilities"; + override val variableName: String get() = "utility"; + + constructor(plugin: V8Plugin, config: IV8PluginConfig): super(plugin) { + _config = config; + } + + @V8Function + fun toBase64(arr: ByteArray): String { + return Base64.encodeToString(arr, Base64.NO_WRAP); + } + + @V8Function + fun randomUUID(): String { + return UUID.randomUUID().toString(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/V8Package.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/V8Package.kt new file mode 100644 index 00000000..beb7c118 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/V8Package.kt @@ -0,0 +1,25 @@ +package com.futo.platformplayer.engine.packages + +import com.futo.platformplayer.engine.V8Plugin + +abstract class V8Package { + @Transient + protected val _plugin: V8Plugin; + @Transient + private val _scripts: MutableList = mutableListOf(); + + abstract val name: String; + @Transient + open val variableName: String? = null; + + constructor(v8Plugin: V8Plugin) { + _plugin = v8Plugin; + } + + fun withScript(str: String) { + _scripts.add(str); + } + fun getScripts() : List { + return _scripts.toList(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/exceptions/ChannelException.kt b/app/src/main/java/com/futo/platformplayer/exceptions/ChannelException.kt new file mode 100644 index 00000000..1309584f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/exceptions/ChannelException.kt @@ -0,0 +1,19 @@ +package com.futo.platformplayer.exceptions + +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel + +class ChannelException : Exception { + val url: String?; + val channel: IPlatformChannel?; + + val channelNameOrUrl: String? get() = channel?.name ?: url; + + constructor(url: String, ex: Throwable): super("Channel: ${url} failed", ex) { + this.url = url; + this.channel = null; + } + constructor(channel: IPlatformChannel, ex: Throwable): super("Channel: ${channel.name} failed (${ex.message})", ex) { + this.url = channel.url; + this.channel = channel; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/exceptions/MigrationException.kt b/app/src/main/java/com/futo/platformplayer/exceptions/MigrationException.kt new file mode 100644 index 00000000..9d1e74b6 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/exceptions/MigrationException.kt @@ -0,0 +1,5 @@ +package com.futo.platformplayer.exceptions + +class MigrationException(msg: String, inner: Throwable) : Throwable(msg, inner) { + +} diff --git a/app/src/main/java/com/futo/platformplayer/exceptions/ReconstructionException.kt b/app/src/main/java/com/futo/platformplayer/exceptions/ReconstructionException.kt new file mode 100644 index 00000000..a7f836a3 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/exceptions/ReconstructionException.kt @@ -0,0 +1,5 @@ +package com.futo.platformplayer.exceptions + +class ReconstructionException(val name: String? = null, message: String, innerException: Throwable): Exception(message, innerException) { + +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelAboutFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelAboutFragment.kt new file mode 100644 index 00000000..9e28d6f9 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelAboutFragment.kt @@ -0,0 +1,155 @@ +package com.futo.platformplayer.fragment.channel.tab + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.fragment.app.Fragment +import com.bumptech.glide.Glide +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.views.platform.PlatformLinkView +import com.futo.polycentric.core.toName +import com.futo.polycentric.core.toURLInfoSystemLinkUrl + +class ChannelAboutFragment : Fragment, IChannelTabFragment { + private var _textName: TextView? = null; + private var _textMetadata: TextView? = null; + private var _textFindOn: TextView? = null; + private var _textDescription: TextView? = null; + private var _imageThumbnail: ImageView? = null; + private var _linksContainer: LinearLayout? = null; + + private var _lastChannel: IPlatformChannel? = null; + private var _lastPolycentricProfile: PolycentricProfile? = null; + + constructor() : super() { + + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_channel_about, container, false); + + _textName = view.findViewById(R.id.text_channel_name); + _textMetadata = view.findViewById(R.id.text_channel_metadata); + _textDescription = view.findViewById(R.id.text_description); + _textDescription!!.setPlatformPlayerLinkMovementMethod(view.context); + _textFindOn = view.findViewById(R.id.text_find_on); + _imageThumbnail = view.findViewById(R.id.image_channel_thumbnail); + _linksContainer = view.findViewById(R.id.links_container); + _imageThumbnail?.clipToOutline = true; + _lastChannel?.also { + setChannel(it); + }; + _lastPolycentricProfile?.also { + setPolycentricProfile(it, animate = false); + } + + return view; + } + + override fun onDestroyView() { + super.onDestroyView(); + + _textName = null; + _textMetadata = null; + _textDescription = null; + _textFindOn = null; + _imageThumbnail = null; + _linksContainer = null; + } + + override fun setChannel(channel: IPlatformChannel) { + if(channel.description != null) + _textDescription?.text = channel.description!!.fixHtmlLinks(); + + _imageThumbnail?.let { + Glide.with(it) + .load(channel.thumbnail) + .placeholder(R.drawable.placeholder_channel_thumbnail) + .into(it); + }; + _textName?.text = channel.name; + + val metadata = "${channel.subscribers.toHumanNumber()} subscribers"; + _textMetadata?.text = metadata; + _lastChannel = channel; + setLinks(channel.links, channel.name); + } + + private fun setLinks(links: Map, name: String) { + val c = context; + val l = _linksContainer; + + if (c != null && l != null) { + l.removeAllViews(); + + if (links.isNotEmpty()) { + _textFindOn?.text = "Find $name on"; + _textFindOn?.visibility = View.VISIBLE; + + for (pair in links) { + val platformLinkView = PlatformLinkView(c); + platformLinkView.setPlatform(pair.key, pair.value); + l.addView(platformLinkView); + } + } else { + _textFindOn?.visibility = View.GONE; + } + } else { + _textFindOn?.visibility = View.GONE; + } + + } + + fun setPolycentricProfile(polycentricProfile: PolycentricProfile?, animate: Boolean) { + _lastPolycentricProfile = polycentricProfile; + + if (polycentricProfile == null) { + return; + } + + val map = hashMapOf(); + for (c in polycentricProfile.ownedClaims) { + try { + val url = c.claim.resolveChannelUrl(); + val name = c.claim.toName(); + if (url != null && name != null) { + map[name] = url; + } + } catch (e: Throwable) { + Logger.w(TAG, "Failed to parse claim=$c", e) + } + } + + if (map.isNotEmpty()) + setLinks(map, if (polycentricProfile.systemState.username.isNotBlank()) polycentricProfile.systemState.username else _lastChannel?.name ?: "") + + if(polycentricProfile.systemState.description.isNotBlank()) + _textDescription?.text = polycentricProfile.systemState.description.fixHtmlLinks(); + + if (polycentricProfile.systemState.username.isNotBlank()) + _textName?.text = polycentricProfile.systemState.username; + + val dp_80 = 80.dp(StateApp.instance.context.resources) + val avatar = polycentricProfile.systemState.avatar?.selectBestImage(dp_80 * dp_80)?.let { + it.toURLInfoSystemLinkUrl(polycentricProfile.system.toProto(), it.process, polycentricProfile.systemState.servers.toList()) + }; + + if (avatar != null && _imageThumbnail != null) + Glide.with(_imageThumbnail!!) + .load(avatar) + .into(_imageThumbnail!!); + } + + companion object { + val TAG = "AboutFragment"; + fun newInstance() = ChannelAboutFragment().apply { } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt new file mode 100644 index 00000000..be12e48f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt @@ -0,0 +1,344 @@ +package com.futo.platformplayer.fragment.channel.tab + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.R +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.UISlideOverlays +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.platforms.js.models.JSPager +import com.futo.platformplayer.api.media.structures.IAsyncPager +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.api.media.structures.IRefreshPager +import com.futo.platformplayer.api.media.structures.IReplacerPager +import com.futo.platformplayer.api.media.structures.MultiPager +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.engine.exceptions.PluginException +import com.futo.platformplayer.fragment.mainactivity.main.FeedView +import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile +import com.futo.platformplayer.states.StatePolycentric +import com.futo.platformplayer.views.FeedStyle +import com.futo.platformplayer.views.adapters.PreviewContentListAdapter +import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder +import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class ChannelContentsFragment : Fragment(), IChannelTabFragment { + private var _recyclerResults: RecyclerView? = null; + private var _llmVideo: LinearLayoutManager? = null; + private var _loading = false; + private var _pager_parent: IPager? = null; + private var _pager: IPager? = null; + private var _cache: FeedView.ItemCache? = null; + private var _channel: IPlatformChannel? = null; + private var _results: ArrayList = arrayListOf(); + private var _adapterResults: InsertedViewAdapterWithLoader? = null; + private var _lastPolycentricProfile: PolycentricProfile? = null; + + val onContentClicked = Event2(); + val onContentUrlClicked = Event2(); + val onChannelClicked = Event1(); + val onAddToClicked = Event1(); + + private fun getContentPager(channel: IPlatformChannel): IPager { + Logger.i(TAG, "getContentPager"); + + val lastPolycentricProfile = _lastPolycentricProfile; + var pager: IPager? = null; + if (lastPolycentricProfile != null) + pager= StatePolycentric.instance.getChannelContent(lifecycleScope, lastPolycentricProfile); + + if(pager == null) + pager = StatePlatform.instance.getChannelContent(channel.url); + + return pager; + } + + private val _taskLoadVideos = TaskHandler>({lifecycleScope}, { + return@TaskHandler getContentPager(it); + }).success { + setLoading(false); + setPager(it); + }.exception { + Logger.w(TAG, "Failed to load initial videos.", it); + UIDialogs.showGeneralRetryErrorDialog(requireContext(), it.message ?: "", it, { loadNextPage() }); + }; + + private var _nextPageHandler: TaskHandler, List> = TaskHandler, List>({lifecycleScope}, { + if (it is IAsyncPager<*>) + it.nextPageAsync(); + else + it.nextPage(); + + processPagerExceptions(it); + return@TaskHandler it.getResults(); + }).success { + setLoading(false); + if (it.isEmpty()) { + return@success; + } + + val posBefore = _results.size; + val toAdd = it.filter { it is IPlatformVideo }.map { it as IPlatformVideo }; + _results.addAll(toAdd); + _adapterResults?.let { adapterVideo -> adapterVideo.notifyItemRangeInserted(adapterVideo.childToParentPosition(posBefore), toAdd.size); }; + }.exception { + Logger.w(TAG, "Failed to load next page.", it); + UIDialogs.showGeneralRetryErrorDialog(requireContext(), it.message ?: "", it, { loadNextPage() }); + }; + + private val _scrollListener = object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy); + + val recyclerResults = _recyclerResults ?: return; + val llmVideo = _llmVideo ?: return; + + val visibleItemCount = recyclerResults.childCount; + val firstVisibleItem = llmVideo.findFirstVisibleItemPosition(); + val visibleThreshold = 15; + if (!_loading && firstVisibleItem + visibleItemCount + visibleThreshold >= _results.size) { + loadNextPage(); + } + } + }; + + override fun setChannel(channel: IPlatformChannel) { + val c = _channel; + if (c != null && c.url == channel.url) { + Logger.i(TAG, "setChannel skipped because previous was same"); + return; + } + + Logger.i(TAG, "setChannel setChannel=${channel}") + + _taskLoadVideos.cancel(); + + _channel = channel; + _results.clear(); + _adapterResults?.notifyDataSetChanged(); + + loadInitial(); + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_channel_videos, container, false); + + _recyclerResults = view.findViewById(R.id.recycler_videos); + + _adapterResults = PreviewContentListAdapter(view.context, FeedStyle.THUMBNAIL, _results).apply { + this.onContentUrlClicked.subscribe(this@ChannelContentsFragment.onContentUrlClicked::emit); + this.onContentClicked.subscribe(this@ChannelContentsFragment.onContentClicked::emit); + this.onChannelClicked.subscribe(this@ChannelContentsFragment.onChannelClicked::emit); + this.onAddToClicked.subscribe(this@ChannelContentsFragment.onAddToClicked::emit); + } + + _llmVideo = LinearLayoutManager(view.context); + _recyclerResults?.adapter = _adapterResults; + _recyclerResults?.layoutManager = _llmVideo; + _recyclerResults?.addOnScrollListener(_scrollListener); + + return view; + } + + override fun onDestroyView() { + super.onDestroyView(); + _recyclerResults?.removeOnScrollListener(_scrollListener); + _recyclerResults = null; + _pager = null; + + _taskLoadVideos.cancel(); + _nextPageHandler.cancel(); + } + + /* + private fun setPager(pager: IPager, cache: FeedFragment.ItemCache? = null) { + if (_pager_parent != null && _pager_parent is IRefreshPager<*>) { + (_pager_parent as IRefreshPager<*>).onPagerError?.remove(this); + (_pager_parent as IRefreshPager<*>).onPagerChanged?.remove(this); + _pager_parent = null; + } + + if(pager is IRefreshPager<*>) { + _pager_parent = pager; + _pager = pager.getCurrentPager() as IPager; + pager.onPagerChanged.subscribe { + lifecycleScope.launch(Dispatchers.Main) { + setPager(it as IPager); + } + }; + pager.onPagerError.subscribe { + Logger.e(TAG, "Search pager failed: ${it.message}", it); + if(it is PluginException) + UIDialogs.toast("Plugin [${it.config.name}] failed due to:\n${it.message}"); + else + UIDialogs.toast("Plugin failed due to:\n${it.message}"); + }; + } + else _pager = pager; + _cache = cache; + + processPagerExceptions(pager); + + _results.clear(); + val toAdd = pager.getResults(); + _results.addAll(toAdd); + _adapterResults?.notifyDataSetChanged(); + _recyclerResults?.scrollToPosition(0); + }*/ + + fun setPager(pager: IPager, cache: FeedView.ItemCache? = null) { + if (_pager_parent != null && _pager_parent is IRefreshPager<*>) { + (_pager_parent as IRefreshPager<*>).onPagerError?.remove(this); + (_pager_parent as IRefreshPager<*>).onPagerChanged?.remove(this); + _pager_parent = null; + } + if(_pager is IReplacerPager<*>) + (_pager as IReplacerPager<*>).onReplaced.remove(this); + + var pagerToSet: IPager? = null; + if(pager is IRefreshPager<*>) { + _pager_parent = pager; + pagerToSet = pager.getCurrentPager() as IPager; + pager.onPagerChanged.subscribe(this) { + + lifecycleScope.launch(Dispatchers.Main) { + try { + loadPagerInternal(it as IPager); + } catch (e: Throwable) { + Logger.e(TAG, "loadPagerInternal failed.", e) + } + } + }; + pager.onPagerError.subscribe(this) { + Logger.e(TAG, "Search pager failed: ${it.message}", it); + if(it is PluginException) + UIDialogs.toast("Plugin [${it.config.name}] failed due to:\n${it.message}"); + else + UIDialogs.toast("Plugin failed due to:\n${it.message}"); + }; + } + else pagerToSet = pager; + + loadPagerInternal(pagerToSet, cache); + } + private fun loadPagerInternal(pager: IPager, cache: FeedView.ItemCache? = null) { + _cache = cache; + + if(_pager is IReplacerPager<*>) + (_pager as IReplacerPager<*>).onReplaced.remove(this); + + if(pager is IReplacerPager<*>) { + pager.onReplaced.subscribe(this) { oldItem, newItem -> + if(_pager != pager) + return@subscribe; + + if(_pager !is IPager) + return@subscribe; + + val toReplaceIndex = _results.indexOfFirst { it == newItem }; + if(toReplaceIndex >= 0) { + _results[toReplaceIndex] = newItem as IPlatformContent; + _adapterResults?.let { + it.notifyItemChanged(it.childToParentPosition(toReplaceIndex)); + } + } + } + } + + _pager = pager; + + processPagerExceptions(pager); + + _results.clear(); + val toAdd = pager.getResults(); + _results.addAll(toAdd); + //insertPagerResults(toAdd, true); + _adapterResults?.notifyDataSetChanged(); + _recyclerResults?.scrollToPosition(0); + } + + private fun loadInitial() { + val channel: IPlatformChannel = _channel ?: return; + setLoading(true); + _taskLoadVideos.run(channel); + } + + private fun loadNextPage() { + val pager: IPager = _pager ?: return; + if(_pager?.hasMorePages() ?: false) { + setLoading(true); + _nextPageHandler.run(pager); + } + } + + private fun setLoading(loading: Boolean) { + _loading = loading; + _adapterResults?.setLoading(loading); + } + + fun setPolycentricProfile(polycentricProfile: PolycentricProfile?, animate: Boolean) { + val p = _lastPolycentricProfile; + if (p != null && polycentricProfile != null && p.system == polycentricProfile.system) { + Logger.i(TAG, "setPolycentricProfile skipped because previous was same"); + return; + } + + _lastPolycentricProfile = polycentricProfile; + + if (polycentricProfile != null) { + _taskLoadVideos.cancel(); + val itemsRemoved = _results.size; + _results.clear(); + _adapterResults?.notifyItemRangeRemoved(0, itemsRemoved); + loadInitial(); + } + } + + private fun processPagerExceptions(pager: IPager<*>) { + if(pager is MultiPager<*> && pager.allowFailure) { + val ex = pager.getResultExceptions(); + for(kv in ex) { + val jsVideoPager: JSPager<*>? = if(kv.key is MultiPager<*>) + (kv.key as MultiPager<*>).findPager { it is JSPager<*> } as JSPager<*>?; + else if(kv.key is JSPager<*>) + kv.key as JSPager<*>; + else null; + + context?.let { + lifecycleScope.launch(Dispatchers.Main) { + try { + if(jsVideoPager != null) + UIDialogs.toast(it, "Plugin ${jsVideoPager.getPluginConfig().name} failed:\n${kv.value.message}", false); + else + UIDialogs.toast(it, kv.value.message ?: "", false); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to show toast.", e) + } + } + } + } + } + } + + companion object { + val TAG = "VideoListFragment"; + fun newInstance() = ChannelContentsFragment().apply { } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelListFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelListFragment.kt new file mode 100644 index 00000000..e3cf3061 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelListFragment.kt @@ -0,0 +1,149 @@ +package com.futo.platformplayer.fragment.channel.tab + +import android.os.Bundle +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.fragment.mainactivity.main.ChannelFragment +import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader +import com.futo.platformplayer.views.adapters.viewholders.CreatorViewHolder +import com.futo.polycentric.core.toUrl +import kotlinx.coroutines.runBlocking + +class ChannelListFragment : Fragment, IChannelTabFragment { + private var _channels: ArrayList = arrayListOf(); + private var _authorLinks: ArrayList = arrayListOf(); + private var _currentLoadIndex = 0; + private var _adapterCreator: InsertedViewAdapterWithLoader? = null; + private var _recyclerCreator: RecyclerView? = null; + private var _lm: GridLayoutManager? = null; + private var _lastPolycentricProfile: PolycentricProfile? = null; + private var _lastChannel : IPlatformChannel? = null; + + val onClickChannel = Event1(); + + private var _taskLoadChannel = TaskHandler({lifecycleScope}, { link -> + if (!StatePlatform.instance.hasEnabledChannelClient(link)) { + return@TaskHandler null; + } + + return@TaskHandler StatePlatform.instance.getChannel(link).await(); + }).success { + val adapter = _adapterCreator; + if (it == null || adapter == null || _channels.any { c -> c.url == it.url }) { + loadNext(); + return@success; + } + + _channels.add(it); + _authorLinks.add(PlatformAuthorLink(it.id, it.name, it.url, it.thumbnail)); + adapter.notifyItemInserted(adapter.childToParentPosition(_authorLinks.size - 1)); + loadNext(); + }.exceptionWithParameter { ex, para -> + Logger.w(ChannelFragment.TAG, "Failed to load results.", ex); + UIDialogs.toast(requireContext(), "Failed to fetch\n${para}", false) + loadNext(); + }; + + constructor() : super() { + + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_channel_list, container, false); + + val recyclerCreator: RecyclerView = view.findViewById(R.id.recycler_creators); + _adapterCreator = InsertedViewAdapterWithLoader(view.context, arrayListOf(), arrayListOf(), + childCountGetter = { _authorLinks.size }, + childViewHolderBinder = { viewHolder, position -> viewHolder.bind(_authorLinks[position]); }, + childViewHolderFactory = { viewGroup, _ -> + val holder = CreatorViewHolder(viewGroup, true); + holder.onClick.subscribe { c -> onClickChannel.emit(c) }; + return@InsertedViewAdapterWithLoader holder; + } + ); + + recyclerCreator.adapter = _adapterCreator; + + _lm = GridLayoutManager(view.context, 2); + recyclerCreator.layoutManager = _lm; + _recyclerCreator = recyclerCreator; + _lastChannel?.also { setChannel(it); }; + _lastPolycentricProfile?.also { setPolycentricProfile(it, animate = false); } + + return view; + } + + override fun onDestroyView() { + super.onDestroyView(); + } + + override fun setChannel(channel: IPlatformChannel) { + _lastChannel = channel; + } + + private fun load() { + val profile = _lastPolycentricProfile ?: return; + setLoading(true); + + val url = profile.ownedClaims[_currentLoadIndex].claim.resolveChannelUrl(); + if (url == null) { + loadNext(); + return; + } + + _taskLoadChannel.run(url); + } + + private fun loadNext() { + val profile = _lastPolycentricProfile; + if (profile == null) { + setLoading(false); + return; + } + + _currentLoadIndex++; + if (_currentLoadIndex < profile.ownedClaims.size) { + load(); + } else { + setLoading(false); + } + } + + fun setPolycentricProfile(polycentricProfile: PolycentricProfile?, animate: Boolean) { + _taskLoadChannel.cancel(); + _lastPolycentricProfile = polycentricProfile; + + val adapter = _adapterCreator ?: return; + _channels.clear(); + _authorLinks.clear(); + adapter.notifyDataSetChanged(); + + if (polycentricProfile != null) { + _currentLoadIndex = 0; + load(); + } + } + + private fun setLoading(isLoading: Boolean) { + _adapterCreator?.setLoading(isLoading); + } + + companion object { + val TAG = "ChannelListFragment"; + fun newInstance() = ChannelListFragment().apply { } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelMonetizationFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelMonetizationFragment.kt new file mode 100644 index 00000000..83a37e9a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelMonetizationFragment.kt @@ -0,0 +1,79 @@ +package com.futo.platformplayer.fragment.channel.tab + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.views.buttons.BigButton + + +class ChannelMonetizationFragment : Fragment, IChannelTabFragment { + private var _buttonStore: BigButton? = null; + + private var _lastChannel: IPlatformChannel? = null; + private var _lastPolycentricProfile: PolycentricProfile? = null; + + constructor() : super() { } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_channel_monetization, container, false); + _buttonStore = view.findViewById(R.id.button_store); + + _buttonStore?.onClick?.subscribe { + _lastPolycentricProfile?.systemState?.store?.let { + try { + val uri = Uri.parse(it); + val intent = Intent(Intent.ACTION_VIEW) + intent.data = uri + startActivity(intent) + } catch (e: Throwable) { + Logger.e(TAG, "Failed to open URI: '${it}'.", e); + } + } + }; + + _lastChannel?.also { + setChannel(it); + }; + + _lastPolycentricProfile?.also { + setPolycentricProfile(it, animate = false); + } + + return view; + } + + override fun onDestroyView() { + super.onDestroyView(); + _buttonStore = null; + } + + override fun setChannel(channel: IPlatformChannel) { + _lastChannel = channel; + _buttonStore?.visibility = View.GONE; + } + + fun setPolycentricProfile(polycentricProfile: PolycentricProfile?, animate: Boolean) { + _lastPolycentricProfile = polycentricProfile; + + if (polycentricProfile == null) { + return; + } + + if (polycentricProfile.systemState.store.isNotEmpty()) { + _buttonStore?.visibility = View.VISIBLE; + } + } + + companion object { + val TAG = "ChannelMonetizationFragment"; + fun newInstance() = ChannelMonetizationFragment().apply { } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelStoreFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelStoreFragment.kt new file mode 100644 index 00000000..4651c806 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelStoreFragment.kt @@ -0,0 +1,33 @@ +package com.futo.platformplayer.fragment.channel.tab + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel + +class ChannelStoreFragment : Fragment, IChannelTabFragment { + constructor() : super() { + + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_channel_store, container, false); + return view; + } + + override fun onDestroyView() { + super.onDestroyView(); + } + + override fun setChannel(channel: IPlatformChannel) { + + } + + companion object { + val TAG = "StoreListFragment"; + fun newInstance() = ChannelStoreFragment().apply { } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/IChannelTabFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/IChannelTabFragment.kt new file mode 100644 index 00000000..e1da77c5 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/IChannelTabFragment.kt @@ -0,0 +1,7 @@ +package com.futo.platformplayer.fragment.channel.tab + +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel + +interface IChannelTabFragment { + fun setChannel(channel: IPlatformChannel); +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/MainActivityFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/MainActivityFragment.kt new file mode 100644 index 00000000..6244004b --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/MainActivityFragment.kt @@ -0,0 +1,52 @@ +package com.futo.platformplayer.fragment.mainactivity + +import android.util.Log +import androidx.fragment.app.Fragment +import com.futo.platformplayer.activities.MainActivity +import com.futo.platformplayer.fragment.mainactivity.main.MainFragment + +open class MainActivityFragment : Fragment() { + protected val currentMain : MainFragment + get() { + isValidMainActivity(); + return (activity as MainActivity).fragCurrent; + } + + fun closeSegment() { + val a = activity + if (a is MainActivity) + return a.closeSegment() + else + Log.e(TAG, "Failed to close segment due to activity not being a main activity.") + } + + fun navigate(frag: MainFragment, parameter: Any? = null, withHistory: Boolean = true) { + val a = activity + if (a is MainActivity) + (activity as MainActivity).navigate(frag, parameter, withHistory) + else + Log.e(TAG, "Failed to navigate due to activity not being a main activity.") + } + + inline fun navigate(parameter: Any? = null, withHistory: Boolean = true): T { + val target = requireFragment(); + navigate(target, parameter, withHistory); + return target; + } + + inline fun requireFragment() : T { + isValidMainActivity(); + return (activity as MainActivity).getFragment(); + } + + fun isValidMainActivity(){ + if(activity == null) + throw java.lang.IllegalStateException("Attempted to use fragment without an activity"); + if(!(activity is MainActivity)) + throw java.lang.IllegalStateException("Attempted to use fragment without a MainActivty"); + } + + companion object { + private const val TAG = "MainActivityFragment" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/BotFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/BotFragment.kt new file mode 100644 index 00000000..199758d5 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/BotFragment.kt @@ -0,0 +1,7 @@ +package com.futo.platformplayer.fragment.mainactivity.bottombar + +import com.futo.platformplayer.fragment.mainactivity.MainActivityFragment + +class BotFragment : MainActivityFragment() { + +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt new file mode 100644 index 00000000..42b9569a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt @@ -0,0 +1,367 @@ +package com.futo.platformplayer.fragment.mainactivity.bottombar + +import android.animation.Animator +import android.animation.AnimatorSet +import android.animation.ObjectAnimator +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.graphics.Color +import android.os.Bundle +import android.view.Gravity +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.* +import androidx.core.animation.doOnEnd +import androidx.lifecycle.lifecycleScope +import com.futo.platformplayer.R +import com.futo.platformplayer.Settings +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.activities.MainActivity +import com.futo.platformplayer.activities.SettingsActivity +import com.futo.platformplayer.dp +import com.futo.platformplayer.fragment.mainactivity.MainActivityFragment +import com.futo.platformplayer.fragment.mainactivity.main.* +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StatePayment +import com.futo.platformplayer.states.StateSubscriptions +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.util.Collections +import kotlin.math.floor +import kotlin.math.roundToInt + +class MenuBottomBarFragment : MainActivityFragment() { + private var _view: MenuBottomBarView? = null; + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = MenuBottomBarView(this, inflater); + _view = view; + return view; + } + + override fun onResume() { + super.onResume() + _view?.updateAllButtonVisibility() + } + + override fun onDestroyView() { + super.onDestroyView(); + + _view?.cleanup(); + _view = null; + } + + fun onBackPressed() : Boolean { + return _view?.onBackPressed() ?: false; + } + + @SuppressLint("ViewConstructor") + class MenuBottomBarView : LinearLayout { + private val _fragment: MenuBottomBarFragment; + private val _inflater: LayoutInflater; + private val _subscribedActivity: MainActivity?; + + private var _overlayMore: FrameLayout; + private var _overlayMoreBackground: FrameLayout; + private var _layoutMoreButtons: LinearLayout; + private var _layoutBottomBarButtons: LinearLayout; + + private var _moreVisible = false; + private var _moreVisibleAnimating = false; + + private var _bottomButtons = arrayListOf(); + private var _moreButtons = arrayListOf(); + + private var _buttonsVisible = 0; + private var _subscriptionsVisible = false; + + var currentButtonDefinitions: List? = null; + + constructor(fragment: MenuBottomBarFragment, inflater: LayoutInflater) : super(inflater.context) { + _fragment = fragment; + _inflater = inflater; + inflater.inflate(R.layout.fragment_overview_bottom_bar, this); + + _overlayMore = findViewById(R.id.more_overlay); + _overlayMoreBackground = findViewById(R.id.more_overlay_background); + _layoutMoreButtons = findViewById(R.id.more_menu_buttons); + _layoutBottomBarButtons = findViewById(R.id.bottom_bar_buttons) + + _overlayMoreBackground.setOnClickListener { setMoreVisible(false); }; + + _subscribedActivity = fragment.activity as MainActivity? + _subscribedActivity?.onNavigated?.subscribe(this) { + updateMenuIcons(); + } + + registerUpdateButtonEvents(); + updateButtonDefinitions(); + } + + fun cleanup() { + _subscribedActivity?.onNavigated?.remove(this) + unregisterUpdateButtonEvents(); + } + + fun onBackPressed() : Boolean { + if(_moreVisible) { + setMoreVisible(false); + return true; + } + return false; + } + + private fun setMoreVisible(visible: Boolean) { + if (_moreVisibleAnimating) { + return + } + + if (_moreVisible == visible) { + return + } + + val height = _moreButtons.firstOrNull()?.let { + it.height.toFloat() + (it.layoutParams as MarginLayoutParams).bottomMargin + } ?: return + + _moreVisibleAnimating = true + val moreOverlayBackground = _overlayMoreBackground + val moreOverlay = _overlayMore + val duration: Long = 300 + val staggerFactor = 3.0f + + if (visible) { + moreOverlay.visibility = LinearLayout.VISIBLE + val animations = arrayListOf() + animations.add(ObjectAnimator.ofFloat(moreOverlayBackground, "alpha", 0.0f, 1.0f).setDuration(duration)) + + for ((index, button) in _moreButtons.withIndex()) { + val i = _moreButtons.size - index + animations.add(ObjectAnimator.ofFloat(button, "translationY", height * staggerFactor * (i + 1), 0.0f).setDuration(duration)) + } + + val animatorSet = AnimatorSet() + animatorSet.doOnEnd { + _moreVisibleAnimating = false + _moreVisible = true + } + animatorSet.playTogether(animations) + animatorSet.start() + } else { + val animations = arrayListOf() + animations.add(ObjectAnimator.ofFloat(moreOverlayBackground, "alpha", 1.0f, 0.0f).setDuration(duration)) + + for ((index, button) in _moreButtons.withIndex()) { + val i = _moreButtons.size - index + animations.add(ObjectAnimator.ofFloat(button, "translationY", 0.0f, height * staggerFactor * (i + 1)).setDuration(duration)) + } + + val animatorSet = AnimatorSet() + animatorSet.doOnEnd { + _moreVisibleAnimating = false + _moreVisible = false + moreOverlay.visibility = LinearLayout.INVISIBLE + } + animatorSet.playTogether(animations) + animatorSet.start() + } + } + + private fun updateBottomMenuButtons(buttons: MutableList, hasMore: Boolean) { + if (hasMore) { + buttons.add(ButtonDefinition(99, R.drawable.ic_more, R.drawable.ic_more, R.string.more, canToggle = false, { false }, { setMoreVisible(true) })) + } + + _bottomButtons.clear(); + //_bottomButtonImages.clear(); + _layoutBottomBarButtons.removeAllViews(); + + _layoutBottomBarButtons.addView(Space(context).apply { + layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f) + }) + + for ((index, button) in buttons.withIndex()) { + val menuButton = MenuButton(context, button, _fragment, false); + menuButton.setOnClickListener { + updateMenuIcons() + button.action(_fragment) + setMoreVisible(false); + } + + _layoutBottomBarButtons.addView(menuButton) + if (index < buttonDefinitions.size - 1) { + _layoutBottomBarButtons.addView(Space(context).apply { + layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f) + }) + } + + _bottomButtons.add(menuButton) + } + + _layoutBottomBarButtons.addView(Space(context).apply { + layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f) + }) + } + + private fun updateMoreButtons(buttons: MutableList) { + //_moreButtonImages.clear(); + _moreButtons.clear(); + _layoutMoreButtons.removeAllViews(); + + //Force buy to be on top for more buttons + val buyIndex = buttons.indexOfFirst { b -> b.id == 98 }; + if (buyIndex != -1) { + val button = buttons[buyIndex] + buttons.removeAt(buyIndex) + buttons.add(0, button) + } + + for (data in buttons) { + val button = MenuButton(context, data, _fragment, true); + button.setOnClickListener { + updateMenuIcons() + data.action(_fragment) + setMoreVisible(false); + }; + + _moreButtons.add(button); + _layoutMoreButtons.addView(button); + } + } + + private fun updateMenuIcons() { + for(button in _bottomButtons.toList()) + button.updateActive(_fragment); + for(button in _moreButtons.toList()) + button.updateActive(_fragment); + } + + fun updateAllButtonVisibility() { + val defs = currentButtonDefinitions?.toMutableList() ?: return + val metrics = StateApp.instance.displayMetrics ?: resources.displayMetrics; + _buttonsVisible = floor(metrics.widthPixels.toDouble() / 65.dp(resources).toDouble()).roundToInt(); + if (_buttonsVisible - 2 >= defs.size) { + updateBottomMenuButtons(defs.slice(IntRange(0, defs.size - 1)).toMutableList(), false); + } else { + updateBottomMenuButtons(defs.slice(IntRange(0, _buttonsVisible - 2)).toMutableList(), true); + updateMoreButtons(defs.slice(IntRange(_buttonsVisible - 1, defs.size - 1)).toMutableList()); + } + } + + private fun registerUpdateButtonEvents() { + _subscriptionsVisible = StateSubscriptions.instance.getSubscriptionCount() > 0; + StateSubscriptions.instance.onSubscriptionsChanged.subscribe(this) { subs, _ -> + _subscriptionsVisible = subs.isNotEmpty(); + updateButtonDefinitions() + } + + StatePayment.instance.hasPaidChanged.subscribe(this) { + _fragment.lifecycleScope.launch(Dispatchers.Main) { + updateButtonDefinitions() + } + }; + + Settings.instance.onTabsChanged.subscribe(this) { + updateButtonDefinitions() + } + } + + private fun unregisterUpdateButtonEvents() { + StateSubscriptions.instance.onSubscriptionsChanged.remove(this); + Settings.instance.onTabsChanged.remove(this) + StatePayment.instance.hasPaidChanged.remove(this) + } + + private fun updateButtonDefinitions() { + val newCurrentButtonDefinitions = Settings.instance.tabs.filter { it.enabled }.mapNotNull { + if (it.id == 1 && !_subscriptionsVisible) { + return@mapNotNull null + } + + buttonDefinitions.find { d -> d.id == it.id } + }.toMutableList() + + if (!StatePayment.instance.hasPaid) { + newCurrentButtonDefinitions.add(ButtonDefinition(98, R.drawable.ic_paid, R.drawable.ic_paid, R.string.buy, canToggle = false, { it.currentMain is BuyFragment }, { it.navigate() })) + } + + //Add conditional buttons here, when you add a conditional button, be sure to add the register and unregister events for when the button needs to be updated + + currentButtonDefinitions = newCurrentButtonDefinitions + updateAllButtonVisibility() + } + + + class MenuButton: LinearLayout { + val definition: ButtonDefinition; + + private val _buttonImage: ImageView; + private val _textButton: TextView; + + constructor(context: Context, def: ButtonDefinition, fragment: MenuBottomBarFragment, isMore: Boolean): super(context) { + inflate(context, if(isMore) R.layout.view_bottom_more_menu_button else R.layout.view_bottom_menu_button, this); + layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) + + this.definition = def; + + _buttonImage = findViewById(R.id.image_button); + _buttonImage.setImageResource(if (def.isActive(fragment)) def.iconActive else def.icon); + + _textButton = findViewById(R.id.text_button); + _textButton.text = resources.getString(def.string); + + val root = findViewById(R.id.root); + root.setOnClickListener { + this.performClick(); + } + } + + fun updateActive(fragment: MenuBottomBarFragment) { + _buttonImage.setImageResource(if (definition.isActive(fragment)) definition.iconActive else definition.icon); + } + } + } + + companion object { + private const val TAG = "MenuBottomBarFragment"; + + fun newInstance() = MenuBottomBarFragment().apply { } + + //Add configurable buttons here + var buttonDefinitions = listOf( + ButtonDefinition(0, R.drawable.ic_home, R.drawable.ic_home_filled, R.string.home, canToggle = true, { it.currentMain is HomeFragment }, { it.navigate() }), + ButtonDefinition(1, R.drawable.ic_subscriptions, R.drawable.ic_subscriptions_filled, R.string.subscriptions, canToggle = true, { it.currentMain is SubscriptionsFeedFragment }, { it.navigate() }), + ButtonDefinition(2, R.drawable.ic_creators, R.drawable.ic_creators_filled, R.string.creators, canToggle = false, { it.currentMain is CreatorsFragment }, { it.navigate() }), + ButtonDefinition(3, R.drawable.ic_sources, R.drawable.ic_sources_filled, R.string.sources, canToggle = false, { it.currentMain is SourcesFragment }, { it.navigate() }), + ButtonDefinition(4, R.drawable.ic_playlist, R.drawable.ic_playlist_filled, R.string.playlists, canToggle = false, { it.currentMain is PlaylistsFragment }, { it.navigate() }), + ButtonDefinition(5, R.drawable.ic_history, R.drawable.ic_history, R.string.history, canToggle = false, { it.currentMain is HistoryFragment }, { it.navigate() }), + ButtonDefinition(6, R.drawable.ic_download, R.drawable.ic_download, R.string.downloads, canToggle = false, { it.currentMain is DownloadsFragment }, { it.navigate() }), + ButtonDefinition(7, R.drawable.ic_settings, R.drawable.ic_settings, R.string.settings, canToggle = false, { false }, { + val c = it.context ?: return@ButtonDefinition; + Logger.i(TAG, "settings preventPictureInPicture()"); + it.requireFragment().preventPictureInPicture(); + val intent = Intent(c, SettingsActivity::class.java); + c.startActivity(intent); + if (c is Activity) { + c.overridePendingTransition(R.anim.slide_in_up, R.anim.slide_darken); + } + }) + //98 is reversed for buy button + //99 is reserved for more button + ); + } + + data class ButtonDefinition( + val id: Int, + val icon: Int, + val iconActive: Int, + val string: Int, + val canToggle: Boolean, + val isActive: (fragment: MenuBottomBarFragment) -> Boolean, + val action: (fragment: MenuBottomBarFragment) -> Unit); +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/BrowserFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/BrowserFragment.kt new file mode 100644 index 00000000..805cabd4 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/BrowserFragment.kt @@ -0,0 +1,62 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.webkit.CookieManager +import android.webkit.WebResourceRequest +import android.webkit.WebView +import android.webkit.WebViewClient +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.Spinner +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.R +import com.futo.platformplayer.views.adapters.SubscriptionAdapter + +class BrowserFragment : MainFragment() { + override val isMainView : Boolean = true; + override val isTab: Boolean = false; + override val hasBottomBar: Boolean get() = true; + + private var _webview: WebView? = null; + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = inflater.inflate(R.layout.fragment_browser, container, false); + _webview = view.findViewById(R.id.webview).apply { + this.webViewClient = object: WebViewClient() { + override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { + return false; + } + }; + this.settings.javaScriptEnabled = true; + CookieManager.getInstance().setAcceptCookie(true); + this.settings.domStorageEnabled = true; + }; + return view; + } + + override fun onShownWithView(parameter: Any?, isBack: Boolean) { + super.onShownWithView(parameter, isBack) + + if(parameter is String) + _webview?.loadUrl(parameter); + else + _webview?.loadUrl("about:blank"); + } + + override fun onHide() { + super.onHide() + _webview?.loadUrl("about:blank"); + } + + override fun onBackPressed(): Boolean { + return false; + } + + companion object { + fun newInstance() = BrowserFragment().apply {} + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/BuyFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/BuyFragment.kt new file mode 100644 index 00000000..5eecc4a5 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/BuyFragment.kt @@ -0,0 +1,153 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.TextView +import androidx.lifecycle.lifecycleScope +import com.futo.futopay.PaymentConfigurations +import com.futo.futopay.PaymentManager +import com.futo.platformplayer.R +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StatePayment +import com.futo.platformplayer.views.overlays.LoaderOverlay +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuTextInput +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class BuyFragment : MainFragment() { + override val isMainView : Boolean = true; + override val isTab: Boolean = false; + override val hasBottomBar: Boolean get() = true; + + private var _view: BuyView? = null; + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = BuyView(this, inflater); + _view = view; + return view; + } + + class BuyView: LinearLayout { + private val _fragment: BuyFragment; + + private val _buttonBuy: LinearLayout; + private val _buttonBuyText: TextView; + private val _buttonPaid: LinearLayout; + + private val _overlayPaying: FrameLayout; + + private val _paymentManager: PaymentManager; + + private val _overlayLoading: LoaderOverlay; + + constructor(fragment: BuyFragment, inflater: LayoutInflater) : super(inflater.context) { + _fragment = fragment; + inflater.inflate(R.layout.fragment_buy, this); + + _buttonBuy = findViewById(R.id.button_buy); + _buttonBuyText = findViewById(R.id.button_buy_text); + _buttonPaid = findViewById(R.id.button_paid); + _overlayLoading = findViewById(R.id.overlay_loading); + _overlayPaying = findViewById(R.id.overlay_paying); + + _paymentManager = PaymentManager(StatePayment.instance, fragment, _overlayPaying) { success, purchaseId, exception -> + if(success) { + UIDialogs.showDialog(context, R.drawable.ic_check, "Payment succeeded", "Thanks for your purchase, a key will be sent to your email after your payment has been received!", null, 0, + UIDialogs.Action("Ok", {}, UIDialogs.ActionStyle.PRIMARY)); + _fragment.close(true); + } + else { + UIDialogs.showGeneralErrorDialog(context, "Payment failed", exception); + } + } + + _buttonBuy.setOnClickListener { + buy(); + } + _buttonPaid.setOnClickListener { + paid(); + } + + fragment.lifecycleScope.launch(Dispatchers.IO) { + //Calling this function will cache first call + try { + val currencies = StatePayment.instance.getAvailableCurrencies("grayjay"); + val prices = StatePayment.instance.getAvailableCurrencyPrices("grayjay"); + val country = StatePayment.instance.getPaymentCountryFromIP()?.let { c -> PaymentConfigurations.COUNTRIES.find { it.id.equals(c, ignoreCase = true) } }; + val currency = country?.let { c -> PaymentConfigurations.CURRENCIES.find { it.id == c.defaultCurrencyId && (currencies.contains(it.id) ?: true) } }; + + if(currency != null && prices.containsKey(currency.id)) { + val price = prices[currency.id]!!; + val priceDecimal = (price.toDouble() / 100); + withContext(Dispatchers.Main) { + _buttonBuyText.text = currency.symbol + String.format("%.2f", priceDecimal); + } + } + } + catch(ex: Throwable) { + Logger.e("BuyFragment", "Failed to prefetch payment info", ex); + } + } + } + + + private fun buy() { + _paymentManager.startPayment(StatePayment.instance, _fragment.lifecycleScope, "grayjay"); + } + + private fun paid() { + val licenseInput = SlideUpMenuTextInput(context, "License"); + val productLicenseDialog = SlideUpMenuOverlay(context, findViewById(R.id.overlay_paid), "Enter license key", "Ok", true, licenseInput); + productLicenseDialog.onOK.subscribe { + val licenseText = licenseInput.text; + if (licenseText.isNullOrEmpty()) { + UIDialogs.showDialogOk(context, R.drawable.ic_error_pred, "Invalid license key"); + return@subscribe; + } + + _fragment.lifecycleScope.launch(Dispatchers.IO) { + + try{ + val activationResult = StatePayment.instance.setPaymentLicense(licenseText); + + withContext(Dispatchers.Main) { + if(activationResult) { + licenseInput.deactivate(); + licenseInput.clear(); + productLicenseDialog.hide(true); + + UIDialogs.showDialogOk(context, R.drawable.ic_check, "Your license key has been set!\nAn app restart might be required."); + _fragment.close(true); + } + else + { + UIDialogs.showDialogOk(context, R.drawable.ic_error_pred, "Invalid license key"); + } + } + } + catch(ex: Throwable) { + Logger.e("BuyFragment", "Failed to activate key", ex); + withContext(Dispatchers.Main) { + UIDialogs.showGeneralErrorDialog(context, "Failed to activate key", ex); + } + } + } + }; + productLicenseDialog.show(); + } + + } + + + + companion object { + fun newInstance() = BuyFragment().apply {} + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt new file mode 100644 index 00000000..c38eb798 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt @@ -0,0 +1,425 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.annotation.SuppressLint +import android.graphics.drawable.Animatable +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.appcompat.widget.AppCompatImageView +import androidx.lifecycle.lifecycleScope +import androidx.viewpager2.widget.ViewPager2 +import com.bumptech.glide.Glide +import com.futo.platformplayer.* +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.api.media.models.channels.SerializedChannel +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist +import com.futo.platformplayer.api.media.models.post.IPlatformPost +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.fragment.channel.tab.ChannelAboutFragment +import com.futo.platformplayer.fragment.channel.tab.ChannelListFragment +import com.futo.platformplayer.fragment.channel.tab.ChannelMonetizationFragment +import com.futo.platformplayer.fragment.channel.tab.ChannelContentsFragment +import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFragment +import com.futo.platformplayer.images.GlideHelper.Companion.crossfade +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.SearchType +import com.futo.platformplayer.models.Subscription +import com.futo.platformplayer.polycentric.PolycentricCache +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StatePlaylists +import com.futo.platformplayer.views.others.CreatorThumbnail +import com.futo.platformplayer.views.subscriptions.SubscribeButton +import com.futo.platformplayer.views.adapters.ChannelViewPagerAdapter +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay +import com.futo.polycentric.core.* +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.serialization.Serializable + +@Serializable +data class PolycentricProfile(val system: PublicKey, val systemState: SystemState, val ownedClaims: List); + +class ChannelFragment : MainFragment() { + override val isMainView : Boolean = true; + override val hasBottomBar: Boolean = true; + private var _view: ChannelView? = null; + + override fun onShownWithView(parameter: Any?, isBack: Boolean) { + super.onShownWithView(parameter, isBack); + _view?.onShown(parameter, isBack); + } + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = ChannelView(this, inflater); + _view = view; + return view; + } + + override fun onBackPressed(): Boolean { + return _view?.onBackPressed() ?: false; + } + + override fun onDestroyMainView() { + super.onDestroyMainView(); + + _view?.cleanup(); + _view = null; + } + + fun selectTab(selectedTabIndex: Int) { + _view?.selectTab(selectedTabIndex); + } + + @SuppressLint("ViewConstructor") + class ChannelView : LinearLayout { + private val _fragment: ChannelFragment; + + private var _textChannel: TextView; + private var _textChannelSub: TextView; + private var _creatorThumbnail: CreatorThumbnail; + private var _imageBanner: AppCompatImageView; + + private var _tabs: TabLayout; + private var _viewPager: ViewPager2; + private var _tabLayoutMediator: TabLayoutMediator; + private var _buttonSubscribe: SubscribeButton; + + private var _overlayContainer: FrameLayout; + private var _overlay_loading: LinearLayout; + private var _overlay_loading_spinner: ImageView; + + private var _slideUpOverlay: SlideUpMenuOverlay? = null; + + private var _isLoading: Boolean = false; + private var _selectedTabIndex: Int = -1; + var channel: IPlatformChannel? = null + private set; + private var _url: String? = null; + + private val _onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() { + override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { + super.onPageScrolled(position, positionOffset, positionOffsetPixels); + //recalculate(position, positionOffset); + } + } + + private val _taskLoadPolycentricProfile: TaskHandler; + private val _taskGetChannel: TaskHandler; + + constructor(fragment: ChannelFragment, inflater: LayoutInflater) : super(inflater.context) { + _fragment = fragment; + inflater.inflate(R.layout.fragment_channel, this); + + _taskLoadPolycentricProfile = TaskHandler({fragment.lifecycleScope}, { id -> + return@TaskHandler PolycentricCache.instance.getProfileAsync(id); + }) + .success { it -> setPolycentricProfile(it, animate = true) } + .exception { + Logger.w(TAG, "Failed to load polycentric profile.", it); + }; + + _taskGetChannel = TaskHandler({fragment.lifecycleScope}, { url -> StatePlatform.instance.getChannelLive(url) }) + .success { showChannel(it); } + .exception { + + UIDialogs.showDialog(context, + R.drawable.ic_sources, + "No source enabled to support this channel\n(${_url})", null, null, + 0, + UIDialogs.Action("Back", { + fragment.close(true); + }, UIDialogs.ActionStyle.PRIMARY) + ); + } + .exception { + Logger.e(TAG, "Failed to load channel.", it); + UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadChannel() }); + } + + val tabs: TabLayout = findViewById(R.id.tabs); + val viewPager: ViewPager2 = findViewById(R.id.view_pager); + _textChannel = findViewById(R.id.text_channel_name); + _textChannelSub = findViewById(R.id.text_metadata); + _creatorThumbnail = findViewById(R.id.creator_thumbnail); + _imageBanner = findViewById(R.id.image_channel_banner); + _buttonSubscribe = findViewById(R.id.button_subscribe); + _overlay_loading = findViewById(R.id.channel_loading_overlay); + _overlay_loading_spinner = findViewById(R.id.channel_loader); + _overlayContainer = findViewById(R.id.overlay_container); + + //TODO: Determine if this is really the only solution (isSaveEnabled=false) + viewPager.isSaveEnabled = false; + viewPager.registerOnPageChangeCallback(_onPageChangeCallback); + val adapter = ChannelViewPagerAdapter(fragment.childFragmentManager, fragment.lifecycle); + adapter.onChannelClicked.subscribe { c -> fragment.navigate(c) } + adapter.onContentClicked.subscribe { v, _ -> + if(v is IPlatformVideo) { + fragment.navigate(v).maximizeVideoDetail(); + } else if (v is IPlatformPlaylist) { + fragment.navigate(v); + } else if (v is IPlatformPost) { + fragment.navigate(v); + } + } + adapter.onAddToClicked.subscribe {content -> + _overlayContainer.let { + if(content is IPlatformVideo) + _slideUpOverlay = UISlideOverlays.showVideoOptionsOverlay(content, it); + } + } + adapter.onContentUrlClicked.subscribe { url, contentType -> + when(contentType) { + ContentType.MEDIA -> fragment.navigate(url).maximizeVideoDetail(); + ContentType.URL -> fragment.navigate(url); + else -> {}; + } + } + viewPager.adapter = adapter; + + val tabLayoutMediator = TabLayoutMediator(tabs, viewPager) { tab, position -> + tab.text = when (position) { + 0 -> "VIDEOS" + 1 -> "CHANNELS" + //2 -> "STORE" + 2 -> "SUPPORT" + 3 -> "ABOUT" + else -> "Unknown $position" + }; + }; + tabLayoutMediator.attach(); + + _tabLayoutMediator = tabLayoutMediator; + _tabs = tabs; + _viewPager = viewPager; + + if (_selectedTabIndex != -1) { + selectTab(_selectedTabIndex); + } + + setLoading(true); + } + + fun cleanup() { + _taskLoadPolycentricProfile.cancel(); + _taskGetChannel.cancel(); + _tabLayoutMediator.detach(); + _viewPager.unregisterOnPageChangeCallback(_onPageChangeCallback); + hideSlideUpOverlay(); + (_overlay_loading_spinner.drawable as Animatable?)?.stop(); + } + + fun onShown(parameter: Any?, isBack: Boolean) { + hideSlideUpOverlay(); + _taskLoadPolycentricProfile.cancel(); + _selectedTabIndex = -1; + + if (!isBack) { + _imageBanner.setImageDrawable(null); + + if (parameter is String) { + _buttonSubscribe.setSubscribeChannel(parameter); + _textChannel.text = ""; + _textChannelSub.text = ""; + + _url = parameter; + loadChannel(); + } else if (parameter is SerializedChannel) { + showChannel(parameter); + _url = parameter.url; + _creatorThumbnail.setThumbnail(parameter.url, false); + loadChannel(); + } else if (parameter is IPlatformChannel) + showChannel(parameter); + else if (parameter is PlatformAuthorLink) { + _textChannel.text = parameter.name; + _textChannelSub.text = ""; + _creatorThumbnail.setThumbnail(parameter.url, false); + _url = parameter.url; + loadChannel(); + } else if (parameter is Subscription) { + _textChannel.text = parameter.channel.name; + _textChannelSub.text = ""; + _creatorThumbnail.setThumbnail(parameter.channel.thumbnail, false); + + _url = parameter.channel.url; + loadChannel(); + } + } else { + loadChannel(); + } + } + + fun selectTab(selectedTabIndex: Int) { + _selectedTabIndex = selectedTabIndex; + _tabs.selectTab(_tabs.getTabAt(selectedTabIndex)); + } + + private fun setLoading(isLoading: Boolean) { + if (_isLoading == isLoading) { + return; + } + + _isLoading = isLoading; + if(isLoading){ + _overlay_loading.visibility = View.VISIBLE; + (_overlay_loading_spinner.drawable as Animatable?)?.start(); + } + else { + (_overlay_loading_spinner.drawable as Animatable?)?.stop(); + _overlay_loading.visibility = View.GONE; + } + } + + fun onBackPressed(): Boolean { + if (_slideUpOverlay != null) { + hideSlideUpOverlay(); + return true; + } + + return false; + } + + private fun hideSlideUpOverlay() { + _slideUpOverlay?.hide(false); + _slideUpOverlay = null; + } + + + private fun loadChannel() { + val url = _url; + if (url != null) { + setLoading(true); + _taskGetChannel.run(url); + } + } + + private fun showChannel(channel: IPlatformChannel) { + setLoading(false); + + _fragment.topBar?.onShown(channel); + + val buttons = arrayListOf(Pair(R.drawable.ic_playlist_add) { + UIDialogs.showConfirmationDialog(context, "Do you want to convert channel ${channel.name} to a playlist?", { + UIDialogs.showDialogProgress(context) { + _fragment.lifecycleScope.launch(Dispatchers.IO) { + try { + StatePlaylists.instance.createPlaylistFromChannel(channel) { page -> + _fragment.lifecycleScope.launch(Dispatchers.Main) { + it.setText("${channel.name}\nPage ${page}"); + } + }; + } + catch(ex: Exception) { + Logger.e(TAG, "Error", ex); + UIDialogs.showGeneralErrorDialog(context, "Failed to convert channel", ex); + } + + withContext(Dispatchers.Main) { + it.hide(); + } + }; + }; + }); + }); + + val plugin = StatePlatform.instance.getChannelClientOrNull(channel.url); + if (plugin != null && plugin.capabilities.hasSearchChannelContents) { + buttons.add(Pair(R.drawable.ic_search) { + _fragment.navigate(SuggestionsFragmentData("", SearchType.VIDEO, channel.url)); + }); + } + + _fragment.topBar?.assume()?.setMenuItems(buttons); + + _buttonSubscribe.setSubscribeChannel(channel); + _textChannel.text = channel.name; + _textChannelSub.text = "${channel.subscribers.toHumanNumber()} subscribers"; + + _creatorThumbnail.setThumbnail(channel.thumbnail, true); + Glide.with(_imageBanner) + .load(channel.banner) + .crossfade() + .into(_imageBanner) + + //TODO: Find a better way to access the adapter fragments.. + + (_viewPager.adapter as ChannelViewPagerAdapter?)?.let { + it.getFragment().setChannel(channel); + it.getFragment().setChannel(channel); + it.getFragment().setChannel(channel); + it.getFragment().setChannel(channel); + //TODO: Call on other tabs as needed + } + + this.channel = channel; + + val cachedProfile = PolycentricCache.instance.getCachedProfile(channel.url); + if (cachedProfile != null) { + setPolycentricProfile(cachedProfile, animate = false); + } else { + setPolycentricProfile(null, animate = false); + _taskLoadPolycentricProfile.run(channel.id); + } + } + + private fun setPolycentricProfile(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean) { + Log.i(TAG, "setPolycentricProfile(cachedPolycentricProfile = $cachedPolycentricProfile, animate = $animate)") + + val polycentricProfile = cachedPolycentricProfile?.profile; + if (polycentricProfile != null) { + _fragment.topBar?.onShown(polycentricProfile); + + if (polycentricProfile.systemState.username.isNotBlank()) + _textChannel.text = polycentricProfile.systemState.username; + + val dp_35 = 35.dp(resources) + val avatar = polycentricProfile.systemState.avatar?.selectBestImage(dp_35 * dp_35) + ?.let { it.toURLInfoSystemLinkUrl(polycentricProfile.system.toProto(), it.process, polycentricProfile.systemState.servers.toList()) }; + + if (avatar != null) { + _creatorThumbnail.setThumbnail(avatar, true); + } else { + _creatorThumbnail.setHarborAvailable(true, true); + } + + val banner = polycentricProfile.systemState.banner?.selectHighestResolutionImage() + ?.let { it.toURLInfoSystemLinkUrl(polycentricProfile.system.toProto(), it.process, polycentricProfile.systemState.servers.toList()) }; + + if (banner != null) { + Glide.with(_imageBanner) + .load(banner) + .crossfade() + .into(_imageBanner); + } + } + + (_viewPager.adapter as ChannelViewPagerAdapter?)?.let { + it.getFragment().setPolycentricProfile(polycentricProfile, animate); + it.getFragment().setPolycentricProfile(polycentricProfile, animate); + it.getFragment().setPolycentricProfile(polycentricProfile, animate); + it.getFragment().setPolycentricProfile(polycentricProfile, animate); + //TODO: Call on other tabs as needed + } + } + } + + companion object { + val TAG = "ChannelFragment"; + fun newInstance() = ChannelFragment().apply { } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt new file mode 100644 index 00000000..a7382097 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt @@ -0,0 +1,202 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.annotation.SuppressLint +import android.content.Context +import android.util.Log +import android.view.LayoutInflater +import android.widget.LinearLayout +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist +import com.futo.platformplayer.api.media.models.post.IPlatformPost +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.structures.* +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StatePlayer +import com.futo.platformplayer.video.PlayerManager +import com.futo.platformplayer.views.FeedStyle +import com.futo.platformplayer.views.adapters.PreviewContentListAdapter +import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder +import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader +import com.futo.platformplayer.views.adapters.InsertedViewHolder +import com.futo.platformplayer.views.adapters.PreviewNestedVideoViewHolder +import com.futo.platformplayer.views.adapters.PreviewVideoViewHolder +import kotlin.math.floor + +abstract class ContentFeedView : FeedView, ContentPreviewViewHolder> where TFragment : MainFragment { + private var _exoPlayer: PlayerManager? = null; + + override val feedStyle: FeedStyle = FeedStyle.PREVIEW; + + private var _previewsEnabled: Boolean = true; + override val visibleThreshold: Int get() = if (feedStyle == FeedStyle.PREVIEW) { 5 } else { 10 }; + protected lateinit var headerView: LinearLayout; + + constructor(fragment: TFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData, LinearLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>? = null) : super(fragment, inflater, cachedRecyclerData) { + + } + + override fun filterResults(results: List): List { + return results; + } + + override fun createAdapter(recyclerResults: RecyclerView, context: Context, dataset: ArrayList): InsertedViewAdapterWithLoader { + val player = StatePlayer.instance.getThumbnailPlayerOrCreate(context); + player.modifyState("ThumbnailPlayer", { state -> state.muted = true }); + _exoPlayer = player; + + val v = LinearLayout(context).apply { + layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + orientation = LinearLayout.VERTICAL; + }; + headerView = v; + + return PreviewContentListAdapter(context, feedStyle, dataset, player, _previewsEnabled, arrayListOf(v)).apply { + attachAdapterEvents(this); + } + } + + private fun attachAdapterEvents(adapter: PreviewContentListAdapter) { + adapter.onContentUrlClicked.subscribe(this, this@ContentFeedView::onContentUrlClicked); + adapter.onContentClicked.subscribe(this) { content, time -> + this@ContentFeedView.onContentClicked(content, time); + }; + adapter.onChannelClicked.subscribe(this) { fragment.navigate(it) }; + adapter.onAddToClicked.subscribe(this) { content -> + //TODO: Reconstruct search video from detail if search is null + _overlayContainer.let { + if(content is IPlatformVideo) + UISlideOverlays.showVideoOptionsOverlay(content, it) { + if (fragment is HomeFragment) { + val removeIndex = recyclerData.results.indexOf(content); + if (removeIndex >= 0) { + recyclerData.results.removeAt(removeIndex); + recyclerData.adapter.notifyItemRemoved(recyclerData.adapter.childToParentPosition(removeIndex)); + } + } + }; + } + }; + adapter.onAddToQueueClicked.subscribe(this) { + if(it is IPlatformVideo) { + StatePlayer.instance.addToQueue(it); + val name = if (it.name.length > 20) (it.name.subSequence(0, 20).toString() + "...") else it.name; + UIDialogs.toast(context, "Queued [$name]", false); + } + }; + } + + private fun detachAdapterEvents() { + val adapter = recyclerData.adapter as PreviewContentListAdapter? ?: return; + adapter.onContentUrlClicked.remove(this); + adapter.onContentClicked.remove(this); + adapter.onChannelClicked.remove(this); + adapter.onAddToClicked.remove(this); + adapter.onAddToQueueClicked.remove(this); + } + + override fun onRestoreCachedData(cachedData: RecyclerData, LinearLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>) { + super.onRestoreCachedData(cachedData) + val v = LinearLayout(context).apply { + layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + orientation = LinearLayout.VERTICAL; + }; + headerView = v; + cachedData.adapter.viewsToPrepend.add(v); + (cachedData.adapter as PreviewContentListAdapter?)?.let { attachAdapterEvents(it) }; + } + + override fun createLayoutManager(recyclerResults: RecyclerView, context: Context): LinearLayoutManager { + val llmResults = LinearLayoutManager(context); + llmResults.orientation = LinearLayoutManager.VERTICAL; + return llmResults; + } + + override fun onScrollStateChanged(newState: Int) { + if (!_previewsEnabled) + return; + + if (newState == RecyclerView.SCROLL_STATE_IDLE) + playPreview(); + } + + protected open fun onContentClicked(content: IPlatformContent, time: Long) { + if(content is IPlatformVideo) { + if (Settings.instance.playback.shouldResumePreview(time)) + fragment.navigate(content.withTimestamp(time)).maximizeVideoDetail(); + else + fragment.navigate(content).maximizeVideoDetail(); + } else if (content is IPlatformPlaylist) { + fragment.navigate(content); + } else if (content is IPlatformPost) { + fragment.navigate(content); + } + } + protected open fun onContentUrlClicked(url: String, contentType: ContentType) { + when(contentType) { + ContentType.MEDIA -> fragment.navigate(url).maximizeVideoDetail(); + ContentType.PLAYLIST -> fragment.navigate(url); + ContentType.URL -> fragment.navigate(url); + else -> {}; + } + } + + private fun playPreview() { + if(feedStyle == FeedStyle.THUMBNAIL) + return; + + val firstVisible = recyclerData.layoutManager.findFirstVisibleItemPosition(); + val lastVisible = recyclerData.layoutManager.findLastVisibleItemPosition(); + val itemsVisible = lastVisible - firstVisible + 1; + val autoPlayIndex = (firstVisible + floor(itemsVisible / 2.0 + 0.49).toInt()).coerceAtLeast(0).coerceAtMost((recyclerData.results.size - 1)); + + Log.v(TAG, "auto play index=$autoPlayIndex"); + val viewHolder = _recyclerResults.findViewHolderForAdapterPosition(autoPlayIndex) ?: return; + Logger.i(TAG, "viewHolder=$viewHolder") + if (viewHolder !is InsertedViewHolder<*>) { + return; + } + + if (viewHolder.childViewHolder !is PreviewVideoViewHolder && viewHolder.childViewHolder !is PreviewNestedVideoViewHolder) { + return; + } + + //TODO: Is this still necessary? + if(viewHolder.childViewHolder is ContentPreviewViewHolder) + (recyclerData.adapter as PreviewContentListAdapter?)?.preview(viewHolder.childViewHolder) + } + + fun stopVideo() { + //TODO: Is this still necessary? + (recyclerData.adapter as PreviewContentListAdapter?)?.stopPreview(); + } + + fun onPause() { + stopVideo(); + } + + override fun cleanup() { + super.cleanup(); + val viewCount = recyclerData.adapter.viewsToPrepend.size; + detachAdapterEvents(); + recyclerData.adapter.viewsToPrepend.clear(); + recyclerData.adapter.notifyItemRangeRemoved(0, viewCount); + (recyclerData.adapter as PreviewContentListAdapter?)?.release(); + } + + fun setPreviewsEnabled(previewsEnabled: Boolean) { + if (!previewsEnabled) + stopVideo(); + else + playPreview(); + + _previewsEnabled = previewsEnabled; + } + + companion object { + private val TAG = "ContentFeedView"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt new file mode 100644 index 00000000..035ff8ed --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt @@ -0,0 +1,289 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.Settings +import com.futo.platformplayer.UISlideOverlays +import com.futo.platformplayer.api.media.models.ResultCapabilities +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment +import com.futo.platformplayer.views.FeedStyle +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class ContentSearchResultsFragment : MainFragment() { + override val isMainView : Boolean = true; + override val isTab: Boolean = false; + override val hasBottomBar: Boolean get() = true; + + private var _view: ContentSearchResultsView? = null; + + override fun onShownWithView(parameter: Any?, isBack: Boolean) { + super.onShownWithView(parameter, isBack); + _view?.onShown(parameter, isBack); + } + + override fun onHide() { + super.onHide() + _view?.onHide(); + } + + override fun onResume() { + super.onResume() + _view?.onResume(); + } + + override fun onPause() { + super.onPause() + _view?.onPause(); + } + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = ContentSearchResultsView(this, inflater); + _view = view; + return view; + } + + override fun onDestroyMainView() { + super.onDestroyMainView(); + _view?.cleanup(); + _view = null; + } + + fun setPreviewsEnabled(previewsEnabled: Boolean) { + _view?.setPreviewsEnabled(previewsEnabled); + } + + @SuppressLint("ViewConstructor") + class ContentSearchResultsView : ContentFeedView { + override val feedStyle: FeedStyle get() = Settings.instance.search.getSearchFeedStyle(); + + private var _query: String? = null; + private var _sortBy: String? = null; + private var _filterValues: HashMap> = hashMapOf(); + private var _enabledClientIds: List? = null; + private var _channelUrl: String? = null; + + private val _taskSearch: TaskHandler>; + + constructor(fragment: ContentSearchResultsFragment, inflater: LayoutInflater) : super(fragment, inflater) { + _taskSearch = TaskHandler>({fragment.lifecycleScope}, { query -> + Logger.i(TAG, "Searching for: $query") + val channelUrl = _channelUrl; + if (channelUrl != null) { + StatePlatform.instance.searchChannel(channelUrl, query, null, _sortBy, _filterValues, _enabledClientIds) + } else { + StatePlatform.instance.searchRefresh(fragment.lifecycleScope, query, null, _sortBy, _filterValues, _enabledClientIds) + } + }) + .success { loadedResult(it); } + .exception { + Logger.w(ChannelFragment.TAG, "Failed to load results.", it); + UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() }); + } + } + + override fun cleanup() { + super.cleanup(); + _taskSearch.cancel(); + } + + fun onShown(parameter: Any?, isBack: Boolean) { + if(parameter is SuggestionsFragmentData) { + if(!isBack) { + setQuery(parameter.query, false); + setChannelUrl(parameter.channelUrl, false); + + fragment.topBar?.apply { + if (this is SearchTopBarFragment) { + this.setText(parameter.query); + } + } + } + } + + fragment.topBar?.apply { + if (this is SearchTopBarFragment) { + setFilterButtonVisible(true); + + onFilterClick.subscribe(this) { + _overlayContainer?.let { + val filterValuesCopy = HashMap(_filterValues); + val filtersOverlay = UISlideOverlays.showFiltersOverlay(lifecycleScope, it, _enabledClientIds!!, filterValuesCopy); + filtersOverlay.onOK.subscribe { enabledClientIds, changed -> + if (changed) { + setFilterValues(filtersOverlay.commonCapabilities, filterValuesCopy); + } + + _enabledClientIds = enabledClientIds; + + val sorts = filtersOverlay.commonCapabilities?.sorts ?: listOf(); + if (sorts.isNotEmpty()) { + setSortByOptions(sorts); + if (!sorts.contains(_sortBy)) { + _sortBy = null; + } + } else { + setSortByOptions(null); + _sortBy = null; + } + + loadResults(); + }; + }; + }; + + onSearch.subscribe(this) { + setQuery(it, true); + }; + } + } + + fragment.lifecycleScope.launch(Dispatchers.IO) { + try { + val commonCapabilities = StatePlatform.instance.getCommonSearchCapabilities(StatePlatform.instance.getEnabledClients().map { it.id }); + val sorts = commonCapabilities?.sorts ?: listOf(); + if (sorts.size > 1) { + withContext(Dispatchers.Main) { + try { + setSortByOptions(sorts); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to set sort options.", e); + } + } + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to gtet common search capabilities.", e) + } + } + + onSortBySelect.subscribe(this) { + if (_sortBy == it) { + return@subscribe; + } + + Logger.i(TAG, "Sort by changed: $it") + setSortBy(it); + }; + + _enabledClientIds = StatePlatform.instance.getEnabledClients().map { it -> it.id }; + _sortBy = null; + _filterValues.clear(); + + clearResults(); + loadResults(); + } + + fun onHide() { + onSortBySelect.remove(this); + + fragment.topBar?.apply { + if (this is SearchTopBarFragment) { + setFilterButtonVisible(false); + onFilterClick.remove(this); + onSearch.remove(this); + } + }; + + setActiveTags(null); + setSortByOptions(null); + } + + override fun reload() { + loadResults(); + } + + private fun setQuery(query: String, updateResults: Boolean = true) { + _query = query; + + if (updateResults) { + clearResults(); + loadResults(); + } + } + + private fun setChannelUrl(channelUrl: String?, updateResults: Boolean = true) { + _channelUrl = channelUrl; + + if (updateResults) { + clearResults(); + loadResults(); + } + } + + private fun setSortBy(sortBy: String?, updateResults: Boolean = true) { + _sortBy = sortBy; + + if (updateResults) { + clearResults(); + loadResults(); + } + } + + private fun setFilterValues(resultCapabilities: ResultCapabilities?, filterValues: HashMap>) { + clearResults(); + + if (resultCapabilities != null) { + val tags = arrayListOf(); + for (filter in resultCapabilities.filters) { + val values = filterValues[filter.idOrName] ?: continue; + if (values.isNotEmpty()) { + val titles = arrayListOf(); + for (value in values) { + val title = filter.filters.firstOrNull { it.idOrName == value } ?: continue; + titles.add(title.idOrName); + } + + tags.add("${filter.name}: ${titles.joinToString(", ")}"); + } + } + + setActiveTags(tags); + } else { + setActiveTags(null); + } + + _filterValues = filterValues; + loadResults(); + } + + override fun onContentClicked(content: IPlatformContent, time: Long) { + super.onContentClicked(content, time) + + (fragment.topBar as SearchTopBarFragment?)?.apply { + clearFocus(); + } + } + + private fun loadResults() { + val query = _query; + if (query.isNullOrBlank()) { + return; + } + + setLoading(true); + _taskSearch.run(query); + } + private fun loadedResult(pager : IPager) { + finishRefreshLayoutLoader(); + setLoading(false); + setPager(pager); + } + } + + companion object { + private const val TAG = "VideoSearchResultsFragment"; + + fun newInstance() = ContentSearchResultsFragment().apply {} + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorFeedView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorFeedView.kt new file mode 100644 index 00000000..78f90fb1 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorFeedView.kt @@ -0,0 +1,51 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.content.Context +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.ViewGroup.MarginLayoutParams +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.structures.* +import com.futo.platformplayer.views.FeedStyle +import com.futo.platformplayer.views.adapters.* +import com.futo.platformplayer.views.adapters.viewholders.CreatorViewHolder + +abstract class CreatorFeedView : FeedView, CreatorViewHolder> where TFragment : MainFragment { + override val feedStyle: FeedStyle = FeedStyle.THUMBNAIL; //R.layout.list_creator; + + constructor(fragment: TFragment, inflater: LayoutInflater) : super(fragment, inflater) { + + } + + override fun createAdapter(recyclerResults: RecyclerView, context: Context, dataset: ArrayList): InsertedViewAdapterWithLoader { + return InsertedViewAdapterWithLoader(context, arrayListOf(), arrayListOf(), + childCountGetter = { dataset.size }, + childViewHolderBinder = { viewHolder, position -> viewHolder.bind(dataset[position]); }, + childViewHolderFactory = { viewGroup, _ -> + val holder = CreatorViewHolder(viewGroup, false); + holder.onClick.subscribe { c -> fragment.navigate(c) }; + return@InsertedViewAdapterWithLoader holder; + } + ); + } + + override fun createLayoutManager(recyclerResults: RecyclerView, context: Context): LinearLayoutManager { + val glmResults = GridLayoutManager(context, 2); + glmResults.orientation = LinearLayoutManager.VERTICAL; + + _swipeRefresh.layoutParams = (_swipeRefresh.layoutParams as MarginLayoutParams?)?.apply { + rightMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8.0f, context.resources.displayMetrics).toInt(); + }; + + return glmResults; + } + + companion object { + private val TAG = "CreatorFeedView"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorSearchResultsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorSearchResultsFragment.kt new file mode 100644 index 00000000..ab58f63f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorSearchResultsFragment.kt @@ -0,0 +1,116 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.Settings +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment +import com.futo.platformplayer.views.FeedStyle + +class CreatorSearchResultsFragment : MainFragment() { + override val isMainView : Boolean = true; + override val isTab: Boolean = false; + override val hasBottomBar: Boolean get() = true; + + private var _view: CreatorSearchResultsView? = null; + + override fun onShownWithView(parameter: Any?, isBack: Boolean) { + super.onShownWithView(parameter, isBack); + _view?.onShown(parameter, isBack); + } + + override fun onResume() { + super.onResume() + _view?.onResume(); + } + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = CreatorSearchResultsView(this, inflater); + _view = view; + return view; + } + + override fun onDestroyMainView() { + super.onDestroyMainView(); + _view?.cleanup(); + _view = null; + } + + @SuppressLint("ViewConstructor") + class CreatorSearchResultsView : CreatorFeedView { + override val feedStyle: FeedStyle get() = Settings.instance.search.getSearchFeedStyle(); + + private var _query: String? = null; + + private val _taskSearch: TaskHandler>; + + constructor(fragment: CreatorSearchResultsFragment, inflater: LayoutInflater): super(fragment, inflater) { + _taskSearch = TaskHandler>({fragment.lifecycleScope}, { query -> StatePlatform.instance.searchChannels(query) }) + .success { loadedResult(it); } + .exception { + Logger.w(ChannelFragment.TAG, "Failed to load results.", it); + UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() }); + } + } + + override fun cleanup() { + super.cleanup(); + _taskSearch.cancel(); + } + + fun onShown(parameter: Any?, isBack: Boolean) { + if(parameter is String) { + if(!isBack) { + setQuery(parameter); + + fragment.topBar?.apply { + if (this is SearchTopBarFragment) { + setText(parameter); + onSearch.subscribe(this) { + setQuery(it); + }; + } + } + } + } + } + + override fun reload() { + loadResults(); + } + + private fun setQuery(query: String) { + clearResults(); + _query = query; + loadResults(); + } + + private fun loadResults() { + val query = _query; + if (query.isNullOrBlank()) { + return; + } + + setLoading(true); + _taskSearch.run(query); + } + private fun loadedResult(pager: IPager) { + finishRefreshLayoutLoader(); + setLoading(false); + setPager(pager); + } + } + + companion object { + fun newInstance() = CreatorSearchResultsFragment().apply {} + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorsFragment.kt new file mode 100644 index 00000000..421ae22a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorsFragment.kt @@ -0,0 +1,56 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.Spinner +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.R +import com.futo.platformplayer.views.adapters.SubscriptionAdapter + +class CreatorsFragment : MainFragment() { + override val isMainView : Boolean = true; + override val isTab: Boolean = true; + override val hasBottomBar: Boolean get() = true; + + private var _spinnerSortBy: Spinner? = null; + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = inflater.inflate(R.layout.fragment_creators, container, false); + + val adapter = SubscriptionAdapter(inflater, getString(R.string.confirm_delete_subscription)); + adapter.onClick.subscribe { platformUser -> navigate(platformUser) }; + + val spinnerSortBy: Spinner = view.findViewById(R.id.spinner_sortby); + spinnerSortBy.adapter = ArrayAdapter(view.context, R.layout.spinner_item_simple, resources.getStringArray(R.array.subscriptions_sortby_array)).also { + it.setDropDownViewResource(R.layout.spinner_dropdownitem_simple); + }; + spinnerSortBy.setSelection(adapter.sortBy); + spinnerSortBy.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>, view: View?, pos: Int, id: Long) { + adapter.sortBy = pos; + } + override fun onNothingSelected(parent: AdapterView<*>?) = Unit + }; + + _spinnerSortBy = spinnerSortBy; + + val recyclerView = view.findViewById(R.id.recycler_subscriptions); + recyclerView.adapter = adapter; + recyclerView.layoutManager = LinearLayoutManager(view.context); + return view; + } + + override fun onDestroyMainView() { + super.onDestroyMainView(); + _spinnerSortBy = null; + } + + companion object { + fun newInstance() = CreatorsFragment().apply {} + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/DownloadsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/DownloadsFragment.kt new file mode 100644 index 00000000..9d1f5680 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/DownloadsFragment.kt @@ -0,0 +1,183 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.TextView +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.* +import com.futo.platformplayer.downloads.VideoDownload +import com.futo.platformplayer.downloads.VideoLocal +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateDownloads +import com.futo.platformplayer.views.AnyInsertedAdapterView +import com.futo.platformplayer.views.AnyInsertedAdapterView.Companion.asAnyWithTop +import com.futo.platformplayer.views.others.ProgressBar +import com.futo.platformplayer.views.adapters.viewholders.VideoDownloadViewHolder +import com.futo.platformplayer.views.items.ActiveDownloadItem +import com.futo.platformplayer.views.items.PlaylistDownloadItem +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class DownloadsFragment : MainFragment() { + private val TAG = "DownloadsFragment"; + + override val isMainView : Boolean = true; + override val isTab: Boolean = true; + override val hasBottomBar: Boolean get() = true; + + private var _view: DownloadsView? = null; + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = DownloadsView(this, inflater); + _view = view; + return view; + } + + override fun onResume() { + super.onResume() + _view?.reloadUI(); + + StateDownloads.instance.onDownloadsChanged.subscribe(this) { + lifecycleScope.launch(Dispatchers.Main) { + try { + Logger.i(TAG, "Reloading UI for downloads"); + _view?.reloadUI() + } catch (e: Throwable) { + Logger.e(TAG, "Failed to reload UI for downloads", e) + } + } + }; + StateDownloads.instance.onDownloadedChanged.subscribe(this) { + lifecycleScope.launch(Dispatchers.Main) { + try { + Logger.i(TAG, "Reloading UI for downloaded"); + _view?.reloadUI(); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to reload UI for downloaded", e) + } + } + }; + StateDownloads.instance.onExportsChanged.subscribe(this) { + lifecycleScope.launch(Dispatchers.Main) { + try { + Logger.i(TAG, "Reloading UI for exports"); + _view?.reloadUI() + } catch (e: Throwable) { + Logger.e(TAG, "Failed to reload UI for exports", e) + } + } + }; + } + + override fun onPause() { + super.onPause(); + + StateDownloads.instance.onDownloadsChanged.remove(this); + StateDownloads.instance.onDownloadedChanged.remove(this); + StateDownloads.instance.onExportsChanged.remove(this); + } + + private class DownloadsView : LinearLayout { + private val TAG = "DownloadsView"; + private val _frag: DownloadsFragment; + + private val _usageUsed: TextView; + private val _usageAvailable: TextView; + private val _usageProgress: ProgressBar; + + private val _listActiveDownloadsContainer: LinearLayout; + private val _listActiveDownloadsMeta: TextView; + private val _listActiveDownloads: LinearLayout; + + private val _listPlaylistsContainer: LinearLayout; + private val _listPlaylistsMeta: TextView; + private val _listPlaylists: LinearLayout; + + private val _listDownloadedHeader: LinearLayout; + private val _listDownloadedMeta: TextView; + private val _listDownloaded: AnyInsertedAdapterView; + + constructor(frag: DownloadsFragment, inflater: LayoutInflater): super(frag.requireContext()) { + inflater.inflate(R.layout.fragment_downloads, this); + _frag = frag; + + _usageUsed = findViewById(R.id.downloads_usage_used); + _usageAvailable = findViewById(R.id.downloads_usage_available); + _usageProgress = findViewById(R.id.downloads_usage_progress); + + _listActiveDownloadsContainer = findViewById(R.id.downloads_active_downloads_container); + _listActiveDownloadsMeta = findViewById(R.id.downloads_active_downloads_meta); + _listActiveDownloads = findViewById(R.id.downloads_active_downloads_list); + + _listPlaylistsContainer = findViewById(R.id.downloads_playlist_container); + _listPlaylistsMeta = findViewById(R.id.downloads_playlist_meta); + _listPlaylists = findViewById(R.id.downloads_playlist_list); + + _listDownloadedHeader = findViewById(R.id.downloads_videos_header); + _listDownloadedMeta = findViewById(R.id.downloads_videos_meta); + + _listDownloaded = findViewById(R.id.list_downloaded) + .asAnyWithTop(findViewById(R.id.downloads_top)) { + it.onClick.subscribe { + _frag.navigate(it).maximizeVideoDetail(); + } + }; + + + reloadUI(); + } + + + fun reloadUI() { + val usage = StateDownloads.instance.getTotalUsage(true); + _usageUsed.text = "${usage.usage.toHumanBytesSize()} Used"; + _usageAvailable.text = "${usage.available.toHumanBytesSize()} Available"; + _usageProgress.progress = usage.percentage.toFloat(); + + + val activeDownloads = StateDownloads.instance.getDownloading(); + val playlists = StateDownloads.instance.getCachedPlaylists(); + val downloaded = StateDownloads.instance.getDownloadedVideos() + .filter { it.groupType != VideoDownload.GROUP_PLAYLIST || it.groupID == null || !StateDownloads.instance.hasCachedPlaylist(it.groupID!!) }; + + if(activeDownloads.isEmpty()) + _listActiveDownloadsContainer.visibility = GONE; + else { + _listActiveDownloadsContainer.visibility = VISIBLE; + _listActiveDownloadsMeta.text = "(${activeDownloads.size})"; + + _listActiveDownloads.removeAllViews(); + for(view in activeDownloads.map { ActiveDownloadItem(context, it, _frag.lifecycleScope) }) + _listActiveDownloads.addView(view); + } + + if(playlists.isEmpty()) + _listPlaylistsContainer.visibility = GONE; + else { + _listPlaylistsContainer.visibility = VISIBLE; + _listPlaylistsMeta.text = "(${playlists.size} playlists, ${playlists.sumOf { it.playlist.videos.size }} videos)"; + + _listPlaylists.removeAllViews(); + for(view in playlists.map { PlaylistDownloadItem(context, it) }) { + view.setOnClickListener { + _frag.navigate(view.playlist.playlist); + }; + _listPlaylists.addView(view); + } + } + + if(downloaded.isEmpty()) { + _listDownloadedHeader.visibility = GONE; + } else { + _listDownloadedHeader.visibility = VISIBLE; + _listDownloadedMeta.text = "(${downloaded.size} videos)"; + } + + _listDownloaded.setData(downloaded); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt new file mode 100644 index 00000000..d6c2c1bf --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt @@ -0,0 +1,425 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.content.Context +import android.graphics.Color +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.* +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.LayoutManager +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.platforms.js.models.JSPager +import com.futo.platformplayer.api.media.structures.* +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.engine.exceptions.PluginException +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.views.FeedStyle +import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder +import com.futo.platformplayer.views.others.ProgressBar +import com.futo.platformplayer.views.others.TagsView +import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader +import com.futo.platformplayer.views.adapters.InsertedViewHolder +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.time.OffsetDateTime + +abstract class FeedView : LinearLayout where TPager : IPager, TViewHolder : RecyclerView.ViewHolder, TFragment : MainFragment { + protected val _recyclerResults: RecyclerView; + protected val _overlayContainer: FrameLayout; + protected val _swipeRefresh: SwipeRefreshLayout; + private val _progress_bar: ProgressBar; + private val _spinnerSortBy: Spinner; + private val _containerSortBy: LinearLayout; + private val _tagsView: TagsView; + + protected val _toolbarContentView: LinearLayout; + + private var _loading: Boolean = true; + + private val _pager_lock = Object(); + private var _cache: ItemCache? = null; + + open val visibleThreshold = 15; + + protected abstract val feedStyle: FeedStyle; + + val onTagClick = Event1(); + val onSortBySelect = Event1(); + + private var _sortByOptions: List? = null; + private var _activeTags: List? = null; + + private var _nextPageHandler: TaskHandler>; + val recyclerData: RecyclerData, LinearLayoutManager, TPager, TResult, TConverted, InsertedViewHolder>; + + val fragment: TFragment; + + private val _scrollListener: RecyclerView.OnScrollListener; + + constructor(fragment: TFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData, LinearLayoutManager, TPager, TResult, TConverted, InsertedViewHolder>? = null) : super(inflater.context) { + this.fragment = fragment; + inflater.inflate(R.layout.fragment_feed, this); + + _progress_bar = findViewById(R.id.progress_bar); + _progress_bar.inactiveColor = Color.TRANSPARENT; + + _swipeRefresh = findViewById(R.id.swipe_refresh); + val recyclerResults: RecyclerView = findViewById(R.id.list_results); + + if (cachedRecyclerData != null) { + recyclerData = cachedRecyclerData; + onRestoreCachedData(cachedRecyclerData); + attachParentPagerEvents(); + attachPagerEvents(); + setLoading(false); + } else { + val lmResults = createLayoutManager(recyclerResults, context); + val dataset = arrayListOf(); + val adapterResults = createAdapter(recyclerResults, context, dataset); + recyclerData = RecyclerData(adapterResults, lmResults, dataset); + } + + _swipeRefresh.setOnRefreshListener { + reload(); + }; + + recyclerResults.layoutManager = recyclerData.layoutManager; + recyclerResults.adapter = recyclerData.adapter; + + _overlayContainer = findViewById(R.id.overlay_container); + _recyclerResults = recyclerResults; + + _containerSortBy = findViewById(R.id.container_sort_by); + _spinnerSortBy = findViewById(R.id.spinner_sortby); + _spinnerSortBy.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>, view: View?, pos: Int, id: Long) { + val sortByOptions = _sortByOptions ?: return; + if (pos == 0) { + onSortBySelect.emit(null); + return; + } + + onSortBySelect.emit(sortByOptions[pos - 1]); + } + override fun onNothingSelected(parent: AdapterView<*>?) { + onSortBySelect.emit(null); + } + }; + setSortByOptions(null); + _tagsView = findViewById(R.id.tags_view); + _tagsView.onClick.subscribe { onTagClick.emit(it.first) }; + setActiveTags(null); + + _toolbarContentView = findViewById(R.id.container_toolbar_content); + + _nextPageHandler = TaskHandler>({fragment.lifecycleScope}, { + if (it is IAsyncPager<*>) + it.nextPageAsync(); + else + it.nextPage(); + + processPagerExceptions(it); + return@TaskHandler it.getResults(); + }).success { + setLoading(false); + + if (it.isEmpty()) { + return@success; + } + + val posBefore = recyclerData.results.size; + val filteredResults = filterResults(it); + recyclerData.results.addAll(filteredResults); + recyclerData.resultsUnfiltered.addAll(it); + recyclerData.adapter.notifyItemRangeInserted(recyclerData.adapter.childToParentPosition(posBefore), filteredResults.size); + }.exception { + Logger.w(TAG, "Failed to load next page.", it); + UIDialogs.showGeneralRetryErrorDialog(context, "Failed to load next page", it, { + loadNextPage(); + }); + //UIDialogs.showDataRetryDialog(layoutInflater, it.message, { loadNextPage() }); + }; + + _scrollListener = object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + super.onScrollStateChanged(recyclerView, newState); + onScrollStateChanged(newState); + } + + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy); + + val visibleItemCount = _recyclerResults.childCount; + val firstVisibleItem = recyclerData.layoutManager.findFirstVisibleItemPosition(); + if (!_loading && firstVisibleItem + visibleItemCount + visibleThreshold >= recyclerData.results.size && firstVisibleItem > 0) { + //Logger.i(TAG, "loadNextPage(): firstVisibleItem=$firstVisibleItem visibleItemCount=$visibleItemCount visibleThreshold=$visibleThreshold _results.size=${_results.size}") + loadNextPage(); + } + } + }; + + _recyclerResults.addOnScrollListener(_scrollListener); + } + + fun onResume() { + //Reload the pager if the plugin was killed + val pager = recyclerData.pager; + if((pager is MultiPager<*> && pager.findPager { it is JSPager<*> && !it.isAvailable } != null) || + (pager is JSPager<*> && !pager.isAvailable)) { + Logger.w(TAG, "Detected pager of a dead plugin instance, reloading"); + reload(); + } + } + + open fun cleanup() { + detachParentPagerEvents(); + detachPagerEvents(); + + _recyclerResults.removeOnScrollListener(_scrollListener); + _nextPageHandler.cancel(); + + _recyclerResults.adapter = null; + _recyclerResults.layoutManager = null; + } + + protected open fun onScrollStateChanged(newState: Int) {} + + protected open fun setActiveTags(activeTags: List?) { + _activeTags = activeTags; + + if (activeTags != null && activeTags.isNotEmpty()) { + _tagsView.setTags(activeTags); + _tagsView.visibility = View.VISIBLE; + } else { + _tagsView.visibility = View.GONE; + } + } + protected open fun setSortByOptions(options: List?) { + _sortByOptions = options; + + if (options != null && options.isNotEmpty()) { + val allOptions = arrayListOf(); + allOptions.add("Default"); + allOptions.addAll(options); + + _spinnerSortBy.adapter = ArrayAdapter(context, R.layout.spinner_item_simple, allOptions).also { + it.setDropDownViewResource(R.layout.spinner_dropdownitem_simple); + }; + + _containerSortBy.visibility = View.VISIBLE; + } else { + _containerSortBy.visibility = View.GONE; + } + } + protected abstract fun createAdapter(recyclerResults: RecyclerView, context: Context, dataset: ArrayList): InsertedViewAdapterWithLoader; + protected abstract fun createLayoutManager(recyclerResults: RecyclerView, context: Context): LinearLayoutManager; + protected open fun onRestoreCachedData(cachedData: RecyclerData, LinearLayoutManager, TPager, TResult, TConverted, InsertedViewHolder>) {} + + protected fun setProgress(fin: Int, total: Int) { + val progress = (fin.toFloat() / total); + _progress_bar.progress = progress; + if(progress > 0 && progress < 1) + { + if(_progress_bar.height == 0) + _progress_bar.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 5); + } + else if(_progress_bar.height > 0) { + _progress_bar.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0); + } + } + + private fun processPagerExceptions(pager: IPager<*>) { + if(pager is MultiPager<*> && pager.allowFailure) { + val ex = pager.getResultExceptions(); + for(kv in ex) { + val jsVideoPager: JSPager<*>? = if(kv.key is MultiPager<*>) + (kv.key as MultiPager<*>).findPager { it is JSPager<*> } as JSPager<*>?; + else if(kv.key is JSPager<*>) + kv.key as JSPager<*>; + else null; + + context?.let { + fragment.lifecycleScope.launch(Dispatchers.Main) { + try { + if(jsVideoPager != null) + UIDialogs.toast(it, "Plugin ${jsVideoPager.getPluginConfig().name} failed:\n${kv.value.message}", false); + else + UIDialogs.toast(it, kv.value.message ?: "", false); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to show toast.", e) + } + } + } + } + } + } + + open fun filterResults(results: List): List { + return results as List; + } + + open fun reload() { + + } + + protected fun finishRefreshLayoutLoader() { + _swipeRefresh.isRefreshing = false; + } + + fun clearResults(){ + setPager(EmptyPager() as TPager); + } + + fun preloadCache(cache: ItemCache) { + _cache = cache + recyclerData.results.clear(); + val results = cache.cachePager.getResults(); + val resultsFiltered = filterResults(results); + recyclerData.results.addAll(resultsFiltered); + recyclerData.adapter.notifyDataSetChanged(); + //insertPagerResults(_cache!!.cachePager.getResults(), false); + } + fun setPager(pager: TPager, cache: ItemCache? = null) { + synchronized(_pager_lock) { + detachParentPagerEvents(); + detachPagerEvents(); + + val pagerToSet: TPager?; + if(pager is IRefreshPager<*>) { + recyclerData.parentPager = pager; + attachParentPagerEvents(); + pagerToSet = pager.getCurrentPager() as TPager; + } + else pagerToSet = pager; + + loadPagerInternal(pagerToSet, cache); + } + } + + private fun detachParentPagerEvents() { + val parentPager = recyclerData.parentPager; + if (parentPager != null && parentPager is IRefreshPager<*>) { + parentPager.onPagerError.remove(this); + parentPager.onPagerChanged.remove(this); + recyclerData.parentPager = null; + } + } + + private fun attachParentPagerEvents() { + val parentPager = recyclerData.parentPager as IRefreshPager<*>? ?: return; + parentPager.onPagerChanged.subscribe(this) { + fragment.lifecycleScope.launch(Dispatchers.Main) { + try { + loadPagerInternal(it as TPager); + } catch (e: Throwable) { + Logger.e(TAG, "Failed loadPagerInternal", e) + } + } + }; + parentPager.onPagerError.subscribe(this) { + Logger.e(TAG, "Search pager failed: ${it.message}", it); + when (it) { + is PluginException -> UIDialogs.toast("Plugin [${it.config.name}] failed due to:\n${it.message}") + is CancellationException -> { + //Hide cancelled toast + } + else -> UIDialogs.toast("Plugin failed due to:\n${it.message}") + }; + }; + } + + private fun loadPagerInternal(pager: TPager, cache: ItemCache? = null) { + _cache = cache; + + detachPagerEvents(); + recyclerData.pager = pager; + attachPagerEvents(); + + processPagerExceptions(pager); + + recyclerData.results.clear(); + recyclerData.resultsUnfiltered.clear(); + val toAdd = pager.getResults(); + val filteredResults = filterResults(toAdd); + recyclerData.results.addAll(filteredResults); + //insertPagerResults(toAdd, true); + recyclerData.resultsUnfiltered.addAll(toAdd); + recyclerData.adapter.notifyDataSetChanged(); + recyclerData.loadedFeedStyle = feedStyle; + } + + private fun detachPagerEvents() { + val p = recyclerData.pager; + if(p is IReplacerPager<*>) + p.onReplaced.remove(this); + } + + private fun attachPagerEvents() { + val p = recyclerData.pager; + if(p is IReplacerPager<*>) { + p.onReplaced.subscribe(this) { _, newItem -> + synchronized(_pager_lock) { + val filtered = filterResults(listOf(newItem as TResult)); + if(filtered.isEmpty()) + return@subscribe; + val newItemConverted = filtered[0]; + + val toReplaceIndex = recyclerData.results.indexOfFirst { it == newItemConverted }; + if(toReplaceIndex >= 0) { + recyclerData.results[toReplaceIndex] = newItemConverted; + recyclerData.adapter.notifyItemChanged(recyclerData.adapter.childToParentPosition(toReplaceIndex)); + } + } + } + } + } + + private fun loadNextPage() { + synchronized(_pager_lock) { + val pager: TPager = recyclerData.pager ?: return; + val hasMorePages = pager.hasMorePages(); + Logger.i(TAG, "loadNextPage() hasMorePages=$hasMorePages"); + + //loadCachedPage(); + if (pager.hasMorePages()) { + setLoading(true); + _nextPageHandler.run(pager); + } + } + } + + protected fun setLoading(loading: Boolean) { + Logger.v(TAG, "setLoading loading=${loading}"); + _loading = loading; + recyclerData.adapter.setLoading(loading); + } + + companion object { + private val TAG = "FeedView"; + } + + abstract class ItemCache(val cachePager: IPager) { + abstract fun isSame(item: TResult, toCompare: TResult): Boolean; + abstract fun compareOrder(item: TResult, toCompare: TResult): Int; + } + + data class RecyclerData ( + val adapter: TAdapter, + val layoutManager: TLayoutManager, + val results: ArrayList, + val resultsUnfiltered: ArrayList = ArrayList(), + var pager: TPager? = null, + var parentPager: TPager? = null, + var loadedFeedStyle: FeedStyle = FeedStyle.UNKNOWN, + var lastLoad: OffsetDateTime = OffsetDateTime.MIN, + var lastClients: List? = null + ) where TViewHolder : RecyclerView.ViewHolder, TPager : IPager, TAdapter : RecyclerView.Adapter, TLayoutManager : LayoutManager +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HistoryFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HistoryFragment.kt new file mode 100644 index 00000000..ac1eb702 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HistoryFragment.kt @@ -0,0 +1,92 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import android.widget.EditText +import android.widget.ImageButton +import androidx.core.widget.addTextChangedListener +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.* +import com.futo.platformplayer.states.StatePlaylists +import com.futo.platformplayer.views.others.TagsView +import com.futo.platformplayer.views.adapters.HistoryListAdapter + +class HistoryFragment : MainFragment() { + override val isMainView : Boolean = true; + override val isTab: Boolean = true; + override val hasBottomBar: Boolean get() = true; + + private var _adapter: HistoryListAdapter? = null; + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = inflater.inflate(R.layout.fragment_history, container, false); + + val inputMethodManager = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager; + + val recyclerHistory = view.findViewById(R.id.recycler_history); + val clearSearch = view.findViewById(R.id.button_clear_search); + val editSearch = view.findViewById(R.id.edit_search); + var tagsView = view.findViewById(R.id.tags_text); + tagsView.setPairs(listOf( + Pair(getString(R.string.last_hour), 60L), + Pair(getString(R.string.last_24_hours), 24L * 60L), + Pair(getString(R.string.last_week), 7L * 24L * 60L), + Pair(getString(R.string.last_30_days), 30L * 24L * 60L), + Pair(getString(R.string.last_year), 365L * 30L * 24L * 60L), + Pair(getString(R.string.all_time), -1L))); + + val adapter = HistoryListAdapter(); + adapter.onClick.subscribe { v -> + val diff = v.video.duration - v.position; + val vid: Any = if (diff > 5) { v.video.withTimestamp(v.position) } else { v.video }; + navigate(vid).maximizeVideoDetail(); + editSearch.clearFocus(); + inputMethodManager.hideSoftInputFromWindow(editSearch.windowToken, 0); + }; + _adapter = adapter; + + recyclerHistory.adapter = adapter; + recyclerHistory.isSaveEnabled = false; + recyclerHistory.layoutManager = LinearLayoutManager(context); + + tagsView.onClick.subscribe { timeMinutesToErase -> + UIDialogs.showConfirmationDialog(requireContext(), getString(R.string.are_you_sure_delete_historical), { + StatePlaylists.instance.removeHistoryRange(timeMinutesToErase.second as Long); + UIDialogs.toast(view.context, timeMinutesToErase.first + " " + getString(R.string.removed)); + adapter.updateFilteredVideos(); + adapter.notifyDataSetChanged(); + }); + }; + + clearSearch.setOnClickListener { + editSearch.text.clear(); + clearSearch.visibility = View.GONE; + adapter.setQuery(""); + editSearch.clearFocus(); + inputMethodManager.hideSoftInputFromWindow(editSearch.windowToken, 0); + }; + + editSearch.addTextChangedListener { _ -> + val text = editSearch.text; + clearSearch.visibility = if (text.isEmpty()) { View.GONE } else { View.VISIBLE }; + adapter.setQuery(text.toString()); + }; + + return view; + } + + override fun onDestroyMainView() { + super.onDestroyMainView(); + _adapter?.cleanup(); + _adapter = null; + } + + companion object { + fun newInstance() = HistoryFragment().apply {} + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt new file mode 100644 index 00000000..25600b4e --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt @@ -0,0 +1,165 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.platforms.js.JSClient +import com.futo.platformplayer.api.media.structures.EmptyPager +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.engine.exceptions.ScriptExecutionException +import com.futo.platformplayer.engine.exceptions.ScriptImplementationException +import com.futo.platformplayer.fragment.mainactivity.topbar.ImportTopBarFragment +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.AnnouncementType +import com.futo.platformplayer.states.StateAnnouncement +import com.futo.platformplayer.states.StateMeta +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.views.announcements.AnnouncementView +import com.futo.platformplayer.views.FeedStyle +import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder +import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader +import com.futo.platformplayer.views.adapters.InsertedViewHolder +import java.time.OffsetDateTime +import java.util.UUID + +class HomeFragment : MainFragment() { + override val isMainView : Boolean = true; + override val isTab: Boolean = true; + override val hasBottomBar: Boolean get() = true; + + private var _view: HomeView? = null; + private var _cachedRecyclerData: FeedView.RecyclerData, LinearLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>? = null; + + override fun onShownWithView(parameter: Any?, isBack: Boolean) { + super.onShownWithView(parameter, isBack); + _view?.onShown(); + } + + override fun onResume() { + super.onResume() + _view?.onResume(); + } + + override fun onPause() { + super.onPause() + _view?.onPause(); + } + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = HomeView(this, inflater, _cachedRecyclerData); + _view = view; + return view; + } + + override fun onDestroyMainView() { + super.onDestroyMainView(); + + val view = _view; + if (view != null) { + _cachedRecyclerData = view.recyclerData; + view.cleanup(); + _view = null; + } + } + + fun setPreviewsEnabled(previewsEnabled: Boolean) { + _view?.setPreviewsEnabled(previewsEnabled); + } + + @SuppressLint("ViewConstructor") + class HomeView : ContentFeedView { + override val feedStyle: FeedStyle get() = Settings.instance.home.getHomeFeedStyle(); + + private var _announcementsView: AnnouncementView; + + private val _taskGetPager: TaskHandler>; + + constructor(fragment: HomeFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData, LinearLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>? = null) : super(fragment, inflater, cachedRecyclerData) { + _announcementsView = AnnouncementView(context).apply { + headerView.addView(AnnouncementView(context)) + }; + + _taskGetPager = TaskHandler>({ fragment.lifecycleScope }, { + StatePlatform.instance.getHomeRefresh(fragment.lifecycleScope) + }) + .success { loadedResult(it); } + .exception { + Logger.w(ChannelFragment.TAG, "Plugin failure.", it); + UIDialogs.showDialog(context, R.drawable.ic_error_pred, "Failed to get Home\nPlugin [${it.config.name}]", it.message, null, 0, + UIDialogs.Action("Ignore", {}), + UIDialogs.Action("Sources", { fragment.navigate() }, UIDialogs.ActionStyle.PRIMARY) + ); + } + .exception { + Logger.w(ChannelFragment.TAG, "Plugin failure.", it); + UIDialogs.showDialog(context, R.drawable.ic_error_pred, "Failed to get Home\nPlugin [${it.config.name}]", it.message, null, 0, + UIDialogs.Action("Ignore", {}), + UIDialogs.Action("Sources", { fragment.navigate() }, UIDialogs.ActionStyle.PRIMARY) + ); + } + .exception { + Logger.w(ChannelFragment.TAG, "Failed to load channel.", it); + UIDialogs.showGeneralRetryErrorDialog(context, "Failed to get Home", it, { + loadResults() + }); + }; + } + + fun onShown() { + val lastClients = recyclerData.lastClients; + val clients = StatePlatform.instance.getSortedEnabledClient().filter { if (it is JSClient) it.enableInHome else true }; + + val feedstyleChanged = recyclerData.loadedFeedStyle != feedStyle; + val clientsChanged = lastClients == null || lastClients.size != clients.size || !lastClients.containsAll(clients); + val outdated = recyclerData.lastLoad.getNowDiffSeconds() > 60; + Logger.i(TAG, "onShown (recyclerData.loadedFeedStyle=${recyclerData.loadedFeedStyle}, recyclerData.lastLoad=${recyclerData.lastLoad}, feedstyleChanged=$feedstyleChanged, clientsChanged=$clientsChanged, outdated=$outdated)") + + if(feedstyleChanged || outdated || clientsChanged) { + recyclerData.lastLoad = OffsetDateTime.now(); + recyclerData.loadedFeedStyle = feedStyle; + recyclerData.lastClients = clients; + loadResults(); + } else { + setLoading(false); + } + } + + override fun reload() { + loadResults(); + } + + override fun filterResults(contents: List): List { + return contents.filter { it !is IPlatformVideo || !StateMeta.instance.isVideoHidden(it.url) }; + } + + private fun loadResults() { + setLoading(true); + _taskGetPager.run(true); + } + private fun loadedResult(pager : IPager) { + if (pager is EmptyPager) { + StateAnnouncement.instance.registerAnnouncement(UUID.randomUUID().toString(), "No home available", "No home page is available, please check if you are connected to the internet and refresh.", AnnouncementType.SESSION); + } + + Logger.i(TAG, "Got new home pager ${pager}"); + finishRefreshLayoutLoader(); + setLoading(false); + setPager(pager); + } + } + + companion object { + val TAG = "HomeFragment"; + + fun newInstance() = HomeFragment().apply {} + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportPlaylistsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportPlaylistsFragment.kt new file mode 100644 index 00000000..8c503318 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportPlaylistsFragment.kt @@ -0,0 +1,203 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.annotation.SuppressLint +import android.graphics.drawable.Animatable +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.* +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.fragment.mainactivity.topbar.ImportTopBarFragment +import com.futo.platformplayer.views.AnyAdapterView +import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.Playlist +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StatePlaylists +import com.futo.platformplayer.views.adapters.viewholders.ImportPlaylistsViewHolder +import com.futo.platformplayer.views.adapters.viewholders.SelectablePlaylist + +class ImportPlaylistsFragment : MainFragment() { + override val isMainView : Boolean = true; + override val isTab: Boolean = true; + override val hasBottomBar: Boolean get() = true; + + private var _view: ImportPlaylistsView? = null; + + override fun onShownWithView(parameter: Any?, isBack: Boolean) { + super.onShownWithView(parameter, isBack); + _view?.onShown(parameter, isBack); + } + + override fun onHide() { + super.onHide(); + + val tb = this.topBar as ImportTopBarFragment?; + tb?.onImport?.remove(this); + } + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = ImportPlaylistsView(this, inflater); + _view = view; + return view; + } + + override fun onDestroyMainView() { + super.onDestroyMainView(); + _view?.cleanup(); + _view = null; + } + + @SuppressLint("ViewConstructor") + class ImportPlaylistsView : LinearLayout { + private val _fragment: ImportPlaylistsFragment; + + private var _spinner: ImageView; + private var _textSelectDeselectAll: TextView; + private var _textNothingToImport: TextView; + private var _textCounter: TextView; + private var _adapterView: AnyAdapterView; + private var _links: List = listOf(); + private val _items: ArrayList = arrayListOf(); + private var _currentLoadIndex = 0; + + private var _taskLoadPlaylist: TaskHandler; + + constructor(fragment: ImportPlaylistsFragment, inflater: LayoutInflater) : super(inflater.context) { + _fragment = fragment; + inflater.inflate(R.layout.fragment_import, this); + + _textNothingToImport = findViewById(R.id.nothing_to_import); + _textSelectDeselectAll = findViewById(R.id.text_select_deselect_all); + _textCounter = findViewById(R.id.text_select_counter); + _spinner = findViewById(R.id.channel_loader); + + _adapterView = findViewById(R.id.recycler_import).asAny( _items) { + it.onSelectedChange.subscribe { c -> + updateSelected(); + }; + }; + + _textSelectDeselectAll.setOnClickListener { + val itemsSelected = _items.count { i -> i.selected }; + if (itemsSelected > 0) { + for (i in _items) { + i.selected = false; + } + } else { + for (i in _items) { + i.selected = true; + } + } + + _adapterView.adapter.notifyContentChanged(); + updateSelected(); + }; + + setLoading(false); + + _taskLoadPlaylist = TaskHandler({fragment.lifecycleScope}, { link -> StatePlatform.instance.getPlaylist(link).toPlaylist(); }) + .success { + if (it != null) { + _items.add(SelectablePlaylist(it)); + _adapterView.adapter.notifyItemInserted(_items.size - 1); + } + + loadNext(); + }.exceptionWithParameter { ex, para -> + //setLoading(false); + Logger.w(ChannelFragment.TAG, "Failed to load results.", ex); + UIDialogs.toast(context, "Failed to fetch\n${para}", false) + //UIDialogs.showDataRetryDialog(layoutInflater, { load(); }); + loadNext(); + }; + } + + fun cleanup() { + _taskLoadPlaylist.cancel(); + } + + fun onShown(parameter: Any ?, isBack: Boolean) { + updateSelected(); + + val itemsRemoved = _items.size; + if (itemsRemoved > 0) { + _items.clear(); + _adapterView.adapter.notifyItemRangeRemoved(0, itemsRemoved); + } + + _links = (parameter as Array).toList(); + _currentLoadIndex = 0; + if (_links.isNotEmpty()) { + load(); + _textNothingToImport.visibility = View.GONE; + } else { + setLoading(false); + _textNothingToImport.visibility = View.VISIBLE; + } + + val tb = _fragment.topBar as ImportTopBarFragment?; + tb?.let { + it.title = "Import Playlists"; + it.onImport.subscribe(this) { + val playlistsToImport = _items.filter { i -> i.selected }.toList(); + for (playlistToImport in playlistsToImport) { + StatePlaylists.instance.createOrUpdatePlaylist(playlistToImport.playlist); + } + + UIDialogs.toast("${playlistsToImport.size} playlists imported."); + _fragment.closeSegment(); + }; + } + } + + private fun load() { + setLoading(true); + _taskLoadPlaylist.run(_links[_currentLoadIndex]); + } + + private fun loadNext() { + _currentLoadIndex++; + if (_currentLoadIndex < _links.size) { + load(); + } else { + setLoading(false); + } + } + + private fun updateSelected() { + val itemsSelected = _items.count { i -> i.selected }; + if (itemsSelected > 0) { + _textSelectDeselectAll.text = context.getString(R.string.deselect_all); + _textCounter.text = "$itemsSelected out of ${_items.size} selected"; + (_fragment.topBar as ImportTopBarFragment?)?.setImportEnabled(true); + } else { + _textSelectDeselectAll.text = context.getString(R.string.select_all); + _textCounter.text = ""; + (_fragment.topBar as ImportTopBarFragment?)?.setImportEnabled(false); + } + } + + private fun setLoading(isLoading: Boolean) { + if(isLoading){ + (_spinner.drawable as Animatable?)?.start(); + _spinner.visibility = View.VISIBLE; + } + else { + _spinner.visibility = View.GONE; + (_spinner.drawable as Animatable?)?.stop(); + } + } + } + + companion object { + val TAG = "ImportSubscriptionsFragment"; + fun newInstance() = ImportPlaylistsFragment().apply {} + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportSubscriptionsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportSubscriptionsFragment.kt new file mode 100644 index 00000000..d813a0ac --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportSubscriptionsFragment.kt @@ -0,0 +1,201 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.annotation.SuppressLint +import android.graphics.drawable.Animatable +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.fragment.mainactivity.topbar.ImportTopBarFragment +import com.futo.platformplayer.views.AnyAdapterView +import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny +import com.futo.platformplayer.views.adapters.viewholders.ImportSubscriptionViewHolder +import com.futo.platformplayer.views.adapters.viewholders.SelectableIPlatformChannel +import com.futo.platformplayer.states.StateSubscriptions +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StatePlatform + +class ImportSubscriptionsFragment : MainFragment() { + override val isMainView : Boolean = true; + override val isTab: Boolean = true; + override val hasBottomBar: Boolean get() = true; + + private var _view: ImportSubscriptionsView? = null; + + + override fun onShownWithView(parameter: Any?, isBack: Boolean) { + super.onShown(parameter, isBack); + _view?.onShown(parameter, isBack); + } + + override fun onHide() { + super.onHide(); + + val tb = this.topBar as ImportTopBarFragment?; + tb?.onImport?.remove(this); + } + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = ImportSubscriptionsView(this, inflater); + _view = view; + return view; + } + + override fun onDestroyMainView() { + super.onDestroyMainView(); + _view?.cleanup(); + _view = null; + } + + @SuppressLint("ViewConstructor") + class ImportSubscriptionsView : LinearLayout { + private val _fragment: ImportSubscriptionsFragment; + + private var _spinner: ImageView; + private var _textSelectDeselectAll: TextView; + private var _textNothingToImport: TextView; + private var _textCounter: TextView; + private var _adapterView: AnyAdapterView; + private var _links: List = listOf(); + private val _items: ArrayList = arrayListOf(); + private var _currentLoadIndex = 0; + + private var _taskLoadChannel: TaskHandler; + + constructor(fragment: ImportSubscriptionsFragment, inflater: LayoutInflater) : super(inflater.context) { + _fragment = fragment; + inflater.inflate(R.layout.fragment_import, this); + + _textNothingToImport = findViewById(R.id.nothing_to_import); + _textSelectDeselectAll = findViewById(R.id.text_select_deselect_all); + _textCounter = findViewById(R.id.text_select_counter); + _spinner = findViewById(R.id.channel_loader); + + _adapterView = findViewById(R.id.recycler_import).asAny( _items) { + it.onSelectedChange.subscribe { c -> + updateSelected(); + }; + }; + + _textSelectDeselectAll.setOnClickListener { + val itemsSelected = _items.count { i -> i.selected }; + if (itemsSelected > 0) { + for (i in _items) { + i.selected = false; + } + } else { + for (i in _items) { + i.selected = true; + } + } + + _adapterView.adapter.notifyContentChanged(); + updateSelected(); + }; + + setLoading(false); + + _taskLoadChannel = TaskHandler({_fragment.lifecycleScope}, { link -> + val channel: IPlatformChannel = StatePlatform.instance.getChannelLive(link, false); + return@TaskHandler channel; + }).success { + _items.add(SelectableIPlatformChannel(it)); + _adapterView.adapter.notifyItemInserted(_items.size - 1); + loadNext(); + }.exceptionWithParameter { ex, para -> + //setLoading(false); + Logger.w(ChannelFragment.TAG, "Failed to load results.", ex); + UIDialogs.toast(context, "Failed to fetch\n${para}", false) + //UIDialogs.showDataRetryDialog(layoutInflater, { load(); }); + loadNext(); + }; + } + + fun cleanup() { + _taskLoadChannel.cancel(); + } + + fun onShown(parameter: Any ?, isBack: Boolean) { + updateSelected(); + + val itemsRemoved = _items.size; + _items.clear(); + _adapterView?.adapter?.notifyItemRangeRemoved(0, itemsRemoved); + + _links = (parameter as List).filter { i -> !StateSubscriptions.instance.isSubscribed(i) }.toList(); + _currentLoadIndex = 0; + if (_links.isNotEmpty()) { + load(); + _textNothingToImport.visibility = View.GONE; + } else { + setLoading(false); + _textNothingToImport.visibility = View.VISIBLE; + } + + val tb = _fragment.topBar as ImportTopBarFragment?; + tb?.let { + it.title = "Import Subscriptions"; + it.onImport.subscribe(this) { + val subscriptionsToImport = _items.filter { i -> i.selected }.toList(); + for (subscriptionToImport in subscriptionsToImport) { + StateSubscriptions.instance.addSubscription(subscriptionToImport.channel); + } + + UIDialogs.toast("${subscriptionsToImport.size} subscriptions imported."); + _fragment.closeSegment(); + }; + } + } + + private fun load() { + setLoading(true); + _taskLoadChannel.run(_links[_currentLoadIndex]); + } + + private fun loadNext() { + _currentLoadIndex++; + if (_currentLoadIndex < _links.size) { + load(); + } else { + setLoading(false); + } + } + + private fun updateSelected() { + val itemsSelected = _items.count { i -> i.selected }; + if (itemsSelected > 0) { + _textSelectDeselectAll.text = context.getString(R.string.deselect_all); + _textCounter.text = "$itemsSelected out of ${_items.size} selected"; + (_fragment.topBar as ImportTopBarFragment?)?.setImportEnabled(true); + } else { + _textSelectDeselectAll.text = context.getString(R.string.select_all); + _textCounter.text = ""; + (_fragment.topBar as ImportTopBarFragment?)?.setImportEnabled(false); + } + } + + private fun setLoading(isLoading: Boolean) { + if(isLoading){ + (_spinner.drawable as Animatable?)?.start(); + _spinner.visibility = View.VISIBLE; + } + else { + _spinner.visibility = View.GONE; + (_spinner.drawable as Animatable?)?.stop(); + } + } + } + + companion object { + val TAG = "ImportSubscriptionsFragment"; + fun newInstance() = ImportSubscriptionsFragment().apply {} + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/MainFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/MainFragment.kt new file mode 100644 index 00000000..e9e1b0b6 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/MainFragment.kt @@ -0,0 +1,99 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.futo.platformplayer.activities.MainActivity +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.fragment.mainactivity.MainActivityFragment +import com.futo.platformplayer.fragment.mainactivity.topbar.TopFragment +import com.futo.platformplayer.listeners.OrientationManager + +abstract class MainFragment : MainActivityFragment() { + open val isMainView: Boolean = false; + open val isTab: Boolean = false; + open val isOverlay: Boolean = false; + open val isHistory: Boolean = true; + open val hasBottomBar: Boolean = true; + var topBar: TopFragment? = null; + + val onShownEvent = Event1(); + val onHideEvent = Event1(); + val onCloseEvent = Event1(); + + private val _fragmentLock = Object(); + private var _mainView: View? = null; + private var _lastOnShownParameters: Pair? = null; + + open fun onShown(parameter: Any?, isBack: Boolean) { + onShownEvent.emit(this); + + if (_mainView == null) { + synchronized(_fragmentLock) { + _lastOnShownParameters = Pair(parameter, isBack); + } + } else { + synchronized(_fragmentLock) { + _lastOnShownParameters = null; + } + + onShownWithView(parameter, isBack); + } + } + + open fun onShownWithView(parameter: Any?, isBack: Boolean) { + + } + + open fun onOrientationChanged(orientation: OrientationManager.Orientation) { + + } + + open fun onBackPressed(): Boolean { + return false; + } + + open fun onHide() { + onHideEvent.emit(this); + } + + final override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = onCreateMainView(inflater, container, savedInstanceState); + _mainView = view; + + val lastOnShownParameters = synchronized(_fragmentLock) { + val value = _lastOnShownParameters; + _lastOnShownParameters = null; + return@synchronized value; + }; + + if (lastOnShownParameters != null) + onShownWithView(lastOnShownParameters.first, lastOnShownParameters.second); + + return view; + } + + final override fun onDestroyView() { + super.onDestroyView(); + onDestroyMainView(); + _mainView = null; + } + + abstract fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View; + open fun onDestroyMainView() {} + + override fun onDestroy() { + super.onDestroy(); + onShownEvent.clear(); + onHideEvent.clear(); + onCloseEvent.clear(); + } + + fun close(withNavigate: Boolean = false) { + isValidMainActivity(); + onCloseEvent.emit(this); + if (withNavigate) + (activity as MainActivity).closeSegment(this); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistFragment.kt new file mode 100644 index 00000000..5d623461 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistFragment.kt @@ -0,0 +1,365 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.annotation.SuppressLint +import android.graphics.drawable.Animatable +import android.os.Bundle +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.app.ShareCompat +import androidx.core.view.setPadding +import androidx.lifecycle.lifecycleScope +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist +import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.downloads.VideoDownload +import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFragment +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.Playlist +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StateDownloads +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StatePlayer +import com.futo.platformplayer.states.StatePlaylists +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuTextInput +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class PlaylistFragment : MainFragment() { + override val isMainView : Boolean = true; + override val isTab: Boolean = true; + override val hasBottomBar: Boolean get() = true; + + private var _view: PlaylistView? = null; + + override fun onShownWithView(parameter: Any?, isBack: Boolean) { + super.onShownWithView(parameter, isBack); + _view?.onShown(parameter, isBack); + } + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = PlaylistView(this, inflater); + _view = view; + return view; + } + + override fun onDestroyMainView() { + super.onDestroyMainView(); + _view = null; + } + + override fun onResume() { + super.onResume() + _view?.onResume(); + } + + override fun onPause() { + super.onPause() + _view?.onPause(); + } + + @SuppressLint("ViewConstructor") + class PlaylistView : VideoListEditorView { + private val _fragment: PlaylistFragment; + + private var _playlist: Playlist? = null; + private var _remotePlaylist: IPlatformPlaylistDetails? = null; + private var _editPlaylistNameInput: SlideUpMenuTextInput? = null; + private var _editPlaylistOverlay: SlideUpMenuOverlay? = null; + private var _url: String? = null; + + private val _taskLoadPlaylist: TaskHandler; + + constructor(fragment: PlaylistFragment, inflater: LayoutInflater) : super(inflater) { + _fragment = fragment; + + val nameInput = SlideUpMenuTextInput(context, "Name"); + val editPlaylistOverlay = SlideUpMenuOverlay(context, overlayContainer, "Edit playlist", "Ok", false, nameInput); + + _buttonDownload.visibility = View.VISIBLE; + editPlaylistOverlay.onOK.subscribe { + val text = nameInput.text; + if (text.isBlank()) { + return@subscribe; + } + + setName(text); + _playlist?.let { + it.name = text; + StatePlaylists.instance.createOrUpdatePlaylist(it); + } + + editPlaylistOverlay.hide(); + nameInput.deactivate(); + nameInput.clear(); + } + + editPlaylistOverlay.onCancel.subscribe { + nameInput.deactivate(); + nameInput.clear(); + }; + + _editPlaylistOverlay = editPlaylistOverlay; + _editPlaylistNameInput = nameInput; + + setOnShare { + val playlist = _playlist ?: return@setOnShare; + val reconstruction = StatePlaylists.instance.playlistStore.getReconstructionString(playlist); + + UISlideOverlays.showOverlay(overlayContainer, "Playlist [${playlist.name}]", null, {}, + SlideUpMenuItem(context, R.drawable.ic_list, "Share as Text", "Share as a list of video urls", 1, { + _fragment.startActivity(ShareCompat.IntentBuilder(context) + .setType("text/plain") + .setText(reconstruction) + .intent); + }), + SlideUpMenuItem(context, R.drawable.ic_move_up, "Share as Import", "Share as a import file for Grayjay", 2, { + val shareUri = StatePlaylists.instance.createPlaylistShareJsonUri(context, playlist); + _fragment.startActivity(ShareCompat.IntentBuilder(context) + .setType("application/json") + .setStream(shareUri) + .intent); + }) + ); + }; + + _taskLoadPlaylist = TaskHandler( + StateApp.instance.scopeGetter, + { + return@TaskHandler StatePlatform.instance.getPlaylist(it); + }) + .success { + setLoading(false); + _remotePlaylist = it; + setName(it.name); + setVideos(it.contents.getResults(), false); + setVideoCount(it.videoCount); + //TODO: Implement support for pagination + } + .exception { + Logger.w(TAG, "Failed to load playlist.", it); + val c = context ?: return@exception; + UIDialogs.showGeneralRetryErrorDialog(c, "Failed to load playlist", it, ::fetchPlaylist); + }; + } + + fun onShown(parameter: Any ?, isBack: Boolean) { + _taskLoadPlaylist.cancel(); + + if (parameter is Playlist?) { + _playlist = parameter; + _remotePlaylist = null; + _url = null; + + if(parameter != null) { + setName(parameter.name); + setVideos(parameter.videos, true); + setVideoCount(parameter.videos.size); + setButtonDownloadVisible(true); + setButtonEditVisible(true); + } else { + setName(null); + setVideos(null, false); + setVideoCount(-1); + setButtonDownloadVisible(false); + setButtonEditVisible(false); + } + + //TODO: Do I have to remove the showConvertPlaylistButton(); button here? + } else if (parameter is IPlatformPlaylist) { + _playlist = null; + _remotePlaylist = null; + _url = parameter.url; + + setVideoCount(parameter.videoCount); + setName(parameter.name); + setVideos(null, false); + setButtonDownloadVisible(false); + setButtonEditVisible(false); + + fetchPlaylist(); + showConvertPlaylistButton(); + } else if (parameter is String) { + _playlist = null; + _remotePlaylist = null; + _url = parameter; + + setName(null); + setVideos(null, false); + setVideoCount(-1); + setButtonDownloadVisible(false); + setButtonEditVisible(false); + + fetchPlaylist(); + showConvertPlaylistButton(); + } + + updateDownloadState(); + } + + fun onResume() { + StateDownloads.instance.onDownloadsChanged.subscribe(this) { + _fragment.lifecycleScope.launch(Dispatchers.Main) { + try { + updateDownloadState(); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to update download state onDownloadedChanged.") + } + } + }; + StateDownloads.instance.onDownloadedChanged.subscribe(this) { + _fragment.lifecycleScope.launch(Dispatchers.Main) { + try { + updateDownloadState(); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to update download state onDownloadedChanged.") + } + } + }; + } + + fun onPause() { + StateDownloads.instance.onDownloadsChanged.remove(this); + StateDownloads.instance.onDownloadedChanged.remove(this); + } + + private fun showConvertPlaylistButton() { + _fragment.topBar?.assume()?.setMenuItems(arrayListOf(Pair(R.drawable.ic_copy) { + val remotePlaylist = _remotePlaylist; + if (remotePlaylist == null) { + UIDialogs.toast("Please wait for playlist to finish loading"); + return@Pair; + } + + setLoading(true); + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { + try { + StatePlaylists.instance.playlistStore.save(remotePlaylist.toPlaylist()); + + withContext(Dispatchers.Main) { + setLoading(false); + UIDialogs.toast("Playlist copied as local playlist"); + } + } catch (e: Throwable) { + withContext(Dispatchers.Main) { + setLoading(false); + } + + throw e; + } + } + })); + } + + private fun fetchPlaylist() { + Logger.i(TAG, "fetchPlaylist") + + val url = _url; + if (!url.isNullOrBlank()) { + setLoading(true); + _taskLoadPlaylist.run(url); + } + } + + private fun updateDownloadState() { + val playlist = _playlist ?: return; + val isDownloading = StateDownloads.instance.getDownloading().any { it.groupType == VideoDownload.GROUP_PLAYLIST && it.groupID == playlist.id }; + val isDownloaded = StateDownloads.instance.isPlaylistCached(playlist.id); + + val dp10 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, resources.displayMetrics); + + if(isDownloaded && !isDownloading) + _buttonDownload.setBackgroundResource(R.drawable.background_button_round_green); + else + _buttonDownload.setBackgroundResource(R.drawable.background_button_round); + + if(isDownloading) { + _buttonDownload.setImageResource(R.drawable.ic_loader_animated); + _buttonDownload.drawable.assume { it.start() }; + _buttonDownload.setOnClickListener { + StateDownloads.instance.deleteCachedPlaylist(playlist.id); + } + } + else if(isDownloaded) { + _buttonDownload.setImageResource(R.drawable.ic_download_off); + _buttonDownload.setOnClickListener { + StateDownloads.instance.deleteCachedPlaylist(playlist.id); + } + } + else { + _buttonDownload.setImageResource(R.drawable.ic_download); + _buttonDownload.setOnClickListener { + UISlideOverlays.showDownloadPlaylistOverlay(playlist, overlayContainer); + } + } + _buttonDownload.setPadding(dp10.toInt()); + } + + override fun canEdit(): Boolean { return _playlist != null; } + + override fun onEditClick() { + _editPlaylistNameInput?.activate(); + _editPlaylistOverlay?.show(); + } + + override fun onPlayAllClick() { + val playlist = _playlist; + val remotePlaylist = _remotePlaylist; + if (playlist != null) { + StatePlayer.instance.setPlaylist(playlist, focus = true); + } else if (remotePlaylist != null) { + StatePlayer.instance.setPlaylist(remotePlaylist, focus = true, shuffle = false); + } + } + + override fun onShuffleClick() { + val playlist = _playlist; + val remotePlaylist = _remotePlaylist; + if (playlist != null) { + StatePlayer.instance.setPlaylist(playlist, focus = true, shuffle = true); + } else if (remotePlaylist != null) { + StatePlayer.instance.setPlaylist(remotePlaylist, focus = true, shuffle = true); + } + } + + override fun onVideoOrderChanged(videos: List) { + val playlist = _playlist ?: return; + playlist.videos = ArrayList(videos.map { it as SerializedPlatformVideo }); + StatePlaylists.instance.createOrUpdatePlaylist(playlist); + } + override fun onVideoRemoved(video: IPlatformVideo) { + val playlist = _playlist ?: return; + playlist.videos = ArrayList(playlist.videos.filter { it != video }); + StatePlaylists.instance.createOrUpdatePlaylist(playlist); + } + override fun onVideoClicked(video: IPlatformVideo) { + val playlist = _playlist; + val remotePlaylist = _remotePlaylist; + if (playlist != null) { + val index = playlist.videos.indexOf(video); + if (index == -1) + return; + + StatePlayer.instance.setPlaylist(playlist, index, true); + } else if (remotePlaylist != null) { + val index = remotePlaylist.contents.getResults().indexOf(video); + if (index == -1) + return; + + StatePlayer.instance.setPlaylist(remotePlaylist, index, true); + } + } + } + + companion object { + private const val TAG = "PlaylistFragment"; + fun newInstance() = PlaylistFragment().apply {} + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistSearchResultsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistSearchResultsFragment.kt new file mode 100644 index 00000000..58020c07 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistSearchResultsFragment.kt @@ -0,0 +1,120 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.Settings +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment +import com.futo.platformplayer.views.FeedStyle + +class PlaylistSearchResultsFragment : MainFragment() { + override val isMainView : Boolean = true; + override val isTab: Boolean = false; + override val hasBottomBar: Boolean get() = true; + + private var _view: PlaylistSearchResultsView? = null; + + override fun onShownWithView(parameter: Any?, isBack: Boolean) { + super.onShownWithView(parameter, isBack); + _view?.onShown(parameter, isBack); + } + + override fun onResume() { + super.onResume() + _view?.onResume(); + } + + override fun onPause() { + super.onPause() + _view?.onPause(); + } + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = PlaylistSearchResultsView(this, inflater); + _view = view; + return view; + } + + override fun onDestroyMainView() { + super.onDestroyMainView(); + _view?.cleanup(); + _view = null; + } + + @SuppressLint("ViewConstructor") + class PlaylistSearchResultsView : ContentFeedView { + override val feedStyle: FeedStyle get() = Settings.instance.search.getSearchFeedStyle(); + + private var _query: String? = null; + + private val _taskSearch: TaskHandler>; + constructor(fragment: PlaylistSearchResultsFragment, inflater: LayoutInflater) : super(fragment, inflater) { + _taskSearch = TaskHandler>({fragment.lifecycleScope}, { query -> StatePlatform.instance.searchPlaylist(query) }) + .success { loadedResult(it); } + .exception { + Logger.w(ChannelFragment.TAG, "Failed to load results.", it); + UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() }); + } + } + + override fun cleanup() { + super.cleanup(); + _taskSearch.cancel(); + } + + fun onShown(parameter: Any?, isBack: Boolean) { + if(parameter is String) { + if(!isBack) { + setQuery(parameter); + + fragment.topBar?.apply { + if (this is SearchTopBarFragment) { + setText(parameter); + onSearch.subscribe(this) { + setQuery(it); + }; + } + } + } + } + } + + override fun reload() { + loadResults(); + } + + private fun setQuery(query: String) { + clearResults(); + _query = query; + loadResults(); + } + + private fun loadResults() { + val query = _query; + if (query.isNullOrBlank()) { + return; + } + + setLoading(true); + _taskSearch.run(query); + } + private fun loadedResult(pager: IPager) { + finishRefreshLayoutLoader(); + setLoading(false); + setPager(pager); + } + } + + companion object { + fun newInstance() = PlaylistSearchResultsFragment().apply {} + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistsFragment.kt new file mode 100644 index 00000000..622e98fe --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistsFragment.kt @@ -0,0 +1,180 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.annotation.SuppressLint +import android.os.Bundle +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageButton +import android.widget.LinearLayout +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.states.StatePlayer +import com.futo.platformplayer.states.StatePlaylists +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.assume +import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFragment +import com.futo.platformplayer.models.Playlist +import com.futo.platformplayer.models.SearchType +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.views.adapters.* +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuTextInput +import com.google.android.material.appbar.AppBarLayout +import kotlin.collections.ArrayList + + +class PlaylistsFragment : MainFragment() { + override val isMainView : Boolean = true; + override val isTab: Boolean = true; + override val hasBottomBar: Boolean get() = true; + + private var _view: PlaylistsView? = null; + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = PlaylistsView(this, inflater); + _view = view; + return view; + } + + override fun onDestroyMainView() { + super.onDestroyMainView(); + _view?.cleanup(); + _view = null; + } + + override fun onShownWithView(parameter: Any?, isBack: Boolean) { + super.onShownWithView(parameter, isBack); + _view?.onShown(parameter, isBack); + } + + @SuppressLint("ViewConstructor") + class PlaylistsView : LinearLayout { + private val _fragment: PlaylistsFragment; + + var watchLater: ArrayList = arrayListOf(); + var playlists: ArrayList = arrayListOf(); + private var _appBar: AppBarLayout; + private var _adapterWatchLater: VideoListHorizontalAdapter; + private var _adapterPlaylist: PlaylistsAdapter; + private var _layoutWatchlist: ConstraintLayout; + + constructor(fragment: PlaylistsFragment, inflater: LayoutInflater) : super(inflater.context) { + _fragment = fragment; + inflater.inflate(R.layout.fragment_playlists, this); + + watchLater = ArrayList(); + playlists = ArrayList(); + + val recyclerWatchLater = findViewById(R.id.recycler_watch_later); + + _adapterWatchLater = VideoListHorizontalAdapter(watchLater); + recyclerWatchLater.adapter = _adapterWatchLater; + recyclerWatchLater.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false); + + _adapterWatchLater.onClick.subscribe { v -> + val index = watchLater.indexOf(v); + if (index == -1) { + return@subscribe; + } + + StatePlayer.instance.setQueueWithPosition(watchLater, StatePlayer.TYPE_WATCHLATER, index, true); + }; + + val recyclerPlaylists = findViewById(R.id.recycler_playlists); + _adapterPlaylist = PlaylistsAdapter(playlists, inflater, context.getString(R.string.confirm_delete_playlist)); + recyclerPlaylists.adapter = _adapterPlaylist; + recyclerPlaylists.layoutManager = LinearLayoutManager(context); + + val nameInput = SlideUpMenuTextInput(context, "Name"); + val addPlaylistOverlay = SlideUpMenuOverlay(context, findViewById(R.id.overlay_create_playlist), "Create new playlist", "Ok", false, nameInput); + + _adapterPlaylist.onClick.subscribe { p -> _fragment.navigate(p); }; + _adapterPlaylist.onPlay.subscribe { p -> + StatePlayer.instance.setPlaylist(p, 0, true); + }; + + addPlaylistOverlay.onOK.subscribe { + val text = nameInput.text; + if (text.isBlank()) { + return@subscribe; + } + + val playlist = Playlist(text, arrayListOf()); + playlists.add(0, playlist); + StatePlaylists.instance.createOrUpdatePlaylist(playlist); + + _adapterPlaylist.notifyItemInserted(0); + addPlaylistOverlay.hide(); + nameInput.deactivate(); + nameInput.clear(); + }; + + addPlaylistOverlay.onCancel.subscribe { + nameInput.deactivate(); + nameInput.clear(); + }; + + val buttonCreatePlaylist = findViewById(R.id.button_create_playlist); + buttonCreatePlaylist.setOnClickListener { + addPlaylistOverlay.show(); + nameInput.activate(); + }; + + _appBar = findViewById(R.id.app_bar); + _layoutWatchlist = findViewById(R.id.layout_watchlist); + + findViewById(R.id.text_view_all).setOnClickListener { _fragment.navigate("Watch Later"); }; + StatePlaylists.instance.onWatchLaterChanged.subscribe(this) { + updateWatchLater(); + }; + } + + fun cleanup() { + StatePlaylists.instance.onWatchLaterChanged.remove(this); + } + + fun onShown(parameter: Any?, isBack: Boolean) { + playlists.clear() + playlists.addAll(StatePlaylists.instance.getPlaylists().sortedByDescending { maxOf(it.datePlayed, it.dateUpdate, it.dateCreation) }); + _adapterPlaylist.notifyDataSetChanged(); + + updateWatchLater(); + } + + private fun updateWatchLater() { + val watchList = StatePlaylists.instance.getWatchLater(); + if (watchList.isNotEmpty()) { + _layoutWatchlist.visibility = View.VISIBLE; + + _appBar.let { appBar -> + val layoutParams: CoordinatorLayout.LayoutParams = appBar.layoutParams as CoordinatorLayout.LayoutParams; + layoutParams.height = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 230.0f, resources.displayMetrics).toInt(); + appBar.layoutParams = layoutParams; + } + } else { + _layoutWatchlist.visibility = View.GONE; + + _appBar.let { appBar -> + val layoutParams: CoordinatorLayout.LayoutParams = appBar.layoutParams as CoordinatorLayout.LayoutParams; + layoutParams.height = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 25.0f, resources.displayMetrics).toInt(); + appBar.layoutParams = layoutParams; + }; + } + + watchLater.clear(); + watchLater.addAll(StatePlaylists.instance.getWatchLater()); + _adapterWatchLater.notifyDataSetChanged(); + } + } + + companion object { + fun newInstance() = PlaylistsFragment().apply {} + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt new file mode 100644 index 00000000..6441c5fa --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt @@ -0,0 +1,710 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.content.Context +import android.content.Intent +import android.graphics.drawable.Animatable +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.ViewPropertyAnimator +import android.widget.FrameLayout +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.children +import androidx.lifecycle.lifecycleScope +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.models.Thumbnails +import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment +import com.futo.platformplayer.api.media.models.post.IPlatformPost +import com.futo.platformplayer.api.media.models.post.IPlatformPostDetails +import com.futo.platformplayer.api.media.models.ratings.IRating +import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes +import com.futo.platformplayer.api.media.models.ratings.RatingLikes +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.dp +import com.futo.platformplayer.fixHtmlWhitespace +import com.futo.platformplayer.images.GlideHelper.Companion.crossfade +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.polycentric.PolycentricCache +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StatePolycentric +import com.futo.platformplayer.toHumanNowDiffString +import com.futo.platformplayer.toHumanNumber +import com.futo.platformplayer.views.comments.AddCommentView +import com.futo.platformplayer.views.segments.CommentsList +import com.futo.platformplayer.views.others.CreatorThumbnail +import com.futo.platformplayer.views.platform.PlatformIndicator +import com.futo.platformplayer.views.subscriptions.SubscribeButton +import com.futo.platformplayer.views.others.Toggle +import com.futo.platformplayer.views.adapters.PreviewPostView +import com.futo.platformplayer.views.overlays.RepliesOverlay +import com.futo.platformplayer.views.pills.PillRatingLikesDislikes +import com.futo.polycentric.core.ApiMethods +import com.futo.polycentric.core.ContentType +import com.futo.polycentric.core.Models +import com.futo.polycentric.core.Opinion +import com.google.android.flexbox.FlexboxLayout +import com.google.android.material.imageview.ShapeableImageView +import com.google.android.material.shape.CornerFamily +import com.google.android.material.shape.ShapeAppearanceModel +import com.google.protobuf.ByteString +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import userpackage.Protocol +import java.lang.Integer.min + +class PostDetailFragment : MainFragment { + override val isMainView: Boolean = true; + override val isTab: Boolean = true; + override val hasBottomBar: Boolean get() = true; + + private var _viewDetail: PostDetailView? = null; + + constructor() : super() { } + + override fun onBackPressed(): Boolean { + return false; + } + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = PostDetailView(inflater.context).applyFragment(this); + _viewDetail = view; + return view; + } + + override fun onDestroyMainView() { + super.onDestroyMainView(); + _viewDetail?.onDestroy(); + _viewDetail = null; + } + + override fun onShownWithView(parameter: Any?, isBack: Boolean) { + super.onShownWithView(parameter, isBack); + + if (parameter is IPlatformPostDetails) { + _viewDetail?.clear(); + _viewDetail?.setPostDetails(parameter); + } else if (parameter is IPlatformPost) { + _viewDetail?.setPostOverview(parameter); + } else if(parameter is String) { + _viewDetail?.setPostUrl(parameter); + } + } + + private class PostDetailView : ConstraintLayout { + private lateinit var _fragment: PostDetailFragment; + private var _url: String? = null; + private var _isLoading = false; + private var _post: IPlatformPostDetails? = null; + private var _postOverview: IPlatformPost? = null; + private var _polycentricProfile: PolycentricCache.CachedPolycentricProfile? = null; + private var _version = 0; + private var _isRepliesVisible: Boolean = false; + private var _repliesAnimator: ViewPropertyAnimator? = null; + + private val _creatorThumbnail: CreatorThumbnail; + private val _buttonSubscribe: SubscribeButton; + private val _channelName: TextView; + private val _channelMeta: TextView; + private val _textTitle: TextView; + private val _textMeta: TextView; + private val _textContent: TextView; + private val _platformIndicator: PlatformIndicator; + private val _buttonShare: ImageButton; + + private val _buttonSupport: LinearLayout; + private val _buttonStore: LinearLayout; + private val _layoutMonetization: LinearLayout; + + private val _layoutRating: LinearLayout; + private val _imageLikeIcon: ImageView; + private val _textLikes: TextView; + private val _imageDislikeIcon: ImageView; + private val _textDislikes: TextView; + + private val _textComments: TextView; + private val _textCommentType: TextView; + private val _addCommentView: AddCommentView; + private val _toggleCommentType: Toggle; + + private val _rating: PillRatingLikesDislikes; + + private val _layoutLoadingOverlay: FrameLayout; + private val _imageLoader: ImageView; + + private val _imageActive: ImageView; + private val _layoutThumbnails: FlexboxLayout; + + private val _repliesOverlay: RepliesOverlay; + + private val _commentsList: CommentsList; + + private val _taskLoadPost = if(!isInEditMode) TaskHandler( + StateApp.instance.scopeGetter, + { + val result = StatePlatform.instance.getContentDetails(it).await(); + if(result !is IPlatformPostDetails) + throw IllegalStateException("Expected media content, found ${result.contentType}"); + return@TaskHandler result; + }) + .success { setPostDetails(it) } + .exception { + Logger.w(ChannelFragment.TAG, "Failed to load post.", it); + UIDialogs.showGeneralRetryErrorDialog(context, "Failed to load post", it, ::fetchPost); + } else TaskHandler(IPlatformPostDetails::class.java) { _fragment.lifecycleScope }; + + private val _taskLoadPolycentricProfile = TaskHandler(StateApp.instance.scopeGetter, { PolycentricCache.instance.getProfileAsync(it) }) + .success { it -> setPolycentricProfile(it, animate = true) } + .exception { + Logger.w(TAG, "Failed to load claims.", it); + }; + + constructor(context: Context) : super(context) { + inflate(context, R.layout.fragview_post_detail, this); + + val root = findViewById(R.id.root); + + _creatorThumbnail = findViewById(R.id.creator_thumbnail); + _buttonSubscribe = findViewById(R.id.button_subscribe); + _channelName = findViewById(R.id.text_channel_name); + _channelMeta = findViewById(R.id.text_channel_meta); + _textTitle = findViewById(R.id.text_title); + _textMeta = findViewById(R.id.text_meta); + _textContent = findViewById(R.id.text_content); + _platformIndicator = findViewById(R.id.platform_indicator); + _buttonShare = findViewById(R.id.button_share); + + _buttonSupport = findViewById(R.id.button_support); + _buttonStore = findViewById(R.id.button_store); + _layoutMonetization = findViewById(R.id.layout_monetization); + + _layoutRating = findViewById(R.id.layout_rating); + _imageLikeIcon = findViewById(R.id.image_like_icon); + _textLikes = findViewById(R.id.text_likes); + _imageDislikeIcon = findViewById(R.id.image_dislike_icon); + _textDislikes = findViewById(R.id.text_dislikes); + + _commentsList = findViewById(R.id.comments_list); + _textCommentType = findViewById(R.id.text_comment_type); + _toggleCommentType = findViewById(R.id.toggle_comment_type); + _textComments = findViewById(R.id.text_comments); + _addCommentView = findViewById(R.id.add_comment_view); + + _rating = findViewById(R.id.rating); + + _layoutLoadingOverlay = findViewById(R.id.layout_loading_overlay); + _imageLoader = findViewById(R.id.image_loader); + + _imageActive = findViewById(R.id.image_active); + _layoutThumbnails = findViewById(R.id.layout_thumbnails); + + _repliesOverlay = findViewById(R.id.replies_overlay); + + val layoutTop: LinearLayout = findViewById(R.id.layout_top); + root.removeView(layoutTop); + _commentsList.setPrependedView(layoutTop); + + _commentsList.onCommentsLoaded.subscribe { count -> + updateCommentType(false); + }; + + _commentsList.onClick.subscribe { c -> + val replyCount = c.replyCount ?: 0; + var metadata = ""; + if (replyCount > 0) { + metadata += "$replyCount replies"; + } + + if (c is PolycentricPlatformComment) { + var parentComment: PolycentricPlatformComment = c; + _repliesOverlay.load(_toggleCommentType.value, metadata, c.contextUrl, c.reference, + { StatePolycentric.instance.getCommentPager(c.contextUrl, c.reference) }, + { + val newComment = parentComment.cloneWithUpdatedReplyCount((parentComment.replyCount ?: 0) + 1); + _commentsList.replaceComment(parentComment, newComment); + parentComment = newComment; + }); + } else { + _repliesOverlay.load(_toggleCommentType.value, metadata, null, null, { StatePlatform.instance.getSubComments(c) }); + } + + setRepliesOverlayVisible(isVisible = true, animate = true); + }; + + + _toggleCommentType.onValueChanged.subscribe { + updateCommentType(true); + }; + + _textCommentType.setOnClickListener { + _toggleCommentType.setValue(!_toggleCommentType.value, true); + updateCommentType(true); + }; + + _layoutMonetization.visibility = View.GONE; + + _buttonSupport.setOnClickListener { + val author = _post?.author ?: _postOverview?.author; + author?.let { _fragment.navigate(it).selectTab(2); }; + }; + + _buttonStore.setOnClickListener { + _polycentricProfile?.profile?.systemState?.store?.let { + try { + val uri = Uri.parse(it); + val intent = Intent(Intent.ACTION_VIEW); + intent.data = uri; + context.startActivity(intent); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to open URI: '${it}'.", e); + } + } + }; + + _addCommentView.onCommentAdded.subscribe { + _commentsList.addComment(it); + }; + + _repliesOverlay.onClose.subscribe { setRepliesOverlayVisible(isVisible = false, animate = true); }; + + _buttonShare.setOnClickListener { share() }; + + _creatorThumbnail.onClick.subscribe { openChannel() }; + _channelName.setOnClickListener { openChannel() }; + _channelMeta.setOnClickListener { openChannel() }; + } + + private fun openChannel() { + val author = _post?.author ?: _postOverview?.author ?: return; + _fragment.navigate(author); + } + + private fun share() { + try { + Logger.i(PreviewPostView.TAG, "sharePost") + + val url = _post?.shareUrl ?: _postOverview?.shareUrl ?: _url; + _fragment.startActivity(Intent.createChooser(Intent().apply { + action = Intent.ACTION_SEND; + putExtra(Intent.EXTRA_TEXT, url); + type = "text/plain"; //TODO: Determine alt types? + }, null)); + } catch (e: Throwable) { + //Ignored + Logger.e(PreviewPostView.TAG, "Failed to share.", e); + } + } + + private fun updatePolycentricRating() { + _rating.visibility = View.GONE; + + val value = _post?.id?.value ?: _postOverview?.id?.value ?: return; + val ref = Models.referenceFromBuffer(value.toByteArray()); + val version = _version; + + _rating.onLikeDislikeUpdated.remove(this); + _fragment.lifecycleScope.launch(Dispatchers.IO) { + if (version != _version) { + return@launch; + } + + try { + val queryReferencesResponse = ApiMethods.getQueryReferences(PolycentricCache.SERVER, ref, null,null, + arrayListOf( + Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType( + ContentType.OPINION.value).setValue( + ByteString.copyFrom(Opinion.like.data)).build(), + Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType( + ContentType.OPINION.value).setValue( + ByteString.copyFrom(Opinion.dislike.data)).build() + ) + ); + + if (version != _version) { + return@launch; + } + + val likes = queryReferencesResponse.countsList[0]; + val dislikes = queryReferencesResponse.countsList[1]; + val hasLiked = StatePolycentric.instance.hasLiked(ref); + val hasDisliked = StatePolycentric.instance.hasDisliked(ref); + + withContext(Dispatchers.Main) { + if (version != _version) { + return@withContext; + } + + _rating.visibility = VISIBLE; + _rating.setRating(RatingLikeDislikes(likes, dislikes), hasLiked, hasDisliked); + _rating.onLikeDislikeUpdated.subscribe(this) { processHandle, newHasLiked, newHasDisliked -> + if (newHasLiked) { + processHandle.opinion(ref, Opinion.like); + } else if (newHasDisliked) { + processHandle.opinion(ref, Opinion.dislike); + } else { + processHandle.opinion(ref, Opinion.neutral); + } + + StateApp.instance.scopeGetter().launch(Dispatchers.IO) { + try { + processHandle.fullyBackfillServers(); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to backfill servers", e) + } + } + + StatePolycentric.instance.updateLikeMap(ref, newHasLiked, newHasDisliked) + }; + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to get polycentric likes/dislikes.", e); + _rating.visibility = View.GONE; + } + } + } + + private fun setPlatformRating(rating: IRating?) { + if (rating == null) { + _layoutRating.visibility = View.GONE; + return; + } + + _layoutRating.visibility = View.VISIBLE; + + when (rating) { + is RatingLikeDislikes -> { + _textLikes.visibility = View.VISIBLE; + _imageLikeIcon.visibility = View.VISIBLE; + _textLikes.text = rating.likes.toHumanNumber(); + + _imageDislikeIcon.visibility = View.VISIBLE; + _textDislikes.visibility = View.VISIBLE; + _textDislikes.text = rating.dislikes.toHumanNumber(); + } + is RatingLikes -> { + _textLikes.visibility = View.VISIBLE; + _imageLikeIcon.visibility = View.VISIBLE; + _textLikes.text = rating.likes.toHumanNumber(); + + _imageDislikeIcon.visibility = View.GONE; + _textDislikes.visibility = View.GONE; + } + else -> { + _textLikes.visibility = View.GONE; + _imageLikeIcon.visibility = View.GONE; + _imageDislikeIcon.visibility = View.GONE; + _textDislikes.visibility = View.GONE; + } + } + } + + fun applyFragment(frag: PostDetailFragment): PostDetailView { + _fragment = frag; + return this; + } + + fun clear() { + _commentsList.cancel(); + _taskLoadPost.cancel(); + _taskLoadPolycentricProfile.cancel(); + _version++; + + _toggleCommentType.setValue(false, false); + _url = null; + _post = null; + _postOverview = null; + _creatorThumbnail.clear(); + //_buttonSubscribe.setSubscribeChannel(null); TODO: clear button + _channelName.text = ""; + setChannelMeta(null); + _textTitle.text = ""; + _textMeta.text = ""; + _textContent.text = ""; + setPlatformRating(null); + _polycentricProfile = null; + _rating.visibility = View.GONE; + updatePolycentricRating(); + setRepliesOverlayVisible(isVisible = false, animate = false); + setImages(null, null); + + _addCommentView.setContext(null, null); + _platformIndicator.clearPlatform(); + } + + fun setPostDetails(value: IPlatformPostDetails) { + _url = value.url; + _post = value; + + _creatorThumbnail.setThumbnail(value.author.thumbnail, false); + _buttonSubscribe.setSubscribeChannel(value.author.url); + _channelName.text = value.author.name; + setChannelMeta(value); + _textTitle.text = value.name; + _textMeta.text = value.datetime?.toHumanNowDiffString()?.let { "$it ago" } ?: "" //TODO: Include view count? + _textContent.text = value.content.fixHtmlWhitespace(); + _platformIndicator.setPlatformFromClientID(value.id.pluginId); + setPlatformRating(value.rating); + setImages(value.thumbnails.filterNotNull(), value.images); + + //Fetch only when not already called in setPostOverview + if (_postOverview == null) { + fetchPolycentricProfile(); + updatePolycentricRating(); + + val ref = value.id.value?.let { Models.referenceFromBuffer(it.toByteArray()); }; + _addCommentView.setContext(value.url, ref); + } + + updateCommentType(true); + } + + fun setPostOverview(value: IPlatformPost) { + clear(); + _url = value.url; + _postOverview = value; + + _creatorThumbnail.setThumbnail(value.author.thumbnail, false); + _buttonSubscribe.setSubscribeChannel(value.author.url); + _channelName.text = value.author.name; + setChannelMeta(value); + _textTitle.text = value.name; + _textMeta.text = value.datetime?.toHumanNowDiffString()?.let { "$it ago" } ?: "" //TODO: Include view count? + _textContent.text = value.description.fixHtmlWhitespace(); + _platformIndicator.setPlatformFromClientID(value.id.pluginId); + + val ref = value.id.value?.let { Models.referenceFromBuffer(it.toByteArray()); }; + _addCommentView.setContext(value.url, ref); + + updatePolycentricRating(); + fetchPolycentricProfile(); + fetchPost(); + } + + private fun setImages(images: List?, fullImages: List?) { + for (child in _layoutThumbnails.children) { + if (child is ImageView) { + Glide.with(child).clear(child); + } + } + + _layoutThumbnails.removeAllViews(); + + if (images.isNullOrEmpty() || fullImages.isNullOrEmpty()) { + _imageActive.visibility = View.GONE; + _layoutThumbnails.visibility = View.GONE; + return; + } + + _imageActive.visibility = View.VISIBLE; + + Glide.with(_imageActive) + .load(fullImages[0]) + .crossfade() + .into(_imageActive); + + if (images.size > 1) { + val dp_6f = 6.dp(resources).toFloat() + val dp_5 = 5.dp(resources) + val dp_12 = 12.dp(resources) + val dp_90 = 90.dp(resources) + + for (i in 0 until min(images.size, fullImages.size)) { + val image = images[i]; + val fullImage = fullImages[i]; + + _layoutThumbnails.addView(ShapeableImageView(context).apply { + scaleType = ImageView.ScaleType.CENTER_CROP + layoutParams = FlexboxLayout.LayoutParams(dp_90, dp_90).apply { setContentPadding(dp_5, dp_12, dp_5, 0) } + shapeAppearanceModel = ShapeAppearanceModel.builder().setAllCorners(CornerFamily.ROUNDED, dp_6f).build() + }.apply { + Glide.with(this) + .load(image.getLQThumbnail()) + .crossfade() + .into(this); + + setOnClickListener { + Glide.with(_imageActive) + .load(fullImage) + .crossfade() + .into(_imageActive); + } + }); + } + + _layoutThumbnails.visibility = View.VISIBLE; + } else { + _layoutThumbnails.visibility = View.GONE; + } + } + + private fun setRepliesOverlayVisible(isVisible: Boolean, animate: Boolean) { + if (_isRepliesVisible == isVisible) { + return; + } + + _isRepliesVisible = isVisible; + _repliesAnimator?.cancel(); + + if (isVisible) { + _repliesOverlay.visibility = View.VISIBLE; + + if (animate) { + _repliesOverlay.translationY = _repliesOverlay.height.toFloat(); + + _repliesAnimator = _repliesOverlay.animate() + .setDuration(300) + .translationY(0f) + .withEndAction { + _repliesAnimator = null; + }.apply { start() }; + } + } else { + if (animate) { + _repliesOverlay.translationY = 0f; + + _repliesAnimator = _repliesOverlay.animate() + .setDuration(300) + .translationY(_repliesOverlay.height.toFloat()) + .withEndAction { + _repliesOverlay.visibility = GONE; + _repliesAnimator = null; + }.apply { start(); } + } else { + _repliesOverlay.visibility = View.GONE; + _repliesOverlay.translationY = _repliesOverlay.height.toFloat(); + } + } + } + + private fun fetchPolycentricProfile() { + val author = _post?.author ?: _postOverview?.author ?: return; + val cachedPolycentricProfile = PolycentricCache.instance.getCachedProfile(author.url); + if (cachedPolycentricProfile != null) { + setPolycentricProfile(cachedPolycentricProfile, animate = false); + } else { + setPolycentricProfile(null, animate = false); + _taskLoadPolycentricProfile.run(author.id); + } + } + + private fun setChannelMeta(value: IPlatformPost?) { + val subscribers = value?.author?.subscribers; + if(subscribers != null && subscribers > 0) { + _channelMeta.visibility = View.VISIBLE; + _channelMeta.text = value.author.subscribers!!.toHumanNumber() + " subscribers"; + } else { + _channelMeta.visibility = View.GONE; + _channelMeta.text = ""; + } + } + + fun setPostUrl(url: String) { + clear(); + _url = url; + fetchPost(); + } + + fun onDestroy() { + _commentsList.cancel(); + _taskLoadPost.cancel(); + _repliesOverlay.cleanup(); + } + + private fun setPolycentricProfile(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean) { + _polycentricProfile = cachedPolycentricProfile; + + if (cachedPolycentricProfile?.profile == null) { + _layoutMonetization.visibility = View.GONE; + _creatorThumbnail.setHarborAvailable(false, animate); + return; + } + + _layoutMonetization.visibility = View.VISIBLE; + _creatorThumbnail.setHarborAvailable(true, animate); + } + + private fun fetchPost() { + Logger.i(TAG, "fetchVideo") + _post = null; + + val url = _url; + if (!url.isNullOrBlank()) { + setLoading(true); + _taskLoadPost.run(url); + } + } + + private fun fetchComments() { + Logger.i(TAG, "fetchComments") + _post?.let { + _commentsList.load(true) { StatePlatform.instance.getComments(it); }; + } + } + + private fun fetchPolycentricComments() { + Logger.i(TAG, "fetchPolycentricComments") + val post = _post; + val idValue = post?.id?.value + if (idValue == null) { + Logger.w(TAG, "Failed to fetch polycentric comments because id was null") + _commentsList.clear(); + return + } + + _commentsList.load(false) { StatePolycentric.instance.getCommentPager(post.url, Models.referenceFromBuffer(idValue.toByteArray())); }; + } + + private fun updateCommentType(reloadComments: Boolean) { + if (_toggleCommentType.value) { + _textCommentType.text = "Platform"; + _addCommentView.visibility = View.GONE; + + if (reloadComments) { + fetchComments(); + } + } else { + _textCommentType.text = "Polycentric"; + _addCommentView.visibility = View.VISIBLE; + + if (reloadComments) { + fetchPolycentricComments() + } + } + } + + private fun setLoading(isLoading : Boolean) { + if (_isLoading == isLoading) { + return; + } + + _isLoading = isLoading; + + if(isLoading) { + (_imageLoader.drawable as Animatable?)?.start() + _layoutLoadingOverlay.visibility = View.VISIBLE; + } + else { + _layoutLoadingOverlay.visibility = View.GONE; + (_imageLoader.drawable as Animatable?)?.stop() + } + } + + companion object { + const val TAG = "PostDetailFragment" + } + } + + companion object { + fun newInstance() = PostDetailFragment().apply {} + } +} diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt new file mode 100644 index 00000000..d7ec2197 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt @@ -0,0 +1,450 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.content.Intent +import android.graphics.drawable.Animatable +import android.net.Uri +import android.os.Bundle +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.webkit.CookieManager +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.LinearLayout +import androidx.lifecycle.lifecycleScope +import com.futo.platformplayer.* +import com.futo.platformplayer.activities.AddSourceActivity +import com.futo.platformplayer.activities.LoginActivity +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.api.media.platforms.js.JSClient +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StatePlugins +import com.futo.platformplayer.views.buttons.BigButton +import com.futo.platformplayer.views.buttons.BigButtonGroup +import com.futo.platformplayer.views.sources.SourceHeaderView +import com.futo.platformplayer.views.fields.FieldForm +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class SourceDetailFragment : MainFragment() { + override val isMainView : Boolean = true; + override val isTab: Boolean = true; + override val hasBottomBar: Boolean get() = true; + + private var _view: SourceDetailView? = null; + + override fun onShownWithView(parameter: Any?, isBack: Boolean) { + super.onShownWithView(parameter, isBack); + _view?.onShown(parameter, isBack); + } + + override fun onHide() { + super.onHide(); + _view?.onHide(); + } + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = SourceDetailView(this, inflater); + _view = view; + return view; + } + override fun onDestroyMainView() { + super.onDestroyMainView(); + _view = null; + } + + class SourceDetailView: LinearLayout { + private val fragment: SourceDetailFragment; + + private val _sourceHeader: SourceHeaderView; + private val _sourceButtons: LinearLayout; + private val _layoutLoader: FrameLayout; + private val _imageSpinner: ImageView; + + private val _settingsAppForm: FieldForm; + private var _settingsAppChanged = false; + + private val _settingsForm: FieldForm; + private var _settings: HashMap? = null; + private var _settingsChanged = false; + + private var _config: SourcePluginConfig? = null; + + private var _loading = false; + + constructor(fragment: SourceDetailFragment, inflater: LayoutInflater) : super(inflater.context) { + inflater.inflate(R.layout.fragment_source_detail, this); + this.fragment = fragment; + _sourceHeader = findViewById(R.id.source_header); + _sourceButtons = findViewById(R.id.source_buttons); + _settingsAppForm = findViewById(R.id.source_app_setings); + _settingsForm = findViewById(R.id.source_settings); + _layoutLoader = findViewById(R.id.layout_loader); + _imageSpinner = findViewById(R.id.image_spinner); + + updateSourceViews(); + } + + fun onShown(parameter: Any?, isBack: Boolean) { + if (parameter is SourcePluginConfig) { + loadConfig(parameter); + updateSourceViews(); + } + + setLoading(false); + } + + fun onHide() { + val id = _config?.id ?: return; + + if(_settingsChanged && _settings != null) { + _settingsChanged = false; + StatePlugins.instance.setPluginSettings(id, _settings!!); + reloadSource(id); + + UIDialogs.toast("Plugin settings saved", false); + } + if(_settingsAppChanged) { + _settingsAppForm.setObjectValues(); + StatePlugins.instance.savePlugin(id); + } + } + + + private fun loadConfig(config: SourcePluginConfig?) { + _config = config; + if(config != null) { + try { + val settings = config.settings; + val source = StatePlatform.instance.getClient(config.id) as JSClient; + val settingValues = source.settings; + + fragment.lifecycleScope.launch(Dispatchers.Main) { + + //Set any defaults + source.descriptor.appSettings.loadDefaults(source.descriptor.config); + + //App settings + try { + _settingsAppForm.fromObject(source.descriptor.appSettings); + _settingsAppForm.onChanged.clear(); + _settingsAppForm.onChanged.subscribe { field, value -> + _settingsAppChanged = true; + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to load app settings form from plugin settings", e) + } + + //Plugin settings + try { + _settings = settingValues; + _settingsForm.fromPluginSettings( + settings, settingValues, "Plugin settings", + "These settings are defined by the plugin" + ); + _settingsForm.onChanged.clear(); + _settingsForm.onChanged.subscribe { field, value -> + _settingsChanged = true; + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to load settings form from plugin settings", e) + } + } + } + catch(ex: Throwable) { + Logger.e(TAG, "Failed to load source", ex); + UIDialogs.toast("Failed to loast source"); + } + } + } + + private fun setLoading(isLoading: Boolean) { + fragment.lifecycleScope.launch(Dispatchers.Main) { + try { + if (isLoading) { + _layoutLoader.visibility = View.VISIBLE; + (_imageSpinner.drawable as Animatable?)?.start(); + } else { + _layoutLoader.visibility = View.GONE; + (_imageSpinner.drawable as Animatable?)?.stop(); + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to setLoading.", e) + } + } + + _loading = isLoading; + } + + private fun updateSourceViews() { + val config = _config; + + if (config != null) { + _sourceHeader.loadConfig(config); + } else { + _sourceHeader.clear(); + } + + //updateAllToggles(); + updateButtons(); + } + + private fun updateButtons() { + val groups = mutableListOf(); + _sourceButtons.removeAllViews(); + + val c = context ?: return; + val config = _config ?: return; + val source = StatePlatform.instance.getClient(config.id) as JSClient; + val isEnabled = StatePlatform.instance.isClientEnabled(source); + + groups.add( + BigButtonGroup(c, "Update", + BigButton(c, "Check for updates", "Checks for new versions of the source", R.drawable.ic_update) { + checkForUpdatesSource(); + } + ) + ); + + if (source.isLoggedIn) { + groups.add( + BigButtonGroup(c, "Authentication", + BigButton(c, "Logout", "Sign out of the platform", R.drawable.ic_logout) { + logoutSource(); + } + ) + ); + + val migrationButtons = mutableListOf(); + if (isEnabled && source.capabilities.hasGetUserSubscriptions) { + migrationButtons.add( + BigButton(c, "Import Subscriptions", "Import your subscriptions from this source", R.drawable.ic_subscriptions) { + Logger.i(TAG, "Import subscriptions clicked."); + importSubscriptionsSource(); + } + ); + } + + if (isEnabled && source.capabilities.hasGetUserPlaylists && source.capabilities.hasGetPlaylist) { + val bigButton = BigButton(c, "Import Playlists", "Import your playlists from this source", R.drawable.ic_playlist) { + Logger.i(TAG, "Import playlists clicked."); + importPlaylistsSource(); + }; + + bigButton.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply { + setMargins(0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics).toInt(), 0, 0); + }; + + migrationButtons.add(bigButton); + } + + if (migrationButtons.size > 0) { + groups.add(BigButtonGroup(c, "Migration", *migrationButtons.toTypedArray())); + } + } else { + if(config.authentication != null) { + groups.add( + BigButtonGroup(c, "Authentication", + BigButton(c, "Login", "Sign into the platform of this source", R.drawable.ic_login) { + loginSource(); + } + ) + ); + } + } + + groups.add( + BigButtonGroup(c, "Management", + BigButton(c, "Uninstall", "Removes the plugin from the app", R.drawable.ic_block) { + uninstallSource(); + }.withBackground(R.drawable.background_big_button_red) + ) + ) + + for (group in groups) { + _sourceButtons.addView(group); + } + } + + + private fun loginSource() { + val config = _config ?: return; + + if(config.authentication == null) + return; + + LoginActivity.showLogin(StateApp.instance.context, config) { + StatePlugins.instance.setPluginAuth(config.id, it); + + reloadSource(config.id); + }; + } + private fun logoutSource() { + val config = _config ?: return; + + StatePlugins.instance.setPluginAuth(config.id, null); + reloadSource(config.id); + + + //TODO: Maybe add a dialog option.. + if(Settings.instance.plugins.clearCookiesOnLogout) { + val cookieManager: CookieManager = CookieManager.getInstance(); + cookieManager.removeAllCookies(null); + } + } + private fun importPlaylistsSource() { + if (_loading) { + return; + } + setLoading(true); + + try { + val config = _config ?: return; + val source = StatePlatform.instance.getClient(config.id); + + fragment.lifecycleScope.launch(Dispatchers.IO) { + try { + val playlists = source.getUserPlaylists(); + withContext(Dispatchers.Main) { + fragment.navigate(playlists); + } + } catch (e: Throwable) { + withContext(Dispatchers.Main) { + context?.let { UIDialogs.showGeneralErrorDialog(it, "Failed to retrieve playlists.", e) } + } + } finally { + setLoading(false); + } + } + } catch (e: Throwable) { + setLoading(false); + } + } + private fun importSubscriptionsSource() { + if (_loading) { + return; + } + + setLoading(true); + + try { + val config = _config ?: return; + val source = StatePlatform.instance.getClient(config.id); + + Logger.i(TAG, "Getting user subscriptions."); + fragment.lifecycleScope.launch(Dispatchers.IO) { + try { + val subscriptions = source.getUserSubscriptions().distinct(); + Logger.i(TAG, "${subscriptions.size} user subscriptions retrieved."); + + withContext(Dispatchers.Main) { + fragment.navigate(subscriptions); + } + } catch(e: Throwable) { + withContext(Dispatchers.Main) { + context?.let { UIDialogs.showGeneralErrorDialog(it, "Failed to retrieve subscriptions.", e) } + } + } finally { + setLoading(false); + } + } + } catch(e: Throwable) { + setLoading(false); + } + } + private fun uninstallSource() { + val config = _config ?: return; + val source = StatePlatform.instance.getClient(config.id); + + UIDialogs.showConfirmationDialog(context, "Are you sure you want to uninstall ${source.name}", { + StatePlugins.instance.deletePlugin(source.id); + + fragment.lifecycleScope.launch(Dispatchers.IO) { + try { + StatePlatform.instance.updateAvailableClients(context); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to update available clients."); + } + + withContext(Dispatchers.Main) { + UIDialogs.toast(context, "Uninstalled ${source.name}"); + fragment.closeSegment(); + } + } + }); + } + private fun checkForUpdatesSource() { + val c = _config ?: return; + val sourceUrl = c.sourceUrl ?: return; + + Logger.i(TAG, "Check for updates tapped."); + fragment.lifecycleScope.launch(Dispatchers.IO) { + try { + val client = ManagedHttpClient(); + val response = client.get(sourceUrl); + Logger.i(TAG, "Downloading source config '$sourceUrl'."); + + if (!response.isOk || response.body == null) { + Logger.w(TAG, "Failed to check for updates (sourceUrl=${sourceUrl}, response.isOk=${response.isOk}, response.body=${response.body})."); + withContext(Dispatchers.Main) { UIDialogs.toast("Failed to check for updates"); }; + return@launch; + } + + val configJson = response.body.string(); + Logger.i(TAG, "Downloaded source config ($sourceUrl):\n${configJson}"); + + val config = SourcePluginConfig.fromJson(configJson); + if (config.version <= c.version) { + Logger.i(TAG, "Plugin is up to date."); + withContext(Dispatchers.Main) { UIDialogs.toast("Plugin is fully up to date"); }; + return@launch; + } + + Logger.i(TAG, "Update is available (config.version=${config.version}, source.config.version=${c.version})."); + + val c = context ?: return@launch; + val intent = Intent(c, AddSourceActivity::class.java).apply { + data = Uri.parse(sourceUrl) + }; + + fragment.startActivity(intent); + Logger.i(TAG, "Started add source activity."); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to check for updates.", e); + withContext(Dispatchers.Main) { UIDialogs.toast("Failed to check for updates"); }; + } + } + } + + private fun reloadSource(id: String) { + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { + try { + StatePlatform.instance.reloadClient(context, id); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to reload client.", e) + return@launch; + } + + withContext(Dispatchers.Main) { + try { + updateSourceViews(); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to update source views.", e) + } + } + } + } + } + + + + companion object { + const val TAG = "SourceDetailFragment"; + fun newInstance() = SourceDetailFragment().apply {} + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourcesFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourcesFragment.kt new file mode 100644 index 00000000..83e94208 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourcesFragment.kt @@ -0,0 +1,239 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.R +import com.futo.platformplayer.activities.AddSourceOptionsActivity +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.platforms.js.JSClient +import com.futo.platformplayer.fragment.mainactivity.topbar.AddTopBarFragment +import com.futo.platformplayer.states.StatePlugins +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.SubscriptionStorage +import com.futo.platformplayer.views.sources.SourceUnderConstructionView +import com.futo.platformplayer.views.adapters.DisabledSourceView +import com.futo.platformplayer.views.adapters.EnabledSourceAdapter +import com.futo.platformplayer.views.adapters.EnabledSourceViewHolder +import com.futo.platformplayer.views.adapters.ItemMoveCallback +import kotlinx.coroutines.runBlocking +import java.util.* + +class SourcesFragment : MainFragment() { + override val isMainView : Boolean = true; + override val isTab: Boolean = true; + override val hasBottomBar: Boolean get() = true; + + private var _view: SourcesView? = null; + + override fun onShownWithView(parameter: Any?, isBack: Boolean) { + super.onShownWithView(parameter, isBack) + + if(topBar is AddTopBarFragment) + (topBar as AddTopBarFragment).onAdd.subscribe { + startActivity(Intent(requireContext(), AddSourceOptionsActivity::class.java)); + }; + + _view?.reloadSources(); + } + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = SourcesView(requireContext(), this); + _view = view; + return view; + } + + companion object { + private const val TAG = "SourcesFragment"; + fun newInstance() = SourcesFragment().apply {} + } + + + private class SourcesView: LinearLayout { + private val _fragment: SourcesFragment; + + private val enabledSources: MutableList = mutableListOf(); + private val disabledSources: MutableList = mutableListOf(); + private val _recyclerSourcesEnabled: RecyclerView; + private val _adapterSourcesEnabled: EnabledSourceAdapter; + private var _didCreateView = false; + + private val _containerEnabled: LinearLayout; + private val _containerDisabled: LinearLayout; + private val _containerDisabledViews: LinearLayout; + private val _containerConstruction: LinearLayout; + + constructor(context: Context, fragment: SourcesFragment): super(context) { + inflate(context, R.layout.fragment_sources, this); + + _fragment = fragment; + + val recyclerSourcesEnabled = findViewById(R.id.recycler_sources_enabled); + _containerEnabled = findViewById(R.id.container_enabled); + _containerDisabled = findViewById(R.id.container_disabled); + _containerDisabledViews = findViewById(R.id.container_disabled_views); + _containerConstruction = findViewById(R.id.container_construction); + + for(inConstructSource in StatePlugins.instance.getSourcesUnderConstruction(context)) + _containerConstruction.addView(SourceUnderConstructionView(context, inConstructSource.key, inConstructSource.value)); + + val callback = ItemMoveCallback(); + val touchHelper = ItemTouchHelper(callback); + val adapterSourcesEnabled = EnabledSourceAdapter(enabledSources, touchHelper); + + recyclerSourcesEnabled.adapter = adapterSourcesEnabled; + recyclerSourcesEnabled.layoutManager = LinearLayoutManager(context); + touchHelper.attachToRecyclerView(recyclerSourcesEnabled); + + //Enabled Sources control + callback.onRowMoved.subscribe { fromPosition, toPosition -> + if (fromPosition < toPosition) { + for (i in fromPosition until toPosition) { + Collections.swap(enabledSources, i, i + 1) + } + } else { + for (i in fromPosition downTo toPosition + 1) { + Collections.swap(enabledSources, i, i - 1) + } + } + + adapterSourcesEnabled.notifyItemMoved(fromPosition, toPosition); + onEnabledChanged(enabledSources); + if(toPosition == 0) + onPrimaryChanged(enabledSources.first()); + + StatePlatform.instance.setPlatformOrder(enabledSources.map { it.name }); + }; + adapterSourcesEnabled.onRemove.subscribe { source -> + val subscriptionStorage = FragmentedStorage.get(); + val enabledSourcesWithSourceRemoved = enabledSources.filter({ s -> s.id != source.id }).toList(); + val unresolvableBefore = subscriptionStorage.subscriptions.count({ s -> !enabledSources.any({ c -> c.isChannelUrl(s.channel.url) }) }); + val unresolvableAfter = subscriptionStorage.subscriptions.count({ s -> !enabledSourcesWithSourceRemoved.any({ c -> c.isChannelUrl(s.channel.url) }) }); + + val removeAction = { + val index = enabledSources.indexOf(source); + if (index >= 0) { + enabledSources.removeAt(index); + disabledSources.add(source); + adapterSourcesEnabled.notifyItemRemoved(index); + updateDisabledSources(); + } + + updateContainerVisibility(); + onEnabledChanged(enabledSources); + if(index == 0) + onPrimaryChanged(enabledSources.first()); + + if(enabledSources.size <= 1) + setCanRemove(false); + }; + + if (unresolvableAfter > unresolvableBefore) { + UIDialogs.showConfirmationDialog(context, fragment.getString(R.string.confirm_remove_source), removeAction); + } else { + removeAction(); + } + }; + adapterSourcesEnabled.onClick.subscribe { source -> + if (source is JSClient) { + fragment.navigate(source.config); + } + }; + + updateContainerVisibility(); + + _recyclerSourcesEnabled = recyclerSourcesEnabled; + _adapterSourcesEnabled = adapterSourcesEnabled; + //_adapterSourcesDisabled = adapterSourcesDisabled; + + setCanRemove(enabledSources.size > 1); + _didCreateView = true; + } + + fun reloadSources() { + enabledSources.clear(); + disabledSources.clear(); + + enabledSources.addAll(StatePlatform.instance.getSortedEnabledClient()); + disabledSources.addAll(StatePlatform.instance.getAvailableClients().filter { !enabledSources.contains(it) }); + _adapterSourcesEnabled?.notifyDataSetChanged(); + setCanRemove(enabledSources.size > 1); + //_adapterSourcesDisabled?.notifyDataSetChanged(); + updateDisabledSources(); + + if(_didCreateView) { + _containerEnabled.visibility = if (enabledSources.isNotEmpty()) { View.VISIBLE } else { View.GONE }; + _containerDisabled.visibility = if (disabledSources.isNotEmpty()) { View.VISIBLE } else { View.GONE }; + } + } + private fun updateDisabledSources() { + _containerDisabledViews.removeAllViews(); + disabledSources.toList().let { + for(source in disabledSources) { + _containerDisabledViews.addView(DisabledSourceView(context, source).apply { + this.onAdd.subscribe { + enableSource(it) + }; + this.onClick.subscribe { + if (source is JSClient) + _fragment.navigate(source.config); + } + }); + } + }; + } + + private fun enableSource(client: IPlatformClient) { + if (disabledSources.remove(client)) { + enabledSources.add(client); + _adapterSourcesEnabled.notifyItemInserted(enabledSources.size - 1); + } + updateDisabledSources(); + + updateContainerVisibility(); + onEnabledChanged(enabledSources); + + if(enabledSources.size > 1) + setCanRemove(true); + } + + private fun setCanRemove(canRemove: Boolean) { + val recyclerSourcesEnabled = _recyclerSourcesEnabled ?: return; + var adapterSourcesEnabled = _adapterSourcesEnabled ?: return; + + for (i in 0 until recyclerSourcesEnabled.childCount) { + val view: View = recyclerSourcesEnabled.getChildAt(i) + val viewHolder = recyclerSourcesEnabled.getChildViewHolder(view) + if (viewHolder is EnabledSourceViewHolder) { + viewHolder.setCanRemove(canRemove); + } + } + + adapterSourcesEnabled.canRemove = canRemove; + } + + private fun onPrimaryChanged(client: IPlatformClient) { + StatePlatform.instance.selectPrimaryClient(client.id); + } + private fun onEnabledChanged(clients: List) { + runBlocking { + StatePlatform.instance.selectClients(*clients.map { it.id }.toTypedArray()); + } + } + + + fun updateContainerVisibility() { + _containerEnabled.visibility = if (enabledSources.isNotEmpty()) { View.VISIBLE } else { View.GONE }; + _containerDisabled.visibility = if (disabledSources.isNotEmpty()) { View.VISIBLE } else { View.GONE }; + }; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt new file mode 100644 index 00000000..5a61b2f5 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt @@ -0,0 +1,302 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.cache.ChannelContentCache +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.engine.exceptions.PluginException +import com.futo.platformplayer.exceptions.ChannelException +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StateSubscriptions +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.FragmentedStorageFileJson +import com.futo.platformplayer.views.announcements.AnnouncementView +import com.futo.platformplayer.views.FeedStyle +import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder +import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader +import com.futo.platformplayer.views.adapters.InsertedViewHolder +import com.futo.platformplayer.views.subscriptions.SubscriptionBar +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.time.OffsetDateTime + +class SubscriptionsFeedFragment : MainFragment() { + override val isMainView : Boolean = true; + override val isTab: Boolean = true; + override val hasBottomBar: Boolean get() = true; + + private var _view: SubscriptionsFeedView? = null; + private var _cachedRecyclerData: FeedView.RecyclerData, LinearLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>? = null; + + override fun onShownWithView(parameter: Any?, isBack: Boolean) { + super.onShownWithView(parameter, isBack); + _view?.onShown(); + } + + override fun onResume() { + super.onResume() + _view?.onResume(); + } + + override fun onPause() { + super.onPause() + _view?.onPause(); + } + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = SubscriptionsFeedView(this, inflater, _cachedRecyclerData); + _view = view; + return view; + } + + override fun onDestroyMainView() { + super.onDestroyMainView(); + val view = _view; + if (view != null) { + _cachedRecyclerData = view.recyclerData; + view.cleanup(); + _view = null; + } + } + + fun setPreviewsEnabled(previewsEnabled: Boolean) { + _view?.setPreviewsEnabled(previewsEnabled); + } + + @SuppressLint("ViewConstructor") + class SubscriptionsFeedView : ContentFeedView { + constructor(fragment: SubscriptionsFeedFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData, LinearLayoutManager, IPager, IPlatformContent, IPlatformContent, InsertedViewHolder>? = null) : super(fragment, inflater, cachedRecyclerData) { + StateSubscriptions.instance.onGlobalSubscriptionsUpdateProgress.subscribe(this) { progress, total -> + fragment.lifecycleScope.launch(Dispatchers.Main) { + try { + setProgress(progress, total); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to set progress", e); + } + } + }; + + StateSubscriptions.instance.onSubscriptionsChanged.subscribe(this) { subs, added -> + if(!added) + StateSubscriptions.instance.clearSubscriptionFeed(); + StateApp.instance.scopeOrNull?.let { + StateSubscriptions.instance.updateSubscriptionFeed(it); + } + recyclerData.lastLoad = OffsetDateTime.MIN; + }; + + initializeToolbarContent(); + } + + fun onShown() { + val currentProgress = StateSubscriptions.instance.getGlobalSubscriptionProgress(); + setProgress(currentProgress.first, currentProgress.second); + + if(recyclerData.loadedFeedStyle != feedStyle || + recyclerData.lastLoad.getNowDiffSeconds() > 60 ) { + recyclerData.lastLoad = OffsetDateTime.now(); + loadResults(); + } + + val announcementsView = _announcementsView; + val homeTab = Settings.instance.tabs.find { it.id == 0 }; + val isHomeEnabled = homeTab?.enabled == true; + if (announcementsView != null && isHomeEnabled) { + headerView?.removeView(announcementsView); + _announcementsView = null; + } + + if (announcementsView == null && !isHomeEnabled) { + val c = context; + if (c != null) { + _announcementsView = AnnouncementView(c).apply { + headerView?.addView(AnnouncementView(c)) + }; + } + } + } + + override fun cleanup() { + super.cleanup() + StateSubscriptions.instance.onGlobalSubscriptionsUpdateProgress.remove(this); + StateSubscriptions.instance.onSubscriptionsChanged.remove(this); + } + + override val feedStyle: FeedStyle get() = Settings.instance.subscriptions.getSubscriptionsFeedStyle(); + + private var _subscriptionBar: SubscriptionBar? = null; + + private var _announcementsView: AnnouncementView? = null; + + @Serializable + class FeedFilterSettings: FragmentedStorageFileJson() { + val allowContentTypes: MutableList = mutableListOf(ContentType.MEDIA, ContentType.POST); + var allowLive: Boolean = true; + var allowPlanned: Boolean = false; + override fun encode(): String { + return Json.encodeToString(this); + } + } + private val _filterLock = Object(); + private val _filterSettings = FragmentedStorage.get("subFeedFilter"); + + private val _lastExceptions: List? = null; + private val _taskGetPager = TaskHandler>({StateApp.instance.scope}, { withRefresh -> + val resp = StateSubscriptions.instance.getGlobalSubscriptionFeed(StateApp.instance.scope, withRefresh); + + val currentExs = StateSubscriptions.instance.globalSubscriptionExceptions; + if(currentExs != _lastExceptions && currentExs.any()) + handleExceptions(currentExs); + + return@TaskHandler resp; + }) + .success { loadedResult(it); } + .exception { + Logger.w(ChannelFragment.TAG, "Failed to load channel.", it); + UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() }); + }; + + private fun initializeToolbarContent() { + _subscriptionBar = SubscriptionBar(context).apply { + layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + }; + _subscriptionBar?.onClickChannel?.subscribe { c -> fragment.navigate(c); }; + + synchronized(_filterLock) { + _subscriptionBar?.setToggles( + SubscriptionBar.Toggle("Videos", _filterSettings.allowContentTypes.contains(ContentType.MEDIA)) { toggleFilterContentTypes(listOf(ContentType.MEDIA, ContentType.NESTED_VIDEO), it); }, + SubscriptionBar.Toggle("Posts", _filterSettings.allowContentTypes.contains(ContentType.POST)) { toggleFilterContentType(ContentType.POST, it); }, + SubscriptionBar.Toggle("Live", _filterSettings.allowLive) { _filterSettings.allowLive = it; _filterSettings.save(); loadResults(false); }, + SubscriptionBar.Toggle("Planned", _filterSettings.allowPlanned) { _filterSettings.allowPlanned = it; _filterSettings.save(); loadResults(false); } + ); + } + + _toolbarContentView.addView(_subscriptionBar, 0); + } + private fun toggleFilterContentTypes(contentTypes: List, isTrue: Boolean) { + for(contentType in contentTypes) + toggleFilterContentType(contentType, isTrue); + } + private fun toggleFilterContentType(contentType: ContentType, isTrue: Boolean) { + synchronized(_filterLock) { + if(!isTrue) + _filterSettings.allowContentTypes.remove(contentType); + else if(!_filterSettings.allowContentTypes.contains(contentType)) + _filterSettings.allowContentTypes.add(contentType) + else null; + _filterSettings.save(); + }; + loadResults(false) + } + + override fun filterResults(results: List): List { + val nowSoon = OffsetDateTime.now().plusMinutes(5); + return results.filter { + val allowedContentType = _filterSettings.allowContentTypes.contains(if(it.contentType == ContentType.NESTED_VIDEO) ContentType.MEDIA else it.contentType); + + if(it.datetime?.isAfter(nowSoon) == true) { + if(!_filterSettings.allowPlanned) + return@filter false; + } + + if(_filterSettings.allowLive) { //If allowLive, always show live + if(it is IPlatformVideo && it.isLive) + return@filter true; + } + else if(it is IPlatformVideo && it.isLive) + return@filter false; + + return@filter allowedContentType; + }; + } + + override fun reload() { + loadResults(true); + } + + private fun loadResults(withRefetch: Boolean = false) { + setLoading(true); + Logger.i(TAG, "Subscriptions load"); + if(recyclerData.results.size == 0) { + val cachePager = ChannelContentCache.instance.getSubscriptionCachePager(); + Logger.i(TAG, "Subscription show cache (${cachePager.getResults().size})"); + setPager(cachePager); + } + _taskGetPager.run(withRefetch); + } + + private fun loadedResult(pager: IPager) { + Logger.i(TAG, "Subscriptions new pager loaded"); + + fragment.lifecycleScope.launch(Dispatchers.Main) { + try { + finishRefreshLayoutLoader(); + setLoading(false); + setPager(pager); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to finish loading", e) + } + } + } + + private fun handleExceptions(exs: List) { + context?.let { + fragment.lifecycleScope.launch(Dispatchers.Main) { + try { + if (exs!!.size <= 8) { + for (ex in exs) { + var toShow = ex; + var channel: String? = null; + if (toShow is ChannelException) { + channel = toShow.channelNameOrUrl; + toShow = toShow.cause!!; + } + Logger.e(TAG, "Channel [${channel}] failed", ex); + if (toShow is PluginException) + UIDialogs.toast( + it, + "Plugin [${toShow.config.name}] (${channel}) failed:\n${toShow.message}" + ); + else + UIDialogs.toast(it, ex.message ?: ""); + } + } + else { + val failedPlugins = exs.filter { it is PluginException || (it is ChannelException && it.cause is PluginException) } + .map { if(it is ChannelException) (it.cause as PluginException) else if(it is PluginException) it else null } + .filter { it != null } + .distinctBy { it?.config?.name } + .map { it!! } + .toList(); + for(distinctPluginFail in failedPlugins) + UIDialogs.toast(it, "Plugin [${distinctPluginFail.config.name}] failed:\n${distinctPluginFail.message}"); + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to handle exceptions", e) + } + } + } + } + } + + companion object { + val TAG = "SubscriptionsFeedFragment"; + + fun newInstance() = SubscriptionsFeedFragment().apply {} + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SuggestionsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SuggestionsFragment.kt new file mode 100644 index 00000000..27a96094 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SuggestionsFragment.kt @@ -0,0 +1,195 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.* +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.SearchType +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.SearchHistoryStorage +import com.futo.platformplayer.views.adapters.SearchSuggestionAdapter + +data class SuggestionsFragmentData(val query: String, val searchType: SearchType, val channelUrl: String? = null); + +class SuggestionsFragment : MainFragment { + override val isMainView : Boolean = true; + override val hasBottomBar: Boolean = false; + override val isHistory: Boolean = false; + + private var _recyclerSuggestions: RecyclerView? = null; + private var _llmSuggestions: LinearLayoutManager? = null; + private val _suggestions: ArrayList = ArrayList(); + private var _query: String? = null; + private var _searchType: SearchType = SearchType.VIDEO; + private var _channelUrl: String? = null; + + private val _adapterSuggestions = SearchSuggestionAdapter(_suggestions); + + private val _getSuggestions = TaskHandler>({lifecycleScope}, { + query -> StatePlatform.instance.searchSuggestions(query) + }) + .success { suggestions -> updateSuggestions(suggestions, false) } + .exception { + Logger.w(ChannelFragment.TAG, "Failed to load suggestions.", it); + UIDialogs.showGeneralRetryErrorDialog(requireContext(), it.message ?: "", it, { loadSuggestions() }); + }; + + constructor(): super() { + _adapterSuggestions.onAddToQuery.subscribe { suggestion -> (topBar as SearchTopBarFragment?)?.setText(suggestion); }; + _adapterSuggestions.onClicked.subscribe { suggestion -> + val storage = FragmentedStorage.get(); + storage.add(suggestion); + + if (_searchType == SearchType.CREATOR) { + navigate(suggestion); + } else if (_searchType == SearchType.PLAYLIST) { + navigate(suggestion); + } else { + navigate(SuggestionsFragmentData(suggestion, SearchType.VIDEO, _channelUrl)); + } + } + _adapterSuggestions.onRemove.subscribe { suggestion -> + val index = _suggestions.indexOf(suggestion); + if (index == -1) { + return@subscribe; + } + + val storage = FragmentedStorage.get(); + storage.lastQueries.removeAt(index); + _suggestions.removeAt(index); + _adapterSuggestions.notifyItemRemoved(index); + storage.save(); + }; + } + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = inflater.inflate(R.layout.fragment_suggestion_list, container, false); + + val recyclerSuggestions: RecyclerView = view.findViewById(R.id.list_suggestions); + recyclerSuggestions.layoutManager = _llmSuggestions; + recyclerSuggestions.adapter = _adapterSuggestions; + _recyclerSuggestions = recyclerSuggestions; + + loadSuggestions(); + return view; + } + + override fun onAttach(context: Context) { + super.onAttach(context) + + val llmSuggestions = LinearLayoutManager(context); + llmSuggestions.orientation = LinearLayoutManager.VERTICAL; + _llmSuggestions = llmSuggestions; + } + + override fun onDetach() { + super.onDetach(); + _llmSuggestions = null; + } + + override fun onShownWithView(parameter: Any?, isBack: Boolean) { + super.onShownWithView(parameter, isBack); + + loadSuggestions(); + + if (parameter is SuggestionsFragmentData) { + _searchType = parameter.searchType; + _channelUrl = parameter.channelUrl; + } else if (parameter is SearchType) { + _searchType = parameter; + _channelUrl = null; + } + + topBar?.apply { + if (this is SearchTopBarFragment) { + onSearch.subscribe(this) { + if (_searchType == SearchType.CREATOR) { + navigate(it); + } else if (_searchType == SearchType.PLAYLIST) { + navigate(it); + } else { + navigate(SuggestionsFragmentData(it, SearchType.VIDEO, _channelUrl)); + } + }; + + onTextChange.subscribe(this) { + setQuery(it); + }; + } + } + } + + override fun onHide() { + _query = null; + updateSuggestions(arrayOf(), false); + + topBar?.apply { + if (this is SearchTopBarFragment) { + onSearch.remove(this); + onTextChange.remove(this); + } + } + } + private fun setQuery(query: String) { + _query = query; + loadSuggestions(); + } + + private fun loadSuggestions() { + _getSuggestions.cancel(); + + val query = _query; + Logger.i(TAG, "loadSuggestions query='$query'"); + + if (query.isNullOrBlank()) { + if (!Settings.instance.search.searchHistory) { + updateSuggestions(arrayOf(), false); + return; + } + + val lastQueries = FragmentedStorage.get().lastQueries.toTypedArray(); + updateSuggestions(lastQueries, true); + return; + } + + _getSuggestions.run(query); + } + + private fun updateSuggestions(suggestions: Array, isHistorical: Boolean) { + Logger.i(TAG, "updateSuggestions suggestions='${suggestions.size}' isHistorical=${isHistorical}"); + + _suggestions.clear(); + if (suggestions.isNotEmpty()) { + _suggestions.addAll(suggestions); + } + + _adapterSuggestions.isHistorical = isHistorical; + _adapterSuggestions.notifyDataSetChanged(); + } + + override fun onDestroyMainView() { + super.onDestroyMainView(); + _getSuggestions.onError.clear(); + _recyclerSuggestions = null; + } + + override fun onDestroy() { + super.onDestroy(); + _getSuggestions.cancel(); + } + + companion object { + val TAG = "SuggestionsFragment"; + + fun newInstance() = SuggestionsFragment().apply { } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt new file mode 100644 index 00000000..fea155de --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt @@ -0,0 +1,470 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.content.pm.ActivityInfo +import android.content.res.Configuration +import android.os.Bundle +import android.view.* +import androidx.constraintlayout.motion.widget.MotionLayout +import androidx.core.view.* +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StatePlayer +import com.futo.platformplayer.R +import com.futo.platformplayer.Settings +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.activities.MainActivity +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails +import com.futo.platformplayer.casting.CastConnectionState +import com.futo.platformplayer.casting.StateCasting +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.listeners.OrientationManager +import com.futo.platformplayer.models.PlatformVideoWithTime +import com.futo.platformplayer.models.UrlVideoWithTime +import com.futo.platformplayer.states.StateSaved +import com.futo.platformplayer.states.VideoToOpen +import com.futo.platformplayer.views.containers.SingleViewTouchableMotionLayout + +class VideoDetailFragment : MainFragment { + override val isMainView : Boolean = false; + override val hasBottomBar: Boolean = true; + override val isOverlay : Boolean = true; + override val isHistory: Boolean = false; + + private var _isActive: Boolean = false; + + private var _viewDetail : VideoDetailView? = null; + private var _view : SingleViewTouchableMotionLayout? = null; + + var isFullscreen : Boolean = false; + var isTransitioning : Boolean = false + private set; + var isInPictureInPicture : Boolean = false + private set; + + var state: State = State.CLOSED; + val currentUrl get() = _viewDetail?.currentUrl; + + val onMinimize = Event0(); + val onTransitioning = Event1(); + val onMaximized = Event0(); + + var lastOrientation : OrientationManager.Orientation = OrientationManager.Orientation.PORTRAIT + private set; + + private var _isInitialMaximize = true; + + private val _maximizeProgress get() = _view?.progress ?: 0.0f; + + private var _loadUrlOnCreate: UrlVideoWithTime? = null; + private var _leavingPiP = false; + +//region Fragment + constructor() : super() { + } + + override fun onShownWithView(parameter: Any?, isBack: Boolean) { + super.onShownWithView(parameter, isBack); + Logger.i(TAG, "onShownWithView parameter=$parameter") + + activity?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + if(parameter is IPlatformVideoDetails) + _viewDetail?.setVideoDetails(parameter, true); + else if (parameter is IPlatformVideo) + _viewDetail?.setVideoOverview(parameter); + else if(parameter is PlatformVideoWithTime) + _viewDetail?.setVideoOverview(parameter.video, true, parameter.time); + else if (parameter is UrlVideoWithTime) { + if (_viewDetail == null) { + _loadUrlOnCreate = parameter; + } else { + _viewDetail?.setVideo(parameter.url, parameter.timeSeconds, parameter.playWhenReady); + } + } else if(parameter is String) { + if (_viewDetail == null) { + _loadUrlOnCreate = UrlVideoWithTime(parameter, 0, true); + } else { + _viewDetail?.setVideo(parameter, 0, true); + } + } + } + + override fun onOrientationChanged(orientation: OrientationManager.Orientation) { + super.onOrientationChanged(orientation); + + if(!_isActive || state != State.MAXIMIZED) + return; + + var newOrientation = orientation; + val d = StateCasting.instance.activeDevice; + if (d != null && d.connectionState == CastConnectionState.CONNECTED) { + newOrientation = OrientationManager.Orientation.PORTRAIT; + } else if(StatePlayer.instance.rotationLock) { + return; + } + + if(lastOrientation == newOrientation) + return; + + activity?.let { + if (isFullscreen) { + if(newOrientation == OrientationManager.Orientation.REVERSED_LANDSCAPE && it.requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) + changeOrientation(OrientationManager.Orientation.REVERSED_LANDSCAPE); + else if(newOrientation == OrientationManager.Orientation.LANDSCAPE && it.requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) + changeOrientation(OrientationManager.Orientation.LANDSCAPE); + else if(Settings.instance.playback.isAutoRotate() && (newOrientation == OrientationManager.Orientation.PORTRAIT || newOrientation == OrientationManager.Orientation.REVERSED_PORTRAIT)) { + _viewDetail?.setFullscreen(false); + } + } + else { + if(Settings.instance.playback.isAutoRotate() && (lastOrientation == OrientationManager.Orientation.PORTRAIT || lastOrientation == OrientationManager.Orientation.REVERSED_PORTRAIT)) { + lastOrientation = newOrientation; + _viewDetail?.setFullscreen(true); + } + } + } + lastOrientation = newOrientation; + } + override fun onBackPressed(): Boolean { + Logger.i(TAG, "onBackPressed") + + if (_viewDetail?.onBackPressed() == true) { + return true; + } + + if(state == State.MAXIMIZED) + minimizeVideoDetail(); + else + closeVideoDetails(); + return true; + } + override fun onHide() { + super.onHide(); + activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + fun preventPictureInPicture() { + Logger.i(TAG, "preventPictureInPicture() preventPictureInPicture = true"); + _viewDetail?.preventPictureInPicture = true; + } + + fun minimizeVideoDetail(){ + _viewDetail?.setFullscreen(false); + if(_view != null) + _view!!.transitionToStart(); + } + fun maximizeVideoDetail(instant: Boolean = false) { + if(_maximizeProgress > 0.9f && state != State.MAXIMIZED) { + state = State.MAXIMIZED; + onMaximized.emit(); + } + _view?.let { + if(!instant) + it.transitionToEnd(); + else { + it.progress = 1f; + onTransitioning.emit(true); + } + }; + } + fun closeVideoDetails() { + Logger.i(TAG, "closeVideoDetails()") + state = State.CLOSED; + _viewDetail?.onStop(); + close(); + + StatePlayer.instance.clearQueue(); + StatePlayer.instance.setPlayerClosed(); + } + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + _view = inflater.inflate(R.layout.fragment_video_detail, container, false) as SingleViewTouchableMotionLayout; + _viewDetail = _view!!.findViewById(R.id.fragview_videodetail).also { + it.applyFragment(this); + it.onFullscreenChanged.subscribe(::onFullscreenChanged); + it.onMinimize.subscribe { + _view!!.transitionToStart(); + }; + it.onClose.subscribe { + Logger.i(TAG, "onClose") + closeVideoDetails(); + }; + it.onMaximize.subscribe { maximizeVideoDetail(it) }; + it.onPlayChanged.subscribe { + if(isInPictureInPicture) { + val params = _viewDetail?.getPictureInPictureParams(); + if (params != null) + activity?.setPictureInPictureParams(params); + } + }; + it.onEnterPictureInPicture.subscribe { + Logger.i(TAG, "onEnterPictureInPicture") + isInPictureInPicture = true; + _viewDetail?.handleEnterPictureInPicture(); + _viewDetail?.invalidate(); + }; + it.onTouchCancel.subscribe { + val v = _view ?: return@subscribe; + if (v.progress >= 0.5 && v.progress < 1) { + maximizeVideoDetail(); + } + if (v.progress < 0.5 && v.progress > 0) { + minimizeVideoDetail(); + } + }; + } + _view!!.setTransitionListener(object : MotionLayout.TransitionListener { + override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) { + + if (state != State.MINIMIZED && progress < 0.1) { + state = State.MINIMIZED; + onMinimize.emit(); + } + else if (state != State.MAXIMIZED && progress > 0.9) { + if (_isInitialMaximize) { + state = State.CLOSED; + _isInitialMaximize = false; + } + else { + state = State.MAXIMIZED; + onMaximized.emit(); + } + } + + if (isTransitioning && (progress > 0.95 || progress < 0.05)) { + isTransitioning = false; + onTransitioning.emit(isTransitioning); + + if(isInPictureInPicture) leavePictureInPictureMode(false); //Workaround to prevent getting stuck in p2p + } + else if (!isTransitioning && (progress < 0.95 && progress > 0.05)) { + isTransitioning = true; + onTransitioning.emit(isTransitioning); + + if(isInPictureInPicture) leavePictureInPictureMode(false); //Workaround to prevent getting stuck in p2p + } + } + override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) { } + override fun onTransitionStarted(p0: MotionLayout?, p1: Int, p2: Int) { } + override fun onTransitionTrigger(p0: MotionLayout?, p1: Int, p2: Boolean, p3: Float) { } + }); + + context + _view?.let { + if (it.progress >= 0.5 && it.progress < 1.0) + maximizeVideoDetail(); + if (it.progress < 0.5 && it.progress > 0.0) + minimizeVideoDetail(); + } + + _loadUrlOnCreate?.let { _viewDetail?.setVideo(it.url, it.timeSeconds, it.playWhenReady) }; + + maximizeVideoDetail(); + return _view!!; + } + + fun onUserLeaveHint() { + val viewDetail = _viewDetail; + Logger.i(TAG, "onUserLeaveHint preventPictureInPicture=${viewDetail?.preventPictureInPicture} isCasting=${StateCasting.instance.isCasting} isBackgroundPictureInPicture=${Settings.instance.playback.isBackgroundPictureInPicture()} allowBackground=${viewDetail?.allowBackground}"); + + if(viewDetail?.preventPictureInPicture == false && !StateCasting.instance.isCasting && Settings.instance.playback.isBackgroundPictureInPicture() && viewDetail?.allowBackground != true) { + _leavingPiP = false; + + val params = _viewDetail?.getPictureInPictureParams(); + if(params != null) { + Logger.i(TAG, "enterPictureInPictureMode") + activity?.enterPictureInPictureMode(params); + } + } + } + + fun forcePictureInPicture() { + val params = _viewDetail?.getPictureInPictureParams(); + if(params != null) + activity?.enterPictureInPictureMode(params); + } + fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, isStop: Boolean, newConfig: Configuration) { + if (isInPictureInPictureMode) { + _viewDetail?.startPictureInPicture(); + } else if (isInPictureInPicture) { + leavePictureInPictureMode(isStop); + } + } + fun leavePictureInPictureMode(isStop: Boolean) { + isInPictureInPicture = false; + _leavingPiP = true; + + UIDialogs.dismissAllDialogs(); + + _viewDetail?.handleLeavePictureInPicture(); + if (isStop) { + stopIfRequired(); + } + } + + override fun onResume() { + super.onResume(); + Logger.i(TAG, "onResume"); + _isActive = true; + _leavingPiP = false; + + _viewDetail?.let { + Logger.i(TAG, "onResume preventPictureInPicture=false"); + it.preventPictureInPicture = false; + + if (state != State.CLOSED) { + it.onResume(); + } + } + + val realOrientation = if(activity is MainActivity) (activity as MainActivity).orientation else lastOrientation; + Logger.i(TAG, "Real orientation on boot ${realOrientation}, lastOrientation: ${lastOrientation}"); + if(realOrientation != lastOrientation) + onOrientationChanged(realOrientation); + } + override fun onPause() { + super.onPause(); + Logger.i(TAG, "onPause"); + _isActive = false; + + if(!isInPictureInPicture && state != State.CLOSED) + _viewDetail?.onPause(); + } + + override fun onStop() { + Logger.i(TAG, "onStop"); + + stopIfRequired(); + super.onStop(); + } + + private fun stopIfRequired() { + var shouldStop = true; + if (_viewDetail?.allowBackground == true) { + shouldStop = false; + } else if (Settings.instance.playback.isBackgroundPictureInPicture() && !_leavingPiP) { + shouldStop = false; + } else if (Settings.instance.playback.isBackgroundContinue()) { + shouldStop = false; + } else if (StateCasting.instance.isCasting) { + shouldStop = false; + } + + Logger.i(TAG, "shouldStop: $shouldStop"); + if(shouldStop) { + _viewDetail?.let { + val v = it.video ?: return@let; + StateSaved.instance.setVideoToOpenBlocking(VideoToOpen(v.url, (it.lastPositionMilliseconds / 1000.0f).toLong())); + } + + _viewDetail?.onStop(); + StateCasting.instance.onStop(); + Logger.i(TAG, "called onStop() shouldStop: $shouldStop"); + } + } + + override fun onDestroyMainView() { + super.onDestroyMainView(); + Logger.i(TAG, "onDestroyMainView"); + _viewDetail?.let { + _viewDetail = null; + it.onDestroy(); + } + _view = null; + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + StateCasting.instance.onActiveDeviceConnectionStateChanged.subscribe(this) { _, _ -> + onOrientationChanged(lastOrientation); + }; + } + + override fun onDestroy() { + super.onDestroy() + + _viewDetail?.let { + _viewDetail = null; + it.onDestroy(); + } + + StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this); + + Logger.i(TAG, "onDestroy"); + onMinimize.clear(); + onMaximized.clear(); + } + + private fun onFullscreenChanged(fullscreen : Boolean) { + activity?.let { + if (fullscreen) { + var orient = lastOrientation; + if(orient == OrientationManager.Orientation.PORTRAIT || orient == OrientationManager.Orientation.REVERSED_PORTRAIT) + orient = OrientationManager.Orientation.LANDSCAPE; + changeOrientation(orient); + } + else + changeOrientation(OrientationManager.Orientation.PORTRAIT); + } + isFullscreen = fullscreen; + } + private fun changeOrientation(orientation: OrientationManager.Orientation) { + Logger.i(TAG, "Orientation Change:" + orientation.name); + activity?.let { + when (orientation) { + OrientationManager.Orientation.LANDSCAPE -> { + it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; + _view?.allowMotion = false; + + WindowCompat.setDecorFitsSystemWindows(requireActivity().window, false) + WindowInsetsControllerCompat(it.window, _viewDetail!!).let { controller -> + controller.hide(WindowInsetsCompat.Type.statusBars()); + controller.hide(WindowInsetsCompat.Type.systemBars()); + controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; + } + } + OrientationManager.Orientation.REVERSED_LANDSCAPE -> { + it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; + _view?.allowMotion = false; + + WindowCompat.setDecorFitsSystemWindows(requireActivity().window, false) + WindowInsetsControllerCompat(it.window, _viewDetail!!).let { controller -> + controller.hide(WindowInsetsCompat.Type.statusBars()); + controller.hide(WindowInsetsCompat.Type.systemBars()); + controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; + } + } + else -> { + it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; + _view?.allowMotion = true; + + WindowCompat.setDecorFitsSystemWindows(it.window, true) + WindowInsetsControllerCompat(it.window, _viewDetail!!).let { controller -> + controller.show(WindowInsetsCompat.Type.statusBars()); + controller.show(WindowInsetsCompat.Type.systemBars()) + } + } + } + } + } + + companion object { + private val TAG = "VideoDetailFragment"; + + fun newInstance() = VideoDetailFragment().apply {} + } + + enum class State { + CLOSED, + MINIMIZED, + MAXIMIZED + } + +//endregion + +//region View + //TODO: Determine if encapsulated would be readable enough +//endregion +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt new file mode 100644 index 00000000..0a72510c --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -0,0 +1,2123 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.app.PictureInPictureParams +import android.app.RemoteAction +import android.content.Context +import android.content.Intent +import android.content.res.Resources +import android.graphics.Bitmap +import android.graphics.Rect +import android.graphics.drawable.Animatable +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.graphics.drawable.Icon +import android.net.Uri +import android.provider.Browser +import android.support.v4.media.session.PlaybackStateCompat +import android.text.Spanned +import android.util.AttributeSet +import android.util.Log +import android.util.Rational +import android.util.TypedValue +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.widget.* +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.lifecycle.lifecycleScope +import com.bumptech.glide.Glide +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.futo.platformplayer.* + +import com.futo.platformplayer.api.media.IPluginSourced +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.LiveChatManager +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.exceptions.ContentNotAvailableYetException +import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException +import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment +import com.futo.platformplayer.api.media.models.live.ILiveChatWindowDescriptor +import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent +import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker +import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes +import com.futo.platformplayer.api.media.models.ratings.RatingLikes +import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor +import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails +import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo +import com.futo.platformplayer.api.media.platforms.js.models.JSVideoDetails +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.casting.CastConnectionState +import com.futo.platformplayer.casting.StateCasting +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.downloads.VideoLocal +import com.futo.platformplayer.engine.exceptions.ScriptAgeException +import com.futo.platformplayer.engine.exceptions.ScriptException +import com.futo.platformplayer.engine.exceptions.ScriptImplementationException +import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException +import com.futo.platformplayer.helpers.VideoHelper +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.polycentric.PolycentricCache +import com.futo.platformplayer.receivers.MediaControlReceiver +import com.futo.platformplayer.states.* +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.StringArrayStorage +import com.futo.platformplayer.views.behavior.TouchInterceptFrameLayout +import com.futo.platformplayer.views.casting.CastView +import com.futo.platformplayer.views.comments.AddCommentView +import com.futo.platformplayer.views.others.CreatorThumbnail +import com.futo.platformplayer.views.others.Toggle +import com.futo.platformplayer.views.overlays.DescriptionOverlay +import com.futo.platformplayer.views.overlays.LiveChatOverlay +import com.futo.platformplayer.views.overlays.QueueEditorOverlay +import com.futo.platformplayer.views.overlays.RepliesOverlay +import com.futo.platformplayer.views.overlays.slideup.* +import com.futo.platformplayer.views.pills.PillRatingLikesDislikes +import com.futo.platformplayer.views.pills.RoundButton +import com.futo.platformplayer.views.pills.RoundButtonGroup +import com.futo.platformplayer.views.platform.PlatformIndicator +import com.futo.platformplayer.views.segments.CommentsList +import com.futo.platformplayer.views.subscriptions.SubscribeButton +import com.futo.platformplayer.views.video.FutoVideoPlayer +import com.futo.platformplayer.views.video.FutoVideoPlayerBase +import com.futo.platformplayer.views.videometa.UpNextView +import com.futo.polycentric.core.* +import com.google.android.exoplayer2.C +import com.google.android.exoplayer2.Format +import com.google.android.exoplayer2.ui.PlayerControlView +import com.google.android.exoplayer2.ui.TimeBar +import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException +import com.google.protobuf.ByteString +import kotlinx.coroutines.* +import userpackage.Protocol +import java.time.OffsetDateTime +import kotlin.collections.ArrayList +import kotlin.math.roundToLong +import kotlin.streams.toList + + +class VideoDetailView : ConstraintLayout { + private val TAG = "VideoDetailView" + + lateinit var fragment: VideoDetailFragment; + + private var _destroyed = false; + + private var _url: String? = null; + private var _playWhenReady = true; + private var _searchVideo: IPlatformVideo? = null; + var video: IPlatformVideoDetails? = null + private set; + private var _playbackTracker: IPlaybackTracker? = null; + + val currentUrl get() = video?.url ?: _searchVideo?.url ?: _url; + + private var _liveChat: LiveChatManager? = null; + private var _videoResumePositionMilliseconds : Long = 0L; + + private val _player: FutoVideoPlayer; + private val _cast: CastView; + private val _playerProgress: PlayerControlView; + private val _timeBar: TimeBar; + private var _upNext: UpNextView; + + val rootView: ConstraintLayout; + + private val _title: TextView; + private val _subTitle: TextView; + private val _description: TextView; + private val _descriptionContainer: LinearLayout; + + private val _platform: PlatformIndicator; + + private val _channelName: TextView; + private val _channelMeta: TextView; + private val _creatorThumbnail: CreatorThumbnail; + private val _channelButton: LinearLayout; + + private val _description_viewMore: TextView; + + private val _overlay_loading: FrameLayout; + private val _overlay_loading_spinner: ImageView; + private val _rating: PillRatingLikesDislikes; + + private val _minimize_controls: LinearLayout; + private val _minimize_controls_play: ImageButton; + private val _minimize_controls_pause: ImageButton; + private val _minimize_controls_close: ImageButton; + private val _minimize_title: TextView; + private val _minimize_meta: TextView; + + private val _commentsList: CommentsList; + + private var _minimizeProgress: Float = 0f; + private val _buttonSubscribe: SubscribeButton; + + private val _buttonPins: RoundButtonGroup; + //private val _buttonMore: RoundButton; + + var preventPictureInPicture: Boolean = false; + + private val _textComments: TextView; + private val _textCommentType: TextView; + private val _addCommentView: AddCommentView; + private val _toggleCommentType: Toggle; + + private val _textResume: TextView; + private val _layoutResume: LinearLayout; + private var _jobHideResume: Job? = null; + private val _layoutPlayerContainer: TouchInterceptFrameLayout; + + //Overlays + private val _overlayContainer: FrameLayout; + private val _overlay_quality_container: FrameLayout; + private var _overlay_quality_selector: SlideUpMenuOverlay? = null; + + //Bottom Containers + private val _container_content: FrameLayout; + private val _container_content_main: FrameLayout; + private val _container_content_queue: QueueEditorOverlay; + private val _container_content_replies: RepliesOverlay; + private val _container_content_description: DescriptionOverlay; + private val _container_content_liveChat: LiveChatOverlay; + + private var _container_content_current: View; + + private val _textLikes: TextView; + private val _textDislikes: TextView; + private val _layoutRating: LinearLayout; + private val _imageDislikeIcon: ImageView; + private val _imageLikeIcon: ImageView; + + private val _buttonSupport: LinearLayout; + private val _buttonStore: LinearLayout; + private val _layoutMonetization: LinearLayout; + + private val _buttonMore: RoundButton; + + private var _didStop: Boolean = false; + private var _onPauseCalled = false; + private var _lastVideoSource: IVideoSource? = null; + private var _lastAudioSource: IAudioSource? = null; + private var _lastSubtitleSource: ISubtitleSource? = null; + private var _isCasting: Boolean = false; + var lastPositionMilliseconds: Long = 0 + private set; + private var _historicalPosition: Long = 0; + private var _commentsCount = 0; + private var _polycentricProfile: PolycentricCache.CachedPolycentricProfile? = null; + private var _slideUpOverlay: SlideUpMenuOverlay? = null; + + //Events + val onMinimize = Event0(); + val onMaximize = Event1(); + val onClose = Event0(); + val onFullscreenChanged = Event1(); + val onEnterPictureInPicture = Event0(); + val onPlayChanged = Event1(); + + var allowBackground : Boolean = false + private set; + + val onTouchCancel = Event0(); + private var _lastPositionSaveTime: Long = -1; + + private val DP_5 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics); + private val DP_2 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2f, resources.displayMetrics); + private var _retryJob: Job? = null; + private var _retryCount = 0; + private val _retryIntervals: Array = arrayOf(1, 2, 4, 8, 16, 32); + + constructor(context: Context, attrs : AttributeSet? = null) : super(context, attrs) { + inflate(context, R.layout.fragview_video_detail, this); + + //Declare Views + rootView = findViewById(R.id.videodetail_root); + _cast = findViewById(R.id.videodetail_cast); + _player = findViewById(R.id.videodetail_player); + _playerProgress = findViewById(R.id.videodetail_progress); + _timeBar = _playerProgress.findViewById(com.google.android.exoplayer2.ui.R.id.exo_progress); + _title = findViewById(R.id.videodetail_title); + _subTitle = findViewById(R.id.videodetail_meta); + _platform = findViewById(R.id.videodetail_platform); + _description = findViewById(R.id.videodetail_description); + _descriptionContainer = findViewById(R.id.videodetail_description_container); + _channelName = findViewById(R.id.videodetail_channel_name); + _channelMeta = findViewById(R.id.videodetail_channel_meta); + _creatorThumbnail = findViewById(R.id.creator_thumbnail); + _channelButton = findViewById(R.id.videodetail_channel_button); + _description_viewMore = findViewById(R.id.videodetail_description_view_more); + _overlay_loading = findViewById(R.id.videodetail_loading_overlay); + _overlay_loading_spinner = findViewById(R.id.videodetail_loader); + _rating = findViewById(R.id.videodetail_rating); + _upNext = findViewById(R.id.up_next); + _textCommentType = findViewById(R.id.text_comment_type); + _toggleCommentType = findViewById(R.id.toggle_comment_type); + + _overlayContainer = findViewById(R.id.overlay_container); + _overlay_quality_container = findViewById(R.id.videodetail_quality_overview); + + _minimize_controls = findViewById(R.id.minimize_controls); + _minimize_controls_pause = findViewById(R.id.minimize_pause); + _minimize_controls_close = findViewById(R.id.minimize_close); + _minimize_controls_play = findViewById(R.id.minimize_play); + _minimize_title = findViewById(R.id.videodetail_title_minimized); + _minimize_meta = findViewById(R.id.videodetail_meta_minimized); + _buttonSubscribe = findViewById(R.id.button_subscribe); + + _container_content = findViewById(R.id.contentContainer); + _container_content_main = findViewById(R.id.videodetail_container_main); + _container_content_queue = findViewById(R.id.videodetail_container_queue); + _container_content_replies = findViewById(R.id.videodetail_container_replies); + _container_content_description = findViewById(R.id.videodetail_container_description); + _container_content_liveChat = findViewById(R.id.videodetail_container_livechat); + + _textComments = findViewById(R.id.text_comments); + _addCommentView = findViewById(R.id.add_comment_view); + _commentsList = findViewById(R.id.comments_list); + + _layoutResume = findViewById(R.id.layout_resume); + _textResume = findViewById(R.id.text_resume); + _layoutPlayerContainer = findViewById(R.id.layout_player_container); + _layoutPlayerContainer.onClick.subscribe { onMaximize.emit(false); }; + + _layoutRating = findViewById(R.id.layout_rating); + _textDislikes = findViewById(R.id.text_dislikes); + _textLikes = findViewById(R.id.text_likes); + _imageLikeIcon = findViewById(R.id.image_like_icon); + _imageDislikeIcon = findViewById(R.id.image_dislike_icon); + + _buttonSupport = findViewById(R.id.button_support); + _buttonStore = findViewById(R.id.button_store); + _layoutMonetization = findViewById(R.id.layout_monetization); + + _layoutMonetization.visibility = View.GONE; + _player.attachPlayer(); + + _container_content_liveChat.onRaidNow.subscribe { + fragment.navigate(it.targetUrl); + }; + + _buttonSupport.setOnClickListener { + val author = video?.author ?: _searchVideo?.author; + author?.let { fragment.navigate(it).selectTab(2); }; + fragment.lifecycleScope.launch { + delay(100); + fragment.minimizeVideoDetail(); + }; + }; + + _buttonStore.setOnClickListener { + _polycentricProfile?.profile?.systemState?.store?.let { + try { + val uri = Uri.parse(it); + val intent = Intent(Intent.ACTION_VIEW); + intent.data = uri; + context.startActivity(intent); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to open URI: '${it}'.", e); + } + } + }; + + StateApp.instance.preventPictureInPicture.subscribe(this) { + Logger.i(TAG, "StateApp.instance.preventPictureInPicture.subscribe preventPictureInPicture = true"); + preventPictureInPicture = true; + }; + + _addCommentView.onCommentAdded.subscribe { + _commentsList.addComment(it); + } + + _commentsList.onCommentsLoaded.subscribe { count -> + _commentsCount = count; + updateCommentType(false); + }; + + _toggleCommentType.onValueChanged.subscribe { + updateCommentType(true); + }; + + _textCommentType.setOnClickListener { + _toggleCommentType.setValue(!_toggleCommentType.value, true); + updateCommentType(true); + }; + + val layoutTop: LinearLayout = findViewById(R.id.layout_top); + _container_content_main.removeView(layoutTop); + _commentsList.setPrependedView(layoutTop); + + _buttonPins = layoutTop.findViewById(R.id.buttons_pins); + _buttonPins.alwaysShowLastButton = true; + + var buttonMore: RoundButton? = null; + buttonMore = RoundButton(context, R.drawable.ic_menu, "More", TAG_MORE) { + _slideUpOverlay = UISlideOverlays.showMoreButtonOverlay(_overlayContainer, _buttonPins, listOf(TAG_MORE)) {selected -> + _buttonPins.setButtons(*(selected + listOf(buttonMore!!)).toTypedArray()); + _buttonPinStore.set(*selected.filter { it.tagRef is String }.map{ it.tagRef as String }.toTypedArray()) + _buttonPinStore.save(); + } + }; + _buttonMore = buttonMore; + updateMoreButtons(); + + + _channelButton.setOnClickListener { + (video?.author ?: _searchVideo?.author)?.let { + fragment.navigate(it); + fragment.lifecycleScope.launch { + delay(100); + fragment.minimizeVideoDetail(); + }; + }; + }; + + _rating.visibility = View.GONE; + + _cast.onSettingsClick.subscribe { showVideoSettings() }; + _player.onVideoSettings.subscribe { showVideoSettings() }; + _player.onToggleFullScreen.subscribe(::handleFullScreen); + _cast.onMinimizeClick.subscribe { + _player.setFullScreen(false); + onMinimize.emit(); + }; + _player.onMinimize.subscribe { + _player.setFullScreen(false); + onMinimize.emit(); + }; + + _player.onTimeBarChanged.subscribe { position, _ -> + if (!_isCasting && !_didStop) { + setLastPositionMilliseconds(position, true); + } + }; + + _player.onVideoClicked.subscribe { + if(_minimizeProgress < 0.5) + onMaximize.emit(false); + } + _player.onSourceChanged.subscribe(::onSourceChanged); + _player.onSourceEnded.subscribe { + if (!fragment.isInPictureInPicture) { + _player.gestureControl.showControls(false); + } + + _player.setIsReplay(true); + + val searchVideo = StatePlayer.instance.getCurrentQueueItem(); + if (searchVideo is SerializedPlatformVideo?) { + searchVideo?.let { StatePlaylists.instance.removeFromWatchLater(it) }; + } + + nextVideo(); + }; + _player.onDatasourceError.subscribe(::onDataSourceError); + + _minimize_controls_play.setOnClickListener { handlePlay(); }; + _minimize_controls_pause.setOnClickListener { handlePause(); }; + _minimize_controls_close.setOnClickListener { onClose.emit(); }; + _minimize_title.setOnClickListener { onMaximize.emit(false) }; + _minimize_meta.setOnClickListener { onMaximize.emit(false) }; + + _player.onPlayChanged.subscribe { + if (StateCasting.instance.activeDevice == null) { + handlePlayChanged(it); + } + }; + + if (!isInEditMode) { + StateCasting.instance.onActiveDeviceConnectionStateChanged.subscribe(this) { d, connectionState -> + if (_onPauseCalled) { + return@subscribe; + } + + when (connectionState) { + CastConnectionState.CONNECTED -> { + loadCurrentVideo(lastPositionMilliseconds); + updatePillButtonVisibilities(); + setCastEnabled(true); + } + CastConnectionState.DISCONNECTED -> { + loadCurrentVideo(lastPositionMilliseconds); + updatePillButtonVisibilities(); + setCastEnabled(false); + } + else -> {} + } + }; + + updatePillButtonVisibilities(); + + StateCasting.instance.onActiveDevicePlayChanged.subscribe(this) { + if (StateCasting.instance.activeDevice != null) { + handlePlayChanged(it); + } + }; + + StateCasting.instance.onActiveDeviceTimeChanged.subscribe(this) { + if (_isCasting) { + setLastPositionMilliseconds((it * 1000.0).toLong(), true); + _cast.setTime(lastPositionMilliseconds); + _timeBar.setPosition(it.toLong()); + _timeBar.setBufferedPosition(0); + _timeBar.setDuration(video?.duration ?: 0); + } + }; + } + + _playerProgress.player = _player.exoPlayer?.player; + _playerProgress.setProgressUpdateListener { position, bufferedPosition -> + StatePlayer.instance.updateMediaSessionPlaybackState(_player.exoPlayer?.getPlaybackStateCompat() ?: PlaybackStateCompat.STATE_NONE, position); + } + + StatePlayer.instance.onQueueChanged.subscribe(this) { + if(!_destroyed) { + updateQueueState(); + StatePlayer.instance.updateMediaSession(null); + } + }; + StatePlayer.instance.onVideoChanging.subscribe(this) { + setVideoOverview(it); + }; + MediaControlReceiver.onLowerVolumeReceived.subscribe(this) { handleLowerVolume() }; + MediaControlReceiver.onPlayReceived.subscribe(this) { handlePlay() }; + MediaControlReceiver.onPauseReceived.subscribe(this) { handlePause() }; + MediaControlReceiver.onNextReceived.subscribe(this) { nextVideo() }; + MediaControlReceiver.onPreviousReceived.subscribe(this) { prevVideo() }; + MediaControlReceiver.onCloseReceived.subscribe(this) { + Logger.i(TAG, "MediaControlReceiver.onCloseReceived") + onClose.emit() + }; + MediaControlReceiver.onSeekToReceived.subscribe(this) { handleSeek(it); }; + + _container_content_description.onClose.subscribe { switchContentView(_container_content_main); }; + _container_content_liveChat.onClose.subscribe { switchContentView(_container_content_main); }; + _container_content_queue.onClose.subscribe { switchContentView(_container_content_main); }; + _container_content_replies.onClose.subscribe { switchContentView(_container_content_main); }; + + _description_viewMore.setOnClickListener { + switchContentView(_container_content_description); + }; + + _upNext.onNextItem.subscribe { + val item = StatePlayer.instance.nextQueueItem(); + if(item != null) + setVideoOverview(item, true); + }; + _upNext.onOpenQueueClick.subscribe { + _container_content_queue.updateQueue(); + switchContentView(_container_content_queue); + }; + _upNext.onRestartQueue.subscribe { + val item = StatePlayer.instance.restartQueue(); + if(item != null) + setVideoOverview(item, true); + }; + + _container_content_current = _container_content_main; + + _commentsList.onClick.subscribe { c -> + val replyCount = c.replyCount ?: 0; + var metadata = ""; + if (replyCount > 0) { + metadata += "$replyCount replies"; + } + + if (c is PolycentricPlatformComment) { + var parentComment: PolycentricPlatformComment = c; + _container_content_replies.load(_toggleCommentType.value, metadata, c.contextUrl, c.reference, + { StatePolycentric.instance.getCommentPager(c.contextUrl, c.reference) }, + { + val newComment = parentComment.cloneWithUpdatedReplyCount((parentComment.replyCount ?: 0) + 1); + _commentsList.replaceComment(parentComment, newComment); + parentComment = newComment; + }); + } else { + _container_content_replies.load(_toggleCommentType.value, metadata, null, null, { StatePlatform.instance.getSubComments(c) }); + } + switchContentView(_container_content_replies); + }; + + onClose.subscribe { + _lastVideoSource = null; + _lastAudioSource = null; + _lastSubtitleSource = null; + video = null; + _playbackTracker = null; + }; + + _layoutResume.setOnClickListener { + handleSeek(_historicalPosition * 1000); + + val job = _jobHideResume; + _jobHideResume = null; + job?.cancel(); + + _layoutResume.visibility = View.GONE; + }; + } + + fun updateMoreButtons() { + val buttons = listOf(RoundButton(context, R.drawable.ic_add, "Add", TAG_ADD) { + (video ?: _searchVideo)?.let { + _slideUpOverlay = UISlideOverlays.showAddToOverlay(it, _overlayContainer); + } + }, + if(video?.isLive ?: false) + RoundButton(context, R.drawable.ic_chat, "Live Chat", TAG_LIVECHAT) { + video?.let { + try { + loadLiveChat(it); + } + catch(ex: Throwable) { + Logger.e(TAG, "Failed to reopen live chat", ex); + } + } + } else null, + RoundButton(context, R.drawable.ic_screen_share, "Background", TAG_BACKGROUND) { + if(!allowBackground) { + _player.switchToAudioMode(); + allowBackground = true; + it.text.text = resources.getString(R.string.background_revert); + } + else { + _player.switchToVideoMode(); + allowBackground = false; + it.text.text = resources.getString(R.string.background); + } + }, + RoundButton(context, R.drawable.ic_download, "Download", TAG_DOWNLOAD) { + video?.let { + _slideUpOverlay = UISlideOverlays.showDownloadVideoOverlay(context.contentResolver, it, _overlayContainer); + }; + }, + RoundButton(context, R.drawable.ic_share, "Share", TAG_SHARE) { + video?.let { + Logger.i(TAG, "Share preventPictureInPicture = true"); + preventPictureInPicture = true; + shareVideo(); + }; + }, + RoundButton(context, R.drawable.ic_screen_share, "Overlay", TAG_OVERLAY) { + this.startPictureInPicture(); + fragment.forcePictureInPicture(); + //PiPActivity.startPiP(context); + }, + RoundButton(context, R.drawable.ic_export, "Page", TAG_OPEN) { + video?.let { + val url = video?.shareUrl ?: _searchVideo?.shareUrl ?: _url; + fragment.navigate(url); + fragment.minimizeVideoDetail(); + }; + }, + RoundButton(context, R.drawable.ic_refresh, "Reload", "Reload") { + reloadVideo(); + }).filterNotNull(); + if(!_buttonPinStore.getAllValues().any()) + _buttonPins.setButtons(*(buttons + listOf(_buttonMore)).toTypedArray()); + else { + val selectedButtons = _buttonPinStore.getAllValues() + .map { x-> buttons.find { it.tagRef == x } } + .filter { it != null } + .map { it!! }; + _buttonPins.setButtons(*(selectedButtons + + buttons.filter { !selectedButtons.contains(it) } + + listOf(_buttonMore)).toTypedArray()); + } + } + + fun reloadVideo() { + fragment.lifecycleScope.launch (Dispatchers.IO) { + video?.let { + Logger.i(TAG, "Reloading video"); + try { + val video = StatePlatform.instance.getContentDetails(it.url, true).await(); + if(video !is IPlatformVideoDetails) + throw IllegalStateException("Expected media content, found ${video.contentType}"); + + withContext(Dispatchers.Main) { + setVideoDetails(video); + } + } + catch(ex: Throwable) { + withContext(Dispatchers.Main) { + UIDialogs.showGeneralErrorDialog(context, ex.message ?: "", ex); + } + } + } + } + } + + + //Lifecycle + fun onResume() { + Logger.i(TAG, "onResume"); + _onPauseCalled = false; + + Logger.i(TAG, "_video: ${video?.name ?: "no video"}"); + Logger.i(TAG, "_didStop: $_didStop"); + + //Recover cancelled loads + if(video == null) { + val t = (lastPositionMilliseconds / 1000.0f).roundToLong(); + if(_searchVideo != null) + setVideoOverview(_searchVideo!!, true, t); + else if(_url != null) + setVideo(_url!!, t, _playWhenReady); + } + else if(_didStop) { + _didStop = false; + Logger.i(TAG, "loadCurrentVideo _lastPosition=${lastPositionMilliseconds}"); + loadCurrentVideo(lastPositionMilliseconds); + handlePause(); + } + + if(_player.isAudioMode) { + //Requested behavior to leave it in audio mode. leaving it commented if it causes issues, revert? + if(!allowBackground) { + _player.switchToVideoMode(); + _buttonPins.getButtonByTag(TAG_BACKGROUND)?.text?.text = resources.getString(R.string.background); + } + } + if(!_player.isFitMode && !_player.isFullScreen && !fragment.isInPictureInPicture) + _player.fitHeight(); + + _player.updateRotateLock(); + } + fun onPause() { + Logger.i(TAG, "onPause"); + + _onPauseCalled = true; + _taskLoadVideo.cancel(); + + if(StateCasting.instance.isCasting) + return; + + if(allowBackground) + StatePlayer.instance.startOrUpdateMediaSession(context, video); + else { + when (Settings.instance.playback.backgroundPlay) { + 0 -> handlePause(); + 1 -> { + if(!(video?.isLive ?: false)) + _player.switchToAudioMode(); + StatePlayer.instance.startOrUpdateMediaSession(context, video); + } + } + } + } + fun onStop() { + Logger.i(TAG, "onStop"); + _player.clear(); + StatePlayer.instance.closeMediaSession(); + _overlay_quality_selector?.hide(); + _retryJob?.cancel(); + _retryJob = null; + _taskLoadVideo.cancel(); + handleStop(); + _didStop = true; + Logger.i(TAG, "_didStop set to true"); + + StatePlayer.instance.rotationLock = false; + _player.updateRotateLock(); + Logger.i(TAG, "Stopped"); + } + fun onDestroy() { + Logger.i(TAG, "onDestroy"); + _destroyed = true; + _taskLoadVideo.cancel(); + _commentsList.cancel(); + _player.clear(); + _cast.cleanup(); + _container_content_replies.cleanup(); + _container_content_queue.cleanup(); + _container_content_description.cleanup(); + StateCasting.instance.onActiveDevicePlayChanged.remove(this); + StateCasting.instance.onActiveDeviceTimeChanged.remove(this); + StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this); + StateApp.instance.preventPictureInPicture.remove(this); + StatePlayer.instance.onQueueChanged.remove(this); + StatePlayer.instance.onVideoChanging.remove(this); + MediaControlReceiver.onLowerVolumeReceived.remove(this); + MediaControlReceiver.onPlayReceived.remove(this); + MediaControlReceiver.onPauseReceived.remove(this); + MediaControlReceiver.onNextReceived.remove(this); + MediaControlReceiver.onPreviousReceived.remove(this); + MediaControlReceiver.onCloseReceived.remove(this); + MediaControlReceiver.onSeekToReceived.remove(this); + + val job = _jobHideResume; + _jobHideResume = null; + job?.cancel(); + } + + //Video Setters + fun setEmpty() { + Logger.i(TAG, "setEmpty") + + _title.text = ""; + _rating.visibility = View.GONE; + + _commentsList.clear(); + _minimize_title.text = ""; + _minimize_meta.text = ""; + _platform.clearPlatform(); + _subTitle.text = ""; + _channelName.text = ""; + _channelMeta.text = ""; + _creatorThumbnail.clear(); + setDescription("".fixHtmlWhitespace()); + _descriptionContainer.visibility = View.GONE; + _player.clear(); + _textComments.visibility = View.INVISIBLE; + _commentsList.clear(); + + _lastVideoSource = null; + _lastAudioSource = null; + _lastSubtitleSource = null; + } + fun setVideo(url: String, resumeSeconds: Long = 0, playWhenReady: Boolean = true) { + Logger.i(TAG, "setVideo url=$url resumeSeconds=$resumeSeconds playWhenReady=$playWhenReady") + + _searchVideo = null; + video = null; + _playbackTracker = null; + _url = url; + _videoResumePositionMilliseconds = resumeSeconds * 1000; + _rating.visibility = View.GONE; + _layoutRating.visibility = View.GONE; + _playWhenReady = playWhenReady; + setLastPositionMilliseconds(_videoResumePositionMilliseconds, false); + _addCommentView.setContext(null, null); + + _toggleCommentType.setValue(false, false); + _commentsList.clear(); + + setEmpty(); + + updateQueueState(); + + _retryJob?.cancel(); + _retryJob = null; + _retryCount = 0; + fetchVideo(); + + switchContentView(_container_content_main); + } + fun setVideoOverview(video: IPlatformVideo, fetch: Boolean = true, resumeSeconds: Long = 0) { + Logger.i(TAG, "setVideoOverview") + + val cachedVideo = StateDownloads.instance.getCachedVideo(video.id); + if(cachedVideo != null) { + setVideoDetails(cachedVideo, true); + return; + } + + this.video = null; + this._playbackTracker = null; + _searchVideo = video; + _videoResumePositionMilliseconds = resumeSeconds * 1000; + setLastPositionMilliseconds(_videoResumePositionMilliseconds, false); + _addCommentView.setContext(null, null); + + _toggleCommentType.setValue(false, false); + + _title.text = video.name; + _rating.visibility = View.GONE; + _layoutRating.visibility = View.GONE; + _textComments.visibility = View.VISIBLE; + + _minimize_title.text = video.name; + _minimize_meta.text = video.author.name; + + val subTitleSegments : ArrayList = ArrayList(); + if(video.viewCount > 0) + subTitleSegments.add("${video.viewCount.toHumanNumber()} ${if(video.isLive) "watching now" else "views"}"); + if(video.datetime != null) { + val diff = video.datetime?.getNowDiffSeconds() ?: 0; + val ago = video.datetime?.toHumanNowDiffString(true) + if(diff >= 0) + subTitleSegments.add("${ago} ago"); + else + subTitleSegments.add("available in ${ago}"); + } + + + _commentsList.clear(); + _platform.setPlatformFromClientID(video.id.pluginId); + _subTitle.text = subTitleSegments.joinToString(" • "); + _channelName.text = video.author.name; + _playWhenReady = true; + if(video.author.subscribers != null) { + _channelMeta.text = video.author.subscribers!!.toHumanNumber() + " subscribers"; + (_channelName.layoutParams as MarginLayoutParams).setMargins(0, (DP_5 * -1).toInt(), 0, 0); + } else { + _channelMeta.text = ""; + (_channelName.layoutParams as MarginLayoutParams).setMargins(0, (DP_2).toInt(), 0, 0); + } + setDescription("".fixHtmlWhitespace()); + _player.setMetadata(video.name, video.author.name); + + _buttonSubscribe.setSubscribeChannel(video.author.url); + + if(!_description.text.isEmpty()) + _descriptionContainer.visibility = View.VISIBLE; + else + _descriptionContainer.visibility = View.GONE; + + _creatorThumbnail.setThumbnail(video.author.thumbnail, false); + + val cachedPolycentricProfile = PolycentricCache.instance.getCachedProfile(video.author.url); + if (cachedPolycentricProfile != null) { + setPolycentricProfile(cachedPolycentricProfile, animate = false); + } else { + setPolycentricProfile(null, animate = false); + _taskLoadPolycentricProfile.run(video.author.id); + } + + _player.clear(); + + _url = video.url; + + updateQueueState(); + + if(fetch) { + _lastVideoSource = null; + _lastAudioSource = null; + _lastSubtitleSource = null; + + _retryJob?.cancel(); + _retryJob = null; + _retryCount = 0; + fetchVideo(); + } + + _commentsList.clear(); + + switchContentView(_container_content_main); + } + fun setVideoDetails(videoDetail: IPlatformVideoDetails, newVideo: Boolean = false) { + Logger.i(TAG, "setVideoDetails (${videoDetail.name})") + + if (newVideo) { + _lastVideoSource = null; + _lastAudioSource = null; + _lastSubtitleSource = null; + } + + if(videoDetail.datetime != null && videoDetail.datetime!! > OffsetDateTime.now()) + UIDialogs.toast(context, "Planned in ${videoDetail.datetime?.toHumanNowDiffString(true)}") + + _player.setPlaybackRate(Settings.instance.playback.getDefaultPlaybackSpeed()); + + val video = if(videoDetail is VideoLocal) + videoDetail; + else //TODO: Update cached video if it exists with video + StateDownloads.instance.getCachedVideo(videoDetail.id) ?: videoDetail; + this.video = video; + this._playbackTracker = null; + if(video is JSVideoDetails) { + val me = this; + fragment.lifecycleScope.launch(Dispatchers.IO) { + try { + val tracker = video.getPlaybackTracker() ?: StatePlatform.instance.getPlaybackTracker(video.url); + if(me.video == video) + me._playbackTracker = tracker; + } + catch(ex: Throwable) { + fragment.lifecycleScope.launch(Dispatchers.Main) { + UIDialogs.showGeneralErrorDialog(context, "Failed to get Playback Tracker", ex); + }; + } + }; + } + + val ref = video.id.value?.let { Models.referenceFromBuffer(it.toByteArray()) }; + _addCommentView.setContext(video.url, ref); + + _player.setMetadata(video.name, video.author.name); + + _toggleCommentType.setValue(false, false); + updateCommentType(true); + + //UI + _title.text = video.name; + _channelName.text = video.author.name; + if(video.author.subscribers != null) { + _channelMeta.text = video.author.subscribers!!.toHumanNumber() + " subscribers"; + (_channelName.layoutParams as MarginLayoutParams).setMargins(0, (DP_5 * -1).toInt(), 0, 0); + } else { + _channelMeta.text = ""; + (_channelName.layoutParams as MarginLayoutParams).setMargins(0, (DP_2).toInt(), 0, 0); + } + + _minimize_title.text = video.name; + _minimize_meta.text = video.author.name; + + _buttonSubscribe.setSubscribeChannel(video.author.url); + setDescription(video.description.fixHtmlLinks()); + _creatorThumbnail.setThumbnail(video.author.thumbnail, false); + + val cachedPolycentricProfile = PolycentricCache.instance.getCachedProfile(video.author.url); + if (cachedPolycentricProfile != null) { + setPolycentricProfile(cachedPolycentricProfile, animate = false); + } else { + setPolycentricProfile(null, animate = false); + _taskLoadPolycentricProfile.run(video.author.id); + } + + _platform.setPlatformFromClientID(video.id.pluginId); + val subTitleSegments : ArrayList = ArrayList(); + if(video.viewCount > 0) + subTitleSegments.add("${video.viewCount.toHumanNumber()} ${if(video.isLive) "watching now" else "views"}"); + if(video.datetime != null) { + val diff = video.datetime?.getNowDiffSeconds() ?: 0; + val ago = video.datetime?.toHumanNowDiffString(true) + if(diff >= 0) + subTitleSegments.add("${ago} ago"); + else + subTitleSegments.add("available in ${ago}"); + } + _subTitle.text = subTitleSegments.joinToString(" • "); + + _rating.onLikeDislikeUpdated.remove(this); + if (ref != null) { + _rating.visibility = View.GONE; + + fragment.lifecycleScope.launch(Dispatchers.IO) { + try { + val queryReferencesResponse = ApiMethods.getQueryReferences(PolycentricCache.SERVER, ref, null,null, + arrayListOf( + Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType(ContentType.OPINION.value).setValue( + ByteString.copyFrom(Opinion.like.data)).build(), + Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType(ContentType.OPINION.value).setValue( + ByteString.copyFrom(Opinion.dislike.data)).build() + ) + ); + + val likes = queryReferencesResponse.countsList[0]; + val dislikes = queryReferencesResponse.countsList[1]; + val hasLiked = StatePolycentric.instance.hasLiked(ref); + val hasDisliked = StatePolycentric.instance.hasDisliked(ref); + + withContext(Dispatchers.Main) { + _rating.visibility = View.VISIBLE; + _rating.setRating(RatingLikeDislikes(likes, dislikes), hasLiked, hasDisliked); + _rating.onLikeDislikeUpdated.subscribe(this) { processHandle, newHasLiked, newHasDisliked -> + if (newHasLiked) { + processHandle.opinion(ref, Opinion.like); + } else if (newHasDisliked) { + processHandle.opinion(ref, Opinion.dislike); + } else { + processHandle.opinion(ref, Opinion.neutral); + } + + StateApp.instance.scopeGetter().launch(Dispatchers.IO) { + try { + processHandle.fullyBackfillServers(); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to backfill servers", e) + } + } + + StatePolycentric.instance.updateLikeMap(ref, newHasLiked, newHasDisliked) + }; + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to get polycentric likes/dislikes.", e); + _rating.visibility = View.GONE; + } + } + } else { + _rating.visibility = View.GONE; + } + + if (video.rating != null) { + when (video.rating) { + is RatingLikeDislikes -> { + val r = video.rating as RatingLikeDislikes; + _layoutRating.visibility = View.VISIBLE; + + _textLikes.visibility = View.VISIBLE; + _imageLikeIcon.visibility = View.VISIBLE; + _textLikes.text = r.likes.toHumanNumber(); + + _imageDislikeIcon.visibility = View.VISIBLE; + _textDislikes.visibility = View.VISIBLE; + _textDislikes.text = r.dislikes.toHumanNumber(); + } + is RatingLikes -> { + val r = video.rating as RatingLikes; + _layoutRating.visibility = View.VISIBLE; + + _textLikes.visibility = View.VISIBLE; + _imageLikeIcon.visibility = View.VISIBLE; + _textLikes.text = r.likes.toHumanNumber(); + + _imageDislikeIcon.visibility = View.GONE; + _textDislikes.visibility = View.GONE; + } + else -> { + _layoutRating.visibility = View.GONE; + } + } + } else { + _layoutRating.visibility = View.GONE; + } + + //Overlay + updateQualitySourcesOverlay(video); + + setLoading(false); + + //Set Mediasource + val toResume = _videoResumePositionMilliseconds; + _videoResumePositionMilliseconds = 0; + loadCurrentVideo(toResume); + _player.setGestureSoundFactor(1.0f); + + updateQueueState(); + + _historicalPosition = StatePlaylists.instance.updateHistoryPosition(video, false, (toResume.toFloat() / 1000.0f).toLong()); + Logger.i(TAG, "Historical position: $_historicalPosition, last position: $lastPositionMilliseconds"); + if (_historicalPosition > 60 && video.duration - _historicalPosition > 5 && Math.abs(_historicalPosition - lastPositionMilliseconds / 1000) > 5.0) { + _layoutResume.visibility = View.VISIBLE; + _textResume.text = "Resume at ${_historicalPosition.toHumanTime(false)}"; + + _jobHideResume = fragment.lifecycleScope.launch(Dispatchers.Main) { + try { + delay(8000); + _layoutResume.visibility = View.GONE; + _textResume.text = ""; + } catch (e: Throwable) { + Logger.e(TAG, "Failed to set resume changes.", e); + } + } + } else { + _layoutResume.visibility = View.GONE; + _textResume.text = ""; + } + + StatePlayer.instance.startOrUpdateMediaSession(context, video); + StatePlayer.instance.setCurrentlyPlaying(video); + + + if(video.isLive && video.live != null) { + loadLiveChat(video); + } + + updateMoreButtons(); + } + fun loadLiveChat(video: IPlatformVideoDetails) { + _liveChat?.stop(); + _container_content_liveChat.cancel(); + _liveChat = null; + + fragment.lifecycleScope.launch(Dispatchers.IO) { + try { + var livePager: IPager?; + var liveChatWindow: ILiveChatWindowDescriptor?; + try { + //TODO: Create video.getLiveEvents shortcut/optimalization + livePager = StatePlatform.instance.getLiveEvents(video.url); + } catch (ex: Throwable) { + livePager = null; + UIDialogs.toast("Exception retrieving live events:\n" + ex.message); + Logger.e(TAG, "Failed to retrieve live chat events", ex); + } + try { + //TODO: Create video.getLiveChatWindow shortcut/optimalization + liveChatWindow = if(Settings.instance.playback.useLiveChatWindow) + StatePlatform.instance.getLiveChatWindow(video.url); + else null; + } + catch(ex: Throwable) { + liveChatWindow = null; + UIDialogs.toast("Exception retrieving live chat window:\n" + ex.message); + Logger.e(TAG, "Failed to retrieve live chat window", ex); + } + val liveChat = livePager?.let { + val liveChatManager = LiveChatManager(fragment.lifecycleScope, livePager, video.viewCount); + liveChatManager.start(); + return@let liveChatManager; + } + _liveChat = liveChat; + + fragment.lifecycleScope.launch(Dispatchers.Main) { + try { + _container_content_liveChat.load(fragment.lifecycleScope, liveChat, liveChatWindow, if(liveChat != null) video.viewCount else null); + switchContentView(_container_content_liveChat); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to switch content view to live chat."); + } + } + } + catch(ex: Throwable) { + Logger.e(TAG, "Failed to load live chat", ex); + + UIDialogs.toast("Live chat failed to load\n" + ex.message); + //_liveChat?.handleEvents(listOf(LiveEventComment("SYSTEM", null, "Failed to load live chat:\n" + ex.message, "#FF0000"))) + /* + fragment.lifecycleScope.launch(Dispatchers.Main) { + UIDialogs.showGeneralRetryErrorDialog(context, "Failed to load live chat", ex, { loadLiveChat(video); }); + } */ + } + } + } + + //Source Loads + private fun loadCurrentVideo(resumePositionMs: Long = 0) { + _didStop = false; + + val video = video ?: return; + + try { + val videoSource = _lastVideoSource ?: _player.getPreferredVideoSource(video, Settings.instance.playback.getCurrentPreferredQualityPixelCount()); + val audioSource = _lastAudioSource ?: _player.getPreferredAudioSource(video, Settings.instance.playback.getPrimaryLanguage(context)); + val subtitleSource = _lastSubtitleSource; + Logger.i(TAG, "loadCurrentVideo(videoSource=$videoSource, audioSource=$audioSource, subtitleSource=$subtitleSource, resumePositionMs=$resumePositionMs)") + + if(videoSource == null && audioSource == null) { + handleUnavailableVideo(); + StatePlatform.instance.clearContentDetailCache(video.url); + return; + } + + val isCasting = StateCasting.instance.isCasting + if (!isCasting) { + setCastEnabled(false); + + val thumbnail = video.thumbnails.getHQThumbnail(); + if (videoSource == null && !thumbnail.isNullOrBlank()) + Glide.with(context).asBitmap().load(thumbnail) + .into(object: CustomTarget() { + override fun onResourceReady(resource: Bitmap, transition: Transition?) { + _player.setArtwork(BitmapDrawable(resources, resource)); + } + override fun onLoadCleared(placeholder: Drawable?) { + _player.setArtwork(null); + } + }); + else + _player.setArtwork(null); + + _player.setSource(videoSource, audioSource, _playWhenReady, false); + _player.seekTo(resumePositionMs); + } + else + loadCurrentVideoCast(video, videoSource, audioSource, subtitleSource, resumePositionMs); + + _lastVideoSource = videoSource; + _lastAudioSource = audioSource; + } + catch(ex: Throwable) { + Logger.e(TAG, "Failed to load media", ex); + UIDialogs.showGeneralErrorDialog(context, "Failed to load media", ex); + } + } + private fun loadCurrentVideoCast(video: IPlatformVideoDetails, videoSource: IVideoSource?, audioSource: IAudioSource?, subtitleSource: ISubtitleSource?, resumePositionMs: Long) { + Logger.i(TAG, "loadCurrentVideoCast(video=$video, videoSource=$videoSource, audioSource=$audioSource, resumePositionMs=$resumePositionMs)") + + if(StateCasting.instance.castIfAvailable(context.contentResolver, video, videoSource, audioSource, subtitleSource, resumePositionMs)) { + _cast.setVideoDetails(video, resumePositionMs / 1000); + setCastEnabled(true); + } + else throw IllegalStateException("Disconnected cast during loading"); + } + + //Events + private fun onSourceChanged(videoSource: IVideoSource?, audioSource: IAudioSource?, resume: Boolean){ + Logger.i(TAG, "onSourceChanged(videoSource=$videoSource, audioSource=$audioSource, resume=$resume)") + + if((videoSource == null || videoSource is LocalVideoSource) && (audioSource == null || audioSource is LocalAudioSource)) + UIDialogs.toast(context, "Offline Playback", false); + //If LiveStream, set to end + if(videoSource is IDashManifestSource || videoSource is IHLSManifestSource) { + if (video?.isLive == true) { + _player.seekToEnd(5000); + } + + val videoTracks = _player.exoPlayer?.player?.currentTracks?.groups?.firstOrNull { it.mediaTrackGroup.type == C.TRACK_TYPE_VIDEO } + val audioTracks = _player.exoPlayer?.player?.currentTracks?.groups?.firstOrNull { it.mediaTrackGroup.type == C.TRACK_TYPE_AUDIO } + + val videoTrackFormats = mutableListOf(); + val audioTrackFormats = mutableListOf(); + + if(videoTracks != null) { + for (i in 0 until videoTracks.mediaTrackGroup.length) + videoTrackFormats.add(videoTracks.mediaTrackGroup.getFormat(i)); + } + if(audioTracks != null) { + for (i in 0 until audioTracks.mediaTrackGroup.length) + audioTrackFormats.add(audioTracks.mediaTrackGroup.getFormat(i)); + } + + updateQualityFormatsOverlay( + videoTrackFormats.distinctBy { it.height }.sortedBy { it.height }, + audioTrackFormats.distinctBy { it.bitrate }.sortedBy { it.bitrate }); + } + } + + private var _didTriggerDatasourceError = false; + private fun onDataSourceError(exception: Throwable) { + Logger.e(TAG, "onDataSourceError", exception); + if(exception.cause != null && exception.cause is InvalidResponseCodeException && (exception.cause!! as InvalidResponseCodeException).responseCode == 403) { + val currentVideo = video + if(currentVideo == null || currentVideo !is IPluginSourced) + return; + val config = currentVideo.sourceConfig; + + if(!_didTriggerDatasourceError) { + _didTriggerDatasourceError = true; + + UIDialogs.showDialog(context, R.drawable.ic_error_pred, + "Media Error", + "The media source encountered an unauthorized error.\nThis might be solved by a plugin reload.\nWould you like to reload?\n(Experimental)", + null, + 0, + UIDialogs.Action("No", { _didTriggerDatasourceError = false }), + UIDialogs.Action("Yes", { + fragment.lifecycleScope.launch(Dispatchers.IO) { + try { + StatePlatform.instance.reloadClient(context, config.id); + reloadVideo(); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to reload video.", e) + } + } + }, UIDialogs.ActionStyle.PRIMARY) + ); + } + } + } + + override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { + if (ev?.actionMasked == MotionEvent.ACTION_CANCEL || + ev?.actionMasked == MotionEvent.ACTION_POINTER_DOWN || + ev?.actionMasked == MotionEvent.ACTION_POINTER_UP) { + onTouchCancel.emit(); + } + + return super.onInterceptTouchEvent(ev); + } + + + //Actions + private fun showVideoSettings() { + Logger.i(TAG, "showVideoSettings") + _overlay_quality_selector?.selectOption("video", _lastVideoSource); + _overlay_quality_selector?.selectOption("audio", _lastAudioSource); + _overlay_quality_selector?.selectOption("subtitles", _lastSubtitleSource); + _overlay_quality_selector?.show(); + } + + fun prevVideo() { + Logger.i(TAG, "prevVideo") + val next = StatePlayer.instance.prevQueueItem(_player.duration < 100 || (_player.position.toFloat() / _player.duration) < 0.9); + if(next != null) { + setVideoOverview(next); + } + } + + fun nextVideo(): Boolean { + Logger.i(TAG, "nextVideo") + val next = StatePlayer.instance.nextQueueItem(_player.duration < 100 || (_player.position.toFloat() / _player.duration) < 0.9); + if(next != null) { + setVideoOverview(next); + return true; + } + else + StatePlayer.instance.setCurrentlyPlaying(null); + return false; + } + + //Quality Selector data + private fun updateQualityFormatsOverlay(liveStreamVideoFormats : List?, liveStreamAudioFormats : List?) { + val v = video ?: return; + updateQualitySourcesOverlay(v, liveStreamVideoFormats, liveStreamAudioFormats); + } + private fun updateQualitySourcesOverlay(videoDetails: IPlatformVideoDetails?, liveStreamVideoFormats: List? = null, liveStreamAudioFormats: List? = null) { + Logger.i(TAG, "updateQualitySourcesOverlay"); + + val video: IPlatformVideoDetails?; + val localVideoSources: List?; + val localAudioSource: List?; + val localSubtitleSources: List?; + + if(videoDetails is VideoLocal) { + video = videoDetails.videoSerialized; + localVideoSources = videoDetails.videoSource.toList(); + localAudioSource = videoDetails.audioSource.toList(); + localSubtitleSources = videoDetails.subtitlesSources.toList(); + } + else { + video = videoDetails; + localVideoSources = null; + localAudioSource = null; + localSubtitleSources = null; + } + + val videoSources = video?.video?.videoSources?.toList(); + val audioSources = if(video?.video?.isUnMuxed == true) + (video.video as VideoUnMuxedSourceDescriptor).audioSources.toList() + else null + + val bestVideoSources = videoSources?.map { it.height * it.width } + ?.distinct() + ?.map { x -> VideoHelper.selectBestVideoSource(videoSources.filter { x == it.height * it.width }, -1, FutoVideoPlayerBase.PREFERED_VIDEO_CONTAINERS) } + ?.filter { it != null } + ?.toList() ?: listOf(); + val bestAudioContainer = audioSources?.let { VideoHelper.selectBestAudioSource(it, FutoVideoPlayerBase.PREFERED_AUDIO_CONTAINERS)?.container }; + val bestAudioSources = audioSources + ?.filter { it.container == bestAudioContainer } + ?.toList() ?: listOf(); + + _overlay_quality_selector = SlideUpMenuOverlay(this.context, _overlay_quality_container, "Quality", null, true, + if (!_isCasting && video?.isLive != true) SlideUpMenuTitle(this.context).apply { setTitle("Playback Rate") } else null, + if (!_isCasting && video?.isLive != true) SlideUpMenuButtonList(this.context).apply { + setButtons(listOf("0.25", "0.5", "0.75", "1.0", "1.25", "1.5", "1.75", "2.0", "2.25"), _player.getPlaybackRate().toString()); + onClick.subscribe { v -> + if (_isCasting) { + return@subscribe; + } + + _player.setPlaybackRate(v.toFloat()); + setSelected(v); + }; + } else null, + + if(localVideoSources?.isNotEmpty() == true) + SlideUpMenuGroup(this.context, "Offline Video", "video", + *localVideoSources.stream() + .map { + SlideUpMenuItem(this.context, R.drawable.ic_movie, it!!.name, "${it.width}x${it.height}", it, + { handleSelectVideoTrack(it) }); + }.toList().toTypedArray()) + else null, + if(localAudioSource?.isNotEmpty() == true) + SlideUpMenuGroup(this.context, "Offline Audio", "audio", + *localAudioSource.stream() + .map { + SlideUpMenuItem(this.context, R.drawable.ic_music, it.name, it.bitrate.toHumanBitrate(), it, + { handleSelectAudioTrack(it) }); + }.toList().toTypedArray()) + else null, + if(localSubtitleSources?.isNotEmpty() == true) + SlideUpMenuGroup(this.context, "Offline Subtitles", "subtitles", + *localSubtitleSources + .map { + SlideUpMenuItem(this.context, R.drawable.ic_edit, it.name, "", it, + { handleSelectSubtitleTrack(it) }) + }.toList().toTypedArray()) + else null, + if(liveStreamVideoFormats?.isEmpty() == false) + SlideUpMenuGroup(this.context, "Stream Video", "video", + *liveStreamVideoFormats.stream() + .map { + SlideUpMenuItem(this.context, R.drawable.ic_movie, it?.label ?: it.containerMimeType ?: it.bitrate.toString(), "${it.width}x${it.height}", it, + { _player.selectVideoTrack(it.height) }); + }.toList().toTypedArray()) + else null, + if(liveStreamAudioFormats?.isEmpty() == false) + SlideUpMenuGroup(this.context, "Stream Audio", "audio", + *liveStreamAudioFormats.stream() + .map { + SlideUpMenuItem(this.context, R.drawable.ic_music, "${it?.label ?: it.containerMimeType} ${it.bitrate}", "", it, + { _player.selectAudioTrack(it.bitrate) }); + }.toList().toTypedArray()) + else null, + + if(bestVideoSources.isNotEmpty()) + SlideUpMenuGroup(this.context, "Video", "video", + *bestVideoSources.stream() + .map { + SlideUpMenuItem(this.context, R.drawable.ic_movie, it!!.name, if (it.width > 0 && it.height > 0) "${it.width}x${it.height}" else "", it, + { handleSelectVideoTrack(it) }); + }.toList().toTypedArray()) + else null, + if(bestAudioSources.isNotEmpty()) + SlideUpMenuGroup(this.context, "Audio", "audio", + *bestAudioSources.stream() + .map { + SlideUpMenuItem(this.context, R.drawable.ic_music, it.name, it.bitrate.toHumanBitrate(), it, + { handleSelectAudioTrack(it) }); + }.toList().toTypedArray()) + else null, + if(video?.subtitles?.isNotEmpty() ?: false && video != null) + SlideUpMenuGroup(this.context, "Subtitles", "subtitles", + *video.subtitles + .map { + SlideUpMenuItem(this.context, R.drawable.ic_edit, it.name, "", it, + { handleSelectSubtitleTrack(it) }) + }.toList().toTypedArray()) + else null); + } + + private fun updateQueueState() { + _upNext.update(); + } + + //Handlers + private fun handlePlay() { + Logger.i(TAG, "handlePlay") + if (!StateCasting.instance.resumeVideo()) { + _player.play(); + } + + //TODO: This was needed because handleLowerVolume was done. + //_player.setVolume(1.0f); + } + + private fun handleLowerVolume() { + Logger.i(TAG, "handleLowerVolume") + //TODO: This seems to be handled by OS? + //_player.setVolume(0.2f); + } + + private fun handlePause() { + Logger.i(TAG, "handlePause") + if (!StateCasting.instance.pauseVideo()) { + _player.pause(); + } + } + private fun handleSeek(ms: Long) { + Logger.i(TAG, "handleSeek(ms=$ms)") + if (!StateCasting.instance.videoSeekTo(ms.toDouble() / 1000.0)) { + _player.seekTo(ms); + } + } + private fun handleStop() { + Logger.i(TAG, "handleStop") + if (!StateCasting.instance.stopVideo()) { + _player.stop(); + } + } + + private fun handlePlayChanged(playing: Boolean) { + Logger.i(TAG, "handlePlayChanged(playing=$playing)") + + val ad = StateCasting.instance.activeDevice; + if (ad != null) { + _cast.setIsPlaying(playing); + } else { + StatePlayer.instance.updateMediaSession( null); + StatePlayer.instance.updateMediaSessionPlaybackState(_player.exoPlayer?.getPlaybackStateCompat() ?: PlaybackStateCompat.STATE_NONE, _player.exoPlayer?.player?.currentPosition ?: 0); + } + + if(playing) { + _minimize_controls_pause.visibility = View.VISIBLE; + _minimize_controls_play.visibility = View.GONE; + } + else { + _minimize_controls_pause.visibility = View.GONE; + _minimize_controls_play.visibility = View.VISIBLE; + } + + onPlayChanged.emit(playing); + updateTracker(_player.position, playing, true); + } + + private fun handleSelectVideoTrack(videoSource: IVideoSource) { + Logger.i(TAG, "handleSelectAudioTrack(videoSource=$videoSource)") + val video = video ?: return; + + if(_lastVideoSource == videoSource) + return; + + val d = StateCasting.instance.activeDevice; + if (d != null && d.connectionState == CastConnectionState.CONNECTED) + StateCasting.instance.castIfAvailable(context.contentResolver, video, videoSource, _lastAudioSource, _lastSubtitleSource, (d.expectedCurrentTime * 1000.0).toLong()); + else if(!_player.swapSources(videoSource, _lastAudioSource, true, true, true)) + _player.hideControls(false); //TODO: Disable player? + + _lastVideoSource = videoSource; + } + private fun handleSelectAudioTrack(audioSource: IAudioSource) { + Logger.i(TAG, "handleSelectAudioTrack(audioSource=$audioSource)") + val video = video ?: return; + + if(_lastAudioSource == audioSource) + return; + + val d = StateCasting.instance.activeDevice; + if (d != null && d.connectionState == CastConnectionState.CONNECTED) + StateCasting.instance.castIfAvailable(context.contentResolver, video, _lastVideoSource, audioSource, _lastSubtitleSource, (d.expectedCurrentTime * 1000.0).toLong()); + else(!_player.swapSources(_lastVideoSource, audioSource, true, true, true)) + _player.hideControls(false); //TODO: Disable player? + + _lastAudioSource = audioSource; + } + private fun handleSelectSubtitleTrack(subtitleSource: ISubtitleSource) { + Logger.i(TAG, "handleSelectSubtitleTrack(subtitleSource=$subtitleSource)") + val video = video ?: return; + + var toSet: ISubtitleSource? = subtitleSource + if(_lastSubtitleSource == subtitleSource) + toSet = null; + + val d = StateCasting.instance.activeDevice; + if (d != null && d.connectionState == CastConnectionState.CONNECTED) + StateCasting.instance.castIfAvailable(context.contentResolver, video, _lastVideoSource, _lastAudioSource, toSet, (d.expectedCurrentTime * 1000.0).toLong()); + else + _player.swapSubtitles(fragment.lifecycleScope, toSet); + + _lastSubtitleSource = toSet; + } + + private fun handleUnavailableVideo() { + if (!nextVideo()) { + if(video?.datetime == null || video?.datetime!! < OffsetDateTime.now().minusHours(1)) + UIDialogs.showDialog(context, R.drawable.ic_lock, "Unavailable video", "This video is unavailable.", null, 0, + UIDialogs.Action("Back", { + this@VideoDetailView.onClose.emit(); + }, UIDialogs.ActionStyle.PRIMARY)); + } else { + StateAnnouncement.instance.registerAnnouncement(video?.id?.value + "_Q_UNAVAILABLE", "Unavailable video", "There was an unavailable video in your queue [${video?.name}] by [${video?.author?.name}].", AnnouncementType.SESSION) + } + + video?.let { StatePlatform.instance.clearContentDetailCache(it.url) }; + } + + + //Fetch + private fun fetchComments() { + Logger.i(TAG, "fetchComments") + video?.let { + _commentsList.load(true) { StatePlatform.instance.getComments(it); }; + } + } + private fun fetchPolycentricComments() { + Logger.i(TAG, "fetchPolycentricComments") + val video = video; + val idValue = video?.id?.value + if (idValue == null) { + Logger.w(TAG, "Failed to fetch polycentric comments because id was null") + _commentsList.clear() + return + } + + _commentsList.load(false) { StatePolycentric.instance.getCommentPager(video.url, Models.referenceFromBuffer(idValue.toByteArray())); }; + } + private fun fetchVideo() { + Logger.i(TAG, "fetchVideo") + video = null; + _playbackTracker = null; + + val url = _url; + if (url != null && url.isNotBlank()) { + setLoading(true); + _taskLoadVideo.run(url); + } + } + + private fun handleFullScreen(fullscreen : Boolean) { + Logger.i(TAG, "handleFullScreen(fullscreen=$fullscreen)") + + if(fullscreen) { + _layoutPlayerContainer.setPadding(0, 0, 0, 0); + + val lp = _container_content.layoutParams as ConstraintLayout.LayoutParams; + lp.topMargin = 0; + _container_content.layoutParams = lp; + + this._player.layoutParams = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT + ); + setProgressBarOverlayed(null); + } + else { + _layoutPlayerContainer.setPadding(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, Resources.getSystem().displayMetrics).toInt()); + + val lp = _container_content.layoutParams as ConstraintLayout.LayoutParams; + lp.topMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, -18.0f, Resources.getSystem().displayMetrics).toInt(); + _container_content.layoutParams = lp; + + this._player.layoutParams = FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.WRAP_CONTENT + ) + setProgressBarOverlayed(false); + } + onFullscreenChanged.emit(fullscreen); + } + + private fun setCastEnabled(isCasting: Boolean) { + Logger.i(TAG, "setCastEnabled(isCasting=$isCasting)") + _player.setPlaybackRate(Settings.instance.playback.getDefaultPlaybackSpeed()); + video?.let { updateQualitySourcesOverlay(it); }; + + _isCasting = isCasting; + + if(isCasting) { + _player.stop(); + _player.hideControls(false); + _cast.visibility = View.VISIBLE; + } + else { + StateCasting.instance.stopVideo(); + _cast.stopTimeJob(); + _cast.visibility = View.GONE; + } + } + + fun setFullscreen(fullscreen : Boolean) { + Logger.i(TAG, "setFullscreen(fullscreen=$fullscreen)") + _player.setFullScreen(fullscreen); + } + private fun setLoading(isLoading : Boolean) { + if(isLoading){ + (_overlay_loading_spinner.drawable as Animatable?)?.start() + _overlay_loading.visibility = View.VISIBLE; + } + else { + _overlay_loading.visibility = View.GONE; + (_overlay_loading_spinner.drawable as Animatable?)?.stop() + } + } + + //UI Actions + private fun setDescription(text: Spanned) { + _container_content_description.load(text); + _description.text = text; + + if (_description.text.isNotEmpty()) + _descriptionContainer.visibility = View.VISIBLE; + else + _descriptionContainer.visibility = View.GONE; + } + fun onBackPressed(): Boolean { + val slideUpOverlay = _slideUpOverlay; + if (slideUpOverlay != null) { + if (slideUpOverlay.isVisible) { + slideUpOverlay.hide(); + return true; + } else { + _slideUpOverlay = null; + } + } + + if (_container_content_current != _container_content_main) { + switchContentView(_container_content_main); + return true; + } + + return false; + } + private fun switchContentView(view: View) { + val curView = _container_content_current; + if (curView == view) + return; + + val animHeight = _container_content.height; + + + if(view == _container_content_main) { + curView.elevation = 2f; + view.elevation = 1f; + view.visibility = VISIBLE; + + curView.animate() + .setDuration(300) + .translationY(animHeight.toFloat()) + .withEndAction { + curView.visibility = GONE; + _container_content_current = view; + } + .start(); + } + else { + curView.elevation = 1f; + view.elevation = 2f; + view.translationY = animHeight.toFloat(); + view.visibility = VISIBLE; + + view.animate() + .setDuration(300) + .translationY(0f) + .withEndAction { + curView.visibility = GONE; + _container_content_current = view; + } + .start(); + } + } + + //TODO: Make pill buttons dynamic instead of visiblity + private fun updatePillButtonVisibilities() { + _buttonPins.setButtonVisibility { + (it.tagRef != TAG_BACKGROUND && it.tagRef != TAG_OVERLAY) || !_isCasting + }; + } + + private fun updateCommentType(reloadComments: Boolean) { + if (_toggleCommentType.value) { + _textCommentType.text = "Platform"; + _addCommentView.visibility = View.GONE; + + if (reloadComments) { + fetchComments(); + } + } else { + _textCommentType.text = "Polycentric"; + _addCommentView.visibility = View.VISIBLE; + + if (reloadComments) { + fetchPolycentricComments() + } + } + } + + + //Picture2Picture + fun startPictureInPicture() { + Logger.i(TAG, "startPictureInPicture") + + UIDialogs.dismissAllDialogs(); + onMaximize.emit(true); + onEnterPictureInPicture.emit(); + _player.hideControls(false); + _layoutResume.visibility = View.GONE; + } + fun handleEnterPictureInPicture() { + Logger.i(TAG, "handleEnterPictureInPicture"); + + _overlayContainer.removeAllViews(); + _overlay_quality_selector?.hide(); + + _player.fillHeight(); + _layoutPlayerContainer.setPadding(0, 0, 0, 0); + } + fun handleLeavePictureInPicture() { + Logger.i(TAG, "handleLeavePictureInPicture") + + if(!_player.isFullScreen) { + _player.fitHeight(); + _layoutPlayerContainer.setPadding(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, Resources.getSystem().displayMetrics).toInt()); + } else { + _layoutPlayerContainer.setPadding(0, 0, 0, 0); + } + } + fun getPictureInPictureParams() : PictureInPictureParams { + var videoSourceWidth = _player.exoPlayer?.player?.videoSize?.width ?: 0; + var videoSourceHeight = _player.exoPlayer?.player?.videoSize?.height ?: 0; + + if(videoSourceWidth == 0 || videoSourceHeight == 0) { + videoSourceWidth = 16; + videoSourceHeight = 9; + } + val aspectRatio = videoSourceWidth.toDouble() / videoSourceHeight; + if(aspectRatio > 3) { + videoSourceWidth = 16; + videoSourceHeight = 9; + } + else if(aspectRatio < 0.3) { + videoSourceHeight = 16; + videoSourceWidth = 9; + } + + val r = Rect(); + _player.getGlobalVisibleRect(r); + r.right = r.right - _player.paddingEnd; + val playpauseAction = if(_player.playing) + RemoteAction(Icon.createWithResource(context, R.drawable.ic_pause_notif), "Pause", "Pauses the video", MediaControlReceiver.getPauseIntent(context, 5)); + else + RemoteAction(Icon.createWithResource(context, R.drawable.ic_play_notif), "Play", "Resumes the video", MediaControlReceiver.getPlayIntent(context, 6)); + + return PictureInPictureParams.Builder() + .setAspectRatio(Rational(videoSourceWidth, videoSourceHeight)) + .setSourceRectHint(r) + .setActions(listOf(playpauseAction)) + .build(); + } + + //Other + private fun shareVideo() { + Logger.i(TAG, "shareVideo") + + val url = video?.shareUrl ?: _searchVideo?.shareUrl ?: _url; + fragment.startActivity(Intent.createChooser(Intent().apply { + action = Intent.ACTION_SEND; + putExtra(Intent.EXTRA_TEXT, url); + type = "text/plain"; //TODO: Determine alt types? + }, null)); + } + + private fun setLastPositionMilliseconds(positionMilliseconds: Long, updateHistory: Boolean) { + lastPositionMilliseconds = positionMilliseconds; + + val v = video ?: return; + val currentTime = System.currentTimeMillis(); + if (updateHistory && (_lastPositionSaveTime == -1L || currentTime - _lastPositionSaveTime > 5000)) { + StatePlaylists.instance.updateHistoryPosition(v, true, (positionMilliseconds.toFloat() / 1000.0f).toLong()); + _lastPositionSaveTime = currentTime; + } + updateTracker(positionMilliseconds, _player.playing, false); + } + + private fun updateTracker(positionMs: Long, isPlaying: Boolean, forceUpdate: Boolean = false) { + val playbackTracker = _playbackTracker ?: return; + val shouldUpdate = playbackTracker.shouldUpdate() || forceUpdate; + if (!shouldUpdate) { + return; + } + + fragment.lifecycleScope.launch(Dispatchers.IO) { + playbackTracker.onProgress(positionMs.toDouble() / 1000, isPlaying); + } + } + + //Animation related setters + fun setMinimizeProgress(progress : Float) { + _minimizeProgress = progress; + _player.lockControlsAlpha(progress < 0.9); + _layoutPlayerContainer.shouldInterceptTouches = progress < 0.95; + + if(progress > 0.9) { + if(_minimize_controls.visibility != View.GONE) + _minimize_controls.visibility = View.GONE; + } + else if(_minimize_controls.visibility != View.VISIBLE) { + _minimize_controls.visibility = View.VISIBLE; + } + + //Switching video to fill + if(progress > 0.25) { + if(!_player.isFullScreen && _player.layoutParams.height != WRAP_CONTENT) { + _player.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT); + if(!fragment.isInPictureInPicture) { + _player.fitHeight(); + _layoutPlayerContainer.setPadding(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, Resources.getSystem().displayMetrics).toInt()); + } + else { + _layoutPlayerContainer.setPadding(0, 0, 0, 0); + } + _cast.layoutParams = _cast.layoutParams.apply { + (this as MarginLayoutParams).bottomMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, resources.displayMetrics).toInt(); + }; + setProgressBarOverlayed(false); + _player.hideControls(false); + } + } + else { + if(_player.layoutParams.height == WRAP_CONTENT) { + _player.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT); + _player.fillHeight(); + _cast.layoutParams = _cast.layoutParams.apply { + (this as MarginLayoutParams).bottomMargin = 0; + }; + setProgressBarOverlayed(true); + _player.hideControls(false); + + _layoutPlayerContainer.setPadding(0, 0, 0, 0); + } + } + } + + private fun setPolycentricProfile(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean) { + _polycentricProfile = cachedPolycentricProfile; + + if (cachedPolycentricProfile?.profile == null) { + _layoutMonetization.visibility = View.GONE; + _creatorThumbnail.setHarborAvailable(false, animate); + return; + } + + _layoutMonetization.visibility = View.VISIBLE; + _creatorThumbnail.setHarborAvailable(true, animate); + } + + fun setProgressBarOverlayed(isOverlayed: Boolean?) { + Logger.i(TAG, "setProgressBarOverlayed(isOverlayed: ${isOverlayed ?: "null"})"); + isOverlayed?.let{ _cast.setProgressBarOverlayed(it) }; + + if(isOverlayed == null) { + //For now this seems to be the best way to keep it updated? + _playerProgress.layoutParams = _playerProgress.layoutParams.apply { + (this as MarginLayoutParams).bottomMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, -12f, resources.displayMetrics).toInt(); + }; + _playerProgress.elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2f, resources.displayMetrics); + } + else if(isOverlayed) { + _playerProgress.layoutParams = _playerProgress.layoutParams.apply { + (this as MarginLayoutParams).bottomMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, -6f, resources.displayMetrics).toInt(); + }; + _playerProgress.elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics); + } + else { + _playerProgress.layoutParams = _playerProgress.layoutParams.apply { + (this as MarginLayoutParams).bottomMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6f, resources.displayMetrics).toInt(); + }; + _playerProgress.elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2f, resources.displayMetrics); + } + } + fun setContentAlpha(alpha: Float) { + _container_content.alpha = alpha; + } + fun setControllerAlpha(alpha: Float) { + _layoutResume.alpha = alpha; + _player.videoControls.alpha = alpha; + _cast.setButtonAlpha(alpha); + } + fun setMinimizeControlsAlpha(alpha : Float) { + _minimize_controls.alpha = alpha; + val clickable = alpha > 0.9; + if(_minimize_controls.isClickable != clickable) + _minimize_controls.isClickable = clickable; + } + fun setVideoMinimize(value : Float) { + val padRight = (resources.displayMetrics.widthPixels * 0.70 * value).toInt(); + _player.setPadding(0, _player.paddingTop, padRight, 0); + _cast.setPadding(0, _cast.paddingTop, padRight, 0); + } + fun setTopPadding(value : Float) { + _player.setPadding(0, value.toInt(), _player.paddingRight, 0); + } + + //Tasks + private val _taskLoadVideo = if(!isInEditMode) TaskHandler( + StateApp.instance.scopeGetter, + { + val result = StatePlatform.instance.getContentDetails(it).await(); + if(result !is IPlatformVideoDetails) + throw IllegalStateException("Expected media content, found ${result.contentType}"); + return@TaskHandler result; + }) + .success { setVideoDetails(it, true) } + .exception { + Logger.w(TAG, "exception", it) + + if (!nextVideo()) { + UIDialogs.showDialog(context, + R.drawable.ic_sources, + "No source enabled to support this video\n(${_url})", null, null, + 0, + UIDialogs.Action("Back", { + this@VideoDetailView.onClose.emit(); + }, UIDialogs.ActionStyle.PRIMARY) + ); + } else { + StateAnnouncement.instance.registerAnnouncement(video?.id?.value + "_Q_NOSOURCES", "Video without source", "There was a in your queue [${video?.name}] by [${video?.author?.name}] without the required source being enabled, playback was skipped.", AnnouncementType.SESSION) + } + } + .exception { + Logger.w(TAG, "exception", it) + + if (!nextVideo()) { + UIDialogs.showSingleButtonDialog(context, + R.drawable.ic_schedule, + "Video is available in ${it.availableWhen}.", + "Back", { + this@VideoDetailView.onClose.emit(); + }); + } + } + .exception { + Logger.w(TAG, "exception", it) + + if (!nextVideo()) { + UIDialogs.showGeneralRetryErrorDialog(context, "Failed to load video (ScriptImplementationException)", it, ::fetchVideo); + } else { + StateAnnouncement.instance.registerAnnouncement(video?.id?.value + "_Q_INVALIDVIDEO", "Invalid video", "There was an invalid video in your queue [${video?.name}] by [${video?.author?.name}], playback was skipped.", AnnouncementType.SESSION) + } + } + .exception { + Logger.w(TAG, "exception", it) + + if (!nextVideo()) { + UIDialogs.showDialog(context, + R.drawable.ic_lock, + "Age restricted video", + it.message, null, 0, + UIDialogs.Action("Back", { + this@VideoDetailView.onClose.emit(); + }, UIDialogs.ActionStyle.PRIMARY)); + } else { + StateAnnouncement.instance.registerAnnouncement(video?.id?.value + "_Q_AGERESTRICT", "Age restricted video", "There was an age restricted video in your queue [${video?.name}] by [${video?.author?.name}], this video was not accessible and playback was skipped.", AnnouncementType.SESSION) + } + } + .exception { + Logger.w(TAG, "exception", it); + handleUnavailableVideo(); + } + .exception { + Logger.w(TAG, "exception", it) + + handleErrorOrCall { + _retryCount = 0; + _retryJob?.cancel(); + _retryJob = null; + UIDialogs.showGeneralRetryErrorDialog(context, "Failed to load video (ScriptException)", it, ::fetchVideo); + } + } + .exception { + Logger.w(ChannelFragment.TAG, "Failed to load video.", it); + + handleErrorOrCall { + _retryCount = 0; + _retryJob?.cancel(); + _retryJob = null; + UIDialogs.showGeneralRetryErrorDialog(context, "Failed to load video", it, ::fetchVideo); + } + } else TaskHandler(IPlatformVideoDetails::class.java, {fragment.lifecycleScope}); + + private val _taskLoadPolycentricProfile = TaskHandler(StateApp.instance.scopeGetter, { PolycentricCache.instance.getProfileAsync(it) }) + .success { it -> setPolycentricProfile(it, animate = true) } + .exception { + Logger.w(TAG, "Failed to load claims.", it); + }; + + private fun handleErrorOrCall(action: () -> Unit) { + val isConnected = StateApp.instance.getCurrentNetworkState() != StateApp.NetworkState.DISCONNECTED; + + if (_retryCount < _retryIntervals.size) { + Log.i(TAG, "handleErrorOrCall _retryCount=$_retryCount, starting retry job"); + + _retryJob?.cancel(); + _retryJob = StateApp.instance.scopeGetter().launch(Dispatchers.Main) { + try { + delay(_retryIntervals[_retryCount++] * 1000); + fetchVideo(); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to fetch video.", e) + } + } + } else if (isConnected && nextVideo()) { + Log.i(TAG, "handleErrorOrCall retries failed, is connected, skipped to next video"); + } else { + Log.i(TAG, "handleErrorOrCall retries failed, no video to skip to, called action"); + action(); + } + } + + fun applyFragment(frag: VideoDetailFragment) { + fragment = frag; + fragment.onMinimize.subscribe { + _liveChat?.stop(); + _container_content_liveChat.close(); + } + } + + + companion object { + const val TAG_ADD = "add"; + const val TAG_BACKGROUND = "background"; + const val TAG_DOWNLOAD = "download"; + const val TAG_SHARE = "share"; + const val TAG_OVERLAY = "overlay"; + const val TAG_LIVECHAT = "livechat"; + const val TAG_OPEN = "open"; + const val TAG_MORE = "MORE"; + + private val _buttonPinStore = FragmentedStorage.get("videoPinnedButtons"); + + + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoListEditorView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoListEditorView.kt new file mode 100644 index 00000000..26bc3f79 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoListEditorView.kt @@ -0,0 +1,139 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.graphics.drawable.Animatable +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.images.GlideHelper.Companion.crossfade +import com.futo.platformplayer.views.lists.VideoListEditorView + +abstract class VideoListEditorView : LinearLayout { + private var _videoListEditorView: VideoListEditorView; + private var _imagePlaylistThumbnail: ImageView; + private var _textName: TextView; + private var _textMetadata: TextView; + private var _loaderOverlay: FrameLayout; + private var _imageLoader: ImageView; + protected var overlayContainer: FrameLayout + private set; + protected var _buttonDownload: ImageButton; + private var _buttonShare: ImageButton; + private var _buttonEdit: ImageButton; + + private var _onShare: (()->Unit)? = null; + + constructor(inflater: LayoutInflater) : super(inflater.context) { + inflater.inflate(R.layout.fragment_video_list_editor, this); + + val videoListEditorView = findViewById(R.id.video_list_editor); + _textName = findViewById(R.id.text_name); + _textMetadata = findViewById(R.id.text_metadata); + _imagePlaylistThumbnail = findViewById(R.id.image_playlist_thumbnail); + _loaderOverlay = findViewById(R.id.layout_loading_overlay); + _imageLoader = findViewById(R.id.image_loader); + overlayContainer = findViewById(R.id.overlay_container); + val buttonPlayAll = findViewById(R.id.button_play_all); + val buttonShuffle = findViewById(R.id.button_shuffle); + _buttonEdit = findViewById(R.id.button_edit); + _buttonDownload = findViewById(R.id.button_download); + _buttonDownload.visibility = View.GONE; + + _buttonShare = findViewById(R.id.button_share); + val onShare = _onShare; + if(onShare != null) { + _buttonShare.setOnClickListener { onShare.invoke() }; + _buttonShare.visibility = View.VISIBLE; + } + else + _buttonShare?.visibility = View.GONE; + + buttonPlayAll.setOnClickListener { onPlayAllClick(); }; + buttonShuffle.setOnClickListener { onShuffleClick(); }; + + if (canEdit()) + _buttonEdit.setOnClickListener { onEditClick(); }; + else + _buttonEdit.visibility = View.GONE; + + videoListEditorView.onVideoOrderChanged.subscribe(::onVideoOrderChanged); + videoListEditorView.onVideoRemoved.subscribe(::onVideoRemoved); + videoListEditorView.onVideoClicked.subscribe(::onVideoClicked); + + _videoListEditorView = videoListEditorView; + } + + fun setOnShare(onShare: (()-> Unit)? = null) { + _onShare = onShare; + _buttonShare.setOnClickListener { + onShare?.invoke(); + }; + _buttonShare.visibility = View.VISIBLE; + } + + open fun canEdit(): Boolean { return false; } + open fun onPlayAllClick() { } + open fun onShuffleClick() { } + open fun onEditClick() { } + open fun onVideoRemoved(video: IPlatformVideo) {} + open fun onVideoOrderChanged(videos : List) {} + open fun onVideoClicked(video: IPlatformVideo) { + + } + + + protected fun setName(name: String?) { + _textName.text = name ?: ""; + } + + protected fun setVideoCount(videoCount: Int = -1) { + _textMetadata.text = if (videoCount == -1) "" else "${videoCount} videos"; + } + + protected fun setVideos(videos: List?, canEdit: Boolean) { + if (videos != null && videos.isNotEmpty()) { + val video = videos.first(); + _imagePlaylistThumbnail.let { + Glide.with(it) + .load(video.thumbnails.getHQThumbnail()) + .placeholder(R.drawable.placeholder_video_thumbnail) + .crossfade() + .into(it); + }; + } else { + _textMetadata.text = "0 videos"; + if(_imagePlaylistThumbnail != null) { + Glide.with(_imagePlaylistThumbnail) + .load(R.drawable.placeholder_video_thumbnail) + .into(_imagePlaylistThumbnail); + } + } + + _videoListEditorView.setVideos(videos, canEdit); + } + + protected fun setButtonDownloadVisible(isVisible: Boolean) { + _buttonDownload.visibility = if (isVisible) View.VISIBLE else View.GONE; + } + + protected fun setButtonEditVisible(isVisible: Boolean) { + _buttonEdit.visibility = if (isVisible) View.VISIBLE else View.GONE; + } + + protected fun setLoading(isLoading: Boolean) { + if(isLoading){ + (_imageLoader.drawable as Animatable?)?.start() + _loaderOverlay.visibility = View.VISIBLE; + } + else { + _loaderOverlay.visibility = View.GONE; + (_imageLoader.drawable as Animatable?)?.stop() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/WatchLaterFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/WatchLaterFragment.kt new file mode 100644 index 00000000..9b1e847a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/WatchLaterFragment.kt @@ -0,0 +1,81 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.futo.platformplayer.states.StatePlayer +import com.futo.platformplayer.states.StatePlaylists +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo + +class WatchLaterFragment : MainFragment() { + override val isMainView : Boolean = true; + override val isTab: Boolean = true; + override val hasBottomBar: Boolean get() = true; + + private var _view: WatchLaterView? = null; + + override fun onShownWithView(parameter: Any?, isBack: Boolean) { + super.onShownWithView(parameter, isBack); + _view?.onShown(parameter, isBack); + } + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = WatchLaterView(this, inflater); + _view = view; + return view; + } + + override fun onDestroyMainView() { + super.onDestroyMainView(); + _view = null; + } + + @SuppressLint("ViewConstructor") + class WatchLaterView : VideoListEditorView { + private val _fragment: WatchLaterFragment; + + constructor(fragment: WatchLaterFragment, inflater: LayoutInflater) : super(inflater) { + _fragment = fragment; + + } + + fun onShown(parameter: Any ?, isBack: Boolean) { + setName("Watch Later"); + setVideos(StatePlaylists.instance.getWatchLater(), true); + } + + override fun onPlayAllClick() { + StatePlayer.instance.setQueue(StatePlaylists.instance.getWatchLater(), StatePlayer.TYPE_WATCHLATER, focus = true); + } + + override fun onShuffleClick() { + StatePlayer.instance.setQueue(StatePlaylists.instance.getWatchLater(), StatePlayer.TYPE_WATCHLATER, focus = true, shuffle = true); + } + + override fun onVideoOrderChanged(videos: List) { + StatePlaylists.instance.updateWatchLater(ArrayList(videos.map { it as SerializedPlatformVideo })); + } + override fun onVideoRemoved(video: IPlatformVideo) { + if (video is SerializedPlatformVideo) { + StatePlaylists.instance.removeFromWatchLater(video); + } + } + + override fun onVideoClicked(video: IPlatformVideo) { + val watchLater = StatePlaylists.instance.getWatchLater(); + val index = watchLater.indexOf(video); + if (index == -1) { + return; + } + + StatePlayer.instance.setQueueWithPosition(watchLater, StatePlayer.TYPE_WATCHLATER, index, focus = true); + } + } + + companion object { + fun newInstance() = WatchLaterFragment().apply {} + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/AddTopBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/AddTopBarFragment.kt new file mode 100644 index 00000000..c9359c9c --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/AddTopBarFragment.kt @@ -0,0 +1,49 @@ +package com.futo.platformplayer.fragment.mainactivity.topbar + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageButton +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.views.casting.CastButton + +class AddTopBarFragment : TopFragment() { + private var _buttonAdd: ImageButton? = null; + private var _buttonCast: CastButton? = null; + + val onAdd = Event0(); + + override fun onShown(parameter: Any?) { + + } + override fun onHide() { + onAdd.clear(); + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_add_top_bar, container, false); + + _buttonAdd = view.findViewById(R.id.button_add).apply { + this.setOnClickListener { + onAdd.emit(); + } + }; + _buttonCast = view.findViewById(R.id.button_cast); + + return view; + } + + override fun onDestroyView() { + super.onDestroyView() + + onAdd.clear(); + _buttonCast?.cleanup(); + _buttonCast = null; + } + + companion object { + fun newInstance() = AddTopBarFragment().apply { } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/GeneralTopBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/GeneralTopBarFragment.kt new file mode 100644 index 00000000..2b0a0f4a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/GeneralTopBarFragment.kt @@ -0,0 +1,71 @@ +package com.futo.platformplayer.fragment.mainactivity.topbar + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageButton +import android.widget.ImageView +import com.futo.platformplayer.R +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.fragment.mainactivity.main.CreatorsFragment +import com.futo.platformplayer.fragment.mainactivity.main.PlaylistFragment +import com.futo.platformplayer.fragment.mainactivity.main.PlaylistsFragment +import com.futo.platformplayer.fragment.mainactivity.main.SuggestionsFragment +import com.futo.platformplayer.fragment.mainactivity.main.SuggestionsFragmentData +import com.futo.platformplayer.models.SearchType +import com.futo.platformplayer.views.casting.CastButton + +class GeneralTopBarFragment : TopFragment() { + private var _buttonSearch: ImageButton? = null; + private var _buttonCast: CastButton? = null; + + override fun onShown(parameter: Any?) { + if(currentMain is CreatorsFragment) { + _buttonSearch?.setImageResource(R.drawable.ic_person_search_300w); + } else { + _buttonSearch?.setImageResource(R.drawable.ic_search_300w); + } + } + override fun onHide() { + + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_overview_top_bar, container, false); + + view.findViewById(R.id.app_icon).setOnClickListener { + UIDialogs.toast("This app is in development. Please submit bug reports and understand that many features are incomplete.", true); + }; + + val buttonSearch: ImageButton = view.findViewById(R.id.button_search); + _buttonCast = view.findViewById(R.id.button_cast); + + buttonSearch.setOnClickListener { + if(currentMain is CreatorsFragment) { + navigate(SuggestionsFragmentData("", SearchType.CREATOR)); + } else if (currentMain is PlaylistsFragment || currentMain is PlaylistFragment) { + navigate(SuggestionsFragmentData("", SearchType.PLAYLIST)); + } else { + navigate(SuggestionsFragmentData("", SearchType.VIDEO)); + } + }; + + _buttonSearch = buttonSearch; + + return view; + } + + override fun onDestroyView() { + super.onDestroyView() + + _buttonSearch?.setOnClickListener(null); + _buttonSearch = null; + _buttonCast?.cleanup(); + _buttonCast = null; + } + + companion object { + fun newInstance() = GeneralTopBarFragment().apply { } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/ImportTopBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/ImportTopBarFragment.kt new file mode 100644 index 00000000..1d157ce5 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/ImportTopBarFragment.kt @@ -0,0 +1,87 @@ +package com.futo.platformplayer.fragment.mainactivity.topbar + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageButton +import android.widget.TextView +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.constructs.Event0 + +class ImportTopBarFragment : TopFragment() { + private var _buttonBack: ImageButton? = null; + private var _textImport: TextView? = null; + private var _textTitle: TextView? = null; + private var _importEnabled: Boolean = false; + + val onImport = Event0(); + var title: String + get() = _textTitle?.text?.toString() ?: "" + set(v) { _textTitle?.text = v; }; + + override fun onShown(parameter: Any?) { + if (parameter is String) { + _textTitle?.text = parameter; + } else if (parameter is IPlatformClient) { + _textTitle?.text = parameter.name; + } + } + + override fun onHide() { + + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_import_top_bar, container, false); + + val buttonBack: ImageButton = view.findViewById(R.id.button_back); + val textImport: TextView = view.findViewById(R.id.text_import); + _textTitle = view.findViewById(R.id.text_title); + + buttonBack.setOnClickListener { + closeSegment(); + }; + + textImport.isClickable = true; + textImport.setOnClickListener { + if (!_importEnabled) { + return@setOnClickListener; + } + + onImport.emit(); + }; + + _buttonBack = buttonBack; + _textImport = textImport; + + setImportEnabled(false); + + return view; + } + + override fun onDestroyView() { + super.onDestroyView() + + _buttonBack?.setOnClickListener(null); + _buttonBack = null; + _textImport?.setOnClickListener(null); + _textImport = null; + _textTitle = null; + } + + fun setImportEnabled(enabled: Boolean) { + if (enabled) { + _textImport?.setTextColor(resources.getColor(R.color.colorPrimary)); + } else { + _textImport?.setTextColor(resources.getColor(R.color.gray_67)); + } + + _importEnabled = enabled; + } + + companion object { + fun newInstance() = ImportTopBarFragment().apply { } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/NavigationTopBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/NavigationTopBarFragment.kt new file mode 100644 index 00000000..4348edac --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/NavigationTopBarFragment.kt @@ -0,0 +1,98 @@ +package com.futo.platformplayer.fragment.mainactivity.topbar + +import android.os.Bundle +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.appcompat.widget.AppCompatImageView +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile +import com.futo.platformplayer.models.Playlist +import com.futo.platformplayer.views.casting.CastButton + +class NavigationTopBarFragment : TopFragment() { + private var _buttonBack: ImageButton? = null; + private var _buttonCast: CastButton? = null; + private var _textTitle: TextView? = null; + private var _menuItems: LinearLayout? = null; + + override fun onShown(parameter: Any?) { + if(parameter is IPlatformChannel) { + _textTitle?.text = parameter.name; + } else if(parameter is PlatformAuthorLink) { + _textTitle?.text = parameter.name; + } else if (parameter is Playlist) { + _textTitle?.text = parameter.name; + } else if (parameter is String) { + _textTitle?.text = parameter; + } else if (parameter is IPlatformClient) { + _textTitle?.text = parameter.name; + } else if (parameter is PolycentricProfile) { + _textTitle?.text = parameter.systemState.username; + } + + setMenuItems(listOf()); + } + override fun onHide() { + + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_navigation_top_bar, container, false); + + val buttonBack: ImageButton = view.findViewById(R.id.button_back); + _buttonCast = view.findViewById(R.id.button_cast); + _textTitle = view.findViewById(R.id.text_title); + _menuItems = view.findViewById(R.id.menu_buttons) + + buttonBack.setOnClickListener { + closeSegment(); + }; + + _buttonBack = buttonBack; + + return view; + } + + override fun onDestroyView() { + super.onDestroyView() + + _buttonBack?.setOnClickListener(null); + _buttonBack = null; + _buttonCast?.cleanup(); + _buttonCast = null; + _textTitle = null; + } + + fun setMenuItems(items: ListUnit>>) { + _menuItems?.removeAllViews(); + + val dp4 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4f, resources.displayMetrics).toInt(); + val dp9 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 9f, resources.displayMetrics).toInt(); + + for(item in items) { + val compatImageItem = AppCompatImageView(requireContext()); + compatImageItem.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT); + compatImageItem.setImageResource(item.first); + compatImageItem.setPadding(dp4, dp9, dp4, dp9); + compatImageItem.scaleType = ImageView.ScaleType.FIT_CENTER; + compatImageItem.setOnClickListener { + item.second.invoke(); + }; + + _menuItems?.addView(compatImageItem); + } + } + + companion object { + fun newInstance() = NavigationTopBarFragment().apply { } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/SearchTopBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/SearchTopBarFragment.kt new file mode 100644 index 00000000..511abd3a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/SearchTopBarFragment.kt @@ -0,0 +1,222 @@ +package com.futo.platformplayer.fragment.mainactivity.topbar + +import android.content.Context +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.KeyEvent +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputMethodManager +import android.widget.EditText +import android.widget.ImageButton +import android.widget.TextView +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.R +import com.futo.platformplayer.stores.SearchHistoryStorage +import com.futo.platformplayer.Settings +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.fragment.mainactivity.main.* +import com.futo.platformplayer.models.SearchType + +class SearchTopBarFragment : TopFragment() { + private val TAG = "SearchTopBarFragment" + + private var _editSearch: EditText? = null; + private var _buttonClearSearch: ImageButton? = null; + private var _buttonFilter: ImageButton? = null; + private var _buttonBack: ImageButton? = null; + private var _inputMethodManager: InputMethodManager? = null; + private var _shouldFocus = false; + private var _searchType: SearchType = SearchType.VIDEO; + private var _channelUrl: String? = null; + + private var _lastQuery = ""; + + private val _textChangeListener = object : TextWatcher { + override fun afterTextChanged(s: Editable?) = Unit + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + val text = _editSearch?.text.toString(); + if (text.isBlank()) + _buttonClearSearch?.visibility = EditText.INVISIBLE; + else + _buttonClearSearch?.visibility = EditText.VISIBLE; + onTextChange.emit(text); + } + }; + + private val _searchDoneListener = object : TextView.OnEditorActionListener { + override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean { + if (actionId != EditorInfo.IME_ACTION_DONE) + return false + + onDone(); + return true; + } + }; + + val onFilterClick = Event0(); + val onSearch = Event1(); + val onTextChange = Event1(); + + override fun onAttach(context: Context) { + super.onAttach(context); + _inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager; + } + + override fun onDetach() { + super.onDetach(); + _inputMethodManager = null; + } + + override fun onShown(parameter: Any?) { + if (parameter is String) { + this.setText(parameter); + _channelUrl = null; + } else if (parameter is SearchType) { + _searchType = parameter; + _channelUrl = null; + } else if (parameter is SuggestionsFragmentData) { + this.setText(parameter.query); + _searchType = parameter.searchType; + _channelUrl = parameter.channelUrl; + } + + if(currentMain is SuggestionsFragment) + this.focus(); + else + this.clearFocus(); + } + override fun onHide() { + clearFocus(); + } + + fun focus() { + val editSearch = _editSearch; + val inputMethodManager = _inputMethodManager; + if (editSearch != null && inputMethodManager != null) { + _editSearch?.requestFocus(); + _inputMethodManager?.showSoftInput(_editSearch, 0); + _shouldFocus = false; + } else { + _shouldFocus = true; + } + } + fun clear() { + _editSearch?.text?.clear(); + if (currentMain !is SuggestionsFragment) { + navigate(SuggestionsFragmentData("", _searchType, _channelUrl), false); + } else { + onSearch.emit(""); + } + } + fun clearFocus(){ + _editSearch?.clearFocus(); + _inputMethodManager?.hideSoftInputFromWindow(_editSearch?.windowToken, 0); + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_search_top_bar, container, false); + + val buttonClearSearch: ImageButton = view.findViewById(R.id.button_clear_search); + val editSearch: EditText = view.findViewById(R.id.edit_search); + val buttonBack: ImageButton = view.findViewById(R.id.button_back); + _buttonFilter = view.findViewById(R.id.button_filter); + + editSearch.setOnEditorActionListener(_searchDoneListener); + editSearch.addTextChangedListener(_textChangeListener); + + buttonClearSearch.setOnClickListener { + clear(); + focus(); + } + + buttonBack.setOnClickListener { + close(); + }; + + _buttonFilter?.setOnClickListener { + onFilterClick.emit(); + }; + setFilterButtonVisible(false); + + _buttonClearSearch = buttonClearSearch; + _editSearch = editSearch; + + return view; + } + + override fun onResume() { + super.onResume(); + + //TODO: Supposed to be in onCreateView, but EditText Lifecycle appears broken there. + setText(_lastQuery); + + if (_shouldFocus) { + focus(); + } + } + + override fun onDestroyView() { + super.onDestroyView(); + + _buttonClearSearch?.setOnClickListener(null); + _buttonClearSearch = null; + _editSearch?.removeTextChangedListener(_textChangeListener); + _editSearch?.setOnClickListener(null); + _editSearch = null; + _buttonBack?.setOnClickListener(null); + _buttonBack = null; + _buttonFilter?.setOnClickListener(null); + _buttonFilter = null; + } + + fun setText(text: String) { + _lastQuery = text; + val editSearch = _editSearch ?: return; + editSearch.text.clear(); + editSearch.text.append(text); + } + + fun setFilterButtonVisible(visible: Boolean) { + _buttonFilter?.visibility = if (visible) View.VISIBLE else View.GONE; + } + + private fun onDone() { + val editSearch = _editSearch; + if (editSearch != null) { + val text = editSearch.text.toString(); + if (text.length < 3) { + UIDialogs.toast("Please use at least 3 characters"); + return; + } + + editSearch.clearFocus(); + _inputMethodManager?.hideSoftInputFromWindow(editSearch.windowToken, 0); + + if (Settings.instance.search.searchHistory) { + val storage = FragmentedStorage.get(); + storage.add(text); + } + + if (_searchType == SearchType.CREATOR) { + onSearch.emit(text); + } else { + onSearch.emit(text); + } + } else { + Logger.w(TAG, "Unexpected condition happened where done is edit search is null but done is triggered."); + } + } + + companion object { + fun newInstance() = SearchTopBarFragment().apply { } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/TopFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/TopFragment.kt new file mode 100644 index 00000000..3e88962a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/TopFragment.kt @@ -0,0 +1,15 @@ +package com.futo.platformplayer.fragment.mainactivity.topbar + +import com.futo.platformplayer.activities.MainActivity +import com.futo.platformplayer.fragment.mainactivity.MainActivityFragment + +abstract class TopFragment : MainActivityFragment() { + + open fun onShown(parameter: Any? = null) {} + open fun onHide() {} + + fun close() { + isValidMainActivity(); + return (activity as MainActivity).closeSegment(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/helpers/DashManifestCreatorsUtils.kt b/app/src/main/java/com/futo/platformplayer/helpers/DashManifestCreatorsUtils.kt new file mode 100644 index 00000000..7a14e2f6 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/helpers/DashManifestCreatorsUtils.kt @@ -0,0 +1,228 @@ +package com.futo.platformplayer.helpers + +import org.w3c.dom.DOMException +import org.w3c.dom.Document +import org.w3c.dom.Element +import java.io.StringWriter +import java.util.* +import javax.xml.parsers.DocumentBuilderFactory +import javax.xml.transform.OutputKeys +import javax.xml.transform.TransformerFactory +import javax.xml.transform.dom.DOMSource +import javax.xml.transform.stream.StreamResult + +object DashManifestCreatorsUtils { + val MPD = "MPD" + val PERIOD = "Period" + val ADAPTATION_SET = "AdaptationSet" + val ROLE = "Role" + val REPRESENTATION = "Representation" + val AUDIO_CHANNEL_CONFIGURATION = "AudioChannelConfiguration" + val BASE_URL = "BaseURL" + val SEGMENT_BASE = "SegmentBase" + val INITIALIZATION = "Initialization" + + fun setAttribute(element: Element, doc: Document, name: String?, value: String?) { + val attr = doc.createAttribute(name) + attr.value = value + element.setAttributeNode(attr) + } + + fun generateVideoDocumentAndDoCommonElementsGeneration(streamDuration: Long, mimeType: String, id: Int, codec: String, bitrate: Int, width: Int, height: Int, fps: Int): Document { + val doc: Document = generateDocumentAndMpdElement(streamDuration) + generatePeriodElement(doc) + generateAdaptationSetElement(doc, mimeType) + generateRoleElement(doc) + generateVideoRepresentationElement(doc, id, codec, bitrate, width, height, fps) + return doc + } + + //Audio + fun generateAudioDocumentAndDoCommonElementsGeneration(streamDuration: Long, mimeType: String, audioChannels: Int, id: Int, codec: String, bitrate: Int, sampleRate: Int): Document { + val doc: Document = generateDocumentAndMpdElement(streamDuration) + generatePeriodElement(doc) + generateAdaptationSetElement(doc, mimeType) + generateRoleElement(doc) + generateAudioRepresentationElement(doc, id, codec, bitrate, sampleRate) + generateAudioChannelConfigurationElement(doc, audioChannels) + return doc + } + + private fun generateDocumentAndMpdElement(duration: Long): Document { + try { + val doc: Document = newDocument() + val mpdElement = + doc.createElement(MPD) + doc.appendChild(mpdElement) + setAttribute(mpdElement, doc, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") + setAttribute(mpdElement, doc, "xmlns", "urn:mpeg:DASH:schema:MPD:2011") + setAttribute(mpdElement, doc, "xsi:schemaLocation", "urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd") + setAttribute(mpdElement, doc, "minBufferTime", "PT1.500S") + setAttribute(mpdElement, doc, "profiles", "urn:mpeg:dash:profile:full:2011") + setAttribute(mpdElement, doc, "type", "static") + setAttribute(mpdElement, doc, "mediaPresentationDuration", String.format(Locale.ENGLISH, "PT%.3fS", duration / 1000.0)) + return doc + } catch (e: Exception) { + throw Exception("Could not generate the DASH manifest or append the MPD doc to it", e) + } + } + + private fun generatePeriodElement(doc: Document) { + try { + val mpdElement = doc.getElementsByTagName(MPD).item(0) as Element + val periodElement = doc.createElement(PERIOD) + mpdElement.appendChild(periodElement) + } catch (e: DOMException) { + throw Exception(PERIOD, e) + } + } + + private fun generateAdaptationSetElement(doc: Document, mimeType: String) { + try { + val periodElement = doc.getElementsByTagName(PERIOD).item(0) as Element + val adaptationSetElement = doc.createElement(ADAPTATION_SET) + setAttribute(adaptationSetElement, doc, "id", "0") + if (mimeType.isEmpty()) { + throw Exception("the MediaFormat or its mime type is null or empty") + } + + setAttribute(adaptationSetElement, doc, "mimeType", mimeType) + setAttribute(adaptationSetElement, doc, "subsegmentAlignment", "true") + periodElement.appendChild(adaptationSetElement) + } catch (e: DOMException) { + throw Exception(ADAPTATION_SET, e) + } + } + + private fun generateRoleElement(doc: Document) { + try { + val adaptationSetElement = doc.getElementsByTagName(ADAPTATION_SET).item(0) as Element + val roleElement = doc.createElement(ROLE) + setAttribute(roleElement, doc, "schemeIdUri", "urn:mpeg:DASH:role:2011") + setAttribute(roleElement, doc, "value", "main") + adaptationSetElement.appendChild(roleElement) + } catch (e: DOMException) { + throw Exception(e) + } + } + + private fun generateVideoRepresentationElement(doc: Document, id: Int, codec: String, bitrate: Int, width: Int, height: Int, fps: Int) { + try { + val adaptationSetElement = doc.getElementsByTagName(ADAPTATION_SET).item(0) as Element + val representationElement = doc.createElement(REPRESENTATION) + if (id <= 0) { + throw Exception("the id of the ItagItem is <= 0") + } + setAttribute(representationElement, doc, "id", id.toString()) + if (codec.isEmpty()) { + throw Exception("the codec value of the ItagItem is null or empty") + } + setAttribute(representationElement, doc, "codecs", codec) + setAttribute(representationElement, doc, "startWithSAP", "1") + setAttribute(representationElement, doc, "maxPlayoutRate", "1") + if (bitrate <= 0) { + throw Exception("the bitrate of the ItagItem is <= 0") + } + setAttribute(representationElement, doc, "bandwidth", bitrate.toString()) + if (height <= 0 && width <= 0) { + throw Exception("both width and height of the ItagItem are <= 0") + } + if (width > 0) { + setAttribute( + representationElement, + doc, + "width", + width.toString() + ) + } + setAttribute(representationElement, doc, "height", height.toString()) + if (fps > 0) { + setAttribute(representationElement, doc, "frameRate", fps.toString()) + } + adaptationSetElement.appendChild(representationElement) + } catch (e: DOMException) { + throw Exception(e) + } + } + + private fun generateAudioRepresentationElement(doc: Document, id: Int, codec: String, bitrate: Int, sampleRate: Int) { + try { + val adaptationSetElement = doc.getElementsByTagName(ADAPTATION_SET).item(0) as Element + val representationElement = doc.createElement(REPRESENTATION) + if (id <= 0) { + throw Exception("the id of the ItagItem is <= 0") + } + setAttribute(representationElement, doc, "id", id.toString()) + if (codec.isEmpty()) { + throw Exception("the codec value of the ItagItem is null or empty") + } + setAttribute(representationElement, doc, "codecs", codec) + setAttribute(representationElement, doc, "startWithSAP", "1") + setAttribute(representationElement, doc, "maxPlayoutRate", "1") + if (bitrate <= 0) { + throw Exception("the bitrate of the ItagItem is <= 0") + } + setAttribute(representationElement, doc, "bandwidth", bitrate.toString()) + val audioSamplingRateAttribute = doc.createAttribute("audioSamplingRate") + audioSamplingRateAttribute.value = sampleRate.toString() + adaptationSetElement.appendChild(representationElement) + } catch (e: DOMException) { + throw Exception(e) + } + } + + private fun generateAudioChannelConfigurationElement(doc: Document, audioChannels: Int) { + try { + val representationElement = doc.getElementsByTagName(REPRESENTATION).item(0) as Element + val audioChannelConfigurationElement = doc.createElement(AUDIO_CHANNEL_CONFIGURATION) + setAttribute(audioChannelConfigurationElement, doc, "schemeIdUri", "urn:mpeg:dash:23003:3:audio_channel_configuration:2011") + if (audioChannels <= 0) { + throw Exception("the number of audioChannels in the ItagItem is <= 0: $audioChannels") + } + setAttribute(audioChannelConfigurationElement, doc, "value", audioChannels.toString()) + representationElement.appendChild(audioChannelConfigurationElement) + } catch (e: DOMException) { + throw Exception(e) + } + } + + fun buildAndCacheResult(originalBaseStreamingUrl: String, doc: Document, manifestCreatorCache: ManifestCreatorCache): String { + try { + val documentXml: String = documentToXml(doc) + manifestCreatorCache.put(originalBaseStreamingUrl, documentXml) + return documentXml + } catch (e: Exception) { + throw Exception("Could not convert the DASH manifest generated to a string", e) + } + } + + private fun newDocument(): Document { + val documentBuilderFactory = DocumentBuilderFactory.newInstance() + try { + documentBuilderFactory.setAttribute("http://javax.xml.XMLConstants/property/accessExternalDTD", "") + documentBuilderFactory.setAttribute("http://javax.xml.XMLConstants/property/accessExternalSchema", "") + } catch (ignored: Exception) { + // Ignore exceptions as setting these attributes to secure XML generation is not + // supported by all platforms (like the Android implementation) + } + return documentBuilderFactory.newDocumentBuilder().newDocument() + } + + private fun documentToXml(doc: Document): String { + val transformerFactory = TransformerFactory.newInstance() + try { + transformerFactory.setAttribute("http://javax.xml.XMLConstants/property/accessExternalDTD", "") + transformerFactory.setAttribute("http://javax.xml.XMLConstants/property/accessExternalSchema", "") + } catch (ignored: Exception) { + // Ignore exceptions as setting these attributes to secure XML generation is not + // supported by all platforms (like the Android implementation) + } + val transformer = transformerFactory.newTransformer() + transformer.setOutputProperty(OutputKeys.VERSION, "1.0") + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8") + transformer.setOutputProperty(OutputKeys.STANDALONE, "no") + val result = StringWriter() + transformer.transform(DOMSource(doc), StreamResult(result)) + return result.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/helpers/FileHelper.kt b/app/src/main/java/com/futo/platformplayer/helpers/FileHelper.kt new file mode 100644 index 00000000..0f8565a2 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/helpers/FileHelper.kt @@ -0,0 +1,12 @@ +package com.futo.platformplayer.helpers + +class FileHelper { + companion object { + val allowedCharacters = HashSet("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz-.".toCharArray().toList()); + + + fun String.sanitizeFileName(): String { + return this.filter { allowedCharacters.contains(it) }; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/helpers/ManifestCreatorCache.kt b/app/src/main/java/com/futo/platformplayer/helpers/ManifestCreatorCache.kt new file mode 100644 index 00000000..08545bdc --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/helpers/ManifestCreatorCache.kt @@ -0,0 +1,64 @@ +package com.futo.platformplayer.helpers + +import java.io.Serializable +import java.util.concurrent.ConcurrentHashMap + +class ManifestCreatorCache : Serializable { + private val concurrentHashMap: ConcurrentHashMap> + private var maximumSize: Int = DEFAULT_MAXIMUM_SIZE + private var clearFactor: Double = DEFAULT_CLEAR_FACTOR + + init { + concurrentHashMap = ConcurrentHashMap>() + } + + fun containsKey(key: K): Boolean { + return concurrentHashMap.containsKey(key) + } + + operator fun get(key: K): Pair? { + return concurrentHashMap[key] + } + + fun put(key: K, value: V): V? { + if (!concurrentHashMap.containsKey(key) && concurrentHashMap.size == maximumSize) { + val newCacheSize = Math.round(maximumSize * clearFactor).toInt() + keepNewestEntries(if (newCacheSize != 0) newCacheSize else 1) + } + val returnValue: Pair? = concurrentHashMap.put(key, Pair(concurrentHashMap.size, value)) + return if (returnValue == null) null else returnValue.second + } + + fun clear() { + concurrentHashMap.clear() + } + + fun size(): Int { + return concurrentHashMap.size + } + + override fun toString(): String { + return "ManifestCreatorCache[clearFactor=$clearFactor, maximumSize=$maximumSize, concurrentHashMap=$concurrentHashMap]" + } + + private fun keepNewestEntries(newLimit: Int) { + val difference = concurrentHashMap.size - newLimit + val entriesToRemove: ArrayList>> = ArrayList() + concurrentHashMap.entries.forEach { entry: MutableMap.MutableEntry> -> + val value: Pair = entry.value + if (value.first < difference) { + entriesToRemove.add(entry) + } else { + entry.setValue(value.copy(value.first - difference, value.second)) + } + } + entriesToRemove.forEach { (key, value): Map.Entry> -> + concurrentHashMap.remove(key, value) + } + } + + companion object { + const val DEFAULT_MAXIMUM_SIZE = Int.MAX_VALUE + const val DEFAULT_CLEAR_FACTOR = 0.75 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/helpers/ProgressiveDashManifestCreator.kt b/app/src/main/java/com/futo/platformplayer/helpers/ProgressiveDashManifestCreator.kt new file mode 100644 index 00000000..8542f684 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/helpers/ProgressiveDashManifestCreator.kt @@ -0,0 +1,78 @@ +package com.futo.platformplayer.helpers + +import org.w3c.dom.DOMException +import org.w3c.dom.Document +import org.w3c.dom.Element + +object ProgressiveDashManifestCreator { + private val PROGRESSIVE_STREAMS_CACHE: ManifestCreatorCache = ManifestCreatorCache() + + fun fromVideoProgressiveStreamingUrl(progressiveStreamingBaseUrl: String, streamDuration: Long, mimeType: String, id: Int, codec: String, bitrate: Int, width: Int, height: Int, fps: Int, indexStart: Int, indexEnd: Int, initStart: Int, initEnd: Int): String { + if (PROGRESSIVE_STREAMS_CACHE.containsKey(progressiveStreamingBaseUrl)) { + return PROGRESSIVE_STREAMS_CACHE[progressiveStreamingBaseUrl]!!.second + } + + val doc = DashManifestCreatorsUtils.generateVideoDocumentAndDoCommonElementsGeneration(streamDuration, mimeType, id, codec, bitrate, width, height, fps) + generateBaseUrlElement(doc, progressiveStreamingBaseUrl) + generateSegmentBaseElement(doc, indexStart, indexEnd) + generateInitializationElement(doc, initStart, initEnd) + return DashManifestCreatorsUtils.buildAndCacheResult(progressiveStreamingBaseUrl, doc, PROGRESSIVE_STREAMS_CACHE) + } + + fun fromAudioProgressiveStreamingUrl(progressiveStreamingBaseUrl: String, streamDuration: Long, mimeType: String, audioChannels: Int, id: Int, codec: String, bitrate: Int, sampleRate: Int, indexStart: Int, indexEnd: Int, initStart: Int, initEnd: Int): String { + if (PROGRESSIVE_STREAMS_CACHE.containsKey(progressiveStreamingBaseUrl)) { + return PROGRESSIVE_STREAMS_CACHE[progressiveStreamingBaseUrl]!!.second + } + + val doc = DashManifestCreatorsUtils.generateAudioDocumentAndDoCommonElementsGeneration(streamDuration, mimeType, audioChannels, id, codec, bitrate, sampleRate) + generateBaseUrlElement(doc, progressiveStreamingBaseUrl) + generateSegmentBaseElement(doc, indexStart, indexEnd) + generateInitializationElement(doc, initStart, initEnd) + return DashManifestCreatorsUtils.buildAndCacheResult(progressiveStreamingBaseUrl, doc, PROGRESSIVE_STREAMS_CACHE) + } + + fun clearCache() { + PROGRESSIVE_STREAMS_CACHE.clear(); + } + + private fun generateBaseUrlElement(doc: Document, baseUrl: String) { + try { + val representationElement = doc.getElementsByTagName(DashManifestCreatorsUtils.REPRESENTATION).item(0) as Element + val baseURLElement = doc.createElement(DashManifestCreatorsUtils.BASE_URL) + baseURLElement.textContent = baseUrl + representationElement.appendChild(baseURLElement) + } catch (e: DOMException) { + throw Exception(e) + } + } + + private fun generateSegmentBaseElement(doc: Document, indexStart: Int, indexEnd: Int) { + try { + val representationElement = doc.getElementsByTagName(DashManifestCreatorsUtils.REPRESENTATION).item(0) as Element + val segmentBaseElement = doc.createElement(DashManifestCreatorsUtils.SEGMENT_BASE) + val range: String = "$indexStart-$indexEnd" + if (indexStart < 0 || indexEnd < 0) { + throw Exception("ItagItem's indexStart or indexEnd are < 0: $range") + } + DashManifestCreatorsUtils.setAttribute(segmentBaseElement, doc, "indexRange", range) + representationElement.appendChild(segmentBaseElement) + } catch (e: DOMException) { + throw Exception(e) + } + } + + private fun generateInitializationElement(doc: Document, initStart: Int, initEnd: Int) { + try { + val segmentBaseElement = doc.getElementsByTagName(DashManifestCreatorsUtils.SEGMENT_BASE).item(0) as Element + val initializationElement = doc.createElement(DashManifestCreatorsUtils.INITIALIZATION) + val range = "$initStart-$initEnd" + if (initStart < 0 || initEnd < 0) { + throw Exception("ItagItem's initStart and/or initEnd are/is < 0: $range") + } + DashManifestCreatorsUtils.setAttribute(initializationElement, doc, "range", range) + segmentBaseElement.appendChild(initializationElement) + } catch (e: DOMException) { + throw Exception(e) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/helpers/VideoHelper.kt b/app/src/main/java/com/futo/platformplayer/helpers/VideoHelper.kt new file mode 100644 index 00000000..21799036 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/helpers/VideoHelper.kt @@ -0,0 +1,156 @@ +package com.futo.platformplayer.helpers + +import android.net.Uri +import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor +import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor +import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource +import com.futo.platformplayer.api.media.platforms.js.models.sources.JSAudioUrlRangeSource +import com.futo.platformplayer.api.media.platforms.js.models.sources.JSVideoUrlRangeSource +import com.futo.platformplayer.logging.Logger +import com.google.android.exoplayer2.MediaItem +import com.google.android.exoplayer2.source.MediaSource +import com.google.android.exoplayer2.source.dash.DashMediaSource +import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser +import com.google.android.exoplayer2.upstream.ResolvingDataSource + +class VideoHelper { + companion object { + + fun selectBestVideoSource(desc: IVideoSourceDescriptor, desiredPixelCount : Int, prefContainers : Array) : IVideoSource? = selectBestVideoSource(desc.videoSources.toList(), desiredPixelCount, prefContainers); + fun selectBestVideoSource(sources: Iterable, desiredPixelCount : Int, prefContainers : Array) : IVideoSource? { + val targetVideo = if(desiredPixelCount > 0) + sources.toList() + .sortedBy { x -> Math.abs(x.height * x.width - desiredPixelCount) } + .firstOrNull(); + else + sources.toList() + .lastOrNull(); + + val hasPriority = sources.any { it.priority }; + + val targetPixelCount = if(targetVideo != null) targetVideo.width * targetVideo.height else desiredPixelCount; + val altSources = if(hasPriority) { + sources.filter { it.priority }.sortedBy { x -> Math.abs(x.height * x.width - targetPixelCount) }; + } else { + sources.filter { it.height == (targetVideo?.height ?: 0) }; + } + + var bestSource = altSources.firstOrNull(); + for (prefContainer in prefContainers) { + val betterSource = altSources.firstOrNull { it.container == prefContainer }; + if(betterSource != null) { + bestSource = betterSource; + break; + } + } + + return bestSource; + } + + + fun selectBestAudioSource(desc: IVideoSourceDescriptor, prefContainers : Array, prefLanguage: String? = null, targetBitrate: Long? = null) : IAudioSource? { + if(!desc.isUnMuxed) + return null; + return selectBestAudioSource((desc as VideoUnMuxedSourceDescriptor).audioSources.toList(), prefContainers, prefLanguage); + } + fun selectBestAudioSource(altSources : Iterable, prefContainers : Array, preferredLanguage: String? = null, targetBitrate: Long? = null) : IAudioSource? { + val languageToFilter = if(preferredLanguage != null && altSources.any { it.language == preferredLanguage }) + preferredLanguage + else if(preferredLanguage == null) null + else "Unknown"; + + var usableSources = if(languageToFilter != null && altSources.any { it.language == languageToFilter }) + altSources.filter { it.language == languageToFilter }.sortedBy { it.bitrate }.toList(); + else altSources.sortedBy { it.bitrate }; + + if(usableSources.any { it.priority }) + usableSources = usableSources.filter { it.priority }; + + + var bestSource = if(targetBitrate != null) + usableSources.minByOrNull { Math.abs(it.bitrate - targetBitrate) }; + else + usableSources.lastOrNull(); + + for (prefContainer in prefContainers) { + val betterSources = usableSources.filter { it.container == prefContainer }; + val betterSource = if(targetBitrate != null) + betterSources.minByOrNull { Math.abs(it.bitrate - targetBitrate) }; + else + betterSources.lastOrNull(); + + if(betterSource != null) { + bestSource = betterSource; + break; + } + } + return bestSource; + } + + var breakOnce = hashSetOf() + fun convertItagSourceToChunkedDashSource(videoSource: JSVideoUrlRangeSource) : MediaSource { + var urlToUse = videoSource.getVideoUrl(); + /* + //TODO: REMOVE THIS, PURPOSELY 403s + if(urlToUse.contains("sig=") && !breakOnce.contains(urlToUse)) { + breakOnce.add(urlToUse); + val sigIndex = urlToUse.indexOf("sig="); + urlToUse = urlToUse.substring(0, sigIndex) + "sig=0" + urlToUse.substring(sigIndex + 4); + }*/ + + val manifestConfig = ProgressiveDashManifestCreator.fromVideoProgressiveStreamingUrl(urlToUse, + videoSource.duration * 1000, + videoSource.container, + videoSource.itagId ?: 1, + videoSource.codec, + videoSource.bitrate, + videoSource.width, + videoSource.height, + -1, + videoSource.indexStart ?: 0, + videoSource.indexEnd ?: 0, + videoSource.initStart ?: 0, + videoSource.initEnd ?: 0 + ); + + val manifest = DashManifestParser().parse(Uri.parse(""), manifestConfig.byteInputStream()); + + return DashMediaSource.Factory(ResolvingDataSource.Factory(videoSource.getHttpDataSourceFactory(), ResolvingDataSource.Resolver { dataSpec -> + Logger.v("PLAYBACK", "Video REQ Range [" + dataSpec.position + "-" + (dataSpec.position + dataSpec.length) + "](" + dataSpec.length + ")", null); + return@Resolver dataSpec; + })) + .createMediaSource(manifest, + MediaItem.Builder() + .setUri(Uri.parse(videoSource.getVideoUrl())) + .build()) + } + + fun convertItagSourceToChunkedDashSource(audioSource: JSAudioUrlRangeSource) : MediaSource { + val manifestConfig = ProgressiveDashManifestCreator.fromAudioProgressiveStreamingUrl(audioSource.getAudioUrl(), + audioSource.duration?.times(1000) ?: 0, + audioSource.container, + audioSource.audioChannels, + audioSource.itagId ?: 1, + audioSource.codec, + audioSource.bitrate, + -1, + audioSource.indexStart ?: 0, + audioSource.indexEnd ?: 0, + audioSource.initStart ?: 0, + audioSource.initEnd ?: 0 + ); + + val manifest = DashManifestParser().parse(Uri.parse(""), manifestConfig.byteInputStream()); + + return DashMediaSource.Factory(ResolvingDataSource.Factory(audioSource.getHttpDataSourceFactory(), ResolvingDataSource.Resolver { dataSpec -> + Logger.v("PLAYBACK", "Audio REQ Range [" + dataSpec.position + "-" + (dataSpec.position + dataSpec.length) + "](" + dataSpec.length + ")", null); + return@Resolver dataSpec; + })) + .createMediaSource(manifest, + MediaItem.Builder() + .setUri(Uri.parse(audioSource.getAudioUrl())) + .build()) + } + } +} diff --git a/app/src/main/java/com/futo/platformplayer/images/GlideHelper.kt b/app/src/main/java/com/futo/platformplayer/images/GlideHelper.kt new file mode 100644 index 00000000..682ad9be --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/images/GlideHelper.kt @@ -0,0 +1,38 @@ +package com.futo.platformplayer.images + +import android.graphics.drawable.Drawable +import android.widget.ImageView +import com.bumptech.glide.Glide +import com.bumptech.glide.RequestBuilder +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions +import com.futo.platformplayer.api.media.models.Thumbnails + +class GlideHelper { + + + companion object { + fun ImageView.loadThumbnails(thumbnails: Thumbnails, isHQ: Boolean = true, continuation: ((RequestBuilder) -> Unit)? = null) { + val url = if(isHQ) thumbnails.getHQThumbnail() ?: thumbnails.getLQThumbnail() else thumbnails.getLQThumbnail(); + + val req = Glide.with(this).load(url); + + if (thumbnails.hasMultiple() && false) { //TODO: Resolve issue where fallback triggered on second loads? + val fallbackUrl = if (isHQ) thumbnails.getLQThumbnail() else thumbnails.getHQThumbnail(); + if (continuation != null) + req.error(continuation(Glide.with(this).load(fallbackUrl))) + else + req.error(Glide.with(this).load(fallbackUrl).into(this)); + } + else if (continuation != null) + continuation(req); + else + req.into(this); + } + + + fun RequestBuilder.crossfade(): RequestBuilder { + return this.transition(DrawableTransitionOptions.withCrossFade()); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/images/GrayjayAppGlideModule.java b/app/src/main/java/com/futo/platformplayer/images/GrayjayAppGlideModule.java new file mode 100644 index 00000000..60d962ac --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/images/GrayjayAppGlideModule.java @@ -0,0 +1,18 @@ +package com.futo.platformplayer.images; + +import android.content.Context; +import android.util.Log; +import com.bumptech.glide.Glide; +import com.bumptech.glide.Registry; +import com.bumptech.glide.annotation.GlideModule; +import com.bumptech.glide.module.AppGlideModule; +import java.nio.ByteBuffer; + +@GlideModule +public class GrayjayAppGlideModule extends AppGlideModule { + @Override + public void registerComponents(Context context, Glide glide, Registry registry) { + Log.i("GrayjayAppGlideModule", "registerComponents called"); + registry.prepend(String.class, ByteBuffer.class, new PolycentricModelLoader.Factory()); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/images/PolycentricModelLoader.java b/app/src/main/java/com/futo/platformplayer/images/PolycentricModelLoader.java new file mode 100644 index 00000000..1b8a3e76 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/images/PolycentricModelLoader.java @@ -0,0 +1,89 @@ +package com.futo.platformplayer.images; + +import androidx.annotation.NonNull; + +import com.bumptech.glide.Priority; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.Options; +import com.bumptech.glide.load.data.DataFetcher; +import com.bumptech.glide.load.model.ModelLoader; +import com.bumptech.glide.load.model.ModelLoaderFactory; +import com.bumptech.glide.load.model.MultiModelLoaderFactory; +import com.bumptech.glide.signature.ObjectKey; +import com.futo.platformplayer.polycentric.PolycentricCache; + +import kotlin.Unit; +import kotlinx.coroutines.Deferred; +import java.lang.Exception; +import java.nio.ByteBuffer; +import java.util.concurrent.CancellationException; + +public class PolycentricModelLoader implements ModelLoader { + + @Override + public boolean handles(String model) { + return model.startsWith("polycentric://"); + } + + @Override + public ModelLoader.LoadData buildLoadData(@NonNull String model, int width, int height, @NonNull Options options) { + return new ModelLoader.LoadData(new ObjectKey(model), new Fetcher(model)); + } + + public static class Factory implements ModelLoaderFactory { + @NonNull + @Override + public ModelLoader build(@NonNull MultiModelLoaderFactory multiFactory) { + return new PolycentricModelLoader(); + } + + @Override + public void teardown() { } + } + + public static class Fetcher implements DataFetcher { + private final String _model; + private Deferred _deferred; + + public Fetcher(String model) { + this._model = model; + } + + @NonNull + @Override + public DataSource getDataSource() { + return DataSource.REMOTE; + } + + @Override + public void loadData(@NonNull Priority priority, @NonNull DataFetcher.DataCallback callback) { + _deferred = PolycentricCache.getInstance().getDataAsync(_model); + _deferred.invokeOnCompletion(throwable -> { + if (throwable != null) { + callback.onLoadFailed(new Exception(throwable)); + } + final ByteBuffer completed = _deferred.getCompleted(); + callback.onDataReady(completed); + return Unit.INSTANCE; + }); + } + + @Override + public void cancel() { + if (_deferred != null) { + _deferred.cancel(new CancellationException("Cancelled by Fetcher.")); + } + } + + @Override + public void cleanup() { + _deferred = null; + } + + @NonNull + @Override + public Class getDataClass() { + return ByteBuffer.class; + } + } +} diff --git a/app/src/main/java/com/futo/platformplayer/listeners/OrientationManager.kt b/app/src/main/java/com/futo/platformplayer/listeners/OrientationManager.kt new file mode 100644 index 00000000..f78b4652 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/listeners/OrientationManager.kt @@ -0,0 +1,47 @@ +package com.futo.platformplayer.listeners + +import android.content.Context +import android.view.OrientationEventListener +import com.futo.platformplayer.constructs.Event1 + +class OrientationManager : OrientationEventListener { + + val onOrientationChanged = Event1(); + + var orientation : Orientation = Orientation.PORTRAIT; + + constructor(context: Context) : super(context) { } + constructor(context: Context, rate: Int) : super(context, rate) { } + init { + + } + + override fun onOrientationChanged(orientationAnglep: Int) { + if(orientationAnglep == -1) + return; + + var newOrientation = Orientation.PORTRAIT; + if(orientationAnglep > 60 && orientationAnglep < 140) + newOrientation = Orientation.REVERSED_LANDSCAPE; + else if(orientationAnglep >= 140 && orientationAnglep <= 220) + newOrientation = Orientation.REVERSED_PORTRAIT; + else if(orientationAnglep >= 220 && orientationAnglep <= 300) + newOrientation = Orientation.LANDSCAPE; + else + newOrientation = Orientation.PORTRAIT; + + if(newOrientation != orientation) { + orientation = newOrientation; + onOrientationChanged.emit(newOrientation); + } + } + + + //TODO: Perhaps just use ActivityInfo orientations instead.. + enum class Orientation { + PORTRAIT, + LANDSCAPE, + REVERSED_PORTRAIT, + REVERSED_LANDSCAPE + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/logging/AndroidLogConsumer.kt b/app/src/main/java/com/futo/platformplayer/logging/AndroidLogConsumer.kt new file mode 100644 index 00000000..d121a2ae --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/logging/AndroidLogConsumer.kt @@ -0,0 +1,27 @@ +package com.futo.platformplayer.logging + +import android.util.Log +import com.futo.platformplayer.logging.ILogConsumer +import com.futo.platformplayer.logging.LogLevel + +class AndroidLogConsumer : ILogConsumer { + override fun willConsume(level: LogLevel, tag: String): Boolean { + return Log.isLoggable(tag, when (level) { + LogLevel.VERBOSE -> Log.VERBOSE + LogLevel.INFORMATION -> Log.INFO + LogLevel.WARNING -> Log.WARN + LogLevel.ERROR -> Log.ERROR + else -> throw Exception("Unknown log level") + }); + } + + override fun consume(level: LogLevel, tag: String, text: String?, e: Throwable?) { + when (level) { + LogLevel.VERBOSE -> Log.v("INTERNAL;$tag", text, e) + LogLevel.INFORMATION -> Log.i("INTERNAL;$tag", text, e) + LogLevel.WARNING -> Log.w("INTERNAL;$tag", text, e) + LogLevel.ERROR -> Log.e("INTERNAL;$tag", text, e) + else -> throw Exception("Unknown log level") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/logging/FileLogConsumer.kt b/app/src/main/java/com/futo/platformplayer/logging/FileLogConsumer.kt new file mode 100644 index 00000000..ef773462 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/logging/FileLogConsumer.kt @@ -0,0 +1,95 @@ +package com.futo.platformplayer.logging + +import android.util.Log +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.constructs.Event1 +import java.io.BufferedWriter +import java.io.Closeable +import java.io.File +import java.io.FileWriter +import java.util.concurrent.ConcurrentLinkedQueue + +class FileLogConsumer : ILogConsumer, Closeable { + private var _logThread: Thread? = null; + private var _shouldSubmitLogs = false; + private val _linesToWrite = ConcurrentLinkedQueue(); + private var _writer: BufferedWriter? = null; + private var _running: Boolean = false; + private var _file: File; + private val _level: LogLevel; + + constructor(file: File, level: LogLevel, append: Boolean) { + _file = file; + _level = level; + + if (level.value < LogLevel.ERROR.value) { + throw Exception("Do not use a file logger with log level NONE."); + } + + if (!file.exists()) { + file.createNewFile(); + } + + _writer = BufferedWriter(FileWriter(file, append)) + val t = Thread { + Log.i(TAG, "Started log writer."); + + while (_running) { + Thread.sleep(1000); + + try { + if (_shouldSubmitLogs) { + submitLogs(); + } + + while (_linesToWrite.isNotEmpty()) { + _writer?.appendLine(_linesToWrite.remove()); + } + + _writer?.flush(); + } catch (e: Throwable) { + Log.e(TAG, "Failed to process logs.", e); + } + } + + Log.i(TAG, "Stopped log writer."); + } + t.start(); + + _logThread = t; + _running = true; + } + + + override fun willConsume(level: LogLevel, tag: String): Boolean { + return level.value <= _level.value; + } + + override fun consume(level: LogLevel, tag: String, text: String?, e: Throwable?) { + _linesToWrite.add(Logging.buildLogString(level, tag, text, e)); + } + + fun submitLogs() { + val id = Logging.submitLog(_file); + _shouldSubmitLogs = false; + Logger.onLogSubmitted.emit(id) + } + + fun submitLogsAsync() { + _shouldSubmitLogs = true; + } + + override fun close() { + Log.i(TAG, "Requesting log writer exit."); + + _running = false; + _writer?.close(); + _writer = null; + _logThread?.join(); + _logThread = null; + } + + companion object { + private const val TAG = "FileLogConsumer" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/logging/Logger.kt b/app/src/main/java/com/futo/platformplayer/logging/Logger.kt new file mode 100644 index 00000000..0480e5ed --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/logging/Logger.kt @@ -0,0 +1,89 @@ +package com.futo.platformplayer.logging + +import com.futo.platformplayer.constructs.Event1 + +enum class LogLevel(val value: Int) { + NONE(0), + ERROR(1), + WARNING(2), + INFORMATION(3), + VERBOSE(4); + + companion object { + fun fromInt(value: Int): LogLevel { + return when (value) { + 0 -> NONE + 1 -> ERROR + 2 -> WARNING + 3 -> INFORMATION + 4 -> VERBOSE + else -> throw IllegalArgumentException("Invalid LogLevel value: $value") + } + } + } +} + +interface ILogConsumer { + fun willConsume(level: LogLevel, tag: String) : Boolean; + fun consume(level: LogLevel, tag: String, text: String?, e: Throwable? = null); +} + +class Logger { + companion object { + private const val TAG = "Logger"; + + private var _logConsumers = emptyList(); + + val onLogSubmitted = Event1(); + + val hasConsumers: Boolean get() = !_logConsumers.isEmpty(); + + fun setLogConsumers(logConsumers: List) { + _logConsumers = logConsumers; + } + + fun i(tag: String, e: Throwable? = null, text: () -> String) { log(LogLevel.INFORMATION, tag, e, text); } + fun e(tag: String, e: Throwable? = null, text: () -> String?) { log(LogLevel.ERROR, tag, e, text); } + fun w(tag: String, e: Throwable? = null, text: () -> String?) { log(LogLevel.WARNING, tag, e, text); } + fun v(tag: String, e: Throwable? = null, text: () -> String?) { log(LogLevel.VERBOSE, tag, e, text); } + + fun i(tag: String, text: String, e: Throwable? = null) { log(LogLevel.INFORMATION, tag, text, e); } + fun e(tag: String, text: String?, e: Throwable? = null) { log(LogLevel.ERROR, tag, text, e); } + fun w(tag: String, text: String?, e: Throwable? = null) { log(LogLevel.WARNING, tag, text, e); } + fun v(tag: String, text: String?, e: Throwable? = null) { log(LogLevel.VERBOSE, tag, text, e); } + + fun submitLogs(): Boolean { + var loggingEnabled = false; + for (logConsumer in _logConsumers) { + if (logConsumer is FileLogConsumer) { + logConsumer.submitLogs(); + loggingEnabled = true; + } + } + return loggingEnabled; + } + + fun submitLogsAsync(): Boolean { + var loggingEnabled = false; + for (logConsumer in _logConsumers) { + if (logConsumer is FileLogConsumer) { + logConsumer.submitLogsAsync(); + loggingEnabled = true; + } + } + return loggingEnabled; + } + + private fun log(level: LogLevel, tag: String, e: Throwable? = null, textBuilder: () -> String?) { + if (!_logConsumers.any { c -> c.willConsume(level, tag) }) { + return; + } + + log(level, tag, textBuilder(), e); + } + + private fun log(level: LogLevel, tag: String, text: String?, e: Throwable? = null) { + _logConsumers.forEach { c -> c.consume(level, tag, text, e) }; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/logging/Logging.kt b/app/src/main/java/com/futo/platformplayer/logging/Logging.kt new file mode 100644 index 00000000..8d9e09f6 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/logging/Logging.kt @@ -0,0 +1,77 @@ +package com.futo.platformplayer.logging + +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import okhttp3.* +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.RequestBody.Companion.asRequestBody +import java.io.File +import java.io.PrintWriter +import java.io.StringWriter +import java.text.SimpleDateFormat +import java.util.* + +class Logging { + + companion object { + val logDateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").apply { + timeZone = TimeZone.getTimeZone("UTC") + } + + + //TODO: Why not just stackTraceToString() + private fun throwableToString(t: Throwable): String { + val sw = StringWriter() + val pw = PrintWriter(sw) + t.printStackTrace(pw) + return "${t.message}\n${sw}" + } + + fun buildLogString(logLevel: LogLevel, tag: String, text: String?, e: Throwable? = null): String { + val currentDate = Date(System.currentTimeMillis()); + val timestamp = logDateFormat.format(currentDate); + + val levelString = when (logLevel) { + LogLevel.ERROR -> "e" + LogLevel.WARNING -> "w" + LogLevel.INFORMATION -> "i" + LogLevel.VERBOSE -> "v" + else -> throw Exception("Invalid log level $logLevel") + } + + if (e != null) { + return "($levelString, $tag, ${timestamp}): ${text ?: ""}\n${throwableToString(e)}"; + } else { + return "($levelString, $tag, ${timestamp}): $text"; + } + } + + fun submitLog(file: File): String? { + if (!file.exists()) { + return null; + } + + val requestBody: RequestBody = MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("file", file.name, file.asRequestBody("application/octet-stream".toMediaTypeOrNull())) + .build(); + + val request: Request = Request.Builder() + .url("https://logs.grayjay.app/logs") + //.url("http://192.168.1.231:5413/logs") + .post(requestBody) + .build() + + val client = OkHttpClient() + val response: Response = client.newCall(request).execute() + if (response.isSuccessful) { + val body = response.body?.string(); + return if (body != null) Json.decodeFromString(body) else null; + } else { + Logger.e("Failed to submit log.") { "Failed to submit logs (${response.code}): ${response.body?.string()}" }; + return null; + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/models/CastingDeviceInfo.kt b/app/src/main/java/com/futo/platformplayer/models/CastingDeviceInfo.kt new file mode 100644 index 00000000..a530e415 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/models/CastingDeviceInfo.kt @@ -0,0 +1,18 @@ +package com.futo.platformplayer.models + +import com.futo.platformplayer.casting.CastProtocolType + +@kotlinx.serialization.Serializable +class CastingDeviceInfo { + var name: String; + var type: CastProtocolType; + var addresses: Array; + var port: Int; + + constructor(name: String, type: CastProtocolType, addresses: Array, port: Int) { + this.name = name; + this.type = type; + this.addresses = addresses; + this.port = port; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/models/DiskUsage.kt b/app/src/main/java/com/futo/platformplayer/models/DiskUsage.kt new file mode 100644 index 00000000..0d331596 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/models/DiskUsage.kt @@ -0,0 +1,8 @@ +package com.futo.platformplayer.models + +data class DiskUsage ( + val usage: Long, + val available: Long +) { + val percentage: Double = if((available + usage) > 0) usage.toDouble() / (usage + available) else 0.0; +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/models/HistoryVideo.kt b/app/src/main/java/com/futo/platformplayer/models/HistoryVideo.kt new file mode 100644 index 00000000..b6f092a1 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/models/HistoryVideo.kt @@ -0,0 +1,21 @@ +package com.futo.platformplayer.models + +import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo +import com.futo.platformplayer.serializers.OffsetDateTimeSerializer +import java.time.OffsetDateTime + +@kotlinx.serialization.Serializable +class HistoryVideo { + var video: SerializedPlatformVideo; + var position: Long; + + @kotlinx.serialization.Serializable(with = OffsetDateTimeSerializer::class) + var date: OffsetDateTime; + + + constructor(video: SerializedPlatformVideo, position: Long, date: OffsetDateTime) { + this.video = video; + this.position = position; + this.date = date; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/models/ImageVariable.kt b/app/src/main/java/com/futo/platformplayer/models/ImageVariable.kt new file mode 100644 index 00000000..1497b52d --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/models/ImageVariable.kt @@ -0,0 +1,51 @@ +package com.futo.platformplayer.models + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.widget.ImageView +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import java.io.File + +data class ImageVariable(val url: String? = null, val resId: Int? = null, val bitmap: Bitmap? = null) { + + fun setImageView(imageView: ImageView, fallbackResId: Int = -1) { + if(bitmap != null) { + Glide.with(imageView) + .load(bitmap) + .into(imageView) + } else if(resId != null) { + Glide.with(imageView) + .load(resId) + .into(imageView) + } else if(!url.isNullOrEmpty()) { + Glide.with(imageView) + .load(url) + .placeholder(R.drawable.placeholder_channel_thumbnail) + .into(imageView); + } else if (fallbackResId != -1) { + Glide.with(imageView) + .load(fallbackResId) + .into(imageView) + } else { + Glide.with(imageView) + .clear(imageView) + } + } + + + companion object { + fun fromUrl(url: String): ImageVariable { + return ImageVariable(url, null, null); + } + fun fromResource(id: Int): ImageVariable { + return ImageVariable(null, id, null); + } + fun fromBitmap(bitmap: Bitmap): ImageVariable { + return ImageVariable(null, null, bitmap); + } + fun fromFile(file: File): ImageVariable { + return ImageVariable.fromBitmap(BitmapFactory.decodeFile(file.absolutePath)); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/models/PlatformVideoWithTime.kt b/app/src/main/java/com/futo/platformplayer/models/PlatformVideoWithTime.kt new file mode 100644 index 00000000..4c304928 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/models/PlatformVideoWithTime.kt @@ -0,0 +1,5 @@ +package com.futo.platformplayer.models + +import com.futo.platformplayer.api.media.models.video.IPlatformVideo + +data class PlatformVideoWithTime(val video: IPlatformVideo, val time: Long); \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/models/Playlist.kt b/app/src/main/java/com/futo/platformplayer/models/Playlist.kt new file mode 100644 index 00000000..d7b1035f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/models/Playlist.kt @@ -0,0 +1,61 @@ +package com.futo.platformplayer.models + +import com.caoccao.javet.values.reference.V8ValueArray +import com.caoccao.javet.values.reference.V8ValueObject +import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.api.media.platforms.js.models.JSVideo +import com.futo.platformplayer.getOrThrow +import com.futo.platformplayer.serializers.OffsetDateTimeSerializer +import kotlinx.serialization.Serializable +import java.time.OffsetDateTime +import java.util.* + +@Serializable +class Playlist { + var id: String = UUID.randomUUID().toString(); + var name: String = ""; + var videos: ArrayList = arrayListOf(); + + @kotlinx.serialization.Serializable(with = OffsetDateTimeSerializer::class) + var dateCreation: OffsetDateTime = OffsetDateTime.now(); + @kotlinx.serialization.Serializable(with = OffsetDateTimeSerializer::class) + var dateUpdate: OffsetDateTime = OffsetDateTime.now(); + @kotlinx.serialization.Serializable(with = OffsetDateTimeSerializer::class) + var datePlayed: OffsetDateTime = OffsetDateTime.MIN; + + constructor(){} + constructor(name: String, list: List) { + this.name = name; + this.videos = ArrayList(list); + } + constructor(id: String, name: String, list: List) { + this.id = id; + this.name = name; + this.videos = ArrayList(list); + } + + + companion object { + fun fromV8(config: SourcePluginConfig, obj: V8ValueObject?): Playlist? { + if(obj == null) + return null; + + val contextName = "Playlist"; + + val id = obj.getOrThrow(config, "id", contextName); + val name = obj.getOrThrow(config, "name", contextName); + val videoObjs = obj.getOrThrow(config, "videos", contextName); + + val videos = mutableListOf(); + + for(videoKey in videoObjs.keys) { + val videoObj = videoObjs.get(videoKey); + val jVideo = JSVideo(config, videoObj); + videos.add(jVideo); + } + + return Playlist(id, name, videos.map { SerializedPlatformVideo.fromVideo(it) }); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/models/PlaylistDownloaded.kt b/app/src/main/java/com/futo/platformplayer/models/PlaylistDownloaded.kt new file mode 100644 index 00000000..ed293890 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/models/PlaylistDownloaded.kt @@ -0,0 +1,8 @@ +package com.futo.platformplayer.models + +import com.futo.platformplayer.downloads.PlaylistDownloadDescriptor + +data class PlaylistDownloaded( + val downloadDescriptor: PlaylistDownloadDescriptor, + val playlist: Playlist +); \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/models/SearchType.kt b/app/src/main/java/com/futo/platformplayer/models/SearchType.kt new file mode 100644 index 00000000..368d755b --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/models/SearchType.kt @@ -0,0 +1,7 @@ +package com.futo.platformplayer.models + +enum class SearchType { + VIDEO, + CREATOR, + PLAYLIST +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/models/Subscription.kt b/app/src/main/java/com/futo/platformplayer/models/Subscription.kt new file mode 100644 index 00000000..dbf59fd4 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/models/Subscription.kt @@ -0,0 +1,31 @@ +package com.futo.platformplayer.models + +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.api.media.models.channels.SerializedChannel +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.serializers.OffsetDateTimeSerializer +import java.time.OffsetDateTime + +@kotlinx.serialization.Serializable +class Subscription { + var channel: SerializedChannel; + + @kotlinx.serialization.Serializable(with = OffsetDateTimeSerializer::class) + var lastVideo : OffsetDateTime = OffsetDateTime.MAX; + @kotlinx.serialization.Serializable(with = OffsetDateTimeSerializer::class) + var lastLiveStream : OffsetDateTime = OffsetDateTime.MAX; + + var uploadInterval : Int = 0; + + constructor(channel : SerializedChannel) { + this.channel = channel; + } + + fun updateChannel(channel: IPlatformChannel) { + this.channel = SerializedChannel.fromChannel(channel); + } + + fun updateVideoStatus(allVideos: List? = null, liveStreams: List? = null) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/models/Telemetry.kt b/app/src/main/java/com/futo/platformplayer/models/Telemetry.kt new file mode 100644 index 00000000..ec4b9935 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/models/Telemetry.kt @@ -0,0 +1,16 @@ +package com.futo.platformplayer.models + +@kotlinx.serialization.Serializable +data class Telemetry( + val id: String, + val applicationId: String, + val versionCode: String, + val versionName: String, + val buildType: String, + val debug: Boolean, + val isUnstableBuild: Boolean, + val time: Long, + val brand: String, + val manufacturer: String, + val model: String +) { } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/models/UrlVideoWithTime.kt b/app/src/main/java/com/futo/platformplayer/models/UrlVideoWithTime.kt new file mode 100644 index 00000000..c41b1f91 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/models/UrlVideoWithTime.kt @@ -0,0 +1,3 @@ +package com.futo.platformplayer.models + +data class UrlVideoWithTime(val url: String, val timeSeconds: Long, val playWhenReady: Boolean); \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/others/Language.kt b/app/src/main/java/com/futo/platformplayer/others/Language.kt new file mode 100644 index 00000000..04a0ba82 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/others/Language.kt @@ -0,0 +1,20 @@ +package com.futo.platformplayer.others + +class Language { + //TODO: Do this differently, somehow map them to ids for multilanguage? + companion object { + val UNKNOWN = "Unknown"; + val ARABIC = "Arabic"; + val SPANISH = "Spanish"; + val FRENCH = "French"; + val HINDI = "Hindi"; + val INDONESIAN = "Indonesian"; + val KOREAN = "Korean"; + val PORTBRAZIL = "Portuguese Brazilian"; + val RUSSIAN = "Russian"; + val THAI = "Thai"; + val TURKISH = "Turkish"; + val VIETNAMESE = "Vietnamese"; + val ENGLISH = "English"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/others/LoginWebViewClient.kt b/app/src/main/java/com/futo/platformplayer/others/LoginWebViewClient.kt new file mode 100644 index 00000000..eed9b04a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/others/LoginWebViewClient.kt @@ -0,0 +1,183 @@ +package com.futo.platformplayer.others + +import android.net.Uri +import android.webkit.* +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.api.media.Serializer +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.api.media.platforms.js.SourceAuth +import com.futo.platformplayer.api.media.platforms.js.SourcePluginAuthConfig +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.matchesDomain +import kotlinx.serialization.encodeToString + +class LoginWebViewClient : WebViewClient { + private val LOG_VERBOSE = false; + + private val _pluginConfig: SourcePluginConfig?; + private val _authConfig: SourcePluginAuthConfig; + + private val _client = ManagedHttpClient(); + + val onLogin = Event1(); + val onPageLoaded = Event2() + + constructor(config: SourcePluginConfig) : super() { + _pluginConfig = config; + _authConfig = config.authentication!!; + Logger.i(TAG, "Login [${config.name}]" + + "\nRequired Headers: ${config.authentication?.headersToFind?.joinToString(", ")}" + + "\nRequired Domain Headers: ${Serializer.json.encodeToString(config.authentication?.domainHeadersToFind)}" + + "\nRequired Cookies: ${Serializer.json.encodeToString(config.authentication?.cookiesToFind)}",); + } + constructor(auth: SourcePluginAuthConfig) : super() { + _pluginConfig = null; + _authConfig = auth; + } + + private val headersFoundMap: HashMap> = hashMapOf(); + private val cookiesFoundMap = hashMapOf>(); + private var urlFound = false; + + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url); + onPageLoaded.emit(view, url); + } + + override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? { + if(request == null) + return super.shouldInterceptRequest(view, request as WebResourceRequest?); + + if (_authConfig.allowedDomains != null && !_authConfig.allowedDomains.contains(request.url.host)) { + return null; + } + + val domain = request.url.host; + val domainLower = request.url.host?.lowercase(); + if(_authConfig.completionUrl == null) + urlFound = true; + else urlFound = urlFound || request.url == Uri.parse(_authConfig.completionUrl); + + //HEADERS + if(domainLower != null) { + val headersToFind = ((_authConfig.headersToFind?.map { Pair(it.lowercase(), domainLower) } ?: listOf()) + + (_authConfig.domainHeadersToFind?.filter { domainLower.matchesDomain(it.key.lowercase())} + ?.flatMap { it.value.map { header -> Pair(header.lowercase(), it.key.lowercase()) } } ?: listOf())); + + val foundHeaders = request.requestHeaders.filter { requestHeader -> headersToFind.any { it.first.equals(requestHeader.key, true)} && + (!requestHeader.key.equals("Authorization", ignoreCase = true) || requestHeader.value != "undefined") } //TODO: More universal fix (optional regex?) + for(header in foundHeaders) { + for(headerDomain in headersToFind.filter { it.first.equals(header.key, true) }) { + if (!headersFoundMap.containsKey(headerDomain.second)) + headersFoundMap[headerDomain.second] = hashMapOf(); + headersFoundMap[headerDomain.second]!![header.key.lowercase()] = header.value; + } + } + } + + + //COOKIES + //TODO: This is not an ideal solution, we want to intercept the response, but interception need to be rewritten to support that. Correct implementation commented underneath + //TODO: For now we assume cookies are legit for all subdomains of a top-level domain, this is the most common scenario anyway + val cookieString = CookieManager.getInstance().getCookie(request.url.toString()); + if(cookieString != null) { + val domainParts = domain!!.split("."); + val cookieDomain = "." + domainParts.drop(domainParts.size - 2).joinToString("."); + if(_pluginConfig == null || _pluginConfig.allowUrls.any { it == "everywhere" || it.lowercase().matchesDomain(cookieDomain) }) + _authConfig.cookiesToFind?.let { cookiesToFind -> + val cookies = cookieString.split(";"); + for(cookieStr in cookies) { + val cookieSplitIndex = cookieStr.indexOf("="); + if(cookieSplitIndex <= 0) continue; + val cookieKey = cookieStr.substring(0, cookieSplitIndex).trim(); + val cookieVal = cookieStr.substring(cookieSplitIndex + 1).trim(); + + if (_authConfig.cookiesExclOthers && !cookiesToFind.contains(cookieKey)) + continue; + + if (cookiesFoundMap.containsKey(cookieDomain)) + cookiesFoundMap[cookieDomain]!![cookieKey] = cookieVal; + else + cookiesFoundMap[cookieDomain] = hashMapOf(Pair(cookieKey, cookieVal)); + } + }; + } + //Correct implementation if we could get the response here, but we cant it seems at least for any request with a body. + //This checks for the true domain for a cookie + /* + var cookiesFound = _authConfig.cookiesToFind?.let { cookiesToFind -> + for(setCookie in response.responseHeaders.filter { it.key.equals("Set-Cookie", true) }) { + val cookieParts = setCookie.value.split(';'); + if (cookieParts.size == 0) + continue; + val cookieSplitIndex = cookieParts[0].indexOf("="); + val cookieKey = cookieParts[0].substring(0, cookieSplitIndex); + val cookieValue = cookieParts[0].substring(cookieSplitIndex + 1); + + if (_authConfig.cookiesExclOthers && !cookiesToFind.contains(cookieKey)) + continue; + + val cookieVariables = cookieParts.drop(1).map { + val splitIndex = it.indexOf("="); + return@map Pair( + it.substring(0, splitIndex), + it.substring(splitIndex + 1).trim() + ); + }.toMap(); + val domainToUse = if (cookieVariables.containsKey("domain")) + cookieVariables["domain"]!! + else domain!!; + + if (cookiesFoundMap.containsKey(domainToUse)) + cookiesFoundMap[domainToUse]!![cookieKey] = cookieValue; + else + cookiesFoundMap[domainToUse] = hashMapOf(Pair(cookieKey, cookieValue)); + } + return@let cookiesToFind.all { toFind -> cookiesFoundMap.any { it.value.containsKey(toFind) } }; + } ?: true; + */ + + val headersFound = _authConfig.headersToFind?.map { it.lowercase() }?.all { reqHeader -> headersFoundMap.any { it.value.containsKey(reqHeader) } } ?: true + val domainHeadersFound = _authConfig.domainHeadersToFind?.all { + if(it.value.isEmpty()) + return@all true; + if(!headersFoundMap.containsKey(it.key.lowercase())) + return@all false; + val foundDomainHeaders = headersFoundMap[it.key.lowercase()] ?: mapOf(); + return@all it.value.all { reqHeader -> foundDomainHeaders.containsKey(reqHeader.lowercase()) }; + } ?: true; + val cookiesFound = _authConfig.cookiesToFind?.all { toFind -> cookiesFoundMap.any { it.value.containsKey(toFind) } } ?: true; + + if(LOG_VERBOSE) { + val builder = StringBuilder(); + builder.appendLine("Request (method: ${request.method}, host: ${request.url.host}, url: ${request.url}, path: ${request.url.path}):"); + for (pair in request.requestHeaders) { + builder.appendLine(" ${pair.key}: ${pair.value}"); + } + builder.appendLine(" Cookies: ${cookiesFoundMap.values.sumOf { it.values.size }}"); + Logger.i(TAG, builder.toString()); + Logger.i(TAG, "Result (urlFound: $urlFound, headersFound: $headersFound, cookiesFound: $cookiesFound)"); + } + + if (urlFound && headersFound && domainHeadersFound && cookiesFound) { + onLogin.emit(SourceAuth( + cookieMap = cookiesFoundMap, + headers = headersFoundMap /*.associate { headerToFind -> + headerToFind to headersFoundMap.firstNotNullOf { requestHeader -> + if (requestHeader.key.equals(headerToFind, ignoreCase = true)) + requestHeader.value + else null; + } + } ?: mapOf()*/ + )); + } + + return super.shouldInterceptRequest(view, request); + } + + companion object { + private val TAG = "LoginWebViewClient"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt b/app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt new file mode 100644 index 00000000..dc414440 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt @@ -0,0 +1,74 @@ +package com.futo.platformplayer.others + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.text.Spannable +import android.text.method.LinkMovementMethod +import android.text.style.URLSpan +import android.view.MotionEvent +import android.widget.TextView +import com.futo.platformplayer.activities.MainActivity +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.receivers.MediaControlReceiver +import com.futo.platformplayer.timestampRegex + +class PlatformLinkMovementMethod : LinkMovementMethod { + private val _context: Context; + + constructor(context: Context) : super() { + _context = context; + } + + override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean { + val action = event.action; + if (action == MotionEvent.ACTION_UP) { + val x = event.x.toInt() - widget.totalPaddingLeft + widget.scrollX; + val y = event.y.toInt() - widget.totalPaddingTop + widget.scrollY; + + val layout = widget.layout; + val line = layout.getLineForVertical(y); + val off = layout.getOffsetForHorizontal(line, x.toFloat()); + val links = buffer.getSpans(off, off, URLSpan::class.java); + + if (links.isNotEmpty()) { + for (link in links) { + Logger.i(TAG) { "Link clicked '${link.url}'." }; + + if (_context is MainActivity) { + if (_context.handleUrl(link.url)) { + continue; + } + + if (timestampRegex.matches(link.url)) { + val tokens = link.url.split(':'); + + var time_s = -1L; + if (tokens.size == 2) { + time_s = tokens[0].toLong() * 60 + tokens[1].toLong(); + } else if (tokens.size == 3) { + time_s = tokens[0].toLong() * 60 * 60 + tokens[1].toLong() * 60 + tokens[2].toLong(); + } + + if (time_s != -1L) { + MediaControlReceiver.onSeekToReceived.emit(time_s * 1000); + continue; + } + } + } + + + _context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))); + } + + return true; + } + } + + return super.onTouchEvent(widget, buffer, event); + } + + companion object { + val TAG = "PlatformLinkMovementMethod"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricCache.kt b/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricCache.kt new file mode 100644 index 00000000..5854249e --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricCache.kt @@ -0,0 +1,292 @@ +package com.futo.platformplayer.polycentric + +import com.futo.polycentric.core.* +import userpackage.Protocol +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.constructs.BatchedTaskHandler +import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile +import com.futo.platformplayer.getNowDiffSeconds +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.resolveChannelUrl +import com.futo.platformplayer.serializers.OffsetDateTimeSerializer +import com.futo.platformplayer.stores.CachedPolycentricProfileStorage +import com.futo.platformplayer.stores.FragmentedStorage +import com.google.protobuf.ByteString +import kotlinx.coroutines.* +import kotlinx.serialization.Serializable +import java.nio.ByteBuffer +import java.time.OffsetDateTime + +class PolycentricCache { + data class CachedOwnedClaims(val ownedClaims: List?, val creationTime: OffsetDateTime = OffsetDateTime.now()); + @Serializable + data class CachedPolycentricProfile(val profile: PolycentricProfile?, @Serializable(with = OffsetDateTimeSerializer::class) val creationTime: OffsetDateTime = OffsetDateTime.now()); + + private val _cacheExpirationSeconds = 60 * 60 * 3; + private val _cache = hashMapOf() + private val _profileCache = hashMapOf() + private val _profileUrlCache = FragmentedStorage.get("profileUrlCache") + private val _scope = CoroutineScope(Dispatchers.IO); + + private val _taskGetProfile = BatchedTaskHandler(_scope, { system -> + val signedProfileEvents = ApiMethods.getQueryLatest( + SERVER, + system.toProto(), + listOf( + ContentType.BANNER.value, + ContentType.AVATAR.value, + ContentType.USERNAME.value, + ContentType.DESCRIPTION.value, + ContentType.STORE.value + ) + ).eventsList.map { e -> SignedEvent.fromProto(e) }; + + val storageSystemState = StorageTypeSystemState.create() + for (signedEvent in signedProfileEvents) { + storageSystemState.update(signedEvent.event) + } + + val signedClaimEvents = ApiMethods.getQueryIndex( + SERVER, + system.toProto(), + ContentType.CLAIM.value, + limit = 200 + ).eventsList.map { e -> SignedEvent.fromProto(e) }; + + val ownedClaims: ArrayList = arrayListOf() + for (signedEvent in signedClaimEvents) { + if (signedEvent.event.contentType != ContentType.CLAIM.value) { + continue; + } + + val response = ApiMethods.getQueryReferences( + SERVER, + Protocol.Reference.newBuilder() + .setReference(signedEvent.toPointer().toProto().toByteString()) + .setReferenceType(2) + .build(), + null, + Protocol.QueryReferencesRequestEvents.newBuilder() + .setFromType(ContentType.VOUCH.value) + .build() + ); + + val ownedClaim = response.itemsList.map { SignedEvent.fromProto(it.event) }.getClaimIfValid(signedEvent); + if (ownedClaim != null) { + ownedClaims.add(ownedClaim); + } + } + + Logger.i(TAG, "Retrieved profile (ownedClaims = $ownedClaims)"); + val systemState = SystemState.fromStorageTypeSystemState(storageSystemState); + return@BatchedTaskHandler CachedPolycentricProfile(PolycentricProfile(system, systemState, ownedClaims)); + }, + { system -> return@BatchedTaskHandler getCachedProfile(system); }, + { system, result -> + synchronized(_cache) { + _profileCache[system] = result; + + if (result.profile != null) { + for (claim in result.profile.ownedClaims) { + val url = claim.claim.resolveChannelUrl() ?: continue; + _profileUrlCache.map[url] = result; + } + } + + _profileUrlCache.save(); + } + }); + + private val _batchTaskGetClaims = BatchedTaskHandler(_scope, + { id -> + val resolved = if (id.claimFieldType == -1) ApiMethods.getResolveClaim(SERVER, system, id.claimType.toLong(), id.value!!) + else ApiMethods.getResolveClaim(SERVER, system, id.claimType.toLong(), id.claimFieldType.toLong(), id.value!!); + Logger.v(TAG, "getResolveClaim(url = $SERVER, system = $system, id = $id, claimType = ${id.claimType}, matchAnyField = ${id.value})"); + val protoEvents = resolved.matchesList.flatMap { arrayListOf(it.claim).apply { addAll(it.proofChainList) } } + val resolvedEvents = protoEvents.map { i -> SignedEvent.fromProto(i) }; + return@BatchedTaskHandler CachedOwnedClaims(resolvedEvents.getValidClaims()); + }, + { id -> return@BatchedTaskHandler getCachedValidClaims(id); }, + { id, result -> + synchronized(_cache) { + _cache[id] = result; + } + }); + + private val _batchTaskGetData = BatchedTaskHandler(_scope, + { + val urlData = if (it.startsWith("polycentric://")) { + it.substring("polycentric://".length) + } else it; + + val urlBytes = urlData.base64UrlToByteArray(); + val urlInfo = Protocol.URLInfo.parseFrom(urlBytes); + if (urlInfo.urlType != 4L) { + throw Exception("Only URLInfoDataLink is supported"); + } + + val dataLink = Protocol.URLInfoDataLink.parseFrom(urlInfo.body); + return@BatchedTaskHandler ApiMethods.getDataFromServerAndReassemble(dataLink); + }, + { return@BatchedTaskHandler null }, + { _, _ -> }); + + fun getCachedValidClaims(id: PlatformID, ignoreExpired: Boolean = false): CachedOwnedClaims? { + if (id.claimType <= 0) { + return CachedOwnedClaims(null); + } + + synchronized(_cache) { + val cached = _cache[id] + if (cached == null) { + return null + } + + if (!ignoreExpired && cached.creationTime.getNowDiffSeconds() > _cacheExpirationSeconds) { + return null; + } + + return cached; + } + } + + //TODO: Review all return null in this file, perhaps it should be CachedX(null) instead + fun getValidClaimsAsync(id: PlatformID): Deferred { + if (id.value == null || id.claimType <= 0) { + return _scope.async { CachedOwnedClaims(null) }; + } + + Logger.v(TAG, "getValidClaims (id: $id)") + val def = _batchTaskGetClaims.execute(id); + def.invokeOnCompletion { + if (it == null) { + return@invokeOnCompletion + } + + handleException(it, handleNetworkException = { /* Do nothing (do not cache) */ }, handleOtherException = { + //Cache failed result + synchronized(_cache) { + _cache[id] = CachedOwnedClaims(null); + } + }) + }; + return def; + } + + fun getDataAsync(url: String): Deferred { + return _batchTaskGetData.execute(url); + } + + fun getCachedProfile(url: String, ignoreExpired: Boolean = false): CachedPolycentricProfile? { + synchronized (_profileCache) { + val cached = _profileUrlCache.get(url) ?: return null; + if (!ignoreExpired && cached.creationTime.getNowDiffSeconds() > _cacheExpirationSeconds) { + return null; + } + + return cached; + } + } + + fun getCachedProfile(system: PublicKey, ignoreExpired: Boolean = false): CachedPolycentricProfile? { + synchronized(_profileCache) { + val cached = _profileCache[system] ?: return null; + if (!ignoreExpired && cached.creationTime.getNowDiffSeconds() > _cacheExpirationSeconds) { + return null; + } + + return cached; + } + } + + suspend fun getProfileAsync(id: PlatformID): CachedPolycentricProfile? { + if (id.claimType <= 0) { + return CachedPolycentricProfile(null); + } + + val cachedClaims = getCachedValidClaims(id); + if (cachedClaims != null) { + if (!cachedClaims.ownedClaims.isNullOrEmpty()) { + Logger.v(TAG, "getProfileAsync (id: $id) != null (with cached valid claims)") + return getProfileAsync(cachedClaims.ownedClaims.first().system).await(); + } else { + return null; + } + } else { + Logger.v(TAG, "getProfileAsync (id: $id) no cached valid claims, will be retrieved") + + val claims = getValidClaimsAsync(id).await() + if (!claims.ownedClaims.isNullOrEmpty()) { + Logger.v(TAG, "getProfileAsync (id: $id) != null (with retrieved valid claims)") + return getProfileAsync(claims.ownedClaims.first().system).await() + } else { + return null; + } + } + } + + fun getProfileAsync(system: PublicKey): Deferred { + Logger.i(TAG, "getProfileAsync (system: ${system})") + val def = _taskGetProfile.execute(system); + def.invokeOnCompletion { + if (it == null) { + return@invokeOnCompletion + } + + handleException(it, handleNetworkException = { /* Do nothing (do not cache) */ }, handleOtherException = { + //Cache failed result + synchronized(_cache) { + val cachedProfile = CachedPolycentricProfile(null); + _profileCache[system] = cachedProfile; + } + }) + }; + return def; + } + + private fun handleException(e: Throwable, handleNetworkException: () -> Unit, handleOtherException: () -> Unit) { + val isNetworkException = when(e) { + is java.net.UnknownHostException, + is java.net.SocketTimeoutException, + is java.net.ConnectException -> true + else -> when(e.cause) { + is java.net.UnknownHostException, + is java.net.SocketTimeoutException, + is java.net.ConnectException -> true + else -> false + } + } + if (isNetworkException) { + handleNetworkException() + } else { + handleOtherException() + } + } + + companion object { + private val system = Protocol.PublicKey.newBuilder() + .setKeyType(1) + .setKey(ByteString.copyFrom("gX0eCWctTm6WHVGot4sMAh7NDAIwWsIM5tRsOz9dX04=".base64ToByteArray())) //Production key + //.setKey(ByteString.copyFrom("LeQkzn1j625YZcZHayfCmTX+6ptrzsA+CdAyq+BcEdQ".base64ToByteArray())) //Test key koen-futo + .build(); + + private const val TAG = "PolycentricCache" + const val SERVER = "https://srv1-stg.polycentric.io" + private var _instance: PolycentricCache? = null; + + @JvmStatic + val instance: PolycentricCache + get(){ + if(_instance == null) + _instance = PolycentricCache(); + return _instance!!; + }; + + fun finish() { + _instance?.let { + _instance = null; + it._scope.cancel("PolycentricCache finished"); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/receivers/AudioNoisyReceiver.kt b/app/src/main/java/com/futo/platformplayer/receivers/AudioNoisyReceiver.kt new file mode 100644 index 00000000..a8c3894d --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/receivers/AudioNoisyReceiver.kt @@ -0,0 +1,19 @@ +package com.futo.platformplayer.receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.futo.platformplayer.logging.Logger + + +class AudioNoisyReceiver : BroadcastReceiver() { + + override fun onReceive(context: Context?, intent: Intent?) { + Logger.i(TAG, "Audio Noisy received"); + MediaControlReceiver.onPauseReceived.emit(); + } + + companion object { + private val TAG = "AudioNoisyReceiver" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/receivers/InstallReceiver.kt b/app/src/main/java/com/futo/platformplayer/receivers/InstallReceiver.kt new file mode 100644 index 00000000..abac844a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/receivers/InstallReceiver.kt @@ -0,0 +1,46 @@ +package com.futo.platformplayer.receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.pm.PackageInstaller +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event1 + + +class InstallReceiver : BroadcastReceiver() { + private val TAG = "InstallReceiver" + + companion object { + val onReceiveResult = Event1(); + } + + override fun onReceive(context: Context, intent: Intent) { + val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1); + Logger.i(TAG, "Received status $status."); + + when (status) { + PackageInstaller.STATUS_PENDING_USER_ACTION -> { + val activityIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT) + if (activityIntent == null) { + Logger.w(TAG, "Received STATUS_PENDING_USER_ACTION and activity intent is null.") + return; + } + context.startActivity(activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) + } + PackageInstaller.STATUS_SUCCESS -> onReceiveResult.emit(null); + PackageInstaller.STATUS_FAILURE -> onReceiveResult.emit(context.getString(R.string.general_failure)); + PackageInstaller.STATUS_FAILURE_ABORTED -> onReceiveResult.emit(context.getString(R.string.aborted)); + PackageInstaller.STATUS_FAILURE_BLOCKED -> onReceiveResult.emit(context.getString(R.string.blocked)); + PackageInstaller.STATUS_FAILURE_CONFLICT -> onReceiveResult.emit(context.getString(R.string.conflict)); + PackageInstaller.STATUS_FAILURE_INCOMPATIBLE -> onReceiveResult.emit(context.getString(R.string.incompatible)); + PackageInstaller.STATUS_FAILURE_INVALID -> onReceiveResult.emit(context.getString(R.string.invalid)); + PackageInstaller.STATUS_FAILURE_STORAGE -> onReceiveResult.emit(context.getString(R.string.not_enough_storage)); + else -> { + val msg = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE); + onReceiveResult.emit(msg) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/receivers/MediaControlReceiver.kt b/app/src/main/java/com/futo/platformplayer/receivers/MediaControlReceiver.kt new file mode 100644 index 00000000..61bec334 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/receivers/MediaControlReceiver.kt @@ -0,0 +1,69 @@ +package com.futo.platformplayer.receivers + +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event1 + + +class MediaControlReceiver : BroadcastReceiver() { + + override fun onReceive(context: Context?, intent: Intent?) { + val act = intent?.getStringExtra(EXTRA_MEDIA_ACTION); + Logger.i(TAG, "Received MediaControl Event $act"); + + try { + when (act) { + EVENT_PLAY -> onPlayReceived.emit(); + EVENT_PAUSE -> onPauseReceived.emit(); + EVENT_NEXT -> onNextReceived.emit(); + EVENT_PREV -> onPreviousReceived.emit(); + EVENT_CLOSE -> onCloseReceived.emit(); + } + } + catch(ex: Throwable) { + Logger.w(TAG, "Failed to handle intent: ${act}"); + } + } + + companion object { + private val TAG = "MediaControlReceiver" + + const val EXTRA_MEDIA_ACTION = "MediaAction"; + + const val EVENT_PLAY = "Play"; + const val EVENT_PAUSE = "Pause"; + const val EVENT_NEXT = "Next"; + const val EVENT_PREV = "Prev"; + const val EVENT_CLOSE = "Close"; + + val onPlayReceived = Event0(); + val onPauseReceived = Event0(); + val onNextReceived = Event0(); + val onPreviousReceived = Event0(); + val onSeekToReceived = Event1(); + + val onLowerVolumeReceived = Event0(); + + val onCloseReceived = Event0() + + fun getPlayIntent(context: Context, code: Int = 0) : PendingIntent = PendingIntent.getBroadcast(context, code, Intent(context, MediaControlReceiver::class.java).apply { + this.putExtra(EXTRA_MEDIA_ACTION, EVENT_PLAY); + },PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT); + fun getPauseIntent(context: Context, code: Int = 0) : PendingIntent = PendingIntent.getBroadcast(context, code, Intent(context, MediaControlReceiver::class.java).apply { + this.putExtra(EXTRA_MEDIA_ACTION, EVENT_PAUSE); + },PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT); + fun getNextIntent(context: Context, code: Int = 0) : PendingIntent = PendingIntent.getBroadcast(context, code, Intent(context, MediaControlReceiver::class.java).apply { + this.putExtra(EXTRA_MEDIA_ACTION, EVENT_NEXT); + },PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT); + fun getPrevIntent(context: Context, code: Int = 0) : PendingIntent = PendingIntent.getBroadcast(context, code, Intent(context, MediaControlReceiver::class.java).apply { + this.putExtra(EXTRA_MEDIA_ACTION, EVENT_PREV); + },PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT); + fun getCloseIntent(context: Context, code: Int = 0) : PendingIntent = PendingIntent.getBroadcast(context, code, Intent(context, MediaControlReceiver::class.java).apply { + this.putExtra(EXTRA_MEDIA_ACTION, EVENT_CLOSE); + },PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/serializers/FlexibleBooleanSerializer.kt b/app/src/main/java/com/futo/platformplayer/serializers/FlexibleBooleanSerializer.kt new file mode 100644 index 00000000..b73f1492 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/serializers/FlexibleBooleanSerializer.kt @@ -0,0 +1,49 @@ +package com.futo.platformplayer.serializers + +import com.futo.platformplayer.Settings +import com.futo.platformplayer.logging.Logger +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.booleanOrNull +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.int +import kotlinx.serialization.json.intOrNull +import kotlinx.serialization.json.jsonPrimitive +import java.time.LocalDateTime +import java.time.OffsetDateTime +import java.time.ZoneOffset + +class FlexibleBooleanSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("FlexibleBoolean", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: Boolean) { + encoder.encodeBoolean(value); + } + override fun deserialize(decoder: Decoder): Boolean { + Logger.i("Settings", "Deserializing Flexible Boolean"); + + val element = (decoder as JsonDecoder).decodeJsonElement(); + + if(element.jsonPrimitive.booleanOrNull != null) + return element.jsonPrimitive.boolean; + else if(element.jsonPrimitive.intOrNull != null) + return element.jsonPrimitive.int == 1; + else if(element.jsonPrimitive.isString) { + val strValue = element.jsonPrimitive.content; + val intValue = strValue.toIntOrNull(); + val value = if(intValue != null) + intValue == 1; + else + strValue.toBooleanStrictOrNull() ?: throw SerializationException("Non-Boolean type found in flexible boolean for value [${strValue}]"); + return value; + } + else throw SerializationException("Failed to deserialize flexible boolean with value: ${element.jsonPrimitive.contentOrNull}"); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/serializers/IRatingSerializer.kt b/app/src/main/java/com/futo/platformplayer/serializers/IRatingSerializer.kt new file mode 100644 index 00000000..66b607cb --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/serializers/IRatingSerializer.kt @@ -0,0 +1,39 @@ +package com.futo.platformplayer.serializers + +import com.futo.platformplayer.api.media.models.ratings.* +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.KSerializer +import kotlinx.serialization.PolymorphicSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.decodeStructure +import kotlinx.serialization.json.* +import java.time.LocalDateTime +import java.time.OffsetDateTime +import java.time.ZoneOffset +import kotlin.reflect.KClass + + +class IRatingSerializer() : JsonContentPolymorphicSerializer(IRating::class) { + + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { + val obj = element.jsonObject["type"]; + if(obj?.jsonPrimitive?.isString ?: true) + return when(obj?.jsonPrimitive?.contentOrNull) { + "LIKES" -> RatingLikes.serializer(); + "LIKEDISLIKES" -> RatingLikeDislikes.serializer(); + "SCALE" -> RatingScaler.serializer(); + else -> throw NotImplementedError("Rating Value: ${obj?.jsonPrimitive?.contentOrNull}") + }; + else + return when(element.jsonObject["type"]?.jsonPrimitive?.int) { + RatingType.LIKES.value -> RatingLikes.serializer(); + RatingType.LIKEDISLIKES.value -> RatingLikeDislikes.serializer(); + RatingType.SCALE.value -> RatingScaler.serializer(); + else -> throw NotImplementedError("Rating Value: ${obj?.jsonPrimitive?.int}") + }; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/serializers/OffsetDateTimeSerializer.kt b/app/src/main/java/com/futo/platformplayer/serializers/OffsetDateTimeSerializer.kt new file mode 100644 index 00000000..31fbaadd --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/serializers/OffsetDateTimeSerializer.kt @@ -0,0 +1,42 @@ +package com.futo.platformplayer.serializers + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import java.time.LocalDateTime +import java.time.OffsetDateTime +import java.time.ZoneOffset + +class OffsetDateTimeNullableSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("OffsetDateTime", PrimitiveKind.LONG) + + override fun serialize(encoder: Encoder, value: OffsetDateTime?) { + encoder.encodeLong(value?.toEpochSecond() ?: -1); + } + override fun deserialize(decoder: Decoder): OffsetDateTime? { + val epochSecond = decoder.decodeLong(); + if(epochSecond > 9999999999) + return OffsetDateTime.MAX; + else if(epochSecond < -9999999999) + return OffsetDateTime.MIN; + return OffsetDateTime.of(LocalDateTime.ofEpochSecond(epochSecond, 0, ZoneOffset.UTC), ZoneOffset.UTC); + } +} +class OffsetDateTimeSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("OffsetDateTime", PrimitiveKind.LONG) + + override fun serialize(encoder: Encoder, value: OffsetDateTime) { + encoder.encodeLong(value.toEpochSecond()); + } + override fun deserialize(decoder: Decoder): OffsetDateTime { + val epochSecond = Math.max(decoder.decodeLong(), 0); + if(epochSecond > 9999999999) + return OffsetDateTime.MAX; + else if(epochSecond < -9999999999) + return OffsetDateTime.MIN; + return OffsetDateTime.of(LocalDateTime.ofEpochSecond(epochSecond, 0, ZoneOffset.UTC), ZoneOffset.UTC); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/serializers/PlatformContentSerializer.kt b/app/src/main/java/com/futo/platformplayer/serializers/PlatformContentSerializer.kt new file mode 100644 index 00000000..6214d62e --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/serializers/PlatformContentSerializer.kt @@ -0,0 +1,37 @@ +package com.futo.platformplayer.serializers + +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.video.SerializedPlatformContent +import com.futo.platformplayer.api.media.models.video.SerializedPlatformNestedContent +import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.json.* + + +class PlatformContentSerializer() : JsonContentPolymorphicSerializer(SerializedPlatformContent::class) { + + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { + val obj = element.jsonObject["contentType"]; + + //TODO: Remove this temporary fallback..at some point + if(obj == null && element.jsonObject["isLive"]?.jsonPrimitive?.booleanOrNull != null) + return SerializedPlatformVideo.serializer(); + + if(obj?.jsonPrimitive?.isString ?: true) + return when(obj?.jsonPrimitive?.contentOrNull) { + "MEDIA" -> SerializedPlatformVideo.serializer(); + "NESTED" -> SerializedPlatformNestedContent.serializer(); + "ARTICLE" -> throw NotImplementedError("Articles not yet implemented"); + "POST" -> throw NotImplementedError("Post not yet implemented"); + else -> throw NotImplementedError("Unknown Content Type Value: ${obj?.jsonPrimitive?.int}") + }; + else + return when(obj?.jsonPrimitive?.int) { + ContentType.MEDIA.value -> SerializedPlatformVideo.serializer(); + ContentType.NESTED_VIDEO.value -> SerializedPlatformNestedContent.serializer(); + ContentType.ARTICLE.value -> throw NotImplementedError("Articles not yet implemented"); + ContentType.POST.value -> throw NotImplementedError("Post not yet implemented"); + else -> throw NotImplementedError("Unknown Content Type Value: ${obj?.jsonPrimitive?.int}") + }; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/serializers/VideoDescriptorSerializer.kt b/app/src/main/java/com/futo/platformplayer/serializers/VideoDescriptorSerializer.kt new file mode 100644 index 00000000..b9ffee13 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/serializers/VideoDescriptorSerializer.kt @@ -0,0 +1,19 @@ +package com.futo.platformplayer.serializers + +import com.futo.platformplayer.api.media.models.video.ISerializedVideoSourceDescriptor +import com.futo.platformplayer.api.media.models.video.SerializedVideoMuxedSourceDescriptor +import com.futo.platformplayer.api.media.models.video.SerializedVideoNonMuxedSourceDescriptor +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.json.* + + +class VideoDescriptorSerializer() : JsonContentPolymorphicSerializer(ISerializedVideoSourceDescriptor::class) { + + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { + return when(element.jsonObject["isUnMuxed"]?.jsonPrimitive?.boolean) { + false -> SerializedVideoMuxedSourceDescriptor.serializer(); + true -> SerializedVideoNonMuxedSourceDescriptor.serializer(); + else -> throw NotImplementedError("Unknown mux") + }; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/services/DownloadService.kt b/app/src/main/java/com/futo/platformplayer/services/DownloadService.kt new file mode 100644 index 00000000..dc094b21 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/services/DownloadService.kt @@ -0,0 +1,291 @@ +package com.futo.platformplayer.services + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.Service +import android.content.Context +import android.content.Intent +import android.os.IBinder +import androidx.core.app.NotificationCompat +import com.futo.platformplayer.* +import com.futo.platformplayer.activities.MainActivity +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.downloads.VideoDownload +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.Announcement +import com.futo.platformplayer.states.AnnouncementType +import com.futo.platformplayer.states.StateAnnouncement +import com.futo.platformplayer.states.StateDownloads +import com.futo.platformplayer.stores.FragmentedStorage +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import java.net.SocketException +import java.time.OffsetDateTime + +class DownloadService : Service() { + private val TAG = "DownloadService"; + + private val DOWNLOAD_NOTIF_ID = 3; + private val DOWNLOAD_NOTIF_TAG = "download"; + private val DOWNLOAD_NOTIF_CHANNEL_ID = "downloadChannel"; + + //Context + private val _scope: CoroutineScope = CoroutineScope(Dispatchers.Default); + private var _notificationManager: NotificationManager? = null; + private var _notificationChannel: NotificationChannel? = null; + + private val _client = ManagedHttpClient(); + + private var _started = false; + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + Logger.i(TAG, "onStartCommand"); + synchronized(this) { + if(_started) + return START_STICKY; + + if(!FragmentedStorage.isInitialized) { + Logger.i(TAG, "Attempted to start DownloadService without initialized files"); + closeDownloadSession(); + return START_NOT_STICKY; + } + _started = true; + } + setupNotificationRequirements(); + notifyDownload(null); + + _callOnStarted?.invoke(this); + _instance = this; + + _scope.launch { + try { + doDownloading(); + } + catch(ex: Throwable) { + try { + StateAnnouncement.instance.registerAnnouncementSession( + Announcement( + "rootDownloadException", + "An root download service exception happened", + ex.message ?: "", + AnnouncementType.SESSION, + OffsetDateTime.now() + ) + ); + } catch(_: Throwable){} + try { + closeDownloadSession(); + } + catch(ex: Throwable) { + + } + } + }; + + return START_STICKY; + } + fun setupNotificationRequirements() { + _notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager; + _notificationChannel = NotificationChannel(DOWNLOAD_NOTIF_CHANNEL_ID, "Temp", NotificationManager.IMPORTANCE_DEFAULT).apply { + this.enableVibration(false); + this.setSound(null, null); + }; + _notificationManager!!.createNotificationChannel(_notificationChannel!!); + } + + override fun onCreate() { + Logger.i(TAG, "onCreate"); + super.onCreate() + } + + override fun onBind(p0: Intent?): IBinder? { + return null; + } + + private suspend fun doDownloading() { + Logger.i(TAG, "doDownloading - Starting Downloads"); + val ignore = mutableListOf(); + var currentVideo: VideoDownload? = StateDownloads.instance.getDownloading().firstOrNull(); + while (currentVideo != null) + { + try { + doDownload(currentVideo); + } + catch(ex: SocketException) { + var msg = ex.message; + if(ex.message == "Software caused connection abort") + msg = "Downloading disabled on current network"; + + Logger.e(TAG, "Failed download [${currentVideo.name}]: ${msg}", ex); + currentVideo.error = msg; + currentVideo.changeState(VideoDownload.State.ERROR); + ignore.add(currentVideo); + + //Give it a sec + Thread.sleep(500); + } + catch(ex: Throwable) { + Logger.e(TAG, "Download failed", ex); + if(currentVideo.video == null && currentVideo.videoDetails == null) { + //Corrupt? + Logger.w(TAG, "Video had no video or videodetail, removing download"); + StateDownloads.instance.removeDownload(currentVideo); + } + else + Logger.e(TAG, "Failed download [${currentVideo.name}]: ${ex.message}", ex); + currentVideo.error = ex.message; + currentVideo.changeState(VideoDownload.State.ERROR); + ignore.add(currentVideo); + + StateAnnouncement.instance.registerAnnouncement(currentVideo?.id?.value?:"" + currentVideo?.id?.pluginId?:"" + "_FailDownload", + "Download failed", + "Download for [${currentVideo.name}] failed.\nDownloads are automatically retried.\nReason: ${ex.message}", AnnouncementType.SESSION, null, "download"); + + //Give it a sec + Thread.sleep(500); + } + StateDownloads.instance.updateDownloading(currentVideo); + + currentVideo = StateDownloads.instance.getDownloading().filter { !ignore.contains(it) }.firstOrNull(); + } + Logger.i(TAG, "doDownloading - Ending Downloads"); + stopService(this); + } + private suspend fun doDownload(download: VideoDownload) { + if(!Settings.instance.downloads.shouldDownload()) + throw IllegalStateException("Downloading disabled on current network"); + + if((download.prepareTime?.getNowDiffMinutes() ?: 99) > 15) { + Logger.w(TAG, "Video Download [${download.name}] expired, re-preparing"); + download.videoDetails = null; + + if(download.targetPixelCount == null && download.videoSource != null) + download.targetPixelCount = (download.videoSource!!.width * download.videoSource!!.height).toLong(); + download.videoSource = null; + if(download.targetBitrate == null && download.audioSource != null) + download.targetBitrate = download.audioSource!!.bitrate.toLong(); + download.audioSource = null; + } + if(download.videoDetails == null || (download.videoSource == null && download.audioSource == null)) + download.changeState(VideoDownload.State.PREPARING); + notifyDownload(download); + + Logger.i(TAG, "Preparing [${download.name}] started"); + if(download.state == VideoDownload.State.PREPARING) + download.prepare(); + download.changeState(VideoDownload.State.DOWNLOADING); + notifyDownload(download); + + var lastNotifyTime: Long = 0L; + Logger.i(TAG, "Downloading [${download.name}] started"); + //TODO: Use plugin client? + download.download(_client) { progress -> + download.progress = progress; + + val currentTime = System.currentTimeMillis(); + if (currentTime - lastNotifyTime > 500) { + notifyDownload(download); + lastNotifyTime = currentTime; + } + }; + Logger.i(TAG, "Download [${download.name}] finished"); + + download.changeState(VideoDownload.State.VALIDATING); + notifyDownload(download); + + Logger.i(TAG, "Validating [${download.name}]"); + download.validate(); + download.changeState(VideoDownload.State.FINALIZING); + notifyDownload(download); + + Logger.i(TAG, "Completing [${download.name}]"); + download.complete(); + download.changeState(VideoDownload.State.COMPLETED); + + StateDownloads.instance.removeDownload(download); + notifyDownload(download); + } + + private fun notifyDownload(download: VideoDownload?) { + val channel = _notificationChannel ?: return; + + val bringUpIntent = Intent(this, MainActivity::class.java); + bringUpIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + bringUpIntent.action = "TAB"; + bringUpIntent.putExtra("TAB", "Downloads"); + + var builder = if(download != null) + NotificationCompat.Builder(this, DOWNLOAD_NOTIF_TAG) + .setSmallIcon(R.drawable.ic_download) + .setOngoing(true) + .setContentIntent(PendingIntent.getActivity(this, 5, bringUpIntent, PendingIntent.FLAG_IMMUTABLE)) + .setContentTitle("${download.state}: ${download.name}") + .setContentText(download.getDownloadInfo()) + .setProgress(100, (download.progress * 100).toInt(), download.progress == 0.0) + .setChannelId(channel.id) + else + NotificationCompat.Builder(this, DOWNLOAD_NOTIF_TAG) + .setSmallIcon(R.drawable.ic_download) + .setOngoing(true) + .setContentIntent(PendingIntent.getActivity(this, 5, bringUpIntent, PendingIntent.FLAG_IMMUTABLE)) + .setContentTitle("Preparing for download...") + .setContentText("Initializing download process...") + .setChannelId(channel.id) + + val notif = builder.build(); + notif.flags = notif.flags or NotificationCompat.FLAG_ONGOING_EVENT or NotificationCompat.FLAG_NO_CLEAR; + + startForeground(DOWNLOAD_NOTIF_ID, notif); + } + + fun closeDownloadSession() { + Logger.i(TAG, "closeDownloadSession"); + stopForeground(true); + _notificationManager?.cancel(DOWNLOAD_NOTIF_ID); + stopService(); + _started = false; + super.stopSelf(); + } + override fun onDestroy() { + Logger.i(TAG, "onDestroy"); + _instance = null; + _scope.cancel("onDestroy"); + super.onDestroy(); + } + + companion object { + private var _instance: DownloadService? = null; + private var _callOnStarted: ((DownloadService)->Unit)? = null; + + @Synchronized + fun getOrCreateService(context: Context, handle: ((DownloadService)->Unit)? = null) { + if(!FragmentedStorage.isInitialized) + return; + if(_instance == null) { + _callOnStarted = handle; + val intent = Intent(context, DownloadService::class.java); + context.startForegroundService(intent); + } + else _instance?.let { + if(handle != null) + handle(it); + } + } + @Synchronized + fun getService() : DownloadService? { + return _instance; + } + + @Synchronized + fun stopService(service: DownloadService? = null) { + (service ?: _instance)?.let { + if(_instance == it) + _instance = null; + it.closeDownloadSession(); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/services/ExportingService.kt b/app/src/main/java/com/futo/platformplayer/services/ExportingService.kt new file mode 100644 index 00000000..c76991f4 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/services/ExportingService.kt @@ -0,0 +1,225 @@ +package com.futo.platformplayer.services + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.Service +import android.content.Context +import android.content.Intent +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK +import android.os.IBinder +import androidx.core.app.NotificationCompat +import androidx.core.content.FileProvider +import com.futo.platformplayer.* +import com.futo.platformplayer.activities.MainActivity +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.downloads.VideoExport +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.Announcement +import com.futo.platformplayer.states.AnnouncementType +import com.futo.platformplayer.states.StateAnnouncement +import com.futo.platformplayer.states.StateDownloads +import com.futo.platformplayer.stores.FragmentedStorage +import kotlinx.coroutines.* +import java.time.OffsetDateTime +import java.util.UUID + + +class ExportingService : Service() { + private val TAG = "ExportingService"; + + private val EXPORT_NOTIF_ID = 4; + private val EXPORT_NOTIF_TAG = "export"; + private val EXPORT_NOTIF_CHANNEL_ID = "exportChannel"; + + //Context + private val _scope: CoroutineScope = CoroutineScope(Dispatchers.Default); + private var _notificationManager: NotificationManager? = null; + private var _notificationChannel: NotificationChannel? = null; + + private val _client = ManagedHttpClient(); + + private var _started = false; + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + Logger.i(TAG, "onStartCommand"); + + synchronized(this) { + if(_started) + return START_STICKY; + + if(!FragmentedStorage.isInitialized) { + closeExportSession(); + return START_NOT_STICKY; + } + + _started = true; + } + setupNotificationRequirements(); + + _callOnStarted?.invoke(this); + _instance = this; + + _scope.launch { + try { + doExporting(); + } + catch(ex: Throwable) { + try { + StateAnnouncement.instance.registerAnnouncementSession( + Announcement( + "rootExportException", + "An root export service exception happened", + ex.message ?: "", + AnnouncementType.SESSION, + OffsetDateTime.now() + ) + ); + } catch(_: Throwable){} + } + }; + + return START_STICKY; + } + fun setupNotificationRequirements() { + _notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager; + _notificationChannel = NotificationChannel(EXPORT_NOTIF_CHANNEL_ID, "Temp", NotificationManager.IMPORTANCE_DEFAULT).apply { + this.enableVibration(false); + this.setSound(null, null); + }; + _notificationManager!!.createNotificationChannel(_notificationChannel!!); + } + + override fun onCreate() { + Logger.i(TAG, "onCreate"); + super.onCreate() + } + + override fun onBind(p0: Intent?): IBinder? { + return null; + } + + private suspend fun doExporting() { + Logger.i(TAG, "doExporting - Starting Exports"); + val ignore = mutableListOf(); + var currentExport: VideoExport? = StateDownloads.instance.getExporting().firstOrNull(); + while (currentExport != null) + { + try{ + notifyExport(currentExport); + doExport(currentExport); + } + catch(ex: Throwable) { + Logger.e(TAG, "Failed export [${currentExport.videoLocal.name}]: ${ex.message}", ex); + currentExport.error = ex.message; + currentExport.changeState(VideoExport.State.ERROR); + ignore.add(currentExport); + + //Give it a sec + Thread.sleep(500); + } + + currentExport = StateDownloads.instance.getExporting().filter { !ignore.contains(it) }.firstOrNull(); + } + Logger.i(TAG, "doExporting - Ending Exports"); + stopService(this); + } + + private suspend fun doExport(export: VideoExport) { + Logger.i(TAG, "Exporting [${export.videoLocal.name}] started"); + + export.changeState(VideoExport.State.EXPORTING); + + var lastNotifyTime: Long = 0L; + val file = export.export { progress -> + export.progress = progress; + + val currentTime = System.currentTimeMillis(); + if (currentTime - lastNotifyTime > 500) { + notifyExport(export); + lastNotifyTime = currentTime; + } + } + export.changeState(VideoExport.State.COMPLETED); + Logger.i(TAG, "Export [${export.videoLocal.name}] finished"); + StateDownloads.instance.removeExport(export); + notifyExport(export); + + withContext(Dispatchers.Main) { + StateAnnouncement.instance.registerAnnouncement(UUID.randomUUID().toString(), "File exported", "Exported [${file.path}]", AnnouncementType.SESSION, time = null, category = "download", actionButton = "Open") { + file.share(this@ExportingService); + }; + } + } + + private fun notifyExport(export: VideoExport) { + val channel = _notificationChannel ?: return; + + val bringUpIntent = Intent(this, MainActivity::class.java); + bringUpIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + bringUpIntent.action = "TAB"; + bringUpIntent.putExtra("TAB", "Exports"); + + var builder = NotificationCompat.Builder(this, EXPORT_NOTIF_TAG) + .setSmallIcon(R.drawable.ic_export) + .setOngoing(true) + .setContentIntent(PendingIntent.getActivity(this, 5, bringUpIntent, PendingIntent.FLAG_IMMUTABLE)) + .setContentTitle("${export.state}: ${export.videoLocal.name}") + .setContentText(export.getExportInfo()) + .setProgress(100, (export.progress * 100).toInt(), export.progress == 0.0) + .setChannelId(channel.id) + + val notif = builder.build(); + notif.flags = notif.flags or NotificationCompat.FLAG_ONGOING_EVENT or NotificationCompat.FLAG_NO_CLEAR; + + startForeground(EXPORT_NOTIF_ID, notif); + } + + fun closeExportSession() { + Logger.i(TAG, "closeExportSession"); + stopForeground(true); + _notificationManager?.cancel(EXPORT_NOTIF_ID); + stopService(); + _started = false; + super.stopSelf(); + } + override fun onDestroy() { + Logger.i(TAG, "onDestroy"); + _instance = null; + _scope.cancel("onDestroy"); + super.onDestroy(); + } + + companion object { + private var _instance: ExportingService? = null; + private var _callOnStarted: ((ExportingService)->Unit)? = null; + + @Synchronized + fun getOrCreateService(context: Context, handle: ((ExportingService)->Unit)? = null) { + if(!FragmentedStorage.isInitialized) + return; + if(_instance == null) { + _callOnStarted = handle; + val intent = Intent(context, ExportingService::class.java); + context.startForegroundService(intent); + } + else _instance?.let { + if(handle != null) + handle(it); + } + } + @Synchronized + fun getService() : ExportingService? { + return _instance; + } + + @Synchronized + fun stopService(service: ExportingService? = null) { + (service ?: _instance)?.let { + if(_instance == it) + _instance = null; + it.closeExportSession(); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/services/MediaPlaybackService.kt b/app/src/main/java/com/futo/platformplayer/services/MediaPlaybackService.kt new file mode 100644 index 00000000..c4852057 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/services/MediaPlaybackService.kt @@ -0,0 +1,385 @@ +package com.futo.platformplayer.services + +import android.app.* +import android.content.Context +import android.content.Intent +import android.content.pm.ServiceInfo +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.media.AudioFocusRequest +import android.media.AudioManager +import android.media.AudioManager.OnAudioFocusChangeListener +import android.media.MediaMetadata +import android.os.IBinder +import android.os.SystemClock +import android.support.v4.media.MediaMetadataCompat +import android.support.v4.media.session.MediaSessionCompat +import android.support.v4.media.session.PlaybackStateCompat +import android.util.Log +import androidx.core.app.NotificationCompat +import com.bumptech.glide.Glide +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.R +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StatePlayer +import com.futo.platformplayer.activities.MainActivity +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.receivers.MediaControlReceiver +import com.futo.platformplayer.stores.FragmentedStorage + +class MediaPlaybackService : Service() { + private val TAG = "MediaPlaybackService"; + + private val MEDIA_NOTIF_ID = 2; + private val MEDIA_NOTIF_TAG = "media"; + private val MEDIA_NOTIF_CHANNEL_ID = "mediaChannel"; + private val MEDIA_NOTIF_CHANNEL_NAME = "Player"; + + //Notifs + private var _notif_last_video: IPlatformVideo? = null; + private var _notif_last_bitmap: Bitmap? = null; + + //Context + private var _audioManager: AudioManager? = null; + private var _notificationManager: NotificationManager? = null; + private var _notificationChannel: NotificationChannel? = null; + private var _mediaSession: MediaSessionCompat? = null; + private var _hasFocus: Boolean = false; + private var _focusRequest: AudioFocusRequest? = null; + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + Logger.i(TAG, "onStartCommand"); + + + if(!FragmentedStorage.isInitialized) { + Logger.i(TAG, "Attempted to start MediaPlaybackService without initialized files"); + closeMediaSession(); + return START_NOT_STICKY; + } + + try { + + setupNotificationRequirements(); + + notifyMediaSession(null, null); + + _callOnStarted?.invoke(this); + _instance = this; + } + catch(ex: Throwable) { + Logger.e(TAG, "Failed to start MediaPlaybackService due to: " + ex.message, ex); + closeMediaSession(); + return START_NOT_STICKY; + } + + return START_STICKY; + } + fun setupNotificationRequirements() { + _audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager; + _notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager; + _notificationChannel = NotificationChannel(MEDIA_NOTIF_CHANNEL_ID, MEDIA_NOTIF_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH).apply { + this.enableVibration(false); + this.setSound(null, null); + }; + _notificationManager!!.createNotificationChannel(_notificationChannel!!); + + _mediaSession = MediaSessionCompat(this, "PlayerState"); + _mediaSession?.setPlaybackState(PlaybackStateCompat.Builder() + .setState(PlaybackStateCompat.STATE_PLAYING, 0, 1f) + .build()); + _mediaSession?.setCallback(object: MediaSessionCompat.Callback() { + override fun onSeekTo(pos: Long) { + super.onSeekTo(pos) + Log.i(TAG, "Media session callback onSeekTo(pos = $pos)"); + MediaControlReceiver.onSeekToReceived.emit(pos); + } + + override fun onPlay() { + super.onPlay(); + Log.i(TAG, "Media session callback onPlay()"); + MediaControlReceiver.onPlayReceived.emit(); + } + + override fun onPause() { + super.onPause(); + Log.i(TAG, "Media session callback onPause()"); + MediaControlReceiver.onPauseReceived.emit(); + } + + override fun onStop() { + super.onStop(); + Log.i(TAG, "Media session callback onStop()"); + MediaControlReceiver.onCloseReceived.emit(); + } + + override fun onSkipToPrevious() { + super.onSkipToPrevious(); + Log.i(TAG, "Media session callback onSkipToPrevious()"); + MediaControlReceiver.onPreviousReceived.emit(); + } + }); + } + + override fun onCreate() { + Logger.i(TAG, "onCreate called"); + super.onCreate() + } + + override fun onDestroy() { + Logger.i(TAG, "onDestroy called"); + _instance = null; + MediaControlReceiver.onCloseReceived.emit(); + super.onDestroy(); + } + + override fun onBind(p0: Intent?): IBinder? { + return null; + } + + fun closeMediaSession() { + Logger.i(TAG, "closeMediaSession called"); + stopForeground(true); + + val focusRequest = _focusRequest; + if (focusRequest != null) { + _audioManager?.abandonAudioFocusRequest(focusRequest); + _focusRequest = null; + } + _hasFocus = false; + + _notificationManager?.cancel(MEDIA_NOTIF_ID); + _notif_last_video = null; + _notif_last_bitmap = null; + _mediaSession = null; + + if(_instance == this) + _instance = null; + this.stopSelf(); + } + + fun updateMediaSession(videoUpdated: IPlatformVideo?) { + Logger.i(TAG, "updateMediaSession called"); + var isUpdating = false; + val video: IPlatformVideo; + if(videoUpdated == null) { + val notifLastVideo = _notif_last_video ?: return; + video = notifLastVideo; + isUpdating = true; + } + else + video = videoUpdated; + + if(_notificationChannel == null || _mediaSession == null) + setupNotificationRequirements(); + + _mediaSession?.setMetadata( + MediaMetadataCompat.Builder() + .putString(MediaMetadata.METADATA_KEY_ARTIST, video.author.name) + .putString(MediaMetadata.METADATA_KEY_TITLE, video.name) + .putLong(MediaMetadata.METADATA_KEY_DURATION, video.duration * 1000) + .build()); + + val thumbnail = video.thumbnails.getHQThumbnail(); + + _notif_last_video = video; + + if(isUpdating) + notifyMediaSession(video, _notif_last_bitmap); + else if(thumbnail != null) { + notifyMediaSession(video, null); + val tag = video; + Glide.with(this).asBitmap() + .load(thumbnail) + .into(object: CustomTarget() { + override fun onResourceReady(resource: Bitmap,transition: Transition?) { + if(tag == _notif_last_video) + notifyMediaSession(video, resource) + } + override fun onLoadCleared(placeholder: Drawable?) { + if(tag == _notif_last_video) + notifyMediaSession(video, null) + } + }); + } + else + notifyMediaSession(video, null); + } + private fun generateMediaAction(context: Context, icon: Int, title: String, intent: PendingIntent) : NotificationCompat.Action { + return NotificationCompat.Action.Builder(icon, title, intent).build(); + } + private fun notifyMediaSession(video: IPlatformVideo?, desiredBitmap: Bitmap?) { + val channel = _notificationChannel ?: return; + val session = _mediaSession ?: return; + val icon = StatePlatform.instance.getPlatformIcon(video?.id?.pluginId)?.resId ?: R.drawable.ic_play_white_nopad; + var bitmap = desiredBitmap; + + val bringUpIntent = Intent(this, MainActivity::class.java); + bringUpIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + + val hasQueue = StatePlayer.instance.getNextQueueItem() != null; + + /* Fixes album art on older devices, not sure we wanna use it yet. + if(desiredBitmap != null) { + _mediaSession?.setMetadata( + MediaMetadataCompat.Builder() + .putString(MediaMetadata.METADATA_KEY_ARTIST, video.author.name) + .putString(MediaMetadata.METADATA_KEY_TITLE, video.name) + .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, desiredBitmap) + .putLong(MediaMetadata.METADATA_KEY_DURATION, video.duration * 1000) + .build()); + }*/ + + val deleteIntent = MediaControlReceiver.getCloseIntent(this, 99); + var builder = NotificationCompat.Builder(this, MEDIA_NOTIF_TAG) + .setSmallIcon(icon) + .setOngoing(true) + .setContentIntent(PendingIntent.getActivity(this, 5, bringUpIntent, PendingIntent.FLAG_IMMUTABLE)) + .setStyle(if(hasQueue) + androidx.media.app.NotificationCompat.MediaStyle() + .setMediaSession(session.sessionToken) + .setShowActionsInCompactView(0, 1, 2) + else + androidx.media.app.NotificationCompat.MediaStyle() + .setMediaSession(session.sessionToken) + .setShowActionsInCompactView(0)) + .setDeleteIntent(deleteIntent) + .setChannelId(channel.id) + + val playWhenReady = StatePlayer.instance.isPlaying; + + if(hasQueue) + builder = builder.addAction(generateMediaAction(this, R.drawable.ic_fast_rewind_notif, "Back", MediaControlReceiver.getPrevIntent(this, 3))) + + if(playWhenReady) + builder = builder.addAction(generateMediaAction(this, R.drawable.ic_pause_notif, "Pause", MediaControlReceiver.getPauseIntent(this, 2))); + else + builder = builder.addAction(generateMediaAction(this, R.drawable.ic_play_notif, "Play", MediaControlReceiver.getPlayIntent(this, 1))); + + if(hasQueue) + builder = builder.addAction(generateMediaAction(this, R.drawable.ic_fast_forward_notif, "Forward", MediaControlReceiver.getNextIntent(this, 4))); + + builder = builder.addAction(generateMediaAction(this, R.drawable.ic_stop_notif, "Stop", MediaControlReceiver.getCloseIntent(this, 5))); + + if(bitmap?.isRecycled ?: false) + bitmap = null; + if(bitmap != null) + builder.setLargeIcon(bitmap); + + val notif = builder.build(); + notif.flags = notif.flags or NotificationCompat.FLAG_ONGOING_EVENT or NotificationCompat.FLAG_NO_CLEAR; + + Logger.i(TAG, "Updating notification bitmap=${if (bitmap != null) "not null" else "null"} channelId=${channel.id} icon=${icon} video=${video?.name ?: ""} playWhenReady=${playWhenReady} session.sessionToken=${session.sessionToken}"); + + startForeground(MEDIA_NOTIF_ID, notif, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK); + + _notif_last_bitmap = bitmap; + } + + fun updateMediaSessionPlaybackState(state: Int, pos: Long) { + _mediaSession?.setPlaybackState( + PlaybackStateCompat.Builder() + .setActions( + PlaybackStateCompat.ACTION_SEEK_TO or + PlaybackStateCompat.ACTION_PLAY or + PlaybackStateCompat.ACTION_PAUSE or + PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or + PlaybackStateCompat.ACTION_PLAY_PAUSE + ) + .setState(state, pos, 1f, SystemClock.elapsedRealtime()) + .build()); + + if(_focusRequest == null) + setAudioFocus(); + } + + //TODO: (TBD) This code probably more fitting inside FutoVideoPlayer, as this service is generally only used for global events + private fun setAudioFocus() { + Log.i(TAG, "Requested audio focus."); + + val focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) + .setAcceptsDelayedFocusGain(true) + .setOnAudioFocusChangeListener(_audioFocusChangeListener) + .build() + + _focusRequest = focusRequest; + val result = _audioManager?.requestAudioFocus(focusRequest) + Log.i(TAG, "Audio focus request result $result"); + if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + //TODO: Handle when not possible to get audio focus + _hasFocus = true; + Log.i(TAG, "Audio focus received"); + } + } + + private val _audioFocusChangeListener = + OnAudioFocusChangeListener { focusChange -> + try { + when (focusChange) { + AudioManager.AUDIOFOCUS_GAIN -> { + //Do not start playing on gaining audo focus + //MediaControlReceiver.onPlayReceived.emit(); + _hasFocus = true; + Log.i(TAG, "Audio focus gained"); + } + AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> { + MediaControlReceiver.onPauseReceived.emit(); + Log.i(TAG, "Audio focus transient loss"); + } + AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> { + Log.i(TAG, "Audio focus transient loss, can duck"); + } + AudioManager.AUDIOFOCUS_LOSS -> { + _hasFocus = false; + MediaControlReceiver.onPauseReceived.emit(); + Log.i(TAG, "Audio focus lost"); + + val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val runningAppProcesses = activityManager.runningAppProcesses + for (processInfo in runningAppProcesses) { + // Check the importance of the running app process + if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { + // This app is in the foreground, which might have caused the loss of audio focus + Log.i("AudioFocus", "App ${processInfo.processName} might have caused the loss of audio focus") + } + } + } + } + } catch(ex: Throwable) { + Logger.w(TAG, "Failed to handle audio focus event", ex); + } + } + + companion object { + private const val TAG = "MediaPlaybackService"; + private var _ignore = false; + private var _instance: MediaPlaybackService? = null; + + private var _callOnStarted: ((MediaPlaybackService)->Unit)? = null; + + @Synchronized + fun getOrCreateService(context: Context, handle: (MediaPlaybackService)->Unit) { + if(_instance == null) { + _callOnStarted = handle; + val intent = Intent(context, MediaPlaybackService::class.java); + context.startForegroundService(intent); + } + else _instance?.let { + handle(it); + } + } + @Synchronized + fun getService() : MediaPlaybackService? { + return _instance; + } + + @Synchronized + fun closeService() { + _instance?.let { + _instance = null; + it.closeMediaSession(); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StateAnnouncement.kt b/app/src/main/java/com/futo/platformplayer/states/StateAnnouncement.kt new file mode 100644 index 00000000..76d06783 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/states/StateAnnouncement.kt @@ -0,0 +1,353 @@ +package com.futo.platformplayer.states + +import android.content.Context +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.StringHashSetStorage +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import java.time.OffsetDateTime +import java.util.Random +import java.util.UUID + +class StateAnnouncement { + private val _lock = Object(); + + private val _sessionAnnouncementsNever = FragmentedStorage.get("announcementNeverSession"); + private val _sessionAnnouncements: HashMap = hashMapOf(); + private val _sessionActions: HashMapUnit> = hashMapOf(); + + private val _announcementsNever = FragmentedStorage.get("announcementNever"); + private val _announcementsStore = FragmentedStorage.storeJson("announcements").load(); + private val _announcementsClosed = HashSet(); + + val onAnnouncementChanged = Event0(); + + suspend fun loadAnnouncements() { + Logger.i(TAG, "Loading announcements") + + withContext(Dispatchers.IO) { + try { + val client = ManagedHttpClient(); + val response = client.get("https://announcements.grayjay.app/grayjay.json"); + if (response.isOk && response.body != null) { + val body = response.body.string(); + val announcements = Json.decodeFromString>(body); + + synchronized(_lock) { + for (announcement in announcements) { + if (_sessionAnnouncements.containsKey(announcement.id)) + return@synchronized; + + if (!_announcementsStore.hasItem { it.id == announcement.id }) { + _announcementsStore.saveAsync(announcement); + } + } + } + + withContext(Dispatchers.Main) { + onAnnouncementChanged.emit(); + } + } + } catch (e: Throwable) { + Logger.w(TAG, "Failed to load announcements", e) + } + } + + Logger.i(TAG, "Finished loading announcements") + } + + fun registerAnnouncement(id: String?, title: String, msg: String, announceType: AnnouncementType = AnnouncementType.SESSION, time: OffsetDateTime? = null, category: String? = null, actionButton: String, action: ((announcement: Announcement)->Unit)) { + synchronized(_lock) { + val idActual = id ?: UUID.randomUUID().toString(); + val announcement = SessionAnnouncement(idActual, title, msg, announceType, time, category, actionButton, idActual); + + if(action != null) + _sessionActions.put(idActual, action); + registerAnnouncementSession(announcement); + } + } + fun registerAnnouncement(id: String?, title: String, msg: String, announceType: AnnouncementType = AnnouncementType.SESSION, time: OffsetDateTime? = null, category: String? = null, actionButton: String, action: ((announcement: Announcement)->Unit), cancelButton: String? = null, cancelAction: ((announcement: Announcement)-> Unit)? = null) { + synchronized(_lock) { + val idActual = id ?: UUID.randomUUID().toString(); + val announcement = SessionAnnouncement(idActual, title, msg, announceType, time, category, actionButton, idActual, cancelButton, if(cancelAction != null) idActual + "_cancel" else null); + + if(action != null) + _sessionActions.put(idActual, action); + if(cancelAction != null) + _sessionActions.put(idActual + "_cancel", cancelAction); + registerAnnouncementSession(announcement); + } + } + fun registerAnnouncement(id: String?, title: String, msg: String, announceType: AnnouncementType = AnnouncementType.DELETABLE, time: OffsetDateTime? = null, category: String? = null, actionButton: String? = null, actionId: String? = null) { + val newAnnouncement = Announcement(if(id == null) UUID.randomUUID().toString() else id, title, msg, announceType, time, category, actionButton, actionId); + + if(announceType == AnnouncementType.SESSION || announceType == AnnouncementType.SESSION_RECURRING) + registerAnnouncementSession(newAnnouncement); + else + registerAnnouncement(newAnnouncement); + } + fun registerAnnouncementSession(announcement: Announcement) { + synchronized(_lock) { + _sessionAnnouncements.put(announcement.id, announcement); + } + + onAnnouncementChanged.emit(); + } + fun registerAnnouncement(announcement: Announcement) { + synchronized(_lock) { + if(_sessionAnnouncements.containsKey(announcement.id)) + return@synchronized; + + if (!_announcementsStore.hasItem { it.id == announcement.id }) { + _announcementsStore.saveAsync(announcement); + } + } + + onAnnouncementChanged.emit(); + } + + fun getVisibleAnnouncements(category: String? = null): List { + synchronized(_lock) { + if (category != null) { + return _announcementsStore.getItems().filter { it.category == category && !_announcementsNever.contains(it.id) && !_announcementsClosed.contains(it.id) } + + _sessionAnnouncements.values.filter { it.category == category && !_sessionAnnouncementsNever.contains(it.id) && !_announcementsClosed.contains(it.id) } + } else { + return _announcementsStore.getItems().filter { !_announcementsNever.contains(it.id) && !_announcementsClosed.contains(it.id) } + + _sessionAnnouncements.values.filter { !_sessionAnnouncementsNever.contains(it.id) && !_announcementsClosed.contains(it.id) } + } + } + } + + fun closeAnnouncement(id: String) { + val item: Announcement?; + synchronized(_lock) { + item = _announcementsStore.findItem { it.id == id }; + + if (item != null) { + when (item.announceType) { + AnnouncementType.DELETABLE -> { + neverAnnouncement(item.id); + } + AnnouncementType.SESSION -> { + deleteAnnouncement(item.id); + } + else -> { + _announcementsClosed.add(item.id); + } + } + } + val itemSession = _sessionAnnouncements.get(id); + if(itemSession != null) { + when (itemSession.announceType) { + AnnouncementType.DELETABLE -> { + neverAnnouncement(itemSession.id); + } + AnnouncementType.SESSION -> { + deleteAnnouncement(itemSession.id); + + if(itemSession is SessionAnnouncement) + cancelActionAnnouncement(itemSession); + } + else -> { + _announcementsClosed.add(itemSession.id); + } + } + } + } + if(item is SessionAnnouncement) { + if(item.cancelActionId != null) { + val cancelAction = _sessionActions[item.cancelActionId]; + cancelAction?.invoke(item); + } + } + } + + fun deleteAllAnnouncements() { + synchronized(_lock) { + val items = _announcementsStore.getItems().toList(); + for (item in items) { + _announcementsStore.delete(item); + } + + val sessionItems = _sessionAnnouncements.toList(); + for (item in sessionItems) { + _sessionAnnouncements.remove(item.first); + } + } + + onAnnouncementChanged.emit(); + } + + fun deleteAnnouncement(id: String) { + synchronized(_lock) { + val item = _announcementsStore.findItem { it.id == id }; + if (item != null) + _announcementsStore.delete(item); + val itemSession = _sessionAnnouncements.get(id); + if(itemSession != null) + _sessionAnnouncements.remove(id); + } + + onAnnouncementChanged.emit(); + } + fun neverAnnouncement(id: String) { + synchronized(_lock) { + val item = _announcementsStore.findItem { it.id == id }; + if (item != null && !_announcementsNever.contains(id)) + _announcementsNever.add(id); + val itemSession = _sessionAnnouncements.get(id); + if(itemSession != null && !_sessionAnnouncementsNever.contains(id)) + _sessionAnnouncementsNever.add(id); + } + + _sessionAnnouncementsNever.save(); + _announcementsNever.save(); + onAnnouncementChanged.emit(); + } + fun actionAnnouncement(id: String) { + val item = _announcementsStore.findItem { it.id == id } ?: _sessionAnnouncements[id]; + if(item != null) + actionAnnouncement(item); + } + fun actionAnnouncement(item: Announcement) { + val action = _sessionActions[item.id]; + if (action != null) { + action(item); + } else { + when (item.actionId) { + ACTION_NEVER -> neverAnnouncement(item.id); + ACTION_SOMETHING -> actionSomething(); + } + } + } + fun cancelActionAnnouncement(id: String) { + val item = _announcementsStore.findItem { it.id == id } ?: _sessionAnnouncements[id]; + if(item != null) + cancelActionAnnouncement(item); + } + fun cancelActionAnnouncement(item: Announcement) { + if(item is SessionAnnouncement && item.cancelActionId != null) { + val action = _sessionActions[item.cancelActionId]; + action?.invoke(item); + } + } + + fun resetAnnouncements() { + _announcementsClosed.clear(); + _announcementsNever.values.clear(); + _announcementsNever.save(); + _sessionAnnouncementsNever.values.clear(); + _sessionAnnouncementsNever.save(); + _sessionAnnouncements.clear(); + onAnnouncementChanged.emit(); + } + + //TODO Actions + private fun actionSomething() { + + } + + + + + fun registerDidYouKnow() { + val random = Random(); + val message: String? = when (random.nextInt(4 * 18 + 1)) { + 0 -> "You can login to different platforms and unify your content experience. Check it out in the source settings!" + 1 -> "Importing your playlists and subscriptions from other platforms to Grayjay is quick and easy. Check it out in the source settings!" + 2 -> "Want to cast to a big screen? Try out FCast (https://fcast.org/)." + 3 -> "Explore Grayjay's gesture controls. When in full-screen swipe on the left to change brightness, swipe on the right to change volume." + 4 -> "Explore Grayjay's gesture controls. Swipe up in the center of a video to toggle full-screen." + 5 -> "Grayjay's multi-platform search lets you find content from various sources." + 6 -> "Grayjay's multi-platform search filters will unify filters across platforms. If your expected filters are not there, try toggling some platforms off in the search filters." + 7 -> "You can share playlists with friends on the playlist page and make full-backups in the settings page." + 8 -> "Discover Grayjay's offline playback feature. Save content for when you're on the go!" + 9 -> "Paid content from your favorite creators gets seamlessly integrated into your Grayjay feed. Login to a platform to seamlessly see content you paid for." + 10 -> "Explore Grayjay's plugin features! Login, import playlists, and tweak plugin settings for a tailored experience." + 11 -> "Directly engage with content by liking, disliking, or leaving comments on the Polycentric network." + 12 -> "With Grayjay's rotation lock, you can watch videos in your preferred orientation regardless of device settings. Check it out during playback!" + 13 -> "Grayjay supports background play. Listen to your favorite content even while multitasking!" + 14 -> "Use Grayjay's quality selection to adjust video resolution. Save data or watch in high definition – it's up to you." + 15 -> "Customize your Grayjay experience by changing playback speed. Watch content at your own pace." + 16 -> "Save time by adding videos to your 'Watch Later' list. Perfect for catching up on content during your free time." + 17 -> "On Grayjay, your playlists, subscriptions, and settings are stored offline for privacy and quick access." + 18 -> "Explore and engage with live content using Grayjay's live stream feature." + else -> null + }; + + if (message != null) { + registerAnnouncement( + "did-you-know?", + "Did you know?", + message, + AnnouncementType.SESSION_RECURRING + ); + } + } + + companion object { + private var _instance: StateAnnouncement? = null; + val instance: StateAnnouncement + get(){ + if(_instance == null) + _instance = StateAnnouncement(); + return _instance!!; + }; + + + const val ACTION_SOMETHING = "SOMETHING"; + const val ACTION_NEVER = "NEVER"; + private const val TAG = "StateAnnouncement"; + } +} + +@Serializable +open class Announcement( + val id: String, + val title: String, + val msg: String, + val announceType: AnnouncementType, + @Serializable(with = OffsetDateTimeNullableSerializer::class) + val time: OffsetDateTime? = null, + val category: String? = null, + val actionName: String? = null, + val actionId: String? = null +); +class SessionAnnouncement( + id: String, + title: String, + msg: String, + announceType: AnnouncementType, + time: OffsetDateTime? = null, + category: String? = null, + actionName: String? = null, + actionId: String? = null, + val cancelName: String? = null, + val cancelActionId: String? = null +): Announcement( + id= id, + title = title, + msg = msg, + announceType = announceType, + time = time, + category = category, + actionName = actionName, + actionId = actionId +); + +enum class AnnouncementType(val value : Int) { + DELETABLE(0), //Close button deletes announcement (generally for actions) + RECURRING(1), //Shows up till never is pressed (generally for patchnotes etc) + PERMANENT(2), //Shows up until deleted through other means (action) + SESSION(3), //Not persistent, only during this session + SESSION_RECURRING(4); //Not persistent, only during this session, recurring id +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt new file mode 100644 index 00000000..444eafa4 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt @@ -0,0 +1,578 @@ +package com.futo.platformplayer.states + +import android.Manifest +import android.annotation.SuppressLint +import android.content.Context +import android.content.IntentFilter +import android.content.pm.PackageManager +import android.media.AudioManager +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest +import android.os.Environment +import android.util.DisplayMetrics +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.work.* +import com.futo.platformplayer.* +import com.futo.platformplayer.activities.MainActivity +import com.futo.platformplayer.background.BackgroundWorker +import com.futo.platformplayer.casting.StateCasting +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.logging.AndroidLogConsumer +import com.futo.platformplayer.logging.FileLogConsumer +import com.futo.platformplayer.logging.LogLevel +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.receivers.AudioNoisyReceiver +import com.futo.platformplayer.services.DownloadService +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.v2.ManagedStore +import kotlinx.coroutines.* +import java.io.File +import java.time.OffsetDateTime +import java.util.* +import java.util.concurrent.TimeUnit + +/*** + * This class contains global context for unconventional cases where obtaining context is hard. + * This context is only alive while MainActivity is active + * Ideally StateApp.withContext is used to only run code when it is available or throw + */ +class StateApp { + val isMainActive: Boolean get() = contextOrNull != null && contextOrNull is MainActivity; //if context is MainActivity, it means its active + + private val externalRootDirectory = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), "Grayjay"); + fun getExternalRootDirectory(): File? { + if(!externalRootDirectory.exists()) { + val result = externalRootDirectory.mkdirs(); + if(!result) + return null; + return externalRootDirectory; + } + else + return externalRootDirectory; + } + + //Scope + private var _scope: CoroutineScope? = null; + val scopeOrNull: CoroutineScope? get() { + return _scope; + } + val scope: CoroutineScope get() { + val thisScope = scopeOrNull + ?: throw IllegalStateException("Attempted to use a global lifetime scope while MainActivity is no longer available"); + return thisScope; + } + val scopeGetter: ()->CoroutineScope get() { + return {scope}; + } + + var displayMetrics: DisplayMetrics? = null; + + //Context + private var _context: Context? = null; + val contextOrNull: Context? get() { + return _context; + } + val context: Context get() { + val thisContext = contextOrNull + ?: throw IllegalStateException("Attempted to use a global context while MainActivity is no longer available"); + return thisContext; + } + + //Files + private var _tempDirectory: File? = null; + + + //AutoRotate + var systemAutoRotate: Boolean = false; + + //Network + private var _lastMeteredState: Boolean = false; + private var _connectivityManager: ConnectivityManager? = null; + private var _lastNetworkState: NetworkState = NetworkState.UNKNOWN; + + //Logging + private var _fileLogConsumer: FileLogConsumer? = null; + + //Receivers + private var _receiverBecomingNoisy: AudioNoisyReceiver? = null; + + val onConnectionAvailable = Event0(); + val preventPictureInPicture = Event0(); + + fun getTempDirectory(): File { + return _tempDirectory!!; + } + fun getTempFile(extension: String? = null): File { + val name = UUID.randomUUID().toString() + + if(extension != null) + ".${extension}" + else + ""; + + return File(_tempDirectory, name); + } + + fun getCurrentSystemAutoRotate(): Boolean { + _context?.let { + systemAutoRotate = android.provider.Settings.System.getInt( + it.contentResolver, + android.provider.Settings.System.ACCELEROMETER_ROTATION, 0 + ) == 1; + }; + return systemAutoRotate; + } + + + fun isCurrentMetered(): Boolean { + ensureConnectivityManager(); + return _connectivityManager?.isActiveNetworkMetered ?: throw IllegalStateException("Connectivity manager not available"); + } + fun isNetworkState(vararg states: NetworkState): Boolean { + return states.contains(getCurrentNetworkState()); + } + fun getCurrentNetworkState(): NetworkState { + var state = NetworkState.DISCONNECTED; + + ensureConnectivityManager(); + _connectivityManager?.activeNetwork?.let { + val networkCapabilities = _connectivityManager?.getNetworkCapabilities(it) ?: throw IllegalStateException("Connectivity manager could not be found"); + + val connected = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + + if(connected && networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { + state = NetworkState.ETHERNET; + return@let; + } + if(connected && networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + state = NetworkState.WIFI; + return@let; + } + if(connected && networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + state = NetworkState.CELLULAR; + return@let; + } + } + return state; + } + + //Lifecycle + fun setGlobalContext(context: Context, coroutineScope: CoroutineScope? = null) { + _context = context; + _scope = coroutineScope + + //System checks + systemAutoRotate = getCurrentSystemAutoRotate(); + } + + fun initializeFiles(force: Boolean = false) { + if(force || !FragmentedStorage.isInitialized) { + FragmentedStorage.initialize(context.filesDir); + _tempDirectory = File(context.filesDir, "temp"); + if (_tempDirectory?.exists() == true) { + Logger.i(TAG, "Deleting ${_tempDirectory?.listFiles()?.size} temp files"); + _tempDirectory?.deleteRecursively(); + } + _tempDirectory?.mkdirs(); + } + } + + /*** + * This method starts a background context, should only be used under the assumption that your app is not active (eg. Scheduled background worker) + */ + suspend fun startBackground(context: Context, withFiles: Boolean, withPlugins: Boolean, backgroundWorker: suspend () -> Unit) { + withContext(Dispatchers.IO) { + backgroundStarting(context, this, withFiles, withPlugins); + try { + backgroundWorker(); + } + catch (ex: Throwable) { + Logger.e(TAG, "Background work failed: ${ex.message}", ex); + throw ex; + } + finally { + backgroundStopping(context); + } + } + } + suspend fun backgroundStarting(context: Context, scope: CoroutineScope, withFiles: Boolean, withPlugins: Boolean) { + if(contextOrNull == null) { + Logger.i(TAG, "BACKGROUND STATE: Starting"); + if(!Logger.hasConsumers && BuildConfig.DEBUG) { + Logger.i(TAG, "BACKGROUND STATE: Initialize logger"); + Logger.setLogConsumers(listOf(AndroidLogConsumer())); + } + + Logger.i(TAG, "BACKGROUND STATE: Initialize context"); + setGlobalContext(context, scope); + + if(withFiles) { + Logger.i(TAG, "BACKGROUND STATE: Initialize files"); + initializeFiles(); + } + + if (withPlugins) { + Logger.i(TAG, "BACKGROUND STATE: Initialize plugins"); + StatePlatform.instance.updateAvailableClients(context, true); + } + } + } + fun backgroundStopping(context: Context) { + if(contextOrNull == context || contextOrNull == null) { + Logger.i(TAG, "STOPPING BACKGROUND STATE"); + StatePlatform.instance.disableAllClients(); + dispose(); + } + } + + fun mainAppStarting(context: Context) { + initializeFiles(true); + + val logFile = File(context.filesDir, "log.txt"); + if (Settings.instance.logging.logLevel > LogLevel.NONE.value) { + val fileLogConsumer = FileLogConsumer(logFile, LogLevel.fromInt(Settings.instance.logging.logLevel), false); + Logger.setLogConsumers(listOf( + AndroidLogConsumer(), + fileLogConsumer + )); + + _fileLogConsumer = fileLogConsumer; + } else if (BuildConfig.DEBUG) { + if (logFile.exists()) { + logFile.delete(); + } + + Logger.setLogConsumers(listOf(AndroidLogConsumer())); + } + + StatePayment.instance.initialize(); + StatePolycentric.instance.load(context); + StateSaved.instance.load(); + + displayMetrics = context.resources.displayMetrics; + ensureConnectivityManager(context); + + if (!BuildConfig.DEBUG) { + StateTelemetry.instance.initialize(); + StateTelemetry.instance.upload(); + } + + Logger.onLogSubmitted.subscribe { + scopeGetter().launch(Dispatchers.Main) { + try { + if (it != null) { + UIDialogs.toast("Uploaded " + (it ?: "null"), true); + } else { + UIDialogs.toast("Failed to upload"); + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to show toast", e) + } + } + } + } + fun mainAppStarted(context: Context) { + Logger.i(TAG, "App started"); + + StateAnnouncement.instance.registerAnnouncement("fa4647d3-36fa-4c8c-832d-85b00fc72dca", "Disclaimer", "This is an early alpha build of the application, expect bugs and unfinished features.", AnnouncementType.DELETABLE, OffsetDateTime.now()) + + if(SettingsDev.instance.developerMode && SettingsDev.instance.devServerSettings.devServerOnBoot) + StateDeveloper.instance.runServer(); + + if(StateSubscriptions.instance.shouldMigrate()) + StateSubscriptions.instance.tryMigrateIfNecessary(); + + if(Settings.instance.downloads.shouldDownload()) { + StateDownloads.instance.checkForOutdatedPlaylists(); + + StateDownloads.instance.getDownloadPlaylists(); + if (!StateDownloads.instance.getDownloading().isEmpty()) + DownloadService.getOrCreateService(context); + } + + StateDownloads.instance.checkForExportTodos(); + + val autoUpdateEnabled = Settings.instance.autoUpdate.isAutoUpdateEnabled(); + val shouldDownload = Settings.instance.autoUpdate.shouldDownload(); + val backgroundDownload = Settings.instance.autoUpdate.backgroundDownload == 1; + when { + //Background download + autoUpdateEnabled && shouldDownload && backgroundDownload -> { + StateUpdate.instance.setShouldBackgroundUpdate(true); + } + + autoUpdateEnabled && !shouldDownload && backgroundDownload -> { + Logger.i(TAG, "Auto update skipped due to wrong network state"); + } + + //Foreground download + autoUpdateEnabled -> { + StateUpdate.instance.checkForUpdates(context, false); + } + + else -> { + Logger.i(TAG, "Auto update disabled"); + } + } + + _receiverBecomingNoisy?.let { + _receiverBecomingNoisy = null; + context.unregisterReceiver(it); + } + _receiverBecomingNoisy = AudioNoisyReceiver(); + context.registerReceiver(_receiverBecomingNoisy, IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); + + //Migration + migrateStores(context, listOf( + StateSubscriptions.instance.toMigrateCheck(), + StatePlaylists.instance.toMigrateCheck() + ).flatten(), 0); + + scope.launch { + delay(5000); + StateSubscriptions.instance.updateSubscriptionFeed(scope, false); + } + + val interval = Settings.instance.subscriptions.getSubscriptionsBackgroundIntervalMinutes(); + scheduleBackgroundWork(context, interval != 0, interval); + + + if(!Settings.instance.backup.didAskAutoBackup && !Settings.instance.backup.shouldAutomaticBackup()) { + StateAnnouncement.instance.registerAnnouncement("backup", "Set Automatic Backup", "Configure daily backups of your data to restore in case of catastrophic failure.", AnnouncementType.SESSION, null, null, "Configure", { + UIDialogs.showAutomaticBackupDialog(context); + StateAnnouncement.instance.deleteAnnouncement("backup"); + }, "No Backup", { + Settings.instance.backup.didAskAutoBackup = true; + Settings.instance.save(); + }); + } + + instance.scopeOrNull?.launch(Dispatchers.IO) { + try { + StateAnnouncement.instance.loadAnnouncements(); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to load announcements.", e) + } + } + + if (BuildConfig.IS_PLAYSTORE_BUILD) { + StateAnnouncement.instance.registerAnnouncement( + "playstore-version", + "Playstore version", + "This version is the playstore version of the app. Your experience will be more limited.", + AnnouncementType.SESSION_RECURRING + ); + } + + StateAnnouncement.instance.registerDidYouKnow(); + + } + fun mainAppStartedWithExternalFiles(context: Context) { + if(!Settings.instance.didFirstStart) { + if(StateBackup.hasAutomaticBackup()) { + UIDialogs.showAutomaticRestoreDialog(context, if(context is LifecycleOwner) context.lifecycleScope else scope); + } + + + Settings.instance.didFirstStart = true; + Settings.instance.save(); + } + if(Settings.instance.backup.shouldAutomaticBackup()) { + try { + StateBackup.startAutomaticBackup(); + } + catch(ex: Throwable) { + Logger.e("StateApp", "Automatic backup failed", ex); + UIDialogs.toast(context, "Automatic backup failed due to:\n" + ex.message); + } + } + else + Logger.i("StateApp", "No AutoBackup configured"); + } + + + fun scheduleBackgroundWork(context: Context, active: Boolean = true, intervalMinutes: Int = 60 * 12) { + val wm = WorkManager.getInstance(context); + + if(active) { + if(BuildConfig.DEBUG) + UIDialogs.toast(context, "Scheduling background every ${intervalMinutes} minutes"); + + val req = PeriodicWorkRequest.Builder(BackgroundWorker::class.java, intervalMinutes.toLong(), TimeUnit.MINUTES, 5, TimeUnit.MINUTES) + .setConstraints(Constraints.Builder() + .setRequiredNetworkType(NetworkType.UNMETERED) + .build()) + .build(); + wm.enqueueUniquePeriodicWork("backgroundSubscriptions", ExistingPeriodicWorkPolicy.UPDATE, req); + } + else + wm.cancelAllWork(); + } + + + private fun migrateStores(context: Context, managedStores: List>, index: Int) { + if(managedStores.size <= index) + return; + val store = managedStores[index]; + if(store.hasMissingReconstructions()) + UIDialogs.showMigrateDialog(context, store) { + migrateStores(context, managedStores, index + 1); + }; + else + migrateStores(context, managedStores, index + 1); + } + + fun mainAppDestroyed(context: Context) { + Logger.i(TAG, "App ended"); + _receiverBecomingNoisy?.let { + _receiverBecomingNoisy = null; + context.unregisterReceiver(it); + } + + Logger.i(TAG, "Unregistered network callback on connectivityManager.") + _connectivityManager?.unregisterNetworkCallback(_connectivityEvents); + + StatePlayer.instance.closeMediaSession(); + StateCasting.instance.stop(); + StatePlayer.dispose(); + Companion.dispose(); + _fileLogConsumer?.close(); + } + + fun dispose(){ + _context = null; + _scope = null; + } + + private val _connectivityEvents = object : ConnectivityManager.NetworkCallback() { + override fun onUnavailable() { + super.onUnavailable(); + Logger.i(TAG, "_connectivityEvents onUnavailable"); + + updateNetworkState(); + } + + override fun onLost(network: Network) { + super.onLost(network); + Logger.i(TAG, "_connectivityEvents onLost"); + + updateNetworkState(); + } + + override fun onAvailable(network: Network) { + super.onAvailable(network); + Logger.i(TAG, "_connectivityEvents onAvailable"); + + updateNetworkState(); + + try { + if (_lastNetworkState != NetworkState.DISCONNECTED) { + scopeOrNull?.launch(Dispatchers.Main) { + try { + Logger.i(TAG, "onConnectionAvailable emitted"); + onConnectionAvailable.emit(); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to emit onConnectionAvailable", e) + } + }; + } + } catch(ex: Throwable) { + Logger.w(TAG, "Failed to handle connection available event", ex); + } + } + override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { + super.onCapabilitiesChanged(network, networkCapabilities); + + updateNetworkState(); + + try { + if(FragmentedStorage.isInitialized && Settings.instance.downloads.shouldDownload()) + StateDownloads.instance.checkForDownloadsTodos(); + + val autoUpdateEnabled = Settings.instance.autoUpdate.isAutoUpdateEnabled(); + val shouldDownload = Settings.instance.autoUpdate.shouldDownload(); + val backgroundDownload = Settings.instance.autoUpdate.backgroundDownload == 1; + if (autoUpdateEnabled && shouldDownload && backgroundDownload) { + StateUpdate.instance.setShouldBackgroundUpdate(true); + } else { + StateUpdate.instance.setShouldBackgroundUpdate(false); + } + } catch(ex: Throwable) { + Logger.w(TAG, "Failed to handle capabilities changed event", ex); + } + } + + private fun updateNetworkState() { + try { + val beforeNetworkState = _lastNetworkState; + val beforeMeteredState = _lastMeteredState; + _lastNetworkState = getCurrentNetworkState(); + _lastMeteredState = isCurrentMetered(); + if(beforeNetworkState != _lastNetworkState || beforeMeteredState != _lastMeteredState) + Logger.i(TAG, "Network capabilities changed (State: ${_lastNetworkState}, Metered: ${_lastMeteredState})"); + } catch(ex: Throwable) { + Logger.w(TAG, "Failed to update network state", ex); + } + } + }; + private fun ensureConnectivityManager(context: Context? = null) { + if(_connectivityManager == null) { + _connectivityManager = + (context ?: contextOrNull)?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + ?: throw IllegalStateException("Connectivity manager could not be found"); + + val netReq = NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET) + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .build(); + _connectivityManager!!.registerNetworkCallback(netReq, _connectivityEvents); + } + } + + companion object { + private val TAG = "StateApp"; + @SuppressLint("StaticFieldLeak") //This is only alive while MainActivity is alive + private var _instance : StateApp? = null; + val instance : StateApp + get(){ + if(_instance == null) + _instance = StateApp(); + return _instance!!; + }; + + fun dispose(){ + val instance = _instance; + _instance = null; + instance?.dispose(); + Logger.i(TAG, "StateApp has been disposed"); + } + + fun withContext(handle: (Context)->Unit) { + val context = _instance?.contextOrNull; + if(context != null) + handle(context); + } + fun withContext(throwIfNotAvailable: Boolean, handle: (Context)->Unit) { + if(!throwIfNotAvailable) + withContext(handle); + val context = _instance?.contextOrNull; + if(context != null) + handle(context); + else if(throwIfNotAvailable) + throw IllegalStateException("Attempted to use a global context while MainActivity is no longer available"); + } + } + + + enum class NetworkState { + UNKNOWN, + DISCONNECTED, + CELLULAR, + WIFI, + ETHERNET + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StateAssets.kt b/app/src/main/java/com/futo/platformplayer/states/StateAssets.kt new file mode 100644 index 00000000..94db9864 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/states/StateAssets.kt @@ -0,0 +1,67 @@ +package com.futo.platformplayer.states + +import android.content.Context +import kotlin.streams.toList + +/*** + * Used to read assets + */ +class StateAssets { + companion object { + private val _cache: HashMap = HashMap(); + + private fun resolvePath(base: String, path: String, maxParent: Int = 1): String { + var parentAllowance = maxParent; + var toSkip = 0; + val parts1 = base.split('/').toMutableList(); + val parts2 = path.split('/').toMutableList(); + + for(part in parts2) { + if(part == "." || part == "..") { + if(parentAllowance <= 0) + throw IllegalStateException("Path [${path}] attempted to escape path.."); + parts1.removeLast(); + toSkip++; + } + else + break; + } + return (parts1 + parts2.stream().skip(toSkip.toLong()).toList()).joinToString("/"); + } + + /** + * Does basic asset resolving under certain conditions + */ + fun readAssetRelative(context: Context, base: String, path: String, cache: Boolean = false) : String? { + val finalPath = resolvePath(base, path); + return readAsset(context, finalPath, cache); + } + fun readAssetBinRelative(context: Context, base: String, path: String) : ByteArray? { + val finalPath = resolvePath(base, path); + return readAssetBin(context, finalPath); + } + + fun readAsset(context: Context, path: String, cache: Boolean = false) : String? { + var text: String? = null; + synchronized(_cache) { + if (!_cache.containsKey(path)) { + text = context + ?.assets + ?.open(path) + ?.bufferedReader() + ?.use { it.readText(); }; + _cache.put(path, text); + } + else + text = _cache.get(path); + } + return text; + } + fun readAssetBin(context: Context, path: String) : ByteArray? { + val str = context.assets?.open(path); + if(str == null) + return null; + else return str.use { it.readBytes() }; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StateBackup.kt b/app/src/main/java/com/futo/platformplayer/states/StateBackup.kt new file mode 100644 index 00000000..c8d35f21 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/states/StateBackup.kt @@ -0,0 +1,405 @@ +package com.futo.platformplayer.states + +import android.content.Context +import androidx.core.app.ShareCompat +import androidx.core.content.FileProvider +import com.futo.platformplayer.R +import com.futo.platformplayer.Settings +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.activities.SettingsActivity +import com.futo.platformplayer.encryption.EncryptionProvider +import com.futo.platformplayer.getNowDiffHours +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.v2.ManagedStore +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.File +import java.time.OffsetDateTime +import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream +import java.util.zip.ZipOutputStream +import kotlin.IllegalStateException + +class StateBackup { + companion object { + val TAG = "StateBackup"; + + private val _autoBackupLock = Object(); + + private fun getAutomaticBackupFiles(): Pair { + val dir = StateApp.instance.getExternalRootDirectory(); + if(dir == null) + throw IllegalStateException("Can't access external files"); + return Pair(File(dir, "GrayjayBackup.ezip"), File(dir, "GrayjayBackup.ezip.old")) + } + + + fun getAllMigrationStores(): List> = listOf( + StateSubscriptions.instance.toMigrateCheck(), + StatePlaylists.instance.toMigrateCheck() + ).flatten(); + + + private fun getAutomaticBackupPassword(customPassword: String? = null): String { + val password = customPassword ?: Settings.instance.backup.autoBackupPassword ?: ""; + val pbytes = password.toByteArray(); + if(pbytes.size < 4 || pbytes.size > 32) + throw IllegalStateException("Automatic backup passwords should atleast be 4 character and smaller than 32"); + return password.padStart(32, '9'); + } + fun hasAutomaticBackup(): Boolean { + if(StateApp.instance.getExternalRootDirectory() == null) + return false; + val files = getAutomaticBackupFiles(); + return files.first.exists() || files.second.exists(); + } + fun startAutomaticBackup(force: Boolean = false) { + val lastBackupHoursAgo = Settings.instance.backup.lastAutoBackupTime.getNowDiffHours(); + if(!force && lastBackupHoursAgo < 24) { + Logger.i(TAG, "Not AutoBackuping, last backup ${lastBackupHoursAgo} hours ago"); + return; + } + + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO){ + try { + Logger.i(TAG, "Starting AutoBackup (Last ${lastBackupHoursAgo} ago)"); + synchronized(_autoBackupLock) { + val data = export(); + val zip = data.asZip(); + + val encryptedZip = EncryptionProvider.instance.encrypt(zip, getAutomaticBackupPassword()); + + val backupFiles = getAutomaticBackupFiles(); + val exportFile = backupFiles.first; + if (exportFile.exists()) + exportFile.copyTo(backupFiles.second, true); + + exportFile.writeBytes(encryptedZip); + + Settings.instance.backup.lastAutoBackupTime = OffsetDateTime.now(); //OffsetDateTime.now(); + Settings.instance.save(); + } + Logger.i(TAG, "Finished AutoBackup"); + } + catch(ex: Throwable) { + Logger.e(TAG, "Failed to AutoBackup", ex); + StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { + UIDialogs.toast("Failed to auto backup:\n" + ex.message); + }; + } + } + } + fun restoreAutomaticBackup(context: Context, scope: CoroutineScope, password: String, ifExists: Boolean = false) { + if(ifExists && !hasAutomaticBackup()) { + Logger.i(TAG, "No AutoBackup exists, not restoring"); + return; + } + + //TODO: Sadly on reinstalls of app this fails on file permissions. + + Logger.i(TAG, "Starting AutoBackup restore"); + synchronized(_autoBackupLock) { + + val backupFiles = getAutomaticBackupFiles(); + try { + if (!backupFiles.first.exists()) + throw IllegalStateException("Backup file does not exist"); + + val backupBytesEncrypted = backupFiles.first.readBytes(); + val backupBytes = EncryptionProvider.instance.decrypt(backupBytesEncrypted, getAutomaticBackupPassword(password)); + importZipBytes(context, scope, backupBytes); + Logger.i(TAG, "Finished AutoBackup restore"); + } catch (ex: Throwable) { + Logger.e(TAG, "Failed main AutoBackup restore", ex) + if (!backupFiles.second.exists()) + throw ex; + + val backupBytesEncrypted = backupFiles.second.readBytes(); + val backupBytes = EncryptionProvider.instance.decrypt(backupBytesEncrypted, getAutomaticBackupPassword(password)); + importZipBytes(context, scope, backupBytes); + Logger.i(TAG, "Finished AutoBackup restore"); + } + } + } + + fun startExternalBackup() { + val data = export(); + val now = OffsetDateTime.now(); + val exportFile = File( + FragmentedStorage.getOrCreateDirectory("shares"), + "export_${now.year}-${now.monthValue.toString().padStart(2, '0')}-${now.dayOfMonth.toString().padStart(2, '0')}_${now.hour.toString().padStart(2, '0')}${now.minute.toString().padStart(2, '0')}.zip"); + exportFile.writeBytes(data.asZip()); + + StateApp.instance.contextOrNull?.let { + val uri = FileProvider.getUriForFile(it, it.resources.getString(R.string.authority), exportFile); + + val activity = SettingsActivity.getActivity() ?: return@let; + activity.startActivity( + ShareCompat.IntentBuilder(activity) + .setType("application/zip") + .setStream(uri) + .intent); + } + } + + fun export(): ExportStructure { + val exportInfo = mapOf( + Pair("version", "1") + ); + val storesToSave = getAllMigrationStores() + .associateBy { it.name } + .mapValues { it.value.getAllReconstructionStrings() }; + val settings = Settings.instance.encode(); + val pluginSettings = StatePlugins.instance.getPlugins() + .associateBy { it.config.id } + .mapValues { it.value.settings }; + val pluginUrls = StatePlugins.instance.getPlugins() + .filter { it.config.sourceUrl != null } + .associateBy { it.config.id } + .mapValues { it.value.config.sourceUrl!! }; + + return ExportStructure(exportInfo, settings, storesToSave, pluginUrls, pluginSettings); + } + + + fun importZipBytes(context: Context, scope: CoroutineScope, zipData: ByteArray) { + val import: StateBackup.ExportStructure; + try { + ByteArrayInputStream(zipData).use { + ZipInputStream(it).use { + import = StateBackup.ExportStructure.fromZip(it); + } + } + } + catch(ex: Throwable) { + UIDialogs.showGeneralErrorDialog(context, "Failed to import zip", ex); + return; + } + import(context, scope, import); + } + fun import(context: Context, scope: CoroutineScope, export: ExportStructure) { + + val availableStores = getAllMigrationStores(); + val unknownPlugins = export.plugins.filter { !StatePlugins.instance.hasPlugin(it.key) }; + + var doImport = false; + var doImportSettings = false; + var doImportPlugins = false; + var doImportPluginSettings = false; + var doEnablePlugins = false; + var doImportStores = false; + Logger.i(TAG, "Starting import choices"); + UIDialogs.multiShowDialog(context, { + Logger.i(TAG, "Starting import"); + if(!doImport) + return@multiShowDialog; + val enabledBefore = StatePlatform.instance.getEnabledClients().map { it.id }; + + val onConclusion = { + scope.launch(Dispatchers.IO) { + StatePlatform.instance.selectClients(*enabledBefore.toTypedArray()); + + withContext(Dispatchers.Main) { + UIDialogs.showDialog(context, R.drawable.ic_update_success_251dp, + "Import has finished", null, null, 0, UIDialogs.Action("Ok", {})); + } + } + }; + //TODO: Probably restructure this to be less nested + scope.launch(Dispatchers.IO) { + try { + if (doImportSettings && export.settings != null) { + Logger.i(TAG, "Importing settings"); + try { + Settings.replace(export.settings); + } + catch(ex: Throwable) { + UIDialogs.toast(context, "Failed to import settings\n(" + ex.message + ")"); + } + } + + val afterPluginInstalls = { + scope.launch(Dispatchers.IO) { + if (doEnablePlugins) { + val availableClients = StatePlatform.instance.getEnabledClients().toMutableList(); + availableClients.addAll(StatePlatform.instance.getAvailableClients().filter { !availableClients.contains(it) }); + + Logger.i(TAG, "Import enabling plugins [${availableClients.map{it.name}.joinToString(", ")}]"); + StatePlatform.instance.updateAvailableClients(context, false); + StatePlatform.instance.selectClients(*availableClients.map { it.id }.toTypedArray()); + } + if(doImportPluginSettings) { + for(settings in export.pluginSettings) { + Logger.i(TAG, "Importing Plugin settings [${settings.key}]"); + StatePlugins.instance.setPluginSettings(settings.key, settings.value); + } + } + val toAwait = export.stores.map { it.key }.toMutableList(); + if(doImportStores) { + for(store in export.stores) { + Logger.i(TAG, "Importing store [${store.key}]"); + val relevantStore = availableStores.find { it.name == store.key }; + if(relevantStore == null) { + Logger.w(TAG, "Unknown store [${store.key}] import"); + continue; + } + withContext(Dispatchers.Main) { + UIDialogs.showImportDialog(context, relevantStore, store.key, store.value) { + synchronized(toAwait) { + toAwait.remove(store.key); + if(toAwait.isEmpty()) + onConclusion(); + } + }; + } + } + } + } + } + + if (doImportPlugins) { + Logger.i(TAG, "Importing plugins"); + StatePlugins.instance.installPlugins(context, scope, unknownPlugins.map { it.value }) { + afterPluginInstalls(); + } + } + else + afterPluginInstalls(); + } + catch(ex: Throwable) { + Logger.e(TAG, "Import failed", ex); + UIDialogs.showGeneralErrorDialog(context, "Import failed", ex); + } + } + }, + UIDialogs.Descriptor(R.drawable.ic_move_up, + "Do you want to import data?", + "Several dialogs will follow asking individual parts", + "Settings: ${export.settings != null}\n" + + "Plugins: ${unknownPlugins.size}\n" + + "Plugin Settings: ${export.pluginSettings.size}\n" + + export.stores.map { "${it.key}: ${it.value.size}" }.joinToString("\n").trim() + , 1, + UIDialogs.Action("Import", { + doImport = true; + }, UIDialogs.ActionStyle.PRIMARY), UIDialogs.Action("Cancel", { doImport = false}) + ), + if(export.settings != null) UIDialogs.Descriptor(R.drawable.ic_settings, + "Would you like to import settings", + "These are the settings that configure how your app works", + null, 0, + UIDialogs.Action("Yes", { + doImportSettings = true; + }, UIDialogs.ActionStyle.PRIMARY), UIDialogs.Action("No", {}) + ).withCondition { doImport } else null, + if(unknownPlugins.isNotEmpty()) UIDialogs.Descriptor(R.drawable.ic_sources, + "Would you like to import plugins?", + "Your import contains the following plugins", + unknownPlugins.map { it.value }.joinToString("\n"), 1, + UIDialogs.Action("Yes", { + doImportPlugins = true; + }, UIDialogs.ActionStyle.PRIMARY), UIDialogs.Action("No", {}) + ).withCondition { doImport } else null, + if(export.pluginSettings.isNotEmpty()) UIDialogs.Descriptor(R.drawable.ic_sources, + "Would you like to import plugin settings?", + null, null, 1, + UIDialogs.Action("Yes", { + doImportPluginSettings = true; + }, UIDialogs.ActionStyle.PRIMARY), UIDialogs.Action("No", {}) + ).withCondition { doImport } else null, + UIDialogs.Descriptor(R.drawable.ic_sources, + "Would you like to enable all plugins?", + "Enabling all plugins ensures all required plugins are available during import", + null, 0, + UIDialogs.Action("Yes", { + doEnablePlugins = true; + }, UIDialogs.ActionStyle.PRIMARY), UIDialogs.Action("No", {}) + ).withCondition { doImport }, + if(export.stores.isNotEmpty()) UIDialogs.Descriptor(R.drawable.ic_move_up, + "Would you like to import stores", + "Stores contain playlists, watch later, subscriptions, etc", + null, 0, + UIDialogs.Action("Yes", { + doImportStores = true; + }, UIDialogs.ActionStyle.PRIMARY), UIDialogs.Action("No", {}) + ).withCondition { doImport } else null + ); + } + } + + class ExportStructure( + val exportInfo: Map, + val settings: String?, + val stores: Map>, + val plugins: Map, + val pluginSettings: Map>, + ) { + + fun asZip(): ByteArray { + return ByteArrayOutputStream().use { byteStream -> + ZipOutputStream(byteStream).use { zipStream -> + zipStream.putNextEntry(ZipEntry("exportInfo")); + zipStream.write(Json.encodeToString(exportInfo).toByteArray()); + + if(settings != null) { + zipStream.putNextEntry(ZipEntry("settings")); + zipStream.write(settings.toByteArray()); + } + + zipStream.putNextEntry(ZipEntry("stores/")); + for(store in stores.mapValues { Json.encodeToString(it.value) }) { + zipStream.putNextEntry(ZipEntry("stores/${store.key}")); + zipStream.write(store.value.toByteArray()); + } + + zipStream.putNextEntry(ZipEntry("plugins")); + zipStream.write(Json.encodeToString(plugins).toByteArray()); + + zipStream.putNextEntry(ZipEntry("plugin_settings")); + zipStream.write(Json.encodeToString(pluginSettings).toByteArray()); + }; + return byteStream.toByteArray(); + } + } + + companion object { + fun fromZip(zipStream: ZipInputStream): ExportStructure { + var entry: ZipEntry? = null + + var exportInfo: Map = mapOf(); + var settings: String? = null; + var stores: MutableMap> = mutableMapOf(); + var plugins: Map = mapOf(); + var pluginSettings: Map> = mapOf(); + + while (zipStream.nextEntry.also { entry = it } != null) { + if(entry!!.isDirectory) + continue; + try{ + if(!entry!!.name.startsWith("stores/")) + when(entry!!.name) { + "exportInfo" -> exportInfo = Json.decodeFromString(String(zipStream.readBytes())); + "settings" -> settings = String(zipStream.readBytes()); + "plugins" -> plugins = Json.decodeFromString(String(zipStream.readBytes())); + "plugin_settings" -> pluginSettings = Json.decodeFromString(String(zipStream.readBytes())); + } + else + stores[entry!!.name.substring("stores/".length)] = Json.decodeFromString(String(zipStream.readBytes())); + } + catch(ex: Throwable) { + throw IllegalStateException("Failed to parse zip [${entry?.name}] due to ${ex.message}"); + } + } + return ExportStructure(exportInfo, settings, stores, plugins, pluginSettings); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StateDeveloper.kt b/app/src/main/java/com/futo/platformplayer/states/StateDeveloper.kt new file mode 100644 index 00000000..8d9ceff6 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/states/StateDeveloper.kt @@ -0,0 +1,133 @@ +package com.futo.platformplayer.states + +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.api.http.server.ManagedHttpServer +import com.futo.platformplayer.developer.DeveloperEndpoints +import com.futo.platformplayer.engine.exceptions.ScriptExecutionException +import com.futo.platformplayer.logging.Logger +import kotlin.system.measureTimeMillis + +/*** + * Used for developer related calls + */ +class StateDeveloper { + private var _server : ManagedHttpServer? = null; + + var currentDevID: String? = null + private set; + + private var _devLogsIndex: Int = 0; + private val _devLogs: MutableList = mutableListOf(); + + fun initializeDev(id: String) { + currentDevID = id; + synchronized(_devLogs) { + _devLogs.clear(); + } + } + inline fun handleDevCall(devId: String, contextName: String, printResult: Boolean = false, handle: ()->T): T { + var resp: T? = null; + + val time = measureTimeMillis { + try { + resp = handle(); + } + catch (castEx: ClassCastException) { + Logger.e("StateDeveloper", "Wrapped Exception: " + castEx.message, castEx); + val exMsg = + "Call [${contextName}] returned incorrect type. Expected [${T::class.simpleName}].\nCastException: ${castEx.message}"; + logDevException(devId, exMsg); + throw castEx; + } + catch (ex: ScriptExecutionException) { + Logger.e("StateDeveloper", "Wrapped Exception: " + ex.message, ex); + logDevException( + devId, + "Call [${contextName}] failed due to: (${ex::class.simpleName}) ${ex.message}" + + (if(ex.stack != null) "\n" + ex.stack else "") + ); + throw ex; + } + catch (ex: Throwable) { + Logger.e("StateDeveloper", "Wrapped Exception: " + ex.message, ex); + logDevException( + devId, + "Call [${contextName}] failed due to: (${ex::class.simpleName}) ${ex.message}" + ); + throw ex; + } + } + var printValue = ""; + if(printResult) { + if(resp is Boolean) + printValue = resp.toString(); + else if(resp is List<*>) + printValue = (resp as List<*>).size.toString(); + } + + logDevInfo(devId, "Call [${contextName}] succesful [${time}ms] ${printValue}"); + return resp!!; + } + fun logDevException(devId: String, msg: String) { + currentDevID.let { + if(it == devId) + synchronized(_devLogs) { + _devLogsIndex++; + _devLogs.add(DevLog(_devLogsIndex, devId, "EXCEPTION", msg)); + } + } + } + fun logDevInfo(devId: String, msg: String) { + currentDevID.let { + if(it == devId) + synchronized(_devLogs) { + _devLogsIndex++; + _devLogs.add(DevLog(_devLogsIndex, devId, "INFO", msg)); + } + } + } + fun getLogs(startIndex: Int) : List { + synchronized(_devLogs) { + val index = _devLogs.indexOfFirst { it.id == startIndex }; + return _devLogs.subList(index + 1, _devLogs.size); + } + } + + + fun runServer() { + if(_server != null) + return; + UIDialogs.toast("DevServer Booted"); + _server = ManagedHttpServer(11337).apply { + this.addBridgeHandlers(DeveloperEndpoints(StateApp.instance.context), "dev"); + }; + _server?.start(); + } + fun stopServer() { + _server?.stop(); + _server = null; + } + + + companion object { + const val DEV_ID = "DEV"; + + private var _instance : StateDeveloper? = null; + val instance : StateDeveloper + get(){ + if(_instance == null) + _instance = StateDeveloper(); + return _instance!!; + }; + + fun finish() { + _instance?.let { + _instance = null; + it._server?.stop(); + } + } + } + + @kotlinx.serialization.Serializable + data class DevLog(val id: Int, val devId: String, val type: String, val log: String); +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt b/app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt new file mode 100644 index 00000000..a689a6a8 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt @@ -0,0 +1,396 @@ +package com.futo.platformplayer.states + +import android.os.StatFs +import com.futo.platformplayer.R +import com.futo.platformplayer.Settings +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.exceptions.AlreadyQueuedException +import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.downloads.PlaylistDownloadDescriptor +import com.futo.platformplayer.downloads.VideoLocal +import com.futo.platformplayer.downloads.VideoDownload +import com.futo.platformplayer.downloads.VideoExport +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.DiskUsage +import com.futo.platformplayer.models.Playlist +import com.futo.platformplayer.models.PlaylistDownloaded +import com.futo.platformplayer.services.DownloadService +import com.futo.platformplayer.services.ExportingService +import com.futo.platformplayer.stores.* +import com.futo.platformplayer.stores.v2.ManagedStore +import okhttp3.internal.platform.Platform +import java.io.File + +/*** + * Used to maintain downloads + */ +class StateDownloads { + private val _downloadsDirectory: File = FragmentedStorage.getOrCreateDirectory("downloads"); + private val _downloadsStat = StatFs(_downloadsDirectory.absolutePath); + + private val _downloaded = FragmentedStorage.storeJson("downloaded") + .load() + .apply { afterLoadingDownloaded(this) }; + private val _downloading = FragmentedStorage.storeJson("downloading") + .load().apply { + for(video in this.getItems()) + video.changeState(VideoDownload.State.QUEUED); + }; + private val _downloadPlaylists = FragmentedStorage.storeJson("playlistDownloads") + .load(); + + private val _exporting = FragmentedStorage.storeJson("exporting") + .load(); + + private lateinit var _downloadedSet: HashSet; + + val onExportsChanged = Event0(); + val onDownloadsChanged = Event0(); + val onDownloadedChanged = Event0(); + + private fun afterLoadingDownloaded(v: ManagedStore) { + _downloadedSet = HashSet(v.getItems().map { it.id }); + } + + fun getTotalUsage(reload: Boolean): DiskUsage { + if(reload) + _downloadsStat.restat(_downloadsDirectory.absolutePath); + val usage = _downloadsDirectory.listFiles()?.sumOf { it.length() } ?: 0; + val available = _downloadsStat.availableBytes; + return DiskUsage(usage, available); + } + + fun getCachedVideo(id: PlatformID): VideoLocal? { + return _downloaded.findItem { it.id.equals(id) }; + } + fun updateCachedVideo(vid: VideoLocal) { + Logger.i("StateDownloads", "Updating local video ${vid.name}"); + _downloaded.save(vid); + onDownloadedChanged.emit(); + } + fun deleteCachedVideo(id: PlatformID) { + Logger.i("StateDownloads", "Deleting local video ${id.value}"); + val downloaded = getCachedVideo(id); + if(downloaded != null) { + synchronized(_downloadedSet) { + _downloadedSet.remove(id); + } + _downloaded.delete(downloaded); + } + onDownloadedChanged.emit(); + } + + fun isDownloaded(id: PlatformID): Boolean { + synchronized(_downloadedSet) { + return _downloadedSet.contains(id); + } + } + + fun getCachedPlaylists(): List { + return _downloadPlaylists.getItems() + .map { Pair(it, StatePlaylists.instance.getPlaylist(it.id)) } + .filter { it.second != null } + .map { PlaylistDownloaded(it.first, it.second!!) } + .toList(); + } + fun hasCachedPlaylist(playlistId: String): Boolean { + return _downloadPlaylists.hasItem { it.id == playlistId }; + } + fun getCachedPlaylist(playlistId: String): PlaylistDownloaded? { + val descriptor = getPlaylistDownload(playlistId) ?: return null; + val playlist = StatePlaylists.instance.getPlaylist(playlistId) ?: return null; + return PlaylistDownloaded(descriptor, playlist); + } + fun getPlaylistDownload(playlistId: String): PlaylistDownloadDescriptor? { + return _downloadPlaylists.findItem { it.id == playlistId }; + } + fun deleteCachedPlaylist(id: String) { + val pdl = getPlaylistDownload(id); + if(pdl != null) + _downloadPlaylists.delete(pdl); + getDownloading().filter { it.groupType == VideoDownload.GROUP_PLAYLIST && it.groupID == id } + .forEach { removeDownload(it) }; + getDownloadedVideos().filter { it.groupType == VideoDownload.GROUP_PLAYLIST && it.groupID == id } + .forEach { deleteCachedVideo(it.id) }; + } + + fun getDownloadedVideos(): List { + return _downloaded.getItems(); + } + + fun getDownloadPlaylists(): List { + return _downloadPlaylists.getItems(); + } + fun isPlaylistCached(id: String): Boolean { + return getDownloadPlaylists().any{it.id == id}; + } + + fun getDownloading(): List { + return _downloading.getItems(); + } + fun updateDownloading(download: VideoDownload) { + _downloading.save(download, false, true); + } + + + fun removeDownload(download: VideoDownload) { + download.isCancelled = true; + _downloading.delete(download); + onDownloadsChanged.emit(); + } + + fun checkForDownloadsTodos() { + val hasPlaylistChanged = checkForOutdatedPlaylists(); + val hasDownloads = _downloading.hasItems(); + + if((hasPlaylistChanged || hasDownloads) && Settings.instance.downloads.shouldDownload()) + StateApp.withContext { + DownloadService.getOrCreateService(it); + } + } + fun checkForOutdatedPlaylists(): Boolean { + var hasChanged = false; + val playlistsDownloaded = getCachedPlaylists(); + for(playlist in playlistsDownloaded) { + val playlistDownload = getPlaylistDownload(playlist.playlist.id) ?: continue; + + if(playlist.playlist.videos.any{ getCachedVideo(it.id) == null }) { + Logger.i(TAG, "Found new videos on playlist [${playlist.playlist.name}]"); + continueDownload(playlistDownload, playlist.playlist); + hasChanged = true; + } + } + return hasChanged; + } + + fun continueDownload(playlistDownload: PlaylistDownloadDescriptor, playlist: Playlist) { + var hasNew = false; + for(item in playlist.videos) { + val existing = getCachedVideo(item.id); + if(existing == null) { + val ongoingDownload = getDownloading().find { it.id.value == item.id.value && it.id.value != null }; + if(ongoingDownload != null) { + Logger.i(TAG, "New playlist video (already downloading) ${item.name}"); + ongoingDownload.groupID = playlist.id; + ongoingDownload.groupType = VideoDownload.GROUP_PLAYLIST; + } + else { + Logger.i(TAG, "New playlist video ${item.name}"); + download(VideoDownload(item, playlistDownload.targetPxCount, playlistDownload.targetBitrate) + .withGroup(VideoDownload.GROUP_PLAYLIST, playlist.id), false); + hasNew = true; + } + } + else { + Logger.i(TAG, "New playlist video (already downloaded) ${item.name}"); + if(existing.groupID == null) { + existing.groupID = playlist.id; + existing.groupType = VideoDownload.GROUP_PLAYLIST; + synchronized(_downloadedSet) { + _downloadedSet.add(existing.id); + } + _downloaded.save(existing); + } + } + } + if(playlist.videos.isNotEmpty() && Settings.instance.downloads.shouldDownload()) { + if(hasNew) { + UIDialogs.toast("Downloading [${playlist.name}]") + StateApp.withContext { + DownloadService.getOrCreateService(it); + } + } + onDownloadsChanged.emit(); + } + } + fun download(playlist: Playlist, targetPixelcount: Long?, targetBitrate: Long?) { + val playlistDownload = PlaylistDownloadDescriptor(playlist.id, targetPixelcount, targetBitrate); + _downloadPlaylists.save(playlistDownload); + continueDownload(playlistDownload, playlist); + } + fun download(video: IPlatformVideo, targetPixelcount: Long?, targetBitrate: Long?) { + download(VideoDownload(video, targetPixelcount, targetBitrate)); + } + fun download(video: IPlatformVideoDetails, videoSource: IVideoUrlSource?, audioSource: IAudioUrlSource?, subtitleSource: SubtitleRawSource?) { + download(VideoDownload(video, videoSource, audioSource, subtitleSource)); + } + + private fun download(videoState: VideoDownload, notify: Boolean = true) { + val shortName = if(videoState.name.length > 23) + videoState.name.substring(0, 20) + "..."; + else + videoState.name; + + try { + validateDownload(videoState); + _downloading.save(videoState); + + + if(notify) { + if(Settings.instance.downloads.shouldDownload()) { + UIDialogs.toast("Downloading [${shortName}]") + StateApp.withContext { + DownloadService.getOrCreateService(it); + } + onDownloadsChanged.emit(); + } + else { + UIDialogs.toast("Registered [${shortName}]\n(Can't download now)"); + } + } + } + catch(ex: Throwable) { + Logger.e(TAG, "Failed to start download", ex); + StateApp.withContext { + UIDialogs.showDialog( + it, + R.drawable.ic_error, + "Failed to start download due to:\n${ex.message}", null, null, + 0, + UIDialogs.Action("Ok", {}, UIDialogs.ActionStyle.PRIMARY) + ); + } + } + } + private fun validateDownload(videoState: VideoDownload) { + if(_downloading.hasItem { it.videoEither.url == videoState.videoEither.url }) + throw IllegalStateException("Video [${videoState.name}] is already queued for dowload"); + + val existing = getCachedVideo(videoState.id); + if(existing != null) { + //Verify for better video + val targetPx = if(videoState.targetPixelCount != null) + videoState.targetPixelCount!!.toInt(); + else if(videoState.videoSource != null) + videoState.videoSource!!.width * videoState.videoSource!!.height; + else + null; + if(targetPx != null) { + val bestExistingVideo = existing.videoSource.maxBy { it.width * it.height }; + val bestPx = bestExistingVideo.height * bestExistingVideo.width; + if (bestPx.toFloat() / targetPx >= 0.85f) + throw IllegalStateException("A higher resolution video source is already downloaded"); + } + + //Verify for better bitrate + val targetBitrate = if(videoState.targetBitrate != null) + videoState.targetBitrate!!.toInt(); + else if(videoState.audioSource != null) + videoState.audioSource!!.bitrate; + else + null; + if(targetBitrate != null) { + val bestExistingAudio = existing.audioSource.maxBy { it.bitrate }; + if(bestExistingAudio.bitrate / targetBitrate >= 0.85f) + throw IllegalStateException("A higher bitrate audio source is already downloaded"); + } + } + } + + fun cleanupDownloads(): Pair { + val expected = getDownloadedVideos(); + val validFiles = HashSet(expected.flatMap { it.videoSource.map { it.filePath } + it.audioSource.map { it.filePath } }); + + var totalDeleted: Long = 0; + var totalDeletedCount = 0; + for(file in _downloadsDirectory.listFiles()) { + val absUrl = file.absolutePath; + if(!validFiles.contains(absUrl)) { + Logger.i("StateDownloads", "Deleting unresolved ${file.name}"); + totalDeletedCount++; + totalDeleted += file.length(); + file.delete(); + } + } + return Pair(totalDeletedCount, totalDeleted); + } + + fun getDownloadsDirectory(): File{ + return _downloadsDirectory; + } + + + + //Export + fun getExporting(): List { + return _exporting.getItems(); + } + fun checkForExportTodos() { + if(_exporting.hasItems()) { + StateApp.withContext { + ExportingService.getOrCreateService(it); + } + } + } + + fun validateExport(export: VideoExport) { + if(_exporting.hasItem { it.videoLocal.url == export.videoLocal.url }) + throw AlreadyQueuedException("Video [${export.videoLocal.name}] is already queued for export"); + } + fun export(videoLocal: VideoLocal, videoSource: LocalVideoSource?, audioSource: LocalAudioSource?, subtitleSource: LocalSubtitleSource?, notify: Boolean = true) { + val shortName = if(videoLocal.name.length > 23) + videoLocal.name.substring(0, 20) + "..."; + else + videoLocal.name; + + val videoExport = VideoExport(videoLocal, videoSource, audioSource, subtitleSource); + + try { + validateExport(videoExport); + _exporting.save(videoExport); + + if(notify) { + if(videoSource == null) + UIDialogs.toast("Exporting [${shortName}]\nIn your music directory under Grayjay"); + else + UIDialogs.toast("Exporting [${shortName}]\nIn your movies directory under Grayjay"); + StateApp.withContext { ExportingService.getOrCreateService(it) }; + onExportsChanged.emit(); + } + } + catch (ex: AlreadyQueuedException) { + Logger.e(TAG, "File is already queued for export.", ex); + StateApp.withContext { ExportingService.getOrCreateService(it) }; + } + catch(ex: Throwable) { + StateApp.withContext { + UIDialogs.showDialog( + it, + R.drawable.ic_error, + "Failed to start export due to:\n${ex.message}", null, null, + 0, + UIDialogs.Action("Ok", {}, UIDialogs.ActionStyle.PRIMARY) + ); + } + } + } + + + fun removeExport(export: VideoExport) { + _exporting.delete(export); + export.isCancelled = true; + onExportsChanged.emit(); + } + + companion object { + const val TAG = "StateDownloads"; + + private var _instance : StateDownloads? = null; + val instance : StateDownloads + get(){ + if(_instance == null) + _instance = StateDownloads(); + return _instance!!; + }; + + fun finish() { + _instance?.let { + _instance = null; + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StateMeta.kt b/app/src/main/java/com/futo/platformplayer/states/StateMeta.kt new file mode 100644 index 00000000..2b9ae9fb --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/states/StateMeta.kt @@ -0,0 +1,34 @@ +package com.futo.platformplayer.states + +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.StringHashSetStorage + +class StateMeta { + val hiddenVideos = FragmentedStorage.get("hiddenVideos"); + + fun isVideoHidden(videoUrl: String) : Boolean { + return hiddenVideos.contains(videoUrl); + } + fun addHiddenVideo(videoUrl: String) { + hiddenVideos.addDistinct(videoUrl); + } + fun removeHiddenVideo(videoUrl: String) { + hiddenVideos.remove(videoUrl); + } + + companion object { + private var _instance : StateMeta? = null; + val instance : StateMeta + get(){ + if(_instance == null) + _instance = StateMeta(); + return _instance!!; + }; + + fun finish() { + _instance?.let { + _instance = null; + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePayment.kt b/app/src/main/java/com/futo/platformplayer/states/StatePayment.kt new file mode 100644 index 00000000..862121aa --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/states/StatePayment.kt @@ -0,0 +1,37 @@ +package com.futo.platformplayer.states + +import com.futo.futopay.PaymentState +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.StringStorage + +const val isTestingPayment = false; +class StatePayment : PaymentState(if(!isTestingPayment) VERIFICATION_PUBLIC_KEY else VERIFICATION_PUBLIC_KEY_TESTING) { + override val isTesting: Boolean get() = isTestingPayment; + + override fun savePaymentKey(licenseKey: String, licenseActivation: String) { + FragmentedStorage.get("paymentLicenseKey").setAndSave(licenseKey); + FragmentedStorage.get("paymentLicenseActivation").setAndSave(licenseActivation); + } + + override fun getPaymentKey(): Pair { + return Pair(FragmentedStorage.get("paymentLicenseKey").value, FragmentedStorage.get("paymentLicenseActivation").value); + } + + companion object { + private val VERIFICATION_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzJqqETLa42xw4AfbNOLQolMdMiGgg8DAC4RXEcH4/gytLhaqp1XsjiiMkADi1C7sDtGj6kOuAuQkqXQKpZ2dJSZsO+GPyop6DmgfAM6MQgOgFUpwsb3Lt3SvskJcls8MeOC+jg+GjjcuJI8qOfYevj4/7wAOpqzAwocTYnJivlK5nrC+qNtUC2HZX93OVu69aU5yvA1SQe9GiiU7vBld+CbzHxTcABCK/THu/BpLtGx0M7W3HNMKK1Z79dopCL9ZZWbWdkGDY8Zf39Gn/WVrs5elBvPzU+AfNYty77vx2r+sKgyohlbz4KVYpnw8HfawKcwuRE/GUyD3F2hUcXy8dQIDAQAB"; + private val VERIFICATION_PUBLIC_KEY_TESTING = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqyDuxsRtD5gmBoLCNoZa" + + "XSRTwyUxgzcPHzLZkvomXVSQqzD+3aOKngcTKAZ83rm4GvoyMlBukxQMLShannSx" + + "k8GQGTCT7VStQKNc4lKVER5ASB6aEaypaFMIYI3rXN1xLF1LqY/j7cu5GgMsvAuU" + + "VYFBexYFF6xcC5JDBZW6Pw/KYoJm3rswFixjPMGESmZRFCjjdAkHk47BhRPFBlvz" + + "wv9Ez1stdHcTpa/odEXIeJWIsZk9DHtCNCZyt6B6FXojVzrXsF2TxCNHGcHhlX43" + + "ALgQikiRcof1FsxoewTQhjLwMiDqB02mHCdRxssdnW3xadqyK678kQKfoIB1KB2N" + + "/QIDAQAB"; + private var _instance : StatePayment? = null; + val instance : StatePayment + get(){ + if(_instance == null) + _instance = StatePayment(); + return _instance!!; + }; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt new file mode 100644 index 00000000..ca901261 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt @@ -0,0 +1,875 @@ +package com.futo.platformplayer.states + +import android.content.Context +import androidx.collection.LruCache +import com.futo.platformplayer.R +import com.futo.platformplayer.Settings +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.IPluginSourced +import com.futo.platformplayer.api.media.PlatformClientPool +import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException +import com.futo.platformplayer.api.media.models.FilterGroup +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.ResultCapabilities +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails +import com.futo.platformplayer.api.media.models.contents.PlatformContentPlaceholder +import com.futo.platformplayer.api.media.models.live.ILiveChatWindowDescriptor +import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent +import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker +import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.platforms.js.DevJSClient +import com.futo.platformplayer.api.media.platforms.js.JSClient +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.api.media.structures.* +import com.futo.platformplayer.awaitFirstNotNullDeferred +import com.futo.platformplayer.constructs.BatchedTaskHandler +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.getNowDiffDays +import com.futo.platformplayer.getNowDiffSeconds +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.ImageVariable +import com.futo.platformplayer.stores.* +import kotlinx.coroutines.* +import okhttp3.internal.concat +import java.time.OffsetDateTime +import kotlin.streams.toList + +/*** + * Used to interact with sources/clients + */ +class StatePlatform { + private val TAG = "StatePlatform"; + private val VIDEO_CACHE = 1024 * 1024 * 10; + + private val _scope = CoroutineScope(Dispatchers.IO); + + //Caches + private data class CachedPlatformContent(val video: IPlatformContentDetails, val creationTime: OffsetDateTime = OffsetDateTime.now()); + private val _cacheExpirationSeconds = 60 * 5; + private val _cache : LruCache = LruCache(VIDEO_CACHE); + + //Clients + private val _enabledClientsPersistent = FragmentedStorage.get("enabledClients"); + private val _platformOrderPersistent = FragmentedStorage.get("platformOrder"); + private val _clientsLock = Object(); + private val _availableClients : ArrayList = ArrayList(); + private val _enabledClients : ArrayList = ArrayList(); + + private val _clientPools: HashMap = hashMapOf(); + + private val _primaryClientPersistent = FragmentedStorage.get("primaryClient"); + private var _primaryClientObj : IPlatformClient? = null; + val primaryClient : IPlatformClient get() = _primaryClientObj ?: throw IllegalStateException("PlatformState not yet initialized"); + + + private val _icons : HashMap = HashMap(); + + val hasClients: Boolean get() = _availableClients.size > 0; + + val onSourceDisabled = Event1(); + + val onDevSourceChanged = Event0(); + + //TODO: Remove after verifying that enabled clients are already in persistent order + val platformOrder get() = _platformOrderPersistent.values.toList(); + + //Batched Requests + private val _batchTaskGetVideoDetails: BatchedTaskHandler = BatchedTaskHandler(_scope, + { url -> + Logger.i(StatePlatform::class.java.name, "Fetching video details [${url}]"); + _enabledClients.find { it.isContentDetailsUrl(url) }?.getContentDetails(url) + ?: throw NoPlatformClientException("No client enabled that supports this url ($url)"); + }, + { + if(!Settings.instance.browsing.videoCache) + return@BatchedTaskHandler null; + else { + val cached = synchronized(_cache) { _cache.get(it); } ?: return@BatchedTaskHandler null; + if (cached.creationTime.getNowDiffSeconds() > _cacheExpirationSeconds) { + Logger.i(TAG, "Invalidated cache for [${it}]"); + synchronized(_cache) { + _cache.remove(it); + } + return@BatchedTaskHandler null; + } + return@BatchedTaskHandler cached.video; + } + }, + { para, result -> + if(!Settings.instance.browsing.videoCache || (result is IPlatformVideo && result.isLive)) + return@BatchedTaskHandler + else { + Logger.i(TAG, "Caching [${para}]"); + if (result.datetime == null || result.datetime!! < OffsetDateTime.now()) + synchronized(_cache) { + _cache.put(para, CachedPlatformContent(result)) + } + } + }); + + constructor() { + onSourceDisabled.subscribe { + synchronized(_cache) { + for(item in _cache.snapshot()) { + if(item.value.video is IPluginSourced) + if(it.id == (item.value.video as IPluginSourced).sourceConfig.id) { + Logger.i(TAG, "Removing [${item.value.video.name}] from cache because plugin disabled"); + _cache.remove(item.key); + + } + } + } + }; + } + + + suspend fun updateAvailableClients(context: Context, reloadPlugins: Boolean = false) { + if(reloadPlugins) + StatePlugins.instance.reloadPluginFile(); + withContext(Dispatchers.IO) { + var enabled: Array; + synchronized(_clientsLock) { + for(enabled in _enabledClients) { + enabled.disable(); + onSourceDisabled.emit(enabled); + } + + _enabledClients.clear(); + _availableClients.clear(); + //_availableClients.add(YoutubeClient()); + //_availableClients.add(OdyseeClient()); + + _icons.clear(); + _icons[StateDeveloper.DEV_ID] = ImageVariable(null, R.drawable.ic_security_red); + + StatePlugins.instance.updateEmbeddedPlugins(context); + StatePlugins.instance.installMissingEmbeddedPlugins(context); + + for(plugin in StatePlugins.instance.getPlugins()) { + + _icons[plugin.config.id] = StatePlugins.instance.getPluginIconOrNull(plugin.config.id) ?: + ImageVariable(plugin.config.absoluteIconUrl, null); + + _availableClients.add(JSClient(context, plugin)); + } + + if(_availableClients.distinctBy { it.id }.count() < _availableClients.size) + throw IllegalStateException("Attempted to add 2 clients with the same ID"); + + enabled = _enabledClientsPersistent.getAllValues() + .filter { _availableClients.any { ac -> ac.id == it } } + .toTypedArray(); + if(enabled.isEmpty()) + enabled = StatePlugins.instance.getEmbeddedSourcesDefault(context) + .filter { id -> _availableClients.any { it.id == id } } + .toTypedArray(); + + + val primary = _primaryClientPersistent.value; + if(primary.isNullOrEmpty() || primary == StateDeveloper.DEV_ID) + selectPrimaryClient(enabled.firstOrNull() ?: _availableClients.first().id); + else if(!_availableClients.any { it.id == primary }) + selectPrimaryClient(_availableClients.firstOrNull()?.id!!); + else + selectPrimaryClient(primary); + + if(!enabled.any { it == primaryClient.id }) + enabled = enabled.concat(primaryClient.id); + } + selectClients(*enabled); + }; + } + + fun isClientEnabled(id: String): Boolean { + synchronized(_clientsLock) { + return _enabledClients.any { it.id == id }; + } + } + fun isClientEnabled(client: IPlatformClient): Boolean { + synchronized (_clientsLock) { + return _enabledClients.contains(client); + } + } + + fun getAvailableClients(): List { + synchronized(_clientsLock) { + return _availableClients.toList(); + } + } + fun getEnabledClients(): List { + synchronized(_clientsLock) { + return _enabledClients.toList(); + } + } + + //TODO: getEnabledClients should already be ordered, remove after verify that that is the case + fun getSortedEnabledClient(): List { + synchronized(_clientsLock) { + val enabledClients = _enabledClients; + val orderedSources = platformOrder.mapNotNull { order -> + enabledClients.firstOrNull { it.name == order } + } + + val remainingSources = enabledClients.filter { it !in orderedSources } + return orderedSources + remainingSources; + } + } + fun getClientOrNullByUrl(url: String): IPlatformClient? { + return getChannelClientOrNull(url) ?: getPlaylistClientOrNull(url) ?: getContentClientOrNull(url); + } + fun getClientOrNull(id: String): IPlatformClient? { + synchronized(_clientsLock) { + return _availableClients.find { it.id == id }; + } + } + fun getClient(id: String): IPlatformClient { + return getClientOrNull(id) ?: throw IllegalArgumentException("Client with id $id does not exist"); + } + fun getClientPooled(parentClient: IPlatformClient, capacity: Int): IPlatformClient { + val pool = synchronized(_clientPools) { + if(!_clientPools.containsKey(parentClient)) + _clientPools[parentClient] = PlatformClientPool(parentClient).apply { + this.onDead.subscribe { client, pool -> + synchronized(_clientPools) { + if(_clientPools[parentClient] == pool) + _clientPools.remove(parentClient); + } + } + } + _clientPools[parentClient]!!; + }; + return pool.getClient(capacity); + } + fun getClientsByClaimType(claimType: Int): List { + return getEnabledClients().filter { it.isClaimTypeSupported(claimType) }; + } + fun getClientByClaimTypeOrNull(claimType: Int): IPlatformClient? { + return getEnabledClients().firstOrNull { it.isClaimTypeSupported(claimType) }; + } + fun getDevClient() : DevJSClient? { + return getClientOrNull(StateDeveloper.DEV_ID) as DevJSClient?; + } + + fun getPlatformIcon(type: String?) : ImageVariable? { + if(type == null) + return null; + if(_icons.containsKey(type)) + return _icons[type]; + return null; + } + + fun setPlatformOrder(platformOrder: List) { + _platformOrderPersistent.values.clear(); + _platformOrderPersistent.values.addAll(platformOrder); + _platformOrderPersistent.save(); + } + + suspend fun reloadClient(context: Context, id: String) : JSClient? { + return withContext(Dispatchers.IO) { + val client = getClient(id); + if (client !is JSClient) + return@withContext null; //TODO: Error? + + Logger.i(TAG, "Reloading plugin ${client.name}"); + + val newClient = if (client is DevJSClient) + client.recreate(context); + else + JSClient(context, + StatePlugins.instance.getPlugin(id) + ?: throw IllegalStateException("Client existed, but plugin config didn't") + ); + + synchronized(_clientsLock) { + if (_enabledClients.contains(client)) { + _enabledClients.remove(client); + client.disable(); + onSourceDisabled.emit(client); + newClient.initialize(); + _enabledClients.add(newClient); + } + if (_primaryClientObj == client) + _primaryClientObj = newClient; + + _availableClients.removeIf { it.id == id }; + _availableClients.add(newClient); + } + return@withContext newClient; + }; + } + + /** + * Selects the enabled clients, meaning all clients that data is actively requested from. + * If a client is disabled, NO requests are made to said client + */ + suspend fun selectClients(vararg ids: String) { + withContext(Dispatchers.IO) { + synchronized(_clientsLock) { + val removed = _enabledClients.toMutableList(); + _enabledClients.clear(); + for (id in ids) { + val client = getClient(id); + try { + if (!removed.removeIf { it == client }) + client.initialize(); + _enabledClients.add(client); + } + catch(ex: Exception) { + Logger.e(TAG, "Plugin ${client.name} failed to load\n${ex.message}", ex) + UIDialogs.toast("Plugin ${client.name} failed to load\n${ex.message}"); + } + } + _enabledClientsPersistent.set(*ids); + _enabledClientsPersistent.save(); + + for (oldClient in removed) { + oldClient.disable(); + onSourceDisabled.emit(oldClient); + } + } + }; + } + + /** + * Selects the primary client, meaning the first target for requests. + * At the moment, since multi-client requests are not yet implemented, this is the goto client. + */ + fun selectPrimaryClient(id: String) { + synchronized(_clientsLock) { + _primaryClientObj = getClient(id); + _primaryClientPersistent.setAndSave(id); + } + } + + fun getHome(): IPager { + Logger.i(TAG, "Platform - getHome"); + var clientIdsOngoing = mutableListOf(); + val clients = getSortedEnabledClient().filter { if (it is JSClient) it.enableInHome else true }; + + StateApp.instance.scopeOrNull?.let { + it.launch(Dispatchers.Default) { + try { + delay(5000); + val slowClients = synchronized(clientIdsOngoing) { + return@synchronized clients.filter { clientIdsOngoing.contains(it.id) }; + }; + for(client in slowClients) + UIDialogs.toast("${client.name} is still loading..\nConsider disabling it for Home", false); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to show toast for slow source.", e) + } + } + }; + + val pages = clients.parallelStream() + .map { + Logger.i(TAG, "getHome - ${it.name}") + synchronized(clientIdsOngoing) { + clientIdsOngoing.add(it.id); + } + val homeResult = it.getHome(); + synchronized(clientIdsOngoing) { + clientIdsOngoing.remove(it.id); + } + return@map homeResult; + } + .toList() + .associateWith { 1f }; + + val pager = MultiDistributionContentPager(pages); + pager.initialize(); + return pager; + } + suspend fun getHomeRefresh(scope: CoroutineScope): IPager { + Logger.i(TAG, "Platform - getHome (Refresh)"); + val clients = getSortedEnabledClient().filter { if (it is JSClient) it.enableInHome else true }; + + val deferred: List?>>> = clients.map { + return@map Pair(it, scope.async(Dispatchers.IO) { + try { + val searchResult = it.getHome(); + return@async searchResult; + } catch(ex: Throwable) { + Logger.e(TAG, "getHomeRefresh", ex); + return@async null; + } + }); + }.toList(); + + val finishedPager = deferred.map { it.second }.awaitFirstNotNullDeferred() ?: return EmptyPager(); + val toAwait = deferred.filter { it.second != finishedPager.first }; + return RefreshDistributionContentPager( + listOf(finishedPager.second), + toAwait.map { it.second }, + toAwait.map { PlaceholderPager(5, { PlatformContentPlaceholder(it.first.id) }) }); + } + + fun getHomePrimary(): IPager { + return primaryClient.getHome(); + } + + //Search + fun searchSuggestions(query: String): Array { + Logger.i(TAG, "Platform - searchSuggestions"); + return primaryClient.searchSuggestions(query); + } + + fun search(query: String, type: String? = null, sort: String? = null, filters: Map> = mapOf(), clientIds: List? = null): IPager { + Logger.i(TAG, "Platform - search"); + val pagers = mutableMapOf, Float>(); + val clients = if (clientIds != null) getSortedEnabledClient().filter { (if (it is JSClient) it.enableInSearch else true) && clientIds.contains(it.id) } + else getSortedEnabledClient().filter { if (it is JSClient) it.enableInSearch else true }; + + for (c in clients) { + Logger.i(TAG, "Client enabled for search: " + c.name) + } + + clients.parallelStream().forEach { + val searchCapabilities = it.getSearchCapabilities(); + val mappedFilters = filters.map { pair -> Pair(pair.key, pair.value.map { v -> searchCapabilities.filters.first { g -> g.idOrName == pair.key }.filters.first { f -> f.idOrName == v }.value }) }.toMap(); + pagers.put(it.search(query, type, sort, mappedFilters), 1f); + }; + + val pager = MultiDistributionContentPager(pagers); + pager.initialize(); + return pager; + } + fun searchPlaylist(query: String, type: String? = null, sort: String? = null, filters: Map> = mapOf(), clientIds: List? = null): IPager { + Logger.i(TAG, "Platform - search playlist"); + val pagers = mutableMapOf, Float>(); + val clients = if (clientIds != null) getSortedEnabledClient().filter { (if (it is JSClient) it.enableInSearch else true) && clientIds.contains(it.id) } + else getSortedEnabledClient().filter { if (it is JSClient) it.enableInSearch else true }; + + for (c in clients) { + Logger.i(TAG, "Client enabled for search: " + c.name) + } + + clients.filter { it.capabilities.hasSearchPlaylists }.parallelStream().forEach { + val searchCapabilities = it.getSearchCapabilities(); + val mappedFilters = filters.map { pair -> Pair(pair.key, pair.value.map { v -> searchCapabilities.filters.first { g -> g.idOrName == pair.key }.filters.first { f -> f.idOrName == v }.value }) }.toMap(); + pagers.put(it.searchPlaylists(query, type, sort, mappedFilters), 1f); + }; + + val pager = MultiDistributionContentPager(pagers); + pager.initialize(); + return pager; + } + suspend fun searchRefresh(scope: CoroutineScope, query: String, type: String? = null, sort: String? = null, filters: Map> = mapOf(), clientIds: List? = null): IPager { + Logger.i(TAG, "Platform - search (refresh)"); + val clients = + if (clientIds != null) getSortedEnabledClient().filter { (if (it is JSClient) it.enableInSearch else true) && clientIds.contains(it.id) } + else getSortedEnabledClient().filter { if (it is JSClient) it.enableInSearch else true }; + + for (c in clients) { + Logger.i(TAG, "Client enabled for search: " + c.name) + } + + val deferred: List?>>> = clients.map { + return@map Pair(it, scope.async(Dispatchers.IO) { + try { + val searchCapabilities = it.getSearchCapabilities(); + val mappedFilters = filters.map { pair -> Pair(pair.key, pair.value.map { v -> searchCapabilities.filters.first { g -> g.idOrName == pair.key }.filters.first { f -> f.idOrName == v }.value }) }.toMap(); + val searchResult = it.search(query, type, sort, mappedFilters); + return@async searchResult; + } catch(ex: Throwable) { + Logger.e(TAG, "searchRefresh", ex); + return@async null; + } + }); + }.toList(); + + val finishedPager = deferred.map { it.second }.awaitFirstNotNullDeferred() ?: return EmptyPager(); + val toAwait = deferred.filter { it.second != finishedPager.first }; + return RefreshDistributionContentPager( + listOf(finishedPager.second), + toAwait.map { it.second }, + toAwait.map { PlaceholderPager(5, { PlatformContentPlaceholder(it.first.id) }) }); + } + + fun searchChannel(channelUrl: String, query: String, type: String? = null, sort: String? = null, filters: Map> = mapOf(), clientIds: List? = null): IPager { + Logger.i(TAG, "Platform - search channel $channelUrl"); + + val pagers = mutableMapOf, Float>(); + val clients = if (clientIds != null) getSortedEnabledClient().filter { (if (it is JSClient) it.enableInSearch else true) && clientIds.contains(it.id) } + else getSortedEnabledClient().filter { if (it is JSClient) it.enableInSearch else true }; + + clients.parallelStream().forEach { + val searchCapabilities = it.getSearchCapabilities(); + val mappedFilters = filters.map { pair -> Pair(pair.key, pair.value.map { v -> searchCapabilities.filters.first { g -> g.idOrName == pair.key }.filters.first { f -> f.idOrName == v }.value }) }.toMap(); + + if (it.isChannelUrl(channelUrl)) { + pagers.put(it.searchChannelContents(channelUrl, query, type, sort, mappedFilters), 1f); + } + }; + + val pager = MultiDistributionContentPager(pagers); + pager.initialize(); + return pager; + } + + fun getCommonSearchCapabilities(clientIds: List): ResultCapabilities? { + try { + Logger.i(TAG, "Platform - getCommonSearchCapabilities"); + + val clients = getEnabledClients().filter { clientIds.contains(it.id) }; + val c = clients.firstOrNull() ?: return null; + val cap = c.getSearchCapabilities(); + + //var types = arrayListOf(); + var sorts = cap.sorts.toMutableList(); + var filters = cap.filters.toMutableList(); + + val sortsToRemove = arrayListOf(); + val filtersToRemove = arrayListOf(); + + for (i in 1 until clients.size) { + val clientSearchCapabilities = clients[i].getSearchCapabilities(); + + for (j in 0 until sorts.size) { + if (!clientSearchCapabilities.sorts.contains(sorts[j])) { + sortsToRemove.add(j); + } + } + + sorts = sorts.filterIndexed { index, _ -> index !in sortsToRemove }.toMutableList(); + + for (k in 0 until filters.size) { + val matchingFilterGroup = clientSearchCapabilities.filters.firstOrNull { f -> filters[k].idOrName == f.idOrName }; + if (matchingFilterGroup == null) { + filtersToRemove.add(k); + } else { + val currentFilterGroup = filters[k]; + filters[k] = FilterGroup(currentFilterGroup.name, currentFilterGroup.filters.filter { a -> matchingFilterGroup.filters.any { b -> a.idOrName == b.idOrName } } + , currentFilterGroup.isMultiSelect, currentFilterGroup.id); + } + } + + filters = filters.filterIndexed { index, _ -> index !in filtersToRemove }.toMutableList(); + } + + return ResultCapabilities(listOf(), sorts, filters); + } catch (e: Throwable) { + Logger.w(TAG, "Failed to get common search capabilities.", e); + return null; + } + } + + fun isSearchChannelsAvailable(): Boolean { + return getEnabledClients().any { it.capabilities.hasChannelSearch }; + } + fun searchChannels(query: String): IPager { + Logger.i(TAG, "Platform - searchChannels"); + val pagers = mutableMapOf, Float>(); + getSortedEnabledClient().parallelStream().forEach { + try { + if (it.capabilities.hasChannelSearch) + pagers.put(it.searchChannels(query), 1f); + } + catch(ex: Throwable) { + UIDialogs.toast("Failed search channels on [${it.name}]\n(${ex.message})"); + } + }; + if(pagers.isEmpty()) + return EmptyPager(); + + val pager = MultiDistributionChannelPager(pagers); + pager.initialize(); + return pager; + } + + + //Video + fun hasEnabledVideoClient(url: String) : Boolean = getEnabledClients().any { it.isContentDetailsUrl(url) }; + fun getContentClient(url: String) : IPlatformClient = getContentClientOrNull(url) + ?: throw NoPlatformClientException("No client enabled that supports this channel url (${url})"); + fun getContentClientOrNull(url: String) : IPlatformClient? = getEnabledClients().find { it.isContentDetailsUrl(url) }; + fun getContentDetails(url: String, forceRefetch: Boolean = false): Deferred { + Logger.i(TAG, "Platform - getContentDetails (${url})"); + if(forceRefetch) + clearContentDetailCache(url); + + return _batchTaskGetVideoDetails.execute(url); + } + fun clearContentDetailCache(url: String) { + if(_cache.get(url) != null) { + Logger.i(TAG, "Force clearing cache (${url})"); + _cache.remove(url); + } + } + + fun getPlaybackTracker(url: String): IPlaybackTracker? { + return getContentClientOrNull(url)?.getPlaybackTracker(url); + } + + fun hasEnabledChannelClient(url : String) : Boolean = getEnabledClients().any { it.isChannelUrl(url) }; + fun getChannelClient(url : String) : IPlatformClient = getChannelClientOrNull(url) + ?: throw NoPlatformClientException("No client enabled that supports this channel url (${url})"); + fun getChannelClientOrNull(url : String) : IPlatformClient? = getEnabledClients().find { it.isChannelUrl(url) }; + + fun getChannel(url: String, updateSubscriptions: Boolean = true): Deferred { + Logger.i(TAG, "Platform - getChannel"); + val channel = StateSubscriptions.instance.getSubscription(url); + if(channel != null) + return _scope.async { getChannelLive(url, updateSubscriptions) }; //_batchTaskGetChannel.execute(channel); + else + return _scope.async { getChannelLive(url, updateSubscriptions) }; + } + + fun getChannelContent(channelUrl: String, isSubscriptionOptimized: Boolean = false, usePooledClients: Int = 0): IPager { + Logger.i(TAG, "Platform - getChannelVideos"); + val baseClient = getChannelClient(channelUrl); + val clientCapabilities = baseClient.getChannelCapabilities(); + + val client = if(usePooledClients > 1) + getClientPooled(baseClient, usePooledClients); + else baseClient; + + var lastStream: OffsetDateTime? = null; + + val pagerResult: IPager; + if(!clientCapabilities.hasType(ResultCapabilities.TYPE_MIXED) && + clientCapabilities.hasType(ResultCapabilities.TYPE_VIDEOS) && + clientCapabilities.hasType(ResultCapabilities.TYPE_STREAMS)) { + val toQuery = mutableListOf(); + if(clientCapabilities.hasType(ResultCapabilities.TYPE_VIDEOS)) + toQuery.add(ResultCapabilities.TYPE_VIDEOS); + if(clientCapabilities.hasType(ResultCapabilities.TYPE_STREAMS)) + toQuery.add(ResultCapabilities.TYPE_STREAMS); + if(clientCapabilities.hasType(ResultCapabilities.TYPE_LIVE)) + toQuery.add(ResultCapabilities.TYPE_LIVE); + + if(isSubscriptionOptimized) { + val sub = StateSubscriptions.instance.getSubscription(channelUrl); + if(sub != null) { + val daysSinceLiveStream = sub.lastLiveStream.getNowDiffDays() + if(daysSinceLiveStream > 7) { + Logger.i(TAG, "Subscription [${channelUrl}] Last livestream > 7 days, skipping live streams [${daysSinceLiveStream} days ago]"); + toQuery.remove(ResultCapabilities.TYPE_LIVE); + } + if(daysSinceLiveStream > 14) { + Logger.i(TAG, "Subscription [${channelUrl}] Last livestream > 15 days, skipping streams [${daysSinceLiveStream} days ago]"); + toQuery.remove(ResultCapabilities.TYPE_STREAMS); + } + } + } + + //Merged pager + val pagers = toQuery + .parallelStream() + .map { + val results = client.getChannelContents(channelUrl, it, ResultCapabilities.ORDER_CHONOLOGICAL) ; + + when(it) { + ResultCapabilities.TYPE_STREAMS -> { + val streamResults = results.getResults(); + if(streamResults.size == 0) + lastStream = OffsetDateTime.MIN; + else + lastStream = results.getResults().firstOrNull()?.datetime; + } + } + return@map results; + } + .toList(); + + val pager = MultiChronoContentPager(pagers.toTypedArray()); + pager.initialize(); + pagerResult = pager; + } + else + pagerResult = client.getChannelContents(channelUrl, ResultCapabilities.TYPE_MIXED, ResultCapabilities.ORDER_CHONOLOGICAL); + + //Subscription optimization + val sub = StateSubscriptions.instance.getSubscription(channelUrl); + if(sub != null) { + var hasChanges = false; + val lastVideo = pagerResult.getResults().firstOrNull(); + + if(lastVideo?.datetime != null && sub.lastVideo.getNowDiffDays() != lastVideo.datetime!!.getNowDiffDays()) { + Logger.i(TAG, "Subscription [${channelUrl}] has new last video date [${lastVideo.datetime?.getNowDiffDays()} Days]"); + sub.lastVideo = lastVideo.datetime ?: sub.lastVideo; + hasChanges = true; + } + + if(lastStream != null && sub.lastLiveStream.getNowDiffDays() != lastStream!!.getNowDiffDays()) { + Logger.i(TAG, "Subscription [${channelUrl}] has new last stream date [${lastStream!!.getNowDiffDays()} Days]"); + sub.lastLiveStream = lastStream!!; + hasChanges = true; + } + + val now = OffsetDateTime.now(); + val firstPage = pagerResult.getResults().filter { it.datetime != null && it.datetime!! < now } + if(firstPage.size > 0) { + val newestVideoDays = firstPage[0].datetime?.getNowDiffDays()?.toInt() ?: 0; + val diffs = mutableListOf() + for(i in (firstPage.size - 1) downTo 1) { + val currentVideoDays = firstPage[i].datetime?.getNowDiffDays(); + val nextVideoDays = firstPage[i - 1].datetime?.getNowDiffDays(); + + if(currentVideoDays != null && nextVideoDays != null) { + val diff = nextVideoDays - currentVideoDays; + diffs.add(diff.toInt()); + } + } + val averageDiff = if(diffs.size > 0) + newestVideoDays.coerceAtLeast(diffs.average().toInt()) + else + newestVideoDays; + + if(sub.uploadInterval != averageDiff) { + Logger.i(TAG, "Subscription [${channelUrl}] has new upload interval [${averageDiff} Days]"); + sub.uploadInterval = averageDiff; + hasChanges = true; + } + } + + if(hasChanges) + StateSubscriptions.instance.saveSubscription(sub); + } + + return pagerResult; + } + + fun getChannelLive(url: String, updateSubscriptions: Boolean = true): IPlatformChannel { + val channel = getChannelClient(url).getChannel(url); + + if(updateSubscriptions && StateSubscriptions.instance.isSubscribed(channel)) + StateSubscriptions.instance.updateSubscriptionChannel(channel); + + return channel + } + + fun getChannelUrlByClaim(claimType: Int, claimValues: Map): String? { + val client = getClientByClaimTypeOrNull(claimType) ?: return null; + return client.getChannelUrlByClaim(claimType, claimValues); + } + fun resolveChannelUrlByClaimTemplates(claimType: Int, claimValues: Map): String? { + for(client in getClientsByClaimType(claimType).filter { it is JSClient }) { + val url = (client as JSClient).resolveChannelUrlByClaimTemplates(claimType, claimValues); + if(!url.isNullOrEmpty()) + return url; + } + return null; + } + + fun getPlaylistClientOrNull(url: String): IPlatformClient? = getEnabledClients().find { it.isPlaylistUrl(url) } + fun getPlaylistClient(url: String): IPlatformClient = getEnabledClients().find { it.isPlaylistUrl(url) } + ?: throw NoPlatformClientException("No client enabled that supports this playlist url (${url})"); + fun getPlaylist(url: String): IPlatformPlaylistDetails { + return getPlaylistClient(url).getPlaylist(url); + } + + //Comments + fun getComments(content: IPlatformContentDetails): IPager { + val client = getContentClient(content.url); + val pager = content.getComments(client); + + return pager ?: getComments(content.url); + } + fun getComments(url: String): IPager { + Logger.i(TAG, "Platform - getComments"); + val client = getContentClient(url); + if(!client.capabilities.hasGetComments) + return EmptyPager(); + + return client.getComments(url); + } + fun getSubComments(comment: IPlatformComment): IPager { + Logger.i(TAG, "Platform - getSubComments"); + val client = getContentClient(comment.contextUrl); + return client.getSubComments(comment); + } + + fun getLiveEvents(url: String): IPager? { + Logger.i(TAG, "Platform - getLiveChat"); + var client = getContentClient(url); + return client.getLiveEvents(url); + } + fun getLiveChatWindow(url: String): ILiveChatWindowDescriptor? { + Logger.i(TAG, "Platform - getLiveChat"); + var client = getContentClient(url); + return client.getLiveChatWindow(url); + } + + + fun injectDevPlugin(source: SourcePluginConfig, script: String): String? { + var devId: String? = null; + synchronized(_clientsLock) { + val enabledExisting = _enabledClients.filter { it is DevJSClient }; + val isEnabled = !enabledExisting.isEmpty() + val isPrimary = _primaryClientObj is DevJSClient; + + for (enabled in enabledExisting) { + enabled.disable(); + } + + //Remove existing dev clients + _enabledClients.removeIf { it is DevJSClient }; + _availableClients.removeIf { it is DevJSClient }; + + source.id = StateDeveloper.DEV_ID; + val newClient = DevJSClient(StateApp.instance.context, source, script); + devId = newClient.devID; + try { + StateDeveloper.instance.initializeDev(devId!!); + var didEnable = false; + if (isPrimary) { + _primaryClientObj = newClient; + _enabledClients.add(0, newClient); + newClient.initialize(); + didEnable = true; + } else if (isEnabled) { + _enabledClients.add(newClient); + if(!didEnable) { + newClient.initialize(); + didEnable = true; + } + } + _availableClients.add(newClient); + } catch (ex: Exception) { + Logger.e("StatePlatform", "Failed to initialize DevPlugin: ${ex.message}", ex); + StateDeveloper.instance.logDevException(devId!!, "Failed to initialize due to: ${ex.message}"); + } + } + onDevSourceChanged.emit(); + return devId; + } + + //TODO: Be careful with calling this unless you know for sure you're not gonna need the current app state anymore + fun disableAllClients() { + synchronized(_clientsLock) { + val enabledClients = _enabledClients; + _enabledClients.clear(); + for(enabled in enabledClients) { + Logger.i(TAG, "Disabling plugin [${enabled.name}]"); + try { + enabled.disable(); + } + catch (ex: Throwable) {} + } + } + } + + companion object { + private var _instance : StatePlatform? = null; + val instance : StatePlatform + get(){ + if(_instance == null) + _instance = StatePlatform(); + return _instance!!; + }; + + fun finish() { + _instance?.let { + _instance = null; + it._scope.cancel("PlatformState finished"); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt new file mode 100644 index 00000000..a2dfd6f9 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt @@ -0,0 +1,505 @@ +package com.futo.platformplayer.states + +import android.content.Context +import android.util.Log +import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist +import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.models.Playlist +import com.futo.platformplayer.services.MediaPlaybackService +import com.futo.platformplayer.video.PlayerManager +import com.google.android.exoplayer2.C +import com.google.android.exoplayer2.DefaultLoadControl +import com.google.android.exoplayer2.ExoPlayer +import com.google.android.exoplayer2.upstream.DefaultAllocator +import kotlin.random.Random + +/*** + * Used to keep track of queue and other player related stuff + */ +class StatePlayer { + private val MIN_BUFFER_DURATION = 10000; + private val MAX_BUFFER_DURATION = 60000; + private val MIN_PLAYBACK_START_BUFFER = 500; + private val MIN_PLAYBACK_RESUME_BUFFER = 1000; + private val BUFFER_SIZE = 1024 * 64; + + var isOpen : Boolean = false + private set; + + //Players + private var _exoplayer : PlayerManager? = null; + private var _thumbnailExoPlayer : PlayerManager? = null; + + //Video Status + var rotationLock : Boolean = false; + + val isPlaying: Boolean get() = _exoplayer?.player?.playWhenReady ?: false; + + //Queue + private val _queue = ArrayList(); + private var _queueShuffled: MutableList? = null; + private var _queueType = TYPE_QUEUE; + private var _queueName: String? = null; + private var _queuePosition = -1; + private var _queueRemoveOnFinish = false; + var queueFocused : Boolean = false + private set; + var queueRepeat: Boolean = false + private set; + var queueShuffle: Boolean = false + private set; + + val queueName: String get() = _queueName ?: _queueType; + + //Events + val onVideoChanging = Event1(); + val onQueueChanged = Event1(); + val onPlayerOpened = Event0(); + val onPlayerClosed = Event0(); + + var currentVideo: IPlatformVideoDetails? = null + private set; + + fun setCurrentlyPlaying(video: IPlatformVideoDetails?) { + currentVideo = video; + } + + + //Player Status + fun setPlayerOpen() { + isOpen = true; + onPlayerOpened.emit(); + } + fun setPlayerClosed() { + setCurrentlyPlaying(null); + isOpen = false; + clearQueue(); + onPlayerClosed.emit(); + closeMediaSession(); + } + + //Notifications + fun hasMediaSession() : Boolean { + return MediaPlaybackService.getService() != null; + } + fun startOrUpdateMediaSession(context: Context, videoUpdated: IPlatformVideoDetails?) { + MediaPlaybackService.getOrCreateService(context) { + it.updateMediaSession(videoUpdated); + }; + } + fun updateMediaSession(videoUpdated: IPlatformVideoDetails?) { + MediaPlaybackService.getService()?.updateMediaSession(videoUpdated); + } + fun updateMediaSessionPlaybackState(state: Int, pos: Long) { + MediaPlaybackService.getService()?.updateMediaSessionPlaybackState(state, pos); + } + fun closeMediaSession() { + MediaPlaybackService.getService()?.closeMediaSession(); + } + + //Queue Status + fun getQueueProgress(): Int { + synchronized(_queue) { + return _queuePosition; + } + } + fun getQueueLength() : Int { + synchronized(_queue) { + return _queue.size; + } + } + fun isInQueue(id : String) : Boolean { + synchronized(_queue) { + return _queue.any { it.id.value == id }; + } + } + + fun getQueueType() : String { + return _queueType; + } + fun getQueue() : List { + synchronized(_queue) { + val queueShuffled = _queueShuffled; + if (queueShuffle && queueShuffled != null) { + return queueShuffled.toList() + } else { + return _queue.toList() + } + } + } + + fun setQueueType(queueType : String) { + when(queueType) { + TYPE_QUEUE -> { + _queueRemoveOnFinish = true; + } + TYPE_WATCHLATER -> { + _queueRemoveOnFinish = true; + } + TYPE_PLAYLIST -> { + _queueRemoveOnFinish = false; + } + } + _queueType = queueType; + } + + fun setQueueRepeat(enabled: Boolean) { + synchronized(_queue) { + queueRepeat = enabled; + } + } + fun setQueueShuffle(shuffle: Boolean, excludeCurrent: Boolean = true) { + synchronized(_queue) { + queueShuffle = shuffle; + if (shuffle) { + createShuffledQueue(); + } else { + _queueShuffled = null; + } + + onQueueChanged.emit(false); + } + } + + private fun createShuffledQueue() { + val currentItem = getCurrentQueueItem(); + if (_queuePosition == -1 || currentItem == null) { + _queueShuffled = _queue.shuffled().toMutableList() + return; + } + + val nextItems = _queue.subList(Math.min(_queuePosition + 1, _queue.size - 1), _queue.size).shuffled(); + val previousItems = _queue.subList(0, _queuePosition).shuffled(); + _queueShuffled = (previousItems + currentItem + nextItems).toMutableList(); + } + + private fun addToShuffledQueue(video: IPlatformVideo) { + val isLastVideo = _queuePosition + 1 >= _queue.size; + if (isLastVideo) { + _queueShuffled?.add(video) + } else { + val indexToInsert = Random.nextInt(_queuePosition + 1, _queue.size) + _queueShuffled?.add(indexToInsert, video) + } + } + private fun removeFromShuffledQueue(video: IPlatformVideo) { + _queueShuffled?.remove(video); + } + + //Modify Queue + fun setQueue(videos: List, type: String, queueName: String? = null, focus: Boolean = false, shuffle: Boolean = false) { + synchronized(_queue) { + _queue.clear(); + setQueueType(type); + _queueName = queueName; + queueRepeat = false; + _queue.addAll(videos); + _queuePosition = 0; + queueFocused = focus; + queueShuffle = shuffle; + if (shuffle) { + createShuffledQueue(); + } + } + onQueueChanged.emit(true); + } + fun setPlaylist(playlist: IPlatformPlaylistDetails, toPlayIndex: Int = 0, focus: Boolean = false, shuffle: Boolean = false) { + synchronized(_queue) { + _queue.clear(); + setQueueType(TYPE_PLAYLIST); + _queueName = playlist.name; + _queue.addAll(playlist.contents.getResults()); + queueFocused = focus; + queueShuffle = shuffle; + if (shuffle) { + createShuffledQueue(); + } + _queuePosition = toPlayIndex; + } + playlist.id.value?.let { StatePlaylists.instance.didPlay(it); }; + + onQueueChanged.emit(true); + } + fun setPlaylist(playlist: Playlist, toPlayIndex: Int = 0, focus: Boolean = false, shuffle: Boolean = false) { + synchronized(_queue) { + _queue.clear(); + setQueueType(TYPE_PLAYLIST); + _queueName = playlist.name; + _queue.addAll(playlist.videos); + queueFocused = focus; + queueShuffle = shuffle; + if (shuffle) { + createShuffledQueue(); + } + _queuePosition = toPlayIndex; + } + StatePlaylists.instance.didPlay(playlist.id); + + onQueueChanged.emit(true); + } + fun setQueueWithPosition(videos: List, type: String, pos: Int, focus: Boolean = false) { + //TODO: Implement support for pagination + val index = if(videos.size <= pos) 0 else pos; + synchronized(_queue) { + _queue.clear(); + setQueueType(type); + _queue.addAll(videos); + queueShuffle = false; + _queuePosition = index; + queueFocused = focus; + } + onQueueChanged.emit(true); + } + fun setQueueWithExisting(videos: List, withFocus: Boolean = false) { + val currentItem = getCurrentQueueItem(); + val index = videos.indexOf(currentItem); + setQueueWithPosition(videos, _queueType, index, withFocus); + } + fun addToQueue(video: IPlatformVideo) { + synchronized(_queue) { + if(_queue.isEmpty()) { + setQueueType(TYPE_QUEUE); + currentVideo?.let { + _queue.add(it); + } + } + + _queue.add(video); + if (queueShuffle) { + addToShuffledQueue(video); + } + + if (_queuePosition < 0) { + _queuePosition = 0; + } + } + onQueueChanged.emit(true); + } + fun setQueuePosition(video: IPlatformVideo) { + synchronized(_queue) { + if (getCurrentQueueItem() == video) { + return; + } + + _queuePosition = getQueuePosition(video); + onVideoChanging.emit(video); + } + } + fun getQueuePosition(video: IPlatformVideo): Int { + synchronized(_queue) { + val queueShuffled = _queueShuffled; + return if (queueRepeat && queueShuffled != null) + queueShuffled.indexOf(video); + else + _queue.indexOf(video); + } + } + fun removeFromQueue(video: IPlatformVideo, shouldSwapCurrentItem: Boolean = false) { + synchronized(_queue) { + _queue.remove(video); + if (queueShuffle) { + removeFromShuffledQueue(video); + } + } + + onQueueChanged.emit(shouldSwapCurrentItem); + } + fun clearQueue() { + synchronized(_queue) { + _queue.clear(); + _queueShuffled = null; + queueShuffle = false; + _queuePosition = -1; + } + onQueueChanged.emit(false); + } + + //Queue Nav + fun getCurrentQueueItem(adjustIfNegative: Boolean = true) : IPlatformVideo? { + synchronized(_queue) { + val shuffledQueue = _queueShuffled; + val queue = if (queueShuffle && shuffledQueue != null) { + shuffledQueue; + } else { + _queue; + } + + if(adjustIfNegative && queue.isNotEmpty()) { + if(_queuePosition == -1) + return queue[0]; + else if(_queuePosition < queue.size) + return queue[_queuePosition]; + } else if(_queuePosition >= 0 && _queuePosition < queue.size) { + return queue[_queuePosition]; + } + } + return null; + } + + fun getNextQueueItem() : IPlatformVideo? { + synchronized(_queue) { + val shuffledQueue = _queueShuffled; + val queue = if (queueShuffle && shuffledQueue != null) { + shuffledQueue; + } else { + _queue; + } + + //Init Behavior + if(_queuePosition == -1 && queue.isNotEmpty()) + return queue[0]; + //Standard Behavior + if(_queuePosition + 1 < queue.size) + return queue[_queuePosition + 1]; + //Repeat Behavior (End of queue) + if(_queuePosition + 1 == queue.size && queue.isNotEmpty() && queueRepeat) + return queue[0]; + } + return null; + } + fun restartQueue() : IPlatformVideo? { + synchronized(_queue) { + _queuePosition = -1; + return nextQueueItem(); + } + }; + fun nextQueueItem(withoutRemoval: Boolean = false) : IPlatformVideo? { + synchronized(_queue) { + if (_queue.isEmpty()) + return null; + + val nextPosition: Int; + var isRepeat = false; + val lastItem = getCurrentQueueItem(false); + if(_queueRemoveOnFinish && !withoutRemoval && lastItem != null) { + _queue.remove(lastItem); + removeFromShuffledQueue(lastItem); + nextPosition = _queuePosition; + } else { + if (_queuePosition + 1 >= _queue.size) { + isRepeat = true; + nextPosition = 0; + } else { + nextPosition = _queuePosition + 1; + } + } + + if (_queue.isEmpty()) { + return null; + } + + if (isRepeat && !queueRepeat || isRepeat && _queue.size == 1) { + return null; + } + + _queuePosition = nextPosition + return getCurrentQueueItem(); + } + } + + fun prevQueueItem(withoutRemoval: Boolean = false) : IPlatformVideo? { + synchronized(_queue) { + if (_queue.size == 0) { + return null; + } + + val currentPos = _queuePosition; + + if(_queueRemoveOnFinish && !withoutRemoval) { + _queue.removeAt(currentPos); + _queuePosition = (_queuePosition - 1); + } + else + _queuePosition = (_queuePosition - 1); + if(_queuePosition < 0) + _queuePosition += _queue.size; + if(_queuePosition < _queue.size) + return _queue[_queuePosition]; + } + return null; + } + + fun setQueueItem(video: IPlatformVideo) : IPlatformVideo? { + synchronized(_queue) { + val index = _queue.indexOf(video); + if(index >= 0) { + _queuePosition = index; + return video; + } + else { + _queue.add(_queuePosition, video); + return video; + } + } + } + + //Player Initialization + fun getPlayerOrCreate(context : Context) : PlayerManager { + if(_exoplayer == null) { + val player = createExoPlayer(context); + _exoplayer = PlayerManager(player); + } + return _exoplayer!!; + } + fun getThumbnailPlayerOrCreate(context : Context) : PlayerManager { + if(_thumbnailExoPlayer == null) { + val player = createExoPlayer(context); + _thumbnailExoPlayer = PlayerManager(player); + } + return _thumbnailExoPlayer!!; + } + private fun createExoPlayer(context : Context) : ExoPlayer { + return ExoPlayer.Builder(context) + .setLoadControl( + DefaultLoadControl.Builder() + .setAllocator(DefaultAllocator(true, BUFFER_SIZE)) + .setBufferDurationsMs( + MIN_BUFFER_DURATION, + MAX_BUFFER_DURATION, + MIN_PLAYBACK_START_BUFFER, + MIN_PLAYBACK_RESUME_BUFFER + ) + .setTargetBufferBytes(-1) + .setPrioritizeTimeOverSizeThresholds(true) + .build()) + .setVideoScalingMode(C.VIDEO_SCALING_MODE_SCALE_TO_FIT) + .build(); + } + + + fun dispose(){ + val player = _exoplayer; + val thumbPlayer = _thumbnailExoPlayer; + _exoplayer = null; + _thumbnailExoPlayer = null; + player?.release(); + thumbPlayer?.release(); + } + + + companion object { + val TAG = "PlayerState"; + val TYPE_QUEUE = "Queue"; + val TYPE_PLAYLIST = "Playlist"; + val TYPE_WATCHLATER = "Watch Later"; + + private var _instance : StatePlayer? = null; + val instance : StatePlayer + get(){ + if(_instance == null) + _instance = StatePlayer(); + return _instance!!; + }; + + fun dispose(){ + val instance = _instance; + _instance = null; + instance?.dispose(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt new file mode 100644 index 00000000..b5f6258c --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt @@ -0,0 +1,264 @@ +package com.futo.platformplayer.states + +import android.content.Context +import android.net.Uri +import androidx.core.content.FileProvider +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails +import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException +import com.futo.platformplayer.exceptions.ReconstructionException +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.HistoryVideo +import com.futo.platformplayer.models.Playlist +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.v2.ManagedStore +import com.futo.platformplayer.stores.v2.ReconstructStore +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.io.File +import java.time.OffsetDateTime +import java.time.temporal.ChronoUnit + +/*** + * Used to maintain playlists + */ +class StatePlaylists { + private val _watchlistStore = FragmentedStorage.storeJson("watch_later") + .withUnique { it.url } + .withRestore(object: ReconstructStore() { + override fun toReconstruction(obj: SerializedPlatformVideo): String = obj.url; + override suspend fun toObject(id: String, backup: String, builder: Builder): SerializedPlatformVideo + = SerializedPlatformVideo.fromVideo(StatePlatform.instance.getContentDetails(backup).await() as IPlatformVideoDetails); + }) + .load(); + private val _historyStore = FragmentedStorage.storeJson("history") + .load(); + val playlistStore = FragmentedStorage.storeJson("playlists") + .withRestore(PlaylistBackup()) + .load(); + + val playlistShareDir = FragmentedStorage.getOrCreateDirectory("shares"); + + var onHistoricVideoChanged = Event2(); + val onWatchLaterChanged = Event0(); + + fun toMigrateCheck(): List> { + return listOf(playlistStore, _watchlistStore); + } + + fun getWatchLater() : List { + synchronized(_watchlistStore) { + return _watchlistStore.getItems(); + } + } + fun updateWatchLater(updated: List) { + synchronized(_watchlistStore) { + _watchlistStore.deleteAll(); + _watchlistStore.saveAllAsync(updated); + } + onWatchLaterChanged.emit(); + } + fun removeFromWatchLater(video: SerializedPlatformVideo) { + synchronized(_watchlistStore) { + _watchlistStore.delete(video); + } + + onWatchLaterChanged.emit(); + } + fun addToWatchLater(video: SerializedPlatformVideo) { + synchronized(_watchlistStore) { + _watchlistStore.saveAsync(video); + } + onWatchLaterChanged.emit(); + } + + fun getLastPlayedPlaylist() : Playlist? { + return playlistStore.queryItem { it.maxByOrNull { x -> x.datePlayed } }; + } + fun getLastUpdatedPlaylist() : Playlist? { + return playlistStore.queryItem { it.maxByOrNull { x -> x.dateUpdate } }; + } + + fun getPlaylists() : List { + return playlistStore.getItems(); + } + fun getPlaylist(id: String): Playlist? { + return playlistStore.findItem { it.id == id }; + } + + fun didPlay(playlistId: String) { + val playlist = getPlaylist(playlistId); + if(playlist != null) { + playlist.datePlayed = OffsetDateTime.now(); + playlistStore.saveAsync(playlist); + } + } + + fun getHistoryPosition(url: String): Long { + val histVideo = _historyStore.findItem { it.video.url == url }; + if(histVideo != null) + return histVideo.position; + return 0; + } + fun updateHistoryPosition(video: IPlatformVideo, updateExisting: Boolean, position: Long = -1L): Long { + val pos = if(position < 0) 0 else position; + val historyVideo = _historyStore.findItem { it.video.url == video.url }; + if (historyVideo != null) { + val positionBefore = historyVideo.position; + if (updateExisting) { + var shouldUpdate = false; + if (positionBefore < 30) { + shouldUpdate = true; + } else { + if (position > 30) { + shouldUpdate = true; + } + } + + if (shouldUpdate) { + historyVideo.position = pos; + historyVideo.date = OffsetDateTime.now(); + _historyStore.saveAsync(historyVideo); + onHistoricVideoChanged.emit(video, pos); + } + } + + return positionBefore; + } else { + val newHistItem = HistoryVideo(SerializedPlatformVideo.fromVideo(video), pos, OffsetDateTime.now()); + _historyStore.saveAsync(newHistItem); + return 0; + } + } + + fun getHistory() : List { + return _historyStore.getItems().sortedByDescending { it.date }; + } + + fun removeHistory(url: String) { + val hist = _historyStore.findItem { it.video.url == url }; + if(hist != null) + _historyStore.delete(hist); + } + + fun removeHistoryRange(minutesToDelete: Long) { + val now = OffsetDateTime.now(); + val toDelete = _historyStore.findItems { minutesToDelete == -1L || ChronoUnit.MINUTES.between(it.date, now) < minutesToDelete }; + + for(item in toDelete) + _historyStore.delete(item); + } + + suspend fun createPlaylistFromChannel(channelUrl: String, onPage: (Int) -> Unit): Playlist { + val channel = StatePlatform.instance.getChannel(channelUrl).await(); + return createPlaylistFromChannel(channel, onPage); + } + fun createPlaylistFromChannel(channel: IPlatformChannel, onPage: (Int) -> Unit): Playlist { + val contents = StatePlatform.instance.getChannelContent(channel.url); + val allContents = mutableListOf(); + allContents.addAll(contents.getResults()); + var page = 1; + while(contents.hasMorePages()) { + Logger.i("StatePlaylists", "Fetching channel video page ${page} from ${channel.url}"); + onPage(page); + contents.nextPage(); + allContents.addAll(contents.getResults()); + page++; + } + val allVideos = allContents.filter { it is IPlatformVideo }.map { it as IPlatformVideo }; + val newPlaylist = Playlist(channel.name, allVideos.map { SerializedPlatformVideo.fromVideo(it) }); + createOrUpdatePlaylist(newPlaylist); + return newPlaylist; + } + fun createOrUpdatePlaylist(playlist: Playlist) { + playlist.dateUpdate = OffsetDateTime.now(); + playlistStore.saveAsync(playlist, true); + } + fun addToPlaylist(id: String, video: IPlatformVideo) { + synchronized(playlistStore) { + val playlist = getPlaylist(id) ?: return; + playlist.videos.add(SerializedPlatformVideo.fromVideo(video)); + playlist.dateUpdate = OffsetDateTime.now(); + playlistStore.saveAsync(playlist, true); + } + } + + fun removePlaylist(playlist: Playlist) { + playlistStore.delete(playlist); + } + + fun createPlaylistShareUri(context: Context, playlist: Playlist): Uri { + val reconstruction = playlistStore.getReconstructionString(playlist, true); + + val newFile = File(playlistShareDir, playlist.name + ".recp"); + newFile.writeText(reconstruction, Charsets.UTF_8); + + return FileProvider.getUriForFile(context, context.resources.getString(R.string.authority), newFile); + } + fun createPlaylistShareJsonUri(context: Context, playlist: Playlist): Uri { + val reconstruction = playlistStore.getReconstructionString(playlist, true); + + val newFile = File(playlistShareDir, playlist.name + ".json"); + newFile.writeText(Json.encodeToString(reconstruction.split("\n")), Charsets.UTF_8); + + return FileProvider.getUriForFile(context, context.resources.getString(R.string.authority), newFile); + } + + companion object { + val TAG = "StatePlaylists"; + private var _instance : StatePlaylists? = null; + val instance : StatePlaylists + get(){ + if(_instance == null) + _instance = StatePlaylists(); + return _instance!!; + }; + + fun finish() { + _instance?.let { + _instance = null; + } + } + } + + + + class PlaylistBackup: ReconstructStore() { + override fun toReconstruction(obj: Playlist): String { + val items = ArrayList(); + items.add(obj.name); + items.addAll(obj.videos.map { it.url }); + return items.map { it.replace("\n","") }.joinToString("\n"); + } + override suspend fun toObject(id: String, backup: String, builder: Builder): Playlist { + val items = backup.split("\n"); + if(items.size <= 0) + throw IllegalStateException("Cannot reconstructor playlist ${id}"); + + val name = items[0]; + val videos = items.drop(1).filter { it.isNotEmpty() }.map { + try { + val video = StatePlatform.instance.getContentDetails(it).await(); + if (video is IPlatformVideoDetails) + return@map SerializedPlatformVideo.fromVideo(video); + else return@map null; + } + catch(ex: ScriptUnavailableException) { + Logger.w(TAG, "${name}:[${it}] is no longer available"); + builder.messages.add("${name}:[${it}] is no longer available"); + return@map null; + } + catch(ex: Throwable) { + throw ReconstructionException(name, "${name}:[${it}] ${ex.message}", ex); + } + }.filter { it != null }.map { it!! } + return Playlist(id, name, videos); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt new file mode 100644 index 00000000..e804a31a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt @@ -0,0 +1,426 @@ +package com.futo.platformplayer.states + +import android.content.Context +import com.futo.platformplayer.R +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.api.media.platforms.js.JSClient +import com.futo.platformplayer.api.media.platforms.js.SourceAuth +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.api.media.platforms.js.SourcePluginDescriptor +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.ImageVariable +import com.futo.platformplayer.stores.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json + +/*** + * Used to maintain plugin settings and configs + */ +class StatePlugins { + private val TAG = "StatePlugins"; + + private val FORCE_REINSTALL_EMBEDDED = false; + + private val _pluginScripts = FragmentedStorage.getDirectory(); + private var _plugins = FragmentedStorage.storeJson("plugins") + .load(); + private val iconsDir = FragmentedStorage.getDirectory(); + + fun getPluginIconOrNull(id: String): ImageVariable? { + if(iconsDir.hasIcon(id)) + return iconsDir.getIconBinary(id); + return null; + } + + fun reloadPluginFile(){ + _plugins = FragmentedStorage.storeJson("plugins") + .load(); + } + + private fun getResourceIdFromString(resourceName: String, c: Class<*> = R.drawable::class.java): Int? { + return try { + val idField = c.getDeclaredField(resourceName) + idField.getInt(idField) + } catch (exception: Exception) { + null + } + } + + @Serializable + private data class PluginConfig( + val SOURCES_EMBEDDED: Map, + val SOURCES_EMBEDDED_DEFAULT: List, + val SOURCES_UNDER_CONSTRUCTION: Map + ) + + private val _syncObject = Object() + private var _embeddedSources: Map? = null + private var _embeddedSourcesDefault: List? = null + private var _sourcesUnderConstruction: Map? = null + + private fun ensureSourcesConfigLoaded(context: Context) { + if (_embeddedSources != null && _embeddedSourcesDefault != null && _sourcesUnderConstruction != null) { + return + } + + val inputStream = context.resources.openRawResource(R.raw.plugin_config) + val jsonString = inputStream.bufferedReader().use { it.readText() } + val config = Json.decodeFromString(jsonString) + + _embeddedSources = config.SOURCES_EMBEDDED + _embeddedSourcesDefault = config.SOURCES_EMBEDDED_DEFAULT + _sourcesUnderConstruction = config.SOURCES_UNDER_CONSTRUCTION.mapNotNull { + val imageVariable = getResourceIdFromString(it.value)?.let { ImageVariable.fromResource(it) } ?: return@mapNotNull null + Pair(it.key, imageVariable) + }.toMap() + + Logger.i(TAG, "ensureSourcesConfigLoaded _embeddedSources:\n${_embeddedSources!!.map { " { ${it.key}: ${it.value} }" }.joinToString("\n")}") + Logger.i(TAG, "ensureSourcesConfigLoaded _embeddedSourcesDefault:\n${_embeddedSourcesDefault!!.map { " ${it}" }.joinToString("\n")}") + Logger.i(TAG, "ensureSourcesConfigLoaded _sourcesUnderConstruction:\n${_sourcesUnderConstruction!!.map { " { ${it.key}: ${it.value} }" }.joinToString("\n")}") + } + + fun getEmbeddedSources(context: Context): Map { + synchronized(_syncObject) { + ensureSourcesConfigLoaded(context) + return _embeddedSources!! + } + } + fun getEmbeddedSourcesDefault(context: Context): List { + synchronized(_syncObject) { + ensureSourcesConfigLoaded(context) + return _embeddedSourcesDefault!! + } + } + fun getSourcesUnderConstruction(context: Context): Map { + synchronized(_syncObject) { + ensureSourcesConfigLoaded(context) + return _sourcesUnderConstruction!! + } + } + + + suspend fun reinstallEmbeddedPlugins(context: Context) { + for(embedded in getEmbeddedSources(context)) + instance.deletePlugin(embedded.key); + StatePlatform.instance.updateAvailableClients(context); + } + fun updateEmbeddedPlugins(context: Context) { + for(embedded in getEmbeddedSources(context)) { + val embeddedConfig = getEmbeddedPluginConfig(context, embedded.value); + if(FORCE_REINSTALL_EMBEDDED) + deletePlugin(embedded.key); + else if(embeddedConfig != null) { + val existing = getPlugin(embedded.key); + if(existing != null && existing.config.version < embeddedConfig.version ) { + Logger.i(TAG, "Found outdated embedded plugin [${existing.config.id}] ${existing.config.name}, deleting and reinstalling"); + deletePlugin(embedded.key); + } + } + } + } + fun installMissingEmbeddedPlugins(context: Context) { + val plugins = getPlugins(); + for(embedded in getEmbeddedSources(context)) { + if(!plugins.any { it.config.id == embedded.key }) { + Logger.i(TAG, "Installing missing embedded plugin [${embedded.key}] ${embedded.value}, deleting and reinstalling"); + val success = instance.installEmbeddedPlugin(context, embedded.value, embedded.key); + if(!success) + Logger.i(TAG, "Failed to install embedded plugin [${embedded.key}]: ${embedded.value}"); + } + } + } + fun getEmbeddedPluginConfig(context: Context, assetConfigPath: String): SourcePluginConfig? { + val configJson = StateAssets.readAsset(context, assetConfigPath, false) ?: null; + if(configJson == null) + return null; + return SourcePluginConfig.fromJson(configJson, ""); + } + fun installEmbeddedPlugin(context: Context, assetConfigPath: String, id: String? = null): Boolean { + try { + val configJson = StateAssets.readAsset(context, assetConfigPath, false) ?: + throw IllegalStateException("Plugin config asset [${assetConfigPath}] not found"); + val config = SourcePluginConfig.fromJson(configJson, ""); + if(id != null && config.id != id) + throw IllegalStateException("Attempted to install embedded plugin with different id [${config.id}]"); + + val script = StateAssets.readAssetRelative(context, assetConfigPath, config.scriptUrl, false); + if(script.isNullOrEmpty()) + throw IllegalStateException("Plugin script asset [${config.scriptUrl}] could not be found in assets"); + + val icon = if(!config.iconUrl.isNullOrEmpty()) + StateAssets.readAssetBinRelative(context, assetConfigPath, config.iconUrl); + else null; + + createPlugin(config, script, icon, true); + return true; + } + catch(ex: Throwable) { + Logger.e(TAG, "Exception installing embedded plugin", ex); + return false; + } + } + fun installPlugins(context: Context, scope: CoroutineScope, sourceUrls: List, handler: ((Boolean) -> Unit)? = null) { + if(sourceUrls.isEmpty()) { + handler?.invoke(true); + return; + } + installPlugin(context, scope, sourceUrls[0]) { + installPlugins(context, scope, sourceUrls.drop(1), handler); + } + } + fun installPlugin(context: Context, scope: CoroutineScope, sourceUrl: String, handler: ((Boolean) -> Unit)? = null) { + scope.launch(Dispatchers.IO) { + try { + val configResp = ManagedHttpClient().get(sourceUrl); + if(!configResp.isOk) + throw IllegalStateException("Failed request with ${configResp.code}"); + val configJson = configResp.body?.string(); + if(configJson.isNullOrEmpty()) + throw IllegalStateException("No response"); + val config = SourcePluginConfig.fromJson(configJson, sourceUrl); + + withContext(Dispatchers.Main) { + installPlugin(context, scope, config, handler); + } + } + catch(ex: SerializationException) { + Logger.e(TAG, "Failed decode config", ex); + withContext(Dispatchers.Main) { + UIDialogs.showDialog(context, R.drawable.ic_error, + "Invalid Config Format", null, null, + 0, UIDialogs.Action("Ok", { + finish(); + handler?.invoke(false); + }, UIDialogs.ActionStyle.PRIMARY)); + + }; + } + catch(ex: Exception) { + Logger.e(TAG, "Failed fetch config", ex); + withContext(Dispatchers.Main) { + UIDialogs.showGeneralErrorDialog(context, "Failed to install plugin\n(${sourceUrl})", ex, "Ok", { + handler?.invoke(false); + }); + }; + } + } + } + fun installPlugin(context: Context, scope: CoroutineScope, config: SourcePluginConfig, handler: ((Boolean)->Unit)? = null) { + val client = ManagedHttpClient(); + val warnings = config.getWarnings(); + + + fun doInstall(reinstall: Boolean) { + UIDialogs.showDialogProgress(context) { + it.setText("Downloading script..."); + it.setProgress(0f); + + scope.launch(Dispatchers.IO) { + try { + val scriptResp = client.get(config.absoluteScriptUrl); + if (!scriptResp.isOk) + throw IllegalStateException("script not available [${scriptResp.code}]"); + val script = scriptResp.body?.string(); + if (script.isNullOrEmpty()) + throw IllegalStateException("script empty"); + + withContext(Dispatchers.Main) { + it.setText("Validating script..."); + it.setProgress(0.25); + } + + val tempDescriptor = SourcePluginDescriptor(config); + val plugin = JSClient(context, tempDescriptor, null, script); + plugin.validate(); + + withContext(Dispatchers.Main) { + it.setText("Downloading Icon..."); + it.setProgress(0.5); + } + + val icon = config.absoluteIconUrl?.let { absIconUrl -> + withContext(Dispatchers.Main) { + it.setText("Saving plugin..."); + it.setProgress(0.75); + } + val iconResp = client.get(absIconUrl); + if(iconResp.isOk) + return@let iconResp.body?.byteStream()?.use { it.readBytes() }; + return@let null; + } + val installEx = StatePlugins.instance.createPlugin(config, script, icon, reinstall); + if(installEx != null) + throw installEx; + StatePlatform.instance.updateAvailableClients(context); + + withContext(Dispatchers.Main) { + it.setText("Plugin created!"); + it.setProgress(1.0); + it.dismiss(); + + UIDialogs.toast(context, "Plugin ${config.name} installed"); + handler?.invoke(true); + } + } catch (ex: Exception) { + Logger.e(TAG, ex.message ?: "null", ex); + withContext(Dispatchers.Main) { + it.dismiss(); + UIDialogs.showDialogOk( + context, + R.drawable.ic_error, + "Failed to install due to:\n${ex.message}" + ) { + handler?.invoke(false); + } + } + } + }; + }; + } + fun verifyCanInstall() { + val installed = StatePlatform.instance.getClientOrNull(config.id); + if(installed != null) + UIDialogs.showDialog(context, R.drawable.ic_security_pred, + "A plugin with this id already exists named:\n" + + "${installed.name}\n[${config.id}]\n\n" + + "Would you like to reinstall it?", null, null, + 1, + UIDialogs.Action("Reinstall", { doInstall(true) }, UIDialogs.ActionStyle.DANGEROUS_TEXT), + UIDialogs.Action("Cancel", { handler?.invoke(false); }, UIDialogs.ActionStyle.DANGEROUS) + ); + else + doInstall(false); + } + + if(!warnings.isEmpty()) { + UIDialogs.showDialog(context, R.drawable.ic_security_pred, + "You are trying to install a plugin (${config.name}) with security vunerabilities.\n" + + "Are you sure you want to install it", null, + warnings.map { "${it.first}:\n${it.second}\n" }.joinToString("\n"), + 1, + UIDialogs.Action("Install Anyway", { verifyCanInstall() }, UIDialogs.ActionStyle.DANGEROUS_TEXT), + UIDialogs.Action("Cancel", { }, UIDialogs.ActionStyle.DANGEROUS)); + } + else verifyCanInstall(); + } + + fun getPlugin(id: String): SourcePluginDescriptor? { + if(id == StateDeveloper.DEV_ID) + throw IllegalStateException("Attempted to retrieve a persistent developer plugin, this is not allowed"); + + synchronized(_plugins) { + return _plugins.findItem { it.config.id == id }; + } + } + fun getPlugins(): List { + return _plugins.getItems(); + } + fun hasPlugin(id: String): Boolean = _plugins.findItem { it.config.id == id } != null; + + fun deletePlugin(id: String) { + synchronized(_pluginScripts) { + synchronized(_plugins) { + _pluginScripts.deleteFile(id); + val plugins = _plugins.findItems { it.config.id == id }; + for(plugin in plugins) + _plugins.delete(plugin); + } + } + } + fun createPlugin(config: SourcePluginConfig, script: String, icon: ByteArray? = null, reinstall: Boolean = false, flags: List = listOf()) : Throwable? { + try { + if(config.id == StateDeveloper.DEV_ID) + throw IllegalStateException("Attempted to make developer plugin persistent, this is not allowed"); + + if(!config.scriptSignature.isNullOrBlank()) { + val isValid = config.validate(script); + if(!isValid) + throw SecurityException("Script signature is invalid. Possible tampering"); + } + + val existing = getPlugin(config.id) + if (existing != null) { + if(!reinstall) + throw IllegalStateException("Plugin with id ${config.id} already exists"); + else deletePlugin(config.id); + } + _pluginScripts.setScript(config.id, script); + + if(_pluginScripts.getScript(config.id).isNullOrEmpty()) + throw IllegalStateException("Plugin script corrupted?"); + + if(icon != null) + iconsDir.saveIconBinary(config.id, icon); + + _plugins.save(SourcePluginDescriptor(config, null, flags)); + return null; + } + catch(ex: Throwable) { + deletePlugin(config.id); + return ex; + } + } + + fun getScript(pluginId: String) : String? { + return _pluginScripts.getScript(pluginId); + } + + fun setPluginSettings(id: String, map: Map) { + val newSettings = HashMap(map); + val plugin = getPlugin(id); + + if(plugin != null) { + for(setting in plugin.config.settings) { + if(!newSettings.containsKey(setting.variableOrName) || newSettings[setting.variableOrName] == null) + newSettings[setting.variableOrName] = setting.default; + } + + plugin.settings = newSettings; + _plugins.save(plugin, false, true); + } + } + fun savePlugin(id: String) { + val plugin = getPlugin(id); + + if(plugin != null) { + _plugins.save(plugin, false, true); + } + } + + fun setPluginAuth(id: String, auth: SourceAuth?) { + if(id == StateDeveloper.DEV_ID) { + StatePlatform.instance.getDevClient()?.let { + it.setAuth(auth); + }; + return; + } + + val descriptor = getPlugin(id) ?: throw IllegalArgumentException("Plugin [${id}] does not exist"); + descriptor.updateAuth(auth); + _plugins.save(descriptor); + } + + + companion object { + private var _instance : StatePlugins? = null; + val instance : StatePlugins + get(){ + if(_instance == null) + _instance = StatePlugins(); + return _instance!!; + }; + + fun finish() { + _instance?.let { + _instance = null; + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt b/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt new file mode 100644 index 00000000..dd6b70fa --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt @@ -0,0 +1,328 @@ +package com.futo.platformplayer.states + +import android.content.Context +import android.content.Intent +import com.futo.platformplayer.R +import com.futo.platformplayer.Settings +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.activities.PolycentricHomeActivity +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.contents.PlatformContentPlaceholder +import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes +import com.futo.platformplayer.api.media.structures.DedupContentPager +import com.futo.platformplayer.api.media.structures.EmptyPager +import com.futo.platformplayer.api.media.structures.IAsyncPager +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.api.media.structures.MultiChronoContentPager +import com.futo.platformplayer.api.media.structures.PlaceholderPager +import com.futo.platformplayer.api.media.structures.RefreshDedupContentPager +import com.futo.platformplayer.api.media.structures.RefreshDistributionContentPager +import com.futo.platformplayer.awaitFirstDeferred +import com.futo.platformplayer.dp +import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.polycentric.PolycentricCache +import com.futo.platformplayer.resolveChannelUrl +import com.futo.platformplayer.selectBestImage +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.StringStorage +import com.futo.polycentric.core.* +import com.google.protobuf.ByteString +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import userpackage.Protocol +import java.time.Instant +import java.time.OffsetDateTime +import java.time.ZoneOffset + +class StatePolycentric { + private data class LikeDislikeEntry(val unixMilliseconds: Long, val hasLiked: Boolean, val hasDisliked: Boolean); + + var processHandle: ProcessHandle? = null; private set; + private var _likeDislikeMap = hashMapOf() + private val _activeProcessHandle = FragmentedStorage.get("activeProcessHandle"); + + fun load(context: Context) { + val db = SqlLiteDbHelper(context); + Store.initializeSqlLiteStore(db); + + val activeProcessHandleString = _activeProcessHandle.value; + if (activeProcessHandleString.isNotEmpty()) { + val system = PublicKey.fromProto(Protocol.PublicKey.parseFrom(activeProcessHandleString.base64ToByteArray())); + setProcessHandle(Store.instance.getProcessSecret(system)?.toProcessHandle()); + } + } + + fun getProcessHandles(): List { + return Store.instance.getProcessSecrets().map { it.toProcessHandle(); }; + } + + fun setProcessHandle(processHandle: ProcessHandle?) { + this.processHandle = processHandle; + + if (processHandle != null) { + _activeProcessHandle.setAndSave(processHandle.system.toProto().toByteArray().toBase64()); + + val newMap = hashMapOf() + Store.instance.enumerateSignedEvents(processHandle.system, ContentType.OPINION) { + try { + for (ref in it.event.references) { + val refd = ref.toByteArray().toBase64(); + val e = newMap[refd]; + if (e == null || it.event.unixMilliseconds!! > e.unixMilliseconds) { + val data = it.event.lwwElement?.value ?: continue; + newMap[refd] = LikeDislikeEntry(it.event.unixMilliseconds!!, Opinion(data) == Opinion.like, Opinion(data) == Opinion.dislike); + } + } + } catch (e: Throwable) { + Logger.w(TAG, "Failed to get opinion, skipped.") + } + } + + _likeDislikeMap = newMap + } else { + _activeProcessHandle.setAndSave(""); + _likeDislikeMap = hashMapOf() + } + } + + fun updateLikeMap(ref: Protocol.Reference, hasLiked: Boolean, hasDisliked: Boolean) { + _likeDislikeMap[ref.toByteArray().toBase64()] = LikeDislikeEntry(System.currentTimeMillis(), hasLiked, hasDisliked); + } + + fun hasDisliked(ref: Protocol.Reference): Boolean { + val entry = _likeDislikeMap[ref.toByteArray().toBase64()] ?: return false; + return entry.hasDisliked; + } + + fun hasLiked(ref: Protocol.Reference): Boolean { + val entry = _likeDislikeMap[ref.toByteArray().toBase64()] ?: return false; + return entry.hasLiked; + } + + fun requireLogin(context: Context, text: String, action: (processHandle: ProcessHandle) -> Unit) { + val p = processHandle; + if (p == null) { + Logger.i(TAG, "requireLogin preventPictureInPicture.emit()"); + StateApp.instance.preventPictureInPicture.emit(); + UIDialogs.showDialog(context, R.drawable.ic_login, + text, null, null, + 1, + UIDialogs.Action("Cancel", { }, UIDialogs.ActionStyle.ACCENT), + UIDialogs.Action("OK", { + context.startActivity(Intent(context, PolycentricHomeActivity::class.java)); + }, UIDialogs.ActionStyle.PRIMARY) + ); + } else { + action(p); + } + } + + fun getChannelContent(profile: PolycentricProfile, isSubscriptionOptimized: Boolean = false, channelConcurrency: Int = -1): IPager { + //TODO: Currently abusing subscription concurrency for parallelism + val concurrency = if (channelConcurrency == -1) Settings.instance.subscriptions.getSubscriptionsConcurrency() else channelConcurrency; + val pagers = profile.ownedClaims.groupBy { it.claim.claimType }.mapNotNull { + //TODO: Deduplicate once multiple urls in single claim is supported + return@mapNotNull it.value.firstOrNull(); + }.mapNotNull { + val url = it.claim.resolveChannelUrl() ?: return@mapNotNull null; + if (!StatePlatform.instance.hasEnabledChannelClient(url)) { + return@mapNotNull null; + } + + return@mapNotNull StatePlatform.instance.getChannelContent(url, isSubscriptionOptimized, concurrency); + }.toTypedArray(); + + val pager = MultiChronoContentPager(pagers); + pager.initialize(); + return DedupContentPager(pager, StatePlatform.instance.getEnabledClients().map { it.id }); + } + + fun getChannelContent(scope: CoroutineScope, profile: PolycentricProfile, isSubscriptionOptimized: Boolean = false, channelConcurrency: Int = -1): IPager? { + //TODO: Currently abusing subscription concurrency for parallelism + val concurrency = if (channelConcurrency == -1) Settings.instance.subscriptions.getSubscriptionsConcurrency() else channelConcurrency; + val deferred = profile.ownedClaims.groupBy { it.claim.claimType } + .mapNotNull { + //TODO: Deduplicate once multiple urls in single claim is supported + return@mapNotNull it.value.firstOrNull(); + }.mapNotNull { + val url = it.claim.resolveChannelUrl() ?: return@mapNotNull null; + val client = StatePlatform.instance.getChannelClientOrNull(url) ?: return@mapNotNull null; + + return@mapNotNull Pair(client, scope.async(Dispatchers.IO) { + try { + return@async StatePlatform.instance.getChannelContent(url, isSubscriptionOptimized, concurrency); + } catch (ex: Throwable) { + Logger.e(TAG, "getChannelContent", ex); + return@async null; + } + }) + } + .groupBy { it.first.name } + .map { it.value.first() }; + val finishedPager: Pair?>, IPager?> = (if(deferred.isEmpty()) null else runBlocking { + deferred.map { it.second }.awaitFirstDeferred(); + }) ?: return null; + + val toAwait = deferred.filter { it.second != finishedPager.first }; + return RefreshDedupContentPager(RefreshDistributionContentPager( + listOf(finishedPager.second!!), + toAwait.map { it.second }, + toAwait.map { PlaceholderPager(5) { PlatformContentPlaceholder(it.first.id) } }), + StatePlatform.instance.getEnabledClients().map { it.id } + ); + } + suspend fun getChannelContent(profile: PolycentricProfile): IPager { + return withContext(Dispatchers.IO) { + getChannelContent(this, profile) ?: EmptyPager(); + } + } + + suspend fun getCommentPager(contextUrl: String, reference: Protocol.Reference): IPager { + val response = ApiMethods.getQueryReferences(PolycentricCache.SERVER, reference, null, + Protocol.QueryReferencesRequestEvents.newBuilder() + .setFromType(ContentType.POST.value) + .addAllCountLwwElementReferences(arrayListOf( + Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder() + .setFromType(ContentType.OPINION.value) + .setValue(ByteString.copyFrom(Opinion.like.data)) + .build(), + Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder() + .setFromType(ContentType.OPINION.value) + .setValue(ByteString.copyFrom(Opinion.dislike.data)) + .build() + )) + .addCountReferences( + Protocol.QueryReferencesRequestCountReferences.newBuilder() + .setFromType(ContentType.POST.value) + .build()) + .build() + ); + + val results = mapQueryReferences(contextUrl, response); + val nextCursor = if (response.hasCursor()) response.cursor.toByteArray() else null + return object : IAsyncPager, IPager { + private var _results: List = results + private var _cursor: ByteArray? = nextCursor + + override fun hasMorePages(): Boolean { + return _cursor != null; + } + + override fun nextPage() { + runBlocking { nextPageAsync() } + } + + override suspend fun nextPageAsync() { + val nextPageResponse = ApiMethods.getQueryReferences(PolycentricCache.SERVER, reference, _cursor, + Protocol.QueryReferencesRequestEvents.newBuilder() + .setFromType(ContentType.POST.value) + .addAllCountLwwElementReferences(arrayListOf( + Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder() + .setFromType(ContentType.OPINION.value) + .setValue(ByteString.copyFrom(Opinion.like.data)) + .build(), + Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder() + .setFromType(ContentType.OPINION.value) + .setValue(ByteString.copyFrom(Opinion.dislike.data)) + .build() + )) + .addCountReferences( + Protocol.QueryReferencesRequestCountReferences.newBuilder() + .setFromType(ContentType.POST.value) + .build()) + .build() + ); + + _cursor = if (nextPageResponse.hasCursor()) nextPageResponse.cursor.toByteArray() else null + _results = mapQueryReferences(contextUrl, nextPageResponse) + } + + override fun getResults(): List { + return _results; + } + }; + } + + private suspend fun mapQueryReferences(contextUrl: String, response: Protocol.QueryReferencesResponse): List { + return response.itemsList.mapNotNull { + val sev = SignedEvent.fromProto(it.event); + val ev = sev.event; + if (ev.contentType != ContentType.POST.value) { + return@mapNotNull null; + } + + try { + val post = Protocol.Post.parseFrom(ev.content); + val id = ev.system.toProto().key.toByteArray().toBase64(); + val likes = it.countsList[0]; + val dislikes = it.countsList[1]; + val replies = it.countsList[2]; + + val profileEvents = ApiMethods.getQueryLatest( + PolycentricCache.SERVER, + ev.system.toProto(), + listOf( + ContentType.AVATAR.value, + ContentType.USERNAME.value + ) + ).eventsList.map { e -> SignedEvent.fromProto(e) }; + + val nameEvent = profileEvents.firstOrNull { e -> e.event.contentType == ContentType.USERNAME.value }; + val avatarEvent = profileEvents.firstOrNull { e -> e.event.contentType == ContentType.AVATAR.value }; + val imageBundle = if (avatarEvent != null) { + val lwwElementValue = avatarEvent.event.lwwElement?.value; + if (lwwElementValue != null) { + Protocol.ImageBundle.parseFrom(lwwElementValue) + } else { + null + } + } else { + null + } + + val unixMilliseconds = ev.unixMilliseconds + //TODO: Don't use single hardcoded sderver here + val systemLinkUrl = ev.system.systemToURLInfoSystemLinkUrl(listOf(PolycentricCache.SERVER)); + val dp_25 = 25.dp(StateApp.instance.context.resources) + return@mapNotNull PolycentricPlatformComment( + contextUrl = contextUrl, + author = PlatformAuthorLink( + id = PlatformID("polycentric", systemLinkUrl, null, ClaimType.POLYCENTRIC.value.toInt()), + name = nameEvent?.event?.lwwElement?.value?.decodeToString() ?: "Unknown", + url = systemLinkUrl, + thumbnail = imageBundle?.selectBestImage(dp_25 * dp_25)?.let { img -> img.toURLInfoSystemLinkUrl(ev.system.toProto(), img.process, listOf(PolycentricCache.SERVER)) }, + subscribers = null + ), + msg = post.content, + rating = RatingLikeDislikes(likes, dislikes), + date = if (unixMilliseconds != null) Instant.ofEpochMilli(unixMilliseconds).atOffset(ZoneOffset.UTC) else OffsetDateTime.MIN, + replyCount = replies.toInt(), + reference = sev.toPointer().toReference() + ); + } catch (e: Throwable) { + return@mapNotNull null; + } + }; + } + + companion object { + private const val TAG = "StatePolycentric"; + + private var _instance: StatePolycentric? = null; + val instance: StatePolycentric + get(){ + if(_instance == null) + _instance = StatePolycentric(); + return _instance!!; + }; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StateSaved.kt b/app/src/main/java/com/futo/platformplayer/states/StateSaved.kt new file mode 100644 index 00000000..1bd4df34 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/states/StateSaved.kt @@ -0,0 +1,52 @@ +package com.futo.platformplayer.states + +import com.futo.platformplayer.api.media.Serializer +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.StringStorage +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString + +@kotlinx.serialization.Serializable +data class VideoToOpen(val url: String, val timeSeconds: Long); + +class StateSaved { + var videoToOpen: VideoToOpen? = null; + + private val _videoToOpen = FragmentedStorage.get("videoToOpen") + + fun load() { + val videoToOpenString = _videoToOpen.value; + if (videoToOpenString.isNotEmpty()) { + try { + val v = Serializer.json.decodeFromString(videoToOpenString); + videoToOpen = v; + } catch (e: Throwable) { + Logger.w(TAG, "Failed to load video to open", e) + } + } + + Logger.i(TAG, "loaded videoToOpen=$videoToOpen"); + } + + fun setVideoToOpenNonBlocking(v: VideoToOpen? = null) { + Logger.i(TAG, "set videoToOpen=$v"); + + videoToOpen = v; + _videoToOpen.setAndSave(if (v != null) Serializer.json.encodeToString(v) else ""); + } + + + fun setVideoToOpenBlocking(v: VideoToOpen? = null) { + Logger.i(TAG, "set videoToOpen=$v"); + + videoToOpen = v; + _videoToOpen.setAndSaveBlocking(if (v != null) Serializer.json.encodeToString(v) else ""); + } + + companion object { + const val TAG = "StateSaved" + + val instance: StateSaved = StateSaved() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt b/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt new file mode 100644 index 00000000..ba940c79 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt @@ -0,0 +1,375 @@ +package com.futo.platformplayer.states + +import com.futo.platformplayer.Settings +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.api.media.models.channels.SerializedChannel +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.structures.* +import com.futo.platformplayer.api.media.structures.ReusablePager.Companion.asReusable +import com.futo.platformplayer.cache.ChannelContentCache +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.engine.exceptions.PluginException +import com.futo.platformplayer.exceptions.ChannelException +import com.futo.platformplayer.findNonRuntimeException +import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.Subscription +import com.futo.platformplayer.polycentric.PolycentricCache +import com.futo.platformplayer.resolveChannelUrl +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.SubscriptionStorage +import com.futo.platformplayer.stores.v2.ReconstructStore +import com.futo.platformplayer.stores.v2.ManagedStore +import kotlinx.coroutines.* +import java.util.concurrent.ExecutionException +import java.util.concurrent.ForkJoinPool +import java.util.concurrent.ForkJoinTask +import kotlin.collections.ArrayList +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine +import kotlin.system.measureTimeMillis + +/*** + * Used to maintain subscriptions + */ +class StateSubscriptions { + private val _subscriptions = FragmentedStorage.storeJson("subscriptions") + .withUnique { it.channel.url } + .withRestore(object: ReconstructStore(){ + override fun toReconstruction(obj: Subscription): String = + obj.channel.url; + override suspend fun toObject(id: String, backup: String, reconstructionBuilder: Builder): Subscription = + Subscription(SerializedChannel.fromChannel(StatePlatform.instance.getChannelLive(backup, false))); + }).load(); + private val _subscriptionsPool = ForkJoinPool(Settings.instance.subscriptions.getSubscriptionsConcurrency()); + private val _legacySubscriptions = FragmentedStorage.get(); + + val onSubscriptionsChanged = Event2, Boolean>(); + + private var _globalSubscriptionsLock = Object(); + private var _globalSubscriptionFeed: ReusablePager? = null; + var isGlobalUpdating: Boolean = false + private set; + var globalSubscriptionExceptions: List = listOf() + private set; + + private var _lastGlobalSubscriptionProgress: Int = 0; + private var _lastGlobalSubscriptionTotal: Int = 0; + val onGlobalSubscriptionsUpdateProgress = Event2(); + val onGlobalSubscriptionsUpdated = Event0(); + val onGlobalSubscriptionsUpdatedOnce = Event1(); + val onGlobalSubscriptionsException = Event1>(); + + fun getGlobalSubscriptionProgress(): Pair { + return Pair(_lastGlobalSubscriptionProgress, _lastGlobalSubscriptionTotal); + } + fun updateSubscriptionFeed(scope: CoroutineScope, onlyIfNull: Boolean = false, onProgress: ((Int, Int)->Unit)? = null) { + Logger.i(TAG, "updateSubscriptionFeed"); + scope.launch(Dispatchers.IO) { + synchronized(_globalSubscriptionsLock) { + if (isGlobalUpdating || (onlyIfNull && _globalSubscriptionFeed != null)) { + Logger.i(TAG, "Already updating subscriptions or not required") + return@launch; + } + isGlobalUpdating = true; + } + try { + val subsResult = getSubscriptionsFeedWithExceptions(true, true, scope, { progress, total -> + _lastGlobalSubscriptionProgress = progress; + _lastGlobalSubscriptionTotal = total; + onGlobalSubscriptionsUpdateProgress.emit(progress, total); + onProgress?.invoke(progress, total); + }); + if (subsResult.second.any()) { + globalSubscriptionExceptions = subsResult.second; + onGlobalSubscriptionsException.emit(subsResult.second); + } + _globalSubscriptionFeed = subsResult.first.asReusable(); + synchronized(_globalSubscriptionsLock) { + onGlobalSubscriptionsUpdated.emit(); + onGlobalSubscriptionsUpdatedOnce.emit(null); + onGlobalSubscriptionsUpdatedOnce.clear(); + } + } + catch (e: Throwable) { + synchronized(_globalSubscriptionsLock) { + onGlobalSubscriptionsUpdatedOnce.emit(e); + onGlobalSubscriptionsUpdatedOnce.clear(); + } + Logger.e(TAG, "Failed to update subscription feed.", e); + } + finally { + isGlobalUpdating = false; + } + }; + } + fun clearSubscriptionFeed() { + synchronized(_globalSubscriptionsLock) { + _globalSubscriptionFeed = null; + } + } + + private var loadIndex = 0; + suspend fun getGlobalSubscriptionFeed(scope: CoroutineScope, updated: Boolean): IPager { + //Get Subscriptions only if null + updateSubscriptionFeed(scope, !updated); + + val evRef = Object(); + val result = suspendCoroutine { + synchronized(_globalSubscriptionsLock) { + if (_globalSubscriptionFeed != null && !updated) { + Logger.i(TAG, "Subscriptions got feed preloaded"); + it.resumeWith(Result.success(_globalSubscriptionFeed!!.getWindow())); + } else { + val loadIndex = loadIndex++; + Logger.i(TAG, "[${loadIndex}] Starting await update"); + onGlobalSubscriptionsUpdatedOnce.subscribe(evRef) {ex -> + Logger.i(TAG, "[${loadIndex}] Subscriptions got feed after update"); + if(ex != null) + it.resumeWithException(ex); + else if (_globalSubscriptionFeed != null) + it.resumeWith(Result.success(_globalSubscriptionFeed!!.getWindow())); + else + it.resumeWithException(IllegalStateException("No subscription pager after change? Illegal null set on global subscriptions")) + } + } + } + }; + return result; + } + + suspend fun updateSubscriptions(doSave: Boolean = true) { + for (sub in _subscriptions.getItems()) { + Logger.i(TAG, "Updating channel ${sub.channel.name} with url ${sub.channel.url}"); + val updatedSub = StatePlatform.instance.getChannel(sub.channel.url, false).await(); + sub.updateChannel(updatedSub); + if(doSave) + _subscriptions.save(sub); + } + } + + fun getSubscription(url: String) : Subscription? { + synchronized(_subscriptions) { + return _subscriptions.findItem { it.channel.url == url || it.channel.urlAlternatives.contains(url) }; + } + } + fun saveSubscription(sub: Subscription) { + _subscriptions.save(sub, false, true); + } + fun getSubscriptionCount(): Int { + synchronized(_subscriptions) { + return _subscriptions.getItems().size; + } + } + fun getSubscriptions(): List { + return _subscriptions.getItems(); + } + + fun addSubscription(channel : IPlatformChannel) : Subscription { + val subObj = Subscription(SerializedChannel.fromChannel(channel)); + _subscriptions.save(subObj); + onSubscriptionsChanged.emit(getSubscriptions(), true); + return subObj; + } + fun removeSubscription(url: String) : Subscription? { + var sub : Subscription? = getSubscription(url); + if(sub != null) { + _subscriptions.delete(sub); + onSubscriptionsChanged.emit(getSubscriptions(), false); + } + return sub; + } + + fun isSubscribed(channel: IPlatformChannel): Boolean { + val urls = (listOf(channel.url) + channel.urlAlternatives).distinct(); + return isSubscribed(urls); + } + fun isSubscribed(url: String) : Boolean { + return isSubscribed(listOf(url)); + } + fun isSubscribed(urls: List) : Boolean { + if(urls.isEmpty()) + return false; + synchronized(_subscriptions) { + if (_subscriptions.hasItem { urls.contains(it.channel.url) }) { + return true; + } + + //TODO: This causes issues, because what if the profile is not cached yet when the susbcribe button is loaded for example? + val cachedProfile = PolycentricCache.instance.getCachedProfile(urls.first(), true)?.profile; + if (cachedProfile != null) { + return cachedProfile.ownedClaims.any { c -> _subscriptions.hasItem { s -> c.claim.resolveChannelUrl() == s.channel.url } }; + } + + return false; + } + } + fun updateSubscriptionChannel(channel: IPlatformChannel, doSave: Boolean = true) { + val sub = getSubscription(channel.url) ?: channel.urlAlternatives.firstNotNullOfOrNull { getSubscription(it) }; + if(sub != null) { + sub.updateChannel(channel); + if(doSave) + _subscriptions.save(sub); + } + } + + fun getSubscriptionsFeed(allowFailure: Boolean = false): MultiChronoContentPager { + val result = getSubscriptionsFeedWithExceptions(allowFailure, true); + if(result.second.any()) + throw result.second.first(); + return result.first; + } + fun getSubscriptionsFeedWithExceptions(allowFailure: Boolean = false, withCacheFallback: Boolean = false, cacheScope: CoroutineScope? = null, onProgress: ((Int, Int)->Unit)? = null, onNewCacheHit: ((Subscription, IPlatformContent)->Unit)? = null): Pair> { + val subsPager: Array>; + val exs: ArrayList = arrayListOf(); + + val tasks = mutableListOf?>>>(); + var finished = 0; + val exceptionMap: HashMap = hashMapOf(); + val concurrency = Settings.instance.subscriptions.getSubscriptionsConcurrency(); + for (sub in getSubscriptions().filter { StatePlatform.instance.hasEnabledChannelClient(it.channel.url) }) { + tasks.add(_subscriptionsPool.submit?>> { + var polycentricProfile : PolycentricCache.CachedPolycentricProfile? = null; + val getProfileTime = measureTimeMillis { + try { + polycentricProfile = PolycentricCache.instance.getCachedProfile(sub.channel.url); + if (polycentricProfile == null) { + Logger.i("StateSubscriptions", "Get polycentric profile not cached"); + polycentricProfile = runBlocking { PolycentricCache.instance.getProfileAsync(sub.channel.id) }; + } else { + Logger.i("StateSubscriptions", "Get polycentric profile cached"); + } + } + catch(ex: Throwable) { + Logger.w(TAG, "Polycentric getCachedProfile failed for subscriptions", ex); + //TODO: Some way to communicate polycentric failing without blocking here + //UIDialogs.toast("Polycentric failed\n" + ex.message, false); + //UIDialogs.showGeneralErrorDialog(it, "Polycentric getCachedProfile failed for subscriptions", ex); + } + } + + Logger.i("StateSubscriptions", "Get polycentric profile time ${getProfileTime}ms"); + + var pager: IPager; + try { + val time = measureTimeMillis { + val profile = polycentricProfile?.profile + pager = if (profile != null) + StatePolycentric.instance.getChannelContent(profile, true, concurrency) + else + StatePlatform.instance.getChannelContent(sub.channel.url, true, concurrency); + + if (cacheScope != null) + pager = ChannelContentCache.cachePagerResults(cacheScope, pager) { + onNewCacheHit?.invoke(sub, it); + }; + + finished++; + onProgress?.invoke(finished, tasks.size); + } + Logger.i( + "StateSubscriptions", + "Subscription [${sub.channel.name}] results in ${time}ms" + ); + } + catch(ex: Throwable) { + finished++; + onProgress?.invoke(finished, tasks.size); + val channelEx = ChannelException(sub.channel, ex); + synchronized(exceptionMap) { + exceptionMap.put(sub, channelEx); + } + if(!withCacheFallback) + throw channelEx; + else { + Logger.i(TAG, "Channel ${sub.channel.name} failed, substituting with cache"); + pager = ChannelContentCache.instance.getChannelCachePager(sub.channel.url); + } + } + return@submit Pair(sub, pager); + }); + } + val timeTotal = measureTimeMillis { + val taskResults = arrayListOf>(); + for(task in tasks) { + try { + val result = task.get(); + if(result != null) { + if(result.second != null) + taskResults.add(result.second!!); + if(exceptionMap.containsKey(result.first)) { + val ex = exceptionMap[result.first]; + if(ex != null) { + val nonRuntimeEx = findNonRuntimeException(ex); + if (nonRuntimeEx != null && (nonRuntimeEx is PluginException || nonRuntimeEx is ChannelException)) + exs.add(nonRuntimeEx); + else + throw ex.cause ?: ex; + } + } + } + } catch (ex: ExecutionException) { + val nonRuntimeEx = findNonRuntimeException(ex.cause); + if(nonRuntimeEx != null && (nonRuntimeEx is PluginException || nonRuntimeEx is ChannelException)) + exs.add(nonRuntimeEx); + else + throw ex.cause ?: ex; + }; + } + subsPager = taskResults.toTypedArray(); + } + Logger.i("StateSubscriptions", "Subscriptions results in ${timeTotal}ms") + + if(subsPager.size <= 0 && exs.any()) + throw exs.first(); + + Logger.i(TAG, "Subscription pager with ${subsPager.size} channels"); + val pager = MultiChronoContentPager(subsPager, allowFailure); + pager.initialize(); + return Pair(pager, exs); + } + + //New Migration + fun toMigrateCheck(): List> { + return listOf(_subscriptions); + } + + //Old migrate + fun shouldMigrate(): Boolean { + return _legacySubscriptions.subscriptions.any(); + } + fun tryMigrateIfNecessary() { + Logger.i(TAG, "MIGRATING SUBS"); + val oldSubs = _legacySubscriptions.subscriptions.toList(); + + for(sub in oldSubs) { + if(!this.isSubscribed(sub.channel.url)) { + Logger.i(TAG, "MIGRATING ${sub.channel.url}"); + addSubscription(sub.channel); + } + } + _legacySubscriptions.delete(); + } + + companion object { + const val TAG = "StateSubscriptions"; + const val VERSION = 1; + + private var _instance : StateSubscriptions? = null; + val instance : StateSubscriptions + get(){ + if(_instance == null) + _instance = StateSubscriptions(); + return _instance!!; + }; + + fun finish() { + _instance?.let { + _instance = null; + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StateTelemetry.kt b/app/src/main/java/com/futo/platformplayer/states/StateTelemetry.kt new file mode 100644 index 00000000..61e07910 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/states/StateTelemetry.kt @@ -0,0 +1,77 @@ +package com.futo.platformplayer.states + +import android.content.Context +import android.os.Build +import com.futo.platformplayer.BuildConfig +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.Telemetry +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.StringStorage +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.time.Instant +import java.util.UUID + +class StateTelemetry { + private val _id = FragmentedStorage.get("id"); + + fun initialize() { + if (_id.value.isEmpty()) { + _id.setAndSave(UUID.randomUUID().toString()); + } + } + + fun upload() { + GlobalScope.launch(Dispatchers.IO) { + try { + val telemetry = Telemetry( + _id.value, + BuildConfig.APPLICATION_ID, + BuildConfig.VERSION_CODE.toString(), + BuildConfig.VERSION_NAME, + BuildConfig.BUILD_TYPE, + BuildConfig.DEBUG, + BuildConfig.IS_UNSTABLE_BUILD, + Instant.now().epochSecond, + Build.BRAND, + Build.MANUFACTURER, + Build.MODEL + ); + + val headers = hashMapOf( + "Content-Type" to "text/plain" + ); + + val json = Json.encodeToString(telemetry); + val url = "https://logs.grayjay.app/telemetry"; + //val url = "http://10.0.0.5:5413/telemetry"; + val client = ManagedHttpClient(); + val response = client.post(url, json, headers); + if (response.isOk) { + Logger.i(TAG, "Launch telemetry submitted."); + } else { + Logger.w(TAG, "Failed to submit launch telemetry (${response.code}): '${response.body?.string()}'."); + } + } catch (e: Throwable) { + Logger.w(TAG, "Failed to submit launch telemetry.", e); + } + } + } + + companion object { + private var _instance: StateTelemetry? = null; + val instance: StateTelemetry + get(){ + if(_instance == null) + _instance = StateTelemetry(); + return _instance!!; + }; + + private const val TAG = "StateTelemetry"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StateUpdate.kt b/app/src/main/java/com/futo/platformplayer/states/StateUpdate.kt new file mode 100644 index 00000000..1a5e4c80 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/states/StateUpdate.kt @@ -0,0 +1,278 @@ +package com.futo.platformplayer.states + +import android.content.Context +import android.os.Build +import android.os.Environment +import com.futo.platformplayer.* +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.logging.Logger +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File +import java.io.FileNotFoundException +import java.io.InputStream +import java.io.OutputStream + +class StateUpdate { + private var _backgroundUpdateFinished = false; + private var _gettingOrDownloadingLastApk = false; + private var _shouldBackgroundUpdate = false; + private val _lockObject = Object(); + + private fun getOrDownloadLastApkFile(filesDir: File): File? { + try { + Logger.i(TAG, "Started getting or downloading latest APK file."); + + if (!_shouldBackgroundUpdate) { + Logger.i(TAG, "Update download cancelled 1."); + return null; + } + + Logger.i(TAG, "Started background update download."); + val client = ManagedHttpClient(); + val latestVersion = downloadVersionCode(client); + if (!_shouldBackgroundUpdate) { + Logger.i(TAG, "Update download cancelled 2."); + return null; + } + + if (latestVersion != null) { + val currentVersion = BuildConfig.VERSION_CODE; + Logger.i(TAG, "Current version ${currentVersion} latest version ${latestVersion}."); + + if (latestVersion <= currentVersion) { + Logger.i(TAG, "Already up to date."); + _backgroundUpdateFinished = true; + return null; + } + + val outputDirectory = File(filesDir, "autoupdate"); + if (!outputDirectory.exists()) { + outputDirectory.mkdirs(); + } + + if (!_shouldBackgroundUpdate) { + Logger.i(TAG, "Update download cancelled 3."); + return null; + } + + val apkOutputFile = File(outputDirectory, "last_version.apk"); + val versionOutputFile = File(outputDirectory, "last_version.txt"); + + var cachedVersionInvalid = false; + if (!versionOutputFile.exists() || !apkOutputFile.exists()) { + Logger.i(TAG, "No downloaded version exists."); + cachedVersionInvalid = true; + } else { + try { + val downloadedVersion = versionOutputFile.readText().toInt(); + Logger.i(TAG, "Downloaded version is $downloadedVersion."); + if (downloadedVersion != latestVersion) { + Logger.i(TAG, "Downloaded version is not newest version."); + cachedVersionInvalid = true; + } + } + catch(ex: Throwable) { + Logger.w(TAG, "Deleted version file as it was inaccessible"); + versionOutputFile.delete(); + cachedVersionInvalid = true; + } + } + + if (!_shouldBackgroundUpdate) { + Logger.i(TAG, "Update download cancelled 4."); + return null; + } + + if (cachedVersionInvalid) { + Logger.i(TAG, "Downloading new APK to '${apkOutputFile.path}'..."); + downloadApkToFile(client, apkOutputFile) { !_shouldBackgroundUpdate }; + versionOutputFile.writeText(latestVersion.toString()); + + Logger.i(TAG, "Downloaded APK to '${apkOutputFile.path}'."); + } else { + Logger.i(TAG, "Latest APK is already downloaded in '${apkOutputFile.path}'..."); + } + + if (!_shouldBackgroundUpdate) { + Logger.i(TAG, "Update download cancelled 5."); + return null; + } + + return apkOutputFile; + } else { + Logger.w(TAG, "Failed to retrieve version from version URL."); + return null; + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to download APK.", e); + return null; + } finally { + _gettingOrDownloadingLastApk = false; + } + } + + fun setShouldBackgroundUpdate(shouldBackgroundUpdate: Boolean) { + synchronized (_lockObject) { + if (_backgroundUpdateFinished) { + _shouldBackgroundUpdate = false; + return; + } + + _shouldBackgroundUpdate = shouldBackgroundUpdate; + if (shouldBackgroundUpdate && !_gettingOrDownloadingLastApk) { + Logger.i(TAG, "Auto Updating in Background"); + + _gettingOrDownloadingLastApk = true; + StateApp.withContext { context -> + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { + try { + val file = getOrDownloadLastApkFile(context.filesDir); + if (file == null) { + Logger.i(TAG, "Failed to get or download update."); + return@launch; + } + + withContext(Dispatchers.Main) { + try { + context.let { c -> + _backgroundUpdateFinished = true; + UIDialogs.showInstallDownloadedUpdateDialog(c, file); + }; + Logger.i(TAG, "Showing install dialog for '${file.path}'."); + } catch (e: Throwable) { + context.let { c -> UIDialogs.toast(c, "Failed to show update dialog"); }; + Logger.w(TAG, "Error occurred in update dialog.", e); + } + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to get last downloaded APK file.", e) + } + } + } + } + } + } + + fun checkForUpdates(context: Context, showUpToDateToast: Boolean) { + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { + try { + val client = ManagedHttpClient(); + val latestVersion = downloadVersionCode(client); + + if (latestVersion != null) { + val currentVersion = BuildConfig.VERSION_CODE; + Logger.i(TAG, "Current version ${currentVersion} latest version ${latestVersion}."); + + if (latestVersion > currentVersion) { + withContext(Dispatchers.Main) { + try { + UIDialogs.showUpdateAvailableDialog(context, latestVersion); + } catch (e: Throwable) { + UIDialogs.toast(context, "Failed to show update dialog"); + Logger.w(TAG, "Error occurred in update dialog."); + } + } + } else { + if (showUpToDateToast) { + withContext(Dispatchers.Main) { + UIDialogs.toast(context, "Already on latest version"); + } + } + } + } else { + Logger.w(TAG, "Failed to retrieve version from version URL."); + + withContext(Dispatchers.Main) { + UIDialogs.toast(context, "Failed to retrieve version"); + } + } + } catch (e: Throwable) { + Logger.w(TAG, "Failed to check for updates.", e); + + withContext(Dispatchers.Main) { + UIDialogs.toast(context, "Failed to check for updates"); + } + } + }; + } + + private fun downloadApkToFile(client: ManagedHttpClient, destinationFile: File, isCancelled: (() -> Boolean)? = null) { + var apkStream: InputStream? = null; + var outputStream: OutputStream? = null; + + try { + val response = client.get(APK_URL); + if (response.isOk && response.body != null) { + apkStream = response.body.byteStream(); + outputStream = destinationFile.outputStream(); + apkStream.copyToOutputStream(outputStream, isCancelled); + apkStream.close(); + outputStream.close(); + } + } finally { + apkStream?.close(); + outputStream?.close(); + } + } + + fun downloadVersionCode(client: ManagedHttpClient): Int? { + val response = client.get(VERSION_URL); + if (!response.isOk || response.body == null) { + return null; + } + + return response.body.string().trim().toInt(); + } + + fun downloadChangelog(client: ManagedHttpClient, version: Int): String? { + val response = client.get("${CHANGELOG_BASE_URL}/${version}"); + if (!response.isOk || response.body == null) { + return null; + } + + return response.body.string().trim(); + } + + companion object { + private val TAG = "StateUpdate"; + + private var _instance : StateUpdate? = null; + val instance : StateUpdate + get(){ + if(_instance == null) + _instance = StateUpdate(); + return _instance!!; + }; + + val APP_SUPPORTED_ABIS = arrayOf("x86", "x86_64", "arm64-v8a", "armeabi-v7a"); + val DESIRED_ABI: String get() { + for (i in 0 until Build.SUPPORTED_ABIS.size) { + val abi = Build.SUPPORTED_ABIS[i]; + if (APP_SUPPORTED_ABIS.contains(abi)) { + return abi; + } + } + + throw Exception("App is not compatible. Supported ABIS: ${Build.SUPPORTED_ABIS.joinToString()}}."); + }; + val VERSION_URL = if (BuildConfig.IS_UNSTABLE_BUILD) { + "https://releases.grayjay.app/version-unstable.txt" + } else { + "https://releases.grayjay.app/version.txt" + } + val APK_URL = if (BuildConfig.IS_UNSTABLE_BUILD) { + "https://releases.grayjay.app/app-$DESIRED_ABI-release-unstable.apk" + } else { + "https://releases.grayjay.app/app-$DESIRED_ABI-release.apk" + } + val CHANGELOG_BASE_URL = "https://releases.grayjay.app/changelogs"; + + fun finish() { + _instance?.let { + _instance = null; + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/CastingDeviceInfoStorage.kt b/app/src/main/java/com/futo/platformplayer/stores/CastingDeviceInfoStorage.kt new file mode 100644 index 00000000..40d375b2 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/stores/CastingDeviceInfoStorage.kt @@ -0,0 +1,47 @@ +package com.futo.platformplayer.stores + +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.CastingDeviceInfo +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +@kotlinx.serialization.Serializable +class CastingDeviceInfoStorage : FragmentedStorageFileJson() { + var deviceInfos = mutableListOf(); + + @Synchronized + fun getDevicesCount(): Int { + return deviceInfos.size; + } + + @Synchronized + fun getDevices() : List { + return deviceInfos.toList(); + } + + @Synchronized + fun addDevice(castingDeviceInfo: CastingDeviceInfo): Boolean { + if (deviceInfos.any { d -> d.name == castingDeviceInfo.name }) { + Logger.i("CastingDeviceInfoStorage", "Device '${castingDeviceInfo.name}' already existed in device storage.") + return false; + } + + if (deviceInfos.size >= 5) { + deviceInfos.removeAt(0); + } + + deviceInfos.add(castingDeviceInfo); + save(); + return true; + } + + @Synchronized + fun removeDevice(name: String) { + deviceInfos.removeIf { d -> d.name == name }; + save(); + } + + override fun encode(): String { + return Json.encodeToString(this); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorage.kt b/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorage.kt new file mode 100644 index 00000000..a996a9c8 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorage.kt @@ -0,0 +1,229 @@ +package com.futo.platformplayer.stores + +import com.futo.platformplayer.Settings +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.stores.v2.JsonStoreSerializer +import com.futo.platformplayer.stores.v2.ManagedStore +import com.futo.platformplayer.stores.v2.StoreSerializer +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlinx.serialization.modules.SerializersModule +import java.io.File +import java.util.UUID +import kotlin.reflect.KType +import kotlin.reflect.full.createInstance +import kotlin.reflect.full.createType + +@Serializable() +class FragmentedStorage { + companion object { + val TAG = "LocalStorage"; + + @kotlin.jvm.Transient + val jsonSerializer = Json { ignoreUnknownKeys = true; }; + + var _cachedFiles = hashMapOf(); + var _cachedDirectories = hashMapOf(); + + var _filesDir: File? = null; + val isInitialized: Boolean get() = _filesDir != null; + + fun initialize(filesDir: File) { + _filesDir = filesDir; + } + + inline fun storeJson(parentDir: File, name: String, serializer: KSerializer? = null): ManagedStore = store(name, JsonStoreSerializer.create(serializer), null, parentDir); + inline fun storeJson(name: String, prettyName: String? = null, parentDir: File? = null): ManagedStore = store(name, JsonStoreSerializer.create(), prettyName, parentDir); + inline fun store(name: String, serializer: StoreSerializer, prettyName: String? = null, parentDir: File? = null): ManagedStore { + return ManagedStore(name, parentDir ?: _filesDir!!, kotlin.reflect.typeOf() , serializer); + } + + inline fun replace(text: String, verify: Boolean = true): T where T: FragmentedStorageFile { + if(verify) { + val dir = getOrCreateDirectory("temp"); + val tempFile = File(dir, UUID.randomUUID().toString()); + tempFile.writeText(text); + + val parsed = if (FragmentedStorageFileJson::class.java.isAssignableFrom(T::class.java)) + loadJsonFile(tempFile, null) + else if (FragmentedStorageFileString::class.java.isAssignableFrom(T::class.java)) + loadTextFile(tempFile, null) + else + throw NotImplementedError("Unknown file type for ${T::class.java.name}"); + + if (parsed == null) + throw IllegalStateException("Failed to import type [${T::class.java.name}]"); + } + + val name = T::class.java.name; + synchronized(_cachedFiles) { + val cachedFile = _cachedFiles[name]; + val file = cachedFile?.getUnderlyingFile() ?: File(_filesDir, "${T::class.java.name}.json"); + file.writeText(text); + _cachedFiles.remove(name); + } + return load(); + } + + inline fun get(reload: Boolean = false): T where T : FragmentedStorageFile { + val name = T::class.java.name; + + synchronized(_cachedFiles) { + if(reload) + _cachedFiles.remove(name); + var instance = _cachedFiles.get(name); + if (instance == null) { + instance = load(); + _cachedFiles[name] = instance; + return instance; + } + + return instance as T; + } + } + inline fun get(name: String): T where T : FragmentedStorageFile { + synchronized(_cachedFiles) { + var instance = _cachedFiles.get(name); + if (instance == null) { + instance = load(name); + _cachedFiles[name] = instance; + return instance; + } + + return instance as T; + } + } + inline fun getDirectory(): T { + val name = T::class.java.name; + + synchronized(_cachedDirectories) { + var instance = _cachedDirectories.get(name); + if (instance == null) { + instance = loadDirectory(getOrCreateDirectory(name)); + _cachedDirectories[name] = instance; + return instance; + } + + return instance as T; + } + } + + inline fun load(): T where T : FragmentedStorageFile { + if (_filesDir == null) { + throw Exception("Files dir should be initialized before loading a file.") + } + + val storageFile = File(_filesDir, "${T::class.java.name}.json"); + val storageBakFile = File(_filesDir, "${T::class.java.name}.json.bak"); + return loadFile(storageFile, storageBakFile); + } + inline fun load(dir: File, fileName: String): T where T : FragmentedStorageFile { + val storageFile = File(dir, "${fileName}"); + val storageBakFile = File(dir, "${fileName}.bak"); + return loadFile(storageFile, storageBakFile); + } + inline fun load(fileName: String): T where T : FragmentedStorageFile { + if (_filesDir == null) { + throw Exception("Files dir should be initialized before loading a file.") + } + + val storageFile = File(_filesDir, "${fileName}.json"); + val storageBakFile = File(_filesDir, "${fileName}.json.bak"); + return loadFile(storageFile, storageBakFile); + } + + fun loadFile(dir: File, fileName: String): File { + return File(dir, fileName); + } + + fun deleteFile(dir: File, fileName: String) { + val storageFile = File(dir, "${fileName}"); + val storageBakFile = File(dir, "${fileName}.bak"); + + if(storageFile.exists()) + storageFile.delete(); + if(storageBakFile.exists()) + storageBakFile.delete(); + } + + + fun getOrCreateDirectory(dirName: String) : File { + if (_filesDir == null) { + throw Exception("Files dir should be initialized before loading a file.") + } + + val dirFile = File(_filesDir, dirName); + if(!dirFile.exists()) + dirFile.mkdir(); + return dirFile; + } + inline fun loadFile(file: File, bakFile: File?): T where T : FragmentedStorageFile { + val typeName = T::class.java.name; + if (file.exists()) { + var resp = + if(FragmentedStorageFileJson::class.java.isAssignableFrom(T::class.java)) + loadJsonFile(file, bakFile) + else if(FragmentedStorageFileString::class.java.isAssignableFrom(T::class.java)) + loadTextFile(file, bakFile) + else + throw NotImplementedError("Unknown file type for ${typeName}"); + if(resp != null) + return resp; + } else { + Logger.w(TAG, "Failed to fragment storage because the file does not exist. Attempting backup. [${typeName}]"); + } + + if (bakFile?.exists() ?: false) { + var resp = + if(FragmentedStorageFileJson::class.java.isAssignableFrom(T::class.java)) + loadJsonFile(file, bakFile, true) + else if(FragmentedStorageFileString::class.java.isAssignableFrom(T::class.java)) + loadTextFile(file, bakFile, true) + else + throw NotImplementedError("Unknown file type"); + if(resp != null) + return resp; + } else { + Logger.w(TAG, "Failed to fragment storage because the backup file does not exist. Using default instance. [${typeName}]"); + } + + return (T::class.java.newInstance() as T).withFile(file, bakFile) as T; + } + inline fun loadDirectory(file: File): T where T : IFragmentedStorageDirectory { + return (T::class.java.newInstance() as T).withDirectory(file) as T; + } + + inline fun loadJsonFile(file: File, bakFile: File?, fromBak: Boolean = false) : T? { + try { + val json = (if(!fromBak) file else bakFile)?.readText() ?: return null; + val fileObj = jsonSerializer.decodeFromString(json); + if(fromBak && bakFile != null) + bakFile.copyTo(file, true); + return fileObj.withFile(file, bakFile) as T; + } catch (e: Throwable) { + if(!fromBak) + Logger.e(TAG, "Failed to load fragment storage. Attempting backup.", e); + else + Logger.e(TAG, "Failed to load fragment storage. Using default instance.", e); + } + return null; + } + inline fun loadTextFile(file: File, bakFile: File?, fromBak: Boolean = false) : T? { + try { + val text = (if(!fromBak) file else bakFile)?.readText() ?: return null; + val fileObj = (T::class.java.newInstance() as T).withFile(file, bakFile) as T + (fileObj as FragmentedStorageFileString).decode(text); + if(fromBak && bakFile != null) + bakFile.copyTo(file, true); + return fileObj.withFile(file, bakFile) as T; + } catch (e: Throwable) { + if(!fromBak) + Logger.w(TAG, "Failed to load fragment storage. Attempting backup.", e); + else + Logger.w(TAG, "Failed to load fragment storage. Using default instance.", e); + } + return null; + } + } + //endregion +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageDirectory.kt b/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageDirectory.kt new file mode 100644 index 00000000..08b2ec10 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageDirectory.kt @@ -0,0 +1,40 @@ +package com.futo.platformplayer.stores + +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import java.io.File + +interface IFragmentedStorageDirectory { + fun withDirectory(dir: File): IFragmentedStorageDirectory; +} + +@Serializable +open class FragmentedStorageDirectory : IFragmentedStorageDirectory { + private val TAG = "FragmentedStorageDirectory"; + + @Transient + var directory: File? = null; + + fun getFiles() : List { + return directory!!.listFiles() + .filter { it.extension != ".bak" } + .map { it.name }; + } + fun hasFile(name: String) : Boolean { + return File(directory, name).exists(); + } + fun getFileReference(name: String): File { + return FragmentedStorage.loadFile(directory!!, name); + } + inline fun getFileOrCreate(name : String) : T{ + return FragmentedStorage.load(directory!!, name); + } + fun deleteFile(name: String) { + FragmentedStorage.deleteFile(directory!!, name); + } + + override fun withDirectory(dir: File): IFragmentedStorageDirectory { + directory = dir; + return this; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageFile.kt b/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageFile.kt new file mode 100644 index 00000000..7733fcfc --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageFile.kt @@ -0,0 +1,73 @@ +package com.futo.platformplayer.stores + +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.constructs.BackgroundTaskHandler +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import java.io.File + + +@Serializable +open class FragmentedStorageFile() { + @kotlinx.serialization.Transient + private val _lock = Object(); + + private val TAG = "FragmentedStorageFile"; + + @Transient + private var _file: File? = null; + + @Transient + private var _bakFile: File? = null; + + + @Transient + private val _backgroundSave = BackgroundTaskHandler(StateApp.instance.scope, { + saveBlocking(); + }); + + fun getUnderlyingFile(): File? { + return _file; + } + + fun withFile(file: File, bakFile: File?): FragmentedStorageFile { + _file = file; + _bakFile = bakFile; + return this; + } + + fun save() { + _backgroundSave.run(); + } + fun saveBlocking() { + synchronized(_lock) { + val file = _file; + if (file == null) { + Logger.w(TAG, "Failed to flush settings because file was null.") + return; + } + + if (file.exists()) { + val bakFile = _bakFile; + if (bakFile != null) { + file.copyTo(bakFile, true); + } + } + + val json = encode(); + file.writeText(json); + } + } + + fun delete() { + if(_file?.exists() ?: false) + _file?.delete(); + if(_bakFile?.exists() ?: false) + _bakFile?.delete(); + } + + open fun encode(): String { + return "{}"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageFileJson.kt b/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageFileJson.kt new file mode 100644 index 00000000..5132e070 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageFileJson.kt @@ -0,0 +1,11 @@ +package com.futo.platformplayer.stores + +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +abstract class FragmentedStorageFileJson : FragmentedStorageFile() { + + override fun encode(): String { + return Json.encodeToString(this); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageFileString.kt b/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageFileString.kt new file mode 100644 index 00000000..f03c85ea --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageFileString.kt @@ -0,0 +1,12 @@ +package com.futo.platformplayer.stores + +open class FragmentedStorageFileString : FragmentedStorageFile() { + var value : String? = null; + + override fun encode(): String { + return value ?: ""; + } + open fun decode(str: String) { + value = str; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/MapStorage.kt b/app/src/main/java/com/futo/platformplayer/stores/MapStorage.kt new file mode 100644 index 00000000..b9c036ac --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/stores/MapStorage.kt @@ -0,0 +1,31 @@ +package com.futo.platformplayer.stores + +import com.futo.platformplayer.polycentric.PolycentricCache +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +@kotlinx.serialization.Serializable +class CachedPolycentricProfileStorage : FragmentedStorageFileJson() { + var map: HashMap = hashMapOf(); + + override fun encode(): String { + val encoded = Json.encodeToString(this); + return encoded; + } + + fun get(key: String) : PolycentricCache.CachedPolycentricProfile? { + return map[key]; + } + + fun setAndSave(key: String, value: PolycentricCache.CachedPolycentricProfile) : PolycentricCache.CachedPolycentricProfile { + map[key] = value; + save(); + return value; + } + + fun setAndSaveBlocking(key: String, value: PolycentricCache.CachedPolycentricProfile) : PolycentricCache.CachedPolycentricProfile { + map[key] = value; + saveBlocking(); + return value; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/PluginIconStorage.kt b/app/src/main/java/com/futo/platformplayer/stores/PluginIconStorage.kt new file mode 100644 index 00000000..e14ce3ad --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/stores/PluginIconStorage.kt @@ -0,0 +1,42 @@ +package com.futo.platformplayer.stores + +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.ImageVariable +import java.io.File + +class PluginIconStorage : FragmentedStorageDirectory() { + + fun hasIcon(name: String) : Boolean { + val ref = getFileReference(name); + return ref.exists(); + } + + fun getIconBinary(name: String) : ImageVariable { + return ImageVariable.fromFile(getFileOrThrow(name)); + } + fun saveIconBinary(name: String, binary: ByteArray) { + val file = getFileReference(name); + try { + file.writeBytes(binary); + } + catch(ex: Throwable) { + Logger.e("Failed to save icon", ex.message, ex); + file.delete(); + } + finally { + } + } + fun deleteIconBinary(name: String) { + val file = getFileReference(name); + if(file.exists()) + file.delete(); + } + + + fun getFileOrThrow(name: String) : File { + val ref = getFileReference(name); + if(!ref.exists()) + throw IllegalArgumentException("File does not exist [${name}]"); + return ref; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/PluginScriptsDirectory.kt b/app/src/main/java/com/futo/platformplayer/stores/PluginScriptsDirectory.kt new file mode 100644 index 00000000..56d41bff --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/stores/PluginScriptsDirectory.kt @@ -0,0 +1,17 @@ +package com.futo.platformplayer.stores + +class PluginScriptsDirectory : FragmentedStorageDirectory() { + fun getScript(id: String) : String? { + if(hasFile(id)) + return getFileOrCreate(id).value; + return null; + } + fun setScript(id: String, script: String) { + val file = getFileOrCreate(id); + file.value = script; + file.saveBlocking(); + } + fun removeScript(id: String) { + deleteFile(id); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/PluginStorage.kt b/app/src/main/java/com/futo/platformplayer/stores/PluginStorage.kt new file mode 100644 index 00000000..b9fcfc57 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/stores/PluginStorage.kt @@ -0,0 +1,15 @@ +package com.futo.platformplayer.stores + +import com.futo.platformplayer.api.media.platforms.js.SourcePluginDescriptor +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +@kotlinx.serialization.Serializable +class PluginStorage : FragmentedStorageFileJson() { + + var sourcePlugins = mutableListOf(); + + override fun encode(): String { + return Json.encodeToString(this); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/SearchHistoryStorage.kt b/app/src/main/java/com/futo/platformplayer/stores/SearchHistoryStorage.kt new file mode 100644 index 00000000..2bd43968 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/stores/SearchHistoryStorage.kt @@ -0,0 +1,26 @@ +package com.futo.platformplayer.stores + +import kotlinx.serialization.* +import kotlinx.serialization.json.Json + +@Serializable() +class SearchHistoryStorage : FragmentedStorageFileJson() { + var lastQueries = arrayListOf(); + + fun add(text: String) { + if (!lastQueries.contains(text)) { + lastQueries.add(0, text); + if (lastQueries.size > 10) + lastQueries.removeLast(); + } + else { + lastQueries.remove(text); + lastQueries.add(0, text); + } + save(); + } + + override fun encode(): String { + return Json.encodeToString(this); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/StringArrayStorage.kt b/app/src/main/java/com/futo/platformplayer/stores/StringArrayStorage.kt new file mode 100644 index 00000000..be1e69e3 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/stores/StringArrayStorage.kt @@ -0,0 +1,44 @@ +package com.futo.platformplayer.stores + +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +@kotlinx.serialization.Serializable +class StringArrayStorage : FragmentedStorageFileJson() { + + var values = mutableListOf(); + + override fun encode(): String { + return Json.encodeToString(this); + } + + fun add(obj: String) { + synchronized(values) { + values.add(obj) + } + } + fun addDistinct(obj: String) { + synchronized(values) { + if(!values.any { it == obj }) + values.add(obj); + } + } + + fun remove(obj: String) { + synchronized(values) { + values.removeIf { it == obj }; + } + } + fun set(vararg objs: String) { + synchronized(values) { + values.clear(); + values.addAll(objs); + } + } + + fun getAllValues(): List { + synchronized(values){ + return values.toList(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/StringHashSetStorage.kt b/app/src/main/java/com/futo/platformplayer/stores/StringHashSetStorage.kt new file mode 100644 index 00000000..0f397782 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/stores/StringHashSetStorage.kt @@ -0,0 +1,50 @@ +package com.futo.platformplayer.stores + +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +@kotlinx.serialization.Serializable +class StringHashSetStorage : FragmentedStorageFileJson() { + + var values = HashSet(); + + override fun encode(): String { + return Json.encodeToString(this); + } + + fun contains(obj: String): Boolean { + synchronized(values) { + return values.contains(obj); + } + } + + fun add(obj: String) { + synchronized(values) { + values.add(obj) + } + } + fun addDistinct(obj: String) { + synchronized(values) { + if(!values.contains(obj)) + values.add(obj); + } + } + + fun remove(obj: String) { + synchronized(values) { + values.remove(obj); + } + } + fun set(vararg objs: String) { + synchronized(values) { + values.clear(); + values.addAll(objs); + } + } + + fun getAllValues(): List { + synchronized(values){ + return values.toList(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/StringStorage.kt b/app/src/main/java/com/futo/platformplayer/stores/StringStorage.kt new file mode 100644 index 00000000..e029adf9 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/stores/StringStorage.kt @@ -0,0 +1,26 @@ +package com.futo.platformplayer.stores + +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +@kotlinx.serialization.Serializable +class StringStorage : FragmentedStorageFileJson() { + + var value : String = ""; + + override fun encode(): String { + return Json.encodeToString(this); + } + + fun setAndSave(str: String) : String { + value = str; + save(); + return value; + } + + fun setAndSaveBlocking(str: String) : String { + value = str; + saveBlocking(); + return value; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/SubscriptionStorage.kt b/app/src/main/java/com/futo/platformplayer/stores/SubscriptionStorage.kt new file mode 100644 index 00000000..1e047dca --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/stores/SubscriptionStorage.kt @@ -0,0 +1,32 @@ +package com.futo.platformplayer.stores + +import com.futo.platformplayer.states.StateSubscriptions +import com.futo.platformplayer.models.Subscription +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import kotlinx.serialization.* +import kotlinx.serialization.json.Json + +@Serializable() +class SubscriptionStorage : FragmentedStorageFileJson() { + var version = StateSubscriptions.VERSION; + var subscriptions = arrayListOf(); + + fun addSubscription(channel: Subscription) : Subscription { + subscriptions.add(channel); + return channel; + } + + fun removeSubscription(url : String) { + val toRemove = subscriptions.firstOrNull { it.channel.url == url }; + subscriptions.removeIf { it.channel.url == url }; + } + + fun isSubscribedTo(channel: IPlatformChannel): Boolean = isSubscribedTo(channel.url); + fun isSubscribedTo(channel: PlatformAuthorLink): Boolean = isSubscribedTo(channel.url); + fun isSubscribedTo(url: String) : Boolean = subscriptions.any { u -> u.channel.url == url }; + + override fun encode(): String { + return Json.encodeToString(this); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/WatchLaterStorage.kt b/app/src/main/java/com/futo/platformplayer/stores/WatchLaterStorage.kt new file mode 100644 index 00000000..0c1b77ea --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/stores/WatchLaterStorage.kt @@ -0,0 +1,15 @@ +package com.futo.platformplayer.stores + +import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +@kotlinx.serialization.Serializable +class WatchLaterStorage : FragmentedStorageFileJson() { + + var playlist = listOf(); + + override fun encode(): String { + return Json.encodeToString(this); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/v2/IStoreItem.kt b/app/src/main/java/com/futo/platformplayer/stores/v2/IStoreItem.kt new file mode 100644 index 00000000..5273ea3c --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/stores/v2/IStoreItem.kt @@ -0,0 +1,5 @@ +package com.futo.platformplayer.stores.v2 + +interface IStoreItem { + fun onDelete(); +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/v2/ManagedStore.kt b/app/src/main/java/com/futo/platformplayer/stores/v2/ManagedStore.kt new file mode 100644 index 00000000..a34aa49d --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/stores/v2/ManagedStore.kt @@ -0,0 +1,500 @@ +package com.futo.platformplayer.stores.v2 + +import com.futo.platformplayer.assume +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateApp +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.serialization.KSerializer +import kotlinx.serialization.json.Json +import kotlinx.serialization.serializer +import java.io.File +import java.io.FileNotFoundException +import java.lang.Exception +import java.util.* +import kotlin.collections.ArrayList +import kotlin.collections.HashMap +import kotlin.reflect.KClass +import kotlin.reflect.KType +import kotlin.reflect.javaType + +class ManagedStore{ + private val _class: KType; + private val _name: String; + private val _dir: File; + private val _files: HashMap = hashMapOf(); + private val _serializer: StoreSerializer; + + private val _toReconstruct: ArrayList = ArrayList(); + + private var _isLoaded = false; + + private var _withBackup: Boolean = true; + private var _reconstructStore: ReconstructStore? = null; + + private var _withUnique: ((T) -> Any)? = null; + + val className: String? get() = _class.classifier?.assume>()?.simpleName; + + val name: String; + + constructor(name: String, dir: File, clazz: KType, serializer: StoreSerializer, niceName: String? = null) { + _name = name; + this.name = niceName ?: name.let { + if(it.length > 0) + return@let it[0].uppercase() + it.substring(1); + return@let name; + }; + _serializer = serializer; + _class = clazz; + _dir = File(dir, name); + if(!_dir.exists()) + _dir.mkdir(); + } + + fun withUnique(handler: (T) -> Any): ManagedStore { + _withUnique = handler; + return this; + } + fun withRestore(backup: ReconstructStore): ManagedStore { + _reconstructStore = backup; + return this; + } + fun withoutBackup(): ManagedStore{ + _withBackup = false; + return this; + } + + fun load(): ManagedStore { + synchronized(_files) { + _files.clear(); + val newObjs = _dir.listFiles().map { it.nameWithoutExtension }.distinct().toList().map { fileId -> + //Logger.i(TAG, "FILE:" + it.name); + val mfile = ManagedFile(fileId, _dir); + val obj = mfile.load(this, _withBackup); + if(obj == null) { + Logger.w(TAG, "Deleting ${logName(mfile.id)}"); + mfile.delete(false); + if(mfile.reconstructFile.exists()) { + _toReconstruct.add(mfile); + Logger.i(TAG, "Reconstruction required: ${logName(fileId)}"); + } + } + + return@map Pair(obj, mfile); + }.filter { it.first != null }; + + for (obj in newObjs) + _files.put(obj.first!!, obj.second); + } + _isLoaded = true; + return this; + } + fun getMissingReconstructionCount(): Int { + synchronized(_toReconstruct) { + return _toReconstruct.size; + } + } + fun hasMissingReconstructions(): Boolean { + synchronized(_toReconstruct) { + return _toReconstruct.any(); + } + } + + fun deleteMissing() { + synchronized(_toReconstruct) { + for(file in _toReconstruct) + file.delete(true); + _toReconstruct.clear(); + } + } + suspend fun importReconstructions(items: List, onProgress: ((Int, Int)->Unit)? = null): ReconstructionResult { + var successes = 0; + val exs = ArrayList(); + + val total = items.size; + var finished = 0; + + val builder = ReconstructStore.Builder(); + + for (recon in items) { + //Retry once + for (i in 0 .. 1) { + try { + Logger.i(TAG, "Importing ${logName(recon)}"); + val reconId = createFromReconstruction(recon, builder); + successes++; + Logger.i(TAG, "Imported ${logName(reconId)}"); + break; + } catch (ex: Throwable) { + Logger.e(TAG, "Failed to reconstruct import", ex); + if (i == 1) { + exs.add(ex); + } + } + } + finished++; + onProgress?.invoke(finished, total); + } + return ReconstructionResult(successes, exs, builder.messages); + } + + suspend fun reconstructMissing(onProgress: ((Int, Int)->Unit)? = null): ReconstructionResult { + var successes = 0; + val exs = ArrayList(); + val missings = synchronized(_toReconstruct) { _toReconstruct.toList(); } + + val total = missings.size; + var finished = 0; + + val builder = ReconstructStore.Builder(); + + for (missing in missings) { + //Retry once + for (i in 0 .. 1) { + try { + Logger.i(TAG, "Started reconstructing ${logName(missing.id)}"); + val reconstructed = missing.reconstruct(this, builder); + + missing.write(_serializer.serialize(_class, reconstructed), _withBackup); + synchronized(_files) { + _files.put(reconstructed, missing); + } + synchronized(_toReconstruct) { + _toReconstruct.remove(missing); + } + successes++; + Logger.i(TAG, "Reconstructed ${logName(missing.id)}"); + break; + } catch (ex: Throwable) { + Logger.e(TAG, "Failed to reconstruct ${logName(missing.id)}", ex); + + if (i == 1) { + exs.add(ex); + } + } + finished++; + onProgress?.invoke(finished, total); + } + } + return ReconstructionResult(successes, exs, builder.messages); + } + + fun getItems(): List { + synchronized(_files) { + return _files.map { it.key }; + } + } + fun queryItem(query: (Iterable)->T?) : T? { + synchronized(_files) { + return query(_files.keys.asIterable()); + } + } + fun hasItems(): Boolean { + synchronized(_files) { + return _files.any(); + } + } + fun hasItem(query: (T)-> Boolean): Boolean { + synchronized(_files) { + return _files.keys.any { query(it) }; + } + } + fun findItem(query: (T)->Boolean): T? { + synchronized(_files) { + return _files.keys.find(query); + } + } + fun findItems(query: (T)->Boolean): List { + synchronized(_files) { + return _files.keys.filter(query); + } + } + + fun getFile(obj: T): ManagedFile? { + synchronized(_files) { + if(_files.containsKey(obj)) + return _files[obj]; + return null; + } + } + + + fun saveAsync(obj: T, withReconstruction: Boolean = false) { + val scope = StateApp.instance.scopeOrNull; + if(scope != null) + scope.launch(Dispatchers.IO) { + try { + save(obj, withReconstruction); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to save.", e); + } + }; + else + save(obj, withReconstruction); + } + fun saveAllAsync(objs: List, withReconstruction: Boolean = false) { + val scope = StateApp.instance.scopeOrNull; + if(scope != null) + scope.launch(Dispatchers.IO) { + saveAll(objs, withReconstruction); + }; + else + saveAll(objs, withReconstruction); + } + fun save(obj: T, withReconstruction: Boolean = false, onlyExisting: Boolean = false) { + synchronized(_files) { + val uniqueVal = if(_withUnique != null) + _withUnique!!(obj); + else null; + + var file = getFile(obj); + if (file != null) { + Logger.v(TAG, "Saving file ${logName(file.id)}"); + val encoded = _serializer.serialize(_class, obj); + file.write(encoded, _withBackup); + if(_reconstructStore != null && (_reconstructStore!!.backupOnSave || withReconstruction)) + saveReconstruction(file, obj); + } + else if(!onlyExisting && (uniqueVal == null || !_files.any { _withUnique!!(it.key) == uniqueVal })) { + file = saveNew(obj); + if(_reconstructStore != null && (_reconstructStore!!.backupOnCreate || withReconstruction)) + saveReconstruction(file, obj); + } + } + } + fun saveAll(items: List, withReconstruction: Boolean = false, onlyExisting: Boolean = false) { + for(obj in items) + save(obj, withReconstruction, onlyExisting); + } + + suspend fun createFromReconstruction(reconstruction: String, builder: ReconstructStore.Builder): String { + if(_reconstructStore == null) + throw IllegalStateException("Can't reconstruct as no reconstruction is implemented for this type"); + + val id = UUID.randomUUID().toString(); + val reconstruct = _reconstructStore!!.toObjectWithHeader(id, reconstruction, builder); + save(reconstruct); + return id; + } + + fun delete(item: T) { + synchronized(_files) { + val file = _files[item]; + if(file != null) { + if(item is IStoreItem) + item.onDelete(); + _files.remove(item); + Logger.v(TAG, "Deleting file ${logName(file.id)}"); + file.delete(); + } + } + } + fun deleteAll() { + synchronized(_files) { + val keys = _files.keys.toList(); + for(key in keys) + delete(key); + } + } + + private fun saveNew(obj: T): ManagedFile { + synchronized(_files) { + val id = UUID.randomUUID().toString(); + Logger.v(TAG, "New file ${logName(id)}"); + val encoded = _serializer.serialize(_class, obj); + + val mfile = ManagedFile(id, _dir); + mfile.write(encoded, _withBackup); + + _files.put(obj, mfile); + return mfile; + } + } + + fun getAllReconstructionStrings(withHeader: Boolean = false): List { + if(_reconstructStore == null) + throw IllegalStateException("Can't reconstruct as no reconstruction is implemented for this type"); + + return getItems().map { + getReconstructionString(it, withHeader) + }; + } + fun getReconstructionString(obj: T, withHeader: Boolean = false): String { + if(_reconstructStore == null) + throw IllegalStateException("Can't reconstruct as no reconstruction is implemented for this type"); + + if(withHeader) + return _reconstructStore!!.toReconstructionWithHeader(obj, className ?: ""); + else + return _reconstructStore!!.toReconstruction(obj); + } + private fun saveReconstruction(file: ManagedFile, obj: T) { + if(_reconstructStore == null) + return; + val reconstruction = getReconstructionString(obj, true); + file.writeReconstruction(reconstruction); + } + + fun isReconstructionIdentifier(identifier: String): Boolean { + if(_reconstructStore == null) + throw IllegalStateException("Can't reconstruct as no reconstruction is implemented for this type"); + + return identifier == (_reconstructStore?.identifierName ?: className) + } + fun isReconstructionHeader(recon: String): Boolean { + val identifier = getReconstructionIdentifier(recon); + return identifier != null && isReconstructionIdentifier(identifier); + } + + class ManagedFile( + val id: String, + val dir: File + ) { + val file: File = File(dir, id); + val bakFile: File = File(dir, id + ".bak"); + val reconstructFile: File = File(dir, id + ".rec"); + + fun load(store: ManagedStore, withBackup: Boolean = true): T? { + synchronized(this) { + try { + if(!file.exists()) + throw FileNotFoundException(); + val data = read(); + + //Uncomment to test migration + //if(className == "Subscription") throw IllegalStateException("Test Exception"); + + return store._serializer.deserialize(store._class, data); + } + catch(ex: Throwable) { + if(ex !is FileNotFoundException) + Logger.w(TAG, "Failed to parse ${store.logName(id)}", ex); + + if(withBackup) { + val backData = readBackup(); + try { + if (backData != null) { + Logger.i(TAG, "Loading from backup ${store.logName(id)}"); + return store._serializer.deserialize(store._class, backData); + } else Logger.i(TAG, "No backup exists for ${store.logName(id)}") + } catch (bakEx: Throwable) { + Logger.w(TAG, "Failed to bakfile parse ${store.logName(id)}", bakEx); + } + } + } + + Logger.w(TAG, "No object from ${store.logName(id)}"); + return null; + } + } + + suspend fun reconstruct(store: ManagedStore, builder: ReconstructStore.Builder): T { + if(store._reconstructStore == null) + throw IllegalStateException("No reconstruction logic exists?"); + + val reconstruction = readReconstruction() + ?: throw FileNotFoundException("No reconstruction found"); + + val reconstructed: T; + try { + reconstructed = store._reconstructStore!!.toObjectWithHeader(id, reconstruction, builder); + } + catch(ex: Throwable) { + throw ex; + } + return reconstructed; + } + + + fun write(data: ByteArray, withBackup: Boolean = true) { + if(withBackup && file.exists()) + file.copyTo(bakFile, true); + file.writeBytes(data); + } + fun writeReconstruction(str: String) { + reconstructFile.writeText(str, Charsets.UTF_8); + } + + fun read(): ByteArray { + return file.readBytes(); + } + fun readBackup(): ByteArray? { + if(bakFile.exists()) + return bakFile.readBytes(); + return null; + } + fun readReconstruction(): String? { + if(reconstructFile.exists()) + return reconstructFile.readText(Charsets.UTF_8); + return null; + } + + fun delete(deleteReconstruction: Boolean = true) { + if(file.exists()) + file.delete(); + if(bakFile.exists()) + bakFile.delete(); + if(deleteReconstruction && reconstructFile.exists()) + reconstructFile.delete(); + } + } + + data class ReconstructionResult( + val success: Int = 0, + val exceptions: List, + val messages: List + ); + + private fun logName(id: String?): String { + return "${_name}:[${(_class.classifier as KClass<*>).simpleName}] ${id ?: ""}"; + } + + companion object { + val TAG = "ManagedStore"; + val RECONSTRUCTION_HEADER_OPERATOR = "@/"; + + fun getReconstructionIdentifier(recon: String): String? { + if(!recon.startsWith(RECONSTRUCTION_HEADER_OPERATOR) || !recon.contains("\n")) + return null; + else + return recon.substring(2, recon.indexOf("\n")); + } + } +} + +interface StoreSerializer { + fun serialize(clazz: KType, obj: T): ByteArray; + fun deserialize(clazz: KType, obj: ByteArray): T; +} + +class JsonStoreSerializer: StoreSerializer { + private val _serializer: KSerializer + val jsonSer = Json { ignoreUnknownKeys = true; encodeDefaults = true; } + + constructor(serializer: KSerializer) { + _serializer = serializer; + } + + override fun serialize(clazz: KType, obj: T): ByteArray { + val json = jsonSer.encodeToString(_serializer,obj)//gson.toJson(obj); + return json.toByteArray(Charsets.UTF_8); + } + + override fun deserialize(clazz: KType, obj: ByteArray): T { + val json = String(obj, Charsets.UTF_8); + try { + return jsonSer.decodeFromString(_serializer, json); + } + catch(ex: Throwable) { + Logger.e(ManagedStore.TAG, "Json for ${(clazz.classifier as KClass<*>).simpleName}:\n" + json, ex); + throw ex; + } + } + + companion object { + inline fun create(serializer: KSerializer? = null): JsonStoreSerializer { + return JsonStoreSerializer(if(serializer != null) serializer else serializer()); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/stores/v2/ReconstructStore.kt b/app/src/main/java/com/futo/platformplayer/stores/v2/ReconstructStore.kt new file mode 100644 index 00000000..78004a29 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/stores/v2/ReconstructStore.kt @@ -0,0 +1,33 @@ +package com.futo.platformplayer.stores.v2 + +abstract class ReconstructStore { + open val backupOnSave: Boolean = false; + open val backupOnCreate: Boolean = true; + + val identifierName: String?; + + constructor(identifierName: String? = null) { + this.identifierName = identifierName; + } + + abstract fun toReconstruction(obj: T): String; + abstract suspend fun toObject(id: String, backup: String, reconstructionBuilder: Builder): T; + + fun toReconstructionWithHeader(obj: T, fallbackName: String): String { + val identifier = identifierName ?: fallbackName; + return "@/${identifier}\n${toReconstruction(obj)}"; + } + + suspend fun toObjectWithHeader(id: String, backup: String, builder: Builder): T { + if(backup.startsWith("@/") && backup.contains("\n")) + return toObject(id, backup.substring(backup.indexOf("\n") + 1), builder); + else + return toObject(id, backup, builder); + } + + + + class Builder { + val messages: ArrayList = arrayListOf(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/video/PlayerManager.kt b/app/src/main/java/com/futo/platformplayer/video/PlayerManager.kt new file mode 100644 index 00000000..f13442a0 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/video/PlayerManager.kt @@ -0,0 +1,108 @@ +package com.futo.platformplayer.video + +import android.media.MediaPlayer +import android.media.session.PlaybackState +import android.support.v4.media.session.PlaybackStateCompat +import com.futo.platformplayer.constructs.Event1 +import com.google.android.exoplayer2.ExoPlayer +import com.google.android.exoplayer2.Player +import com.google.android.exoplayer2.ui.AspectRatioFrameLayout +import com.google.android.exoplayer2.ui.StyledPlayerView + +class PlayerManager { + private var _currentView: StyledPlayerView? = null; + private val _stateMap = HashMap(); + private var _currentState: PlayerState? = null; + val currentState: PlayerState get() { + if(_currentState == null) + throw java.lang.IllegalStateException("Attempted to access CurrentState while no state is set"); + else + return _currentState!!; + }; + + val player: ExoPlayer; + + constructor(exoPlayer: ExoPlayer) { + this.player = exoPlayer; + } + + fun getPlaybackStateCompat() : Int { + return when(player.playbackState) { + ExoPlayer.STATE_READY -> if(player.playWhenReady) PlaybackStateCompat.STATE_PLAYING else PlaybackStateCompat.STATE_PAUSED; + ExoPlayer.STATE_BUFFERING -> PlaybackState.STATE_BUFFERING; + else -> PlaybackState.STATE_NONE + } + } + + @Synchronized + fun attach(view: StyledPlayerView, stateName: String) { + if(view != _currentView) { + _currentView?.player = null; + switchState(stateName); + view.player = player; + _currentView = view; + } + } + fun detach() { + _currentView?.player = null; + } + + fun getState(name: String): PlayerState { + if(!_stateMap.containsKey(name)) + _stateMap[name] = PlayerState(); + return _stateMap[name]!!; + } + fun modifyState(name: String, cb: (PlayerState) -> Unit) { + val state = getState(name); + cb(state); + if(_currentState == state) + applyState(state); + } + fun switchState(name: String) { + val newState = getState(name); + applyState(newState); + + if(_currentState != newState) { + + if(_currentState?.listener != null) + player.removeListener(_currentState!!.listener!!); + if(newState.listener != null) + player.addListener(newState.listener!!); + + _currentState = newState; + } + } + fun applyState(state: PlayerState) { + player.volume = if(state.muted) 0f else state.volume; + } + + fun setMuted(muted: Boolean) { + currentState.muted = muted; + applyState(currentState); + } + fun setVolume(volume: Float) { + currentState.volume = volume; + applyState(currentState); + } + fun setListener(listener: Player.Listener) { + if(currentState.listener == listener) + return; + if(currentState.listener != null) + player.removeListener(currentState.listener!!); + currentState.listener = listener; + player.addListener(listener); + } + + fun release(){ + player.release(); + } + + class PlayerState { + var muted: Boolean = false; + var volume: Float = 1f; + + var listener: Player.Listener? = null; + + var resizMode: Int = AspectRatioFrameLayout.RESIZE_MODE_FIT; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/AnyAdapterView.kt b/app/src/main/java/com/futo/platformplayer/views/AnyAdapterView.kt new file mode 100644 index 00000000..2ff1e01f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/AnyAdapterView.kt @@ -0,0 +1,87 @@ +package com.futo.platformplayer.views + +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.views.adapters.* + +open class BaseAnyAdapterView + where T : AnyAdapter.AnyViewHolder, IT: RecyclerView.ViewHolder{ + val view: RecyclerView; + val adapter: BaseAnyAdapter; + + constructor(view: RecyclerView, adapter: BaseAnyAdapter, orientation: Int, reversed: Boolean) { + this.view = view; + this.adapter = adapter; + view.adapter = adapter.adapter; + view.layoutManager = LinearLayoutManager(view.context, orientation, reversed); + } + + fun setData(items: Iterable) { + adapter.setData(items); + } + fun add(item: I) { + adapter.add(item); + } + + fun all(cb: (I) -> Unit) { + adapter.all(cb); + } + fun notifyItemRangeInserted(i: Int, itemCount: Int) { + adapter.notifyItemRangeInserted(i, itemCount) + } + fun notifyContentChanged(i: Int) { + adapter.notifyContentChanged(i); + } + fun notifyContentChanged() { + adapter.notifyContentChanged(); + } + fun notifyContentChange(item: I) { + adapter.notifyContentChange(item); + } +} +class AnyAdapterView(view: RecyclerView, adapter: BaseAnyAdapter, orientation: Int, reversed: Boolean) + : BaseAnyAdapterView(view, adapter, orientation, reversed) + where T : AnyAdapter.AnyViewHolder{ + + companion object { + inline fun > RecyclerView.asAny(list: List, orientation: Int = RecyclerView.VERTICAL, reversed: Boolean = false, noinline onCreate: ((T)->Unit)? = null): AnyAdapterView { + return asAny(ArrayList(list), orientation, reversed, onCreate); + } + inline fun > RecyclerView.asAny(list: ArrayList, orientation: Int = RecyclerView.VERTICAL, reversed: Boolean = false, noinline onCreate: ((T)->Unit)? = null): AnyAdapterView { + return AnyAdapterView(this, AnyAdapter.create(list, onCreate), orientation, reversed); + } + + inline fun > RecyclerView.asAny(orientation: Int = RecyclerView.VERTICAL, reversed: Boolean = false, noinline onCreate: ((T)->Unit)? = null): AnyAdapterView { + return AnyAdapterView(this, AnyAdapter.create(onCreate), orientation, reversed); + } + } +} + +class AnyInsertedAdapterView(view: RecyclerView, adapter: BaseAnyAdapter>, orientation: Int, reversed: Boolean) + : BaseAnyAdapterView>(view, adapter, orientation, reversed) + where T : AnyAdapter.AnyViewHolder { + + companion object { + inline fun> RecyclerView.asAnyWithTop(list: ArrayList, view: View, orientation: Int = RecyclerView.VERTICAL, reversed: Boolean = false, noinline onCreate: ((T)->Unit)? = null) : AnyInsertedAdapterView + = this.asAnyWithViews(list, arrayListOf(view), arrayListOf(), orientation, reversed, onCreate); + + inline fun> RecyclerView.asAnyWithTop(view: View, orientation: Int = RecyclerView.VERTICAL, reversed: Boolean = false, noinline onCreate: ((T)->Unit)? = null) : AnyInsertedAdapterView + = this.asAnyWithViews(arrayListOf(view), arrayListOf(), orientation, reversed, onCreate); + inline fun> RecyclerView.asAnyWithViews(prepend: ArrayList = arrayListOf(), append: ArrayList = arrayListOf(), orientation: Int = RecyclerView.VERTICAL, reversed: Boolean = false, noinline onCreate: ((T)->Unit)? = null) : AnyInsertedAdapterView { + for(view in prepend) + (view.parent as ViewGroup).removeView(view); + for(view in append) + (view.parent as ViewGroup).removeView(view); + return AnyInsertedAdapterView(this, AnyInsertedAdapter.create(prepend, append, onCreate), orientation, reversed); + } + inline fun> RecyclerView.asAnyWithViews(list: ArrayList, prepend: ArrayList = arrayListOf(), append: ArrayList = arrayListOf(), orientation: Int = RecyclerView.VERTICAL, reversed: Boolean = false, noinline onCreate: ((T)->Unit)? = null) : AnyInsertedAdapterView { + for(view in prepend) + (view.parent as ViewGroup).removeView(view); + for(view in append) + (view.parent as ViewGroup).removeView(view); + return AnyInsertedAdapterView(this, AnyInsertedAdapter.create(list, prepend, append, onCreate), orientation, reversed); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/FeedStyle.kt b/app/src/main/java/com/futo/platformplayer/views/FeedStyle.kt new file mode 100644 index 00000000..c261b755 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/FeedStyle.kt @@ -0,0 +1,25 @@ +package com.futo.platformplayer.views + +import com.futo.platformplayer.api.media.exceptions.UnknownPlatformException +import com.futo.platformplayer.api.media.models.contents.ContentType + +enum class FeedStyle(val value: Int) { + UNKNOWN(-1), + THUMBNAIL(1), + PREVIEW(2); + + + + companion object { + val THUMBNAIL_HEIGHT = 115; + val PREVIEW_HEIGHT = 310; + + fun fromInt(value: Int): FeedStyle + { + val result = FeedStyle.values().firstOrNull { it.value == value }; + if(result == null) + throw UnknownPlatformException(value.toString()); + return result; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/Loader.kt b/app/src/main/java/com/futo/platformplayer/views/Loader.kt new file mode 100644 index 00000000..644d6047 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/Loader.kt @@ -0,0 +1,58 @@ +package com.futo.platformplayer.views + +import android.content.Context +import android.graphics.drawable.Animatable +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.ImageView +import android.widget.LinearLayout +import com.futo.platformplayer.R + +class Loader : LinearLayout { + private val _imageLoader: ImageView; + private val _automatic: Boolean; + private val _animatable: Animatable; + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + LayoutInflater.from(context).inflate(R.layout.view_loader, this, true); + _imageLoader = findViewById(R.id.image_loader); + _animatable = _imageLoader.drawable as Animatable; + + if (attrs != null) { + val attrArr = context.obtainStyledAttributes(attrs, R.styleable.LoaderView, 0, 0); + _automatic = attrArr.getBoolean(R.styleable.LoaderView_automatic, false); + attrArr.recycle(); + } else { + _automatic = false; + } + + visibility = View.GONE; + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + + if (_automatic) { + start(); + } + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + + if (_automatic) { + stop(); + } + } + + fun start() { + _animatable.start(); + visibility = View.VISIBLE; + } + + fun stop() { + _animatable.stop(); + visibility = View.GONE; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/AnyAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/AnyAdapter.kt new file mode 100644 index 00000000..310b4c7e --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/AnyAdapter.kt @@ -0,0 +1,181 @@ +package com.futo.platformplayer.views.adapters + +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.Recycler +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import java.lang.reflect.Constructor + +open class BaseAnyAdapter, IT : ViewHolder> { + protected var _items: ArrayList; + protected val _holderClass: Class; + + protected val _constructor: Constructor; + + protected val _onCreate: ((T)->Unit)?; + + lateinit var adapter: RecyclerView.Adapter + protected set; + + constructor(items: ArrayList, holderClass: Class, onCreate: ((T)->Unit)? = null) : super() { + _items = items; + _holderClass = holderClass; + _constructor = _holderClass.constructors.firstOrNull { it.parameterTypes.size == 1 && it.parameterTypes[0] == ViewGroup::class.java } as Constructor? + ?: throw IllegalStateException("Viewholder [${_holderClass.name}] missing constructor (Context, ViewGroup)"); + _onCreate = onCreate; + } + constructor(holderClass: Class, onCreate: ((T)->Unit)? = null) : super() { + _items = arrayListOf(); + _holderClass = holderClass; + _constructor = _holderClass.constructors.firstOrNull { it.parameterTypes.size == 1 && it.parameterTypes[0] == ViewGroup::class.java } as Constructor? + ?: throw IllegalStateException("Viewholder [${_holderClass.name}] missing constructor (Context, ViewGroup)"); + _onCreate = onCreate; + } + + fun setData(newItems: Iterable) { + _items.clear(); + _items.addAll(newItems); + adapter.notifyDataSetChanged(); + } + fun add(item: I) { + _items.add(item); + notifyItemInserted(_items.size - 1); + } + + fun all(cb: (I)->Unit) { + for(item in _items) + cb(item); + } + + fun notifyContentChanged() { + adapter.notifyDataSetChanged(); + } + + fun notifyContentChanged(position: Int) { + adapter.notifyItemChanged(position); + } + + fun notifyItemInserted(position: Int) { + adapter.notifyItemInserted(position); + } + + fun notifyItemMoved(fromPosition: Int, toPosition: Int) { + adapter.notifyItemMoved(fromPosition, toPosition); + } + + fun notifyItemRangeInserted(positionStart: Int, itemCount: Int) { + adapter.notifyItemRangeInserted(positionStart, itemCount); + } + fun notifyItemRangeChanged(positionStart: Int, itemCount: Int) { + adapter.notifyItemRangeChanged(positionStart, itemCount); + } + fun notifyItemRangeRemoved(positionStart: Int, itemCount: Int) { + adapter.notifyItemRangeRemoved(positionStart, itemCount); + } + + fun notifyItemRangeRemoved(position: Int) { + adapter.notifyItemRemoved(position); + } + + + fun notifyContentChange(item: I) { + val index = _items.indexOf(item); + if(index >= 0) + notifyContentChanged(index); + } + + companion object { + inline fun > create(prepend: ArrayList = arrayListOf(), append: ArrayList = arrayListOf()) : AnyInsertedAdapter { + return AnyInsertedAdapter(T::class.java, prepend, append); + } + } +} + +class AnyAdapter> : BaseAnyAdapter { + + constructor(items: ArrayList, holderClass: Class, onCreate: ((T)->Unit)? = null) : super(items, holderClass, onCreate) { + adapter = Adapter(this); + } + constructor(holderClass: Class, onCreate: ((T)->Unit)? = null) : super(holderClass, onCreate) { + adapter = Adapter(this); + } + + abstract class AnyViewHolder(protected val _view: View) : ViewHolder(_view) { + abstract fun bind(i: I); + } + + companion object { + inline fun > create(list: ArrayList, noinline onCreate: ((T)->Unit)? = null) : AnyAdapter { + return AnyAdapter(list, T::class.java, onCreate); + } + inline fun > create(noinline onCreate: ((T)->Unit)? = null) : AnyAdapter { + return AnyAdapter(T::class.java, onCreate); + } + } + + private class Adapter> : RecyclerView.Adapter { + private val _parent: AnyAdapter; + + + constructor(parentAdapter: AnyAdapter) { + _parent = parentAdapter; + } + + override fun getItemCount() = _parent._items.size; + + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): T { + val item = _parent._constructor.newInstance(viewGroup) as T; + _parent._onCreate?.invoke(item); + return item; + } + + override fun onBindViewHolder(viewHolder: T, position: Int) { + viewHolder.bind(_parent._items[position]); + } + } +} + +class AnyInsertedAdapter> : BaseAnyAdapter>{ + constructor(items: ArrayList, holderClass: Class, prepend: ArrayList = arrayListOf(), append: ArrayList = arrayListOf(), onCreate: ((T)->Unit)? = null) + : super(items, holderClass, onCreate) { + adapter = InsertedViewAdapter(prepend, append, + this::getChildCount, + this::createChild, + this::bindChild) + } + constructor(holderClass: Class, prepend: ArrayList = arrayListOf(), append: ArrayList = arrayListOf(), onCreate: ((T)->Unit)? = null) + : super(holderClass, onCreate) { + adapter = InsertedViewAdapter(prepend, append, + this::getChildCount, + this::createChild, + this::bindChild) + } + + fun getChildCount(): Int { + return _items.size; + } + + fun createChild(viewGroup: ViewGroup, viewType: Int): T { + val view = _constructor.newInstance(viewGroup) as T; + _onCreate?.invoke(view); + return view; + } + + fun bindChild(holder: T, pos: Int) { + holder.bind(_items[pos]); + } + + companion object { + inline fun > create(list: ArrayList, prepend: ArrayList = arrayListOf(), append: ArrayList = arrayListOf(), noinline onCreate: ((T)->Unit)? = null) : AnyInsertedAdapter { + return AnyInsertedAdapter(list, T::class.java, prepend, append, onCreate); + } + + inline fun > create(prepend: ArrayList = arrayListOf(), noinline onCreate: ((T)->Unit)? = null) : AnyInsertedAdapter { + return AnyInsertedAdapter(T::class.java, prepend, arrayListOf(), onCreate); + } + inline fun > create(prepend: ArrayList = arrayListOf(), append: ArrayList = arrayListOf(), noinline onCreate: ((T)->Unit)? = null) : AnyInsertedAdapter { + return AnyInsertedAdapter(T::class.java, prepend, append, onCreate); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/ChannelViewPagerAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/ChannelViewPagerAdapter.kt new file mode 100644 index 00000000..7fe21e19 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/ChannelViewPagerAdapter.kt @@ -0,0 +1,65 @@ +package com.futo.platformplayer.views.adapters + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.fragment.channel.tab.* + +class ChannelViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : FragmentStateAdapter(fragmentManager, lifecycle) { + private val _cache: Array = arrayOfNulls(4); + + val onContentUrlClicked = Event2(); + val onContentClicked = Event2(); + val onChannelClicked = Event1(); + val onAddToClicked = Event1(); + + override fun getItemCount(): Int { + return _cache.size; + } + inline fun getFragment(): T { + + //TODO: I have a feeling this can somehow be synced with createFragment so only 1 mapping exists (without a Map<>) + if(T::class == ChannelContentsFragment::class) + return createFragment(0) as T; + else if(T::class == ChannelListFragment::class) + return createFragment(1) as T; + //else if(T::class == ChannelStoreFragment::class) + // return createFragment(2) as T; + else if(T::class == ChannelMonetizationFragment::class) + return createFragment(2) as T; + else if(T::class == ChannelAboutFragment::class) + return createFragment(3) as T; + else + throw NotImplementedError("Implement other types"); + } + + override fun createFragment(position: Int): Fragment { + val cachedFragment = _cache[position]; + if (cachedFragment != null) { + return cachedFragment; + } + + val fragment = when (position) { + 0 -> ChannelContentsFragment.newInstance().apply { + onContentClicked.subscribe(this@ChannelViewPagerAdapter.onContentClicked::emit); + onContentUrlClicked.subscribe(this@ChannelViewPagerAdapter.onContentUrlClicked::emit); + onChannelClicked.subscribe(this@ChannelViewPagerAdapter.onChannelClicked::emit); + onAddToClicked.subscribe(this@ChannelViewPagerAdapter.onAddToClicked::emit); + }; + 1 -> ChannelListFragment.newInstance().apply { onClickChannel.subscribe(onChannelClicked::emit) }; + //2 -> ChannelStoreFragment.newInstance(); + 2 -> ChannelMonetizationFragment.newInstance(); + 3 -> ChannelAboutFragment.newInstance(); + else -> throw IllegalStateException("Invalid tab position $position") + }; + + _cache[position]= fragment; + return fragment; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/CommentViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/CommentViewHolder.kt new file mode 100644 index 00000000..a946a937 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/CommentViewHolder.kt @@ -0,0 +1,161 @@ +package com.futo.platformplayer.views.adapters + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment +import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes +import com.futo.platformplayer.api.media.models.ratings.RatingLikes +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StatePolycentric +import com.futo.platformplayer.views.others.CreatorThumbnail +import com.futo.platformplayer.views.pills.PillButton +import com.futo.platformplayer.views.pills.PillRatingLikesDislikes +import com.futo.polycentric.core.Opinion +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class CommentViewHolder : ViewHolder { + private val _creatorThumbnail: CreatorThumbnail; + private val _textAuthor: TextView; + private val _textMetadata: TextView; + private val _textBody: TextView; + private val _imageLikeIcon: ImageView; + private val _textLikes: TextView; + private val _imageDislikeIcon: ImageView; + private val _textDislikes: TextView; + private val _buttonReplies: PillButton; + private val _layoutRating: LinearLayout; + private val _pillRatingLikesDislikes: PillRatingLikesDislikes; + + var onClick = Event1(); + var comment: IPlatformComment? = null + private set; + + constructor(viewGroup: ViewGroup) : super(LayoutInflater.from(viewGroup.context).inflate(R.layout.list_comment, viewGroup, false)) { + _creatorThumbnail = itemView.findViewById(R.id.image_thumbnail); + _textAuthor = itemView.findViewById(R.id.text_author); + _textMetadata = itemView.findViewById(R.id.text_metadata); + _textBody = itemView.findViewById(R.id.text_body); + _imageLikeIcon = itemView.findViewById(R.id.image_like_icon); + _textLikes = itemView.findViewById(R.id.text_likes); + _imageDislikeIcon = itemView.findViewById(R.id.image_dislike_icon); + _textDislikes = itemView.findViewById(R.id.text_dislikes); + _buttonReplies = itemView.findViewById(R.id.button_replies); + _layoutRating = itemView.findViewById(R.id.layout_rating); + _pillRatingLikesDislikes = itemView.findViewById(R.id.rating); + + _pillRatingLikesDislikes.onLikeDislikeUpdated.subscribe { processHandle, hasLiked, hasDisliked -> + val c = comment + if (c !is PolycentricPlatformComment) { + throw Exception("Not implemented for non polycentric comments") + } + + if (hasLiked) { + processHandle.opinion(c.reference, Opinion.like); + } else if (hasDisliked) { + processHandle.opinion(c.reference, Opinion.dislike); + } else { + processHandle.opinion(c.reference, Opinion.neutral); + } + + StateApp.instance.scopeGetter().launch(Dispatchers.IO) { + try { + processHandle.fullyBackfillServers(); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to backfill servers.", e) + } + } + + StatePolycentric.instance.updateLikeMap(c.reference, hasLiked, hasDisliked) + }; + + _buttonReplies.onClick.subscribe { + val c = comment ?: return@subscribe; + onClick.emit(c); + } + + _textBody.setPlatformPlayerLinkMovementMethod(viewGroup.context); + } + + fun bind(comment: IPlatformComment, readonly: Boolean) { + _creatorThumbnail.setThumbnail(comment.author.thumbnail, false); + _textAuthor.text = comment.author.name; + + val date = comment.date; + if (date != null) { + _textMetadata.visibility = View.VISIBLE; + _textMetadata.text = " • ${date.toHumanNowDiffString()} ago"; + } else { + _textMetadata.visibility = View.GONE; + } + + _textBody.text = comment.message.fixHtmlLinks(); + + if (readonly) { + _layoutRating.visibility = View.VISIBLE; + _pillRatingLikesDislikes.visibility = View.GONE; + + when (comment.rating) { + is RatingLikeDislikes -> { + val r = comment.rating as RatingLikeDislikes; + _textLikes.visibility = View.VISIBLE; + _imageLikeIcon.visibility = View.VISIBLE; + _textLikes.text = r.likes.toHumanNumber(); + + _imageDislikeIcon.visibility = View.VISIBLE; + _textDislikes.visibility = View.VISIBLE; + _textDislikes.text = r.dislikes.toHumanNumber(); + } + is RatingLikes -> { + val r = comment.rating as RatingLikes; + _textLikes.visibility = View.VISIBLE; + _imageLikeIcon.visibility = View.VISIBLE; + _textLikes.text = r.likes.toHumanNumber(); + + _imageDislikeIcon.visibility = View.GONE; + _textDislikes.visibility = View.GONE; + } + else -> { + _textLikes.visibility = View.GONE; + _imageLikeIcon.visibility = View.GONE; + _imageDislikeIcon.visibility = View.GONE; + _textDislikes.visibility = View.GONE; + } + } + } else { + _layoutRating.visibility = View.GONE; + _pillRatingLikesDislikes.visibility = View.VISIBLE; + + if (comment is PolycentricPlatformComment) { + val hasLiked = StatePolycentric.instance.hasLiked(comment.reference); + val hasDisliked = StatePolycentric.instance.hasDisliked(comment.reference); + _pillRatingLikesDislikes.setRating(comment.rating, hasLiked, hasDisliked); + } else { + _pillRatingLikesDislikes.setRating(comment.rating); + } + } + + val replies = comment.replyCount ?: 0; + if (!readonly || replies > 0) { + _buttonReplies.visibility = View.VISIBLE; + _buttonReplies.text.text = "$replies replies"; + } else { + _buttonReplies.visibility = View.GONE; + } + + this.comment = comment; + } + + companion object { + private const val TAG = "CommentViewHolder"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/ContentPreviewViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/ContentPreviewViewHolder.kt new file mode 100644 index 00000000..67e946e0 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/ContentPreviewViewHolder.kt @@ -0,0 +1,18 @@ +package com.futo.platformplayer.views.adapters + +import android.view.View +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails + +abstract class ContentPreviewViewHolder(itemView: View) : ViewHolder(itemView) { + abstract val content: IPlatformContent?; + + abstract fun bind(content: IPlatformContent); + + abstract fun preview(details: IPlatformContentDetails?, paused: Boolean); + abstract fun stopPreview(); + abstract fun pausePreview(); + abstract fun resumePreview(); + +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/DeviceAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/DeviceAdapter.kt new file mode 100644 index 00000000..e318c3a7 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/DeviceAdapter.kt @@ -0,0 +1,35 @@ +package com.futo.platformplayer.views.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.R +import com.futo.platformplayer.casting.CastingDevice +import com.futo.platformplayer.casting.StateCasting +import com.futo.platformplayer.constructs.Event1 + +class DeviceAdapter : RecyclerView.Adapter { + private val _devices: ArrayList; + private val _isRememberedDevice: Boolean; + + var onRemove = Event1(); + + constructor(devices: ArrayList, isRememberedDevice: Boolean) : super() { + _devices = devices; + _isRememberedDevice = isRememberedDevice; + } + + override fun getItemCount() = _devices.size; + + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): DeviceViewHolder { + val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.list_device, viewGroup, false); + val holder = DeviceViewHolder(view); + holder.setIsRememberedDevice(_isRememberedDevice); + holder.onRemove.subscribe { d -> onRemove.emit(d); }; + return holder; + } + + override fun onBindViewHolder(viewHolder: DeviceViewHolder, position: Int) { + viewHolder.bind(_devices[position]); + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/DeviceViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/DeviceViewHolder.kt new file mode 100644 index 00000000..6eddcc98 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/DeviceViewHolder.kt @@ -0,0 +1,133 @@ +package com.futo.platformplayer.views.adapters + +import android.graphics.drawable.Animatable +import android.view.View +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.futo.platformplayer.R +import com.futo.platformplayer.casting.* +import com.futo.platformplayer.constructs.Event1 + +class DeviceViewHolder : ViewHolder { + private val _imageDevice: ImageView; + private val _textName: TextView; + private val _textType: TextView; + private val _textNotReady: TextView; + private val _buttonDisconnect: LinearLayout; + private val _buttonConnect: LinearLayout; + private val _buttonRemove: LinearLayout; + private val _imageLoader: ImageView; + private var _animatableLoader: Animatable? = null; + private var _isRememberedDevice: Boolean = false; + + var device: CastingDevice? = null + private set + + var onRemove = Event1(); + + constructor(view: View) : super(view) { + _imageDevice = view.findViewById(R.id.image_device); + _textName = view.findViewById(R.id.text_name); + _textType = view.findViewById(R.id.text_type); + _textNotReady = view.findViewById(R.id.text_not_ready); + _buttonDisconnect = view.findViewById(R.id.button_disconnect); + _buttonConnect = view.findViewById(R.id.button_connect); + _buttonRemove = view.findViewById(R.id.button_remove); + _imageLoader = view.findViewById(R.id.image_loader); + + val d = _imageLoader.drawable; + if (d is Animatable) { + _animatableLoader = d; + } + + _buttonDisconnect.setOnClickListener { + StateCasting.instance.activeDevice?.stopCasting(); + updateButton(); + }; + + _buttonConnect.setOnClickListener { + val d = device ?: return@setOnClickListener; + StateCasting.instance.activeDevice?.stopCasting(); + StateCasting.instance.connectDevice(d); + updateButton(); + }; + + _buttonRemove.setOnClickListener { + val d = device ?: return@setOnClickListener; + onRemove.emit(d); + }; + + setIsRememberedDevice(false); + } + + fun setIsRememberedDevice(isRememberedDevice: Boolean) { + _isRememberedDevice = isRememberedDevice; + _buttonRemove.visibility = if (isRememberedDevice) View.VISIBLE else View.GONE; + } + + fun bind(d: CastingDevice) { + if (d is ChromecastCastingDevice) { + _imageDevice.setImageResource(R.drawable.ic_chromecast); + _textType.text = "Chromecast"; + } else if (d is AirPlayCastingDevice) { + _imageDevice.setImageResource(R.drawable.ic_airplay); + _textType.text = "AirPlay"; + } else if (d is FastCastCastingDevice) { + _imageDevice.setImageResource(R.drawable.ic_fc); + _textType.text = "FastCast"; + } + + _textName.text = d.name; + device = d; + updateButton(); + } + + private fun updateButton() { + val d = device ?: return; + + if (!d.isReady) { + _buttonConnect.visibility = View.GONE; + _buttonDisconnect.visibility = View.GONE; + _imageLoader.visibility = View.GONE; + _textNotReady.visibility = View.VISIBLE; + return; + } + + _textNotReady.visibility = View.GONE; + + val dev = StateCasting.instance.activeDevice; + if (dev == d) { + if (dev.connectionState == CastConnectionState.CONNECTED) { + _buttonConnect.visibility = View.GONE; + _buttonDisconnect.visibility = View.VISIBLE; + _imageLoader.visibility = View.GONE; + _textNotReady.visibility = View.GONE; + } else { + _buttonConnect.visibility = View.GONE; + _buttonDisconnect.visibility = View.VISIBLE; + _imageLoader.visibility = View.VISIBLE; + _textNotReady.visibility = View.GONE; + } + } else { + if (d.isReady) { + _buttonConnect.visibility = View.VISIBLE; + _buttonDisconnect.visibility = View.GONE; + _imageLoader.visibility = View.GONE; + _textNotReady.visibility = View.GONE; + } else { + _buttonConnect.visibility = View.GONE; + _buttonDisconnect.visibility = View.GONE; + _imageLoader.visibility = View.GONE; + _textNotReady.visibility = View.VISIBLE; + } + } + + if (_imageLoader.visibility == View.VISIBLE) { + _animatableLoader?.start(); + } else { + _animatableLoader?.stop(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/DisabledSourceAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/DisabledSourceAdapter.kt new file mode 100644 index 00000000..4ce52646 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/DisabledSourceAdapter.kt @@ -0,0 +1,42 @@ +package com.futo.platformplayer.views.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.constructs.Event1 + +class DisabledSourceAdapter : RecyclerView.Adapter { + private val _sources: MutableList; + + var onClick = Event1(); + var onAdd = Event1(); + + constructor(sources: MutableList) : super() { + _sources = sources; + } + + override fun getItemCount() = _sources.size + + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): DisabledSourceViewHolder { + val holder = DisabledSourceViewHolder(viewGroup); + holder.onAdd.subscribe { + val source = holder.source; + if (source != null) { + onAdd.emit(source); + } + } + holder.onClick.subscribe { + val source = holder.source; + if (source != null) { + onClick.emit(source); + } + }; + return holder; + } + + override fun onBindViewHolder(viewHolder: DisabledSourceViewHolder, position: Int) { + viewHolder.bind(_sources[position]) + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/DisabledSourceView.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/DisabledSourceView.kt new file mode 100644 index 00000000..63348033 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/DisabledSourceView.kt @@ -0,0 +1,46 @@ +package com.futo.platformplayer.views.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.platforms.js.JSClient +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event1 + +class DisabledSourceView : LinearLayout { + private val _root: LinearLayout; + private val _imageSource: ImageView; + private val _textSource: TextView; + private val _textSourceSubtitle: TextView; + + private val _buttonAdd: LinearLayout; + + val onClick = Event0(); + val onAdd = Event1(); + val source: IPlatformClient; + + constructor(context: Context, client: IPlatformClient) : super(context) { + inflate(context, R.layout.list_source_disabled, this); + source = client; + + _root = findViewById(R.id.root); + _imageSource = findViewById(R.id.image_source); + _textSource = findViewById(R.id.text_source); + _textSourceSubtitle = findViewById(R.id.text_source_subtitle); + _buttonAdd = findViewById(R.id.button_add); + + client.icon?.setImageView(_imageSource); + + _textSource.text = client.name; + _textSourceSubtitle.text = "Tap to open"; + + _buttonAdd.setOnClickListener { onAdd.emit(source) } + _root.setOnClickListener { onClick.emit(); }; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/DisabledSourceViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/DisabledSourceViewHolder.kt new file mode 100644 index 00000000..c721b553 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/DisabledSourceViewHolder.kt @@ -0,0 +1,44 @@ +package com.futo.platformplayer.views.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.platforms.js.JSClient +import com.futo.platformplayer.constructs.Event0 + +class DisabledSourceViewHolder : ViewHolder { + private val _imageSource: ImageView; + private val _textSource: TextView; + private val _textSourceSubtitle: TextView; + + private val _buttonAdd: LinearLayout; + + var onClick = Event0(); + var onAdd = Event0(); + var source: IPlatformClient? = null + private set + + constructor(viewGroup: ViewGroup) : super(LayoutInflater.from(viewGroup.context).inflate(R.layout.list_source_disabled, viewGroup, false)) { + _imageSource = itemView.findViewById(R.id.image_source); + _textSource = itemView.findViewById(R.id.text_source); + _textSourceSubtitle = itemView.findViewById(R.id.text_source_subtitle); + _buttonAdd = itemView.findViewById(R.id.button_add); + + val root = itemView.findViewById(R.id.root); + _buttonAdd.setOnClickListener { onAdd.emit() } + root.setOnClickListener { onClick.emit(); }; + } + + fun bind(client: IPlatformClient) { + client.icon?.setImageView(_imageSource); + + _textSource.text = client.name; + _textSourceSubtitle.text = "Tap to open"; + source = client; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/EmptyPreviewViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/EmptyPreviewViewHolder.kt new file mode 100644 index 00000000..bfd86f56 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/EmptyPreviewViewHolder.kt @@ -0,0 +1,23 @@ +package com.futo.platformplayer.views.adapters + +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails + +class EmptyPreviewViewHolder(viewGroup: ViewGroup) : ContentPreviewViewHolder(View(viewGroup.context)) { + override val content: IPlatformContent? + get() = null; + + override fun bind(content: IPlatformContent) {} + + override fun preview(details: IPlatformContentDetails?, paused: Boolean) {} + + override fun stopPreview() {} + + override fun pausePreview() {} + + override fun resumePreview() {} + +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/EnabledSourceAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/EnabledSourceAdapter.kt new file mode 100644 index 00000000..6426e90a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/EnabledSourceAdapter.kt @@ -0,0 +1,40 @@ +package com.futo.platformplayer.views.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event1 + +class EnabledSourceAdapter : RecyclerView.Adapter { + private val _sources: MutableList; + private val _touchHelper: ItemTouchHelper; + + var onRemove = Event1(); + var onClick = Event1(); + var canRemove: Boolean = false; + + constructor(sources: MutableList, touchHelper: ItemTouchHelper) : super() { + _sources = sources; + _touchHelper = touchHelper; + } + + override fun getItemCount() = _sources.size; + + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): EnabledSourceViewHolder { + val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.list_source_enabled, viewGroup, false) + val holder = EnabledSourceViewHolder(view, _touchHelper); + holder.onRemove.subscribe { onRemove.emit(it); }; + holder.onClick.subscribe { onClick.emit(it); } + holder.setCanRemove(canRemove); + return holder; + } + + override fun onBindViewHolder(viewHolder: EnabledSourceViewHolder, position: Int) { + viewHolder.setCanRemove(canRemove); + viewHolder.bind(_sources[position]) + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/EnabledSourceViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/EnabledSourceViewHolder.kt new file mode 100644 index 00000000..62aa12dc --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/EnabledSourceViewHolder.kt @@ -0,0 +1,63 @@ +package com.futo.platformplayer.views.adapters + +import android.view.MotionEvent +import android.view.View +import android.view.View.OnTouchListener +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.platforms.js.JSClient +import com.futo.platformplayer.constructs.Event1 + +class EnabledSourceViewHolder : ViewHolder { + private val _imageSource: ImageView; + private val _textSource: TextView; + private val _textSourceSubtitle: TextView; + private val _imageDragDrop: ImageView; + private val _buttonRemove: LinearLayout; + + var onRemove = Event1(); + var onClick = Event1(); + var source: IPlatformClient? = null + private set + + constructor(view: View, touchHelper: ItemTouchHelper) : super(view) { + _imageSource = view.findViewById(R.id.image_source); + _textSource = view.findViewById(R.id.text_source); + _textSourceSubtitle = itemView.findViewById(R.id.text_source_subtitle); + _imageDragDrop = view.findViewById(R.id.image_drag_drop); + _buttonRemove = view.findViewById(R.id.button_remove); + val root = view.findViewById(R.id.root); + + root.setOnClickListener { + source?.let { onClick.emit(it); }; + }; + + _imageDragDrop.setOnTouchListener(OnTouchListener { v, event -> + if (event.action == MotionEvent.ACTION_DOWN) { + touchHelper.startDrag(this); + } + false + }); + + _buttonRemove.setOnClickListener { + source?.let { onRemove.emit(it); }; + }; + } + + fun setCanRemove(canRemove: Boolean) { + _buttonRemove.visibility = if (canRemove) { View.VISIBLE } else { View.GONE }; + } + + fun bind(client: IPlatformClient) { + client.icon?.setImageView(_imageSource); + + _textSource.text = client.name; + _textSourceSubtitle.text = "Tap to open"; + source = client + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/HistoryListAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/HistoryListAdapter.kt new file mode 100644 index 00000000..72d81241 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/HistoryListAdapter.kt @@ -0,0 +1,109 @@ +package com.futo.platformplayer.views.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.* +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.models.HistoryVideo +import com.futo.platformplayer.states.StatePlaylists + +class HistoryListAdapter : RecyclerView.Adapter { + private lateinit var _filteredVideos: MutableList; + + val onClick = Event1(); + private var _query: String = ""; + + constructor() : super() { + updateFilteredVideos(); + + StatePlaylists.instance.onHistoricVideoChanged.subscribe(this) { video, position -> + val index = _filteredVideos.indexOfFirst { v -> v.video.url == video.url }; + if (index == -1) { + return@subscribe; + } + + _filteredVideos[index].position = position; + if (index < _filteredVideos.size - 2) { + notifyItemRangeChanged(index, 2); + } else { + notifyItemChanged(index); + } + }; + } + + fun setQuery(query: String) { + _query = query; + updateFilteredVideos(); + } + + fun updateFilteredVideos() { + val videos = StatePlaylists.instance.getHistory(); + if (_query.isBlank()) { + _filteredVideos = videos.toMutableList(); + } else { + _filteredVideos = videos.filter { v -> v.video.name.lowercase().contains(_query); }.toMutableList(); + } + + notifyDataSetChanged(); + } + + fun cleanup() { + StatePlaylists.instance.onHistoricVideoChanged.remove(this); + } + + override fun getItemCount() = _filteredVideos.size; + + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): HistoryListViewHolder { + val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.list_history, viewGroup, false); + val holder = HistoryListViewHolder(view); + + holder.onRemove.subscribe { v -> + val videos = _filteredVideos; + val index = videos.indexOf(v); + if (index == -1) { + return@subscribe; + } + + StatePlaylists.instance.removeHistory(v.video.url); + _filteredVideos.removeAt(index); + notifyItemRemoved(index); + }; + holder.onClick.subscribe { v -> + val videos = _filteredVideos; + val index = videos.indexOf(v); + if (index == -1) { + return@subscribe; + } + + _filteredVideos.removeAt(index); + _filteredVideos.add(0, v); + + notifyItemMoved(index, 0); + notifyItemRangeChanged(0, 2); + onClick.emit(v); + }; + + return holder; + } + + override fun onBindViewHolder(viewHolder: HistoryListViewHolder, position: Int) { + val videos = _filteredVideos; + var watchTime: String? = null; + if (position == 0) { + watchTime = videos[position].date.toHumanNowDiffStringMinDay(); + } else { + val previousWatchTime = videos[position - 1].date.toHumanNowDiffStringMinDay(); + val currentWatchTime = videos[position].date.toHumanNowDiffStringMinDay(); + if (previousWatchTime != currentWatchTime) { + watchTime = currentWatchTime; + } + } + + viewHolder.bind(videos[position], watchTime); + } + + companion object { + val TAG = "HistoryListAdapter"; + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/HistoryListViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/HistoryListViewHolder.kt new file mode 100644 index 00000000..b990dcc7 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/HistoryListViewHolder.kt @@ -0,0 +1,103 @@ +package com.futo.platformplayer.views.adapters + +import android.view.View +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.images.GlideHelper.Companion.crossfade +import com.futo.platformplayer.models.HistoryVideo +import com.futo.platformplayer.toHumanNumber +import com.futo.platformplayer.toHumanTime +import com.futo.platformplayer.views.others.ProgressBar + +class HistoryListViewHolder : ViewHolder { + private val _root: ConstraintLayout; + private val _imageThumbnail: ImageView; + private val _textName: TextView; + private val _textAuthor: TextView; + private val _textMetadata: TextView; + private val _textVideoDuration: TextView; + private val _containerDuration: LinearLayout; + private val _containerLive: LinearLayout; + private val _imageRemove: ImageButton; + private val _textHeader: TextView; + private val _timeBar: ProgressBar; + + var video: HistoryVideo? = null + private set; + + val onClick = Event1(); + val onRemove = Event1(); + + constructor(view: View) : super(view) { + _root = view.findViewById(R.id.root); + _imageThumbnail = view.findViewById(R.id.image_video_thumbnail); + _imageThumbnail?.clipToOutline = true; + _textName = view.findViewById(R.id.text_video_name); + _textAuthor = view.findViewById(R.id.text_author); + _textMetadata = view.findViewById(R.id.text_video_metadata); + _textVideoDuration = view.findViewById(R.id.thumbnail_duration); + _containerDuration = view.findViewById(R.id.thumbnail_duration_container); + _containerLive = view.findViewById(R.id.thumbnail_live_container); + _imageRemove = view.findViewById(R.id.image_trash); + _textHeader = view.findViewById(R.id.text_header); + _timeBar = view.findViewById(R.id.time_bar); + + _root.setOnClickListener { + val v = video ?: return@setOnClickListener; + onClick.emit(v); + }; + + _imageRemove?.setOnClickListener { + val v = video ?: return@setOnClickListener; + onRemove.emit(v); + }; + } + + fun bind(v: HistoryVideo, watchTime: String?) { + Glide.with(_imageThumbnail) + .load(v.video.thumbnails.getLQThumbnail()) + .placeholder(R.drawable.placeholder_video_thumbnail) + .crossfade() + .into(_imageThumbnail); + + _textName.text = v.video.name; + _textAuthor.text = v.video.author.name; + _textVideoDuration.text = v.video.duration.toHumanTime(false); + + if(v.video.isLive) { + _containerDuration.visibility = View.GONE; + _containerLive.visibility = View.VISIBLE; + } + else { + _containerLive.visibility = View.GONE; + _containerDuration.visibility = View.VISIBLE; + } + + if (watchTime != null) { + _textHeader.text = watchTime; + _textHeader.visibility = View.VISIBLE; + } else { + _textHeader.visibility = View.GONE; + } + + var metadata = ""; + if (v.video.viewCount > 0) + metadata += "${v.video.viewCount.toHumanNumber()} views"; + + _textMetadata.text = metadata; + + _timeBar.progress = v.position.toFloat() / v.video.duration.toFloat(); + video = v; + } + + companion object { + val TAG = "HistoryListViewHolder"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/InsertedViewAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/InsertedViewAdapter.kt new file mode 100644 index 00000000..3cd05372 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/InsertedViewAdapter.kt @@ -0,0 +1,70 @@ +package com.futo.platformplayer.views.adapters + +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ViewHolder + +open class InsertedViewAdapter : RecyclerView.Adapter> where TViewHolder : ViewHolder { + val viewsToPrepend: ArrayList; + var viewsToAppend: ArrayList; + protected var childViewHolderFactory: ((viewGroup: ViewGroup, viewType: Int) -> TViewHolder)? = null; + protected var childViewHolderBinder: ((viewHolder: TViewHolder, position: Int) -> Unit)? = null; + protected var childCountGetter: (() -> Int)? = null; + + constructor(viewsToPrepend: ArrayList, + viewsToAppend: ArrayList, + childCountGetter: () -> Int, + childViewHolderFactory: (viewGroup: ViewGroup, viewType: Int) -> TViewHolder, + childViewHolderBinder: (viewHolder: TViewHolder, position: Int) -> Unit) : super() + { + this.viewsToPrepend = viewsToPrepend; + this.viewsToAppend = viewsToAppend; + this.childCountGetter = childCountGetter; + this.childViewHolderFactory = childViewHolderFactory; + this.childViewHolderBinder = childViewHolderBinder; + } + + protected constructor(viewsToPrepend: ArrayList, viewsToAppend: ArrayList) { + this.viewsToPrepend = viewsToPrepend; + this.viewsToAppend = viewsToAppend; + } + + open fun getChildCount(): Int = childCountGetter!!(); + override fun getItemCount() = viewsToPrepend.size + getChildCount() + viewsToAppend.size; + + open fun createChild(viewGroup: ViewGroup, viewType: Int): TViewHolder = childViewHolderFactory!!.invoke(viewGroup, viewType); + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): InsertedViewHolder { + return InsertedViewHolder(viewGroup.context, createChild(viewGroup, viewType)); + } + + open fun bindChild(holder: TViewHolder, pos: Int) = childViewHolderBinder!!(holder, pos); + override fun onBindViewHolder(viewHolder: InsertedViewHolder, position: Int) { + if (position < viewsToPrepend.size) { + viewHolder.bindView(viewsToPrepend[position]); + return; + } + + val childCount = getChildCount(); + val originalAdapterPosition = position - viewsToPrepend.size; + if (originalAdapterPosition < childCount) { + bindChild(viewHolder.childViewHolder, originalAdapterPosition); + viewHolder.bindChild(); + return; + } + + val viewsToAppendIndex = position - childCount - viewsToPrepend.size; + if (viewsToAppendIndex < viewsToAppend.size) { + viewHolder.bindView(viewsToAppend[viewsToAppendIndex]); + return; + } + } + + fun childToParentPosition(position: Int): Int { + return position + viewsToPrepend.size; + } + + fun parentToChildPosition(position: Int): Int { + return position - viewsToPrepend.size; + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/InsertedViewAdapterWithLoader.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/InsertedViewAdapterWithLoader.kt new file mode 100644 index 00000000..de012903 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/InsertedViewAdapterWithLoader.kt @@ -0,0 +1,91 @@ +package com.futo.platformplayer.views.adapters + +import android.content.Context +import android.graphics.drawable.Animatable +import android.util.TypedValue +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageView +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.futo.platformplayer.R + +open class InsertedViewAdapterWithLoader : InsertedViewAdapter where TViewHolder : ViewHolder { + private var _loaderView: ImageView? = null; + private var _loading = false; + + constructor( + context: Context, + viewsToPrepend: ArrayList, + viewsToAppend: ArrayList, + childCountGetter: () -> Int, + childViewHolderFactory: (viewGroup: ViewGroup, viewType: Int) -> TViewHolder, + childViewHolderBinder: (viewHolder: TViewHolder, position: Int) -> Unit) : super( + viewsToPrepend = viewsToPrepend, + viewsToAppend = viewsToAppend, + childCountGetter = childCountGetter, + childViewHolderFactory = childViewHolderFactory, + childViewHolderBinder = childViewHolderBinder + ) + { + val loaderView = createLoaderView(context); + this.viewsToAppend.add(loaderView); + _loaderView = loaderView; + } + + protected constructor( + context: Context, + viewsToPrepend: ArrayList, + viewsToAppend: ArrayList) : super( + viewsToPrepend = viewsToPrepend, + viewsToAppend = viewsToAppend) + { + val loaderView = createLoaderView(context); + this.viewsToAppend.add(loaderView); + _loaderView = loaderView; + } + + fun setLoading(loading: Boolean) { + if (_loading == loading) { + return; + } + + _loading = loading; + + if (loading) { + _loaderView?.let { + it.visibility = View.VISIBLE; + (it.drawable as Animatable?)?.start(); + }; + } else { + _loaderView?.let { + it.visibility = View.INVISIBLE; + (it.drawable as Animatable?)?.stop(); + }; + } + } + + override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { + super.onDetachedFromRecyclerView(recyclerView); + (_loaderView?.drawable as Animatable?)?.stop(); + } + + companion object { + private fun createLoaderView(context: Context): ImageView { + val loaderView = ImageView(context); + loaderView.visibility = View.GONE; + loaderView.contentDescription = context.resources.getString(R.string.loading); + loaderView.setImageResource(R.drawable.ic_loader_animated); + + val lp = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50.0f, context.resources.displayMetrics).toInt()); + lp.marginStart = 10; + lp.marginEnd = 10; + lp.gravity = Gravity.CENTER; + loaderView.layoutParams = lp; + + return loaderView; + } + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/InsertedViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/InsertedViewHolder.kt new file mode 100644 index 00000000..64ffcc4b --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/InsertedViewHolder.kt @@ -0,0 +1,46 @@ +package com.futo.platformplayer.views.adapters + +import android.content.Context +import android.util.Log +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.recyclerview.widget.RecyclerView.ViewHolder + +class InsertedViewHolder : ViewHolder where TViewHolder : ViewHolder { + private val _container: FrameLayout; + private var _boundView: View? = null; + + val childViewHolder: TViewHolder; + + constructor(context: Context, childViewHolder: TViewHolder) : super(FrameLayout(context)) { + _container = itemView as FrameLayout; + this.childViewHolder = childViewHolder; + + _container.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT); + _container.addView(childViewHolder.itemView); + } + + fun bindView(view: View) { + _boundView?.let { _container.removeView(it); } + childViewHolder.itemView.visibility = View.GONE; + + val parent = view.parent; + if (parent != null && parent is ViewGroup) { + parent.removeView(view); + } + + _container.addView(view); + _boundView = view; + } + + fun bindChild() { + val boundView = _boundView; + if (boundView != null) { + _container.removeView(boundView); + _boundView = null; + } + + childViewHolder.itemView.visibility = View.VISIBLE; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/ItemMoveCallback.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/ItemMoveCallback.kt new file mode 100644 index 00000000..4b34d353 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/ItemMoveCallback.kt @@ -0,0 +1,47 @@ +package com.futo.platformplayer.views.adapters + +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.Event2 + +class ItemMoveCallback : ItemTouchHelper.Callback { + var onRowMoved = Event2(); + var onRowSelected = Event1(); + var onRowClear = Event1(); + + constructor() : super() { } + + override fun isLongPressDragEnabled(): Boolean { return true; } + override fun isItemViewSwipeEnabled(): Boolean { return false; } + + override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { + val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN; + return makeMovementFlags(dragFlags, 0); + } + + override fun onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder): Boolean { + onRowMoved.emit(viewHolder.absoluteAdapterPosition, target.absoluteAdapterPosition); + return true; + } + + override fun onSelectedChanged(viewHolder: ViewHolder?, actionState: Int) { + if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { + if (viewHolder != null) { + onRowSelected.emit(viewHolder); + } + } + + super.onSelectedChanged(viewHolder, actionState); + } + + override fun clearView(recyclerView: RecyclerView, viewHolder: ViewHolder) { + super.clearView(recyclerView, viewHolder); + onRowClear.emit(viewHolder); + } + + override fun onSwiped(viewHolder: ViewHolder, direction: Int) { + + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistView.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistView.kt new file mode 100644 index 00000000..10335eaf --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistView.kt @@ -0,0 +1,169 @@ +package com.futo.platformplayer.views.adapters + +import android.animation.ObjectAnimator +import android.content.Context +import android.view.View +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.bumptech.glide.Glide +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.images.GlideHelper.Companion.crossfade +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.polycentric.PolycentricCache +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.views.others.CreatorThumbnail +import com.futo.platformplayer.views.FeedStyle +import com.futo.platformplayer.views.platform.PlatformIndicator + + +open class PlaylistView : LinearLayout { + protected val _feedStyle : FeedStyle; + + protected val _imageThumbnail: ImageView + protected val _imageChannel: ImageView? + protected val _creatorThumbnail: CreatorThumbnail? + protected val _imageNeopassChannel: ImageView?; + protected val _platformIndicator: PlatformIndicator; + protected val _textPlaylistName: TextView + protected val _textVideoCount: TextView + protected val _textPlaylistItems: TextView + protected val _textChannelName: TextView + protected var _neopassAnimator: ObjectAnimator? = null; + + private val _taskLoadValidClaims = TaskHandler(StateApp.instance.scopeGetter, + { PolycentricCache.instance.getValidClaimsAsync(it).await() }) + .success { it -> updateClaimsLayout(it, animate = true) } + .exception { + Logger.w(TAG, "Failed to load claims.", it); + }; + + val onPlaylistClicked = Event1(); + val onChannelClicked = Event1(); + + var currentPlaylist: IPlatformPlaylist? = null + private set + + val content: IPlatformContent? get() = currentPlaylist; + + constructor(context: Context, feedStyle : FeedStyle) : super(context) { + inflate(feedStyle); + _feedStyle = feedStyle; + + _imageThumbnail = findViewById(R.id.image_thumbnail); + _imageChannel = findViewById(R.id.image_channel_thumbnail); + _creatorThumbnail = findViewById(R.id.creator_thumbnail); + _platformIndicator = findViewById(R.id.thumbnail_platform); + _textPlaylistName = findViewById(R.id.text_playlist_name); + _textVideoCount = findViewById(R.id.text_video_count); + _textChannelName = findViewById(R.id.text_channel_name); + _textPlaylistItems = findViewById(R.id.text_playlist_items); + _imageNeopassChannel = findViewById(R.id.image_neopass_channel); + + setOnClickListener { onOpenClicked() }; + _imageChannel?.setOnClickListener { currentPlaylist?.let { onChannelClicked.emit(it.author) } }; + _textChannelName.setOnClickListener { currentPlaylist?.let { onChannelClicked.emit(it.author) } }; + } + + protected open fun inflate(feedStyle: FeedStyle) { + inflate(context, when(feedStyle) { + FeedStyle.PREVIEW -> R.layout.list_playlist_feed_preview + else -> R.layout.list_playlist_feed + }, this) + } + + protected open fun onOpenClicked() { + currentPlaylist?.let { + onPlaylistClicked.emit(it); + } + } + + + open fun bind(content: IPlatformContent) { + _taskLoadValidClaims.cancel(); + + if (content.author.id.claimType > 0) { + val cachedClaims = PolycentricCache.instance.getCachedValidClaims(content.author.id); + if (cachedClaims != null) { + updateClaimsLayout(cachedClaims, animate = false); + } else { + updateClaimsLayout(null, animate = false); + _taskLoadValidClaims.run(content.author.id); + } + } else { + updateClaimsLayout(null, animate = false); + } + + isClickable = true; + + _imageChannel?.let { + if (content.author.thumbnail != null) + Glide.with(it) + .load(content.author.thumbnail) + .placeholder(R.drawable.placeholder_channel_thumbnail) + .into(it) + else + Glide.with(it).load(R.drawable.placeholder_channel_thumbnail).into(it); + }; + + _imageChannel?.clipToOutline = true; + + _textPlaylistName.text = content.name; + _textChannelName.text = content.author.name; + _textPlaylistItems.text = ""; //TODO: Show items + + _platformIndicator.setPlatformFromClientID(content.id.pluginId); + + if(content is IPlatformPlaylist) { + val playlist = content; + + currentPlaylist = playlist + val thumbnail = playlist.thumbnail + if(thumbnail != null) + Glide.with(_imageThumbnail) + .load(thumbnail) + .placeholder(R.drawable.placeholder_video_thumbnail) + .crossfade() + .into(_imageThumbnail); + else + Glide.with(_imageThumbnail) + .load(R.drawable.placeholder_video_thumbnail) + .crossfade() + .into(_imageThumbnail); + + _textVideoCount.text = content.videoCount.toString(); + } + else { + currentPlaylist = null; + _imageThumbnail.setImageResource(0); + } + } + + private fun updateClaimsLayout(claims: PolycentricCache.CachedOwnedClaims?, animate: Boolean) { + _neopassAnimator?.cancel(); + _neopassAnimator = null; + + val harborAvailable = claims != null && !claims.ownedClaims.isNullOrEmpty(); + if (harborAvailable) { + _imageNeopassChannel?.visibility = View.VISIBLE + if (animate) { + _neopassAnimator = ObjectAnimator.ofFloat(_imageNeopassChannel, "alpha", 0.0f, 1.0f).setDuration(500) + _neopassAnimator?.start() + } + } else { + _imageNeopassChannel?.visibility = View.GONE + } + + _creatorThumbnail?.setHarborAvailable(harborAvailable, animate) + } + + companion object { + private val TAG = "VideoPreviewViewHolder" + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistsAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistsAdapter.kt new file mode 100644 index 00000000..676f68f0 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistsAdapter.kt @@ -0,0 +1,68 @@ +package com.futo.platformplayer.views.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.states.StatePlaylists +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.models.Playlist + +class PlaylistsAdapter : RecyclerView.Adapter { + private val _dataset: ArrayList; + + val onClick = Event1(); + val onPlay = Event1(); + val onRemoved = Event1(); + + private val _inflater: LayoutInflater; + private val _deletionConfirmationMessage: String; + + constructor(dataset: ArrayList, inflater: LayoutInflater, deletionConfirmationMessage: String) : super() { + _dataset = dataset; + _inflater = inflater; + _deletionConfirmationMessage = deletionConfirmationMessage; + } + + override fun getItemCount() = _dataset.size; + + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): PlaylistsViewHolder { + val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.list_playlists, viewGroup, false); + val holder = PlaylistsViewHolder(view); + holder.onClick.subscribe { + val playlist = holder.playlist; + if (playlist != null) + onClick.emit(playlist); + }; + + holder.onPlay.subscribe { + val playlist = holder.playlist; + if (playlist != null) { + onPlay.emit(playlist); + } + }; + + holder.onRemove.subscribe { + val playlist = holder.playlist; + if (playlist != null) { + UIDialogs.showConfirmationDialog(_inflater.context, _deletionConfirmationMessage, { + val index = _dataset.indexOf(playlist); + if (index >= 0) { + _dataset.removeAt(index); + notifyItemRemoved(index); + onRemoved.emit(playlist); + } + + StatePlaylists.instance.removePlaylist(playlist); + }); + } + }; + + return holder; + } + + override fun onBindViewHolder(viewHolder: PlaylistsViewHolder, position: Int) { + viewHolder.bind(_dataset[position]) + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistsViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistsViewHolder.kt new file mode 100644 index 00000000..2ddbd8f6 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistsViewHolder.kt @@ -0,0 +1,58 @@ +package com.futo.platformplayer.views.adapters + +import android.view.View +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.images.GlideHelper.Companion.crossfade +import com.futo.platformplayer.models.Playlist + +class PlaylistsViewHolder : ViewHolder { + private val _root: ConstraintLayout; + private val _imageThumbnail: ImageView; + private val _textName: TextView; + private val _textMetadata: TextView; + private val _buttonTrash: ImageButton; + //private val _buttonPlay: ImageButton; + + var playlist: Playlist? = null + private set; + + val onClick = Event0(); + val onRemove = Event0(); + val onPlay = Event0(); + + constructor(view: View) : super(view) { + _root = view.findViewById(R.id.root); + _imageThumbnail = view.findViewById(R.id.image_video_thumbnail); + _textName = view.findViewById(R.id.text_name); + _textMetadata = view.findViewById(R.id.text_metadata); + _buttonTrash = view.findViewById(R.id.button_trash); + //_buttonPlay = view.findViewById(R.id.button_play); + + _root.setOnClickListener { onClick.emit(); }; + _buttonTrash.setOnClickListener { onRemove.emit(); }; + //_buttonPlay.setOnClickListener { onPlay.emit(); }; + } + + fun bind(p: Playlist) { + if (p.videos.isNotEmpty()) { + Glide.with(_imageThumbnail) + .load(p.videos[0].thumbnails.getLQThumbnail()) + .placeholder(R.drawable.placeholder_video_thumbnail) + .crossfade() + .into(_imageThumbnail); + } else { + _imageThumbnail.setImageResource(R.drawable.placeholder_video_thumbnail); + } + + _textName.text = p.name; + _textMetadata.text = "${p.videos.size} videos"; + playlist = p; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewContentListAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewContentListAdapter.kt new file mode 100644 index 00000000..1e370010 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewContentListAdapter.kt @@ -0,0 +1,159 @@ +package com.futo.platformplayer.views.adapters + +import android.content.Context +import android.util.Log +import android.view.* +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails +import com.futo.platformplayer.api.media.models.nested.IPlatformNestedContent +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.debug.Stopwatch +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.video.PlayerManager +import com.futo.platformplayer.views.FeedStyle + +class PreviewContentListAdapter : InsertedViewAdapterWithLoader { + private var _initialPlay = true; + private var _previewingViewHolder: ContentPreviewViewHolder? = null; + private val _dataSet: ArrayList; + private val _exoPlayer: PlayerManager?; + private val _feedStyle : FeedStyle; + private var _paused: Boolean = false; + + val onContentUrlClicked = Event2(); + val onContentClicked = Event2(); + val onChannelClicked = Event1(); + val onAddToClicked = Event1(); + val onAddToQueueClicked = Event1(); + + private var _taskLoadContent = TaskHandler, Pair>( + StateApp.instance.scopeGetter, { (viewHolder, video) -> + val stopwatch = Stopwatch() + val contentDetails = StatePlatform.instance.getContentDetails(video.url).await(); + stopwatch.logAndNext(TAG, "Retrieving video detail (IO thread)") + return@TaskHandler Pair(viewHolder, contentDetails!!) + }).success { previewContentDetails(it.first, it.second) } + + constructor(context: Context, feedStyle : FeedStyle, dataSet: ArrayList, exoPlayer: PlayerManager? = null, + initialPlay: Boolean = false, viewsToPrepend: ArrayList = arrayListOf(), + viewsToAppend: ArrayList = arrayListOf()) : super(context, viewsToPrepend, viewsToAppend) { + + this._feedStyle = feedStyle; + this._dataSet = dataSet; + this._initialPlay = initialPlay; + this._exoPlayer = exoPlayer; + } + + override fun getChildCount(): Int = _dataSet.size; + override fun getItemViewType(position: Int): Int { + val p = parentToChildPosition(position); + if (p < 0) { + return -1; + } + + val item = _dataSet.getOrNull(p) ?: return -1; + return item.contentType.value; + } + override fun createChild(viewGroup: ViewGroup, viewType: Int): ContentPreviewViewHolder { + if(viewType == -1) + return EmptyPreviewViewHolder(viewGroup); + val contentType = ContentType.fromInt(viewType); + return when(contentType) { + ContentType.PLACEHOLDER -> createPlaceholderViewHolder(viewGroup); + ContentType.MEDIA -> createVideoPreviewViewHolder(viewGroup); + ContentType.POST -> createPostViewHolder(viewGroup); + ContentType.PLAYLIST -> createPlaylistViewHolder(viewGroup); + ContentType.NESTED_VIDEO -> createNestedViewHolder(viewGroup); + else -> EmptyPreviewViewHolder(viewGroup) + } + } + + private fun createPostViewHolder(viewGroup: ViewGroup): PreviewPostViewHolder = PreviewPostViewHolder(viewGroup, _feedStyle).apply { + this.onContentClicked.subscribe { this@PreviewContentListAdapter.onContentClicked.emit(it, 0); } + this.onChannelClicked.subscribe { this@PreviewContentListAdapter.onChannelClicked.emit(it); } + } + private fun createNestedViewHolder(viewGroup: ViewGroup): PreviewNestedVideoViewHolder = PreviewNestedVideoViewHolder(viewGroup, _feedStyle, _exoPlayer).apply { + this.onContentUrlClicked.subscribe(this@PreviewContentListAdapter.onContentUrlClicked::emit); + this.onVideoClicked.subscribe(this@PreviewContentListAdapter.onContentClicked::emit); + this.onChannelClicked.subscribe(this@PreviewContentListAdapter.onChannelClicked::emit); + this.onAddToClicked.subscribe(this@PreviewContentListAdapter.onAddToClicked::emit); + this.onAddToQueueClicked.subscribe(this@PreviewContentListAdapter.onAddToQueueClicked::emit); + }; + private fun createPlaceholderViewHolder(viewGroup: ViewGroup): PreviewPlaceholderViewHolder + = PreviewPlaceholderViewHolder(viewGroup, _feedStyle); + private fun createVideoPreviewViewHolder(viewGroup: ViewGroup): PreviewVideoViewHolder = PreviewVideoViewHolder(viewGroup, _feedStyle, _exoPlayer).apply { + this.onVideoClicked.subscribe(this@PreviewContentListAdapter.onContentClicked::emit); + this.onChannelClicked.subscribe(this@PreviewContentListAdapter.onChannelClicked::emit); + this.onAddToClicked.subscribe(this@PreviewContentListAdapter.onAddToClicked::emit); + this.onAddToQueueClicked.subscribe(this@PreviewContentListAdapter.onAddToQueueClicked::emit); + }; + private fun createPlaylistViewHolder(viewGroup: ViewGroup): PreviewPlaylistViewHolder = PreviewPlaylistViewHolder(viewGroup, _feedStyle).apply { + this.onPlaylistClicked.subscribe { this@PreviewContentListAdapter.onContentClicked.emit(it, 0L) }; + this.onChannelClicked.subscribe(this@PreviewContentListAdapter.onChannelClicked::emit); + }; + + override fun bindChild(viewHolder: ContentPreviewViewHolder, position: Int) { + val value = _dataSet[position]; + + viewHolder.bind(value); + if (_initialPlay && position == 0) { + _initialPlay = false; + + if (_feedStyle != FeedStyle.THUMBNAIL) + preview(viewHolder); + } + } + + fun preview(viewHolder: ContentPreviewViewHolder) { + Log.v(TAG, "previewing content"); + if (viewHolder == _previewingViewHolder) + return + + val content = viewHolder.content ?: return + if(content is IPlatformVideoDetails) + previewContentDetails(viewHolder, content); + else if(content is IPlatformVideo) + _taskLoadContent.run(Pair(viewHolder, content)); + else if(content is IPlatformNestedContent) + previewContentDetails(viewHolder, null); + } + fun stopPreview() { + _taskLoadContent.cancel(); + _previewingViewHolder?.stopPreview(); + _previewingViewHolder = null; + } + fun pausePreview() { + _previewingViewHolder?.pausePreview() + _paused = true; + } + fun resumePreview() { + _previewingViewHolder?.resumePreview() + _paused = false; + } + + fun release() { + _taskLoadContent.dispose(); + onContentUrlClicked.clear(); + onContentClicked.clear(); + onChannelClicked.clear(); + onAddToClicked.clear(); + onAddToQueueClicked.clear(); + } + + private fun previewContentDetails(viewHolder: ContentPreviewViewHolder, videoDetails: IPlatformContentDetails?) { + _previewingViewHolder?.stopPreview(); + viewHolder.preview(videoDetails, _paused); + _previewingViewHolder = viewHolder; + } + + companion object { + private val TAG = "VideoPreviewListAdapter"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewNestedVideoView.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewNestedVideoView.kt new file mode 100644 index 00000000..83b63ff4 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewNestedVideoView.kt @@ -0,0 +1,151 @@ +package com.futo.platformplayer.views.adapters + +import android.content.Context +import android.view.View +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails +import com.futo.platformplayer.api.media.models.nested.IPlatformNestedContent +import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails +import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.images.GlideHelper.Companion.loadThumbnails +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.video.PlayerManager +import com.futo.platformplayer.views.FeedStyle +import com.futo.platformplayer.views.platform.PlatformIndicator +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class PreviewNestedVideoView : PreviewVideoView { + + protected val _platformIndicatorNested: PlatformIndicator; + protected val _containerLoader: LinearLayout; + protected val _containerUnavailable: LinearLayout; + protected val _textNestedUrl: TextView; + + private var _content: IPlatformContent? = null; + private var _contentNested: IPlatformContentDetails? = null; + + private var _contentSupported = false; + + val onContentUrlClicked = Event2(); + + constructor(context: Context, feedStyle: FeedStyle, exoPlayer: PlayerManager? = null): super(context, feedStyle, exoPlayer) { + _platformIndicatorNested = findViewById(R.id.thumbnail_platform_nested); + _containerLoader = findViewById(R.id.container_loader); + _containerUnavailable = findViewById(R.id.container_unavailable); + _textNestedUrl = findViewById(R.id.text_nested_url); + } + + override fun inflate(feedStyle: FeedStyle) { + inflate(context, when(feedStyle) { + FeedStyle.PREVIEW -> R.layout.list_video_preview_nested + else -> R.layout.list_video_thumbnail_nested + }, this) + } + + override fun onOpenClicked() { + if(_contentNested is IPlatformVideoDetails) + super.onOpenClicked(); + else if(_content is IPlatformNestedContent) { + (_content as IPlatformNestedContent).let { + onContentUrlClicked.emit(it.contentUrl, if(_contentSupported) it.nestedContentType else ContentType.URL); + }; + } + } + + + override fun bind(content: IPlatformContent) { + _content = content; + _contentNested = null; + + super.bind(content); + + _platformIndicator.setPlatformFromClientID(content.id.pluginId); + _platformIndicatorNested.setPlatformFromClientID(content.id.pluginId); + + if(content is IPlatformNestedContent) { + _textNestedUrl.text = content.contentUrl; + _imageVideo.loadThumbnails(content.contentThumbnails, true) { + it.placeholder(R.drawable.placeholder_video_thumbnail) + .into(_imageVideo); + }; + + + _contentSupported = content.contentSupported; + if(!_contentSupported) { + _containerUnavailable.visibility = View.VISIBLE; + _containerLoader.visibility = View.GONE; + } + else { + if(_feedStyle == FeedStyle.THUMBNAIL) + _platformIndicator.setPlatformFromClientID(content.contentPlugin); + else + _platformIndicatorNested.setPlatformFromClientID(content.contentPlugin); + _containerUnavailable.visibility = View.GONE; + if(_feedStyle == FeedStyle.PREVIEW) + loadNested(content); + } + } + else { + _contentSupported = false; + _containerUnavailable.visibility = View.VISIBLE; + _containerLoader.visibility = View.GONE; + } + } + + private fun loadNested(content: IPlatformNestedContent) { + Logger.i(TAG, "Loading nested content [${content.contentUrl}]"); + _containerLoader.visibility = View.VISIBLE; + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { + val def = StatePlatform.instance.getContentDetails(content.contentUrl); + def.invokeOnCompletion { + StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { + Logger.i(TAG, "Loaded nested content [${content.contentUrl}] (${_content == content})"); + if(it != null) { + Logger.e(TAG, "Failed to load nested", it); + if(_content == content) { + _containerUnavailable.visibility = View.VISIBLE; + _containerLoader.visibility = View.GONE; + } + //TODO: Handle exception + } + else if(_content == content) { + _containerLoader.visibility = View.GONE; + val nestedContent = def.getCompleted(); + _contentNested = nestedContent; + if(nestedContent is IPlatformVideoDetails) { + super.bind(nestedContent); + if(_feedStyle == FeedStyle.PREVIEW) { + _platformIndicator.setPlatformFromClientID(content.id.pluginId); + _platformIndicatorNested.setPlatformFromClientID(nestedContent.id.pluginId); + } + else + _platformIndicatorNested.setPlatformFromClientID(content.id.pluginId); + } + else { + _containerUnavailable.visibility = View.VISIBLE; + } + } + } + }; + } + } + + override fun preview(video: IPlatformContentDetails?, paused: Boolean) { + if(video != null) + super.preview(video, paused); + else if(_content is IPlatformVideoDetails) _contentNested?.let { + super.preview(it, paused); + }; + } + + companion object { + val TAG = "PreviewNestedVideoView"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewNestedVideoViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewNestedVideoViewHolder.kt new file mode 100644 index 00000000..1f6b516c --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewNestedVideoViewHolder.kt @@ -0,0 +1,62 @@ +package com.futo.platformplayer.views.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.FrameLayout +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.video.PlayerManager +import com.futo.platformplayer.views.FeedStyle + + +class PreviewNestedVideoViewHolder : ContentPreviewViewHolder { + val onContentUrlClicked = Event2(); + val onVideoClicked = Event2(); + val onChannelClicked = Event1(); + val onAddToClicked = Event1(); + val onAddToQueueClicked = Event1(); + + override val content: IPlatformContent? get() = view.content; + private val view: PreviewNestedVideoView get() = itemView as PreviewNestedVideoView; + + constructor(viewGroup: ViewGroup, feedStyle : FeedStyle, exoPlayer: PlayerManager? = null): super(PreviewNestedVideoView(viewGroup.context, feedStyle, exoPlayer)) { + view.onContentUrlClicked.subscribe(onContentUrlClicked::emit); + view.onVideoClicked.subscribe(onVideoClicked::emit); + view.onChannelClicked.subscribe(onChannelClicked::emit); + view.onAddToClicked.subscribe(onAddToClicked::emit); + view.onAddToQueueClicked.subscribe(onAddToQueueClicked::emit); + } + + + override fun bind(content: IPlatformContent) { + view.bind(content); + } + + override fun preview(details: IPlatformContentDetails?, paused: Boolean) { + view.preview(details, paused); + } + + override fun stopPreview() { + view.stopPreview(); + } + + override fun pausePreview() { + view.pausePreview(); + } + + override fun resumePreview() { + view.resumePreview(); + } + + + + companion object { + private val TAG = "PreviewNestedVideoViewHolder" + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewPlaceholderViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewPlaceholderViewHolder.kt new file mode 100644 index 00000000..692aa8f4 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewPlaceholderViewHolder.kt @@ -0,0 +1,52 @@ +package com.futo.platformplayer.views.adapters + +import android.content.Context +import android.graphics.drawable.Animatable +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.ImageView +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails +import com.futo.platformplayer.api.media.models.contents.PlatformContentPlaceholder +import com.futo.platformplayer.views.FeedStyle +import com.futo.platformplayer.views.platform.PlatformIndicator + + +class PreviewPlaceholderViewHolder : ContentPreviewViewHolder { + override var content: IPlatformContent? = null; + + private val _loader: ImageView; + private val _platformIndicator: PlatformIndicator; + + val context: Context; + + //TODO: Aspect ratio sizing of layout + constructor(viewGroup: ViewGroup, feedStyle: FeedStyle) : super(LayoutInflater.from(viewGroup.context).inflate(when(feedStyle) { + FeedStyle.PREVIEW -> R.layout.list_placeholder_preview + FeedStyle.THUMBNAIL -> R.layout.list_placeholder_thumbnail + else -> R.layout.list_placeholder_thumbnail + }, viewGroup, false)) { + context = itemView.context; + _loader = itemView.findViewById(R.id.loader); + _platformIndicator = itemView.findViewById(R.id.thumbnail_platform); + + (_loader.drawable as Animatable?)?.start(); //TODO: stop? + } + + override fun bind(content: IPlatformContent) { + if(content is PlatformContentPlaceholder) + _platformIndicator.setPlatformFromClientID(content.id.pluginId); + else + _platformIndicator.clearPlatform(); + } + + override fun preview(video: IPlatformContentDetails?, paused: Boolean) { } + override fun stopPreview() { } + override fun pausePreview() { } + override fun resumePreview() { } + + companion object { + private val TAG = "PlaceholderPreviewViewHolder" + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewPlaylistViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewPlaylistViewHolder.kt new file mode 100644 index 00000000..8deeca26 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewPlaylistViewHolder.kt @@ -0,0 +1,37 @@ +package com.futo.platformplayer.views.adapters + +import android.view.ViewGroup +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails +import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.views.FeedStyle + + +class PreviewPlaylistViewHolder : ContentPreviewViewHolder { + val onPlaylistClicked = Event1(); + val onChannelClicked = Event1(); + + val currentPlaylist: IPlatformPlaylist? get() = view.currentPlaylist; + + override val content: IPlatformContent? get() = currentPlaylist; + + private val view: PlaylistView get() = itemView as PlaylistView; + + constructor(viewGroup: ViewGroup, feedStyle : FeedStyle): super(PlaylistView(viewGroup.context, feedStyle)) { + view.onPlaylistClicked.subscribe(onPlaylistClicked::emit); + view.onChannelClicked.subscribe(onChannelClicked::emit); + } + + override fun bind(content: IPlatformContent) = view.bind(content); + + override fun preview(details: IPlatformContentDetails?, paused: Boolean) = Unit; + override fun stopPreview() = Unit; + override fun pausePreview() = Unit; + override fun resumePreview() = Unit; + + companion object { + private val TAG = "PlaylistViewHolder" + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewPostView.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewPostView.kt new file mode 100644 index 00000000..68f307e6 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewPostView.kt @@ -0,0 +1,316 @@ +package com.futo.platformplayer.views.adapters + +import android.animation.ObjectAnimator +import android.content.Context +import android.content.res.Resources +import android.graphics.drawable.Drawable +import android.view.View +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.children +import com.bumptech.glide.Glide +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.target.Target +import com.bumptech.glide.request.transition.Transition +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.Thumbnails +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.post.IPlatformPost +import com.futo.platformplayer.api.media.models.post.IPlatformPostDetails +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.dp +import com.futo.platformplayer.fixHtmlWhitespace +import com.futo.platformplayer.images.GlideHelper.Companion.crossfade +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.polycentric.PolycentricCache +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.toHumanNowDiffString +import com.futo.platformplayer.views.FeedStyle +import com.futo.platformplayer.views.platform.PlatformIndicator +import com.google.android.material.imageview.ShapeableImageView +import com.google.android.material.shape.CornerFamily +import com.google.android.material.shape.ShapeAppearanceModel + +class PreviewPostView : LinearLayout { + private var _content: IPlatformContent? = null; + + private val _imageAuthorThumbnail: ImageView; + private val _textAuthorName: TextView; + private val _imageNeopassChannel: ImageView; + private val _textMetadata: TextView; + private val _textTitle: TextView; + private val _textDescription: TextView; + private val _platformIndicator: PlatformIndicator; + + private val _layoutImages: LinearLayout?; + private val _imageImage: ImageView?; + private val _layoutImageCount: LinearLayout?; + private val _textImageCount: TextView?; + + private val _layoutRating: LinearLayout?; + private val _imageLikeIcon: ImageView?; + private val _textLikes: TextView?; + private val _imageDislikeIcon: ImageView?; + private val _textDislikes: TextView?; + + private val _layoutComments: LinearLayout?; + private val _textComments: TextView?; + + private var _neopassAnimator: ObjectAnimator? = null; + + private val _taskLoadValidClaims = TaskHandler(StateApp.instance.scopeGetter, + { PolycentricCache.instance.getValidClaimsAsync(it).await() }) + .success { it -> updateClaimsLayout(it, animate = true) } + .exception { + Logger.w(TAG, "Failed to load claims.", it); + }; + + val content: IPlatformContent? get() = _content; + + val onContentClicked = Event1(); + val onChannelClicked = Event1(); + + constructor(context: Context, feedStyle: FeedStyle): super(context) { + inflate(feedStyle); + + _imageAuthorThumbnail = findViewById(R.id.image_author_thumbnail); + _textAuthorName = findViewById(R.id.text_author_name); + _imageNeopassChannel = findViewById(R.id.image_neopass_channel); + _textMetadata = findViewById(R.id.text_metadata); + _textTitle = findViewById(R.id.text_title); + _textDescription = findViewById(R.id.text_description); + _platformIndicator = findViewById(R.id.platform_indicator); + + _layoutImages = findViewById(R.id.layout_images); + _imageImage = findViewById(R.id.image_image); + _layoutImageCount = findViewById(R.id.layout_image_count); + _textImageCount = findViewById(R.id.text_image_count); + + _layoutRating = findViewById(R.id.layout_rating); + _imageLikeIcon = findViewById(R.id.image_like_icon); + _textLikes = findViewById(R.id.text_likes); + _imageDislikeIcon = findViewById(R.id.image_dislike_icon); + _textDislikes = findViewById(R.id.text_dislikes); + + _layoutComments = findViewById(R.id.layout_comments); + _textComments = findViewById(R.id.text_comments); + + val root = findViewById(R.id.root); + root.isClickable = true; + root.setOnClickListener { + _content?.let { + onContentClicked.emit(it); + } + } + + _imageAuthorThumbnail.setOnClickListener { emitChannelClicked(); }; + _textAuthorName.setOnClickListener { emitChannelClicked(); }; + _textMetadata.setOnClickListener { emitChannelClicked(); }; + } + + private fun emitChannelClicked() { + val channel = _content?.author ?: return; + onChannelClicked.emit(channel); + } + + fun inflate(feedStyle: FeedStyle) { + inflate(context, when(feedStyle) { + FeedStyle.PREVIEW -> R.layout.list_post_preview + //else -> R.layout.list_post_preview + else -> R.layout.list_post_thumbnail + }, this) + } + + fun bind(content: IPlatformContent) { + _taskLoadValidClaims.cancel(); + _content = content; + + if (content.author.id.claimType > 0) { + val cachedClaims = PolycentricCache.instance.getCachedValidClaims(content.author.id); + if (cachedClaims != null) { + updateClaimsLayout(cachedClaims, animate = false); + } else { + updateClaimsLayout(null, animate = false); + _taskLoadValidClaims.run(content.author.id); + } + } else { + updateClaimsLayout(null, animate = false); + } + + _textAuthorName.text = content.author.name; + _textMetadata.text = content.datetime?.toHumanNowDiffString()?.let { "$it ago" } ?: ""; + + if (content.author.thumbnail != null) + Glide.with(_imageAuthorThumbnail) + .load(content.author.thumbnail) + .placeholder(R.drawable.placeholder_channel_thumbnail) + .into(_imageAuthorThumbnail) + else + Glide.with(_imageAuthorThumbnail).load(R.drawable.placeholder_channel_thumbnail).into(_imageAuthorThumbnail); + + _imageAuthorThumbnail.clipToOutline = true; + _platformIndicator.setPlatformFromClientID(content.id.pluginId); + + val description = if(content is IPlatformPost) { + if(content.description.isNotEmpty()) + content.description + else if(content is IPlatformPostDetails) + content.content + else + "" + } else ""; + + if (content.name.isNullOrEmpty()) { + _textTitle.visibility = View.GONE; + } else { + _textTitle.text = content.name; + _textTitle.visibility = View.VISIBLE; + } + + _textDescription.text = description.fixHtmlWhitespace(); + + if (content is IPlatformPost) { + setImages(content.thumbnails.filterNotNull()); + } else { + setImages(null); + } + + //TODO: Rating not implemented + _layoutRating?.visibility = View.GONE; + + //TODO: Comments not implemented + _layoutComments?.visibility = View.GONE; + } + + private fun setImages(images: List?) { + //Update image count if exists + if (images == null) { + _layoutImageCount?.visibility = View.GONE; + } else { + if (images.size <= 1) { + _layoutImageCount?.visibility = View.GONE; + } else { + _layoutImageCount?.visibility = View.VISIBLE; + _textImageCount?.text = "${images.size} Images"; + } + } + + //Set single image if exists + _imageImage?.let { imageImage -> + if (!images.isNullOrEmpty()) { + imageImage.visibility = View.VISIBLE; + + val image = images.firstNotNullOfOrNull { it.getLQThumbnail() }; + if (image != null) + Glide.with(imageImage) + .load(image) + .placeholder(R.drawable.placeholder_video_thumbnail) + .listener(object: RequestListener { + override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean { + imageImage.visibility = View.GONE; + return false; + } + override fun onResourceReady(resource: Drawable?, model: Any?, target: Target?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { + return false; + } + }) + .crossfade() + .into(imageImage) + else + imageImage.visibility = View.GONE; + } else { + imageImage.visibility = View.GONE; + } + } + + //Set multi image if exists + _layoutImages?.let { layoutImages -> + for (child in layoutImages.children) { + if (child is ImageView) { + Glide.with(child).clear(child); + } + } + + layoutImages.removeAllViews(); + + if (!images.isNullOrEmpty()) { + val displayMetrics = Resources.getSystem().displayMetrics + val screenWidth = displayMetrics.widthPixels + val maxWidth = screenWidth + var currentWidth = 0 + val four_dp = 4.dp(resources) + + for (url in images.mapNotNull { it.getLQThumbnail() }) { + val shapeableImageView = ShapeableImageView(context).apply { + scaleType = ImageView.ScaleType.CENTER_CROP + adjustViewBounds = true + shapeAppearanceModel = ShapeAppearanceModel.builder().setAllCorners(CornerFamily.ROUNDED, four_dp.toFloat()).build() + } + + Glide.with(context) + .asDrawable() + .load(url) + .into(object : CustomTarget() { + override fun onResourceReady(resource: Drawable, transition: Transition?) { + shapeableImageView.setImageDrawable(resource); + val ratio = shapeableImageView.drawable.intrinsicWidth.toFloat() / shapeableImageView.drawable.intrinsicHeight.toFloat() + val projectedWidth = 105.dp(resources).toFloat() * ratio + + if (currentWidth + projectedWidth <= maxWidth) { + shapeableImageView.layoutParams = LayoutParams( + projectedWidth.toInt(), + LayoutParams.MATCH_PARENT + ).apply { + marginStart = four_dp + marginEnd = four_dp + } + + currentWidth += projectedWidth.toInt() + 2 * four_dp + _layoutImages.addView(shapeableImageView) + } + } + + override fun onLoadCleared(placeholder: Drawable?) { + + } + }) + } + + layoutImages.visibility = View.VISIBLE; + } else { + layoutImages.visibility = View.GONE; + } + }; + } + + private fun updateClaimsLayout(claims: PolycentricCache.CachedOwnedClaims?, animate: Boolean) { + _neopassAnimator?.cancel(); + _neopassAnimator = null; + + val harborAvailable = claims != null && !claims.ownedClaims.isNullOrEmpty(); + if (harborAvailable) { + _imageNeopassChannel.visibility = View.VISIBLE + if (animate) { + _neopassAnimator = ObjectAnimator.ofFloat(_imageNeopassChannel, "alpha", 0.0f, 1.0f).setDuration(500) + _neopassAnimator?.start() + } + } else { + _imageNeopassChannel.visibility = View.GONE + } + + //TODO: Necessary if we decide to use creator thumbnail with neopass indicator instead + //_creatorThumbnail?.setHarborAvailable(harborAvailable, animate) + } + + companion object { + val TAG = "PreviewPostView"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewPostViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewPostViewHolder.kt new file mode 100644 index 00000000..56ffb855 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewPostViewHolder.kt @@ -0,0 +1,37 @@ +package com.futo.platformplayer.views.adapters + +import android.view.ViewGroup +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.video.PlayerManager +import com.futo.platformplayer.views.FeedStyle + + +class PreviewPostViewHolder : ContentPreviewViewHolder { + + val onContentClicked = Event1(); + val onChannelClicked = Event1(); + + override val content: IPlatformContent? get() = view.content; + + private val view: PreviewPostView get() = itemView as PreviewPostView; + + constructor(viewGroup: ViewGroup, feedStyle : FeedStyle, exoPlayer: PlayerManager? = null): super(PreviewPostView(viewGroup.context, feedStyle)) { + view.onContentClicked.subscribe(onContentClicked::emit); + view.onChannelClicked.subscribe(onChannelClicked::emit); + } + + + override fun bind(content: IPlatformContent) = view.bind(content); + + override fun preview(video: IPlatformContentDetails?, paused: Boolean) {}; + override fun stopPreview() {}; + override fun pausePreview() {}; + override fun resumePreview() {}; + + companion object { + private val TAG = "VideoPreviewViewHolder" + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewVideoView.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewVideoView.kt new file mode 100644 index 00000000..ebd9e05b --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewVideoView.kt @@ -0,0 +1,320 @@ +package com.futo.platformplayer.views.adapters + +import android.animation.ObjectAnimator +import android.content.Context +import android.content.res.Resources +import android.util.Log +import android.util.TypedValue +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.bumptech.glide.Glide +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.images.GlideHelper.Companion.crossfade +import com.futo.platformplayer.images.GlideHelper.Companion.loadThumbnails +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.polycentric.PolycentricCache +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StateDownloads +import com.futo.platformplayer.video.PlayerManager +import com.futo.platformplayer.views.others.CreatorThumbnail +import com.futo.platformplayer.views.FeedStyle +import com.futo.platformplayer.views.platform.PlatformIndicator +import com.futo.platformplayer.views.video.FutoThumbnailPlayer + + +open class PreviewVideoView : LinearLayout { + protected val _feedStyle : FeedStyle; + + protected val _imageVideo: ImageView + protected val _imageChannel: ImageView? + protected val _creatorThumbnail: CreatorThumbnail? + protected val _imageNeopassChannel: ImageView?; + protected val _platformIndicator: PlatformIndicator; + protected val _textVideoName: TextView + protected val _textChannelName: TextView + protected val _textVideoMetadata: TextView + protected val _containerDuration: LinearLayout; + protected val _textVideoDuration: TextView; + protected var _playerVideoThumbnail: FutoThumbnailPlayer? = null; + protected val _containerLive: LinearLayout; + protected val _playerContainer: FrameLayout; + protected var _neopassAnimator: ObjectAnimator? = null; + protected val _layoutDownloaded: FrameLayout; + + protected val _button_add_to_queue : View; + protected val _button_add_to : View; + + protected val _exoPlayer: PlayerManager?; + + private val _taskLoadValidClaims = TaskHandler(StateApp.instance.scopeGetter, + { PolycentricCache.instance.getValidClaimsAsync(it).await() }) + .success { it -> updateClaimsLayout(it, animate = true) } + .exception { + Logger.w(TAG, "Failed to load claims.", it); + }; + + val onVideoClicked = Event2(); + val onChannelClicked = Event1(); + val onAddToClicked = Event1(); + val onAddToQueueClicked = Event1(); + + var currentVideo: IPlatformVideo? = null + private set + + val content: IPlatformContent? get() = currentVideo; + + constructor(context: Context, feedStyle : FeedStyle, exoPlayer: PlayerManager? = null) : super(context) { + inflate(feedStyle); + _feedStyle = feedStyle; + val playerContainer = findViewById(R.id.player_container); + + val displayMetrics = Resources.getSystem().displayMetrics; + val width: Double = if (feedStyle == FeedStyle.PREVIEW) { + displayMetrics.widthPixels.toDouble(); + } else { + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 177.0f, displayMetrics).toDouble(); + }; + + /* + val ar = 16.0 / 9.0; + var height = width / ar; + height += TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, displayMetrics); + + val layoutParams = playerContainer.layoutParams; + layoutParams.height = height.roundToInt(); + playerContainer.layoutParams = layoutParams;*/ + + //Logger.i(TAG, "Player container height calculated to be $height."); + + + _playerContainer = findViewById(R.id.player_container); + _imageVideo = findViewById(R.id.image_video_thumbnail) + _imageChannel = findViewById(R.id.image_channel_thumbnail); + _creatorThumbnail = findViewById(R.id.creator_thumbnail); + _platformIndicator = findViewById(R.id.thumbnail_platform); + _textVideoName = findViewById(R.id.text_video_name) + _textChannelName = findViewById(R.id.text_channel_name) + _textVideoMetadata = findViewById(R.id.text_video_metadata) + _textVideoDuration = findViewById(R.id.thumbnail_duration); + _containerDuration = findViewById(R.id.thumbnail_duration_container); + _containerLive = findViewById(R.id.thumbnail_live_container); + _button_add_to_queue = findViewById(R.id.button_add_to_queue); + _button_add_to = findViewById(R.id.button_add_to); + _imageNeopassChannel = findViewById(R.id.image_neopass_channel); + _layoutDownloaded = findViewById(R.id.layout_downloaded); + + this._exoPlayer = exoPlayer + + setOnClickListener { onOpenClicked() }; + _imageChannel.setOnClickListener { currentVideo?.let { onChannelClicked.emit(it.author) } }; + _textChannelName.setOnClickListener { currentVideo?.let { onChannelClicked.emit(it.author) } }; + _textVideoMetadata.setOnClickListener { currentVideo?.let { onChannelClicked.emit(it.author) } }; + _button_add_to.setOnClickListener { currentVideo?.let { onAddToClicked.emit(it) } }; + _button_add_to_queue.setOnClickListener { currentVideo?.let { onAddToQueueClicked.emit(it) } }; + + } + + protected open fun inflate(feedStyle: FeedStyle) { + inflate(context, when(feedStyle) { + FeedStyle.PREVIEW -> R.layout.list_video_preview + else -> R.layout.list_video_thumbnail + }, this) + } + + protected open fun onOpenClicked() { + currentVideo?.let { + val currentPlayer = _playerVideoThumbnail; + var sec = if(currentPlayer != null && currentPlayer.playing) + (currentPlayer.position / 1000).toLong(); + else 0L; + onVideoClicked.emit(it, sec); + } + } + + + open fun bind(content: IPlatformContent) { + _taskLoadValidClaims.cancel(); + + val cachedClaims = PolycentricCache.instance.getCachedValidClaims(content.author.id); + if (cachedClaims != null) { + updateClaimsLayout(cachedClaims, animate = false); + } else { + updateClaimsLayout(null, animate = false); + _taskLoadValidClaims.run(content.author.id); + } + + isClickable = true; + + val isPlanned = (content.datetime?.getNowDiffSeconds() ?: 0) < 0; + + stopPreview(); + + if(_imageChannel != null) + Glide.with(_imageChannel) + .load(content.author.thumbnail) + .placeholder(R.drawable.placeholder_channel_thumbnail) + .into(_imageChannel); + + _imageChannel?.clipToOutline = true; + + _textVideoName.text = content.name; + _textChannelName.text = content.author.name + _layoutDownloaded.visibility = if (StateDownloads.instance.isDownloaded(content.id)) VISIBLE else GONE; + + _platformIndicator.setPlatformFromClientID(content.id.pluginId); + + var metadata = "" + if (content is IPlatformVideo && content.viewCount > 0) { + if(content.isLive) + metadata += "${content.viewCount.toHumanNumber()} watching • "; + else + metadata += "${content.viewCount.toHumanNumber()} views • "; + } + + var timeMeta = ""; + if(isPlanned) { + val ago = content.datetime?.toHumanNowDiffString(true) ?: "" + timeMeta = "available in " + ago; + } + else { + val ago = content.datetime?.toHumanNowDiffString() ?: "" + timeMeta = if (ago.isNotBlank()) ago + " ago" else ago; + } + + if(content is IPlatformVideo) { + val video = content; + + currentVideo = video + + _imageVideo.loadThumbnails(video.thumbnails, true) { + it.placeholder(R.drawable.placeholder_video_thumbnail) + .crossfade() + .into(_imageVideo); + }; + + if(!isPlanned) + _textVideoDuration.text = video.duration.toHumanTime(false); + else + _textVideoDuration.text = "Planned"; + + _playerVideoThumbnail?.setLive(video.isLive); + if(!isPlanned && video.isLive) { + _containerDuration.visibility = GONE; + _containerLive.visibility = VISIBLE; + timeMeta = "LIVE" + } + else { + _containerLive.visibility = GONE; + _containerDuration.visibility = VISIBLE; + } + } + else { + currentVideo = null; + _imageVideo.setImageResource(0); + _containerDuration.visibility = GONE; + _containerLive.visibility = GONE; + } + _textVideoMetadata.text = metadata + timeMeta; + } + + open fun preview(video: IPlatformContentDetails?, paused: Boolean) { + if(video == null) + return; + Logger.i(TAG, "Previewing"); + if(video !is IPlatformVideoDetails) + throw IllegalStateException("Expected VideoDetails"); + + if(_feedStyle == FeedStyle.THUMBNAIL) + return; + + val exoPlayer = _exoPlayer ?: return; + + Log.v(TAG, "video preview start playing" + video.name); + + val playerVideoThumbnail = FutoThumbnailPlayer(context); + playerVideoThumbnail.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); + playerVideoThumbnail.setPlayer(exoPlayer); + if(!exoPlayer.currentState.muted) + playerVideoThumbnail.unmute(); + else + playerVideoThumbnail.mute(); + + playerVideoThumbnail.setTempDuration(video.duration, false); + playerVideoThumbnail.setPreview(video); + _playerContainer.addView(playerVideoThumbnail); + _playerVideoThumbnail = playerVideoThumbnail; + } + fun stopPreview() { + if(_feedStyle == FeedStyle.THUMBNAIL) + return; + Log.v(TAG, "video preview stopping=" + currentVideo?.name); + + val playerVideoThumbnail = _playerVideoThumbnail; + if (playerVideoThumbnail != null) { + playerVideoThumbnail.stop(); + playerVideoThumbnail.setPlayer(null); + _playerContainer.removeView(playerVideoThumbnail); + _playerVideoThumbnail = null; + } + + Log.v(TAG, "video preview playing and made invisible" + currentVideo?.name) + } + fun pausePreview() { + if(_feedStyle == FeedStyle.THUMBNAIL) + return; + Log.v(TAG, "video preview pausing " + currentVideo?.name) + + _playerVideoThumbnail?.pause(); + + Log.v(TAG, "video preview paused " + currentVideo?.name) + } + fun resumePreview() { + if(_feedStyle == FeedStyle.THUMBNAIL) + return; + Log.v(TAG, "video preview resuming " + currentVideo?.name) + + _playerVideoThumbnail?.play(); + + Log.v(TAG, "video preview resumed" + currentVideo?.name) + } + + + //Events + fun setMuteChangedListener(callback : (FutoThumbnailPlayer, Boolean) -> Unit) { + _playerVideoThumbnail?.setMuteChangedListener(callback); + } + + private fun updateClaimsLayout(claims: PolycentricCache.CachedOwnedClaims?, animate: Boolean) { + _neopassAnimator?.cancel(); + _neopassAnimator = null; + + val harborAvailable = claims != null && !claims.ownedClaims.isNullOrEmpty(); + if (harborAvailable) { + _imageNeopassChannel?.visibility = View.VISIBLE + if (animate) { + _neopassAnimator = ObjectAnimator.ofFloat(_imageNeopassChannel, "alpha", 0.0f, 1.0f).setDuration(500) + _neopassAnimator?.start() + } + } else { + _imageNeopassChannel?.visibility = View.GONE + } + + _creatorThumbnail?.setHarborAvailable(harborAvailable, animate) + } + + companion object { + private val TAG = "VideoPreviewViewHolder" + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewVideoViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewVideoViewHolder.kt new file mode 100644 index 00000000..8fed1d82 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/PreviewVideoViewHolder.kt @@ -0,0 +1,46 @@ +package com.futo.platformplayer.views.adapters + +import android.view.ViewGroup +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.video.PlayerManager +import com.futo.platformplayer.views.FeedStyle + + +class PreviewVideoViewHolder : ContentPreviewViewHolder { + + val onVideoClicked = Event2(); + val onChannelClicked = Event1(); + val onAddToClicked = Event1(); + val onAddToQueueClicked = Event1(); + + //val context: Context; + val currentVideo: IPlatformVideo? get() = view.currentVideo; + + override val content: IPlatformContent? get() = currentVideo; + + private val view: PreviewVideoView get() = itemView as PreviewVideoView; + + constructor(viewGroup: ViewGroup, feedStyle : FeedStyle, exoPlayer: PlayerManager? = null): super(PreviewVideoView(viewGroup.context, feedStyle, exoPlayer)) { + view.onVideoClicked.subscribe(onVideoClicked::emit); + view.onChannelClicked.subscribe(onChannelClicked::emit); + view.onAddToClicked.subscribe(onAddToClicked::emit); + view.onAddToQueueClicked.subscribe(onAddToQueueClicked::emit); + } + + + override fun bind(content: IPlatformContent) = view.bind(content); + + override fun preview(video: IPlatformContentDetails?, paused: Boolean) = view.preview(video, paused); + override fun stopPreview() = view.stopPreview(); + override fun pausePreview() = view.pausePreview(); + override fun resumePreview() = view.resumePreview(); + + companion object { + private val TAG = "VideoPreviewViewHolder" + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/SearchSuggestionAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/SearchSuggestionAdapter.kt new file mode 100644 index 00000000..d96a7862 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/SearchSuggestionAdapter.kt @@ -0,0 +1,37 @@ +package com.futo.platformplayer.views.adapters + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageButton +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event1 + +class SearchSuggestionAdapter : RecyclerView.Adapter { + private val _dataset: ArrayList; + + var onAddToQuery = Event1(); + var onClicked = Event1(); + var onRemove = Event1(); + var isHistorical: Boolean = false; + + constructor(dataSet: ArrayList) : super() { + _dataset = dataSet; + } + + override fun getItemCount() = _dataset.size; + + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): SearchSuggestionViewHolder { + val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.list_search_suggestion, viewGroup, false) + val holder = SearchSuggestionViewHolder(view); + holder.onAddToQuery.subscribe { suggestion -> onAddToQuery.emit(suggestion); }; + holder.onClicked.subscribe { suggestion -> onClicked.emit(suggestion); }; + holder.onRemove.subscribe { suggestion -> onRemove.emit(suggestion); }; + return holder; + } + override fun onBindViewHolder(viewHolder: SearchSuggestionViewHolder, position: Int) { + viewHolder.bind(_dataset[position], isHistorical); + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/SearchSuggestionViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/SearchSuggestionViewHolder.kt new file mode 100644 index 00000000..6da9134e --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/SearchSuggestionViewHolder.kt @@ -0,0 +1,43 @@ +package com.futo.platformplayer.views.adapters + +import android.view.View +import android.widget.ImageButton +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event1 + +class SearchSuggestionViewHolder : ViewHolder { + private val _textSuggestion: TextView + private val _buttonAddToQuery: ImageButton + private val _buttonRemove: ImageButton; + + var onAddToQuery = Event1(); + var onClicked = Event1(); + var onRemove = Event1(); + + var suggestion: String? = null + private set; + + constructor(view: View) : super(view) { + _textSuggestion = view.findViewById(R.id.text_suggestion); + _buttonAddToQuery = view.findViewById(R.id.button_add_to_query); + _buttonRemove = view.findViewById(R.id.button_remove); + + _buttonAddToQuery.setOnClickListener { + suggestion?.let { it1 -> onAddToQuery.emit(it1) }; + }; + _buttonRemove.setOnClickListener { + suggestion?.let { it1 -> onRemove.emit(it1) } + }; + view.setOnClickListener { + suggestion?.let { it1 -> onClicked.emit(it1) }; + }; + } + + fun bind(suggestion: String, isHistorical: Boolean) { + this.suggestion = suggestion; + _textSuggestion.text = suggestion; + _buttonRemove.visibility = if (isHistorical) View.VISIBLE else View.GONE; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionAdapter.kt new file mode 100644 index 00000000..7b7358f3 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionAdapter.kt @@ -0,0 +1,59 @@ +package com.futo.platformplayer.views.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.models.Subscription +import com.futo.platformplayer.states.StateSubscriptions +import com.futo.platformplayer.constructs.Event1 + +class SubscriptionAdapter : RecyclerView.Adapter { + private lateinit var _sortedDataset: List; + private val _inflater: LayoutInflater; + private val _confirmationMessage: String; + + var onClick = Event1(); + var sortBy: Int = 0 + set(value) { + field = value; + updateDataset(); + } + + constructor(inflater: LayoutInflater, confirmationMessage: String) : super() { + _inflater = inflater; + _confirmationMessage = confirmationMessage; + + StateSubscriptions.instance.onSubscriptionsChanged.subscribe { subs, added -> updateDataset(); } + updateDataset(); + } + + override fun getItemCount() = _sortedDataset.size; + + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): SubscriptionViewHolder { + val holder = SubscriptionViewHolder(viewGroup); + holder.onClick.subscribe(onClick::emit); + holder.onTrash.subscribe { + val sub = holder.subscription ?: return@subscribe; + UIDialogs.showConfirmationDialog(_inflater.context, _confirmationMessage, { + StateSubscriptions.instance.removeSubscription(sub.channel.url); + }); + }; + + return holder; + } + + override fun onBindViewHolder(viewHolder: SubscriptionViewHolder, position: Int) { + viewHolder.bind(_sortedDataset[position]); + } + + private fun updateDataset() { + _sortedDataset = when (sortBy) { + 0 -> StateSubscriptions.instance.getSubscriptions().sortedBy({ u -> u.channel.name }) + 1 -> StateSubscriptions.instance.getSubscriptions().sortedByDescending({ u -> u.channel.name }) + else -> throw IllegalStateException("Invalid sorting algorithm selected."); + }.toList(); + + notifyDataSetChanged(); + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionViewHolder.kt new file mode 100644 index 00000000..0cf36cfa --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionViewHolder.kt @@ -0,0 +1,97 @@ +package com.futo.platformplayer.views.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.ImageButton +import android.widget.LinearLayout +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.polycentric.PolycentricCache +import com.futo.platformplayer.R +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.models.Subscription +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.dp +import com.futo.platformplayer.selectBestImage +import com.futo.platformplayer.views.others.CreatorThumbnail +import com.futo.platformplayer.views.platform.PlatformIndicator +import com.futo.polycentric.core.toURLInfoSystemLinkUrl + +class SubscriptionViewHolder : ViewHolder { + private val _layoutSubscription: LinearLayout; + private val _textName: TextView; + private val _creatorThumbnail: CreatorThumbnail; + private val _buttonTrash: ImageButton; + private val _platformIndicator : PlatformIndicator; + + private val _taskLoadProfile = TaskHandler( + StateApp.instance.scopeGetter, + { PolycentricCache.instance.getProfileAsync(it) }) + .success { it -> onProfileLoaded(it, true) } + .exception { + Logger.w(TAG, "Failed to load profile.", it); + }; + + var subscription: Subscription? = null + private set; + + var onClick = Event1(); + var onTrash = Event0(); + + constructor(viewGroup: ViewGroup) : super(LayoutInflater.from(viewGroup.context).inflate(R.layout.list_subscription, viewGroup, false)) { + _layoutSubscription = itemView.findViewById(R.id.layout_subscription); + _textName = itemView.findViewById(R.id.text_name); + _creatorThumbnail = itemView.findViewById(R.id.creator_thumbnail); + _buttonTrash = itemView.findViewById(R.id.button_trash); + _platformIndicator = itemView.findViewById(R.id.platform); + + _layoutSubscription.setOnClickListener { + val sub = subscription; + if (sub != null) { + onClick.emit(sub); + } + }; + + _buttonTrash.setOnClickListener { + onTrash.emit(); + }; + } + + fun bind(sub: Subscription) { + _taskLoadProfile.cancel(); + + this.subscription = sub; + + val cachedProfile = PolycentricCache.instance.getCachedProfile(sub.channel.url, true); + if (cachedProfile != null) { + onProfileLoaded(cachedProfile, false); + } else { + _creatorThumbnail.setThumbnail(sub.channel.thumbnail, false); + _taskLoadProfile.run(sub.channel.id); + } + + _textName.text = sub.channel.name; + _platformIndicator.setPlatformFromClientID(sub.channel.id.pluginId); + } + + private fun onProfileLoaded(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean) { + val dp_46 = 46.dp(itemView.context.resources); + val avatar = cachedPolycentricProfile?.profile?.systemState?.avatar?.selectBestImage(dp_46 * dp_46) + ?.let { it.toURLInfoSystemLinkUrl(cachedPolycentricProfile.profile.system.toProto(), it.process, cachedPolycentricProfile.profile.systemState.servers.toList()) }; + + if (avatar != null) { + _creatorThumbnail.setThumbnail(avatar, animate); + } else { + _creatorThumbnail.setThumbnail(this.subscription?.channel?.thumbnail, animate); + _creatorThumbnail.setHarborAvailable(cachedPolycentricProfile?.profile != null, animate); + } + } + + companion object { + private const val TAG = "SubscriptionViewHolder" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorAdapter.kt new file mode 100644 index 00000000..aa4ee66f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorAdapter.kt @@ -0,0 +1,53 @@ +package com.futo.platformplayer.views.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.constructs.Event1 + +class VideoListEditorAdapter : RecyclerView.Adapter { + private var _videos: ArrayList? = null; + private val _touchHelper: ItemTouchHelper; + + val onClick = Event1(); + val onRemove = Event1(); + var canEdit = false + private set; + + constructor(touchHelper: ItemTouchHelper) : super() { + _touchHelper = touchHelper; + } + + override fun getItemCount() = _videos?.size ?: 0; + + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): VideoListEditorViewHolder { + val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.list_playlist, viewGroup, false); + val holder = VideoListEditorViewHolder(view, _touchHelper); + + holder.onRemove.subscribe { v -> onRemove.emit(v); }; + holder.onClick.subscribe { v -> onClick.emit(v); }; + + return holder; + } + + override fun onBindViewHolder(viewHolder: VideoListEditorViewHolder, position: Int) { + val videos = _videos ?: return; + viewHolder.bind(videos[position], canEdit); + } + + fun setCanEdit(canEdit: Boolean, notify: Boolean = false) { + this.canEdit = canEdit; + if (notify) { + _videos?.let { notifyItemRangeChanged(0, it.size); }; + } + } + + fun setVideos(videos: ArrayList, canEdit: Boolean) { + _videos = videos; + setCanEdit(canEdit, false); + notifyDataSetChanged(); + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorViewHolder.kt new file mode 100644 index 00000000..1089e471 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorViewHolder.kt @@ -0,0 +1,116 @@ +package com.futo.platformplayer.views.adapters + +import android.view.MotionEvent +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.images.GlideHelper.Companion.crossfade +import com.futo.platformplayer.states.StateDownloads +import com.futo.platformplayer.toHumanNowDiffString +import com.futo.platformplayer.toHumanNumber +import com.futo.platformplayer.toHumanTime +import com.futo.platformplayer.views.platform.PlatformIndicator + +class VideoListEditorViewHolder : ViewHolder { + private val _root: ConstraintLayout; + private val _imageThumbnail: ImageView; + private val _textName: TextView; + private val _textAuthor: TextView; + private val _textMetadata: TextView; + private val _textVideoDuration: TextView; + private val _containerDuration: LinearLayout; + private val _containerLive: LinearLayout; + private val _imageRemove: ImageButton; + private val _imageDragDrop: ImageButton; + private val _platformIndicator: PlatformIndicator; + private val _layoutDownloaded: FrameLayout; + + var video: IPlatformVideo? = null + private set; + + val onClick = Event1(); + val onRemove = Event1(); + + constructor(view: View, touchHelper: ItemTouchHelper) : super(view) { + _root = view.findViewById(R.id.root); + _imageThumbnail = view.findViewById(R.id.image_video_thumbnail); + _imageThumbnail?.clipToOutline = true; + _textName = view.findViewById(R.id.text_video_name); + _textAuthor = view.findViewById(R.id.text_author); + _textMetadata = view.findViewById(R.id.text_video_metadata); + _textVideoDuration = view.findViewById(R.id.thumbnail_duration); + _containerDuration = view.findViewById(R.id.thumbnail_duration_container); + _containerLive = view.findViewById(R.id.thumbnail_live_container); + _imageRemove = view.findViewById(R.id.image_trash); + _imageDragDrop = view.findViewById(R.id.image_drag_drop); + _platformIndicator = view.findViewById(R.id.thumbnail_platform); + _layoutDownloaded = view.findViewById(R.id.layout_downloaded); + + _imageDragDrop.setOnTouchListener(View.OnTouchListener { v, event -> + if (event.action == MotionEvent.ACTION_DOWN) { + touchHelper.startDrag(this); + } + false + }); + + _root.setOnClickListener { + val v = video ?: return@setOnClickListener; + onClick.emit(v); + }; + + _imageRemove?.setOnClickListener { + val v = video ?: return@setOnClickListener; + onRemove.emit(v); + }; + } + + fun bind(v: IPlatformVideo, canEdit: Boolean) { + Glide.with(_imageThumbnail) + .load(v.thumbnails.getLQThumbnail()) + .placeholder(R.drawable.placeholder_video_thumbnail) + .crossfade() + .into(_imageThumbnail); + _textName.text = v.name; + _textAuthor.text = v.author.name; + _textVideoDuration.text = v.duration.toHumanTime(false); + + if(v.isLive) { + _containerDuration.visibility = View.GONE; + _containerLive.visibility = View.VISIBLE; + } + else { + _containerLive.visibility = View.GONE; + _containerDuration.visibility = View.VISIBLE; + } + + if (canEdit) { + _imageRemove.visibility = View.VISIBLE; + _imageDragDrop.visibility = View.VISIBLE; + } else { + _imageRemove.visibility = View.GONE; + _imageDragDrop.visibility = View.GONE; + } + + var metadata = ""; + if (v.viewCount > 0) + metadata += "${v.viewCount.toHumanNumber()} views • "; + metadata += v.datetime?.toHumanNowDiffString() ?: ""; + + _platformIndicator.setPlatformFromClientID(v.id.pluginId); + + _textMetadata.text = metadata; + + _layoutDownloaded.visibility = if (StateDownloads.instance.isDownloaded(v.id)) View.VISIBLE else View.GONE; + video = v; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListHorizontalAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListHorizontalAdapter.kt new file mode 100644 index 00000000..eb0dd83f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListHorizontalAdapter.kt @@ -0,0 +1,36 @@ +package com.futo.platformplayer.views.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.constructs.Event1 + +class VideoListHorizontalAdapter : RecyclerView.Adapter { + private val _dataset: ArrayList; + + val onClick = Event1(); + + constructor(dataset: ArrayList) : super() { + _dataset = dataset; + } + + override fun getItemCount() = _dataset.size; + + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): VideoListHorizontalViewHolder { + val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.list_video_horizontal, viewGroup, false); + val holder = VideoListHorizontalViewHolder(view); + holder.onClick.subscribe { + val video = holder.video; + if (video != null) + onClick.emit(video); + }; + + return holder; + } + + override fun onBindViewHolder(viewHolder: VideoListHorizontalViewHolder, position: Int) { + viewHolder.bind(_dataset[position]) + } +} diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListHorizontalViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListHorizontalViewHolder.kt new file mode 100644 index 00000000..001779f1 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListHorizontalViewHolder.kt @@ -0,0 +1,62 @@ +package com.futo.platformplayer.views.adapters + +import android.view.View +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.toHumanTime + +class VideoListHorizontalViewHolder : ViewHolder { + private val _root: ConstraintLayout; + private val _imageThumbnail: ImageView; + private val _textName: TextView; + private val _textAuthor: TextView; + private val _textVideoDuration: TextView; + private val _containerDuration: LinearLayout; + private val _containerLive: LinearLayout; + + var video: IPlatformVideo? = null + private set; + + val onClick = Event0(); + + constructor(view: View) : super(view) { + _root = view.findViewById(R.id.root); + _imageThumbnail = view.findViewById(R.id.image_video_thumbnail); + _imageThumbnail?.clipToOutline = true; + _textName = view.findViewById(R.id.text_video_name); + _textAuthor = view.findViewById(R.id.text_author); + _textVideoDuration = view.findViewById(R.id.thumbnail_duration); + _containerDuration = view.findViewById(R.id.thumbnail_duration_container); + _containerLive = view.findViewById(R.id.thumbnail_live_container); + + _root?.setOnClickListener { onClick.emit(); }; + } + + fun bind(v: IPlatformVideo) { + Glide.with(_imageThumbnail) + .load(v.thumbnails.getLQThumbnail()) + .placeholder(R.drawable.placeholder_video_thumbnail) + .into(_imageThumbnail); + _textName.text = v.name; + _textAuthor.text = v.author.name; + _textVideoDuration.text = v.duration.toHumanTime(false); + + if(v.isLive) { + _containerDuration.visibility = View.GONE; + _containerLive.visibility = View.VISIBLE; + } + else { + _containerLive.visibility = View.GONE; + _containerDuration.visibility = View.VISIBLE; + } + + video = v; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorViewHolder.kt new file mode 100644 index 00000000..fe96d55c --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorViewHolder.kt @@ -0,0 +1,64 @@ +package com.futo.platformplayer.views.adapters.viewholders + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.toHumanNumber +import com.futo.platformplayer.views.adapters.AnyAdapter +import com.futo.platformplayer.views.others.CreatorThumbnail +import com.futo.platformplayer.views.platform.PlatformIndicator +import com.futo.platformplayer.views.subscriptions.SubscribeButton + +class CreatorViewHolder(private val _viewGroup: ViewGroup, private val _tiny: Boolean) : AnyAdapter.AnyViewHolder( + LayoutInflater.from(_viewGroup.context).inflate(R.layout.list_creator, _viewGroup, false)) { + + private val _textName: TextView; + private val _creatorThumbnail: CreatorThumbnail; + private val _textMetadata: TextView; + private val _buttonSubscribe: SubscribeButton; + private val _platformIndicator: PlatformIndicator; + private var _authorLink: PlatformAuthorLink? = null; + + val onClick = Event1(); + + init { + _textName = _view.findViewById(R.id.text_channel_name); + _creatorThumbnail = _view.findViewById(R.id.creator_thumbnail); + _textMetadata = _view.findViewById(R.id.text_channel_metadata); + _buttonSubscribe = _view.findViewById(R.id.button_subscribe); + _platformIndicator = _view.findViewById(R.id.platform_indicator); + + if (_tiny) { + _buttonSubscribe.visibility = View.GONE; + _textMetadata.visibility = View.GONE; + } + + _view.findViewById(R.id.root).setOnClickListener { + val s = _authorLink ?: return@setOnClickListener; + onClick.emit(s); + } + } + + override fun bind(authorLink: PlatformAuthorLink) { + _textName.text = authorLink.name; + _creatorThumbnail.setThumbnail(authorLink.thumbnail, false); + if(authorLink.subscribers == null || (authorLink.subscribers ?: 0) <= 0L) + _textMetadata.visibility = View.GONE; + else { + _textMetadata.text = authorLink.subscribers!!.toHumanNumber() + " subscribers"; + _textMetadata.visibility = View.VISIBLE; + } + _buttonSubscribe.setSubscribeChannel(authorLink.url); + _platformIndicator.setPlatformFromClientID(authorLink.id.pluginId); + _authorLink = authorLink; + } + + companion object { + private const val TAG = "CreatorViewHolder"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/ImportPlaylistsViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/ImportPlaylistsViewHolder.kt new file mode 100644 index 00000000..669f987e --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/ImportPlaylistsViewHolder.kt @@ -0,0 +1,67 @@ +package com.futo.platformplayer.views.adapters.viewholders + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.models.Playlist +import com.futo.platformplayer.views.others.Checkbox +import com.futo.platformplayer.views.adapters.AnyAdapter + +class ImportPlaylistsViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder( + LayoutInflater.from(_viewGroup.context).inflate(R.layout.list_import_playlist, _viewGroup, false)) { + + private val _checkbox: Checkbox; + private val _imageThumbnail: ImageView; + private val _textName: TextView; + private val _textMetadata: TextView; + private val _root: LinearLayout; + private var _playlist: SelectablePlaylist? = null; + + val onSelectedChange = Event1(); + + init { + _checkbox = _view.findViewById(R.id.checkbox); + _imageThumbnail = _view.findViewById(R.id.image_thumbnail); + _textName = _view.findViewById(R.id.text_name); + _textMetadata = _view.findViewById(R.id.text_metadata); + _root = _view.findViewById(R.id.root); + + _checkbox.onValueChanged.subscribe { + _playlist?.selected = it; + _playlist?.let { onSelectedChange.emit(it); }; + }; + + _root.setOnClickListener { + _checkbox.value = !_checkbox.value; + _playlist?.selected = _checkbox.value; + _playlist?.let { onSelectedChange.emit(it); }; + }; + } + + override fun bind(playlist: SelectablePlaylist) { + _textName.text = playlist.playlist.name; + _textMetadata.text = "${playlist.playlist.videos.size} videos"; + _checkbox.value = playlist.selected; + + val thumbnail = playlist.playlist.videos.firstOrNull()?.thumbnails?.getHQThumbnail(); + if (thumbnail != null) + Glide.with(_imageThumbnail) + .load(thumbnail) + .placeholder(R.drawable.placeholder_channel_thumbnail) + .into(_imageThumbnail); + else + Glide.with(_imageThumbnail).clear(_imageThumbnail); + + _playlist = playlist; + } +} + +class SelectablePlaylist( + val playlist: Playlist, + var selected: Boolean = false +) { } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/ImportSubscriptionViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/ImportSubscriptionViewHolder.kt new file mode 100644 index 00000000..499fd3e2 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/ImportSubscriptionViewHolder.kt @@ -0,0 +1,70 @@ +package com.futo.platformplayer.views.adapters.viewholders + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.views.others.Checkbox +import com.futo.platformplayer.views.platform.PlatformIndicator +import com.futo.platformplayer.views.adapters.AnyAdapter + +class ImportSubscriptionViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder( + LayoutInflater.from(_viewGroup.context).inflate(R.layout.list_import_subscription, _viewGroup, false)) { + + private val _checkbox: Checkbox; + private val _imageThumbnail: ImageView; + private val _textName: TextView; + private val _platform: PlatformIndicator; + private val _root: LinearLayout; + private var _channel: SelectableIPlatformChannel? = null; + + val onSelectedChange = Event1(); + + init { + _checkbox = _view.findViewById(R.id.checkbox); + _imageThumbnail = _view.findViewById(R.id.image_channel_thumbnail); + _textName = _view.findViewById(R.id.text_name); + _platform = _view.findViewById(R.id.platform); + _root = _view.findViewById(R.id.root); + + _imageThumbnail.clipToOutline = true; + + _checkbox.onValueChanged.subscribe { + _channel?.selected = it; + _channel?.let { onSelectedChange.emit(it); }; + }; + + _root.setOnClickListener { + _checkbox.value = !_checkbox.value; + _channel?.selected = _checkbox.value; + _channel?.let { onSelectedChange.emit(it); }; + }; + } + + override fun bind(channel: SelectableIPlatformChannel) { + _textName.text = channel.channel.name; + _checkbox.value = channel.selected; + + val thumbnail = channel.channel.thumbnail; + if (thumbnail != null) + Glide.with(_imageThumbnail) + .load(thumbnail) + .placeholder(R.drawable.placeholder_channel_thumbnail) + .into(_imageThumbnail); + else + Glide.with(_imageThumbnail).clear(_imageThumbnail); + + _platform.setPlatformFromClientID(channel.channel.id.pluginId); + _channel = channel; + } +} + +class SelectableIPlatformChannel( + val channel: IPlatformChannel, + var selected: Boolean = false +) { } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/SubscriptionBarViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/SubscriptionBarViewHolder.kt new file mode 100644 index 00000000..9e17d945 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/SubscriptionBarViewHolder.kt @@ -0,0 +1,82 @@ +package com.futo.platformplayer.views.adapters.viewholders + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.polycentric.PolycentricCache +import com.futo.platformplayer.R +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.api.media.PlatformID +import com.futo.platformplayer.api.media.models.channels.SerializedChannel +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.dp +import com.futo.platformplayer.models.Subscription +import com.futo.platformplayer.selectBestImage +import com.futo.platformplayer.views.others.CreatorThumbnail +import com.futo.platformplayer.views.adapters.AnyAdapter +import com.futo.polycentric.core.toURLInfoSystemLinkUrl + +class SubscriptionBarViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder( + LayoutInflater.from(_viewGroup.context).inflate(R.layout.view_subscription_bar_icon, _viewGroup, false)) { + + private val _creatorThumbnail: CreatorThumbnail; + private val _name: TextView; + private var _subscription: Subscription? = null; + private var _channel: SerializedChannel? = null; + + private val _taskLoadProfile = TaskHandler( + StateApp.instance.scopeGetter, + { PolycentricCache.instance.getProfileAsync(it) }) + .success { onProfileLoaded(it, true) } + .exception { + Logger.w(TAG, "Failed to load profile.", it); + }; + + val onClick = Event1(); + + init { + _creatorThumbnail = _view.findViewById(R.id.creator_thumbnail); + _name = _view.findViewById(R.id.text_channel_name); + _view.findViewById(R.id.root).setOnClickListener { + val s = _subscription ?: return@setOnClickListener; + onClick.emit(s); + } + } + + override fun bind(subscription: Subscription) { + _taskLoadProfile.cancel(); + + _channel = subscription.channel; + + val cachedProfile = PolycentricCache.instance.getCachedProfile(subscription.channel.url, true); + if (cachedProfile != null) { + onProfileLoaded(cachedProfile, false); + } else { + _creatorThumbnail.setThumbnail(subscription.channel.thumbnail, false); + _taskLoadProfile.run(subscription.channel.id); + } + + _name.text = subscription.channel.name; + _subscription = subscription; + } + + private fun onProfileLoaded(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean) { + val dp_55 = 55.dp(itemView.context.resources) + val avatar = cachedPolycentricProfile?.profile?.systemState?.avatar?.selectBestImage(dp_55 * dp_55) + ?.let { it.toURLInfoSystemLinkUrl(cachedPolycentricProfile.profile.system.toProto(), it.process, cachedPolycentricProfile.profile.systemState.servers.toList()) }; + + if (avatar != null) { + _creatorThumbnail.setThumbnail(avatar, animate); + } else { + _creatorThumbnail.setThumbnail(_channel?.thumbnail, animate); + _creatorThumbnail.setHarborAvailable(cachedPolycentricProfile?.profile != null, animate); + } + } + + companion object { + private const val TAG = "SubscriptionBarViewHolder"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/TabViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/TabViewHolder.kt new file mode 100644 index 00000000..262311cf --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/TabViewHolder.kt @@ -0,0 +1,58 @@ +package com.futo.platformplayer.views.adapters.viewholders + +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import com.futo.platformplayer.* +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.fragment.mainactivity.bottombar.MenuBottomBarFragment +import com.futo.platformplayer.views.others.Toggle +import com.futo.platformplayer.views.adapters.AnyAdapter + +data class TabViewHolderData(val buttonDefinition: MenuBottomBarFragment.ButtonDefinition, var enabled: Boolean); + +class TabViewHolder(_viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder( + LayoutInflater.from(_viewGroup.context).inflate(R.layout.list_tab, _viewGroup, false)) { + var data: TabViewHolderData? = null; + + private val _imageDragDrop: ImageView = _view.findViewById(R.id.image_drag_drop); + private val _textTabName: TextView = _view.findViewById(R.id.text_tab_name); + private val _toggleTab: Toggle = _view.findViewById(R.id.toggle_tab); + + val onDragDrop = Event1(); + val onEnableChanged = Event1(); + + init { + _toggleTab.onValueChanged.subscribe { + onEnableChanged.emit(it); + }; + _view.isClickable = true; + _view.setOnClickListener { + val d = data ?: return@setOnClickListener; + if (!d.buttonDefinition.canToggle) { + return@setOnClickListener; + } + + d.enabled = !d.enabled; + _toggleTab.setValue(d.enabled, true); + onEnableChanged.emit(d.enabled); + }; + _imageDragDrop.setOnTouchListener(View.OnTouchListener { v, event -> + if (event.action == MotionEvent.ACTION_DOWN) { + onDragDrop.emit(this); + } + false + }); + } + + override fun bind(i: TabViewHolderData) { + _textTabName.text = _view.context.resources.getString(i.buttonDefinition.string); + _toggleTab.visibility = if (i.buttonDefinition.canToggle) View.VISIBLE else View.GONE; + _toggleTab.setValue(i.enabled, false); + data = i; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/VideoDownloadViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/VideoDownloadViewHolder.kt new file mode 100644 index 00000000..e97434b3 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/VideoDownloadViewHolder.kt @@ -0,0 +1,78 @@ +package com.futo.platformplayer.views.adapters.viewholders + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.* +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.downloads.VideoLocal +import com.futo.platformplayer.images.GlideHelper.Companion.loadThumbnails +import com.futo.platformplayer.states.StateDownloads +import com.futo.platformplayer.states.StatePlayer +import com.futo.platformplayer.views.adapters.AnyAdapter + + +class VideoDownloadViewHolder(_viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder( + LayoutInflater.from(_viewGroup.context).inflate(R.layout.list_downloaded, _viewGroup, false)) { + private var _video: VideoLocal? = null; + + private val _videoName: TextView = _view.findViewById(R.id.downloaded_video_name); + private val _videoImage: ImageView = _view.findViewById(R.id.downloaded_video_image); + private val _videoDuration: TextView = _view.findViewById(R.id.downloaded_video_duration); + private val _videoAuthor: TextView = _view.findViewById(R.id.downloaded_author); + private val _videoInfo: TextView = _view.findViewById(R.id.downloaded_video_info); + private val _videoAddToQueue: ImageButton = _view.findViewById(R.id.button_add_to_queue); + private val _videoDelete: LinearLayout = _view.findViewById(R.id.downloaded_video_delete); + private val _videoExport: LinearLayout = _view.findViewById(R.id.button_export); + private val _videoSize: TextView = _view.findViewById(R.id.downloaded_video_size); + + val onClick = Event1(); + + init { + _view.setOnClickListener { + _video?.let { onClick.emit(it) } + }; + _videoDelete.setOnClickListener { + val id = _video?.id ?: return@setOnClickListener; + UIDialogs.showConfirmationDialog(_view.context, "Are you sure you want to delete this video?", { + StateDownloads.instance.deleteCachedVideo(id); + }); + } + _videoAddToQueue.setOnClickListener { + val v = _video ?: return@setOnClickListener; + StatePlayer.instance.addToQueue(v); + } + _videoExport.setOnClickListener { + val v = _video ?: return@setOnClickListener; + StateDownloads.instance.export(v, v.videoSource.firstOrNull(), v.audioSource.firstOrNull(), v.subtitlesSources.firstOrNull()); + } + } + + override fun bind(video: VideoLocal) { + _video = video; + _videoName.text = video.name; + _videoDuration.text = video.duration.toHumanTime(false); + _videoAuthor.text = video.author.name; + _videoSize.text = (video.videoSource.sumOf { it.fileSize } + video.audioSource.sumOf { it.fileSize }).toHumanBytesSize(false); + + val tokens = arrayListOf(); + + if(video.videoSource.isNotEmpty()) { + tokens.add(video.videoSource.maxBy { it.width * it.height }.let { "${it.width}x${it.height} (${it.container})" }); + } + + if (video.audioSource.isNotEmpty()) { + tokens.add(video.audioSource.maxBy { it.bitrate }.let { it.bitrate.toHumanBitrate() }); + } + + _videoInfo.text =tokens.joinToString(" • "); + + _videoImage.loadThumbnails(video.thumbnails, true) { + it.placeholder(R.drawable.placeholder_video_thumbnail) + .into(_videoImage); + }; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt b/app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt new file mode 100644 index 00000000..873b02ea --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt @@ -0,0 +1,163 @@ +package com.futo.platformplayer.views.announcements + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.widget.* +import androidx.constraintlayout.widget.ConstraintLayout +import com.futo.platformplayer.R +import com.futo.platformplayer.dp +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.Announcement +import com.futo.platformplayer.states.AnnouncementType +import com.futo.platformplayer.states.SessionAnnouncement +import com.futo.platformplayer.states.StateAnnouncement +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.toHumanNowDiffString + +class AnnouncementView : LinearLayout { + private val _root: ConstraintLayout; + private val _textTitle: TextView; + private val _textCounter: TextView; + private val _textBody: TextView; + private val _textClose: TextView; + private val _textNever: TextView; + private val _buttonAction: FrameLayout; + private val _textAction: TextView; + private val _textTime: TextView; + private val _category: String?; + private var _currentAnnouncement: Announcement? = null; + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + inflate(context, R.layout.view_announcement, this); + + val dp10 = 10.dp(resources); + setPadding(dp10, dp10, dp10, dp10); + + _root = findViewById(R.id.root); + _textTitle = findViewById(R.id.text_title); + _textCounter = findViewById(R.id.text_counter); + _textBody = findViewById(R.id.text_body); + _textClose = findViewById(R.id.text_close); + _textNever = findViewById(R.id.text_never); + _buttonAction = findViewById(R.id.button_action); + _textAction = findViewById(R.id.text_action); + _textTime = findViewById(R.id.text_time); + + _buttonAction.setOnClickListener { + val a = _currentAnnouncement ?: return@setOnClickListener; + val scope = StateApp.instance.scopeOrNull ?: return@setOnClickListener; + StateAnnouncement.instance.actionAnnouncement(a); + }; + + _textClose.setOnClickListener { + val a = _currentAnnouncement ?: return@setOnClickListener; + StateAnnouncement.instance.closeAnnouncement(a.id); + refresh(); + }; + + _textNever.setOnClickListener { + val a = _currentAnnouncement ?: return@setOnClickListener; + StateAnnouncement.instance.neverAnnouncement(a.id); + refresh(); + }; + + val attrArr = context.obtainStyledAttributes(attrs, R.styleable.AnnouncementView, 0, 0); + _category = attrArr.getText(R.styleable.AnnouncementView_category)?.toString(); + + refresh(); + } + + override fun onAttachedToWindow() { + Logger.i(TAG, "onAttachedToWindow"); + + super.onAttachedToWindow() + StateAnnouncement.instance.onAnnouncementChanged.subscribe(this) { + refresh(); + } + + refresh(); + } + + override fun onDetachedFromWindow() { + Logger.i(TAG, "onDetachedFromWindow"); + + super.onDetachedFromWindow() + StateAnnouncement.instance.onAnnouncementChanged.remove(this) + } + + private fun refresh() { + Logger.i(TAG, "refresh"); + val announcements = StateAnnouncement.instance.getVisibleAnnouncements(_category); + setAnnouncement(announcements.firstOrNull(), announcements.size); + } + + private fun setAnnouncement(announcement: Announcement?, count: Int) { + Logger.i(TAG, "setAnnouncement announcement=$announcement count=$count"); + + _currentAnnouncement = announcement; + + if (announcement == null) { + _root.visibility = View.GONE; + return; + } + + _root.visibility = View.VISIBLE; + + _textTitle.text = announcement.title; + _textBody.text = announcement.msg; + _textCounter.text = "1/${count}"; + + if (announcement.actionName != null) { + _textAction.text = announcement.actionName; + _buttonAction.visibility = View.VISIBLE; + } else { + _buttonAction.visibility = View.GONE; + } + + if(announcement is SessionAnnouncement) { + if(announcement.cancelName != null) + { + _textClose.text = announcement.cancelName; + } + else + _textClose.text = "Dismiss"; + } + else + _textClose.text = "Dismiss"; + + when (announcement.announceType) { + AnnouncementType.DELETABLE -> { + _textClose.visibility = View.VISIBLE; + _textNever.visibility = View.GONE; + } + AnnouncementType.RECURRING -> { + _textClose.visibility = View.VISIBLE; + _textNever.visibility = View.VISIBLE; + } + AnnouncementType.PERMANENT -> { + _textClose.visibility = View.VISIBLE; + _textNever.visibility = View.GONE; + } + AnnouncementType.SESSION -> { + _textClose.visibility = View.VISIBLE; + _textNever.visibility = View.GONE; + } + AnnouncementType.SESSION_RECURRING -> { + _textClose.visibility = View.VISIBLE; + _textNever.visibility = View.VISIBLE; + } + } + + if (announcement.time != null) { + _textTime.visibility = View.VISIBLE; + _textTime.text = announcement.time.toHumanNowDiffString(true) + " ago" + } else { + _textTime.visibility = View.GONE; + } + } + + companion object { + const val TAG = "AnnouncementView" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt b/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt new file mode 100644 index 00000000..1b3ed5a1 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt @@ -0,0 +1,531 @@ +package com.futo.platformplayer.views.behavior + +import android.animation.Animator +import android.animation.AnimatorSet +import android.animation.ObjectAnimator +import android.content.Context +import android.graphics.drawable.Animatable +import android.util.AttributeSet +import android.view.GestureDetector +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.animation.doOnEnd +import androidx.core.animation.doOnStart +import androidx.core.view.GestureDetectorCompat +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.views.others.CircularProgressBar +import kotlinx.coroutines.* + +class GestureControlView : LinearLayout { + private val _scope = CoroutineScope(Dispatchers.Main); + private val _imageFastForward: ImageView; + private val _textFastForward: TextView; + private val _imageRewind: ImageView; + private val _textRewind: TextView; + private val _layoutFastForward: LinearLayout; + private val _layoutRewind: LinearLayout; + private var _rewinding: Boolean = false; + private var _skipping: Boolean = false; + private var _animatorSetControls: AnimatorSet? = null; + private var _animatorSetFastForward: AnimatorSet? = null; + private var _fastForwardCounter: Int = 1; + private var _jobAutoFastForward: Job? = null; + private var _jobExitFastForward: Job? = null; + private var _jobHideControls: Job? = null; + private var _controlsVisible: Boolean = true; + private var _isControlsLocked: Boolean = false; + private var _layoutControls: ViewGroup? = null; + private var _background: View? = null; + private var _soundFactor = 1.0f; + private var _adjustingSound: Boolean = false; + private val _layoutControlsSound: FrameLayout; + private val _progressSound: CircularProgressBar; + private var _animatorSound: ObjectAnimator? = null; + private var _brightnessFactor = 1.0f; + private var _adjustingBrightness: Boolean = false; + private val _layoutControlsBrightness: FrameLayout; + private val _progressBrightness: CircularProgressBar; + private var _isFullScreen = false; + private var _animatorBrightness: ObjectAnimator? = null; + private val _layoutControlsFullscreen: FrameLayout; + private var _adjustingFullscreen: Boolean = false; + private var _fullScreenFactor = 1.0f; + + val onSeek = Event1(); + val onBrightnessAdjusted = Event1(); + val onSoundAdjusted = Event1(); + val onToggleFullscreen = Event0(); + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + LayoutInflater.from(context).inflate(R.layout.view_gesture_controls, this, true); + + _imageFastForward = findViewById(R.id.image_fastforward); + _textFastForward = findViewById(R.id.text_fastforward); + _imageRewind = findViewById(R.id.image_rewind); + _textRewind = findViewById(R.id.text_rewind); + _layoutFastForward = findViewById(R.id.layout_controls_fast_forward); + _layoutRewind = findViewById(R.id.layout_controls_rewind); + _layoutControlsSound = findViewById(R.id.layout_controls_sound); + _progressSound = findViewById(R.id.progress_sound); + _layoutControlsBrightness = findViewById(R.id.layout_controls_brightness); + _progressBrightness = findViewById(R.id.progress_brightness); + _layoutControlsFullscreen = findViewById(R.id.layout_controls_fullscreen); + } + + fun setupTouchArea(view: View, layoutControls: ViewGroup? = null, background: View? = null) { + _layoutControls = layoutControls; + _background = background; + + val gestureController = GestureDetectorCompat(context, object : GestureDetector.OnGestureListener { + override fun onDown(p0: MotionEvent): Boolean { return false; } + override fun onShowPress(p0: MotionEvent) = Unit; + override fun onSingleTapUp(p0: MotionEvent): Boolean { return false; } + override fun onScroll(p0: MotionEvent, p1: MotionEvent, distanceX: Float, distanceY: Float): Boolean { + if (_isFullScreen && _adjustingBrightness) { + val adjustAmount = (distanceY * 2) / height; + _brightnessFactor = (_brightnessFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f); + _progressBrightness.progress = _brightnessFactor; + onBrightnessAdjusted.emit(_brightnessFactor); + } else if (_isFullScreen && _adjustingSound) { + val adjustAmount = (distanceY * 2) / height; + _soundFactor = (_soundFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f); + _progressSound.progress = _soundFactor; + onSoundAdjusted.emit(_soundFactor); + } else if (_adjustingFullscreen) { + val adjustAmount = (distanceY * 2) / height; + _fullScreenFactor = (_fullScreenFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f); + _layoutControlsFullscreen.transitionAlpha = _fullScreenFactor; + } else { + val rx = p0.x / width; + val ry = p0.y / height; + Logger.i(TAG, "rx = $rx, ry = $ry, _isFullScreen = $_isFullScreen") + if (ry > 0.1 && ry < 0.9) { + if (_isFullScreen && rx < 0.4) { + startAdjustingBrightness(); + } else if (_isFullScreen && rx > 0.6) { + startAdjustingSound(); + } else if (rx >= 0.4 && rx <= 0.6) { + startAdjustingFullscreen(); + } + } + } + + return true; + } + override fun onLongPress(p0: MotionEvent) = Unit; + override fun onFling(p0: MotionEvent, p1: MotionEvent, p2: Float, p3: Float): Boolean { return false; } + }); + + gestureController.setOnDoubleTapListener(object : GestureDetector.OnDoubleTapListener { + override fun onSingleTapConfirmed(ev: MotionEvent): Boolean { + if (_skipping) { + return false; + } + + if (_controlsVisible) { + hideControls(); + } else { + showControls(); + } + + return true; + } + + override fun onDoubleTap(ev: MotionEvent): Boolean { + if (_isControlsLocked || _skipping) { + return false; + } + + val rewinding = (ev.x / width) < 0.5; + startFastForward(rewinding); + return true; + } + + override fun onDoubleTapEvent(ev: MotionEvent): Boolean { + return false; + } + }); + + val touchListener = object : OnTouchListener { + override fun onTouch(v: View?, ev: MotionEvent): Boolean { + cancelHideJob(); + + if (_skipping) { + if (ev.action == MotionEvent.ACTION_UP) { + startExitFastForward(); + stopAutoFastForward(); + } else if (ev.action == MotionEvent.ACTION_DOWN) { + _jobExitFastForward?.cancel(); + _jobExitFastForward = null; + + startAutoFastForward(); + fastForwardTick(); + } + } + + if (_adjustingSound && ev.action == MotionEvent.ACTION_UP) { + stopAdjustingSound(); + } + + if (_adjustingBrightness && ev.action == MotionEvent.ACTION_UP) { + stopAdjustingBrightness(); + } + + if (_adjustingFullscreen && ev.action == MotionEvent.ACTION_UP) { + if (_fullScreenFactor > 0.5) { + onToggleFullscreen.emit(); + } + stopAdjustingFullscreen(); + } + + startHideJobIfNecessary(); + return gestureController.onTouchEvent(ev); + } + }; + + view.setOnTouchListener(touchListener); + view.isClickable = true; + } + + fun cancelHideJob() { + _jobHideControls?.cancel(); + _jobHideControls = null; + } + + private fun startHideJob() { + _jobHideControls = _scope.launch(Dispatchers.Main) { + try { + ensureActive(); + delay(3000); + ensureActive(); + + hideControls(); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to hide controls", e); + } + }; + } + + fun startHideJobIfNecessary() { + if (_controlsVisible) { + startHideJob(); + } + } + + fun restartHideJob() { + cancelHideJob(); + startHideJobIfNecessary(); + } + + fun showControls(withHideJob: Boolean = true){ + Logger.i(TAG, "showControls()"); + + if (_isControlsLocked) + return; + + if (_controlsVisible) { + return; + } + + _animatorSetControls?.cancel(); + + val animations = arrayListOf(); + _layoutControls?.let { animations.add(ObjectAnimator.ofFloat(it, "alpha", 0.0f, 1.0f).setDuration( + ANIMATION_DURATION_CONTROLS + )); }; + _background?.let { animations.add(ObjectAnimator.ofFloat(it, "alpha", 0.0f, 1.0f).setDuration( + ANIMATION_DURATION_CONTROLS + )); }; + + val animatorSet = AnimatorSet(); + animatorSet.doOnStart { + _background?.visibility = View.VISIBLE; + _layoutControls?.visibility = View.VISIBLE; + }; + animatorSet.doOnEnd { + _animatorSetControls = null; + }; + animatorSet.playTogether(animations); + animatorSet.start(); + _animatorSetControls = animatorSet; + + _controlsVisible = true + if (withHideJob) { + startHideJobIfNecessary(); + } else { + cancelHideJob(); + } + } + + fun hideControls(animate: Boolean = true){ + Logger.v(TAG, "hideControls(animate: $animate)"); + + if (!_controlsVisible) { + return; + } + + stopFastForward(); + + _animatorSetControls?.cancel(); + + if (animate) { + val animations = arrayListOf(); + _layoutControls?.let { animations.add(ObjectAnimator.ofFloat(it, "alpha", 1.0f, 0.0f).setDuration( + ANIMATION_DURATION_CONTROLS + )); }; + _background?.let { animations.add(ObjectAnimator.ofFloat(it, "alpha", 1.0f, 0.0f).setDuration( + ANIMATION_DURATION_CONTROLS + )); }; + + val animatorSet = AnimatorSet(); + animatorSet.doOnEnd { + _background?.visibility = View.GONE; + _layoutControls?.visibility = View.GONE; + _animatorSetControls = null; + }; + + animatorSet.playTogether(animations); + animatorSet.start(); + + _animatorSetControls = animatorSet; + } else { + _background?.visibility = View.GONE; + _layoutControls?.visibility = View.GONE; + } + + _controlsVisible = false; + } + + fun cleanup() { + _jobExitFastForward?.cancel(); + _jobExitFastForward = null; + _jobAutoFastForward?.cancel(); + _jobAutoFastForward = null; + cancelHideJob(); + _scope.cancel(); + } + + private fun startFastForward(rewinding: Boolean) { + _skipping = true; + _rewinding = rewinding; + _fastForwardCounter = 0; + + fastForwardTick(); + startAutoFastForward(); + + _animatorSetFastForward?.cancel(); + + val animations = arrayListOf(); + val layout = if (rewinding) { _layoutRewind } else { _layoutFastForward }; + animations.add(ObjectAnimator.ofFloat(layout, "alpha", 0.0f, 1.0f).setDuration( + ANIMATION_DURATION_FAST_FORWARD + )); + + if (_controlsVisible) { + _layoutControls?.let { animations.add(ObjectAnimator.ofFloat(it, "alpha", 1.0f, 0.0f).setDuration( + ANIMATION_DURATION_FAST_FORWARD + )); }; + } else { + _background?.let { animations.add(ObjectAnimator.ofFloat(it, "alpha", 0.0f, 1.0f).setDuration( + ANIMATION_DURATION_FAST_FORWARD + )); }; + } + + val animatorSet = AnimatorSet(); + animatorSet.doOnStart { + _background?.visibility = View.VISIBLE; + layout.visibility = View.VISIBLE; + }; + animatorSet.doOnEnd { + _animatorSetFastForward = null; + if (_controlsVisible) { + _layoutControls?.visibility = View.GONE; + } + }; + animatorSet.playTogether(animations); + animatorSet.start(); + _animatorSetFastForward = animatorSet; + + if (rewinding) { + (_imageRewind.drawable as Animatable?)?.start(); + } else { + (_imageFastForward.drawable as Animatable?)?.start(); + } + } + private fun stopFastForward() { + _jobExitFastForward?.cancel(); + _jobExitFastForward = null; + stopAutoFastForward(); + + _animatorSetFastForward?.cancel(); + + val animations = arrayListOf(); + val layout = if (_rewinding) { _layoutRewind } else { _layoutFastForward }; + animations.add(ObjectAnimator.ofFloat(layout, "alpha", 1.0f, 0.0f).setDuration( + ANIMATION_DURATION_FAST_FORWARD + )); + + if (_controlsVisible) { + _layoutControls?.let { animations.add(ObjectAnimator.ofFloat(it, "alpha", 0.0f, 1.0f).setDuration( + ANIMATION_DURATION_FAST_FORWARD + )); }; + } else { + _background?.let { animations.add(ObjectAnimator.ofFloat(it, "alpha", 1.0f, 0.0f).setDuration( + ANIMATION_DURATION_FAST_FORWARD + )); }; + } + + val animatorSet = AnimatorSet(); + animatorSet.doOnStart { + if (_controlsVisible) { + _layoutControls?.visibility = View.VISIBLE; + } + }; + animatorSet.doOnEnd { + layout.visibility = View.GONE; + _animatorSetFastForward = null; + (_imageRewind.drawable as Animatable?)?.stop(); + (_imageFastForward.drawable as Animatable?)?.stop(); + }; + + animatorSet.playTogether(animations); + animatorSet.start(); + _animatorSetFastForward = animatorSet; + + _skipping = false; + } + private fun fastForwardTick() { + _fastForwardCounter++; + + val seekOffset: Long = 10000; + if (_rewinding) { + _textRewind.text = "${_fastForwardCounter * 10} seconds"; + onSeek.emit(-seekOffset); + } else { + _textFastForward.text = "${_fastForwardCounter * 10} seconds"; + onSeek.emit(seekOffset); + } + } + private fun startAutoFastForward() { + _jobAutoFastForward?.cancel(); + _jobAutoFastForward = _scope.launch(Dispatchers.Main) { + try { + while (isActive) { + ensureActive(); + delay(300); + ensureActive(); + + fastForwardTick(); + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to execute fast forward tick.", e); + } + }; + } + private fun startExitFastForward() { + _jobExitFastForward?.cancel(); + _jobExitFastForward = _scope.launch(Dispatchers.Main) { + try { + delay(600); + stopFastForward(); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to stop fast forward.", e) + } + }; + } + private fun stopAutoFastForward() { + _jobAutoFastForward?.cancel(); + _jobAutoFastForward = null; + } + + private fun startAdjustingSound() { + _adjustingSound = true; + _progressSound.progress = _soundFactor; + + _layoutControlsSound.visibility = View.VISIBLE; + _animatorSound?.cancel(); + _animatorSound = ObjectAnimator.ofFloat(_layoutControlsSound, "alpha", 0.0f, 1.0f); + _animatorSound?.duration = ANIMATION_DURATION_GESTURE_CONTROLS; + _animatorSound?.start(); + } + + private fun stopAdjustingSound() { + _adjustingSound = false; + + _animatorSound?.cancel(); + _animatorSound = ObjectAnimator.ofFloat(_layoutControlsSound, "alpha", 1.0f, 0.0f); + _animatorSound?.duration = ANIMATION_DURATION_GESTURE_CONTROLS; + _animatorSound?.doOnEnd { _layoutControlsSound.visibility = View.GONE; }; + _animatorSound?.start(); + } + + private fun startAdjustingFullscreen() { + _adjustingFullscreen = true; + _fullScreenFactor = 0f; + _layoutControlsFullscreen.transitionAlpha = 0f; + _layoutControlsFullscreen.visibility = View.VISIBLE; + } + + private fun stopAdjustingFullscreen() { + _adjustingFullscreen = false; + _layoutControlsFullscreen.visibility = View.GONE; + } + + private fun startAdjustingBrightness() { + _adjustingBrightness = true; + _progressBrightness.progress = _brightnessFactor; + + _layoutControlsBrightness.visibility = View.VISIBLE; + _animatorBrightness?.cancel(); + _animatorBrightness = ObjectAnimator.ofFloat(_layoutControlsBrightness, "alpha", 0.0f, 1.0f); + _animatorBrightness?.duration = ANIMATION_DURATION_GESTURE_CONTROLS; + _animatorBrightness?.start(); + } + + private fun stopAdjustingBrightness() { + _adjustingBrightness = false; + + _animatorBrightness?.cancel(); + _animatorBrightness = ObjectAnimator.ofFloat(_layoutControlsBrightness, "alpha", 1.0f, 0.0f); + _animatorBrightness?.duration = ANIMATION_DURATION_GESTURE_CONTROLS; + _animatorBrightness?.doOnEnd { _layoutControlsBrightness.visibility = View.GONE; }; + _animatorBrightness?.start(); + } + + fun setFullscreen(isFullScreen: Boolean) { + if (isFullScreen) { + onBrightnessAdjusted.emit(_brightnessFactor); + onSoundAdjusted.emit(_soundFactor); + } else { + onBrightnessAdjusted.emit(1.0f); + //onSoundAdjusted.emit(1.0f); + stopAdjustingBrightness(); + stopAdjustingSound(); + stopAdjustingFullscreen(); + } + + _isFullScreen = isFullScreen; + } + + fun setSoundFactor(soundFactor: Float) { + _soundFactor = soundFactor; + onSoundAdjusted.emit(_soundFactor); + } + + companion object { + const val ANIMATION_DURATION_GESTURE_CONTROLS: Long = 200; + const val ANIMATION_DURATION_CONTROLS: Long = 400; + const val ANIMATION_DURATION_FAST_FORWARD: Long = 400; + const val EXIT_DURATION_FAST_FORWARD: Long = 600; + const val TAG = "GestureControlView"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt b/app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt new file mode 100644 index 00000000..65ef8478 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt @@ -0,0 +1,85 @@ +package com.futo.platformplayer.views.behavior + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.text.Layout +import android.text.Spannable +import android.text.style.URLSpan +import android.util.AttributeSet +import android.view.MotionEvent +import com.futo.platformplayer.activities.MainActivity +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.others.PlatformLinkMovementMethod +import com.futo.platformplayer.receivers.MediaControlReceiver +import com.futo.platformplayer.timestampRegex + +class NonScrollingTextView : androidx.appcompat.widget.AppCompatTextView { + constructor(context: Context) : super(context) {} + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {} + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {} + + override fun scrollTo(x: Int, y: Int) { + //do nothing + } + + override fun onTouchEvent(event: MotionEvent?): Boolean { + val action = event?.action + Logger.i(TAG, "onTouchEvent (action = $action)"); + + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { + val x = event.x.toInt() + val y = event.y.toInt() + + val layout: Layout? = this.layout + if (layout != null) { + val line = layout.getLineForVertical(y) + val offset = layout.getOffsetForHorizontal(line, x.toFloat()) + + val text = this.text + if (text is Spannable) { + val links = text.getSpans(offset, offset, URLSpan::class.java) + if (links.isNotEmpty()) { + for (link in links) { + Logger.i(PlatformLinkMovementMethod.TAG) { "Link clicked '${link.url}'." }; + + val c = context; + if (c is MainActivity) { + if (c.handleUrl(link.url)) { + continue; + } + + if (timestampRegex.matches(link.url)) { + val tokens = link.url.split(':'); + + var time_s = -1L; + if (tokens.size == 2) { + time_s = tokens[0].toLong() * 60 + tokens[1].toLong(); + } else if (tokens.size == 3) { + time_s = tokens[0].toLong() * 60 * 60 + tokens[1].toLong() * 60 + tokens[2].toLong(); + } + + if (time_s != -1L) { + MediaControlReceiver.onSeekToReceived.emit(time_s * 1000); + continue; + } + } + + c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))); + } + } + + return true + } + } + } + } + + super.onTouchEvent(event) + return false + } + + companion object { + private const val TAG = "NonScrollingTextView" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/behavior/TouchInterceptFrameLayout.kt b/app/src/main/java/com/futo/platformplayer/views/behavior/TouchInterceptFrameLayout.kt new file mode 100644 index 00000000..563024a6 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/behavior/TouchInterceptFrameLayout.kt @@ -0,0 +1,55 @@ +package com.futo.platformplayer.views.behavior + +import android.content.Context +import android.graphics.Color +import android.util.AttributeSet +import android.view.MotionEvent +import android.widget.FrameLayout +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event0 + +class TouchInterceptFrameLayout : FrameLayout { + var shouldInterceptTouches: Boolean = false; + val onClick = Event0(); + private var _wasDown = false; + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + val attrArr = context.obtainStyledAttributes(attrs, R.styleable.TouchInterceptFrameLayout, 0, 0); + shouldInterceptTouches = attrArr.getBoolean(R.styleable.TouchInterceptFrameLayout_shouldInterceptTouches, false); + } + + override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { + if (!shouldInterceptTouches) { + return super.onInterceptTouchEvent(ev); + } + + if (!_wasDown && ev?.action == MotionEvent.ACTION_DOWN) { + return true; + } else if (_wasDown && ev?.action == MotionEvent.ACTION_UP) { + return true; + } + + return super.onInterceptTouchEvent(ev); + } + + override fun onTouchEvent(ev: MotionEvent?): Boolean { + if (!shouldInterceptTouches) { + return super.onTouchEvent(ev); + } + + if (!_wasDown && ev?.action == MotionEvent.ACTION_DOWN) { + _wasDown = true; + return true; + } else if (_wasDown && ev?.action == MotionEvent.ACTION_UP) { + _wasDown = false; + onClick.emit(); + return true; + } + + return super.onTouchEvent(ev); + } + + companion object { + val TAG = "TouchInterceptFrameLayout"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/buttons/BigButton.kt b/app/src/main/java/com/futo/platformplayer/views/buttons/BigButton.kt new file mode 100644 index 00000000..6dd179ae --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/buttons/BigButton.kt @@ -0,0 +1,136 @@ +package com.futo.platformplayer.views.buttons + +import android.content.Context +import android.graphics.Bitmap +import android.util.AttributeSet +import android.util.TypedValue +import android.view.View +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event0 +import com.google.android.material.imageview.ShapeableImageView +import com.google.android.material.shape.ShapeAppearanceModel + +class BigButton : LinearLayout { + private val _root: LinearLayout; + private val _icon: ShapeableImageView; + private val _textPrimary: TextView; + private val _textSecondary: TextView; + + val onClick = Event0(); + + constructor(context : Context, text: String, subText: String, icon: Int, action: ()->Unit) : super(context) { + inflate(context, R.layout.big_button, this); + _icon = findViewById(R.id.button_icon); + _textPrimary = findViewById(R.id.button_text); + _textSecondary = findViewById(R.id.button_sub_text); + _root = findViewById(R.id.root); + + _textPrimary.text = text; + _textSecondary.text = subText; + _icon.setImageResource(icon); + + _root.setBackgroundResource(R.drawable.background_big_button); + + _root.apply { + isClickable = true; + setOnClickListener { + action(); + onClick.emit(); + }; + } + } + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + inflate(context, R.layout.big_button, this); + _icon = findViewById(R.id.button_icon); + _textPrimary = findViewById(R.id.button_text); + _textSecondary = findViewById(R.id.button_sub_text); + _root = findViewById(R.id.root); + _root.apply { + isClickable = true; + setOnClickListener { + onClick.emit(); + }; + } + + val attrArr = context.obtainStyledAttributes(attrs, R.styleable.BigButton, 0, 0); + val attrIconRef = attrArr.getResourceId(R.styleable.BigButton_buttonIcon, -1); + withIcon(attrIconRef); + + val attrBackgroundRef = attrArr.getResourceId(R.styleable.BigButton_buttonBackground, -1); + withBackground(attrBackgroundRef); + + val attrText = attrArr.getText(R.styleable.BigButton_buttonText) ?: ""; + _textPrimary.text = attrText; + + val attrTextSecondary = attrArr.getText(R.styleable.BigButton_buttonSubText) ?: ""; + _textSecondary.text = attrTextSecondary; + } + + fun withPrimaryText(text: String): BigButton { + _textPrimary.text = text; + return this; + } + + fun withSecondaryText(text: String): BigButton { + _textSecondary.text = text; + return this; + } + + fun withIcon(resourceId: Int, rounded: Boolean = false): BigButton { + if (resourceId != -1) { + _icon.visibility = View.VISIBLE; + _icon.setImageResource(resourceId); + } else + _icon.visibility = View.GONE; + + if (rounded) { + val shapeAppearanceModel = ShapeAppearanceModel().toBuilder() + .setAllCornerSizes(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16.0f, context.resources.displayMetrics)) + .build(); + + _icon.scaleType = ImageView.ScaleType.FIT_CENTER; + _icon.shapeAppearanceModel = shapeAppearanceModel; + } else { + _icon.scaleType = ImageView.ScaleType.CENTER_CROP; + _icon.shapeAppearanceModel = ShapeAppearanceModel(); + } + + return this; + } + + + fun withIcon(bitmap: Bitmap, rounded: Boolean = false): BigButton { + if (bitmap != null) { + _icon.visibility = View.VISIBLE; + _icon.setImageBitmap(bitmap); + } else + _icon.visibility = View.GONE; + + if (rounded) { + val shapeAppearanceModel = ShapeAppearanceModel().toBuilder() + .setAllCornerSizes(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16.0f, context.resources.displayMetrics)) + .build(); + + _icon.scaleType = ImageView.ScaleType.FIT_CENTER; + _icon.shapeAppearanceModel = shapeAppearanceModel; + } else { + _icon.scaleType = ImageView.ScaleType.CENTER_CROP; + _icon.shapeAppearanceModel = ShapeAppearanceModel(); + } + + return this; + } + + fun withBackground(resourceId: Int): BigButton { + if (resourceId != -1) { + _root.visibility = View.VISIBLE; + _root.setBackgroundResource(resourceId); + } else + _root.setBackgroundResource(R.drawable.background_big_button); + + return this; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/buttons/BigButtonGroup.kt b/app/src/main/java/com/futo/platformplayer/views/buttons/BigButtonGroup.kt new file mode 100644 index 00000000..66d3cd67 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/buttons/BigButtonGroup.kt @@ -0,0 +1,35 @@ +package com.futo.platformplayer.views.buttons + +import android.content.Context +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.R + +class BigButtonGroup : LinearLayout { + private val _header: TextView; + private val _buttons: LinearLayout; + + constructor(context: Context) : super(context) { + inflate(context, R.layout.big_button_group, this); + _header = findViewById(R.id.header_title); + _buttons = findViewById(R.id.buttons); + } + constructor(context: Context, header: String, vararg buttons: BigButton) : super(context) { + inflate(context, R.layout.big_button_group, this); + _header = findViewById(R.id.header_title); + _buttons = findViewById(R.id.buttons); + + _header.text = header; + for(button in buttons) + _buttons.addView(button); + } + + fun setText(text: String) { + _header.text = text; + } + fun setButtons(vararg buttons: BigButton) { + _buttons.removeAllViews(); + for(button in buttons) + _buttons.addView(button); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/buttons/DescButton.kt b/app/src/main/java/com/futo/platformplayer/views/buttons/DescButton.kt new file mode 100644 index 00000000..f5e13eae --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/buttons/DescButton.kt @@ -0,0 +1,48 @@ +package com.futo.platformplayer.views.buttons + +import android.content.Context +import android.graphics.Color +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event1 + +class DescButton : LinearLayout { + + val imageIcon: ImageView; + val textTitle: TextView; + val textDescription: TextView; + + var onClick = Event0(); + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + inflate(context, R.layout.view_desc_button, this); + + imageIcon = findViewById(R.id.image_icon) + textTitle = findViewById(R.id.text_title) + textDescription = findViewById(R.id.text_description) + + val attrArr = context.obtainStyledAttributes(attrs, R.styleable.DescButton, 0, 0); + imageIcon.setImageResource(attrArr.getResourceId(R.styleable.DescButton_desc_icon, 0)) + textTitle.text = attrArr.getText(R.styleable.DescButton_desc_title) ?: ""; + textDescription.text = attrArr.getText(R.styleable.DescButton_desc_description) ?: ""; + + this.setOnClickListener { onClick.emit() } + } + constructor(context: Context, icon: Int, title: String, description: String) : super(context) { + imageIcon = findViewById(R.id.image_icon) + textTitle = findViewById(R.id.text_title) + textDescription = findViewById(R.id.text_description) + + imageIcon.setImageResource(icon); + textTitle.text = title ?: ""; + textDescription.text = description ?: ""; + + this.setOnClickListener { onClick.emit() } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/casting/CastButton.kt b/app/src/main/java/com/futo/platformplayer/views/casting/CastButton.kt new file mode 100644 index 00000000..f187230c --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/casting/CastButton.kt @@ -0,0 +1,61 @@ +package com.futo.platformplayer.views.casting + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageButton +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.content.ContextCompat +import com.futo.platformplayer.R +import com.futo.platformplayer.Settings +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.casting.CastConnectionState +import com.futo.platformplayer.casting.StateCasting +import com.futo.platformplayer.constructs.Event1 + +class CastButton : androidx.appcompat.widget.AppCompatImageButton { + var onClick = Event1>(); + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + setOnClickListener { UIDialogs.showCastingDialog(context); }; + + if (!isInEditMode) { + if (!Settings.instance.casting.enabled) { + visibility = View.GONE; + } + + StateCasting.instance.onActiveDeviceConnectionStateChanged.subscribe(this) { _, _ -> + updateCastState(); + }; + + updateCastState(); + } + } + + private fun updateCastState() { + val c = context ?: return; + val d = StateCasting.instance.activeDevice; + + val activeColor = ContextCompat.getColor(c, R.color.colorPrimary); + val connectingColor = ContextCompat.getColor(c, R.color.gray_c3); + val inactiveColor = ContextCompat.getColor(c, R.color.white); + + if (d != null) { + when (d.connectionState) { + CastConnectionState.CONNECTED -> setColorFilter(activeColor) + CastConnectionState.CONNECTING -> setColorFilter(connectingColor) + CastConnectionState.DISCONNECTED -> setColorFilter(activeColor) + } + } else { + setColorFilter(inactiveColor); + } + } + + fun cleanup() { + setOnClickListener(null); + StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt b/app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt new file mode 100644 index 00000000..0c0be08b --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt @@ -0,0 +1,192 @@ +package com.futo.platformplayer.views.casting + +import android.content.Context +import android.media.session.PlaybackState +import android.support.v4.media.session.PlaybackStateCompat +import android.util.AttributeSet +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.View +import android.widget.* +import androidx.constraintlayout.widget.ConstraintLayout +import com.bumptech.glide.Glide +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails +import com.futo.platformplayer.casting.AirPlayCastingDevice +import com.futo.platformplayer.casting.StateCasting +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.states.StatePlayer +import com.futo.platformplayer.views.behavior.GestureControlView +import com.google.android.exoplayer2.ui.DefaultTimeBar +import com.google.android.exoplayer2.ui.TimeBar +import com.google.android.exoplayer2.ui.TimeBar.OnScrubListener +import kotlinx.coroutines.* + +class CastView : ConstraintLayout { + private val _thumbnail: ImageView; + private val _buttonMinimize: ImageButton; + private val _buttonSettings: ImageButton; + private val _buttonPlay: ImageButton; + private val _buttonPause: ImageButton; + private val _buttonCast: CastButton; + private val _textPosition: TextView; + private val _textDuration: TextView; + private val _textDivider: TextView; + private val _timeBar: DefaultTimeBar; + private val _background: FrameLayout; + private val _gestureControlView: GestureControlView; + private var _scope: CoroutineScope = CoroutineScope(Dispatchers.Main); + private var _updateTimeJob: Job? = null; + private var _inPictureInPicture: Boolean = false; + private var _originalBottomMargin: Int = 0; + + val onMinimizeClick = Event0(); + val onSettingsClick = Event0(); + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + LayoutInflater.from(context).inflate(R.layout.view_cast, this, true); + + _thumbnail = findViewById(R.id.image_thumbnail); + _buttonMinimize = findViewById(R.id.button_minimize); + _buttonSettings = findViewById(R.id.button_settings); + _buttonPlay = findViewById(R.id.button_play); + _buttonPause = findViewById(R.id.button_pause); + _buttonCast = findViewById(R.id.button_cast); + _textPosition = findViewById(R.id.text_position); + _textDivider = findViewById(R.id.text_divider); + _textDuration = findViewById(R.id.text_duration); + _timeBar = findViewById(R.id.time_progress); + _background = findViewById(R.id.layout_background); + _gestureControlView = findViewById(R.id.gesture_control); + _gestureControlView.setupTouchArea(_background); + _gestureControlView.onSeek.subscribe { + val d = StateCasting.instance.activeDevice ?: return@subscribe; + StateCasting.instance.videoSeekTo(d.expectedCurrentTime + it / 1000); + }; + + _timeBar.addListener(object : OnScrubListener { + override fun onScrubStart(timeBar: TimeBar, position: Long) { + StateCasting.instance.videoSeekTo(position.toDouble()); + } + + override fun onScrubMove(timeBar: TimeBar, position: Long) { + StateCasting.instance.videoSeekTo(position.toDouble()); + } + + override fun onScrubStop(timeBar: TimeBar, position: Long, canceled: Boolean) { + StateCasting.instance.videoSeekTo(position.toDouble()); + } + }); + + _buttonMinimize.setOnClickListener { onMinimizeClick.emit(); }; + _buttonSettings.setOnClickListener { onSettingsClick.emit(); }; + _buttonPlay.setOnClickListener { StateCasting.instance.resumeVideo(); }; + _buttonPause.setOnClickListener { StateCasting.instance.pauseVideo(); }; + + if (!isInEditMode) { + setIsPlaying(false); + } + } + + fun stopTimeJob() { + _updateTimeJob?.cancel(); + _updateTimeJob = null; + } + + fun setIsPlaying(isPlaying: Boolean) { + _updateTimeJob?.cancel(); + + if(isPlaying) { + val d = StateCasting.instance.activeDevice; + if (d is AirPlayCastingDevice) { + _updateTimeJob = _scope.launch { + while (true) { + val device = StateCasting.instance.activeDevice; + if (device == null || !device.isPlaying) { + break; + } + + delay(1000); + setTime((device.expectedCurrentTime * 1000.0).toLong()); + } + } + } + + if (!_inPictureInPicture) { + _buttonPause.visibility = View.VISIBLE; + _buttonPlay.visibility = View.GONE; + } + } + else if (!_inPictureInPicture) { + _buttonPause.visibility = View.GONE; + _buttonPlay.visibility = View.VISIBLE; + } + + val position = StateCasting.instance.activeDevice?.expectedCurrentTime?.times(1000.0)?.toLong(); + + if(StatePlayer.instance.hasMediaSession()) { + StatePlayer.instance.updateMediaSession(null); + StatePlayer.instance.updateMediaSessionPlaybackState(getPlaybackStateCompat(), (position ?: 0)); + } + } + + fun setButtonAlpha(alpha: Float) { + _background.alpha = alpha; + _textPosition.alpha = alpha; + _textDivider.alpha = alpha; + _textDuration.alpha = alpha; + _buttonMinimize.alpha = alpha; + _buttonSettings.alpha = alpha; + _buttonPause.alpha = alpha; + _buttonPlay.alpha = alpha; + _buttonCast.alpha = alpha; + _timeBar.alpha = alpha; + } + + fun setProgressBarOverlayed(isOverlayed: Boolean) { + if(isOverlayed) { + _thumbnail.layoutParams = _thumbnail.layoutParams.apply { + (this as MarginLayoutParams).bottomMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0f, resources.displayMetrics).toInt(); + }; + } + else { + _thumbnail.layoutParams = _thumbnail.layoutParams.apply { + (this as MarginLayoutParams).bottomMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7f, resources.displayMetrics).toInt(); + }; + } + } + + fun setVideoDetails(video: IPlatformVideoDetails, position: Long) { + Glide.with(_thumbnail) + .load(video.thumbnails.getHQThumbnail()) + .placeholder(R.drawable.placeholder_video_thumbnail) + .into(_thumbnail); + _textPosition.text = position.toHumanTime(false); + _textDuration.text = video.duration.toHumanTime(false); + _timeBar.setPosition(position); + _timeBar.setDuration(video.duration); + } + + fun setTime(ms: Long) { + _textPosition.text = ms.toHumanTime(true); + _timeBar.setPosition(ms / 1000); + StatePlayer.instance.updateMediaSessionPlaybackState(getPlaybackStateCompat(), ms); + } + + fun cleanup() { + _buttonCast.cleanup(); + _gestureControlView.cleanup(); + _updateTimeJob?.cancel(); + _updateTimeJob = null; + _scope.cancel(); + } + + private fun getPlaybackStateCompat(): Int { + val d = StateCasting.instance.activeDevice ?: return PlaybackState.STATE_NONE; + + return when(d.isPlaying) { + true -> PlaybackStateCompat.STATE_PLAYING; + else -> PlaybackStateCompat.STATE_PAUSED; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/comments/AddCommentView.kt b/app/src/main/java/com/futo/platformplayer/views/comments/AddCommentView.kt new file mode 100644 index 00000000..a364a47e --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/comments/AddCommentView.kt @@ -0,0 +1,56 @@ +package com.futo.platformplayer.views.comments + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.* +import com.futo.platformplayer.R +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StatePolycentric +import userpackage.Protocol + +class AddCommentView : LinearLayout { + private val _textComment: TextView; + + private var _contextUrl: String? = null + private var _ref: Protocol.Reference? = null + private var _lastClickTime = 0L + + val onCommentAdded = Event1(); + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + LayoutInflater.from(context).inflate(R.layout.view_add_comment, this, true); + + _textComment = findViewById(R.id.edit_comment); + _textComment.setOnClickListener { + val cu = _contextUrl ?: return@setOnClickListener + val ref = _ref ?: return@setOnClickListener + + val now = System.currentTimeMillis() + if (now - _lastClickTime > 3000) { + StatePolycentric.instance.requireLogin(context, "Please login to post a comment") { + try { + UIDialogs.showCommentDialog(context, cu, ref) { onCommentAdded.emit(it) }; + } catch (e: Throwable) { + Logger.w(TAG, "Failed to post comment", e); + UIDialogs.toast(context, "Failed to post comment: " + e.message); + } + }; + + _lastClickTime = now + } + } + } + + fun setContext(contextUrl: String?, ref: Protocol.Reference?) { + _contextUrl = contextUrl; + _ref = ref; + } + + companion object { + const val TAG = "AddCommentView" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/containers/DoubleTapLayout.kt b/app/src/main/java/com/futo/platformplayer/views/containers/DoubleTapLayout.kt new file mode 100644 index 00000000..119663cc --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/containers/DoubleTapLayout.kt @@ -0,0 +1,41 @@ +package com.futo.platformplayer.views.containers + +import android.content.Context +import android.util.AttributeSet +import android.view.GestureDetector +import android.view.MotionEvent +import android.widget.LinearLayout +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet.Constraint +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event1 + +class DoubleTapLayout : ConstraintLayout { + private var _detector : GestureDetector? = null; + + val onDoubleTap = Event1(); + + constructor(context: Context) : super(context) { + init(); + } + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(); + } + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init(); + } + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { + init(); + } + + fun init(){ + if(!isInEditMode) { + _detector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() { + override fun onDoubleTap(p0: MotionEvent): Boolean { + onDoubleTap.emit(p0); + return true; + } + }); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/containers/SingleViewTouchableMotionLayout.kt b/app/src/main/java/com/futo/platformplayer/views/containers/SingleViewTouchableMotionLayout.kt new file mode 100644 index 00000000..ffc507d3 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/containers/SingleViewTouchableMotionLayout.kt @@ -0,0 +1,94 @@ +package com.futo.platformplayer.views.containers + +import android.content.Context +import android.graphics.Rect +import android.util.AttributeSet +import android.view.GestureDetector +import android.view.MotionEvent +import android.view.View +import androidx.constraintlayout.motion.widget.MotionLayout +import com.futo.platformplayer.R + +class SingleViewTouchableMotionLayout(context: Context, attributeSet: AttributeSet? = null) : MotionLayout(context, attributeSet) { + + private val viewToDetectTouch by lazy { + findViewById(R.id.touchContainer) //TODO move to Attributes + } + private val viewRect = Rect() + private var touchStarted = false + private val transitionListenerList = mutableListOf() + + var allowMotion : Boolean = true; + + init { + addTransitionListener(object : TransitionListener { + override fun onTransitionChange(p0: MotionLayout?, p1: Int, p2: Int, p3: Float) { + } + + override fun onTransitionCompleted(p0: MotionLayout?, p1: Int) { + touchStarted = false + } + + override fun onTransitionStarted(p0: MotionLayout?, p1: Int, p2: Int) { + } + + override fun onTransitionTrigger(p0: MotionLayout?, p1: Int, p2: Boolean, p3: Float) { + } + }) + + super.setTransitionListener(object : TransitionListener { + override fun onTransitionChange(p0: MotionLayout?, p1: Int, p2: Int, p3: Float) { + transitionListenerList.filterNotNull() + .forEach { it.onTransitionChange(p0, p1, p2, p3) } + } + + override fun onTransitionCompleted(p0: MotionLayout?, p1: Int) { + transitionListenerList.filterNotNull() + .forEach { it.onTransitionCompleted(p0, p1) } + } + + override fun onTransitionStarted(p0: MotionLayout?, p1: Int, p2: Int) { + } + + override fun onTransitionTrigger(p0: MotionLayout?, p1: Int, p2: Boolean, p3: Float) { + } + }) + + //isInteractionEnabled = false; + } + + override fun setTransitionListener(listener: TransitionListener?) { + addTransitionListener(listener) + } + + override fun addTransitionListener(listener: TransitionListener?) { + transitionListenerList += listener + } + + //This always triggers, workaround calling super.onTouchEvent + //Blocks click events underneath + override fun onInterceptTouchEvent(event: MotionEvent?): Boolean { + if(!allowMotion) + return false; + if(event != null) { + when (event.actionMasked) { + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + touchStarted = false + return super.onTouchEvent(event) && false; + } + } + if (!touchStarted) { + viewToDetectTouch.getHitRect(viewRect); + val isInView = viewRect.contains(event.x.toInt(), event.y.toInt()); + touchStarted = isInView + } + } + return touchStarted && super.onTouchEvent(event) && false; + } + + + //Not triggered on its own due to child views, intercept is used instead. + override fun onTouchEvent(event: MotionEvent): Boolean { + return false; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/ButtonField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/ButtonField.kt new file mode 100644 index 00000000..7964b95e --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/fields/ButtonField.kt @@ -0,0 +1,69 @@ +package com.futo.platformplayer.views.fields + +import android.content.Context +import android.util.AttributeSet +import android.widget.* +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event2 +import java.lang.reflect.Field +import java.lang.reflect.Method + +class ButtonField : LinearLayout, IField { + override var descriptor: FormField? = null; + private var _obj : Any? = null; + private var _method : Method? = null; + + override var reference: Any? = null; + + override val obj : Any? get() { + if(this._obj == null) + throw java.lang.IllegalStateException("Can only be called if fromField is used"); + return _obj; + }; + override val field : Field? get() { + return null; + }; + + private val _title : TextView; + private val _subtitle : TextView; + + override val onChanged = Event2(); + + constructor(context : Context, attrs : AttributeSet? = null) : super(context, attrs){ + inflate(context, R.layout.field_button, this); + _title = findViewById(R.id.field_title); + _subtitle = findViewById(R.id.field_subtitle); + + setOnClickListener { + if(_method?.parameterCount == 1) + _method?.invoke(_obj, context); + else if(_method?.parameterCount == 2) + _method?.invoke(_obj, context, (if(context is AppCompatActivity) context.lifecycleScope else null)); + else + _method?.invoke(_obj); + } + } + + fun fromMethod(obj : Any, method: Method) : ButtonField { + this._method = method; + this._obj = obj; + + val attrField = method.getAnnotation(FormField::class.java); + if(attrField != null) { + _title.text = attrField.title; + _subtitle.text = attrField.subtitle; + descriptor = attrField; + } + else + _title.text = method.name; + + return this; + } + override fun fromField(obj : Any, field : Field, formField: FormField?) : ButtonField { + throw IllegalStateException("ButtonField should only be used for methods"); + } + override fun setField() { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/DropdownField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/DropdownField.kt new file mode 100644 index 00000000..6ae96479 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/fields/DropdownField.kt @@ -0,0 +1,139 @@ +package com.futo.platformplayer.views.fields + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.widget.* +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event2 +import java.lang.reflect.Field + +class DropdownField : TableRow, IField { + override var descriptor: FormField? = null; + private var _obj : Any? = null; + private var _field : Field? = null; + + override val obj : Any? get() { + if(this._obj == null) + throw java.lang.IllegalStateException("Can only be called if fromField is used"); + return _obj; + }; + override val field : Field? get() { + if(this._field == null) + throw java.lang.IllegalStateException("Can only be called if fromField is used"); + return _field; + }; + + private var _options : Array = arrayOf("Unset"); + private var _selected : Int = 0; + + private var _isInitFire : Boolean = false; + + private val _title : TextView; + private val _description : TextView; + private val _spinner : Spinner; + + override var reference: Any? = null; + + override val onChanged = Event2(); + + constructor(context : Context, attrs : AttributeSet? = null) : super(context, attrs){ + inflate(context, R.layout.field_dropdown, this); + _spinner = findViewById(R.id.field_spinner); + _title = findViewById(R.id.field_title); + _description = findViewById(R.id.field_description); + + _isInitFire = true; + _spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>, view: View?, pos: Int, id: Long) { + if(_isInitFire) { + _isInitFire = false; + return; + } + _selected = pos; + onChanged.emit(this@DropdownField, pos); + } + override fun onNothingSelected(parent: AdapterView<*>?) = Unit + }; + } + + fun asBoolean(name: String, description: String?, obj: Boolean) : DropdownField { + _options = resources.getStringArray(R.array.enabled_disabled_array); + _spinner.adapter = ArrayAdapter(context, R.layout.spinner_item_simple, _options).also { + it.setDropDownViewResource(R.layout.spinner_dropdownitem_simple); + }; + _selected = if(obj) 1 else 0; + _spinner.isSelected = false; + _spinner.setSelection(_selected, true); + + _title.text = name; + if(!description.isNullOrBlank()) { + _description.text = description; + _description.visibility = View.VISIBLE; + } + else + _description.visibility = View.GONE; + + return this; + } + + override fun fromField(obj : Any, field : Field, formField: FormField?) : DropdownField { + this._field = field; + this._obj = obj; + + val attrField = formField ?: field.getAnnotation(FormField::class.java); + if(attrField != null) { + _title.text = attrField.title; + descriptor = attrField; + + if(attrField.subtitle.isNotBlank()) { + _description.text = attrField.subtitle; + _description.visibility = View.VISIBLE; + } + else + _description.visibility = View.GONE; + } + else { + _title.text = field.name; + _description.visibility = View.GONE; + } + + + _options = (field.getAnnotation(DropdownFieldOptions::class.java)?.options ?: + field.getAnnotation(DropdownFieldOptionsId::class.java)?.optionsId?.let { resources.getStringArray(it) } ?: + arrayOf("Unset")) + .toList().toTypedArray(); + + if(_options != null){ + _spinner.adapter = ArrayAdapter(context, R.layout.spinner_item_simple, _options).also { + it.setDropDownViewResource(R.layout.spinner_dropdownitem_simple); + }; + + if(field.type == Int::class.java) + _selected = field.get(obj) as Int; + else { + val valStr = field.get(obj)?.toString(); + _selected = if (_options.contains(valStr)) _options.indexOf(valStr) else 0; + } + _spinner.isSelected = false; + _spinner.setSelection(_selected, true); + } + return this; + } + override fun setField() { + if(this._field == null) + throw java.lang.IllegalStateException("Can only setField if fromField is used"); + + if(_field?.type == Int::class.java) + _field!!.set(_obj, _selected); + else + _field!!.set(_obj, _options[_selected]); + } +} + +@Target(AnnotationTarget.FIELD) +@Retention(AnnotationRetention.RUNTIME) +annotation class DropdownFieldOptions(vararg val options : String); +@Target(AnnotationTarget.FIELD) +@Retention(AnnotationRetention.RUNTIME) +annotation class DropdownFieldOptionsId(val optionsId : Int); \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/Field.kt b/app/src/main/java/com/futo/platformplayer/views/fields/Field.kt new file mode 100644 index 00000000..e072f455 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/fields/Field.kt @@ -0,0 +1,23 @@ +package com.futo.platformplayer.views.fields + +import com.futo.platformplayer.constructs.Event2 +import java.lang.reflect.Field + + +@Target(AnnotationTarget.FIELD, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.RUNTIME) +annotation class FormField(val title : String, val type : String, val subtitle : String = "", val order : Int = 0, val id : String = "") + +interface IField { + var descriptor: FormField?; + val obj : Any?; + val field : Field?; + + val onChanged : Event2; + + var reference: Any?; + + + fun fromField(obj : Any, field : Field, formField: FormField? = null) : IField; + fun setField(); +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/FieldForm.kt b/app/src/main/java/com/futo/platformplayer/views/fields/FieldForm.kt new file mode 100644 index 00000000..4fb7b7ed --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/fields/FieldForm.kt @@ -0,0 +1,194 @@ +package com.futo.platformplayer.views.fields + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.widget.LinearLayout +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.logging.Logger +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import java.lang.reflect.Field +import java.lang.reflect.Method +import kotlin.reflect.full.declaredMemberProperties +import kotlin.reflect.full.findAnnotation +import kotlin.reflect.full.hasAnnotation +import kotlin.reflect.jvm.javaField +import kotlin.reflect.jvm.javaMethod +import kotlin.streams.asStream +import kotlin.streams.toList + +class FieldForm : LinearLayout { + + private val _root : LinearLayout; + + val onChanged = Event2(); + + private var _fields : List = arrayListOf(); + + constructor(context : Context, attrs : AttributeSet? = null) : super(context, attrs) { + inflate(context, R.layout.field_form, this); + _root = findViewById(R.id.field_form_root); + } + + fun fromObject(obj : Any) { + _root.removeAllViews(); + val newFields = getFieldsFromObject(context, obj); + for(field in newFields) { + if(field !is View) + throw java.lang.IllegalStateException("Only views can be IFields"); + + _root.addView(field as View); + field.onChanged.subscribe { a1, a2 -> + onChanged.emit(a1, a2); + }; + } + _fields = newFields; + } + fun fromPluginSettings(settings: List, values: HashMap, groupTitle: String? = null, groupDescription: String? = null) { + _root.removeAllViews(); + val newFields = getFieldsFromPluginSettings(context, settings, values); + if (newFields.isEmpty()) { + return; + } + + if(groupTitle == null) { + for(field in newFields) { + if(field !is View) + throw java.lang.IllegalStateException("Only views can be IFields"); + field.onChanged.subscribe { field, value -> + onChanged.emit(field, value); + } + _root.addView(field as View); + } + _fields = newFields; + } else { + for(field in newFields) { + field.onChanged.subscribe { field, value -> + onChanged.emit(field, value); + } + } + val group = GroupField(context, groupTitle, groupDescription) + .withFields(newFields); + _root.addView(group as View); + } + } + + fun setObjectValues(){ + val fields = _fields; + for (field in fields) + field.setField(); + } + + fun findField(id: String) : IField? { + for(field in _fields) { + if(field?.descriptor?.id == id) + return field; + else if(field is GroupField) + { + val subField = field.findField(id); + if(subField != null) + return subField; + } + } + return null; + } + + companion object + { + const val DROPDOWN = "dropdown"; + const val GROUP = "group"; + const val READONLYTEXT = "readonlytext"; + const val TOGGLE = "toggle"; + const val BUTTON = "button"; + + private val _json = Json {}; + + + fun getFieldsFromPluginSettings(context: Context, settings: List, values: HashMap): List { + val fields = mutableListOf() + + for(setting in settings) { + val field = when(setting.type.lowercase()) { + "boolean" -> { + val value = if(values.containsKey(setting.variableOrName)) values[setting.variableOrName] else setting.default; + val field = ToggleField(context).withValue(setting.name, + setting.description, + value == "true" || value == "1" || value == "True"); + field.onChanged.subscribe { field, value -> + values[setting.variableOrName] = _json.encodeToString (value == 1 || value == true); + } + field; + } + else -> null; + } + + if(field != null) + fields.add(field); + } + return fields; + } + + fun getFieldsFromObject(context : Context, obj : Any) : List { + val objFields = obj::class.declaredMemberProperties + .asSequence() + .asStream() + .filter { it.hasAnnotation() && it.javaField != null } + .map { Pair(it.javaField!!, it.findAnnotation()!!) } + .toList() + + val fields = mutableListOf(); + for(prop in objFields) { + prop.first.isAccessible = true; + + val field = when(prop.second.type) { + GROUP -> GroupField(context).fromField(obj, prop.first, prop.second); + DROPDOWN -> DropdownField(context).fromField(obj, prop.first, prop.second); + TOGGLE -> ToggleField(context).fromField(obj, prop.first, prop.second); + READONLYTEXT -> ReadOnlyTextField(context).fromField(obj, prop.first, prop.second); + else -> throw java.lang.IllegalStateException("Unknown field type ${prop.second.type} for ${prop.second.title}") + } + fields.add(field as IField); + } + + val objProps = obj::class.declaredMemberProperties + .asSequence() + .asStream() + .filter { it.hasAnnotation() && it.javaField == null && it.getter.javaMethod != null} + .map { Pair(it.getter.javaMethod!!, it.findAnnotation()!!) } + .toList(); + + for(prop in objProps) { + prop.first.isAccessible = true; + + val field = when(prop.second.type) { + READONLYTEXT -> ReadOnlyTextField(context).fromProp(obj, prop.first, prop.second); + else -> continue; + } + fields.add(field as IField); + } + + //TODO: replace java.declaredMethods with declaredMemberFunctions instead of filtering out get/set + val objMethods = obj::class.java.declaredMethods + .asSequence() + .asStream() + .filter { it.getAnnotation(FormField::class.java) != null && !it.name.startsWith("get") && !it.name.startsWith("set") } + .map { Pair(it, it.getAnnotation(FormField::class.java)) } + .toList(); + + for(meth in objMethods) { + meth.first.isAccessible = true; + + val field = when(meth.second.type) { + BUTTON -> ButtonField(context).fromMethod(obj, meth.first); + else -> throw java.lang.IllegalStateException("Unknown method type ${meth.second.type} for ${meth.second.title}") + } + fields.add(field as IField); + } + + return fields.sortedBy { it.descriptor?.order }.toList(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/GroupField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/GroupField.kt new file mode 100644 index 00000000..d26e09fe --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/fields/GroupField.kt @@ -0,0 +1,141 @@ +package com.futo.platformplayer.views.fields + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event2 +import java.lang.reflect.Field + +class GroupField : LinearLayout, IField { + override var descriptor : FormField? = null; + private var _obj : Any? = null; + private var _field : Field? = null; + + private var _fields : List = listOf(); + + override val obj : Any? get() { + if(this._obj == null) + throw java.lang.IllegalStateException("Can only be called if fromField is used"); + return _obj; + }; + override val field : Field? get() { + if(this._field == null) + throw java.lang.IllegalStateException("Can only be called if fromField is used"); + return _field; + }; + + override val onChanged = Event2(); + + private val _title : TextView; + private val _subtitle : TextView; + private val _container : LinearLayout; + + override var reference: Any? = null; + + constructor(context : Context, attrs : AttributeSet? = null) : super(context, attrs) { + inflate(context, R.layout.field_group, this); + _title = findViewById(R.id.field_group_title); + _subtitle = findViewById(R.id.field_group_subtitle); + _container = findViewById(R.id.field_group_container); + + _title.visibility = GONE; + } + + constructor(context: Context, title: String, description: String? = null) : super(context) { + inflate(context, R.layout.field_group, this); + _title = findViewById(R.id.field_group_title); + _subtitle = findViewById(R.id.field_group_subtitle); + _container = findViewById(R.id.field_group_container); + + _title.text = title; + _subtitle.text = description ?: ""; + + if(!(_title.text?.isEmpty() ?: true)) + _title.visibility = VISIBLE; + else + _title.visibility = GONE; + if(!(_subtitle.text?.isEmpty() ?: true)) + _subtitle.visibility = VISIBLE; + else + _subtitle.visibility = GONE; + } + + fun findField(id: String) : IField? { + for(field in _fields) { + if(field.descriptor?.id == id) + return field; + else if(field is GroupField) + { + val subField = field.findField(id); + if(subField != null) + return subField; + } + } + return null; + } + + fun withFields(fields: List): GroupField { + _container.removeAllViews(); + val newFields = mutableListOf() + for(field in fields) { + if(!(field is View)) + throw java.lang.IllegalStateException("Only views can be IFields"); + + field.onChanged.subscribe(onChanged::emit); + _container.addView(field as View); + newFields.add(field); + } + _fields = newFields; + + return this; + } + + override fun fromField(obj : Any, field : Field, formField: FormField?) : GroupField { + this._field = field; + this._obj = obj; + + val value = field.get(obj); + + val attrField = formField ?: field.getAnnotation(FormField::class.java); //TODO: Get this to work as default + if(attrField != null) { + _title.text = attrField.title; + _subtitle.text = attrField.subtitle; + descriptor = attrField; + } + else + _title.text = field.name; + + _container.removeAllViews(); + val newFields = mutableListOf() + for(field in FieldForm.getFieldsFromObject(context, value)) { + if(!(field is View)) + throw java.lang.IllegalStateException("Only views can be IFields"); + + field.onChanged.subscribe(onChanged::emit); + _container.addView(field as View); + newFields.add(field); + } + _fields = newFields; + + if(!(_title.text?.isEmpty() ?: true)) + _title.visibility = VISIBLE; + else + _title.visibility = GONE; + if(!(_subtitle.text?.isEmpty() ?: true)) + _subtitle.visibility = VISIBLE; + else + _subtitle.visibility = GONE; + return this; + } + override fun setField() { + if(this._field == null) + throw java.lang.IllegalStateException("Can only setField if fromField is used"); + + for(field in _fields){ + field.setField(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/ReadOnlyTextField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/ReadOnlyTextField.kt new file mode 100644 index 00000000..baeaa31a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/fields/ReadOnlyTextField.kt @@ -0,0 +1,77 @@ +package com.futo.platformplayer.views.fields + +import android.content.Context +import android.util.AttributeSet +import android.widget.* +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event2 +import java.lang.reflect.Field +import java.lang.reflect.Method + +class ReadOnlyTextField : TableRow, IField { + override var descriptor : FormField? = null; + private var _obj : Any? = null; + private var _field : Field? = null; + + override val obj : Any? get() { + if(this._obj == null) + throw java.lang.IllegalStateException("Can only be called if fromField is used"); + return _obj; + }; + override val field : Field? get() { + if(this._field == null) + throw java.lang.IllegalStateException("Can only be called if fromField is used"); + return _field; + }; + + private val _title : TextView; + private val _value : TextView; + + override val onChanged = Event2(); + + override var reference: Any? = null; + constructor(context : Context, attrs : AttributeSet? = null) : super(context, attrs){ + inflate(context, R.layout.field_readonly_text, this); + _title = findViewById(R.id.field_title); + _value = findViewById(R.id.field_value); + } + + override fun fromField(obj : Any, field : Field, formField: FormField?) : ReadOnlyTextField { + this._field = field; + this._obj = obj; + + val attrField = formField ?: field.getAnnotation(FormField::class.java); + if(attrField != null) { + _title.text = attrField.title; + descriptor = attrField; + } + else + _title.text = field.name; + + if(field.type == String::class.java) + _value.text = field.get(obj) as String; + else + _value.text = field.get(obj).toString(); + return this; + } + fun fromProp(obj : Any, field : Method, formField: FormField?) : ReadOnlyTextField { + this._field = null; + this._obj = obj; + + val attrField = formField ?: field.getAnnotation(FormField::class.java); + if(attrField != null) { + _title.text = attrField.title; + descriptor = attrField; + } + else + _title.text = field.name; + + if(field.returnType == String::class.java) + _value.text = field.invoke(obj) as String; + else + _value.text = field.invoke(obj)?.toString() ?: ""; + return this; + } + override fun setField() { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt new file mode 100644 index 00000000..0750c869 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt @@ -0,0 +1,103 @@ +package com.futo.platformplayer.views.fields + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.widget.* +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.views.others.Toggle +import java.lang.reflect.Field + +class ToggleField : TableRow, IField { + override var descriptor: FormField? = null; + private var _obj : Any? = null; + private var _field : Field? = null; + + override val obj : Any? get() { + if(this._obj == null) + throw java.lang.IllegalStateException("Can only be called if fromField is used"); + return _obj; + }; + override val field : Field? get() { + if(this._field == null) + throw java.lang.IllegalStateException("Can only be called if fromField is used"); + return _field; + }; + + private val _title : TextView; + private val _description : TextView; + private val _toggle : Toggle; + + override var reference: Any? = null; + + override val onChanged = Event2(); + + constructor(context : Context, attrs : AttributeSet? = null) : super(context, attrs){ + inflate(context, R.layout.field_toggle, this); + _toggle = findViewById(R.id.field_toggle); + _title = findViewById(R.id.field_title); + _description = findViewById(R.id.field_description); + + _toggle.onValueChanged.subscribe { + onChanged.emit(this, it); + }; + } + + fun withValue(title: String, description: String?, value: Boolean): ToggleField { + + _title.text = title; + _description.text = description; + if(!description.isNullOrEmpty()) + _description.visibility = View.VISIBLE; + else + _description.visibility = View.GONE; + + _toggle.setValue(value, true); + + return this; + } + + override fun fromField(obj : Any, field : Field, formField: FormField?) : ToggleField { + this._field = field; + this._obj = obj; + + val attrField = formField ?: field.getAnnotation(FormField::class.java); + if(attrField != null) { + _title.text = attrField.title; + descriptor = attrField; + } + else + _title.text = field.name; + + if(attrField?.subtitle?.isEmpty() != false) + _description.visibility = View.GONE; + else { + _description.text = attrField.subtitle; + _description.visibility = View.VISIBLE; + } + + val value = field.get(obj); + if(value is Boolean) + _toggle.setValue(value, true); + else if(value is Number) + _toggle.setValue((value as Number).toInt() > 0, true); + else if(value == null) + _toggle.setValue(false, true); + else + _toggle.setValue(false, true); + + return this; + } + override fun setField() { + if(this._field == null) + throw java.lang.IllegalStateException("Can only setField if fromField is used"); + + if(_field?.type == Int::class.java) + _field!!.set(_obj, if(_toggle.value) 1 else 0); + else if(_field?.type == Boolean::class.java) + _field!!.set(_obj, _toggle.value); + else + _field!!.set(_obj, _toggle.value); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/items/ActiveDownloadItem.kt b/app/src/main/java/com/futo/platformplayer/views/items/ActiveDownloadItem.kt new file mode 100644 index 00000000..f4e27d33 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/items/ActiveDownloadItem.kt @@ -0,0 +1,139 @@ +package com.futo.platformplayer.views.items + +import android.content.Context +import android.graphics.Color +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.bumptech.glide.Glide +import com.futo.platformplayer.* +import com.futo.platformplayer.downloads.VideoDownload +import com.futo.platformplayer.images.GlideHelper.Companion.crossfade +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateDownloads +import com.futo.platformplayer.views.others.ProgressBar +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class ActiveDownloadItem: LinearLayout { + private var _finalized: Boolean = false; + private val _download: VideoDownload; + + private val _videoName: TextView; + private val _videoImage: ImageView; + private val _videoSize: TextView; + private val _videoDuration: TextView; + private val _videoAuthor: TextView; + private val _videoInfo: TextView; + private val _videoBar: ProgressBar; + private val _videoSpeed: TextView; + private val _videoState: TextView; + + private val _videoCancel: TextView; + + private val _scope: CoroutineScope; + + constructor(context: Context, download: VideoDownload, lifetimeScope: CoroutineScope): super(context) { + inflate(context, R.layout.list_download, this) + _scope = lifetimeScope; + _download = download; + + _videoName = findViewById(R.id.downloaded_video_name); + _videoImage = findViewById(R.id.downloaded_video_image); + _videoSize = findViewById(R.id.downloaded_video_size); + _videoDuration = findViewById(R.id.downloaded_video_duration); + _videoAuthor = findViewById(R.id.downloaded_author); + _videoInfo = findViewById(R.id.downloaded_video_info); + _videoBar = findViewById(R.id.download_video_progress); + _videoState = findViewById(R.id.download_video_state); + _videoSpeed = findViewById(R.id.download_video_speed); + + _videoCancel = findViewById(R.id.download_cancel); + + _videoName.text = download.name; + _videoDuration.text = download.videoEither.duration.toHumanTime(false); + _videoAuthor.text = download.videoEither.author.name; + + _videoState.setOnClickListener { + UIDialogs.toast(context, _videoState.text.toString(), false); + } + + Glide.with(_videoImage) + .load(download.thumbnail) + .crossfade() + .into(_videoImage); + + updateDownloadUI(); + + _videoCancel.setOnClickListener { + StateDownloads.instance.removeDownload(_download); + }; + + _download.onProgressChanged.subscribe(this) { + _scope.launch(Dispatchers.Main) { + try { + updateDownloadUI() + } catch (e: Throwable) { + Logger.e(TAG, "Failed to update download UI.", e); + } + } + }; + _download.onStateChanged.subscribe(this) { + _scope.launch(Dispatchers.Main) { + try { + updateDownloadUI() + } catch (e: Throwable) { + Logger.e(TAG, "Failed to update download UI.", e); + } + } + } + } + + fun finalize() { + _finalized = true; + _download.onProgressChanged.remove(this); + _download.onStateChanged.remove(this); + } + + fun updateDownloadUI() { + _videoInfo.text = _download.getDownloadInfo(); + + val size = (_download.videoFileSize ?: 0) + (_download.audioFileSize ?: 0); + if(size > 0) + _videoSize.text = size.toHumanBytesSize(false); + else + _videoSize.text = "?"; + + _videoBar.progress = _download.progress.toFloat(); + _videoSpeed.text = "${_download.downloadSpeed.toHumanBytesSpeed()} ${(_download.progress * 100).toInt()}%"; + + _videoState.text = if(!Settings.instance.downloads.shouldDownload()) + "Waiting for unmetered" + (if(!_download.error.isNullOrEmpty()) "\n(Last error: " + _download.error + ")" else ""); + else if(_download.state == VideoDownload.State.QUEUED && !_download.error.isNullOrEmpty()) + _download.state.toString() + "\n(Last error: " + _download.error + ")"; + else + _download.state.toString(); + _videoState.setTextColor(Color.GRAY); + when(_download.state) { + VideoDownload.State.DOWNLOADING -> { + _videoBar.visibility = VISIBLE; + _videoSpeed.visibility = VISIBLE; + }; + VideoDownload.State.ERROR -> { + _videoState.setTextColor(Color.RED); + _videoState.text = _download.error ?: "Error"; + _videoBar.visibility = GONE; + _videoSpeed.visibility = GONE; + } + else -> { + _videoBar.visibility = GONE; + _videoSpeed.visibility = GONE; + } + } + } + + companion object { + private const val TAG = "ActiveDownloadItem" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/items/PlaylistDownloadItem.kt b/app/src/main/java/com/futo/platformplayer/views/items/PlaylistDownloadItem.kt new file mode 100644 index 00000000..22975583 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/items/PlaylistDownloadItem.kt @@ -0,0 +1,25 @@ +package com.futo.platformplayer.views.items + +import android.content.Context +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.images.GlideHelper.Companion.crossfade +import com.futo.platformplayer.models.PlaylistDownloaded + +class PlaylistDownloadItem(context: Context, val playlist: PlaylistDownloaded): LinearLayout(context) { + init { inflate(context, R.layout.list_downloaded_playlist, this) } + + var imageView: ImageView = findViewById(R.id.downloaded_playlist_image); + var imageText: TextView = findViewById(R.id.downloaded_playlist_name); + + init { + imageText.text = playlist.playlist.name; + Glide.with(imageView) + .load(playlist.playlist.videos.firstOrNull()?.thumbnails?.getHQThumbnail()) + .crossfade() + .into(imageView); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/lists/VideoListEditorView.kt b/app/src/main/java/com/futo/platformplayer/views/lists/VideoListEditorView.kt new file mode 100644 index 00000000..d412a40a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/lists/VideoListEditorView.kt @@ -0,0 +1,85 @@ +package com.futo.platformplayer.views.lists + +import android.content.Context +import android.util.AttributeSet +import android.widget.FrameLayout +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.views.adapters.ItemMoveCallback +import com.futo.platformplayer.views.adapters.VideoListEditorAdapter +import java.util.* + +class VideoListEditorView : FrameLayout { + private val _videos : ArrayList = ArrayList(); + + private var _adapterVideos: VideoListEditorAdapter? = null; + + val onVideoOrderChanged = Event1>() + val onVideoRemoved = Event1(); + val onVideoClicked = Event1(); + val isEmpty get() = _videos.isEmpty(); + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + val recyclerPlaylist = RecyclerView(context, attrs); + recyclerPlaylist.isSaveEnabled = false; + + recyclerPlaylist.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + addView(recyclerPlaylist); + + val callback = ItemMoveCallback(); + val touchHelper = ItemTouchHelper(callback); + val adapterVideos = VideoListEditorAdapter(touchHelper); + recyclerPlaylist.adapter = adapterVideos; + recyclerPlaylist.layoutManager = LinearLayoutManager(context); + touchHelper.attachToRecyclerView(recyclerPlaylist); + + callback.onRowMoved.subscribe { fromPosition, toPosition -> + synchronized(_videos) { + if (fromPosition < toPosition) { + for (i in fromPosition until toPosition) + Collections.swap(_videos, i, i + 1) + } + else { + for (i in fromPosition downTo toPosition + 1) + Collections.swap(_videos, i, i - 1) + } + onVideoOrderChanged.emit(_videos.toList()); + adapterVideos.notifyItemMoved(fromPosition, toPosition); + } + }; + + adapterVideos.onRemove.subscribe { v -> + synchronized(_videos) { + val index = _videos.indexOf(v); + if(index >= 0) { + _videos.removeAt(index); + onVideoRemoved.emit(v); + } + adapterVideos.notifyItemRemoved(index); + } + }; + adapterVideos.onClick.subscribe(onVideoClicked::emit); + + _adapterVideos = adapterVideos; + } + + fun setVideos(videos: List?, canEdit: Boolean) { + synchronized(_videos) { + _videos.clear(); + _videos.addAll(videos ?: listOf()); + _adapterVideos?.setVideos(_videos, canEdit); + } + } + + fun addVideos(videos: List) { + synchronized(_videos) { + val index = _videos.size; + _videos.addAll(videos); + _adapterVideos?.notifyItemRangeInserted(index, videos.size); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatDonationListItem.kt b/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatDonationListItem.kt new file mode 100644 index 00000000..05577fcb --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatDonationListItem.kt @@ -0,0 +1,117 @@ +package com.futo.platformplayer.views.livechat + +import android.graphics.Color +import android.graphics.drawable.LevelListDrawable +import android.text.Spannable +import android.text.style.ImageSpan +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.graphics.blue +import androidx.core.graphics.green +import androidx.core.graphics.red +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.models.live.ILiveEventChatMessage +import com.futo.platformplayer.api.media.models.live.LiveEventComment +import com.futo.platformplayer.api.media.models.live.LiveEventDonation +import com.futo.platformplayer.dp +import com.futo.platformplayer.isHexColor +import com.futo.platformplayer.views.adapters.AnyAdapter +import com.futo.platformplayer.views.overlays.LiveChatOverlay +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class LiveChatDonationListItem(viewGroup: ViewGroup) + : LiveChatListItem(LayoutInflater.from(viewGroup.context).inflate(R.layout.list_chat_donation, viewGroup, false)) { + private var _liveEvent: ILiveEventChatMessage? = null; + + private val _authorImage: ImageView = _view.findViewById(R.id.image_thumbnail); + private val _authorName: TextView = _view.findViewById(R.id.text_author); + private val _authorMessage: TextView = _view.findViewById(R.id.text_body); + + private val _amountContainer: LinearLayout = _view.findViewById(R.id.donation_amount_container); + private val _amount: TextView = _view.findViewById(R.id.donation_amount); + + override fun bind(chat: LiveChatOverlay.ChatMessage) { + val event = chat.event; + + _liveEvent = event; + if(event.thumbnail.isNullOrEmpty()) + _authorImage.visibility = View.GONE; + else { + Glide.with(_authorImage) + .load(event.thumbnail) + .into(_authorImage); + _authorImage.visibility = View.VISIBLE; + } + _authorName.text = event.name; + + if(event is LiveEventDonation) { + _amountContainer.visibility = View.VISIBLE; + _amount.text = event.amount.trim(); + + if(event.colorDonation != null && event.colorDonation.isHexColor()) { + val color = Color.parseColor(event.colorDonation); + _amountContainer.background.setTint(color); + + if((color.green > 140 || color.red > 140 || color.blue > 140) && (color.red + color.green + color.blue) > 400) + _amount.setTextColor(Color.BLACK); + else + _amount.setTextColor(Color.WHITE); + } + else { + _amountContainer.background.setTint(Color.parseColor("#2A2A2A")); + _amount.setTextColor(Color.WHITE); + } + } + else + _amountContainer.visibility = View.GONE; + + //Injects emotes + if(!chat.manager.let { liveChat -> + val emojiMatches = REGEX_EMOJIS.findAll(event.message).toList(); + val span = _spanFactory.newSpannable(event.message); + var injected = 0; + + for(emoji in emojiMatches + .filter { it.groupValues.size > 1 && liveChat.hasEmoji(it.groupValues[1]) } + .groupBy { it.groupValues[1] }) { + val emojiVal = emoji.key; + val drawable = LevelListDrawable(); + + for(match in emoji.value) + span.setSpan(ImageSpan(drawable), match.range.first, match.range.last + 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE); + + liveChat.getEmoji(emojiVal) { emojiDrawable -> + if(emojiDrawable != null) { + drawable.addLevel(1, 1, emojiDrawable); + val iconSize = 20.dp(_view.resources); + drawable.setBounds(0, 0, iconSize, iconSize); + drawable.setLevel(1); + if (_liveEvent == event) + chat.scope.launch(Dispatchers.Main) { + _authorMessage.setText(span, TextView.BufferType.SPANNABLE); + } + } + }; + injected++; + } + if(injected > 0) { + _authorMessage.setText(span, TextView.BufferType.SPANNABLE); + return@let true; + } else + return@let false; + }) + _authorMessage.text = event.message; + } + + + companion object { + val REGEX_EMOJIS = Regex("__(.*?)__"); + private val _spanFactory = Spannable.Factory.getInstance(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatDonationPill.kt b/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatDonationPill.kt new file mode 100644 index 00000000..02619424 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatDonationPill.kt @@ -0,0 +1,66 @@ +package com.futo.platformplayer.views.livechat + +import android.content.Context +import android.graphics.Color +import android.view.View +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.graphics.blue +import androidx.core.graphics.green +import androidx.core.graphics.red +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.models.live.LiveEventDonation +import com.futo.platformplayer.isHexColor + +class LiveChatDonationPill: LinearLayout { + private val _imageAuthor: ImageView; + private val _textAmount: TextView; + + private val _expireBar: View; + + constructor(context: Context, donation: LiveEventDonation) : super(context) { + inflate(context, R.layout.list_donation, this) + _imageAuthor = findViewById(R.id.donation_author_image); + _textAmount = findViewById(R.id.donation_amount) + + _textAmount.text = donation.amount; + _expireBar = findViewById(R.id.expire_bar); + _textAmount.text = donation.amount; + + val root = findViewById(R.id.root); + + + if(donation.colorDonation != null && donation.colorDonation.isHexColor()) { + val color = Color.parseColor(donation.colorDonation); + root.background.setTint(color); + + if((color.green > 140 || color.red > 140 || color.blue > 140) && (color.red + color.green + color.blue) > 400) + _textAmount.setTextColor(Color.BLACK); + else + _textAmount.setTextColor(Color.WHITE); + } + else { + root.background.setTint(Color.parseColor("#2A2A2A")); + _textAmount.setTextColor(Color.WHITE); + } + + if(donation.thumbnail.isNullOrEmpty()) + _imageAuthor.visibility = View.GONE; + else + Glide.with(_imageAuthor) + .load(donation.thumbnail) + .circleCrop() + .into(_imageAuthor); + } + + fun animateExpire(ms: Int) { + _expireBar.scaleX = 1f; + _expireBar.animate() + .scaleX(0f) + .translationXBy(-1f) + .setDuration(ms.toLong() + 500) + .start(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatListAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatListAdapter.kt new file mode 100644 index 00000000..eecc6d7e --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatListAdapter.kt @@ -0,0 +1,66 @@ +package com.futo.platformplayer.views.livechat + +import android.content.Context +import android.view.* +import android.widget.LinearLayout +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.api.media.models.contents.ContentType +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.live.LiveEventComment +import com.futo.platformplayer.api.media.models.live.LiveEventDonation +import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder +import com.futo.platformplayer.views.adapters.EmptyPreviewViewHolder +import com.futo.platformplayer.views.overlays.LiveChatOverlay + +class LiveChatListAdapter : RecyclerView.Adapter { + + private val _dataSet: ArrayList; + + + constructor(context: Context, dataSet: ArrayList): super() { + this._dataSet = dataSet; + } + + override fun getItemCount(): Int = _dataSet.size; + override fun getItemViewType(position: Int): Int { + if (position < 0) { + return -1; + } + val item = _dataSet.getOrNull(position) ?: return -1; + + if(item.event is LiveEventComment) + return 1; + else if(item.event is LiveEventDonation) + return 2; + else + return -1; + + } + + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): LiveChatListItem { + return when(viewType) { + 1 -> createLiveChatListItem(viewGroup); + 2 -> createLiveChatDonationListItem(viewGroup); + else -> EmptyItem(viewGroup); + }; + } + + private fun createLiveChatDonationListItem(viewGroup: ViewGroup): LiveChatDonationListItem = LiveChatDonationListItem(viewGroup).apply { + } + private fun createLiveChatListItem(viewGroup: ViewGroup): LiveChatListItem = LiveChatMessageListItem(viewGroup).apply { + }; + + override fun onBindViewHolder(holder: LiveChatListItem, position: Int) { + val value = _dataSet[position]; + + holder.bind(value); + } + + companion object { + private val TAG = "LiveChatListAdapter"; + } + + class EmptyItem(viewGroup: ViewGroup): LiveChatListItem(LinearLayout(viewGroup.context)) { + override fun bind(chat: LiveChatOverlay.ChatMessage) {} + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatListItem.kt b/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatListItem.kt new file mode 100644 index 00000000..88f5a68a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatListItem.kt @@ -0,0 +1,25 @@ +package com.futo.platformplayer.views.livechat + +import android.graphics.Color +import android.graphics.drawable.LevelListDrawable +import android.text.Spannable +import android.text.style.ImageSpan +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.models.live.LiveEventComment +import com.futo.platformplayer.dp +import com.futo.platformplayer.views.adapters.AnyAdapter +import com.futo.platformplayer.views.overlays.LiveChatOverlay +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +abstract class LiveChatListItem(view: View): RecyclerView.ViewHolder(view) { + protected val _view = view; + abstract fun bind(chat: LiveChatOverlay.ChatMessage); +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatMessageListItem.kt b/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatMessageListItem.kt new file mode 100644 index 00000000..ffe7f1b3 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatMessageListItem.kt @@ -0,0 +1,132 @@ +package com.futo.platformplayer.views.livechat + +import android.graphics.Color +import android.graphics.drawable.LevelListDrawable +import android.text.Spannable +import android.text.style.ImageSpan +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.models.live.ILiveEventChatMessage +import com.futo.platformplayer.api.media.models.live.LiveEventComment +import com.futo.platformplayer.dp +import com.futo.platformplayer.views.adapters.AnyAdapter +import com.futo.platformplayer.views.overlays.LiveChatOverlay +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class LiveChatMessageListItem(viewGroup: ViewGroup) + : LiveChatListItem(LayoutInflater.from(viewGroup.context).inflate(R.layout.list_chat_message, viewGroup, false)) { + private var _liveEvent: ILiveEventChatMessage? = null; + + private val _authorImage: ImageView = _view.findViewById(R.id.image_thumbnail); + private val _authorName: TextView = _view.findViewById(R.id.text_author); + private val _authorMessage: TextView = _view.findViewById(R.id.text_body); + + + override fun bind(chat: LiveChatOverlay.ChatMessage) { + val event = chat.event; + + _liveEvent = event; + if(event.thumbnail.isNullOrEmpty()) + _authorImage.visibility = View.GONE; + else { + Glide.with(_authorImage) + .load(event.thumbnail) + .into(_authorImage); + _authorImage.visibility = View.VISIBLE; + } + _authorName.text = event.name; + + if(event is LiveEventComment) { + val badges = event.badges.filter { chat.manager.hasEmoji(it) }; + if (badges.isEmpty()) + _authorName.text = event.name; + else { + val span = + _spanFactory.newSpannable(event.name + " " + badges.map { "." }.joinToString()); + for (i in badges.indices) { + val badge = badges[i]; + val drawable = LevelListDrawable(); + span.setSpan( + ImageSpan(drawable), + event.name.length + i + 1, + event.name.length + i + 2, + Spannable.SPAN_INCLUSIVE_INCLUSIVE + ); + chat.manager.getEmoji(badge) { emojiDrawable -> + if (emojiDrawable != null) { + drawable.addLevel(1, 1, emojiDrawable); + val iconSize = 16.dp(_view.resources); + drawable.setBounds(0, 0, iconSize, iconSize); + drawable.setLevel(1); + if (_liveEvent == event) + chat.scope.launch(Dispatchers.Main) { + _authorName.setText(span, TextView.BufferType.SPANNABLE); + } + } + } + } + } + + if (!event.colorName.isNullOrEmpty()) { + try { + _authorName.setTextColor(Color.parseColor(event.colorName)); + } catch (ex: Throwable) { + } + } else + _authorName.setTextColor(Color.WHITE); + + } + else { + _authorName.setTextColor(Color.WHITE); + } + + //Injects emotes + if(!chat.manager.let { liveChat -> + val emojiMatches = REGEX_EMOJIS.findAll(event.message).toList(); + val span = _spanFactory.newSpannable(event.message); + var injected = 0; + + for(emoji in emojiMatches + .filter { it.groupValues.size > 1 && liveChat.hasEmoji(it.groupValues[1]) } + .groupBy { it.groupValues[1] }) { + val emojiVal = emoji.key; + val drawable = LevelListDrawable(); + + for(match in emoji.value) + span.setSpan(ImageSpan(drawable), match.range.first, match.range.last + 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE); + + liveChat.getEmoji(emojiVal) { emojiDrawable -> + if(emojiDrawable != null) { + drawable.addLevel(1, 1, emojiDrawable); + val iconSize = 20.dp(_view.resources); + drawable.setBounds(0, 0, iconSize, iconSize); + drawable.setLevel(1); + if (_liveEvent == event) + chat.scope.launch(Dispatchers.Main) { + _authorMessage.setText(span, TextView.BufferType.SPANNABLE); + } + } + }; + injected++; + } + if(injected > 0) { + _authorMessage.setText(span, TextView.BufferType.SPANNABLE); + return@let true; + } else + return@let false; + }) + _authorMessage.text = event.message; + } + + + companion object { + val REGEX_EMOJIS = Regex("__(.*?)__"); + private val _spanFactory = Spannable.Factory.getInstance(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/others/BulletPointView.kt b/app/src/main/java/com/futo/platformplayer/views/others/BulletPointView.kt new file mode 100644 index 00000000..33fdca42 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/others/BulletPointView.kt @@ -0,0 +1,41 @@ +package com.futo.platformplayer.views.others + +import android.content.Context +import android.graphics.Color +import android.util.AttributeSet +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event0 + +class BulletPointView : LinearLayout { + + val bulletPoint: TextView; + val bulletPointValue: TextView; + + var onClick = Event0(); + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + inflate(context, R.layout.view_bullet_point, this); + + bulletPointValue = findViewById(R.id.bullet_text) + bulletPoint = findViewById(R.id.bullet_point) + + val attrArr = context.obtainStyledAttributes(attrs, R.styleable.BulletPointView, 0, 0); + bulletPointValue.setTextColor(attrArr.getColor(R.styleable.BulletPointView_valueColor, Color.WHITE)); + bulletPoint.setTextColor(attrArr.getColor(R.styleable.BulletPointView_bulletColor, Color.WHITE)); + bulletPointValue.text = attrArr.getText(R.styleable.BulletPointView_bulletText) ?: ""; + + this.setOnClickListener { onClick.emit() } + } + + fun withTextColor(color: Int) : BulletPointView { + bulletPointValue.setTextColor(color); + return this; + } + + fun withText(str: String) : BulletPointView { + bulletPointValue.text = str; + return this; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/others/Checkbox.kt b/app/src/main/java/com/futo/platformplayer/views/others/Checkbox.kt new file mode 100644 index 00000000..e970221f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/others/Checkbox.kt @@ -0,0 +1,31 @@ +package com.futo.platformplayer.views.others + +import android.content.Context +import android.util.AttributeSet +import android.widget.LinearLayout +import androidx.appcompat.widget.AppCompatImageView +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event1 + +class Checkbox : AppCompatImageView { + var value: Boolean = false + set(v) { + field = v; + if (v) { + setImageResource(R.drawable.ic_checkbox_checked); + } else { + setImageResource(R.drawable.ic_checkbox_unchecked); + } + }; + val onValueChanged = Event1(); + + constructor(context : Context, attrs : AttributeSet) : super(context, attrs) { + setImageResource(R.drawable.ic_checkbox_unchecked); + + isClickable = true; + setOnClickListener { + value = !value; + onValueChanged.emit(value); + }; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/others/CircularProgressBar.kt b/app/src/main/java/com/futo/platformplayer/views/others/CircularProgressBar.kt new file mode 100644 index 00000000..0c666cb7 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/others/CircularProgressBar.kt @@ -0,0 +1,90 @@ +package com.futo.platformplayer.views.others + +import android.content.Context +import android.graphics.* +import android.util.AttributeSet +import android.view.View +import androidx.core.content.ContextCompat +import com.futo.platformplayer.R + + +class CircularProgressBar : View { + private val _paintActive = Paint(Paint.ANTI_ALIAS_FLAG); + private val _paintInactive = Paint(Paint.ANTI_ALIAS_FLAG); + private val _path = Path(); + + var progress: Float = 0.0f + set(value) { + field = value; + invalidate(); + }; + + var strokeWidth: Float + get() { + return _paintInactive.strokeWidth; + } + set(value) { + _paintActive.strokeWidth = value; + _paintInactive.strokeWidth = value; + invalidate(); + }; + + var activeColor: Int + get() { + return _paintActive.color; + } + set(value) { + _paintActive.color = value; + invalidate(); + }; + var inactiveColor: Int + get() { + return _paintInactive.color; + } + set(value) { + _paintInactive.color = value; + invalidate(); + }; + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + _paintActive.style = Paint.Style.STROKE; + _paintInactive.style = Paint.Style.STROKE; + + val a = context.theme.obtainStyledAttributes(attrs, R.styleable.ProgressBar, 0, 0); + try { + progress = a.getFraction(R.styleable.ProgressBar_progress, 1, 1, 0.0f); + _paintActive.color = a.getColor(R.styleable.ProgressBar_activeColor, ContextCompat.getColor(context, R.color.colorPrimary)); + _paintInactive.color = a.getColor(R.styleable.ProgressBar_inactiveColor, ContextCompat.getColor(context, R.color.gray_c3)); + } finally { + a.recycle(); + } + + val b = context.theme.obtainStyledAttributes(attrs, R.styleable.CircularProgressBar, 0, 0); + try { + strokeWidth = b.getDimensionPixelSize(R.styleable.CircularProgressBar_strokeWidth, 10).toFloat(); + } finally { + b.recycle(); + } + } + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas); + + val w = width.toFloat(); + val h = height.toFloat(); + val size = Math.min(w, h) - strokeWidth; + val paddingLeft = (w - size) / 2; + val paddingTop = (h - size) / 2; + + _path.reset(); + _path.addArc(paddingLeft, paddingTop, paddingLeft + size, paddingTop + size, 90.0f, 360.0f); + canvas.drawPath(_path, _paintInactive); + + _path.reset(); + _path.addArc(paddingLeft, paddingTop, paddingLeft + size, paddingTop + size, 90.0f, progress * 360.0f); + canvas.drawPath(_path, _paintActive); + } + + companion object { + val TAG = "ProgressBar"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/others/CreatorThumbnail.kt b/app/src/main/java/com/futo/platformplayer/views/others/CreatorThumbnail.kt new file mode 100644 index 00000000..1d9bffd4 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/others/CreatorThumbnail.kt @@ -0,0 +1,121 @@ +package com.futo.platformplayer.views.others + +import android.animation.ObjectAnimator +import android.content.Context +import android.graphics.Bitmap +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.ImageView +import androidx.constraintlayout.widget.ConstraintLayout +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.images.GlideHelper.Companion.crossfade + +class CreatorThumbnail : ConstraintLayout { + private val _root: ConstraintLayout; + private val _imageChannelThumbnail: ImageView; + private val _imageNewActivity: ImageView; + private val _imageNeoPass: ImageView; + private var _harborAnimator: ObjectAnimator? = null; + private var _imageAnimator: ObjectAnimator? = null; + + var onClick = Event1>(); + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + LayoutInflater.from(context).inflate(R.layout.view_creator_thumbnail, this, true); + + _root = findViewById(R.id.root); + _imageChannelThumbnail = findViewById(R.id.image_channel_thumbnail); + _imageChannelThumbnail.clipToOutline = true; + _imageNewActivity = findViewById(R.id.image_new_activity); + _imageNeoPass = findViewById(R.id.image_neopass); + + if (!isInEditMode) { + setHarborAvailable(false, animate = false); + setNewActivity(false); + } + } + + fun clear() { + _imageChannelThumbnail.setImageResource(R.drawable.placeholder_channel_thumbnail); + setHarborAvailable(false, animate = false); + setNewActivity(false); + } + + fun setThumbnail(url: String?, animate: Boolean) { + if (url == null) { + clear(); + return; + } + + _harborAnimator?.cancel(); + _harborAnimator = null; + + _imageAnimator?.cancel(); + _imageAnimator = null; + + setHarborAvailable(url.startsWith("polycentric://"), animate); + + if (animate) { + Glide.with(_imageChannelThumbnail) + .load(url) + .placeholder(R.drawable.placeholder_channel_thumbnail) + .crossfade() + .into(_imageChannelThumbnail); + } else { + Glide.with(_imageChannelThumbnail) + .load(url) + .placeholder(R.drawable.placeholder_channel_thumbnail) + .into(_imageChannelThumbnail); + } + } + + fun setHarborAvailable(available: Boolean, animate: Boolean) { + _harborAnimator?.cancel(); + _harborAnimator = null; + + if (available) { + _imageNeoPass.visibility = View.VISIBLE; + if (animate) { + _harborAnimator = ObjectAnimator.ofFloat(_imageNeoPass, "alpha", 0.0f, 1.0f).setDuration(100); + _harborAnimator?.start(); + } + } else { + _imageNeoPass.visibility = View.GONE; + } + } + + fun setChannelImageResource(resource: Int?, animate: Boolean) { + setChannelImage(resource?.let { { _imageChannelThumbnail.setImageResource(it) } }, animate); + } + + fun setChannelImageBitmap(bitmap: Bitmap?, animate: Boolean) { + setChannelImage(bitmap?.let { { _imageChannelThumbnail.setImageBitmap(it) } }, animate); + } + + fun setChannelImage(setter: (() -> Unit)?, animate: Boolean) { + _imageAnimator?.cancel(); + _imageAnimator = null; + + if (setter != null) { + _imageChannelThumbnail.visibility = View.VISIBLE; + setter(); + if (animate) { + _imageAnimator = ObjectAnimator.ofFloat(_imageChannelThumbnail, "alpha", 0.0f, 1.0f).setDuration(100); + _imageAnimator?.start(); + } + } else { + _imageChannelThumbnail.visibility = View.GONE; + } + } + + fun setNewActivity(available: Boolean) { + _imageNewActivity.visibility = if (available) View.VISIBLE else View.GONE; + } + + companion object { + private const val TAG = "CreatorThumbnail"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/others/ProgressBar.kt b/app/src/main/java/com/futo/platformplayer/views/others/ProgressBar.kt new file mode 100644 index 00000000..7751d94e --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/others/ProgressBar.kt @@ -0,0 +1,114 @@ +package com.futo.platformplayer.views.others + +import android.content.Context +import android.graphics.* +import android.util.AttributeSet +import android.view.View +import androidx.core.content.ContextCompat +import com.futo.platformplayer.R + + +class ProgressBar : View { + private val _paintActive = Paint(Paint.ANTI_ALIAS_FLAG); + private val _paintInactive = Paint(Paint.ANTI_ALIAS_FLAG); + private val _path = Path(); + private val _progressRect = RectF(); + + var progress: Float = 0.0f + set(value) { + field = value; + invalidate(); + }; + + var radiusBottomLeft: Float = 0.0f + set(value) { + field = value; + updateCornerRadii(); + + + }; + var radiusBottomRight: Float = 0.0f + set(value) { + field = value; + updateCornerRadii(); + }; + var radiusTopLeft: Float = 0.0f + set(value) { + field = value; + updateCornerRadii(); + }; + var radiusTopRight: Float = 0.0f + set(value) { + field = value; + updateCornerRadii(); + }; + + var activeColor: Int + get() { + return _paintActive.color; + } + set(value) { + _paintActive.color = value; + invalidate(); + }; + var inactiveColor: Int + get() { + return _paintInactive.color; + } + set(value) { + _paintInactive.color = value; + invalidate(); + }; + + private var _corners: FloatArray = floatArrayOf(); + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + _paintActive.style = Paint.Style.FILL; + _paintInactive.style = Paint.Style.FILL; + + val a = context.theme.obtainStyledAttributes(attrs, R.styleable.ProgressBar, 0, 0); + try { + progress = a.getFraction(R.styleable.ProgressBar_progress, 1, 1, 0.0f); + radiusBottomLeft = a.getDimensionPixelSize(R.styleable.ProgressBar_radiusBottomLeft, 0).toFloat(); + radiusBottomRight = a.getDimensionPixelSize(R.styleable.ProgressBar_radiusBottomRight, 0).toFloat(); + radiusTopLeft = a.getDimensionPixelSize(R.styleable.ProgressBar_radiusTopLeft, 0).toFloat(); + radiusTopRight = a.getDimensionPixelSize(R.styleable.ProgressBar_radiusTopRight, 0).toFloat(); + _paintActive.color = a.getColor(R.styleable.ProgressBar_activeColor, ContextCompat.getColor(context, R.color.colorPrimary)); + _paintInactive.color = a.getColor(R.styleable.ProgressBar_inactiveColor, ContextCompat.getColor(context, R.color.gray_c3)); + } finally { + a.recycle(); + } + } + + private fun updateCornerRadii() { + _corners = floatArrayOf( + radiusTopLeft, radiusTopLeft, + radiusTopRight, radiusTopRight, + radiusBottomRight, radiusBottomRight, + radiusBottomLeft, radiusBottomLeft + ); + + invalidate(); + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas); + + val w = width.toFloat(); + val h = height.toFloat(); + + _path.reset(); + _progressRect.set(0.0f, 0.0f, w, h); + _path.addRoundRect(_progressRect, _corners, Path.Direction.CW); + canvas.drawPath(_path, _paintInactive); + + _path.reset(); + _progressRect.set(0.0f, 0.0f, progress * w, h); + _path.addRoundRect(_progressRect, _corners, Path.Direction.CW); + canvas.drawPath(_path, _paintActive); + } + + companion object { + val TAG = "ProgressBar"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/others/RadioGroupView.kt b/app/src/main/java/com/futo/platformplayer/views/others/RadioGroupView.kt new file mode 100644 index 00000000..22a5d21f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/others/RadioGroupView.kt @@ -0,0 +1,69 @@ +package com.futo.platformplayer.views.others + +import android.content.Context +import android.util.AttributeSet +import android.util.TypedValue +import com.futo.platformplayer.constructs.Event1 +import com.google.android.flexbox.FlexWrap +import com.google.android.flexbox.FlexboxLayout + +class RadioGroupView : FlexboxLayout { + private val _padding_dp: Float = 4.0f; + private val _padding_px: Int; + + val selectedOptions = arrayListOf(); + val onSelectedChange = Event1>(); + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + flexWrap = FlexWrap.WRAP; + _padding_px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, _padding_dp, context.resources.displayMetrics).toInt(); + + if (isInEditMode) { + setOptions(listOf("Example 1" to 1, "Example 2" to 2, "Example 3" to 3, "Example 4" to 4, "Example 5" to 5), listOf("Example 1", "Example 2"), + multiSelect = true, + atLeastOne = false + ); + } + } + + fun setOptions(options: List>, initiallySelectedOptions: List, multiSelect: Boolean, atLeastOne: Boolean) { + selectedOptions.clear(); + selectedOptions.addAll(initiallySelectedOptions); + + removeAllViews(); + + val radioViews = arrayListOf(); + for (option in options) { + val radioView = RadioView(context); + radioViews.add(radioView); + radioView.setHandleClick(false); + radioView.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) + radioView.setInfo(option.first, initiallySelectedOptions.contains(option.second)); + radioView.setPadding(_padding_px, _padding_px, _padding_px, _padding_px); + radioView.onClick.subscribe { + val selected = !radioView.selected; + if (selected) { + if (selectedOptions.size > 0 && !multiSelect) { + for (v in radioViews) { + v.setIsSelected(false); + } + + selectedOptions.clear(); + } + + radioView.setIsSelected(true); + selectedOptions.add(option.second); + } else { + if (selectedOptions.size < 2 && atLeastOne) { + return@subscribe; + } + + radioView.setIsSelected(false); + selectedOptions.remove(option.second); + } + + onSelectedChange.emit(selectedOptions); + }; + addView(radioView); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/others/RadioView.kt b/app/src/main/java/com/futo/platformplayer/views/others/RadioView.kt new file mode 100644 index 00000000..22ff167e --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/others/RadioView.kt @@ -0,0 +1,60 @@ +package com.futo.platformplayer.views.others + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event1 + +class RadioView : LinearLayout { + private val _root: FrameLayout; + private val _textTag: TextView; + private var _text: String = ""; + private var _selected: Boolean = false; + private var _handleClick: Boolean = true; + + val selected get() = _selected; + var onClick = Event0(); + var onSelectedChange = Event1(); + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + LayoutInflater.from(context).inflate(R.layout.view_tag, this, true); + _root = findViewById(R.id.root); + _textTag = findViewById(R.id.text_tag); + _root.setOnClickListener { + onClick.emit(); + if (_handleClick) { + setIsSelected(!_selected) + } + }; + + _root.setBackgroundResource(R.drawable.background_radio_unselected); + _textTag.setTextColor(resources.getColor(R.color.gray_67)); + } + + fun setInfo(text: String, selected: Boolean) { + _text = text; + _textTag.text = text; + setIsSelected(selected); + } + + fun setIsSelected(selected: Boolean) { + val changed = _selected != selected; + if (!changed) { + return; + } + + _selected = selected; + _root.setBackgroundResource(if (selected) R.drawable.background_radio_selected else R.drawable.background_radio_unselected); + _textTag.setTextColor(resources.getColor(if (selected) R.color.white else R.color.gray_67)); + onSelectedChange.emit(_selected); + } + + fun setHandleClick(handleClick: Boolean) { + _handleClick = handleClick; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/others/TagView.kt b/app/src/main/java/com/futo/platformplayer/views/others/TagView.kt new file mode 100644 index 00000000..54af9e94 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/others/TagView.kt @@ -0,0 +1,32 @@ +package com.futo.platformplayer.views.others + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event1 + +class TagView : LinearLayout { + private val _root: FrameLayout; + private val _textTag: TextView; + private var _text: String = ""; + private var _value: Any? = null; + + var onClick = Event1>(); + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + LayoutInflater.from(context).inflate(R.layout.view_tag, this, true); + _root = findViewById(R.id.root); + _textTag = findViewById(R.id.text_tag); + _root.setOnClickListener { _value?.let { onClick.emit(Pair(_text, it)); }; } + } + + fun setInfo(text: String, value: Any) { + _text = text; + _textTag.text = text; + _value = value; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/others/TagsView.kt b/app/src/main/java/com/futo/platformplayer/views/others/TagsView.kt new file mode 100644 index 00000000..277d58a4 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/others/TagsView.kt @@ -0,0 +1,40 @@ +package com.futo.platformplayer.views.others + +import android.content.Context +import android.util.AttributeSet +import android.util.TypedValue +import com.futo.platformplayer.constructs.Event1 +import com.google.android.flexbox.FlexWrap +import com.google.android.flexbox.FlexboxLayout + +class TagsView : FlexboxLayout { + private val _padding_dp: Float = 4.0f; + private val _padding_px: Int; + + var onClick = Event1>(); + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + flexWrap = FlexWrap.WRAP; + _padding_px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, _padding_dp, context.resources.displayMetrics).toInt(); + + if (isInEditMode) { + setTags(listOf("Example 1", "Example 2", "Example 3", "Example 4", "Example 5", "Example 5")); + } + } + + fun setTags(tags: List) { + setPairs(tags.map { t -> Pair(t, t) }); + } + + fun setPairs(tags: List>) { + removeAllViews(); + for (tag in tags) { + val tagView = TagView(context); + tagView.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) + tagView.setInfo(tag.first, tag.second); + tagView.setPadding(_padding_px, _padding_px, _padding_px, _padding_px); + tagView.onClick.subscribe { onClick.emit(it) }; + addView(tagView); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/others/Toggle.kt b/app/src/main/java/com/futo/platformplayer/views/others/Toggle.kt new file mode 100644 index 00000000..844b58de --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/others/Toggle.kt @@ -0,0 +1,48 @@ +package com.futo.platformplayer.views.others + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatImageView +import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event1 + +class Toggle : AppCompatImageView { + var value: Boolean = false + private set; + + val onValueChanged = Event1(); + private var _currentDrawable: AnimatedVectorDrawableCompat? = null; + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + isClickable = true; + setOnClickListener { + setValue(!value); + onValueChanged.emit(value); + }; + + setImageResource(R.drawable.toggle_disabled); + + val attrArr = context.obtainStyledAttributes(attrs, R.styleable.Toggle, 0, 0); + val toggleEnabled = attrArr.getBoolean(R.styleable.Toggle_toggleEnabled, false); + setValue(toggleEnabled, false); + scaleType = ScaleType.FIT_CENTER; + } + + fun setValue(v: Boolean, animated: Boolean = true) { + if (value == v) { + return; + } + + value = v; + + _currentDrawable?.stop(); + if (animated) { + _currentDrawable = AnimatedVectorDrawableCompat.create(context, if (v) R.drawable.toggle_animated else R.drawable.toggle_animated_reverse); + setImageDrawable(_currentDrawable); + _currentDrawable?.start(); + } else { + setImageResource(if (v) R.drawable.toggle_enabled else R.drawable.toggle_disabled); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/others/ToggleTagView.kt b/app/src/main/java/com/futo/platformplayer/views/others/ToggleTagView.kt new file mode 100644 index 00000000..27c4e68d --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/others/ToggleTagView.kt @@ -0,0 +1,47 @@ +package com.futo.platformplayer.views.others + +import android.content.Context +import android.graphics.Color +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event1 + +class ToggleTagView : LinearLayout { + private val _root: FrameLayout; + private val _textTag: TextView; + private var _text: String = ""; + + var isActive: Boolean = false + private set; + + var onClick = Event1(); + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + LayoutInflater.from(context).inflate(R.layout.view_toggle_tag, this, true); + _root = findViewById(R.id.root); + _textTag = findViewById(R.id.text_tag); + _root.setOnClickListener { setToggle(!isActive); onClick.emit(isActive); } + } + + fun setToggle(isActive: Boolean) { + this.isActive = isActive; + if(isActive) { + _root.setBackgroundResource(R.drawable.background_pill_toggled); + _textTag.alpha = 1f; + } + else { + _root.setBackgroundResource(R.drawable.background_pill_untoggled); + _textTag.alpha = 0.5f; + } + } + + fun setInfo(text: String, isActive: Boolean) { + _text = text; + _textTag.text = text; + setToggle(isActive); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/DescriptionOverlay.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/DescriptionOverlay.kt new file mode 100644 index 00000000..917edb33 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/DescriptionOverlay.kt @@ -0,0 +1,35 @@ +package com.futo.platformplayer.views.overlays + +import android.content.Context +import android.text.Spanned +import android.util.AttributeSet +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.setPlatformPlayerLinkMovementMethod + +class DescriptionOverlay : LinearLayout { + val onClose = Event0(); + + private val _topbar: OverlayTopbar; + private val _textDescription: TextView; + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + inflate(context, R.layout.overlay_description, this) + _topbar = findViewById(R.id.topbar); + _textDescription = findViewById(R.id.text_description); + + _topbar.onClose.subscribe(this, onClose::emit); + + _textDescription.setPlatformPlayerLinkMovementMethod(context); + } + + fun load(text: Spanned?) { + _textDescription.text = text; + } + + fun cleanup() { + _topbar.onClose.remove(this); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/LiveChatOverlay.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/LiveChatOverlay.kt new file mode 100644 index 00000000..8f0e0e09 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/LiveChatOverlay.kt @@ -0,0 +1,417 @@ +package com.futo.platformplayer.views.overlays + +import android.animation.LayoutTransition +import android.content.Context +import android.graphics.Color +import android.graphics.PointF +import android.util.AttributeSet +import android.util.DisplayMetrics +import android.view.View +import android.webkit.CookieManager +import android.webkit.ValueCallback +import android.webkit.WebView +import android.webkit.WebViewClient +import android.widget.Button +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.graphics.blue +import androidx.core.graphics.green +import androidx.core.graphics.red +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearSmoothScroller +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.api.media.LiveChatManager +import com.futo.platformplayer.api.media.models.live.ILiveChatWindowDescriptor +import com.futo.platformplayer.api.media.models.live.ILiveEventChatMessage +import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent +import com.futo.platformplayer.api.media.models.live.LiveEventComment +import com.futo.platformplayer.api.media.models.live.LiveEventDonation +import com.futo.platformplayer.api.media.models.live.LiveEventRaid +import com.futo.platformplayer.api.media.models.live.LiveEventViewCount +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.dp +import com.futo.platformplayer.isHexColor +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.toHumanBitrate +import com.futo.platformplayer.toHumanNumber +import com.futo.platformplayer.views.AnyAdapterView +import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny +import com.futo.platformplayer.views.livechat.LiveChatDonationPill +import com.futo.platformplayer.views.livechat.LiveChatListAdapter +import com.futo.platformplayer.views.livechat.LiveChatMessageListItem +import com.stripe.android.core.utils.encodeToJson +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + + +class LiveChatOverlay : LinearLayout { + val onClose = Event0(); + + private val _closeButton: ImageView; + private val _donationList: LinearLayout; + + private val _overlay: View; + + private val _chatContainer: RecyclerView; + private val _chatWindowContainer: WebView; + private val _overlayHeader: ConstraintLayout; + + private val _overlayDonation: ConstraintLayout; + private val _overlayDonation_AuthorImage: ImageView; + private val _overlayDonation_AuthorName: TextView; + private val _overlayDonation_Text: TextView; + private val _overlayDonation_Amount: TextView; + private val _overlayDonation_AmountContainer: LinearLayout; + + private val _overlayRaid: ConstraintLayout; + private val _overlayRaid_Name: TextView; + private val _overlayRaid_Thumbnail: ImageView; + + private val _overlayRaid_ButtonGo: Button; + private val _overlayRaid_ButtonPrevent: Button; + + private val _textViewers: TextView; + + private val _headerHeightBase = 59; + private val _headerHeightDonations = 94; + + private var _scope: CoroutineScope? = null; + private var _manager: LiveChatManager? = null; + private var _window: ILiveChatWindowDescriptor? = null; + + private val _chatLayoutManager: ChatLayoutManager; + private val _chats = arrayListOf(); + //private val _chatAdapter: AnyAdapterView; + private val _chatAdapter: LiveChatListAdapter; + + private var _detachCounter: Int = 0; + + private var _shownDonations: HashMap = hashMapOf(); + + private var _currentRaid: LiveEventRaid? = null; + + val onRaidNow = Event1(); + val onRaidPrevent = Event1(); + + private val _argJsonSerializer = Json; + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + inflate(context, R.layout.overlay_livechat, this) + + _chatWindowContainer = findViewById(R.id.chatWindowContainer); + _chatWindowContainer.settings.javaScriptEnabled = true; + _chatWindowContainer.settings.domStorageEnabled = true; + _chatWindowContainer.webViewClient = object: WebViewClient() { + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url); + _window?.let { + for(req in it.removeElements) + view?.evaluateJavascript("document.querySelectorAll(" + _argJsonSerializer.encodeToString(req) + ").forEach(x=>x.remove());") {}; + }; + } + }; + + _chatContainer = findViewById(R.id.chatContainer); + _chatLayoutManager = ChatLayoutManager(context); + //_chatAdapter = _chatContainer.asAny(_chats); + _chatAdapter = LiveChatListAdapter(context, _chats); + _chatContainer.adapter = _chatAdapter; + _chatContainer.layoutManager = _chatLayoutManager; + + _donationList = findViewById(R.id.donation_list); + + _overlay = findViewById(R.id.overlay); + _overlay.setOnClickListener { + hideOverlay(); + } + + _overlayHeader = findViewById(R.id.topbar); + _overlayHeader.layoutTransition = LayoutTransition().apply { + this.enableTransitionType(LayoutTransition.CHANGING); + } + + _textViewers = findViewById(R.id.text_viewers); + + _overlayDonation = findViewById(R.id.overlay_donation); + _overlayDonation_AuthorImage = findViewById(R.id.donation_author_image); + _overlayDonation_AuthorName = findViewById(R.id.donation_author_name); + _overlayDonation_Text = findViewById(R.id.donation_text); + _overlayDonation_Amount = findViewById(R.id.donation_amount); + _overlayDonation_AmountContainer = findViewById(R.id.donation_amount_container) + + _overlayRaid = findViewById(R.id.overlay_raid); + _overlayRaid_Name = findViewById(R.id.raid_name); + _overlayRaid_Thumbnail = findViewById(R.id.raid_thumbnail); + _overlayRaid_ButtonGo = findViewById(R.id.raid_button_go); + _overlayRaid_ButtonPrevent = findViewById(R.id.raid_button_prevent); + + _overlayRaid.visibility = View.GONE; + + _overlayRaid_ButtonGo.setOnClickListener { + _currentRaid?.let { + onRaidNow.emit(it); + } + } + _overlayRaid_ButtonPrevent.setOnClickListener { + _currentRaid?.let { + _currentRaid = null; + _overlayRaid.visibility = View.GONE; + onRaidPrevent.emit(it); + } + } + + + _closeButton = findViewById(R.id.button_close); + _closeButton.setOnClickListener { + close(); + }; + + hideOverlay(); + updateDonationUI(); + } + + fun updateDonationUI() { + _overlayHeader.layoutParams = ConstraintLayout.LayoutParams(_overlayHeader.layoutParams).apply { + if (_shownDonations.size > 0) + this.height = _headerHeightDonations.dp(resources); + else + this.height = _headerHeightBase.dp(resources); + }; + } + + fun close() { + cancel(); + onClose.emit(); + } + + fun load(scope: CoroutineScope, manager: LiveChatManager?, window: ILiveChatWindowDescriptor? = null, viewerCount: Long? = null) { + _scope = scope; + _donationList.removeAllViews(); + _chats.clear(); + //_chatAdapter.notifyContentChanged(); + _chatAdapter.notifyDataSetChanged(); + _manager = manager; + _window = window; + + if(viewerCount != null) + _textViewers.text = viewerCount.toHumanNumber() + " viewers"; + else if(manager != null) + _textViewers.text = manager.viewCount.toHumanNumber() + " viewers"; + else + _textViewers.text = ""; + + if(window != null) { + _chatWindowContainer.visibility = View.VISIBLE; + _chatContainer.visibility = View.GONE; + _chatWindowContainer.loadUrl(window.url); + } + else { + _chatContainer.visibility = View.VISIBLE; + _chatWindowContainer.visibility = View.GONE; + } + + manager?.getHistory()?.let {history -> + for(event in history) + handleLiveEvent(event); + } + setRaid(null); + + //handleLiveEvent(LiveEventDonation("Test", null, "TestDonation", "$50.00", 6000, "#FF0000")) + + manager?.follow(this) { + val comments = arrayListOf() + for(event in it) { + if(event is LiveEventComment) + comments.add(ChatMessage(event, manager, scope)); + else if(event is LiveEventDonation) { + comments.add(ChatMessage(event, manager, scope)); + handleLiveEvent(event); + } + else if(event is LiveEventViewCount) + scope.launch(Dispatchers.Main) { + _textViewers.text = "${event.viewCount.toLong().toHumanNumber()} viewers"; + } + else + handleLiveEvent(event); + } + checkDonations(); + addComments(*comments.toTypedArray()); + } + } + + fun cancel() { + _detachCounter++; + _scope = null; + _chats.clear(); + //_chatAdapter.notifyContentChanged(); + _chatWindowContainer.loadUrl("about:blank"); + _chatAdapter.notifyDataSetChanged(); + _manager?.unfollow(this); + _manager?.stop(); //TODO: Remove this after proper manager gets stopped in videodetail for reuse + _manager = null; + } + + fun handleLiveEvent(liveEvent: IPlatformLiveEvent) { + when(liveEvent::class) { + LiveEventDonation::class -> addDonation(liveEvent as LiveEventDonation); + LiveEventRaid::class -> setRaid(liveEvent as LiveEventRaid); + LiveEventViewCount::class -> setViewCount((liveEvent as LiveEventViewCount).viewCount); + } + } + + fun showOverlay(action: ()->Unit) { + _overlay.visibility = VISIBLE; + action(); + } + fun hideOverlay() { + _overlay.visibility = GONE; + _overlayDonation.visibility = GONE; + } + + fun showDonation(donation: LiveEventDonation) { + showOverlay { + //TODO: Fancy animations + if(donation.thumbnail.isNullOrEmpty()) + _overlayDonation_AuthorImage.visibility = View.GONE; + else { + _overlayDonation_AuthorImage.visibility = View.VISIBLE; + Glide.with(_overlayDonation_AuthorImage) + .load(donation.thumbnail) + .into(_overlayDonation_AuthorImage); + } + _overlayDonation_AuthorName.text = donation.name; + _overlayDonation_Text.text = donation.message; + _overlayDonation_Amount.text = donation.amount.trim(); + _overlayDonation.visibility = VISIBLE; + if(donation.colorDonation != null && donation.colorDonation.isHexColor()) { + val color = Color.parseColor(donation.colorDonation); + _overlayDonation_AmountContainer.background.setTint(color); + + if((color.green > 140 || color.red > 140 || color.blue > 140) && (color.red + color.green + color.blue) > 400) + _overlayDonation_Amount.setTextColor(Color.BLACK) + else + _overlayDonation_Amount.setTextColor(Color.WHITE); + } + else { + _overlayDonation_AmountContainer.background.setTint(Color.parseColor("#2A2A2A")); + _overlayDonation_Amount.setTextColor(Color.WHITE); + } + }; + } + fun addDonation(donation: LiveEventDonation) { + if(donation.hasExpired()) { + Logger.i(TAG, "Donation that is already expired: [${donation.amount}]" + donation.name + ":" + donation.message + " EXPIRE: ${donation.expire}"); + return; + } + else + Logger.i(TAG, "Donation Added: [${donation.amount}]" + donation.name + ":" + donation.message + " EXPIRE: ${donation.expire}"); + val view = LiveChatDonationPill(context, donation); + view.setOnClickListener { + showDonation(donation); + }; + _donationList.addView(view, 0); + synchronized(_shownDonations) { + _shownDonations.put(donation, view); + } + updateDonationUI(); + view.animateExpire(donation.expire); + } + fun checkDonations() { + val expireds = synchronized(_shownDonations) { + val toRemove = _shownDonations.filter { it.key.hasExpired() } + for(remove in toRemove) + _shownDonations.remove(remove.key); + return@synchronized toRemove; + } + for(expired in expireds) { + expired.value.animate() + .alpha(0f) + .setDuration(1000) + .withEndAction({ + _donationList.removeView(expired.value); + updateDonationUI(); + }).start(); + } + } + + + fun addComments(vararg comments: ChatMessage) { + val startLength = _chats.size; + + if(_window == null) { + _chats.addAll(comments); + _chatAdapter.notifyItemRangeInserted(startLength, comments.size); + _chatContainer.smoothScrollToPosition(_chats.size); + } + } + fun setRaid(raid: LiveEventRaid?) { + _currentRaid = raid; + _scope?.launch(Dispatchers.Main) { + _overlayRaid_Name.text = raid?.targetName ?: ""; + Glide.with(_overlayRaid_Thumbnail).clear(_overlayRaid_Thumbnail); + if(raid != null) { + Glide.with(_overlayRaid_Thumbnail) + .load(raid.targetThumbnail) + .into(_overlayRaid_Thumbnail); + _overlayRaid.visibility = View.VISIBLE; + } + else + _overlayRaid.visibility = View.GONE; + } + } + fun setViewCount(viewCount: Int) { + _scope?.launch(Dispatchers.Main) { + _textViewers.text = viewCount.toLong().toHumanNumber() + " viewers"; + } + } + + + class ChatLayoutManager: LinearLayoutManager { + var scrollTime: Long = 1000; + + constructor(context: Context): super(context) { + stackFromEnd = true; + } + override fun smoothScrollToPosition( + recyclerView: RecyclerView, + state: RecyclerView.State?, + position: Int + ) { + val linearSmoothScroller: LinearSmoothScroller = + object : LinearSmoothScroller(recyclerView.context) { + val MILLISECONDS_PER_INCH = 2000f; + //TODO: Make scrollspeed = nextRequest time + override fun computeScrollVectorForPosition(targetPosition: Int): PointF? { + return this@ChatLayoutManager + .computeScrollVectorForPosition(targetPosition); + } + + override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float { + return MILLISECONDS_PER_INCH / displayMetrics.densityDpi + } + } + linearSmoothScroller.targetPosition = position + startSmoothScroll(linearSmoothScroller) + } + } + + class ChatMessage( + val event: ILiveEventChatMessage, + val manager: LiveChatManager, + val scope: CoroutineScope + ); + + companion object { + val TAG = "LiveChatOverlay"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/LoaderOverlay.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/LoaderOverlay.kt new file mode 100644 index 00000000..06d43e2c --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/LoaderOverlay.kt @@ -0,0 +1,29 @@ +package com.futo.platformplayer.views.overlays + +import android.content.Context +import android.graphics.drawable.Animatable +import android.util.AttributeSet +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageView +import com.futo.platformplayer.R + +class LoaderOverlay(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) { + private val _container: FrameLayout; + private val _loader: ImageView; + + init { + inflate(context, R.layout.overlay_loader, this); + _container = findViewById(R.id.container); + _loader = findViewById(R.id.loader); + } + + fun show() { + this.visibility = View.VISIBLE; + (_loader.drawable as Animatable?)?.start(); + } + fun hide() { + this.visibility = View.GONE; + (_loader.drawable as Animatable?)?.stop(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/OverlayTopbar.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/OverlayTopbar.kt new file mode 100644 index 00000000..89f91265 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/OverlayTopbar.kt @@ -0,0 +1,45 @@ +package com.futo.platformplayer.views.overlays + +import android.content.Context +import android.util.AttributeSet +import android.widget.ImageView +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.views.lists.VideoListEditorView + +class OverlayTopbar : ConstraintLayout { + + private val _name: TextView; + private val _meta: TextView; + + private val _button_close: ImageView; + + val onClose = Event0(); + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + inflate(context, R.layout.overlay_topbar, this); + + _name = findViewById(R.id.text_name); + _meta = findViewById(R.id.text_meta); + _button_close = findViewById(R.id.button_close); + + val attrArr = context.obtainStyledAttributes(attrs, R.styleable.OverlayTopbar, 0, 0); + val attrText = attrArr.getText(R.styleable.OverlayTopbar_title) ?: ""; + _name.text = attrText; + + val attrMetaText = attrArr.getText(R.styleable.OverlayTopbar_metadata) ?: ""; + _meta.text = attrMetaText; + + _button_close.setOnClickListener { + onClose.emit(); + }; + } + + + fun setInfo(name: String, meta: String) { + _name.text = name; + _meta.text = meta; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/QueueEditorOverlay.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/QueueEditorOverlay.kt new file mode 100644 index 00000000..34955187 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/QueueEditorOverlay.kt @@ -0,0 +1,40 @@ +package com.futo.platformplayer.views.overlays + +import android.content.Context +import android.util.AttributeSet +import android.widget.LinearLayout +import com.futo.platformplayer.states.StatePlayer +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.views.lists.VideoListEditorView + +class QueueEditorOverlay : LinearLayout { + + private val _topbar : OverlayTopbar; + private val _editor : VideoListEditorView; + + val onClose = Event0(); + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + inflate(context, R.layout.overlay_queue, this) + _topbar = findViewById(R.id.topbar); + _editor = findViewById(R.id.editor); + + _topbar.onClose.subscribe(this, onClose::emit); + _editor.onVideoOrderChanged.subscribe { StatePlayer.instance.setQueueWithExisting(it) } + _editor.onVideoRemoved.subscribe { v -> StatePlayer.instance.removeFromQueue(v) } + _editor.onVideoClicked.subscribe { v -> StatePlayer.instance.setQueuePosition(v) } + + _topbar.setInfo("Queue", ""); + } + + fun updateQueue() { + val queue = StatePlayer.instance.getQueue(); + _editor.setVideos(queue, true); + _topbar.setInfo("Queue", "${queue.size} videos"); + } + + fun cleanup() { + _topbar.onClose.remove(this); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/RepliesOverlay.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/RepliesOverlay.kt new file mode 100644 index 00000000..122d3d27 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/RepliesOverlay.kt @@ -0,0 +1,82 @@ +package com.futo.platformplayer.views.overlays + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.widget.LinearLayout +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StatePolycentric +import com.futo.platformplayer.views.comments.AddCommentView +import com.futo.platformplayer.views.segments.CommentsList +import userpackage.Protocol + +class RepliesOverlay : LinearLayout { + val onClose = Event0(); + + private val _topbar: OverlayTopbar; + private val _commentsList: CommentsList; + private val _addCommentView: AddCommentView; + private var _readonly = false; + private var _onCommentAdded: ((comment: IPlatformComment) -> Unit)? = null; + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + inflate(context, R.layout.overlay_replies, this) + _topbar = findViewById(R.id.topbar); + _commentsList = findViewById(R.id.comments_list); + _addCommentView = findViewById(R.id.add_comment_view); + + _addCommentView.onCommentAdded.subscribe { + _commentsList.addComment(it); + _onCommentAdded?.invoke(it); + } + + _commentsList.onCommentsLoaded.subscribe { count -> + if (_readonly && count == 0) { + UIDialogs.toast(context, "Expected at least one reply but no replies were returned by the server"); + } + } + + _commentsList.onClick.subscribe { c -> + val replyCount = c.replyCount; + var metadata = ""; + if (replyCount != null && replyCount > 0) { + metadata += "$replyCount replies"; + } + + if (c is PolycentricPlatformComment) { + load(false, metadata, c.contextUrl, c.reference, { StatePolycentric.instance.getCommentPager(c.contextUrl, c.reference) }); + } else { + load(true, metadata, null, null, { StatePlatform.instance.getSubComments(c) }); + } + }; + + _topbar.onClose.subscribe(this, onClose::emit); + _topbar.setInfo("Replies", ""); + } + + fun load(readonly: Boolean, metadata: String, contextUrl: String?, ref: Protocol.Reference?, loader: suspend () -> IPager, onCommentAdded: ((comment: IPlatformComment) -> Unit)? = null) { + _readonly = readonly; + if (readonly) { + _addCommentView.visibility = View.GONE; + } else { + _addCommentView.visibility = View.VISIBLE; + _addCommentView.setContext(contextUrl, ref); + } + + _topbar.setInfo("Replies", metadata); + _commentsList.load(readonly, loader); + _onCommentAdded = onCommentAdded; + } + + fun cleanup() { + _topbar.onClose.remove(this); + _onCommentAdded = null; + _commentsList.cancel(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuButtonList.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuButtonList.kt new file mode 100644 index 00000000..f5a404fc --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuButtonList.kt @@ -0,0 +1,71 @@ +package com.futo.platformplayer.views.overlays.slideup + +import android.content.Context +import android.util.AttributeSet +import android.util.TypedValue +import android.view.Gravity +import android.view.LayoutInflater +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event1 + +class SlideUpMenuButtonList : LinearLayout { + private val _root: LinearLayout; + + val onClick = Event1(); + val buttons: HashMap = hashMapOf(); + var _activeText: String? = null; + + constructor(context: Context, attrs: AttributeSet? = null): super(context, attrs) { + LayoutInflater.from(context).inflate(R.layout.overlay_slide_up_menu_button_list, this, true); + + _root = findViewById(R.id.root); + } + + fun setButtons(texts: List, activeText: String? = null) { + _root.removeAllViews(); + + val marginLeft = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3.0f, resources.displayMetrics).toInt(); + val marginRight = marginLeft; + + buttons.clear(); + for (t in texts) { + val button = LinearLayout(context); + button.layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT).apply { + weight = 1.0f; + marginStart = marginLeft; + marginEnd = marginRight; + }; + + button.background = if (t == activeText) ContextCompat.getDrawable(context, R.drawable.background_slide_up_option_selected) else ContextCompat.getDrawable(context, R.drawable.background_slide_up_option); + button.gravity = Gravity.CENTER; + button.setOnClickListener { + onClick.emit(t); + }; + + button.setPadding(0, 0, 0, 0); + + val text = TextView(context); + text.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); + text.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 8f); + text.text = t; + text.maxLines = 1; + text.setTextColor(ContextCompat.getColor(context, R.color.white)); + text.typeface = ResourcesCompat.getFont(context, R.font.inter_light); + button.addView(text); + + _activeText = activeText; + buttons[t] = button; + _root.addView(button); + } + } + + fun setSelected(text: String) { + buttons[_activeText]?.background = ContextCompat.getDrawable(context, R.drawable.background_slide_up_option); + buttons[text]?.background = ContextCompat.getDrawable(context, R.drawable.background_slide_up_option_selected); + _activeText = text; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuFilters.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuFilters.kt new file mode 100644 index 00000000..4bac1c6b --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuFilters.kt @@ -0,0 +1,131 @@ +package com.futo.platformplayer.views.overlays.slideup + +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope +import com.futo.platformplayer.UISlideOverlays +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.models.FilterGroup +import com.futo.platformplayer.api.media.models.ResultCapabilities +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StatePlatform +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class SlideUpMenuFilters { + val onOK = Event2?, Boolean>(); + + private val _container: ViewGroup; + private var _enabledClientsIds: List; + private val _filterValues: HashMap>; + private val _slideUpMenuOverlay: SlideUpMenuOverlay; + private var _changed: Boolean = false; + private val _lifecycleScope: CoroutineScope; + + var commonCapabilities: ResultCapabilities? = null; + + constructor(lifecycleScope: CoroutineScope, container: ViewGroup, enabledClientsIds: List, filterValues: HashMap>) { + _lifecycleScope = lifecycleScope; + _container = container; + _enabledClientsIds = enabledClientsIds; + _filterValues = filterValues; + _slideUpMenuOverlay = SlideUpMenuOverlay(_container.context, _container, "Filters", "Done", true, listOf()); + _slideUpMenuOverlay.onOK.subscribe { + onOK.emit(_enabledClientsIds, _changed); + _slideUpMenuOverlay.hide(); + } + + updateCommonCapabilities(); + } + + private fun updateCommonCapabilities() { + _lifecycleScope.launch(Dispatchers.IO) { + try { + val caps = StatePlatform.instance.getCommonSearchCapabilities(_enabledClientsIds); + synchronized(_filterValues) { + if (caps != null) { + val keysToRemove = arrayListOf(); + for (pair in _filterValues) { + //Remove filter groups from selected filters that are not selectable anymore + val currentFilter = + caps.filters.firstOrNull { it.idOrName == pair.key }; + if (currentFilter == null) { + keysToRemove.add(pair.key); + } else { + //Remove selected filter values that are not selectable anymore + _filterValues[pair.key] = + pair.value.filter { currentValue -> currentFilter.filters.any { f -> f.idOrName == currentValue } }; + } + } + + keysToRemove.forEach { _filterValues.remove(it) }; + } else { + _filterValues.clear(); + } + } + + commonCapabilities = caps; + + withContext(Dispatchers.Main) { + updateItems(); + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to update common capabilities", e) + } + } + } + + private fun updateItems() { + val caps = commonCapabilities; + val items = arrayListOf(); + + val group = SlideUpMenuRadioGroup(_container.context, "Sources", StatePlatform.instance.getSortedEnabledClient().map { Pair(it.name, it.id) }, + _enabledClientsIds, true, true); + + group.onSelectedChange.subscribe { + _enabledClientsIds = it as List; + updateCommonCapabilities(); + }; + + items.add(group); + + if (caps == null) { + _slideUpMenuOverlay.setItems(items); + return; + } + + for (filterGroup in caps.filters) { + val value: List; + synchronized(_filterValues) { + value = _filterValues[filterGroup.idOrName] ?: listOf(); + } + + val g = SlideUpMenuRadioGroup(_container.context, filterGroup.name, filterGroup.filters.map { Pair(it.idOrName, it.idOrName) }, + value, filterGroup.isMultiSelect, false); + + g.onSelectedChange.subscribe { + synchronized(_filterValues) { + _filterValues[filterGroup.idOrName] = it.map { v -> v as String }; + } + _changed = true; + }; + + items.add(g); + } + + _slideUpMenuOverlay.setItems(items); + } + + fun show() { + _slideUpMenuOverlay.show(); + } + + companion object { + private const val TAG = "SlideUpMenuFilters"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuGroup.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuGroup.kt new file mode 100644 index 00000000..a6d5dc3a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuGroup.kt @@ -0,0 +1,62 @@ +package com.futo.platformplayer.views.overlays.slideup + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.R + +class SlideUpMenuGroup : LinearLayout { + + private lateinit var title: TextView; + private lateinit var itemContainer: LinearLayout; + private var parentClickListener: (()->Unit)? = null; + private val items: List; + + var groupTag: Any? = null; + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + init(); + this.items = listOf(); + } + + constructor(context: Context, titleText: String, tag: Any, items: List) : super(context){ + init(); + title.text = titleText; + groupTag = tag; + this.items = items.toList(); + addItems(items); + } + + constructor(context: Context, titleText: String, tag: Any, vararg items: SlideUpMenuItem) + : this(context, titleText, tag, items.asList()) + + private fun init(){ + LayoutInflater.from(context).inflate(R.layout.overlay_slide_up_menu_group, this, true); + + title = findViewById(R.id.slide_up_menu_group_title); + itemContainer = findViewById(R.id.slide_up_menu_group_items); + } + + fun selectItem(obj: Any?): Boolean { + var didSelect = false; + for(item in items) { + item.setOptionSelected(item.itemTag == obj); + didSelect = didSelect || item.itemTag == obj; + } + return didSelect; + } + + private fun addItems(items: List) { + for (item in items) { + item.setParentClickListener { parentClickListener?.invoke() } + itemContainer.addView(item); + } + } + + fun setParentClickListener(listener: (()->Unit)?) { + parentClickListener = listener; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuItem.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuItem.kt new file mode 100644 index 00000000..0cb71490 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuItem.kt @@ -0,0 +1,68 @@ +package com.futo.platformplayer.views.overlays.slideup + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.ImageView +import android.widget.RelativeLayout +import android.widget.TextView +import com.futo.platformplayer.R + +class SlideUpMenuItem : RelativeLayout { + + private lateinit var _root: RelativeLayout; + private lateinit var _image: ImageView; + private lateinit var _text: TextView; + private lateinit var _subtext: TextView; + + var selectedOption: Boolean = false; + + private var _parentClickListener: (()->Unit)? = null; + + var itemTag: Any? = null; + + constructor(context: Context, attrs: AttributeSet? = null): super(context, attrs) { + init(); + } + + constructor(context: Context, imageRes: Int = 0, mainText: String, subText: String = "", tag: Any, call: (()->Unit)? = null, invokeParent: Boolean = true): super(context){ + init(); + _image.setImageResource(imageRes); + _text.text = mainText; + _subtext.text = subText; + this.itemTag = tag; + + if (call != null) { + setOnClickListener { + call.invoke(); + if(invokeParent) + _parentClickListener?.invoke(); + }; + } + } + + private fun init(){ + LayoutInflater.from(context).inflate(R.layout.overlay_slide_up_menu_option, this, true); + + _root = findViewById(R.id.slide_up_menu_item_root); + _image = findViewById(R.id.slide_up_menu_item_image); + _text = findViewById(R.id.slide_up_menu_item_text); + _subtext = findViewById(R.id.slide_up_menu_item_subtext); + + setOptionSelected(false); + } + + fun setOptionSelected(isSelected: Boolean): Boolean { + selectedOption = isSelected; + if (!isSelected) { + _root.setBackgroundResource(R.drawable.background_slide_up_option); + } else { + _root.setBackgroundResource(R.drawable.background_slide_up_option_selected); + } + return isSelected; + } + + fun setParentClickListener(listener: (()->Unit)?) { + _parentClickListener = listener; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuOverlay.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuOverlay.kt new file mode 100644 index 00000000..ad94006f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuOverlay.kt @@ -0,0 +1,177 @@ +package com.futo.platformplayer.views.overlays.slideup + +import android.animation.Animator +import android.animation.AnimatorSet +import android.animation.ObjectAnimator +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.RelativeLayout +import android.widget.TextView +import androidx.core.animation.doOnEnd +import androidx.core.view.children +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event0 + +class SlideUpMenuOverlay : RelativeLayout { + + private var _container: ViewGroup? = null; + private lateinit var _textTitle: TextView; + private lateinit var _textCancel: TextView; + private lateinit var _textOK: TextView; + private lateinit var _viewBackground: View; + private lateinit var _viewOverlayContainer: LinearLayout; + private lateinit var _viewContainer: LinearLayout; + private var _animated: Boolean = true; + + private lateinit var _groupItems: List; + + var isVisible = false + private set; + + val onOK = Event0(); + val onCancel = Event0(); + + constructor(context: Context, attrs: AttributeSet? = null): super(context, attrs) { + init(false, null); + _groupItems = listOf(); + } + + constructor(context: Context, parent: ViewGroup, titleText: String, okText: String?, animated: Boolean, items: List): super(context){ + init(animated, okText); + _container = parent; + if(!_container!!.children.contains(this)) { + _container!!.removeAllViews(); + _container!!.addView(this); + } + _textTitle.text = titleText; + _groupItems = items; + + setItems(items); + } + + + constructor(context: Context, parent: ViewGroup, titleText: String, okText: String?, animated: Boolean, vararg items: View?) + : this(context, parent, titleText, okText, animated, items.filterNotNull().toList()) + + fun setItems(items: List) { + _viewContainer.removeAllViews(); + + for (item in items) { + _viewContainer.addView(item); + + if (item is SlideUpMenuGroup) + item.setParentClickListener { hide() }; + else if(item is SlideUpMenuItem) + item.setParentClickListener { hide() }; + + } + } + + private fun init(animated: Boolean, okText: String?){ + LayoutInflater.from(context).inflate(R.layout.overlay_slide_up_menu, this, true); + + _animated = animated; + + _textTitle = findViewById(R.id.overlay_slide_up_menu_title); + _viewContainer = findViewById(R.id.overlay_slide_up_menu_items); + _textCancel = findViewById(R.id.overlay_slide_up_menu_cancel); + _textOK = findViewById(R.id.overlay_slide_up_menu_ok); + setOk(okText); + + _viewBackground = findViewById(R.id.overlay_slide_up_menu_background); + _viewOverlayContainer = findViewById(R.id.overlay_slide_up_menu_ovelay_container); + + _viewBackground.setOnClickListener { + onCancel.emit(); + hide(); + }; + + _textCancel.setOnClickListener { + onCancel.emit(); + hide(); + }; + } + + fun setOk(textOk: String?) { + if (textOk == null) + _textOK.visibility = View.GONE; + else { + _textOK.text = textOk; + _textOK.setOnClickListener { + onOK.emit(); + }; + _textOK.visibility = View.VISIBLE; + } + } + + fun selectOption(groupTag: Any?, itemTag: Any?, multiSelect: Boolean = false, toggle: Boolean = false): Boolean { + var didSelect = false; + for(view in _groupItems) { + if(view is SlideUpMenuGroup && view.groupTag == groupTag) + didSelect = didSelect || view.selectItem(itemTag); + } + if(groupTag == null) + for(item in _groupItems) + if(item is SlideUpMenuItem) { + if(multiSelect) { + if(item.itemTag == itemTag) + didSelect = didSelect || item.setOptionSelected(!toggle || !item.selectedOption); + } + else + didSelect = didSelect || item.setOptionSelected(item.itemTag == itemTag && (!toggle || !item.selectedOption)); + } + return didSelect; + } + + fun show(){ + isVisible = true; + _container?.post { + _container?.visibility = View.VISIBLE; + _container?.bringToFront(); + } + + if (_animated) { + _viewOverlayContainer.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + _viewOverlayContainer.translationY = _viewOverlayContainer.measuredHeight.toFloat() + _viewBackground.alpha = 0f; + + val animations = arrayListOf(); + animations.add(ObjectAnimator.ofFloat(_viewBackground, "alpha", 0.0f, 1.0f).setDuration(500)); + animations.add(ObjectAnimator.ofFloat(_viewOverlayContainer, "translationY", _viewOverlayContainer.measuredHeight.toFloat(), 0.0f).setDuration(500)); + + val animatorSet = AnimatorSet(); + animatorSet.playTogether(animations); + animatorSet.start(); + } else { + _viewBackground.alpha = 1.0f; + _viewOverlayContainer.translationY = 0.0f; + } + } + + fun hide(animate: Boolean = true){ + isVisible = false; + if (_animated && animate) { + val animations = arrayListOf(); + animations.add(ObjectAnimator.ofFloat(_viewBackground, "alpha", 1.0f, 0.0f).setDuration(500)); + animations.add(ObjectAnimator.ofFloat(_viewOverlayContainer, "translationY", 0.0f, _viewOverlayContainer.measuredHeight.toFloat()).setDuration(500)); + + val animatorSet = AnimatorSet(); + animatorSet.doOnEnd { + _container?.post { + _container?.visibility = View.GONE; + } + }; + + animatorSet.playTogether(animations); + animatorSet.start(); + } else { + _viewBackground.alpha = 0.0f; + _viewOverlayContainer.translationY = _viewOverlayContainer.measuredHeight.toFloat(); + _container?.visibility = View.GONE; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuRadioGroup.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuRadioGroup.kt new file mode 100644 index 00000000..43858dd2 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuRadioGroup.kt @@ -0,0 +1,37 @@ +package com.futo.platformplayer.views.overlays.slideup + +import android.content.Context +import android.view.LayoutInflater +import android.view.inputmethod.InputMethodManager +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.views.others.RadioGroupView + +class SlideUpMenuRadioGroup : LinearLayout { + private lateinit var _root: LinearLayout; + private lateinit var _radioGroupView: RadioGroupView; + private lateinit var _inputMethodManager: InputMethodManager; + private lateinit var _textHeader: TextView; + + val selectedOptions: List get() = _radioGroupView.selectedOptions; + val onSelectedChange = Event1>(); + val onSelectedPairChange = Event1>(); + + constructor(context: Context, name: String, options: List>, initiallySelectedOptions: List, multiSelect: Boolean, atLeastOne: Boolean): super(context) { + init(name, options, initiallySelectedOptions, multiSelect, atLeastOne); + } + + private fun init(name: String, options: List>, initiallySelectedOptions: List, multiSelect: Boolean, atLeastOne: Boolean) { + LayoutInflater.from(context).inflate(R.layout.overlay_slide_up_menu_radio_group, this, true); + + _textHeader = findViewById(R.id.text_header); + _textHeader.text = name; + + _root = findViewById(R.id.slide_up_menu_text_input_root); + _radioGroupView = findViewById(R.id.radio_group); + _radioGroupView.setOptions(options, initiallySelectedOptions, multiSelect, atLeastOne); + _radioGroupView.onSelectedChange.subscribe { onSelectedChange.emit(it) }; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuTextInput.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuTextInput.kt new file mode 100644 index 00000000..ca4a39a9 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuTextInput.kt @@ -0,0 +1,57 @@ +package com.futo.platformplayer.views.overlays.slideup + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.view.inputmethod.InputMethodManager +import android.widget.EditText +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.RelativeLayout +import android.widget.TextView +import com.futo.platformplayer.R +import org.w3c.dom.Text + +class SlideUpMenuTextInput : LinearLayout { + private lateinit var _root: LinearLayout; + private lateinit var _editText: EditText; + private lateinit var _inputMethodManager: InputMethodManager; + + val text: String get() = _editText.text.toString(); + + constructor(context: Context, attrs: AttributeSet? = null): super(context, attrs) { + init(); + } + + constructor(context: Context, name: String? = null): super(context) { + init(name); + } + + private fun init(name: String? = null){ + _inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager; + + LayoutInflater.from(context).inflate(R.layout.overlay_slide_up_menu_text_input, this, true); + + _root = findViewById(R.id.slide_up_menu_text_input_root); + _editText = findViewById(R.id.edit_text); + + if (name != null) { + _editText.hint = name; + } + } + + fun activate() { + _editText.requestFocus(); + _inputMethodManager.showSoftInput(_editText, 0); + } + + fun deactivate() { + _editText.clearFocus(); + _inputMethodManager.hideSoftInputFromWindow(_editText.windowToken, 0); + } + + fun clear() { + _editText.text.clear(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuTitle.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuTitle.kt new file mode 100644 index 00000000..25135cfa --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuTitle.kt @@ -0,0 +1,27 @@ +package com.futo.platformplayer.views.overlays.slideup + +import android.content.Context +import android.util.AttributeSet +import android.util.TypedValue +import android.view.Gravity +import android.view.LayoutInflater +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event1 + +class SlideUpMenuTitle : LinearLayout { + private val _title: TextView; + + constructor(context: Context, attrs: AttributeSet? = null): super(context, attrs) { + LayoutInflater.from(context).inflate(R.layout.overlay_slide_up_menu_title, this, true); + + _title = findViewById(R.id.slide_up_menu_group_title); + } + + fun setTitle(title: String) { + _title.text = title; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/pills/PillButton.kt b/app/src/main/java/com/futo/platformplayer/views/pills/PillButton.kt new file mode 100644 index 00000000..014a24b4 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/pills/PillButton.kt @@ -0,0 +1,37 @@ +package com.futo.platformplayer.views.pills + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event0 + +class PillButton : LinearLayout { + val icon: ImageView; + val text: TextView; + val onClick = Event0(); + + constructor(context : Context, attrs : AttributeSet) : super(context, attrs) { + LayoutInflater.from(context).inflate(R.layout.pill_button, this, true); + icon = findViewById(R.id.pill_icon); + text = findViewById(R.id.pill_text); + + val attrArr = context.obtainStyledAttributes(attrs, R.styleable.PillButton, 0, 0); + val attrIconRef = attrArr.getResourceId(R.styleable.PillButton_pillIcon, -1); + if(attrIconRef != -1) + icon.setImageResource(attrIconRef); + else + icon.visibility = View.GONE; + + val attrText = attrArr.getText(R.styleable.PillButton_pillText) ?: ""; + text.text = attrText; + + findViewById(R.id.root).setOnClickListener { + onClick.emit(); + }; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/pills/PillRatingLikesDislikes.kt b/app/src/main/java/com/futo/platformplayer/views/pills/PillRatingLikesDislikes.kt new file mode 100644 index 00000000..e0ea140a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/pills/PillRatingLikesDislikes.kt @@ -0,0 +1,145 @@ +package com.futo.platformplayer.views.pills + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.content.ContextCompat +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.models.ratings.IRating +import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes +import com.futo.platformplayer.api.media.models.ratings.RatingLikes +import com.futo.platformplayer.constructs.Event3 +import com.futo.platformplayer.states.StatePolycentric +import com.futo.platformplayer.toHumanNumber +import com.futo.polycentric.core.ProcessHandle + +class PillRatingLikesDislikes : LinearLayout { + private val _textLikes: TextView; + private val _textDislikes: TextView; + private val _seperator: View; + private val _iconLikes: ImageView; + private val _iconDislikes: ImageView; + + private var _likes = 0L; + private var _hasLiked = false; + private var _dislikes = 0L; + private var _hasDisliked = false; + + val onLikeDislikeUpdated = Event3(); + + constructor(context : Context, attrs : AttributeSet?) : super(context, attrs) { + LayoutInflater.from(context).inflate(R.layout.rating_likesdislikes, this, true); + _textLikes = findViewById(R.id.pill_likes); + _textDislikes = findViewById(R.id.pill_dislikes); + _seperator = findViewById(R.id.pill_seperator); + _iconDislikes = findViewById(R.id.pill_dislike_icon); + _iconLikes = findViewById(R.id.pill_like_icon); + + _iconLikes.setOnClickListener { StatePolycentric.instance.requireLogin(context, "Please login to like") { like(it) }; }; + _textLikes.setOnClickListener { StatePolycentric.instance.requireLogin(context, "Please login to like") { like(it) }; }; + _iconDislikes.setOnClickListener { StatePolycentric.instance.requireLogin(context, "Please login to dislike") { dislike(it) }; }; + _textDislikes.setOnClickListener { StatePolycentric.instance.requireLogin(context, "Please login to dislike") { dislike(it) }; }; + } + + fun setRating(rating: IRating, hasLiked: Boolean = false, hasDisliked: Boolean = false) { + when (rating) { + is RatingLikeDislikes -> { + setRating(rating, hasLiked, hasDisliked); + } + is RatingLikes -> { + setRating(rating, hasLiked, hasDisliked); + } + else -> { + throw Exception("Unknown rating type"); + } + } + } + + fun like(processHandle: ProcessHandle) { + if (_hasDisliked) { + _dislikes--; + _hasDisliked = false; + _textDislikes.text = _dislikes.toHumanNumber(); + } + + if (_hasLiked) { + _likes--; + _hasLiked = false; + } else { + _likes++; + _hasLiked = true; + } + + _textLikes.text = _likes.toHumanNumber(); + updateColors(); + onLikeDislikeUpdated.emit(processHandle, _hasLiked, _hasDisliked); + } + + fun dislike(processHandle: ProcessHandle) { + if (_hasLiked) { + _likes--; + _hasLiked = false; + _textLikes.text = _likes.toHumanNumber(); + } + + if (_hasDisliked) { + _dislikes--; + _hasDisliked = false; + } else { + _dislikes++; + _hasDisliked = true; + } + + _textDislikes.text = _dislikes.toHumanNumber(); + updateColors(); + onLikeDislikeUpdated.emit(processHandle, _hasLiked, _hasDisliked); + } + + private fun updateColors() { + if (_hasLiked) { + _textLikes.setTextColor(ContextCompat.getColor(context, R.color.colorPrimary)); + _iconLikes.setColorFilter(ContextCompat.getColor(context, R.color.colorPrimary)); + } else { + _textLikes.setTextColor(ContextCompat.getColor(context, R.color.white)); + _iconLikes.setColorFilter(ContextCompat.getColor(context, R.color.white)); + } + + if (_hasDisliked) { + _textDislikes.setTextColor(ContextCompat.getColor(context, R.color.colorPrimary)); + _iconDislikes.setColorFilter(ContextCompat.getColor(context, R.color.colorPrimary)); + } else { + _textDislikes.setTextColor(ContextCompat.getColor(context, R.color.white)); + _iconDislikes.setColorFilter(ContextCompat.getColor(context, R.color.white)); + } + } + + fun setRating(rating: RatingLikeDislikes, hasLiked: Boolean = false, hasDisliked: Boolean = false) { + _textLikes.text = rating.likes.toHumanNumber(); + _textDislikes.text = rating.dislikes.toHumanNumber(); + _textLikes.visibility = View.VISIBLE; + _textDislikes.visibility = View.VISIBLE; + _seperator.visibility = View.VISIBLE; + _iconDislikes.visibility = View.VISIBLE; + _likes = rating.likes; + _dislikes = rating.dislikes; + _hasLiked = hasLiked; + _hasDisliked = hasDisliked; + updateColors(); + } + fun setRating(rating: RatingLikes, hasLiked: Boolean = false) { + _textLikes.text = rating.likes.toHumanNumber(); + _textLikes.visibility = View.VISIBLE; + _textDislikes.visibility = View.GONE; + _seperator.visibility = View.GONE; + _iconDislikes.visibility = View.GONE; + _likes = rating.likes; + _dislikes = 0; + _hasLiked = hasLiked; + _hasDisliked = false; + updateColors(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/pills/RoundButton.kt b/app/src/main/java/com/futo/platformplayer/views/pills/RoundButton.kt new file mode 100644 index 00000000..cfed052b --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/pills/RoundButton.kt @@ -0,0 +1,61 @@ +package com.futo.platformplayer.views.pills + +import android.content.Context +import android.util.AttributeSet +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.View +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.states.StateApp + +class RoundButton : LinearLayout { + val icon: ImageView; + val text: TextView; + + val onClick = Event0(); + val handler: ((RoundButton)->Unit)?; + + val iconResource: Int; + val tagRef: Any?; + + constructor(context : Context, iconRes: Int, title: String, tag: Any? = null, handler: ((RoundButton)->Unit)? = null) : super(context) { + LayoutInflater.from(context).inflate(R.layout.button_round, this, true); + this.tagRef = tag; + this.handler = handler; + this.iconResource = iconRes; + + icon = findViewById(R.id.pill_icon); + text = findViewById(R.id.pill_text); + + icon.setImageResource(iconRes); + text.text = title; + + icon.setOnClickListener { + onClick.emit(); + if(handler != null) + handler(this@RoundButton); + }; + } + + constructor(context : Context, attrs : AttributeSet) : super(context, attrs) { + LayoutInflater.from(context).inflate(R.layout.button_round, this, true); + tagRef = null; + handler = null; + iconResource = -1; + + icon = findViewById(R.id.pill_icon); + text = findViewById(R.id.pill_text); + + findViewById(R.id.root).setOnClickListener { + onClick.emit(); + }; + } + + companion object { + val WIDTH = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 55f, StateApp.instance.context.resources.displayMetrics); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/pills/RoundButtonGroup.kt b/app/src/main/java/com/futo/platformplayer/views/pills/RoundButtonGroup.kt new file mode 100644 index 00000000..517b44f8 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/pills/RoundButtonGroup.kt @@ -0,0 +1,91 @@ +package com.futo.platformplayer.views.pills + +import android.content.Context +import android.graphics.Color +import android.text.Layout +import android.util.AttributeSet +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import androidx.core.view.children +import com.futo.platformplayer.R + +class RoundButtonGroup : LinearLayout { + var lastWidth = 0; + + private val _lock = Object(); + + private var _buttons: List = listOf(); + private var _numVisible = 0; + + var alwaysShowLastButton = false; + + constructor(context : Context) : super(context) { + orientation = HORIZONTAL; + } + constructor(context : Context, attributes: AttributeSet) : super(context, attributes) { + orientation = HORIZONTAL; + } + + fun getVisibleButtons(): List { + return _buttons.take(_numVisible).toList(); + } + fun getInvisibleButtons(): List { + return _buttons.drop(_numVisible).toList(); + } + + override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { + super.onLayout(changed, l, t, r, b); + val newWidth = width; + if(newWidth != lastWidth) { + lastWidth = newWidth; + setViews(); + } + } + + fun setButtons(vararg buttons: RoundButton) { + _buttons = buttons.toList(); + setViews(); + } + + fun setButtonVisibility(filter: (RoundButton)->Boolean) { + synchronized(_lock) { + for(button in _buttons) + button.visibility = if(filter(button)) View.VISIBLE else View.GONE; + } + } + fun getButtonByTag(tag: Any) : RoundButton? { + synchronized(_lock) { + return _buttons.find { it.tagRef == tag }; + } + } + + private fun setViews() { + if(lastWidth == 0) + return; + + val buttonSpace = ((lastWidth / RoundButton.WIDTH)).toInt(); + _numVisible = buttonSpace - + if(alwaysShowLastButton && buttonSpace < _buttons.size) 1 else 0; + + post { + synchronized(_lock) { + removeAllViews(); + for (i in 0 until buttonSpace) { + if (i < _buttons.size) { + val index = + if (alwaysShowLastButton && i == buttonSpace - 1) _buttons.size - 1 else i; + val button = _buttons[index]; + button.layoutParams = + LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).let { + it.weight = 1f; + return@let it; + }; + addView(button); + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/platform/PlatformIndicator.kt b/app/src/main/java/com/futo/platformplayer/views/platform/PlatformIndicator.kt new file mode 100644 index 00000000..8ef8520c --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/platform/PlatformIndicator.kt @@ -0,0 +1,25 @@ +package com.futo.platformplayer.views.platform + +import android.content.Context +import android.util.AttributeSet +import com.futo.platformplayer.states.StatePlatform + +class PlatformIndicator : androidx.appcompat.widget.AppCompatImageView { + constructor(context : Context, attrs : AttributeSet) : super(context, attrs) { + } + + fun clearPlatform() { + setImageResource(0); + } + fun setPlatformFromClientID(platformType : String?) { + if(platformType == null) + setImageResource(0); + else { + val result = StatePlatform.instance.getPlatformIcon(platformType); + if (result != null) + result.setImageView(this); + else + setImageResource(0); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/platform/PlatformLinkView.kt b/app/src/main/java/com/futo/platformplayer/views/platform/PlatformLinkView.kt new file mode 100644 index 00000000..8a685dbd --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/platform/PlatformLinkView.kt @@ -0,0 +1,55 @@ +package com.futo.platformplayer.views.platform + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.content.ContextCompat.startActivity +import com.futo.platformplayer.R +import com.futo.platformplayer.states.StatePlatform +import com.futo.polycentric.core.ClaimType + + +class PlatformLinkView : LinearLayout { + private var _imagePlatform: ImageView; + private var _textName: TextView; + private var _url: String? = null; + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + LayoutInflater.from(context).inflate(R.layout.view_platform_link, this, true); + + _imagePlatform = findViewById(R.id.image_platform); + _textName = findViewById(R.id.text_name); + + val root: LinearLayout = findViewById(R.id.root); + root.setOnClickListener { + val i = Intent(Intent.ACTION_VIEW); + val uri = Uri.parse(_url); + + //TODO: Check if it is OK that this is commented + /*if (uri.host != null && uri.host!!.endsWith("youtube.com") && uri.path != null && uri.path!!.startsWith("/redirect")) { + val redirectUrl = uri.getQueryParameter("q"); + i.data = Uri.parse(redirectUrl); + } else {*/ + i.data = uri; + //} + startActivity(context, i, null); + }; + } + + fun setPlatform(name: String, url: String) { + val icon = StatePlatform.instance.getClientOrNullByUrl(url)?.icon; + if (icon != null) { + icon.setImageView(_imagePlatform, R.drawable.ic_web_white); + } else { + _imagePlatform.setImageResource(R.drawable.ic_web_white); + } + + _textName.text = name; + _url = url; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/segments/CommentsList.kt b/app/src/main/java/com/futo/platformplayer/views/segments/CommentsList.kt new file mode 100644 index 00000000..41c38b72 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/segments/CommentsList.kt @@ -0,0 +1,189 @@ +package com.futo.platformplayer.views.segments + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.R +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment +import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails +import com.futo.platformplayer.api.media.structures.IAsyncPager +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.fragment.mainactivity.main.ChannelFragment +import com.futo.platformplayer.views.adapters.CommentViewHolder +import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader + +class CommentsList : ConstraintLayout { + private val _llmReplies: LinearLayoutManager; + private val _taskLoadComments = if(!isInEditMode) TaskHandler IPager, IPager>(StateApp.instance.scopeGetter, { it(); }) + .success { pager -> onCommentsLoaded(pager); } + .exception { + Logger.w(ChannelFragment.TAG, "Failed to load comments.", it); + UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, ::fetchComments); + } else TaskHandler(IPlatformVideoDetails::class.java, StateApp.instance.scopeGetter); + + private var _nextPageHandler: TaskHandler, List> = TaskHandler, List>(StateApp.instance.scopeGetter, { + if (it is IAsyncPager<*>) + it.nextPageAsync(); + else + it.nextPage(); + + return@TaskHandler it.getResults(); + }).success { + onNextPageLoaded(it); + }.exception { + Logger.w(TAG, "Failed to load next page.", it); + UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadNextPage() }); + }; + + private val _scrollListener = object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy); + onScrolled(); + } + }; + + private var _loader: (suspend () -> IPager)? = null; + private val _adapterComments: InsertedViewAdapterWithLoader; + private val _recyclerComments: RecyclerView; + private val _comments: ArrayList = arrayListOf(); + private var _commentsPager: IPager? = null; + private var _loading = false; + private val _prependedView: FrameLayout; + private var _readonly: Boolean = false; + + var onClick = Event1(); + var onCommentsLoaded = Event1(); + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + LayoutInflater.from(context).inflate(R.layout.view_comments_list, this, true); + + _recyclerComments = findViewById(R.id.recycler_comments); + + _prependedView = FrameLayout(context); + _prependedView.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT); + + _adapterComments = InsertedViewAdapterWithLoader(context, arrayListOf(_prependedView), arrayListOf(), + childCountGetter = { _comments.size }, + childViewHolderBinder = { viewHolder, position -> viewHolder.bind(_comments[position], _readonly); }, + childViewHolderFactory = { viewGroup, _ -> + val holder = CommentViewHolder(viewGroup); + holder.onClick.subscribe { c -> onClick.emit(c) }; + return@InsertedViewAdapterWithLoader holder; + } + ); + + _llmReplies = LinearLayoutManager(context); + _recyclerComments.layoutManager = _llmReplies; + _recyclerComments.adapter = _adapterComments; + _recyclerComments.addOnScrollListener(_scrollListener); + } + + fun addComment(comment: IPlatformComment) { + _comments.add(0, comment); + _adapterComments.notifyItemRangeInserted(_adapterComments.childToParentPosition(0), 1); + } + + fun setPrependedView(view: View) { + _prependedView.removeAllViews(); + _prependedView.addView(view); + } + + private fun onScrolled() { + val visibleItemCount = _recyclerComments.childCount; + val firstVisibleItem = _llmReplies.findFirstVisibleItemPosition(); + val visibleThreshold = 15; + if (!_loading && firstVisibleItem + visibleItemCount + visibleThreshold >= _comments.size) { + loadNextPage(); + } + } + + private fun loadNextPage() { + val pager: IPager = _commentsPager ?: return; + + if(pager.hasMorePages()) { + setLoading(true); + _nextPageHandler.run(pager); + } + } + + private fun onNextPageLoaded(comments: List) { + setLoading(false); + + if (comments.isEmpty()) { + return; + } + + val posBefore = _comments.size; + _comments.addAll(comments); + _adapterComments.notifyItemRangeInserted(_adapterComments.childToParentPosition(posBefore), comments.size); + } + + private fun onCommentsLoaded(pager: IPager) { + setLoading(false); + + _comments.addAll(pager.getResults()); + _adapterComments.notifyDataSetChanged(); + _commentsPager = pager; + onCommentsLoaded.emit(_comments.size); + } + + fun load(readonly: Boolean, loader: suspend () -> IPager) { + cancel(); + + _readonly = readonly; + setLoading(true); + _comments.clear(); + _commentsPager = null; + _adapterComments.notifyDataSetChanged(); + + _loader = loader; + fetchComments(); + } + + private fun setLoading(loading: Boolean) { + if (_loading == loading) { + return; + } + + _loading = loading; + _adapterComments.setLoading(loading); + } + + private fun fetchComments() { + val loader = _loader ?: return; + _taskLoadComments.run(loader); + } + + fun clear() { + cancel(); + _comments.clear(); + _commentsPager = null; + _adapterComments.notifyDataSetChanged(); + } + + fun cancel() { + _taskLoadComments.cancel(); + _nextPageHandler.cancel(); + } + + fun replaceComment(c: PolycentricPlatformComment, newComment: PolycentricPlatformComment) { + val index = _comments.indexOf(c); + _comments[index] = newComment; + _adapterComments.notifyItemChanged(_adapterComments.childToParentPosition(index)); + } + + companion object { + private const val TAG = "CommentsList"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/sources/SourceHeaderView.kt b/app/src/main/java/com/futo/platformplayer/views/sources/SourceHeaderView.kt new file mode 100644 index 00000000..612b7168 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/sources/SourceHeaderView.kt @@ -0,0 +1,91 @@ +package com.futo.platformplayer.views.sources + +import android.content.Context +import android.content.Intent +import android.graphics.Color +import android.net.Uri +import android.util.AttributeSet +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.states.StatePlugins +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig + +class SourceHeaderView : LinearLayout { + private val _sourceImage: ImageView; + private val _sourceTitle: TextView; + private val _sourceBy: TextView; + private val _sourceAuthorID: TextView; + private val _sourceDescription: TextView; + + private val _sourceVersion: TextView; + private val _sourceRepositoryUrl: TextView; + private val _sourceScriptUrl: TextView; + + private var _config : SourcePluginConfig? = null; + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + inflate(context, R.layout.view_source_header, this); + + _sourceImage = findViewById(R.id.source_image); + _sourceTitle = findViewById(R.id.source_title); + _sourceBy = findViewById(R.id.source_by); + _sourceAuthorID = findViewById(R.id.source_author_id); + _sourceDescription = findViewById(R.id.source_description); + + _sourceVersion = findViewById(R.id.source_version); + _sourceRepositoryUrl = findViewById(R.id.source_repo); + _sourceScriptUrl = findViewById(R.id.source_script); + + _sourceBy.setOnClickListener { + if(!_config?.authorUrl.isNullOrEmpty()) + context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(_config?.authorUrl))); + } + _sourceRepositoryUrl.setOnClickListener { + if(!_config?.repositoryUrl.isNullOrEmpty()) + context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(_config?.repositoryUrl))); + }; + _sourceScriptUrl.setOnClickListener { + if(!_config?.absoluteScriptUrl.isNullOrEmpty()) + context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(_config?.absoluteScriptUrl))); + }; + } + + fun loadConfig(config: SourcePluginConfig) { + _config = config; + + val loadedIcon = StatePlugins.instance.getPluginIconOrNull(config.id); + if(loadedIcon != null) + loadedIcon.setImageView(_sourceImage); + else + Glide.with(_sourceImage) + .load(config.absoluteIconUrl) + .into(_sourceImage); + + _sourceTitle.text = config.name; + _sourceBy.text = config.author + _sourceDescription.text = config.description; + _sourceVersion.text = config.version.toString(); + _sourceScriptUrl.text = config.absoluteScriptUrl; + _sourceRepositoryUrl.text = config.repositoryUrl; + _sourceAuthorID.text = ""; + + if(!config.authorUrl.isNullOrEmpty()) + _sourceBy.setTextColor(resources.getColor(R.color.colorPrimary)); + else + _sourceBy.setTextColor(Color.WHITE); + } + + fun clear() { + _config = null; + _sourceTitle.text = ""; + _sourceBy.text = "" + _sourceDescription.text = ""; + _sourceVersion.text = ""; + _sourceScriptUrl.text = ""; + _sourceRepositoryUrl.text = ""; + _sourceAuthorID.text = ""; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/sources/SourceInfoView.kt b/app/src/main/java/com/futo/platformplayer/views/sources/SourceInfoView.kt new file mode 100644 index 00000000..ec04f7ec --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/sources/SourceInfoView.kt @@ -0,0 +1,55 @@ +package com.futo.platformplayer.views.sources + +import android.content.Context +import android.graphics.Color +import android.util.AttributeSet +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.views.others.BulletPointView + +class SourceInfoView : LinearLayout { + val image: ImageView; + val textTitle: TextView; + val textDescription: TextView; + val bulletPoints: LinearLayout; + + var onClick = Event1>(); + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + inflate(context, R.layout.view_source_info, this); + image = findViewById(R.id.icon); + textTitle = findViewById(R.id.title); + textDescription = findViewById(R.id.description); + bulletPoints = findViewById(R.id.bullet_points); + bulletPoints.removeAllViews(); + } + constructor(context: Context, iconId: Int, title: String, description: String, points: List = listOf(), isLinks: Boolean = false) : super(context) { + inflate(context, R.layout.view_source_info, this); + image = findViewById(R.id.icon); + textTitle = findViewById(R.id.title); + textDescription = findViewById(R.id.description); + bulletPoints = findViewById(R.id.bullet_points); + + image.setImageResource(iconId); + textTitle.text = title; + textDescription.text = description; + + val primaryColor = resources.getColor(R.color.colorPrimary); + + bulletPoints.removeAllViews(); + for(point in points) { + bulletPoints.addView( + BulletPointView(context) + .withText(point) + .withTextColor(if(!isLinks) Color.WHITE else primaryColor)); + } + } + + fun withDescriptionColor(color: Int) : SourceInfoView { + textDescription.setTextColor(color); + return this; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/sources/SourceUnderConstructionView.kt b/app/src/main/java/com/futo/platformplayer/views/sources/SourceUnderConstructionView.kt new file mode 100644 index 00000000..144db1cf --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/sources/SourceUnderConstructionView.kt @@ -0,0 +1,29 @@ +package com.futo.platformplayer.views.sources + +import android.content.Context +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.R +import com.futo.platformplayer.models.ImageVariable + +class SourceUnderConstructionView : LinearLayout { + private val _imageSource: ImageView; + private val _textSource: TextView; + private val _textSourceSubtitle: TextView; + + private val _buttonAdd: LinearLayout; + + constructor(context: Context, name: String, logo: ImageVariable): super(context) { + inflate(context, R.layout.list_source_construction, this); + + _imageSource = findViewById(R.id.image_source); + _textSource = findViewById(R.id.text_source); + _textSourceSubtitle = findViewById(R.id.text_source_subtitle); + _buttonAdd = findViewById(R.id.button_add); + + + logo.setImageView(_imageSource); + _textSource.text = name; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/subscriptions/SubscribeButton.kt b/app/src/main/java/com/futo/platformplayer/views/subscriptions/SubscribeButton.kt new file mode 100644 index 00000000..c0c7f6e4 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/subscriptions/SubscribeButton.kt @@ -0,0 +1,123 @@ +package com.futo.platformplayer.views.subscriptions + +import android.content.Context +import android.graphics.drawable.Animatable +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StateSubscriptions +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel + +class SubscribeButton : LinearLayout { + private val _root: FrameLayout; + private val _textSubscribe: TextView; + private val _channelLoader: ImageView; + + var channel : IPlatformChannel? = null + private set; + var url : String? = null + private set; + + private var _isSubscribed: Boolean = false; + + private val _subscribeTask = if (!isInEditMode) { + TaskHandler(StateApp.instance.scopeGetter, StatePlatform.instance::getChannelLive).success(::handleSubscribe) + } else { null }; + + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + LayoutInflater.from(context).inflate(R.layout.button_subscribe, this, true); + + _textSubscribe = findViewById(R.id.text_subscribe); + _channelLoader = findViewById(R.id.channel_loader); + + _root = findViewById(R.id.root); + _root.visibility = View.INVISIBLE; + _root.setOnClickListener { + if (channel == null && url == null) + return@setOnClickListener; + + var isSubscribed = if(channel != null) + StateSubscriptions.instance.isSubscribed(channel!!) + else StateSubscriptions.instance.isSubscribed(url!!) + + if (isSubscribed) + handleUnSubscribe(channel?.url ?: url!!); + else { + if (channel != null) + handleSubscribe(channel!!); + else if (url != null) { + setIsLoading(true); + _subscribeTask?.run(url!!); + } + } + }; + + setIsLoading(false); + } + + private fun handleSubscribe(channel: IPlatformChannel) { + setIsLoading(false); + StateSubscriptions.instance.addSubscription(channel); + UIDialogs.toast(context, "Subscribed to ${channel.name}"); + setIsSubscribed(true); + } + private fun handleUnSubscribe(url: String) { + setIsLoading(false); + val removed = StateSubscriptions.instance.removeSubscription(url); + if (removed != null) + UIDialogs.toast(context, "Unsubscribed from ${removed!!.channel.name}"); + setIsSubscribed(false); + } + + fun setSubscribeChannel(url: String) { + this.channel = null; + this.url = url; + setIsSubscribed(StateSubscriptions.instance.isSubscribed(url)); + } + fun setSubscribeChannel(channel: IPlatformChannel) { + this.channel = channel; + this.url = null; + setIsSubscribed(StateSubscriptions.instance.isSubscribed(channel)); + } + + private fun setIsLoading(isLoading: Boolean) { + if (isLoading) { + _channelLoader.visibility = View.VISIBLE; + (_channelLoader.drawable as Animatable?)?.start(); + } else { + (_channelLoader.drawable as Animatable?)?.stop(); + _channelLoader.visibility = View.GONE; + } + } + + private fun setIsSubscribed(isSubcribed: Boolean) { + val url = this.channel?.url ?: this.url; + if (url != null) { + if (isSubcribed) { + _textSubscribe.text = resources.getString(R.string.unsubscribe); + _root.setBackgroundResource(R.drawable.background_button_accent); + } + else { + _textSubscribe.text = resources.getString(R.string.subscribe); + _root.setBackgroundResource(R.drawable.background_button_primary); + } + _root.visibility = VISIBLE; + } + else + _root.visibility = INVISIBLE; + + _isSubscribed = isSubcribed; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/subscriptions/SubscriptionBar.kt b/app/src/main/java/com/futo/platformplayer/views/subscriptions/SubscriptionBar.kt new file mode 100644 index 00000000..452f311f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/subscriptions/SubscriptionBar.kt @@ -0,0 +1,67 @@ +package com.futo.platformplayer.views.subscriptions + +import android.content.Context +import android.util.AttributeSet +import android.widget.LinearLayout +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.models.channels.SerializedChannel +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.models.Subscription +import com.futo.platformplayer.states.StateSubscriptions +import com.futo.platformplayer.views.AnyAdapterView +import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny +import com.futo.platformplayer.views.others.ToggleTagView +import com.futo.platformplayer.views.adapters.viewholders.SubscriptionBarViewHolder + +class SubscriptionBar : LinearLayout { + private var _adapterView: AnyAdapterView? = null; + private val _tagsContainer: LinearLayout; + + val onClickChannel = Event1(); + + + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + inflate(context, R.layout.view_subscription_bar, this); + + val subscriptions = StateSubscriptions.instance.getSubscriptions(); + _adapterView = findViewById(R.id.recycler_creators).asAny(subscriptions, orientation = RecyclerView.HORIZONTAL) { + it.onClick.subscribe { c -> + onClickChannel.emit(c.channel); + }; + }; + _tagsContainer = findViewById(R.id.container_tags); + } + + + fun setToggles(vararg buttons: Toggle) { + _tagsContainer.removeAllViews(); + for(button in buttons) { + _tagsContainer.addView(ToggleTagView(context).apply { + this.setInfo(button.name, button.isActive); + this.onClick.subscribe { button.action(it); }; + }); + } + } + + class Toggle { + val name: String; + val icon: Int; + val action: (Boolean)->Unit; + val isActive: Boolean; + + constructor(name: String, icon: Int, isActive: Boolean = false, action: (Boolean)->Unit) { + this.name = name; + this.icon = icon; + this.action = action; + this.isActive = isActive; + } + constructor(name: String, isActive: Boolean = false, action: (Boolean)->Unit) { + this.name = name; + this.icon = 0; + this.action = action; + this.isActive = isActive; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoThumbnailPlayer.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoThumbnailPlayer.kt new file mode 100644 index 00000000..e8ef2e6f --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoThumbnailPlayer.kt @@ -0,0 +1,118 @@ +package com.futo.platformplayer.views.video + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.ImageButton +import android.widget.LinearLayout +import android.widget.TextView +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor +import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource +import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails +import com.futo.platformplayer.helpers.VideoHelper +import com.futo.platformplayer.video.PlayerManager +import com.google.android.exoplayer2.ui.PlayerControlView +import com.google.android.exoplayer2.ui.StyledPlayerView + + +class FutoThumbnailPlayer : FutoVideoPlayerBase { + companion object { + private const val TAG = "FutoThumbnailVideoPlayer" + private const val PLAYER_STATE_NAME : String = "ThumbnailPlayer"; + } + + //Views + private val videoView : StyledPlayerView; + private val videoControls : PlayerControlView; + private val buttonMute : ImageButton; + private val buttonUnMute : ImageButton; + + private val textDurationInverse : TextView; + private val containerDuration : LinearLayout; + private val containerLive : LinearLayout; + + //Events + private val _evMuteChanged = mutableListOf<(FutoThumbnailPlayer, Boolean)->Unit>(); + + + constructor(context : Context, attrs: AttributeSet? = null) : super(PLAYER_STATE_NAME, context, attrs) { + LayoutInflater.from(context).inflate(R.layout.thumbnail_video_view, this, true); + + videoView = findViewById(R.id.video_player); + videoControls = findViewById(R.id.video_player_controller); + buttonMute = videoControls.findViewById(R.id.thumbnail_player_mute); + buttonUnMute = videoControls.findViewById(R.id.thumbnail_player_unmute); + textDurationInverse = videoControls.findViewById(R.id.exo_duration_inverse); + containerDuration = videoControls.findViewById(R.id.exo_duration_container); + containerLive = videoControls.findViewById(R.id.exo_live_container); + + videoControls.setProgressUpdateListener { position, bufferedPosition -> + if(position < 0) + textDurationInverse.visibility = View.INVISIBLE; + else + textDurationInverse.visibility = View.VISIBLE; + val newText = Math.max(0, ((exoPlayer?.player?.duration ?: 0) - position)).toHumanTime(true); + if(newText != "0:00") + textDurationInverse.text = newText; + } + + buttonMute.setOnClickListener { + mute(); + } + buttonUnMute.setOnClickListener { + unmute(); + } + } + + fun setLive(live : Boolean) { + if(live) { + containerDuration.visibility = GONE; + containerLive.visibility = VISIBLE; + } + else { + containerLive.visibility = GONE; + containerDuration.visibility = VISIBLE; + } + } + + fun setPlayer(player : PlayerManager?){ + changePlayer(player); + player?.attach(videoView, PLAYER_STATE_NAME); + videoControls.player = player?.player; + } + fun setTempDuration(duration : Long, ms : Boolean) { + textDurationInverse.text = duration.toHumanTime(ms); + } + + //Controls + fun mute(){ + this.exoPlayer?.setMuted(true); + this.buttonMute.visibility = View.GONE; + this.buttonUnMute.visibility = View.VISIBLE; + _evMuteChanged.forEach { it(this, false) }; + } + fun unmute(){ + this.exoPlayer?.setMuted(false); + this.buttonMute.visibility = View.VISIBLE; + this.buttonUnMute.visibility = View.GONE; + _evMuteChanged.forEach { it(this, true) }; + } + + + //Events + fun setMuteChangedListener(callback : (FutoThumbnailPlayer, Boolean) -> Unit) { + _evMuteChanged.add(callback); + } + + fun setPreview(video: IPlatformVideoDetails) { + val videoSource = VideoHelper.selectBestVideoSource(video.video, Settings.instance.playback.getPreferredPreviewQualityPixelCount(), PREFERED_VIDEO_CONTAINERS); + val audioSource = VideoHelper.selectBestAudioSource(video.video, PREFERED_AUDIO_CONTAINERS, Settings.instance.playback.getPrimaryLanguage(context)); + setSource(videoSource, audioSource,true, false); + } + override fun onSourceChanged(videoSource: IVideoSource?, audioSource: IAudioSource?, resume: Boolean) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt new file mode 100644 index 00000000..603978db --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt @@ -0,0 +1,452 @@ +package com.futo.platformplayer.views.video + +import android.content.Context +import android.content.res.Resources +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.util.Log +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.widget.FrameLayout +import android.widget.ImageButton +import android.widget.RelativeLayout +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.setMargins +import com.futo.platformplayer.* +import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.constructs.Event3 +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StatePlayer +import com.futo.platformplayer.views.behavior.GestureControlView +import com.google.android.exoplayer2.ExoPlayer +import com.google.android.exoplayer2.PlaybackParameters +import com.google.android.exoplayer2.ui.AspectRatioFrameLayout +import com.google.android.exoplayer2.ui.PlayerControlView +import com.google.android.exoplayer2.ui.StyledPlayerView +import com.google.android.exoplayer2.ui.TimeBar +import com.google.android.exoplayer2.video.VideoSize +import kotlin.math.abs + + +class FutoVideoPlayer : FutoVideoPlayerBase { + companion object { + private const val TAG = "FutoVideoPlayer" + private const val PLAYER_STATE_NAME : String = "DetailPlayer"; + } + + var isFullScreen: Boolean = false + private set; + + //Views + private val _root: ConstraintLayout; + private val _videoView: StyledPlayerView; + + val videoControls: PlayerControlView; + private val _videoControls_fullscreen: PlayerControlView; + val background: FrameLayout; + private val _layoutControls: FrameLayout; + val gestureControl: GestureControlView; + + //Custom buttons + private val _control_fullscreen: ImageButton; + private val _control_videosettings: ImageButton; + private val _control_minimize: ImageButton; + private val _control_rotate_lock: ImageButton; + private val _control_cast: ImageButton; + private val _control_play: ImageButton; + private val _time_bar: TimeBar; + + private val _control_fullscreen_fullscreen: ImageButton; + private val _control_videosettings_fullscreen: ImageButton; + private val _control_minimize_fullscreen: ImageButton; + private val _control_rotate_lock_fullscreen: ImageButton; + private val _control_play_fullscreen: ImageButton; + private val _time_bar_fullscreen: TimeBar; + private val _overlay_brightness: FrameLayout; + + private val _title_fullscreen: TextView; + private val _author_fullscreen: TextView; + private var _shouldRestartHideJobOnPlaybackStateChange: Boolean = false; + + private var _lastSourceFit: Int? = null; + private var _originalBottomMargin: Int = 0; + + private var _isControlsLocked: Boolean = false; + + private val _time_bar_listener: TimeBar.OnScrubListener; + + var isFitMode : Boolean = false + private set; + + //Events + val onMinimize = Event1(); + val onVideoSettings = Event1(); + val onToggleFullScreen = Event1(); + val onSourceChanged = Event3(); + val onSourceEnded = Event0(); + + val onVideoClicked = Event0(); + val onTimeBarChanged = Event2(); + + constructor(context: Context, attrs: AttributeSet? = null) : super(PLAYER_STATE_NAME, context, attrs) { + LayoutInflater.from(context).inflate(R.layout.video_view, this, true); + _root = findViewById(R.id.videoview_root); + _videoView = findViewById(R.id.video_player); + + val subs = _videoView.subtitleView; + + videoControls = findViewById(R.id.video_player_controller); + _control_fullscreen = videoControls.findViewById(R.id.exo_fullscreen); + _control_videosettings = videoControls.findViewById(R.id.exo_settings); + _control_minimize = videoControls.findViewById(R.id.exo_minimize); + _control_rotate_lock = videoControls.findViewById(R.id.exo_rotate_lock); + _control_cast = videoControls.findViewById(R.id.exo_cast); + _control_play = videoControls.findViewById(com.google.android.exoplayer2.ui.R.id.exo_play); + _time_bar = videoControls.findViewById(com.google.android.exoplayer2.ui.R.id.exo_progress); + + _videoControls_fullscreen = findViewById(R.id.video_player_controller_fullscreen); + _control_fullscreen_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_fullscreen); + _control_minimize_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_minimize); + _control_videosettings_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_settings); + _control_rotate_lock_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_rotate_lock); + _control_play_fullscreen = videoControls.findViewById(com.google.android.exoplayer2.ui.R.id.exo_play); + _time_bar_fullscreen = _videoControls_fullscreen.findViewById(com.google.android.exoplayer2.ui.R.id.exo_progress); + + _overlay_brightness = findViewById(R.id.overlay_brightness); + + _title_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_title); + _author_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_author); + + background = findViewById(R.id.layout_controls_background); + _layoutControls = findViewById(R.id.layout_controls); + gestureControl = findViewById(R.id.gesture_control); + + _videoView?.videoSurfaceView?.let { gestureControl.setupTouchArea(it, _layoutControls, background); }; + gestureControl.onSeek.subscribe { seekFromCurrent(it); }; + gestureControl.onSoundAdjusted.subscribe { setVolume(it) }; + gestureControl.onToggleFullscreen.subscribe { setFullScreen(!isFullScreen) }; + gestureControl.onBrightnessAdjusted.subscribe { + if (it == 1.0f) { + _overlay_brightness.visibility = View.GONE; + } else { + _overlay_brightness.visibility = View.VISIBLE; + _overlay_brightness.setBackgroundColor(Color.valueOf(0.0f, 0.0f, 0.0f, (1.0f - it)).toArgb()); + } + }; + + if(!isInEditMode) { + _videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM; + val player = StatePlayer.instance.getPlayerOrCreate(context); + //player.modifyState(PLAYER_STATE_NAME, { it.scaleType = MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}) + changePlayer(player); + + videoControls.player = player.player; + _videoControls_fullscreen.player = player.player; + } + + val attrShowSettings = if(attrs != null) + context.obtainStyledAttributes(attrs, R.styleable.FutoVideoPlayer, 0, 0).getBoolean(R.styleable.FutoVideoPlayer_showSettings, false) ?: false; + else false; + val attrShowFullScreen = if(attrs != null) + context.obtainStyledAttributes(attrs, R.styleable.FutoVideoPlayer, 0, 0).getBoolean(R.styleable.FutoVideoPlayer_showFullScreen, false) ?: false; + else false; + val attrShowMinimize = if(attrs != null) + context.obtainStyledAttributes(attrs, R.styleable.FutoVideoPlayer, 0, 0).getBoolean(R.styleable.FutoVideoPlayer_showMinimize, false) ?: false; + else false; + + if (!attrShowSettings) + _control_videosettings.visibility = View.GONE; + if (!attrShowFullScreen) + _control_fullscreen.visibility = View.GONE; + if (!attrShowMinimize) + _control_minimize.visibility = View.GONE; + + _time_bar_listener = object : TimeBar.OnScrubListener { + override fun onScrubStart(timeBar: TimeBar, position: Long) { + gestureControl.restartHideJob(); + } + + override fun onScrubMove(timeBar: TimeBar, position: Long) { + gestureControl.restartHideJob(); + } + + override fun onScrubStop(timeBar: TimeBar, position: Long, canceled: Boolean) { + gestureControl.restartHideJob(); + } + }; + + _time_bar.addListener(_time_bar_listener); + _time_bar_fullscreen.addListener(_time_bar_listener); + + _control_fullscreen.setOnClickListener { + setFullScreen(true); + } + _control_videosettings.setOnClickListener { + onVideoSettings.emit(this); + } + _control_minimize.setOnClickListener { + onMinimize.emit(this); + }; + _control_rotate_lock.setOnClickListener { + StatePlayer.instance.rotationLock = !StatePlayer.instance.rotationLock; + updateRotateLock(); + }; + _control_cast.setOnClickListener { + UIDialogs.showCastingDialog(context); + }; + + _control_minimize_fullscreen.setOnClickListener { + onMinimize.emit(this); + }; + _control_fullscreen_fullscreen.setOnClickListener { + setFullScreen(false); + } + _control_videosettings_fullscreen.setOnClickListener { + onVideoSettings.emit(this); + } + _control_rotate_lock_fullscreen.setOnClickListener { + StatePlayer.instance.rotationLock = !StatePlayer.instance.rotationLock; + updateRotateLock(); + }; + + videoControls.setProgressUpdateListener { position, bufferedPosition -> + onTimeBarChanged.emit(position, bufferedPosition); + } + + if(!isInEditMode) { + gestureControl.hideControls(); + } + } + + fun attachPlayer() { + exoPlayer?.attach(_videoView, PLAYER_STATE_NAME); + } + + fun setArtwork(drawable: Drawable?) { + if (drawable != null) { + _videoView.defaultArtwork = drawable; + _videoView.useArtwork = true; + fitHeight(); + } else { + _videoView.defaultArtwork = null; + _videoView.useArtwork = false; + } + } + + fun hideControls(animated: Boolean) { + gestureControl.hideControls(animated); + } + + fun setMetadata(title: String, author: String) { + _title_fullscreen.text = title; + _author_fullscreen.text = author; + } + + fun setPlaybackRate(playbackRate: Float) { + val exoPlayer = exoPlayer?.player; + Logger.i(TAG, "setPlaybackRate playbackRate=$playbackRate exoPlayer=${exoPlayer}"); + + val param = PlaybackParameters(playbackRate); + exoPlayer?.playbackParameters = param; + } + + fun getPlaybackRate(): Float { + return exoPlayer?.player?.playbackParameters?.speed ?: 1.0f; + } + + fun setFullScreen(fullScreen: Boolean) { + if (isFullScreen == fullScreen) { + return; + } + + if (fullScreen) { + val lp = background.layoutParams as ConstraintLayout.LayoutParams; + lp.bottomMargin = 0; + background.layoutParams = lp; + + gestureControl.hideControls(); + //videoControlsBar.visibility = View.GONE; + _videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT; + fillHeight(); + _videoControls_fullscreen.show(); + videoControls.hide(); + } + else { + val lp = background.layoutParams as ConstraintLayout.LayoutParams; + lp.bottomMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, Resources.getSystem().displayMetrics).toInt(); + background.layoutParams = lp; + + gestureControl.hideControls(); + //videoControlsBar.visibility = View.VISIBLE; + _videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM; + fitHeight(); + videoControls.show(); + _videoControls_fullscreen.hide(); + } + + gestureControl.setFullscreen(fullScreen); + onToggleFullScreen.emit(fullScreen); + isFullScreen = fullScreen; + } + + fun lockControlsAlpha(locked : Boolean) { + if(locked && _isControlsLocked != locked) { + _isControlsLocked = locked; + _layoutControls.visibility = View.GONE; + } + else if(!locked && _isControlsLocked != locked) + _isControlsLocked = locked; + } + + override fun play() { + super.play(); + } + + override fun onVideoSizeChanged(videoSize: VideoSize) { + _lastSourceFit = null; + if(isFullScreen) + fillHeight(); + else if(_root.layoutParams.height != MATCH_PARENT) + fitHeight(videoSize); + } + + override fun beforeSourceChanged() { + super.beforeSourceChanged(); + attachPlayer(); + } + override fun onSourceChanged(videoSource: IVideoSource?, audioSource: IAudioSource?, resume: Boolean) { + onSourceChanged.emit(videoSource, audioSource, resume); + } + + override fun onPlaybackStateChanged(playbackState: Int) { + Logger.i(TAG, "onPlaybackStateChanged $playbackState"); + val timeLeft = abs(position - duration); + + if (playbackState == ExoPlayer.STATE_ENDED) { + if (abs(position - duration) < 2000) { + onSourceEnded.emit(); + } + + _shouldRestartHideJobOnPlaybackStateChange = true; + } else { + setIsReplay(false); + + if (_shouldRestartHideJobOnPlaybackStateChange) { + gestureControl.restartHideJob(); + _shouldRestartHideJobOnPlaybackStateChange = false; + } + } + } + + fun setIsReplay(isReplay: Boolean) { + if (isReplay) { + _control_play.setImageResource(R.drawable.ic_replay); + _control_play_fullscreen.setImageResource(R.drawable.ic_replay); + } else { + _control_play.setImageResource(R.drawable.ic_play_white_nopad); + _control_play_fullscreen.setImageResource(R.drawable.ic_play_white_nopad); + } + } + + //Sizing + fun fitHeight(videoSize : VideoSize? = null){ + Logger.i(TAG, "Video Fit Height"); + if(_originalBottomMargin != 0) { + val layoutParams = _videoView.layoutParams as ConstraintLayout.LayoutParams; + layoutParams.setMargins(0, 0, 0, _originalBottomMargin); + _videoView.layoutParams = layoutParams; + } + + var h = videoSize?.height ?: lastVideoSource?.height ?: exoPlayer?.player?.videoSize?.height ?: 0; + var w = videoSize?.width ?: lastVideoSource?.width ?: exoPlayer?.player?.videoSize?.width ?: 0; + + if(h == 0 && w == 0) { + Logger.i(TAG, "UNKNOWN VIDEO FIT: (videoSize: ${videoSize != null}, player.videoSize: ${exoPlayer?.player?.videoSize != null})"); + w = 1280; + h = 720; + } + + + if(_lastSourceFit == null){ + val metrics = StateApp.instance.displayMetrics ?: resources.displayMetrics; + + val viewWidth = Math.min(metrics.widthPixels, metrics.heightPixels); //TODO: Get parent width. was this.width + val deviceHeight = Math.max(metrics.widthPixels, metrics.heightPixels); + val maxHeight = deviceHeight * 0.6; + + val determinedHeight = if(w > h) + ((h * (viewWidth.toDouble() / w)).toInt().toInt()) + else + ((h * (viewWidth.toDouble() / w)).toInt().toInt()); + _lastSourceFit = determinedHeight; + _lastSourceFit = Math.max(_lastSourceFit!!, 250); + _lastSourceFit = Math.min(_lastSourceFit!!, maxHeight.toInt()); + if((_lastSourceFit ?: 0) < 300 || (_lastSourceFit ?: 0) > viewWidth) { + Log.d(TAG, "WEIRD HEIGHT DETECTED: ${_lastSourceFit}, Width: ${w}, Height: ${h}, VWidth: ${viewWidth}"); + } + if(_lastSourceFit != determinedHeight) + _videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT; + else + _videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM; + } + + val marginBottom = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7f, resources.displayMetrics).toInt(); + val rootParams = RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, _lastSourceFit!! + marginBottom) + rootParams.bottomMargin = marginBottom; + _root.layoutParams = rootParams + isFitMode = true; + } + fun fillHeight(){ + Logger.i(TAG, "Video Fill Height"); + val width = resources.displayMetrics.heightPixels; + val height = resources.displayMetrics.widthPixels; + + val layoutParams = _videoView.layoutParams as ConstraintLayout.LayoutParams; + _originalBottomMargin = if(layoutParams.bottomMargin > 0) layoutParams.bottomMargin else _originalBottomMargin; + layoutParams.setMargins(0); + _videoView.layoutParams = layoutParams; + _videoView.invalidate(); + + val rootParams = RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + _root.layoutParams = rootParams; + _root.invalidate(); + isFitMode = false; + } + + //Animated Calls + fun setEndPadding(value: Float) { + setPadding(0, 0, value.toInt(), 0) + } + + fun updateRotateLock() { + if(!Settings.instance.playback.isAutoRotate()) { + _control_rotate_lock.visibility = View.GONE; + _control_rotate_lock_fullscreen.visibility = View.GONE; + } + else { + _control_rotate_lock.visibility = View.VISIBLE; + _control_rotate_lock_fullscreen.visibility = View.VISIBLE; + } + if(StatePlayer.instance.rotationLock) { + _control_rotate_lock_fullscreen.setImageResource(R.drawable.ic_screen_rotation); + _control_rotate_lock.setImageResource(R.drawable.ic_screen_rotation); + } + else { + _control_rotate_lock_fullscreen.setImageResource(R.drawable.ic_screen_lock_rotation); + _control_rotate_lock.setImageResource(R.drawable.ic_screen_lock_rotation); + } + } + + fun setGestureSoundFactor(soundFactor: Float) { + gestureControl.setSoundFactor(soundFactor); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt new file mode 100644 index 00000000..aac8e853 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt @@ -0,0 +1,522 @@ +package com.futo.platformplayer.views.video + +import android.content.Context +import android.net.Uri +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.api.media.models.streams.VideoMuxedSourceDescriptor +import com.futo.platformplayer.helpers.VideoHelper +import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource +import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails +import com.futo.platformplayer.api.media.platforms.js.models.sources.JSAudioUrlRangeSource +import com.futo.platformplayer.api.media.platforms.js.models.sources.JSHLSManifestAudioSource +import com.futo.platformplayer.api.media.platforms.js.models.sources.JSVideoUrlRangeSource +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.video.PlayerManager +import com.google.android.exoplayer2.* +import com.google.android.exoplayer2.source.MediaSource +import com.google.android.exoplayer2.source.MergingMediaSource +import com.google.android.exoplayer2.source.ProgressiveMediaSource +import com.google.android.exoplayer2.source.SingleSampleMediaSource +import com.google.android.exoplayer2.source.dash.DashMediaSource +import com.google.android.exoplayer2.source.hls.HlsMediaSource +import com.google.android.exoplayer2.text.CueGroup +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector +import com.google.android.exoplayer2.upstream.DefaultDataSource +import com.google.android.exoplayer2.upstream.DefaultHttpDataSource +import com.google.android.exoplayer2.video.VideoSize +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File +import kotlin.math.abs + +abstract class FutoVideoPlayerBase : RelativeLayout { + private val TAG = "FutoVideoPlayerBase" + + private val TEMP_DIRECTORY = StateApp.instance.getTempDirectory(); + + private var _mediaSource: MediaSource? = null; + + var lastVideoSource: IVideoSource? = null + private set; + var lastAudioSource: IAudioSource? = null + private set; + + private var _lastVideoMediaSource: MediaSource? = null; + private var _lastAudioMediaSource: MediaSource? = null; + private var _lastSubtitleMediaSource: MediaSource? = null; + private var _shouldPlaybackRestartOnConnectivity: Boolean = false; + private val _referenceObject = Object(); + + var exoPlayer: PlayerManager? = null + private set; + val exoPlayerStateName: String; + + val playing: Boolean get() = exoPlayer?.player?.playWhenReady ?: false; + val position: Long get() = exoPlayer?.player?.currentPosition ?: 0; + val duration: Long get() = exoPlayer?.player?.duration ?: 0; + + var isAudioMode: Boolean = false + private set; + + val onPlayChanged = Event1(); + val onStateChange = Event1(); + val onPositionDiscontinuity = Event1(); + val onDatasourceError = Event1(); + + private var _didCallSourceChange = false; + private var _lastState: Int = -1; + + private var _targetTrackVideoHeight = -1; + private var _targetTrackAudioBitrate = -1; + + private var _toResume = false; + + private val _playerEventListener = object: Player.Listener { + //TODO: Figure out why this is deprecated, and what the alternative is. + override fun onPlaybackStateChanged(playbackState: Int) { + super.onPlaybackStateChanged(playbackState) + this@FutoVideoPlayerBase.onPlaybackStateChanged(playbackState); + + if(_lastState != playbackState) { + _lastState = playbackState; + onStateChange.emit(playbackState); + } + when(playbackState) { + Player.STATE_READY -> { + if(!_didCallSourceChange) { + _didCallSourceChange = true; + onSourceChanged(lastVideoSource, lastAudioSource, _toResume); + } + } + } + } + + override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) { + super.onPlayWhenReadyChanged(playWhenReady, reason) + onPlayChanged.emit(playWhenReady); + } + + override fun onVideoSizeChanged(videoSize: VideoSize) { + super.onVideoSizeChanged(videoSize) + this@FutoVideoPlayerBase.onVideoSizeChanged(videoSize); + } + + override fun onPositionDiscontinuity(oldPosition: Player.PositionInfo, newPosition: Player.PositionInfo, reason: Int) { + super.onPositionDiscontinuity(oldPosition, newPosition, reason); + onPositionDiscontinuity.emit(newPosition.positionMs); + } + + override fun onCues(cueGroup: CueGroup) { + super.onCues(cueGroup) + Logger.i(TAG, "CUE GROUP: ${cueGroup.cues.firstOrNull()?.text}"); + } + + override fun onPlayerError(error: PlaybackException) { + super.onPlayerError(error); + this@FutoVideoPlayerBase.onPlayerError(error); + } + }; + + constructor(stateName: String, context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0) : super(context, attrs, defStyleAttr, defStyleRes) { + this.exoPlayerStateName = stateName; + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow(); + + Logger.i(TAG, "Attached onConnectionAvailable listener."); + StateApp.instance.onConnectionAvailable.subscribe(_referenceObject) { + Logger.i(TAG, "onConnectionAvailable"); + + val pos = position; + val dur = duration; + if (_shouldPlaybackRestartOnConnectivity && abs(pos - dur) > 2000) { + Logger.i(TAG, "Playback ended due to connection loss, resuming playback since connection is restored."); + exoPlayer?.player?.playWhenReady = true; + exoPlayer?.player?.prepare(); + exoPlayer?.player?.play(); + } + }; + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow(); + + Logger.i(TAG, "Detached onConnectionAvailable listener."); + StateApp.instance.onConnectionAvailable.remove(_referenceObject); + } + + fun switchToVideoMode() { + Logger.i(TAG, "Switching to Video Mode"); + isAudioMode = false; + loadSelectedSources(playing, true); + } + fun switchToAudioMode() { + Logger.i(TAG, "Switching to Audio Mode"); + isAudioMode = true; + loadSelectedSources(playing, true); + } + + fun seekTo(ms: Long) { + exoPlayer?.player?.seekTo(ms); + } + fun seekToEnd(ms: Long = 0) { + val duration = Math.max(exoPlayer?.player?.duration ?: 0, 0); + exoPlayer?.player?.seekTo(Math.max(duration - ms, 0)); + } + fun seekFromCurrent(ms: Long) { + val to = Math.max((exoPlayer?.player?.currentPosition ?: 0) + ms, 0); + exoPlayer?.player?.seekTo(Math.min(to, exoPlayer?.player?.duration ?: to)); + } + + fun changePlayer(newPlayer: PlayerManager?) { + exoPlayer?.modifyState(exoPlayerStateName, {state -> state.listener = null}); + newPlayer?.modifyState(exoPlayerStateName, {state -> state.listener = _playerEventListener}); + exoPlayer = newPlayer; + } + + //TODO: Temporary solution, Implement custom track selector without using constraints + fun selectVideoTrack(height: Int) { + _targetTrackVideoHeight = height; + updateTrackSelector(); + } + fun selectAudioTrack(bitrate: Int) { + _targetTrackAudioBitrate = bitrate; + updateTrackSelector(); + } + private fun updateTrackSelector() { + var builder = DefaultTrackSelector.Parameters.Builder(); + if(builder != null){ + if(_targetTrackVideoHeight > 0) + builder = builder + .setMinVideoSize(0, height - 10) + .setMaxVideoSize(9999, height + 10); + if(_targetTrackAudioBitrate > 0) + builder = builder + .setMaxAudioBitrate(_targetTrackAudioBitrate); + + if(exoPlayer?.player?.trackSelector != null) + exoPlayer!!.player.trackSelector!!.parameters = builder.build(); + } + } + + fun setSource(videoSource: IVideoSource?, audioSource: IAudioSource? = null, play: Boolean = false, keepSubtitles: Boolean = false) { + swapSources(videoSource, audioSource,false, play, keepSubtitles); + } + fun swapSources(videoSource: IVideoSource?, audioSource: IAudioSource?, resume: Boolean = true, play: Boolean = true, keepSubtitles: Boolean = false): Boolean { + swapSourceInternal(videoSource); + swapSourceInternal(audioSource); + if(!keepSubtitles) + _lastSubtitleMediaSource = null; + return loadSelectedSources(play, resume); + } + fun swapSource(videoSource: IVideoSource?, resume: Boolean = true, play: Boolean = true): Boolean { + swapSourceInternal(videoSource); + return loadSelectedSources(play, resume); + } + fun swapSource(audioSource: IAudioSource?, resume: Boolean = true, play: Boolean = true): Boolean { + swapSourceInternal(audioSource); + return loadSelectedSources(play, resume); + } + + fun swapSubtitles(scope: CoroutineScope, subtitles: ISubtitleSource?) { + if(subtitles == null) + clearSubtitles(); + else { + if(SUPPORTED_SUBTITLES.contains(subtitles.format?.lowercase())) { + if (!subtitles.hasFetch) { + _lastSubtitleMediaSource = SingleSampleMediaSource.Factory(DefaultDataSource.Factory(context, DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT))) + .createMediaSource(MediaItem.SubtitleConfiguration.Builder(Uri.parse(subtitles.url)) + .setMimeType(subtitles.format) + .setSelectionFlags(C.SELECTION_FLAG_DEFAULT) + .build(), + C.TIME_UNSET); + loadSelectedSources(true, true); + } else { + scope.launch(Dispatchers.IO) { + try { + val subUri = subtitles.getSubtitlesURI() ?: return@launch; + withContext(Dispatchers.Main) { + try { + _lastSubtitleMediaSource = SingleSampleMediaSource.Factory(DefaultDataSource.Factory(context, DefaultHttpDataSource.Factory().setUserAgent(DEFAULT_USER_AGENT))) + .createMediaSource(MediaItem.SubtitleConfiguration.Builder(subUri) + .setMimeType(subtitles.format) + .setSelectionFlags(C.SELECTION_FLAG_DEFAULT) + .build(), + C.TIME_UNSET); + loadSelectedSources(true, true); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to load selected sources after subtitle download.", e) + } + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to get subtitles URI.", e) + } + } + } + } + else + clearSubtitles(); + } + } + private fun clearSubtitles() { + _lastSubtitleMediaSource = null; + loadSelectedSources(true, true); + } + + + private fun swapSourceInternal(videoSource: IVideoSource?) { + when(videoSource) { + is LocalVideoSource -> swapVideoSourceLocal(videoSource); + is JSVideoUrlRangeSource -> swapVideoSourceUrlRange(videoSource); + is IDashManifestSource -> swapVideoSourceDash(videoSource); + is IHLSManifestSource -> swapVideoSourceHLS(videoSource); + is IVideoUrlSource -> swapVideoSourceUrl(videoSource); + null -> _lastVideoMediaSource = null; + else -> throw IllegalArgumentException("Unsupported video source [${videoSource.javaClass.simpleName}]"); + } + lastVideoSource = videoSource; + } + private fun swapSourceInternal(audioSource: IAudioSource?) { + when(audioSource) { + is LocalAudioSource -> swapAudioSourceLocal(audioSource); + is JSAudioUrlRangeSource -> swapAudioSourceUrlRange(audioSource); + is JSHLSManifestAudioSource -> swapAudioSourceHLS(audioSource); + is IAudioUrlSource -> swapAudioSourceUrl(audioSource); + null -> _lastAudioMediaSource = null; + else -> throw IllegalArgumentException("Unsupported video source [${audioSource.javaClass.simpleName}]"); + } + lastAudioSource = audioSource; + } + + //Video loads + private fun swapVideoSourceLocal(videoSource: LocalVideoSource) { + Logger.i(TAG, "Loading VideoSource [Local]"); + val file = File(videoSource.filePath); + if(!file.exists()) + throw IllegalArgumentException("File for this video does not exist"); + _lastVideoMediaSource = ProgressiveMediaSource.Factory(DefaultDataSource.Factory(context)) + .createMediaSource(MediaItem.fromUri(Uri.fromFile(file))); + } + private fun swapVideoSourceUrlRange(videoSource: JSVideoUrlRangeSource) { + Logger.i(TAG, "Loading JSVideoUrlRangeSource"); + if(videoSource.hasItag) { + //Temporary workaround for Youtube + try { + _lastVideoMediaSource = VideoHelper.convertItagSourceToChunkedDashSource(videoSource); + if(_lastVideoMediaSource == null) + throw java.lang.IllegalStateException("Dash manifest workaround failed"); + return; + } + //If it fails to create the dash workaround, fallback to standard progressive + catch(ex: Exception) { + Logger.i(TAG, "Dash manifest workaround failed for video, falling back to progressive due to ${ex.message}"); + _lastVideoMediaSource = ProgressiveMediaSource.Factory(videoSource.getHttpDataSourceFactory()) + .createMediaSource(MediaItem.fromUri(videoSource.getVideoUrl())); + return; + } + } + else throw IllegalArgumentException("source without itag data..."); + } + private fun swapVideoSourceUrl(videoSource: IVideoUrlSource) { + Logger.i(TAG, "Loading VideoSource [Url]"); + _lastVideoMediaSource = ProgressiveMediaSource.Factory(DefaultHttpDataSource.Factory() + .setUserAgent(DEFAULT_USER_AGENT)) + .createMediaSource(MediaItem.fromUri(videoSource.getVideoUrl())); + } + private fun swapVideoSourceDash(videoSource: IDashManifestSource) { + Logger.i(TAG, "Loading VideoSource [Dash]"); + _lastVideoMediaSource = if (videoSource != null) DashMediaSource.Factory(DefaultHttpDataSource.Factory() + .setUserAgent(DEFAULT_USER_AGENT)) + .createMediaSource(MediaItem.fromUri(videoSource.url)); + else null; + } + private fun swapVideoSourceHLS(videoSource: IHLSManifestSource) { + Logger.i(TAG, "Loading VideoSource [HLS]"); + _lastVideoMediaSource = HlsMediaSource.Factory(DefaultHttpDataSource.Factory() + .setUserAgent(DEFAULT_USER_AGENT)) + .createMediaSource(MediaItem.fromUri(videoSource.url)); + } + + //Audio loads + private fun swapAudioSourceLocal(audioSource: LocalAudioSource) { + Logger.i(TAG, "Loading AudioSource [Local]"); + val file = File(audioSource.filePath); + if(!file.exists()) + throw IllegalArgumentException("File for this audio does not exist"); + _lastAudioMediaSource = ProgressiveMediaSource.Factory(DefaultDataSource.Factory(context)) + .createMediaSource(MediaItem.fromUri(Uri.fromFile(file))); + } + private fun swapAudioSourceUrlRange(audioSource: JSAudioUrlRangeSource) { + Logger.i(TAG, "Loading JSAudioUrlRangeSource"); + if(audioSource.hasItag) { + try { + _lastAudioMediaSource = VideoHelper.convertItagSourceToChunkedDashSource(audioSource); + if(_lastAudioMediaSource == null) + throw java.lang.IllegalStateException("Missing required parameters for dash workaround?"); + return; + } + //If it fails to create the dash workaround, fallback to standard progressive + catch(ex: Exception) { + Logger.i(TAG, "Dash manifest workaround failed for audio, falling back to progressive due to ${ex.message}"); + _lastAudioMediaSource = ProgressiveMediaSource.Factory(audioSource.getHttpDataSourceFactory()) + .createMediaSource(MediaItem.fromUri((audioSource as IAudioUrlSource).getAudioUrl())); + return; + } + } + else throw IllegalArgumentException("source without itag data...") + } + private fun swapAudioSourceUrl(audioSource: IAudioUrlSource) { + Logger.i(TAG, "Loading AudioSource [Url]"); + _lastAudioMediaSource = ProgressiveMediaSource.Factory(DefaultHttpDataSource.Factory() + .setUserAgent(DEFAULT_USER_AGENT)) + .createMediaSource(MediaItem.fromUri(audioSource.getAudioUrl())); + } + private fun swapAudioSourceHLS(audioSource: IHLSManifestAudioSource) { + Logger.i(TAG, "Loading AudioSource [HLS]"); + _lastAudioMediaSource = HlsMediaSource.Factory(DefaultHttpDataSource.Factory() + .setUserAgent(DEFAULT_USER_AGENT)) + .createMediaSource(MediaItem.fromUri(audioSource.url)); + } + + + //Prefered source selection + fun getPreferredVideoSource(video: IPlatformVideoDetails, targetPixels: Int = -1): IVideoSource? { + val usePreview = false; + if(usePreview) { + if(video.preview != null && video.preview is VideoMuxedSourceDescriptor) + return (video.preview as VideoMuxedSourceDescriptor).videoSources.last(); + return null; + } + else if(video.live != null) + return video.live; + else if(video.dash != null) + return video.dash; + else if(video.hls != null) + return video.hls; + else + return VideoHelper.selectBestVideoSource(video.video, targetPixels, PREFERED_VIDEO_CONTAINERS) + } + fun getPreferredAudioSource(video: IPlatformVideoDetails, preferredLanguage: String?): IAudioSource? { + return VideoHelper.selectBestAudioSource(video.video, PREFERED_AUDIO_CONTAINERS, preferredLanguage); + } + + private fun loadSelectedSources(play: Boolean, resume: Boolean): Boolean { + val sourceVideo = if(!isAudioMode || _lastAudioMediaSource == null) _lastVideoMediaSource else null; + val sourceAudio = _lastAudioMediaSource; + val sourceSubs = _lastSubtitleMediaSource; + + val sources = listOf(sourceVideo, sourceAudio, sourceSubs).filter { it != null }.map { it!! }.toTypedArray() + + beforeSourceChanged(); + + _mediaSource = if(sources.size == 1) { + Logger.i(TAG, "Using single source mode") + (sourceVideo ?: sourceAudio); + } + else if(sources.size > 1) { + Logger.i(TAG, "Using multi source mode ${sources.size}") + MergingMediaSource(true, *sources); + } + else { + Logger.i(TAG, "Using no sources loaded"); + stop(); + return false; + } + + reloadMediaSource(play, resume); + return true; + } + + private fun reloadMediaSource(play: Boolean = false, resume: Boolean = true) { + val player = exoPlayer + if (player == null) + return; + + val positionBefore = player.player.currentPosition; + if(_mediaSource != null) { + player.player.setMediaSource(_mediaSource!!); + _toResume = resume; + _didCallSourceChange = false; + player.player.prepare() + player.player.playWhenReady = play; + if(resume) + seekTo(positionBefore); + else + seekTo(0); + this.onSourceChanged(lastVideoSource, lastAudioSource, resume); + } + else + player.player?.stop(); + } + + fun clear() { + exoPlayer?.player?.stop(); + exoPlayer?.player?.clearMediaItems(); + } + + fun stop(){ + exoPlayer?.player?.stop(); + } + fun pause(){ + exoPlayer?.player?.pause(); + } + open fun play(){ + exoPlayer?.player?.play(); + } + + fun setVolume(volume: Float) { + exoPlayer?.setVolume(volume); + } + + protected open fun onPlayerError(error: PlaybackException) { + when (error.errorCode) { + PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS -> { + onDatasourceError.emit(error); + } + PlaybackException.ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED, + PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND, + PlaybackException.ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE, + PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, + PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT, + PlaybackException.ERROR_CODE_IO_NO_PERMISSION, + PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE, + PlaybackException.ERROR_CODE_IO_UNSPECIFIED -> { + Logger.i(TAG, "IO error, set _shouldPlaybackRestartOnConnectivity=true"); + _shouldPlaybackRestartOnConnectivity = true; + } + } + } + + protected open fun onVideoSizeChanged(videoSize: VideoSize) { + + } + protected open fun beforeSourceChanged() { + + } + protected open fun onSourceChanged(videoSource: IVideoSource?, audioSource: IAudioSource? = null, resume: Boolean = true) { } + + protected open fun onPlaybackStateChanged(playbackState: Int) { + if (_shouldPlaybackRestartOnConnectivity && playbackState == ExoPlayer.STATE_READY) { + Logger.i(TAG, "_shouldPlaybackRestartOnConnectivity=false"); + _shouldPlaybackRestartOnConnectivity = false; + } + + + } + + companion object { + val DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0"; + + val PREFERED_VIDEO_CONTAINERS = arrayOf("video/mp4", "video/webm", "video/3gpp"); + val PREFERED_AUDIO_CONTAINERS = arrayOf("audio/mp3", "audio/mp4", "audio/webm", "audio/opus"); + + val SUPPORTED_SUBTITLES = hashSetOf("text/vtt", "application/x-subrip"); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/video/datasources/JSHttpDataSource.java b/app/src/main/java/com/futo/platformplayer/views/video/datasources/JSHttpDataSource.java new file mode 100644 index 00000000..00a49cf8 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/video/datasources/JSHttpDataSource.java @@ -0,0 +1,854 @@ +package com.futo.platformplayer.views.video.datasources; + +import static com.google.android.exoplayer2.upstream.HttpUtil.buildRangeRequestHeader; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static com.google.android.exoplayer2.util.Util.castNonNull; +import static java.lang.Math.min; + +import android.net.Uri; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import com.futo.platformplayer.api.media.platforms.js.models.JSRequestModifier; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.PlaybackException; +import com.google.android.exoplayer2.upstream.BaseDataSource; +import com.google.android.exoplayer2.upstream.DataSourceException; +import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.upstream.DataSpec.HttpMethod; +import com.google.android.exoplayer2.upstream.HttpDataSource; +import com.google.android.exoplayer2.upstream.HttpUtil; +import com.google.android.exoplayer2.upstream.TransferListener; +import com.google.android.exoplayer2.util.Log; +import com.google.android.exoplayer2.util.Util; +import com.google.common.base.Predicate; +import com.google.common.collect.ForwardingMap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; +import com.google.common.net.HttpHeaders; +import java.io.IOException; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.NoRouteToHostException; +import java.net.URL; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.zip.GZIPInputStream; + +/* + * Based on the default ExoPlayer DefaultHttpDataSource + */ + +public class JSHttpDataSource extends BaseDataSource implements HttpDataSource { + public static final class Factory implements HttpDataSource.Factory { + + private final RequestProperties defaultRequestProperties; + + @Nullable private TransferListener transferListener; + @Nullable private Predicate contentTypePredicate; + @Nullable private String userAgent; + private int connectTimeoutMs; + private int readTimeoutMs; + private boolean allowCrossProtocolRedirects; + private boolean keepPostFor302Redirects; + @Nullable private JSRequestModifier requestModifier = null; + + /** Creates an instance. */ + public Factory() { + defaultRequestProperties = new RequestProperties(); + connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MILLIS; + readTimeoutMs = DEFAULT_READ_TIMEOUT_MILLIS; + } + + @Override + public Factory setDefaultRequestProperties(Map defaultRequestProperties) { + this.defaultRequestProperties.clearAndSet(defaultRequestProperties); + return this; + } + + /** + * Sets the request modifier that will be used. + * + *

The default is {@code null}, which results in no request modification + * + * @param requestModifier The request modifier that will be used, or {@code null} to use no request modifier + * @return This factory. + */ + public Factory setRequestModifier(@Nullable JSRequestModifier requestModifier) { + this.requestModifier = requestModifier; + return this; + } + + /** + * Sets the user agent that will be used. + * + *

The default is {@code null}, which causes the default user agent of the underlying + * platform to be used. + * + * @param userAgent The user agent that will be used, or {@code null} to use the default user + * agent of the underlying platform. + * @return This factory. + */ + public Factory setUserAgent(@Nullable String userAgent) { + this.userAgent = userAgent; + return this; + } + + /** + * Sets the connect timeout, in milliseconds. + * + *

The default is {@link JSHttpDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS}. + * + * @param connectTimeoutMs The connect timeout, in milliseconds, that will be used. + * @return This factory. + */ + public Factory setConnectTimeoutMs(int connectTimeoutMs) { + this.connectTimeoutMs = connectTimeoutMs; + return this; + } + + /** + * Sets the read timeout, in milliseconds. + * + *

The default is {@link JSHttpDataSource#DEFAULT_READ_TIMEOUT_MILLIS}. + * + * @param readTimeoutMs The connect timeout, in milliseconds, that will be used. + * @return This factory. + */ + public Factory setReadTimeoutMs(int readTimeoutMs) { + this.readTimeoutMs = readTimeoutMs; + return this; + } + + /** + * Sets whether to allow cross protocol redirects. + * + *

The default is {@code false}. + * + * @param allowCrossProtocolRedirects Whether to allow cross protocol redirects. + * @return This factory. + */ + public Factory setAllowCrossProtocolRedirects(boolean allowCrossProtocolRedirects) { + this.allowCrossProtocolRedirects = allowCrossProtocolRedirects; + return this; + } + + /** + * Sets a content type {@link Predicate}. If a content type is rejected by the predicate then a + * {@link HttpDataSource.InvalidContentTypeException} is thrown from {@link + * JSHttpDataSource#open(com.google.android.exoplayer2.upstream.DataSpec)}. + * + *

The default is {@code null}. + * + * @param contentTypePredicate The content type {@link Predicate}, or {@code null} to clear a + * predicate that was previously set. + * @return This factory. + */ + public Factory setContentTypePredicate(@Nullable Predicate contentTypePredicate) { + this.contentTypePredicate = contentTypePredicate; + return this; + } + + /** + * Sets the {@link TransferListener} that will be used. + * + *

The default is {@code null}. + * + *

See {@link com.google.android.exoplayer2.upstream.DataSource#addTransferListener(TransferListener)}. + * + * @param transferListener The listener that will be used. + * @return This factory. + */ + public Factory setTransferListener(@Nullable TransferListener transferListener) { + this.transferListener = transferListener; + return this; + } + + /** + * Sets whether we should keep the POST method and body when we have HTTP 302 redirects for a + * POST request. + */ + public Factory setKeepPostFor302Redirects(boolean keepPostFor302Redirects) { + this.keepPostFor302Redirects = keepPostFor302Redirects; + return this; + } + + @Override + public JSHttpDataSource createDataSource() { + JSHttpDataSource dataSource = + new JSHttpDataSource( + userAgent, + connectTimeoutMs, + readTimeoutMs, + allowCrossProtocolRedirects, + defaultRequestProperties, + contentTypePredicate, + keepPostFor302Redirects, + requestModifier); + if (transferListener != null) { + dataSource.addTransferListener(transferListener); + } + return dataSource; + } + } + + /** The default connection timeout, in milliseconds. */ + public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 8 * 1000; + /** The default read timeout, in milliseconds. */ + public static final int DEFAULT_READ_TIMEOUT_MILLIS = 8 * 1000; + + private static final String TAG = "JSHttpDataSource"; + private static final int MAX_REDIRECTS = 20; // Same limit as okhttp. + private static final int HTTP_STATUS_TEMPORARY_REDIRECT = 307; + private static final int HTTP_STATUS_PERMANENT_REDIRECT = 308; + private static final long MAX_BYTES_TO_DRAIN = 2048; + + private final boolean allowCrossProtocolRedirects; + private final int connectTimeoutMillis; + private final int readTimeoutMillis; + @Nullable private final String userAgent; + @Nullable private final RequestProperties defaultRequestProperties; + private final RequestProperties requestProperties; + private final boolean keepPostFor302Redirects; + + @Nullable private Predicate contentTypePredicate; + @Nullable private DataSpec dataSpec; + @Nullable private HttpURLConnection connection; + @Nullable private InputStream inputStream; + private boolean opened; + private int responseCode; + private long bytesToRead; + private long bytesRead; + @Nullable private JSRequestModifier requestModifier; + + private JSHttpDataSource( + @Nullable String userAgent, + int connectTimeoutMillis, + int readTimeoutMillis, + boolean allowCrossProtocolRedirects, + @Nullable RequestProperties defaultRequestProperties, + @Nullable Predicate contentTypePredicate, + boolean keepPostFor302Redirects, + @Nullable JSRequestModifier requestModifier) { + super(/* isNetwork= */ true); + this.userAgent = userAgent; + this.connectTimeoutMillis = connectTimeoutMillis; + this.readTimeoutMillis = readTimeoutMillis; + this.allowCrossProtocolRedirects = allowCrossProtocolRedirects; + this.defaultRequestProperties = defaultRequestProperties; + this.contentTypePredicate = contentTypePredicate; + this.requestProperties = new RequestProperties(); + this.keepPostFor302Redirects = keepPostFor302Redirects; + this.requestModifier = requestModifier; + } + + @Override + @Nullable + public Uri getUri() { + return connection == null ? null : Uri.parse(connection.getURL().toString()); + } + + @Override + public int getResponseCode() { + return connection == null || responseCode <= 0 ? -1 : responseCode; + } + + @Override + public Map> getResponseHeaders() { + if (connection == null) { + return ImmutableMap.of(); + } + // connection.getHeaderFields() always contains a null key with a value like + // ["HTTP/1.1 200 OK"]. The response code is available from HttpURLConnection#getResponseCode() + // and the HTTP version is fixed when establishing the connection. + // DataSource#getResponseHeaders() doesn't allow null keys in the returned map, so we need to + // remove it. + // connection.getHeaderFields() returns a special unmodifiable case-insensitive Map + // so we can't just remove the null key or make a copy without the null key. Instead we wrap it + // in a ForwardingMap subclass that ignores and filters out null keys in the read methods. + return new NullFilteringHeadersMap(connection.getHeaderFields()); + } + + @Override + public void setRequestProperty(String name, String value) { + checkNotNull(name); + checkNotNull(value); + requestProperties.set(name, value); + } + + @Override + public void clearRequestProperty(String name) { + checkNotNull(name); + requestProperties.remove(name); + } + + @Override + public void clearAllRequestProperties() { + requestProperties.clear(); + } + + /** Opens the source to read the specified data. */ + @Override + public long open(DataSpec dataSpec) throws HttpDataSourceException { + this.dataSpec = dataSpec; + bytesRead = 0; + bytesToRead = 0; + transferInitializing(dataSpec); + + String responseMessage; + HttpURLConnection connection; + try { + this.connection = makeConnection(dataSpec); + connection = this.connection; + responseCode = connection.getResponseCode(); + responseMessage = connection.getResponseMessage(); + } catch (IOException e) { + closeConnectionQuietly(); + throw HttpDataSourceException.createForIOException( + e, dataSpec, HttpDataSourceException.TYPE_OPEN); + } + + // Check for a valid response code. + if (responseCode < 200 || responseCode > 299) { + Map> headers = connection.getHeaderFields(); + if (responseCode == 416) { + long documentSize = HttpUtil.getDocumentSize(connection.getHeaderField(HttpHeaders.CONTENT_RANGE)); + if (dataSpec.position == documentSize) { + opened = true; + transferStarted(dataSpec); + return dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : 0; + } + } + + @Nullable InputStream errorStream = connection.getErrorStream(); + byte[] errorResponseBody; + try { + errorResponseBody = + errorStream != null ? Util.toByteArray(errorStream) : Util.EMPTY_BYTE_ARRAY; + } catch (IOException e) { + errorResponseBody = Util.EMPTY_BYTE_ARRAY; + } + closeConnectionQuietly(); + @Nullable + IOException cause = responseCode == 416 + ? new DataSourceException(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE) + : null; + + throw new InvalidResponseCodeException( + responseCode, responseMessage, cause, headers, dataSpec, errorResponseBody); + } + + // Check for a valid content type. + String contentType = connection.getContentType(); + if (contentTypePredicate != null && !contentTypePredicate.apply(contentType)) { + closeConnectionQuietly(); + throw new InvalidContentTypeException(contentType, dataSpec); + } + + // If we requested a range starting from a non-zero position and received a 200 rather than a + // 206, then the server does not support partial requests. We'll need to manually skip to the + // requested position. + long bytesToSkip; + if (requestModifier != null && !requestModifier.getAllowByteSkip()) { + bytesToSkip = 0; + } else { + bytesToSkip = responseCode == 200 && dataSpec.position != 0 ? dataSpec.position : 0; + } + + // Determine the length of the data to be read, after skipping. + boolean isCompressed = isCompressed(connection); + if (!isCompressed) { + if (dataSpec.length != C.LENGTH_UNSET) { + bytesToRead = dataSpec.length; + } else { + long contentLength = + HttpUtil.getContentLength( + connection.getHeaderField(HttpHeaders.CONTENT_LENGTH), + connection.getHeaderField(HttpHeaders.CONTENT_RANGE)); + bytesToRead = + contentLength != C.LENGTH_UNSET ? (contentLength - bytesToSkip) : C.LENGTH_UNSET; + } + } else { + // Gzip is enabled. If the server opts to use gzip then the content length in the response + // will be that of the compressed data, which isn't what we want. Always use the dataSpec + // length in this case. + bytesToRead = dataSpec.length; + } + + try { + inputStream = connection.getInputStream(); + if (isCompressed) { + inputStream = new GZIPInputStream(inputStream); + } + } catch (IOException e) { + closeConnectionQuietly(); + throw new HttpDataSourceException( + e, + dataSpec, + PlaybackException.ERROR_CODE_IO_UNSPECIFIED, + HttpDataSourceException.TYPE_OPEN); + } + + opened = true; + transferStarted(dataSpec); + + try { + skipFully(bytesToSkip, dataSpec); + } catch (IOException e) { + closeConnectionQuietly(); + + if (e instanceof HttpDataSourceException) { + throw (HttpDataSourceException) e; + } + throw new HttpDataSourceException( + e, + dataSpec, + PlaybackException.ERROR_CODE_IO_UNSPECIFIED, + HttpDataSourceException.TYPE_OPEN); + } + + return bytesToRead; + } + + @Override + public int read(byte[] buffer, int offset, int length) throws HttpDataSourceException { + try { + return readInternal(buffer, offset, length); + } catch (IOException e) { + throw HttpDataSourceException.createForIOException( + e, castNonNull(dataSpec), HttpDataSourceException.TYPE_READ); + } + } + + @Override + public void close() throws HttpDataSourceException { + try { + @Nullable InputStream inputStream = this.inputStream; + if (inputStream != null) { + long bytesRemaining = + bytesToRead == C.LENGTH_UNSET ? C.LENGTH_UNSET : bytesToRead - bytesRead; + maybeTerminateInputStream(connection, bytesRemaining); + try { + inputStream.close(); + } catch (IOException e) { + throw new HttpDataSourceException( + e, + castNonNull(dataSpec), + PlaybackException.ERROR_CODE_IO_UNSPECIFIED, + HttpDataSourceException.TYPE_CLOSE); + } + } + } finally { + inputStream = null; + closeConnectionQuietly(); + if (opened) { + opened = false; + transferEnded(); + } + } + } + + /** Establishes a connection, following redirects to do so where permitted. */ + private HttpURLConnection makeConnection(DataSpec dataSpec) throws IOException { + URL url = new URL(dataSpec.uri.toString()); + @HttpMethod int httpMethod = dataSpec.httpMethod; + @Nullable byte[] httpBody = dataSpec.httpBody; + long position = dataSpec.position; + long length = dataSpec.length; + boolean allowGzip = dataSpec.isFlagSet(DataSpec.FLAG_ALLOW_GZIP); + + if (!allowCrossProtocolRedirects && !keepPostFor302Redirects) { + // HttpURLConnection disallows cross-protocol redirects, but otherwise performs redirection + // automatically. This is the behavior we want, so use it. + return makeConnection( + url, + httpMethod, + httpBody, + position, + length, + allowGzip, + /* followRedirects= */ true, + dataSpec.httpRequestHeaders); + } + + // We need to handle redirects ourselves to allow cross-protocol redirects or to keep the POST + // request method for 302. + int redirectCount = 0; + while (redirectCount++ <= MAX_REDIRECTS) { + HttpURLConnection connection = + makeConnection( + url, + httpMethod, + httpBody, + position, + length, + allowGzip, + /* followRedirects= */ false, + dataSpec.httpRequestHeaders); + int responseCode = connection.getResponseCode(); + String location = connection.getHeaderField("Location"); + if ((httpMethod == DataSpec.HTTP_METHOD_GET || httpMethod == DataSpec.HTTP_METHOD_HEAD) + && (responseCode == HttpURLConnection.HTTP_MULT_CHOICE + || responseCode == HttpURLConnection.HTTP_MOVED_PERM + || responseCode == HttpURLConnection.HTTP_MOVED_TEMP + || responseCode == HttpURLConnection.HTTP_SEE_OTHER + || responseCode == HTTP_STATUS_TEMPORARY_REDIRECT + || responseCode == HTTP_STATUS_PERMANENT_REDIRECT)) { + connection.disconnect(); + url = handleRedirect(url, location, dataSpec); + } else if (httpMethod == DataSpec.HTTP_METHOD_POST + && (responseCode == HttpURLConnection.HTTP_MULT_CHOICE + || responseCode == HttpURLConnection.HTTP_MOVED_PERM + || responseCode == HttpURLConnection.HTTP_MOVED_TEMP + || responseCode == HttpURLConnection.HTTP_SEE_OTHER)) { + connection.disconnect(); + boolean shouldKeepPost = + keepPostFor302Redirects && responseCode == HttpURLConnection.HTTP_MOVED_TEMP; + if (!shouldKeepPost) { + // POST request follows the redirect and is transformed into a GET request. + httpMethod = DataSpec.HTTP_METHOD_GET; + httpBody = null; + } + url = handleRedirect(url, location, dataSpec); + } else { + return connection; + } + } + + // If we get here we've been redirected more times than are permitted. + throw new HttpDataSourceException( + new NoRouteToHostException("Too many redirects: " + redirectCount), + dataSpec, + PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, + HttpDataSourceException.TYPE_OPEN); + } + + /** + * Configures a connection and opens it. + * + * @param url The url to connect to. + * @param httpMethod The http method. + * @param httpBody The body data, or {@code null} if not required. + * @param position The byte offset of the requested data. + * @param length The length of the requested data, or {@link C#LENGTH_UNSET}. + * @param allowGzip Whether to allow the use of gzip. + * @param followRedirects Whether to follow redirects. + * @param requestParameters parameters (HTTP headers) to include in request. + */ + private HttpURLConnection makeConnection( + URL url, + @HttpMethod int httpMethod, + @Nullable byte[] httpBody, + long position, + long length, + boolean allowGzip, + boolean followRedirects, + Map requestParameters) + throws IOException { + Map requestHeaders = new HashMap<>(); + if (defaultRequestProperties != null) { + requestHeaders.putAll(defaultRequestProperties.getSnapshot()); + } + requestHeaders.putAll(requestProperties.getSnapshot()); + requestHeaders.putAll(requestParameters); + + @Nullable String rangeHeader = buildRangeRequestHeader(position, length); + if (rangeHeader != null) { + requestHeaders.put(HttpHeaders.RANGE, rangeHeader); + } + + if (userAgent != null) { + requestHeaders.put(HttpHeaders.USER_AGENT, userAgent); + } + + requestHeaders.put(HttpHeaders.ACCEPT_ENCODING, allowGzip ? "gzip" : "identity"); + + String requestUrl = url.toString(); + if (requestModifier != null) { + JSRequestModifier.IRequest result = requestModifier.modifyRequest(requestUrl, requestHeaders); + requestUrl = result.getUrl(); + requestHeaders = result.getHeaders(); + } + + HttpURLConnection connection = openConnection(new URL(requestUrl)); + connection.setConnectTimeout(connectTimeoutMillis); + connection.setReadTimeout(readTimeoutMillis); + + for (Map.Entry property : requestHeaders.entrySet()) { + connection.setRequestProperty(property.getKey(), property.getValue()); + } + + connection.setInstanceFollowRedirects(followRedirects); + connection.setDoOutput(httpBody != null); + connection.setRequestMethod(DataSpec.getStringForHttpMethod(httpMethod)); + + if (httpBody != null) { + connection.setFixedLengthStreamingMode(httpBody.length); + connection.connect(); + OutputStream os = connection.getOutputStream(); + os.write(httpBody); + os.close(); + } else { + connection.connect(); + } + return connection; + } + + /** Creates an {@link HttpURLConnection} that is connected with the {@code url}. */ + @VisibleForTesting + /* package */ HttpURLConnection openConnection(URL url) throws IOException { + return (HttpURLConnection) url.openConnection(); + } + + /** + * Handles a redirect. + * + * @param originalUrl The original URL. + * @param location The Location header in the response. May be {@code null}. + * @param dataSpec The {@link DataSpec}. + * @return The next URL. + * @throws HttpDataSourceException If redirection isn't possible. + */ + private URL handleRedirect(URL originalUrl, @Nullable String location, DataSpec dataSpec) + throws HttpDataSourceException { + if (location == null) { + throw new HttpDataSourceException( + "Null location redirect", + dataSpec, + PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, + HttpDataSourceException.TYPE_OPEN); + } + // Form the new url. + URL url; + try { + url = new URL(originalUrl, location); + } catch (MalformedURLException e) { + throw new HttpDataSourceException( + e, + dataSpec, + PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, + HttpDataSourceException.TYPE_OPEN); + } + + // Check that the protocol of the new url is supported. + String protocol = url.getProtocol(); + if (!"https".equals(protocol) && !"http".equals(protocol)) { + throw new HttpDataSourceException( + "Unsupported protocol redirect: " + protocol, + dataSpec, + PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, + HttpDataSourceException.TYPE_OPEN); + } + if (!allowCrossProtocolRedirects && !protocol.equals(originalUrl.getProtocol())) { + throw new HttpDataSourceException( + "Disallowed cross-protocol redirect (" + + originalUrl.getProtocol() + + " to " + + protocol + + ")", + dataSpec, + PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, + HttpDataSourceException.TYPE_OPEN); + } + return url; + } + + /** + * Attempts to skip the specified number of bytes in full. + * + * @param bytesToSkip The number of bytes to skip. + * @param dataSpec The {@link DataSpec}. + * @throws IOException If the thread is interrupted during the operation, or if the data ended + * before skipping the specified number of bytes. + */ + private void skipFully(long bytesToSkip, DataSpec dataSpec) throws IOException { + if (bytesToSkip == 0) { + return; + } + byte[] skipBuffer = new byte[4096]; + while (bytesToSkip > 0) { + int readLength = (int) min(bytesToSkip, skipBuffer.length); + int read = castNonNull(inputStream).read(skipBuffer, 0, readLength); + if (Thread.currentThread().isInterrupted()) { + throw new HttpDataSourceException( + new InterruptedIOException(), + dataSpec, + PlaybackException.ERROR_CODE_IO_UNSPECIFIED, + HttpDataSourceException.TYPE_OPEN); + } + if (read == -1) { + throw new HttpDataSourceException( + dataSpec, + PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE, + HttpDataSourceException.TYPE_OPEN); + } + bytesToSkip -= read; + bytesTransferred(read); + } + } + + /** + * Reads up to {@code length} bytes of data and stores them into {@code buffer}, starting at index + * {@code offset}. + * + *

This method blocks until at least one byte of data can be read, the end of the opened range + * is detected, or an exception is thrown. + * + * @param buffer The buffer into which the read data should be stored. + * @param offset The start offset into {@code buffer} at which data should be written. + * @param readLength The maximum number of bytes to read. + * @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if the end of the opened + * range is reached. + * @throws IOException If an error occurs reading from the source. + */ + private int readInternal(byte[] buffer, int offset, int readLength) throws IOException { + if (readLength == 0) { + return 0; + } + if (bytesToRead != C.LENGTH_UNSET) { + long bytesRemaining = bytesToRead - bytesRead; + if (bytesRemaining == 0) { + return C.RESULT_END_OF_INPUT; + } + readLength = (int) min(readLength, bytesRemaining); + } + + int read = castNonNull(inputStream).read(buffer, offset, readLength); + if (read == -1) { + return C.RESULT_END_OF_INPUT; + } + + bytesRead += read; + bytesTransferred(read); + return read; + } + + /** + * On platform API levels 19 and 20, okhttp's implementation of {@link InputStream#close} can + * block for a long time if the stream has a lot of data remaining. Call this method before + * closing the input stream to make a best effort to cause the input stream to encounter an + * unexpected end of input, working around this issue. On other platform API levels, the method + * does nothing. + * + * @param connection The connection whose {@link InputStream} should be terminated. + * @param bytesRemaining The number of bytes remaining to be read from the input stream if its + * length is known. {@link C#LENGTH_UNSET} otherwise. + */ + private static void maybeTerminateInputStream( + @Nullable HttpURLConnection connection, long bytesRemaining) { + if (connection == null || Util.SDK_INT < 19 || Util.SDK_INT > 20) { + return; + } + + try { + InputStream inputStream = connection.getInputStream(); + if (bytesRemaining == C.LENGTH_UNSET) { + // If the input stream has already ended, do nothing. The socket may be re-used. + if (inputStream.read() == -1) { + return; + } + } else if (bytesRemaining <= MAX_BYTES_TO_DRAIN) { + // There isn't much data left. Prefer to allow it to drain, which may allow the socket to be + // re-used. + return; + } + String className = inputStream.getClass().getName(); + if ("com.android.okhttp.internal.http.HttpTransport$ChunkedInputStream".equals(className) + || "com.android.okhttp.internal.http.HttpTransport$FixedLengthInputStream" + .equals(className)) { + Class superclass = inputStream.getClass().getSuperclass(); + Method unexpectedEndOfInput = + checkNotNull(superclass).getDeclaredMethod("unexpectedEndOfInput"); + unexpectedEndOfInput.setAccessible(true); + unexpectedEndOfInput.invoke(inputStream); + } + } catch (Exception e) { + // If an IOException then the connection didn't ever have an input stream, or it was closed + // already. If another type of exception then something went wrong, most likely the device + // isn't using okhttp. + } + } + + /** Closes the current connection quietly, if there is one. */ + private void closeConnectionQuietly() { + if (connection != null) { + try { + connection.disconnect(); + } catch (Exception e) { + Log.e(TAG, "Unexpected error while disconnecting", e); + } + connection = null; + } + } + + private static boolean isCompressed(HttpURLConnection connection) { + String contentEncoding = connection.getHeaderField("Content-Encoding"); + return "gzip".equalsIgnoreCase(contentEncoding); + } + + private static class NullFilteringHeadersMap extends ForwardingMap> { + + private final Map> headers; + + public NullFilteringHeadersMap(Map> headers) { + this.headers = headers; + } + + @Override + protected Map> delegate() { + return headers; + } + + @Override + public boolean containsKey(@Nullable Object key) { + return key != null && super.containsKey(key); + } + + @Nullable + @Override + public List get(@Nullable Object key) { + return key == null ? null : super.get(key); + } + + @Override + public Set keySet() { + return Sets.filter(super.keySet(), key -> key != null); + } + + @Override + public Set>> entrySet() { + return Sets.filter(super.entrySet(), entry -> entry.getKey() != null); + } + + @Override + public int size() { + return super.size() - (super.containsKey(null) ? 1 : 0); + } + + @Override + public boolean isEmpty() { + return super.isEmpty() || (super.size() == 1 && super.containsKey(null)); + } + + @Override + public boolean containsValue(@Nullable Object value) { + return super.standardContainsValue(value); + } + + @Override + public boolean equals(@Nullable Object object) { + return object != null && super.standardEquals(object); + } + + @Override + public int hashCode() { + return super.standardHashCode(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/videometa/UpNextView.kt b/app/src/main/java/com/futo/platformplayer/views/videometa/UpNextView.kt new file mode 100644 index 00000000..f217f145 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/videometa/UpNextView.kt @@ -0,0 +1,184 @@ +package com.futo.platformplayer.views.videometa + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat +import com.bumptech.glide.Glide +import com.futo.platformplayer.states.StatePlayer +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.toHumanNowDiffString +import com.futo.platformplayer.toHumanNumber + +class UpNextView : LinearLayout { + private val _layoutContainer: LinearLayout; + private val _textType: TextView; + private val _textTitle: TextView; + private val _imageThumbnail: ImageView; + private val _textMetadata: TextView; + private val _imageChannelThumbnail: ImageView; + private val _textChannelName: TextView; + private val _textPosition: TextView; + private val _textUpNext: TextView; + private val _buttonClear: LinearLayout; + private val _buttonShuffle: LinearLayout; + private val _buttonRepeat: LinearLayout; + private val _buttonView: LinearLayout; + private val _layoutRepeatDivider: FrameLayout; + private val _layoutQueueBox: ConstraintLayout; + private val _layoutEndOfPlaylist: ConstraintLayout; + private val _textEndOfQueue: TextView; + private val _buttonRestartNow: LinearLayout; + + val onNextItem = Event0(); + val onRestartQueue = Event0(); + val onOpenQueueClick = Event0(); + + private val _activeColor = ContextCompat.getColor(context, R.color.gray_0d); + private val _inactiveColor = ContextCompat.getColor(context, R.color.gray_16); + + constructor(context: Context, attrs: AttributeSet? = null): super(context, attrs) { + LayoutInflater.from(context).inflate(R.layout.view_up_next, this, true); + + _layoutContainer = findViewById(R.id.videodetail_queue); + _textType = findViewById(R.id.videodetail_queue_type); + _textTitle = findViewById(R.id.videodetail_queue_title); + _textMetadata = findViewById(R.id.videodetail_queue_meta); + _imageThumbnail = findViewById(R.id.videodetail_queue_thumbnail); + _textChannelName = findViewById(R.id.videodetail_queue_channel_name); + _imageChannelThumbnail = findViewById(R.id.videodetail_queue_channel_image); + _textPosition = findViewById(R.id.videodetail_queue_position); + _textUpNext = findViewById(R.id.videodetail_up_next); + _buttonClear = findViewById(R.id.button_clear); + _buttonShuffle = findViewById(R.id.button_shuffle); + _buttonRepeat = findViewById(R.id.button_repeat); + _buttonView = findViewById(R.id.button_view); + _layoutRepeatDivider = findViewById(R.id.layout_repeat_divider); + _layoutQueueBox = findViewById(R.id.videodetail_queue_box); + _layoutEndOfPlaylist = findViewById(R.id.videodetail_end_of_playlist) + _textEndOfQueue = findViewById(R.id.text_end_of_queue); + _buttonRestartNow = findViewById(R.id.button_restart_now); + + _buttonClear.setOnClickListener { StatePlayer.instance.clearQueue(); }; + _buttonShuffle.setOnClickListener { + StatePlayer.instance.setQueueShuffle(!StatePlayer.instance.queueShuffle); + update(); + }; + + _buttonRepeat.setOnClickListener { + StatePlayer.instance.setQueueRepeat(!StatePlayer.instance.queueRepeat); + update(); + }; + + _buttonView.setOnClickListener { onOpenQueueClick.emit(); }; + + _imageThumbnail.setOnClickListener { onNextItem.emit() }; + _textTitle.setOnClickListener { onNextItem.emit(); }; + + _buttonRestartNow.setOnClickListener { + onRestartQueue.emit(); + }; + } + + private fun updateRepeatButton() { + if (StatePlayer.instance.queueRepeat) + _buttonRepeat.setBackgroundColor(_activeColor); + else + _buttonRepeat.setBackgroundColor(_inactiveColor); + } + + private fun updateShuffleButton() { + if (StatePlayer.instance.queueShuffle) + _buttonShuffle.setBackgroundColor(_activeColor); + else + _buttonShuffle.setBackgroundColor(_inactiveColor); + } + + fun update() + { + updateShuffleButton(); + updateRepeatButton(); + + val isPlaylist = StatePlayer.instance.getQueueLength() > 1; + if (!isPlaylist) { + _layoutContainer.visibility = View.GONE; + return; + } + + val nextItem = StatePlayer.instance.getNextQueueItem(); + + //End of queue + if (nextItem == null) { + if(StatePlayer.instance.getQueueLength() <= 0) { + _layoutContainer.visibility = View.GONE; + return; + } + + _layoutQueueBox.visibility = View.GONE; + _layoutEndOfPlaylist.visibility = View.VISIBLE; + + when (StatePlayer.instance.getQueueType()){ + StatePlayer.TYPE_WATCHLATER -> { + _buttonRestartNow.visibility = View.GONE; + _textEndOfQueue.text = resources.getString(R.string.end_of_watch_later_reached); + } + //TODO: This case doesn't make sense as queue deletes items as they finish + StatePlayer.TYPE_QUEUE -> { + _buttonRestartNow.visibility = View.VISIBLE; + _textEndOfQueue.text = if (StatePlayer.instance.queueRepeat) resources.getString(R.string.the_queue_will_restart_after_the_video_is_finished) else resources.getString(R.string.end_of_queue_reached); + } + StatePlayer.TYPE_PLAYLIST -> { + _buttonRestartNow.visibility = View.VISIBLE; + _textEndOfQueue.text = if (StatePlayer.instance.queueRepeat) resources.getString(R.string.the_playlist_will_restart_after_the_video_is_finished) else resources.getString(R.string.end_of_playlist_reached); + } + } + } + //Next Item + else { + _layoutQueueBox.visibility = View.VISIBLE; + _layoutEndOfPlaylist.visibility = View.GONE; + + _textTitle.text = nextItem.name ?: ""; + + val metadataTokens = mutableListOf(); + if (nextItem.viewCount > 0) { + metadataTokens.add("${nextItem.viewCount.toHumanNumber()} views"); + } + + if (nextItem.datetime != null) { + metadataTokens.add(nextItem.datetime!!.toHumanNowDiffString()) + } + + _textMetadata.text = metadataTokens.joinToString(" • "); + _textChannelName.text = nextItem.author.name ?: ""; + Glide.with(_imageThumbnail) + .load(nextItem.thumbnails.getHQThumbnail()) + .placeholder(R.drawable.placeholder_video_thumbnail) + .into(_imageThumbnail); + Glide.with(_imageChannelThumbnail) + .load(nextItem.author.thumbnail) + .placeholder(R.drawable.placeholder_video_thumbnail) + .into(_imageChannelThumbnail); + } + _layoutContainer.visibility = View.VISIBLE; + _textUpNext.text = StatePlayer.instance.getQueueType(); + _textType.text = if (StatePlayer.instance.queueName != StatePlayer.instance.getQueueType()) { StatePlayer.instance.queueName } else { "" }; + _textPosition.text = "${StatePlayer.instance.getQueueProgress() + 1}/${StatePlayer.instance.getQueueLength()}"; + + val repeatButtonEnabled = StatePlayer.instance.getQueueType() != StatePlayer.TYPE_WATCHLATER; + if (!repeatButtonEnabled) { + _buttonRepeat.visibility = View.GONE; + _layoutRepeatDivider.visibility = View.GONE; + } else { + _buttonRepeat.visibility = View.VISIBLE; + _layoutRepeatDivider.visibility = View.VISIBLE; + } + } +} \ No newline at end of file diff --git a/app/src/main/proto/com/futo/platformplayer/protos/DeviceAuthMessage.proto b/app/src/main/proto/com/futo/platformplayer/protos/DeviceAuthMessage.proto new file mode 100644 index 00000000..f6b090d9 --- /dev/null +++ b/app/src/main/proto/com/futo/platformplayer/protos/DeviceAuthMessage.proto @@ -0,0 +1,82 @@ +syntax = "proto2"; +option optimize_for = LITE_RUNTIME; +package com.futo.platformplayer.protos; + +message CastMessage { + // Always pass a version of the protocol for future compatibility + // requirements. + enum ProtocolVersion { CASTV2_1_0 = 0; } + required ProtocolVersion protocol_version = 1; + // source and destination ids identify the origin and destination of the + // message. They are used to route messages between endpoints that share a + // device-to-device channel. + // + // For messages between applications: + // - The sender application id is a unique identifier generated on behalf of + // the sender application. + // - The receiver id is always the the session id for the application. + // + // For messages to or from the sender or receiver platform, the special ids + // 'sender-0' and 'receiver-0' can be used. + // + // For messages intended for all endpoints using a given channel, the + // wildcard destination_id '*' can be used. + required string source_id = 2; + required string destination_id = 3; + // This is the core multiplexing key. All messages are sent on a namespace + // and endpoints sharing a channel listen on one or more namespaces. The + // namespace defines the protocol and semantics of the message. + required string namespace = 4; + // Encoding and payload info follows. + // What type of data do we have in this message. + enum PayloadType { + STRING = 0; + BINARY = 1; + } + required PayloadType payload_type = 5; + // Depending on payload_type, exactly one of the following optional fields + // will always be set. + optional string payload_utf8 = 6; + optional bytes payload_binary = 7; +} +enum SignatureAlgorithm { + UNSPECIFIED = 0; + RSASSA_PKCS1v15 = 1; + RSASSA_PSS = 2; +} +enum HashAlgorithm { + SHA1 = 0; + SHA256 = 1; +} +// Messages for authentication protocol between a sender and a receiver. +message AuthChallenge { + optional SignatureAlgorithm signature_algorithm = 1 + [default = RSASSA_PKCS1v15]; + optional bytes sender_nonce = 2; + optional HashAlgorithm hash_algorithm = 3 [default = SHA1]; +} +message AuthResponse { + required bytes signature = 1; + required bytes client_auth_certificate = 2; + repeated bytes intermediate_certificate = 3; + optional SignatureAlgorithm signature_algorithm = 4 + [default = RSASSA_PKCS1v15]; + optional bytes sender_nonce = 5; + optional HashAlgorithm hash_algorithm = 6 [default = SHA1]; + optional bytes crl = 7; +} +message AuthError { + enum ErrorType { + INTERNAL_ERROR = 0; + NO_TLS = 1; // The underlying connection is not TLS + SIGNATURE_ALGORITHM_UNAVAILABLE = 2; + } + required ErrorType error_type = 1; +} +message DeviceAuthMessage { + // Request fields + optional AuthChallenge challenge = 1; + // Response fields + optional AuthResponse response = 2; + optional AuthError error = 3; +} diff --git a/app/src/main/res/anim/slide_darken.xml b/app/src/main/res/anim/slide_darken.xml new file mode 100644 index 00000000..28c09f99 --- /dev/null +++ b/app/src/main/res/anim/slide_darken.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_in_up.xml b/app/src/main/res/anim/slide_in_up.xml new file mode 100644 index 00000000..e54c8cae --- /dev/null +++ b/app/src/main/res/anim/slide_in_up.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_lighten.xml b/app/src/main/res/anim/slide_lighten.xml new file mode 100644 index 00000000..d5243935 --- /dev/null +++ b/app/src/main/res/anim/slide_lighten.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_out_up.xml b/app/src/main/res/anim/slide_out_up.xml new file mode 100644 index 00000000..40e7688d --- /dev/null +++ b/app/src/main/res/anim/slide_out_up.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/app/src/main/res/animator/fade.xml b/app/src/main/res/animator/fade.xml new file mode 100644 index 00000000..da874f36 --- /dev/null +++ b/app/src/main/res/animator/fade.xml @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/app/src/main/res/animator/fade_400.xml b/app/src/main/res/animator/fade_400.xml new file mode 100644 index 00000000..79e18249 --- /dev/null +++ b/app/src/main/res/animator/fade_400.xml @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/app/src/main/res/animator/fade_800.xml b/app/src/main/res/animator/fade_800.xml new file mode 100644 index 00000000..b69f0e17 --- /dev/null +++ b/app/src/main/res/animator/fade_800.xml @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/app/src/main/res/animator/rotation_1500_clockwise.xml b/app/src/main/res/animator/rotation_1500_clockwise.xml new file mode 100644 index 00000000..b32fbadb --- /dev/null +++ b/app/src/main/res/animator/rotation_1500_clockwise.xml @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/app/src/main/res/animator/rotation_2000_clockwise.xml b/app/src/main/res/animator/rotation_2000_clockwise.xml new file mode 100644 index 00000000..d787bff1 --- /dev/null +++ b/app/src/main/res/animator/rotation_2000_clockwise.xml @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/app/src/main/res/animator/rotation_2500_counter_clockwise.xml b/app/src/main/res/animator/rotation_2500_counter_clockwise.xml new file mode 100644 index 00000000..8e9c9439 --- /dev/null +++ b/app/src/main/res/animator/rotation_2500_counter_clockwise.xml @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/app/src/main/res/animator/toggle_background.xml b/app/src/main/res/animator/toggle_background.xml new file mode 100644 index 00000000..b142a0fa --- /dev/null +++ b/app/src/main/res/animator/toggle_background.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/animator/toggle_background_reverse.xml b/app/src/main/res/animator/toggle_background_reverse.xml new file mode 100644 index 00000000..cc923483 --- /dev/null +++ b/app/src/main/res/animator/toggle_background_reverse.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/animator/toggle_foreground.xml b/app/src/main/res/animator/toggle_foreground.xml new file mode 100644 index 00000000..ee8b67e9 --- /dev/null +++ b/app/src/main/res/animator/toggle_foreground.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/animator/toggle_foreground_container.xml b/app/src/main/res/animator/toggle_foreground_container.xml new file mode 100644 index 00000000..2c517234 --- /dev/null +++ b/app/src/main/res/animator/toggle_foreground_container.xml @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/app/src/main/res/animator/toggle_foreground_container_reverse.xml b/app/src/main/res/animator/toggle_foreground_container_reverse.xml new file mode 100644 index 00000000..b8e74136 --- /dev/null +++ b/app/src/main/res/animator/toggle_foreground_container_reverse.xml @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/app/src/main/res/animator/toggle_foreground_reverse.xml b/app/src/main/res/animator/toggle_foreground_reverse.xml new file mode 100644 index 00000000..17b245e4 --- /dev/null +++ b/app/src/main/res/animator/toggle_foreground_reverse.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/example_banner.jpg b/app/src/main/res/drawable-v24/example_banner.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1acfcea7ede1b7b77b9fb8e369f60f3a46a7f32b GIT binary patch literal 235230 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�lKsuAkU_N>z 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-v24/placeholder_channel_thumbnail.jpg b/app/src/main/res/drawable-v24/placeholder_channel_thumbnail.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b69aeb812ae16ebbefb0a3b6066d568640ea70fb GIT binary patch literal 3193 zcmeHJYj6`)6h3!%v%7gUyLpsSgH79%mQLt2Y5G*B_HCFlhENdHQIhoOG^Hf1MMr;7 z(IUTi%Ht0$sHo!)Q33TAV13RwBcjlv<5*ODj6Zk?iVm*lZjz=WZ4v+4lY8fWIrlr~ zo^y7Sdz3Nd8<3VX`Wk_txQGitnGgmW)_1i7_4`M5>eK@;@IF+*r@m`!RbVPk6*CyXmzr$1HE-rFcpk=YgQ{{G7xyu~x z(yG$RDsMTm$#DD}YDWd5B5|+O6`6t=jEJQa)U%1`c>Kyw!L|gZJi^5N@W^KpI7SUn zMD?Rgtr7K$Oyx_c1Ci$iULy*Epw)^xgVkuz>kaeG7L#>BX4dTsGVOMUvnbz@1T#EJ-d0^_8mK)+qHYo-u(xjf8oW0FTMQA>u-hSuAyQA;D|G|eJef-&}&%Zc*W^DZI#JO+3`~Lh7KmPRd#Y>ley>j)p->>~a z^P+hsW!k)C%!}bTmJ?`Rgc-oe%A6+K&6{hM3Bh%i9M1;PTDx`R&~a^UN#KGl6g{a+ zcX}uCF4EN0%>G?sga0YB$;485odp9+@ZqsCRKvBuHWv&tb91f+bML_a@*OZKpq{@FT&^PmR_(%=p#42EO9HGY3{ z@-P3!_3CMQdjXtUOnu$BKdZ*r+7=7rRpCI^-QnISUhZTK;$W&eli|`e#$2M2_pAG)9$8h!9G*9_oGz^ zt-2tL7WJ?eA`n#Vfj$G|prmRWYcKJrDq^Sw(0r{Zm-7IG?Eu%eD9WEBigJBFb|+r} z9P3HhuN?-cxPtmEDV=j4fN3MZkxO%D9}b)WXu(=Fe*&-v8?l8Q==;GL@}_OyWZh=vBx~7&aZ~fM5AM#oYV54*Ug#ythsXy@ z3RT{It~Cw`#Fnl2Bv}nKETp1*nm?Iz0ui#=<<2YB}T5grk0ESh*(i32u*%a6RGN6F#?$dkLoqO;$VM zA;Nn+R3PCw*=}3KUuE2=V5O-PPldUFdP^ZY*SH4T)?lH@BT~$MipjK#F4AYKSFv5j zCuJ-;{ZyN>gB`I_bh7PI5e=9MkgY@I*HzVGY~RTTx( zEcK}gzP`l~{T*G*Wcse!Gs*aY$`asjkQOkOK#lJpF zQkJHpm1SADu`bJ8YpbfGwKwu9y;!YQ%QD%a>1gfs82-;8wqZs3rSFnoZRGU_4YQoD literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/background_16_round_4dp.xml b/app/src/main/res/drawable/background_16_round_4dp.xml new file mode 100644 index 00000000..96e7697f --- /dev/null +++ b/app/src/main/res/drawable/background_16_round_4dp.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_1d_round_4dp.xml b/app/src/main/res/drawable/background_1d_round_4dp.xml new file mode 100644 index 00000000..cb3b2fdf --- /dev/null +++ b/app/src/main/res/drawable/background_1d_round_4dp.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_30_round_4dp.xml b/app/src/main/res/drawable/background_30_round_4dp.xml new file mode 100644 index 00000000..580ebe03 --- /dev/null +++ b/app/src/main/res/drawable/background_30_round_4dp.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_big_button.xml b/app/src/main/res/drawable/background_big_button.xml new file mode 100644 index 00000000..e4bafdd8 --- /dev/null +++ b/app/src/main/res/drawable/background_big_button.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_big_button_red.xml b/app/src/main/res/drawable/background_big_button_red.xml new file mode 100644 index 00000000..ff5234a6 --- /dev/null +++ b/app/src/main/res/drawable/background_big_button_red.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_button_accent.xml b/app/src/main/res/drawable/background_button_accent.xml new file mode 100644 index 00000000..3ab7713f --- /dev/null +++ b/app/src/main/res/drawable/background_button_accent.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_button_accent_straight_left.xml b/app/src/main/res/drawable/background_button_accent_straight_left.xml new file mode 100644 index 00000000..e0d3cd37 --- /dev/null +++ b/app/src/main/res/drawable/background_button_accent_straight_left.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/background_button_black.xml b/app/src/main/res/drawable/background_button_black.xml new file mode 100644 index 00000000..3b1cd0d6 --- /dev/null +++ b/app/src/main/res/drawable/background_button_black.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_button_err.xml b/app/src/main/res/drawable/background_button_err.xml new file mode 100644 index 00000000..38184c02 --- /dev/null +++ b/app/src/main/res/drawable/background_button_err.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_button_err_round_4dp.xml b/app/src/main/res/drawable/background_button_err_round_4dp.xml new file mode 100644 index 00000000..b4e1f2c8 --- /dev/null +++ b/app/src/main/res/drawable/background_button_err_round_4dp.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_button_gray_straight_left.xml b/app/src/main/res/drawable/background_button_gray_straight_left.xml new file mode 100644 index 00000000..41ecc289 --- /dev/null +++ b/app/src/main/res/drawable/background_button_gray_straight_left.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/background_button_pred.xml b/app/src/main/res/drawable/background_button_pred.xml new file mode 100644 index 00000000..e92d99cf --- /dev/null +++ b/app/src/main/res/drawable/background_button_pred.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_button_primary.xml b/app/src/main/res/drawable/background_button_primary.xml new file mode 100644 index 00000000..461ba0b2 --- /dev/null +++ b/app/src/main/res/drawable/background_button_primary.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_button_primary_round.xml b/app/src/main/res/drawable/background_button_primary_round.xml new file mode 100644 index 00000000..80d90ac4 --- /dev/null +++ b/app/src/main/res/drawable/background_button_primary_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_button_primary_round_4dp.xml b/app/src/main/res/drawable/background_button_primary_round_4dp.xml new file mode 100644 index 00000000..895fbeff --- /dev/null +++ b/app/src/main/res/drawable/background_button_primary_round_4dp.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_button_primary_straight_left.xml b/app/src/main/res/drawable/background_button_primary_straight_left.xml new file mode 100644 index 00000000..d5dc36b1 --- /dev/null +++ b/app/src/main/res/drawable/background_button_primary_straight_left.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/background_button_round.xml b/app/src/main/res/drawable/background_button_round.xml new file mode 100644 index 00000000..845e8860 --- /dev/null +++ b/app/src/main/res/drawable/background_button_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_button_round_green.xml b/app/src/main/res/drawable/background_button_round_green.xml new file mode 100644 index 00000000..1feaebc5 --- /dev/null +++ b/app/src/main/res/drawable/background_button_round_green.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_button_source.xml b/app/src/main/res/drawable/background_button_source.xml new file mode 100644 index 00000000..e4bafdd8 --- /dev/null +++ b/app/src/main/res/drawable/background_button_source.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_button_transparent_round.xml b/app/src/main/res/drawable/background_button_transparent_round.xml new file mode 100644 index 00000000..c9b25812 --- /dev/null +++ b/app/src/main/res/drawable/background_button_transparent_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_channel_round.xml b/app/src/main/res/drawable/background_channel_round.xml new file mode 100644 index 00000000..a045573b --- /dev/null +++ b/app/src/main/res/drawable/background_channel_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_fade.xml b/app/src/main/res/drawable/background_fade.xml new file mode 100644 index 00000000..acad860d --- /dev/null +++ b/app/src/main/res/drawable/background_fade.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/drawable/background_gesture_controls.xml b/app/src/main/res/drawable/background_gesture_controls.xml new file mode 100644 index 00000000..5dd7365a --- /dev/null +++ b/app/src/main/res/drawable/background_gesture_controls.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_pill.xml b/app/src/main/res/drawable/background_pill.xml new file mode 100644 index 00000000..7112c768 --- /dev/null +++ b/app/src/main/res/drawable/background_pill.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_pill_black.xml b/app/src/main/res/drawable/background_pill_black.xml new file mode 100644 index 00000000..164050d0 --- /dev/null +++ b/app/src/main/res/drawable/background_pill_black.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_pill_toggled.xml b/app/src/main/res/drawable/background_pill_toggled.xml new file mode 100644 index 00000000..a4960fbd --- /dev/null +++ b/app/src/main/res/drawable/background_pill_toggled.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_pill_transparent.xml b/app/src/main/res/drawable/background_pill_transparent.xml new file mode 100644 index 00000000..0e515e7f --- /dev/null +++ b/app/src/main/res/drawable/background_pill_transparent.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_pill_untoggled.xml b/app/src/main/res/drawable/background_pill_untoggled.xml new file mode 100644 index 00000000..c72b9128 --- /dev/null +++ b/app/src/main/res/drawable/background_pill_untoggled.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_radio_selected.xml b/app/src/main/res/drawable/background_radio_selected.xml new file mode 100644 index 00000000..c5fc6bdf --- /dev/null +++ b/app/src/main/res/drawable/background_radio_selected.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_radio_unselected.xml b/app/src/main/res/drawable/background_radio_unselected.xml new file mode 100644 index 00000000..f5ea7dca --- /dev/null +++ b/app/src/main/res/drawable/background_radio_unselected.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_slide_up_option.xml b/app/src/main/res/drawable/background_slide_up_option.xml new file mode 100644 index 00000000..3f0a602f --- /dev/null +++ b/app/src/main/res/drawable/background_slide_up_option.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_slide_up_option_selected.xml b/app/src/main/res/drawable/background_slide_up_option_selected.xml new file mode 100644 index 00000000..166ddbec --- /dev/null +++ b/app/src/main/res/drawable/background_slide_up_option_selected.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_small_button.xml b/app/src/main/res/drawable/background_small_button.xml new file mode 100644 index 00000000..767ee852 --- /dev/null +++ b/app/src/main/res/drawable/background_small_button.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_square.xml b/app/src/main/res/drawable/background_square.xml new file mode 100644 index 00000000..9df3ad52 --- /dev/null +++ b/app/src/main/res/drawable/background_square.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_store.xml b/app/src/main/res/drawable/background_store.xml new file mode 100644 index 00000000..d7cddcfc --- /dev/null +++ b/app/src/main/res/drawable/background_store.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_support.xml b/app/src/main/res/drawable/background_support.xml new file mode 100644 index 00000000..5f5d3e63 --- /dev/null +++ b/app/src/main/res/drawable/background_support.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_thumbnail_duration.xml b/app/src/main/res/drawable/background_thumbnail_duration.xml new file mode 100644 index 00000000..ded5f151 --- /dev/null +++ b/app/src/main/res/drawable/background_thumbnail_duration.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_thumbnail_live.xml b/app/src/main/res/drawable/background_thumbnail_live.xml new file mode 100644 index 00000000..c2f960b5 --- /dev/null +++ b/app/src/main/res/drawable/background_thumbnail_live.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_thumbnail_video_options.xml b/app/src/main/res/drawable/background_thumbnail_video_options.xml new file mode 100644 index 00000000..8a55df12 --- /dev/null +++ b/app/src/main/res/drawable/background_thumbnail_video_options.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_up_next_toggle_active.xml b/app/src/main/res/drawable/background_up_next_toggle_active.xml new file mode 100644 index 00000000..00fc9e42 --- /dev/null +++ b/app/src/main/res/drawable/background_up_next_toggle_active.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/background_videodetail_description.xml b/app/src/main/res/drawable/background_videodetail_description.xml new file mode 100644 index 00000000..1e4df987 --- /dev/null +++ b/app/src/main/res/drawable/background_videodetail_description.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/banner_placeholder.png b/app/src/main/res/drawable/banner_placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..46cf51789334529573727f677ce531228e2ff9f0 GIT binary patch literal 768585 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�!?-?y0wI9r-CwQnCnQMQ^!2nX{B{&e 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%0A>Nobs7b1t;f>rFY6b4ZFwS!xPQ1 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

  • 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

    DiL4T?=7-K^m=R`j&O+X7Z>j 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�K$go*~c7m*6)yuOziKNBtT5%oO-~f8`6q(@a7oyE1K*GhYtpyuX74d;6D+p2Xy(HqQ5_Cdn5rKAdRMu_ zdvpUJcv&LnUhy|7Gh1!$SL#@KfJc~w<>(_*0CB8&WGz%TDs`~fd&a)F&L%_Bq z%d&RcRi58FYy9TUN2!%rd=ULbjz(Zd)>9gQb4P0zZ<2`v<P3aoiwwoAxUZ?~`DUfeZ*N1-ZS(2z5O+ueapAaAx`Z z*8Fg9K&!3SOwlIpz2w}iZvL}&u4X?M z;hYJ2{WUZ@oa;Ur&IJb=rpFKppP`N`aBC`?j{E8COgA z=5qTPJM+UUl%sa!o`21dvdM~10#?1?V6(bLHN7h7vb^=oq>KO*;Ak7sVI5Go49 zqAJ`seZ?+sZ4Edbult>}Y~0YJDc|szU(owFxz}>nn>o_NJ?nT5(qg@e>ZFL~Nd+t3 zP~?C>1WWyV7kYuZ*ePn7pK8FKEffR8oD0Nz1Wuk|sh!=Cck8dTc@+x)OPIX(=kq{Y z@`Jcg7j9<2^(s3f96jQ2zU5a5Weo=r|Nd4MNFhOxQ?4@y0jIhB&r&5uq7d#FGjz2< zzHm#|&$6KjH!c@LI7SLS03B@LPkjg2Z@i}G=$-l`ns}8Ng0vhStX<@u{M7N@)B_Jm z8Aj6sJ9^&jDjQl>JsS2YRrcZDbWAW1t=?NZ-mGNe^5{kZ3bx<1lzaAgc0=V6*TK9Lk|2L*~th1fQR()U(2p<=WA z%Ytpx35m(&Q=j4r$(}cDsqfOxcjQaJzgsaCBT;2*8UvD+DumjiP-nh+a&zo>qx0z< z<1o{y>zX#U8+W07HiQ~=7!UH#Qr^WW7^^5I`-&17!sNPV5o&ub(gl9d%V#W57fM}_ z*lyWh7)8Aqm2S1&@O#feaSB%_w;By^&&HN|-K{1(I(Z^K(0VfLhYCwdhyVXmtr zuVi@VU^KObkH!}584--2$;Dv5nELZP@?CTHEWgy)ARo|0r%FYQ+K?{$y!#Ce7cLnt z)b4a>NDqBjQz{zRcQ%9>pGEst#az&xyyu&@W<2VTsaB^&gf=gHviX$FV;wFq@=oxw zsHWJf$?PZ=t`wz*)Jvw&Z|Rqy1(k)Rse<7%;LR{La&w?agb3;|8s5V3~|N4YSBiuK8eHN%D zc!F>5ie~~+c6B!OvuZha=a#!5sdVRYrCm_a>voQcdMophe3^H=GZOIkHF-90fWlan zClf`E`#Z1ijCLI79`KyzuWp$1~x-*i-~~%@V%Ggn*z^CqfeGgd9|B; zn3F+?!ao7oN25C72YEwGIotBLz3{#SJMZ6Cnw)S$Nkkif7m0LwCSw(O=Ix^yl-vbv zACK)(QC+MSoC;PLf)St;qQfxi+~CS4pu`oHyj4|WB{7LBzQBi!5}bEr^hO>wF<02V z@q%bU;L>cvLk~6Q388h}fonjlx-$|h5A+Y`#MxdbSD&&8x}dRA~8FDwxev1X_(%{of>$z(s4j+d9 zf$(&@FDRUXiAB|xM$ZBS7P_e>=!A+k+Z}xbT2WO@OgC2-2|rI7u|t%dR;@p!S56Bc$a{!1=#%FO@i>9ECgdK!k?LROJrAhQn%j7Ku@ts6IPT zS54m4mcKost+whib{;Zk>z(g-Us5UYRQl+Z|={#b`c$*zNim ztyghV*+o7Q4dUe)-V2Bd@iU<7C=3!cyqwI1t z-+^UY&q6tPEbZci_nL5k==j5LnSofOq2AD_c1x@EdCnY(&v=iHk6|z|RvV{S&`@=O zv+zJtK3ztI{%K(`S35Cy{Nrz;`J6iXKMkHlby&O>7H6wS?{?UshMNcSh2)$LcUEe6Lu0zv&x-@utSvLNUgqHA??D}FPn4U%85C_O=4lnfUfHPpaL z9lCB+DZovRxBP5jz3D{=U~^MseOl*Ts!+Z)$7s^eZ*PqKV((q%59~NEt^g#6hO3L# zmUY)Ja6Lu-Rfp-47BgnOP4(GTZ%W^lMK5HXgH2=sp|hoR@PodZ-R1%H&r^K>`C^X7 zEP=AEu>EZac(kSiUS0O*726QEK$toQoEsTTu3#CEz{iogO?22l!3Z&(m(L*3d4Dl8 zLKbpICxN9bDeE5IMg1PuXSPVatUqMBZ=*vNB0f?54M5yzQSS!R%d!)dFPa`mS#(3! zz)T4tCtT{4QJF%G-=(|H-mHyTfZn{>fC?7Ne_N)Lb3+hH^{7y4@wGu>!BY zDfLdD2O|)U2M>@@^R<&4_HRrXqoeXp&j4#VuL zePKRBAu|Q0u8rX#MX#r-h0;k_X)rh~=-w;v)K!2u4wru!8Z$oUKkD9?qj0Q=@kq;x zdA}P_6@XT$elQMf^A~E{A$4p+T`n22Sg?1tY%~VgjFjR3{;LN;U=yP-O9M^EOx%N& z%pb}BEUtQ|Je)W>dX7>W?&?5ZxcNTWTc)@EnuSX$>JJKz6q88$GaAT$FE9>=dwuDY z=%Q_)o%@Y*+<_>9%@)#P^E&2cCl*(+6}AVKIJ2ex@HBllF{O6ePwte$O+Vmez1)^h zC{JEcM@`q-j#6d4kt);ZaAZ_Nvpz1pnx{MMyDjLeVfm@8(Aq0f1-Z-G26=c zY!+4M;O3q=njtLL^x(9yZs_e1g}G0flklNFBvce!^yH3T%iu zleBf7_Sxr#8-j=d{G`(vN&)GjGA3ggl4?rvQPN`WGxPH`%@O0;;f>=NNF0t)8Gf)| zt&-Z0xrg+Bcy$fV)f3j-ydEyMioGfq@gpM^1%B;3A__->B)Pq~ffz@=i)V3C3Q*|| z>cSqyZ>BXo+dHX2mFwyzFVj zgRu-!XTaK7gxroL@^sGUDaGfI{2h;Cy?$A4Vp_|BbDMH_l6CN84SVJBn@bw5AsVoT zV8aYKRsC+R%)m<}t;gvnvhZEEQ*A2>RQT#w9OTbvW}gAoXX6qRg|?tV`#aC%C~meM zE)NfUEtyzQR(P{~3qkjHb`65x$wE2#=Gt8lE%BJ0T`*U6hda&Rm0!k#?Oh`FWWFY= zd)r)^lz)_D0qC9!Eb+pWY|Uj2+xw}U(hlfP0I!IM=h>^LjsCJLYxiVi$EDVMHI%@1 zOBnRZh!|r(G+D(FQsM-IlfF`WWm%1RAWFp&nzt-w~jbtY|k!ZliIR}ISIAw=F-??qT z;>!|ESwo>fRr5O%yw>=J2c5&i4!zQ@%K~}sS4Mkk zcd*WIsv}LJJDnDr#oQ)qcT#ontqO3FG%P^+By!i5Ep_2ai#RqIh9(nM284zYglYF5{H_7=!D7C`-Y zF}wavN)z zh?hU*S&TK&SB^4jjYOibE8dpxxmo1Tg6@~PBr?yLbeN9q=^BN}B<7g1#J&!@g!VF& zV7^l1a|saQWN_c*#Ag+2*)jG!UL1ldciv|FkD_yrXZru*_-M4D+U8!;%t%aOsT7(u zmzYW+w_JvmW-hsuTP=;|J{4_<@|A?S#oRB=Tw+qx$Xt?;gxo{&+wVXB@8R=4=e#b@ zxRIVvMH)iZGkDKzJWMi|j1rn9-C`^TSR9-=$_tNK4cGHHQ zirh!Cz%fr>C0fWgVO958I0CJn)j;*KJ4_N*vVx$PJEB~^EZVn+RzSRhz0+P915(cJ zCNocn0f^WxeQTWPEtfantcY*?uzuOlqHHHwN%6?!c_`Dy^aXK)f~(|b1WDNRx6|kb z9ZQpm4e2l6W~4K7Inngm_Tz7=>}wflQBDLY^&J=L%2hNI$KO6jp#V4&t7e`4{)d7# zLVwNAr|$jXN1ko02AzB}y1yK*`-y#@UhA`PET;)@tLK{|5C#e}e&0k!_`1%xt_0&; zgPsj|_84@m)-+e=dlP-PHONJfG{EQ_Gv@+VqXe|fKbcDi#3C&pA5u8j*I3O?sN4!! zr_tU9-HVg1EntSo!^HKK=P{&CPXOq0^fK09*2W$u-scwDu6w z@9~r4JCAo(?ycl(U*DN9APe@Y|0=wsswWF5n?wy~zyd{UcIB2y9J!8(?dIdCdB+dh zF1WycHmaq1>wT>7o~IO8SIvY9VixhB6|zBOn6pK>TRh#O2d zr!b{oM_}E(gnOnAYtvqAEl$eJH)@v!A#mL(d*;ob!Kx~$^#FKQkfk&d$wB$62Vzct z&x!?{=svT7`j~#&(YE5#P1rXy&&y)x9pbfBD>RU&O!$20$(>q@w!#s-3GK zwnbTcJG!$O!ZHv;1gbPJsXH&BLI(0mczOgs_o^v&$R=Gr;nuj$uYUDcfCgWJzff-e zl2HK(u({%B5k7WkVcnB7bQ-Z*lAFj#eQRma;+_>-%dwaC?V?)H@STvpOTw^L&qo=d zPVpqJzaI9+gmEd@!+y`hfH(RoXg(od=NwJ_*MJ%~=WNtlURZMQ)%5L9=@(5QD2xxo7uGM?2Km%*6dHT;4 zoH>K`)=rFHlUa26>SYE*JfUk2TSYUO(0?v}i0w<^z5G`Nd{4DgJ-os&tewJ0k+zN1 zz7|WGxsZ{(sWf3uK4JN(yxnIR5gT?^(J|sWb|l;@;PKYBZhHV4n216e-oZe!1{fqv z3!98cv;ZB&5AByw&T-ASEMME$R6NVs@bt=KFg#r}Zq-LU;SMs`OE+r5$!SAuB9Z4m^KpscFzjsFw*Ji;2`))6)u?9hR-$RW7@A( zhwV9e5{($%3QS5+IHG+j$8dAMix^cATE83qT2uP^Ge6iCyY>K~L|==W34VE5Ji_b@ z0@9~YKC&07`Ls3U86{q{6QK#ZBVzXFVs&q`(dhFV9od~;#Epqlve_cfuZh?w_&8z3 z^S&T4guQO2d!OXbi-1sGuFP{OEx12_?)>T?YN4-Gdgm!V*8%AKT&?-O`}DII+MWA{ z|LmX6cvCfxX52~I4Kx3Ap%t3y0Evrvi#AeSR$36;<>|(qLbrbVvl4zUV%SwpFI#>I zFP=d<4TRb^S99l_&(bvdOv7IuBbW41A0ErSoBhghg>@wt;`B#&&h9PEF>Z|9nz*{p zTqWM?JMZ^ou65>M885L4phuy4^NOnMB{BJ@w*~C=&L1o_uhW3sVAY;G8 zquwZWJq%7bse0Ojs9@1ennFWg5ou$FS=CO^az=qObz$Z>XAJn;(}s!-Z8H6G=QS#NI3diN{9Xw4O!ZMzAjUKQ^0$D|7vWYHQ71tJNP~wZbbjP zv0tMU>$={rVyjTDXKt|+*(XSg+Y9ipf8SijjJq!@F9C96(*WS@ZvyhpnJ0=`0LT5J zHLU7VH^)z{OkuC@D}T3pS{jrhYd`9x?(qTraLL-Ip@O-_xpIjxNEOe3vi$6l#aCB?Q`D}anVa5w$sA= z!X8TYhvY=f<6%_C8~#jplBpL1-5)lZ5MWy_8X!L@1c|@ISP{Nk*}8p?#r^^V6+Jvy zug~T<&``y7?EtRXyS%*)c5dsKZvmu6=7!T~MqMcnk0qLktL$Cx6PTYG?}u67@Rv=O zfU(r=Q+gsgcXzgO5K9X?^8-y){NO##9=Q_l;=azeto%FW=D!=fEA~W{Ph)XcgDt0(@|px-SW(S%ayEFs#QXyMN3l9wsbqN&KCy>rbSZ$DwE5SS_DAe$Pd z0oA_|`mIbq64bqHY-UG_5q>L)(ec)zgL=p%9nnwfB0jNcu@LX*(>H+*uG6orf9Z0B zFzDkW-RdurKyB>=ae{{zlDfj5jLZTm&|I{>TI`cg#r`xj|7%;TLIwKmpTEX!eJTb4 z-3oU}v8wWCB^k^rpC~-EI-_$|p9p-1nZr`(+?#S&S;;$QJeTyR1||L7m^{CHx3Lm9>&5*u!M0kvbif z53}r~jyMUQuTx+K({$6kla;!4vkRk`%&qyEcD1dO6wuFk0QGPvxQQMA2~lh~uHS_< zoWkU8%AzIR3771EmFICdhqqJUcPd~J937Kqo1%@V3(b_K@fkzTOs}^pJja)PYk>1` zXOi-G|09GR0w4`3s8r^P(pYb-ox7b$q&<^EPUYr$d27m)8IgHY&9|M2m6eZJVZc;O zUJNu^!W5~>Zge(FlD9h7v_99MY$0^Mc|2?49HfGx8AS;c3&m^*<pHFT178sh>tcG^!j+XL>qYKHd3ceBsS-qj{G9c%W+v$%XaW8Q#58TXnbbEMEb~ z#gj_%qiN29*k`tsY5;sV!mS(8%7+uzTR^B(T>=(*=P`Xsp|i4zT=Ll|bD+30IUXBr z@)?mmIHj2k4m3=uujI?5tpI^yFAJ;DP+hE?V?!*9Mt#yoDMd3eH)iM4OqTJNd_v@Y zcHCEN)R+33*}QRJUa+{4=3GC&D_{5Z!z-kg$X@~%q4GaBdBPX#h2?Y3XCQLTWz6j@ zimpPH&%Qt0#5;h7oPhNPFHf?o_E3cTa4gV*D&G5Dd6K;#d%>ljvotYr&Nhy2n(Y9` z6EL|Bv;P8jV`N=}h=5%(VqN@4qafI$lI1>ixsu`hGf16TrKi6#kB6q5_BXEqNp|ab zssn3E6vu5B(r{KOLu=kJgnAuI^xx;_7zb4oyLLv%kLzibGO0eh?AJyimmJ=ATwAl= z_3zNm`p(5mp&lR#+^do|T4$tH>8NxPVLV+$K9;9#=x9r8I~Q_}txF_ocj~r)3;Z+9 z#oD9eTCdc)$u7(2CyedPue;sbl6~R30QeK!7-OF5Wi*10I%@xnRB_zn(O32Y@jhke zm)l?SoiQP!sZU?=Yll(EAb()>hx56-V0RzDXysVxKD(*#Ml}h{;LmI4pD&#Tf3Qt1 zS>FGdAx}_K8aM~3u~EhIS8F^u$K^fnnJN2dPRSWh`@w-(o@e|DD}0rz4i+?@Zidh)9C^@5kF8`E??p)EU$wA;CpIh-vdX4CP8$<^Ar=4bJn9Q6eo~F&~o{ z6vY7H2qY1EFpqTYpdU(G)mBV3^{wh`YYvqAJ=L-?KM}GNnc6vOcy7*$JqgqP=BbS zh*iFt{SpNDJ6G*LU&89?yxF6rMf|%a@c*nTf|CRSFN;c%BVE^*w^e_sw3&(^x$a?R z15Sdetk~a6w<3g;%tgUgN7L$ZAPk}62t=5Lk5vaqP86>==V=_qjGe}O)0??`<^1iZ z`KwTSGO>Fkm~^>uZPHT;#|=MILRS|8-Yr4I(BF6(P3rq~EuC5)T-LO(=>l(&=W_ub z{ZtTWbo6F)Tu61_X#SV*h{KcD&BTR4l6TX?{sM-Up6Iw>!}Uc}rj^sOM(1ssCW1n% zRrzFo&`>FdWtVFKUjvbIt)m=~8EPJg-1?a53`xjVKIsCEsJ_qBrWd6&5$4KEq~jRl zL=!2nI}}fgQ}|GAJXKk)H<0z_V8kq$_pv%%=;c9OP*&9l+p`N>wsi3Ooh%+x!7sF7 zSd^1K!&l|6t_lAh=IjBN$)$9Y>e2K#kkbIjnS{t2>*=fZy#5{F-?8|oM(d>I-L1KE zdD_l4y>N}%3z#R(r%2baJT+0t-Rqd|_zq+|uwy$kB5^n1&n?j|smr%+dtr+o2n`%B zxbb!mnL`;dVRsrGO@VZqbFi3?k!qImmp{^iPeM@-qeD$aIrDzNjl4#6o#*qJ8=@Lx zx_YTSOk6ZWL|Kuo(a-7kbZ}k97Pv1DuaESr=)!%&u_q?Wr9hF^qI(jY{e{TvUJ_4C znrQ1BoBBv3uFA@10s;XFjUclhax9T~i@agVJRg5>C_(w zfOjY%qNTd!Prg@t(wPgsF?}Q#8L7iC&s|Qr4Le1y-ag$ss$c6$rQuQ~v&%2=T7q1h z{keJrK<7eKt{+oTlQ56BEv?)C1xjGlD(aFRk5-4da{n&Pys1Bic5Clq>Q0B(&XM-Q zKe21wSVu(DLwjC@g?j`RajS=eKKn`vUp{R(H&65K18v;8&3f z+jXkgugkx$&yT+`Gu!I!;fG|m#_l4RIiN&4fhw^;A@1ueKtJuSg+v4Sn19Mib$qWE zkU&Fz%Xe1kNY#PaQZH#*+-qoXi1{f}wZ8Q4?&i#L^@9Jjfq%6T`lbH5&hltxn2h8_ zQwCSW*&l8HBDBXyHG(v~RWmiXmMjv~(-fkf?4Wqx!ukp0tnpC`1nt}2<8!pKDMP=L zIEJUh<4{?33~iEi1g3LIXo7ejc+>|E!o!n#lwiv15k!Zti?{w%ko_kbTD~3nw>CQ( z`u&&ly+|p0IRqtGl9DIdMKAGDC|@=91;;60p4)|VnBpdOVnmV|8o5NfFRI?hd-1L%%8Ycr(Ual z_&yIaerIR}IYR5?*&YcbkHTBvo zcDiJSK#CCHq7aj+um80ubU$Fmx%zKyZDl$_KA$lbNmzJ{{WN6U{vZQu|Ap;zjeftA zL2rws89){n6%bNB7D&;VyDZ1INUvgh`3=p6Az^F*OekiGP%C^v-E~AA`|(sdRkriv z+k=2U-?|s(5KkOWcOavBz*$(wrk-zpZPjY`dEkj(r}jGM7ml~{f{JGirC+QX`Pxxz z&tzJVdgf4Akg@y#oadVTED@n(v@<60-V~q)<@S#OStr*a9u7CB;1+cg~;Z(KLHumRU zWyJD+bpr+?SJmSx`J%D70>q$a7LQKzrvK)3x~ouDWQq3A`zRQ|x~y| zGafz+yTw=1Hma}>1%Sr);(;QbPU>)MPcY*h8u0r^hki-oDOXp1R-q+VF^t6=D5!oV zTJ6>KJd3L_wI9lN|=85gxCb!eKTJxd!p#UPQ=! zSd4j0>G{e>dVnW0X_^+6b4IFq2c>`4&bBqpk&J0v&qp6lU!02&J^pRhIHOPpm5Il4 z-P0-auURT#HY{IjkE8rQ84oOaml5rjWTrO^nHTTC$Sk^v{J!tb4R@U`hbvFUMw@pL zL{+F^p!2d1p(c4M?pKpX`x>g)?HVZmS#lytu%9eA)*Pv-MkZ+0EM9ao4(eZU8)N$P z97n5Qy=V?3(Y$zMO`-b~pRI3pT?`-W_2MSiqa{O9BMn3cTw>VXxRO*WJX{`AJ~4V| zI^-}=ExLux*KKrZvIw_1kS)CU@a_9rwLNk;HmQv}oh;SvDesY(I zRD|Av0g+w~@W&GMTTar@xY>UNG0q<8KcB;+zaZMfPsHDl-2Aht-AvrL*WG=d7W3mZ z>a4EOzCPN#>#QoF#1-UaKRa9dA_aNJiJ=$&g<{ciHZmWI!K?jIZ?PoCe5P66Jab@g z_Jw5qg*}?z&U(gvc{JLwpE*81wADTL8AaDdipW@Q!LJRT~X}& zjx@Qu_i7Z23Lnv3rhOZk9liH9mK)kLqlS|Yq#7%)ds{j0qUcVO1rcL+H&#=kNHlz2 zH`dN`57be5li!{-6=Suu#fMctbYtob&&IVPi3bAF5Ld?`XiEPeCRsa zwD3h&F=@YkiAk0DaV}pFX59C&Va36#Nv|QY*ZR12^S?E5kRP8tVieaIf2%6J=Se#x zkHwnIE9MDBJ4aKy^sv0{-$7swhSoh@9u^ zX=Ag?Xv&HHg)L5DnsAt9zR*4=$yA;cv@)=9+IB_h73kQTP3DstDkI*fD_5Dnu8mSk zqV*h14PQuSrn5rq%B$ifV{ygbJ^+fT=Aox^DoTAn~$ za@Bs!hsUSHnFwSbywk|dZ+mfZaW?1f7ymQhj>IZ^qTce|i1Jwk5OeGJhDWBNw<`cAXOzUUz~vxk%=2n8)m$rI)LEDGm- zWO{?*N&_PrzpP9`n#}6r)4!*_G$CF&aa*=-pP;&lkNDPpD@hGCa0*ShVq%sEv5Thi z0uIxG61U0UAPTd>7>?^T9N)#m)tH(nwXS>3>5UF7ennsW_{Pd>qxnDEDfhw+_7dJr zf=*8pteq!aC7nmNEDU5e@pi^Cvt9(=z&Vw4doEE ztbvKeJ2yN7L)pNB7Xj3BM{d!CVoYw|Bf3{~8k=7_{Lc>q&*z#del!b6yKrG zmDzvNRBt)2Gkl;Mn?>S#8GOiXhXa(eXXs3D@#k8qPJkqC(Dw0%r=R~jq`{O`Xld{{ z5R^52Efex_YrPs0jkk|`^&TY{)%*BLwOr%1gf7e~>|wazX^Q^Cn?cps>G)8Dlia08 zCM`$|?lN46F2%W7*@?g?HKR2|+jJ;=-+~}^waVkN4B&kL@Kckdr=B@iLCB5Y6)-v7 z!ht9B+{bs~73WhqN{+{fUDI8sZ1wMcU31hB{@RF}s(CE82`_HIsR1Tq{5{aI*XzwU zMomY^GZa#@Yfe}BbJ=eTxQoYBsuvwhn<=|j*AsXoK|A96HHO2W(erQp0B=Qk^^xkI z+iyN(nYq*kDyzPHeazF#$C?_k5Oo%JSQ^$9!s4y~%!ZQvh+qqj^4qTd!&A#z`tap3mM#OH~`{zKq5LuC@s0#7$ z-~TI$G(S5T@*ol5m*gxoX0VDXMtg0p1rRGhJV>r?{`}mX@syO^>NRa!108n-%kPxV z{gg_bXC_u(b8zDedH1}26f}Y~rKPtu3Lg*8%DWs%&p)lA0D0tqZ1FWf)S1sZkDD+C zA17ztd@MX`3#^&b3P_&y%|O}Kjtl`mZRYgFM5SpY0caWN>RSbdia&n@Lz{_GkM*lJ zu5WEXQp;L2GAx5_dI2fXO~^c^Zi2b{Cm_7%Fv(MmKw}J5eM64hNQXL%t{EAX=*E;+ zvpAM)KD#R5&^V>+PzM8T_4{!F)*|H9e!2*FAmp{BY#RIlxQA6er92Q8UkEzXw=l3v zQ8GUnAajIuJC)@6HJkDlm#A2yhdc1i66-7pop$J?axH$1$L!ySf#rXfxTtI5#jxI0 zYIdLQzEP*qRY=w0XR05CdKSWBctiQ{Fm z@ml@{yU=fHy<;I;OZclcUMD2g36D6I{AfiOE)!I$|H_e6H}#pl3;q+TVO_e6%O_<; zktXU2Yt3Tb_qb1iRAU{qQgLZ8={_`!P6sm#7+^p;Q%C+*HQmx~N5$)Qzq;7`r_qEt zbE#C9mvNqB&D(3MtCQY|j#atw)ZQvs9CIXhNMq(><;z#;Kh!T%69SIK$?Z)YR!kLk zy*Aw4`FB8804gd@CS&2+?_C?)~2C@W__I0%7vWKj`?-gyLq%x^qW z=YXa*M^SB<@sV@jO7ZWN3e$Q;-MUwW2-BnHD7zZ@yG6;VScQ2u3bH-{U#C@8Nua=; zyxf>O5{HRcr`kvZJrQ~-07cj)$mXP9ylBYNulEyC26Ha8CFFPLn@^&E-~tDIWM|&# z(%DmfoB*SerH|K{e&-GCMc4Sfx-e0ANASA)l(#fQz%~VnJYw@cjPjkp;F-69_p*+4 zyhV9iZrf~_U}eZ{wm+ks?;gWQV8AKxw+|q?WEA54%4h^O37lnjU+S^6Nj(mj3t60q ztXzv6zfO6e2DtN2@dB0$zd=ISc;}BidI_CAeVrUB1WSyLHc9up1i75^^&yQHRT&*1 z&CY=7)lVD!1^dw`-aymA{%c)d(?`RtFnN`=^kG#;uP@l^9^3cJ@XqP@K3WXKOT-d} zDe>USFi|jO(<4RZah~4YJPt|ts^Do>jNav&Ebs8BCQF_DU?d(_j#YrRpm7ObazYOD z@&Ye-;D|uFdbF?KSDoowI!C{!%`4vjEV}n_{9m?K`=?DY04mS+BuNwc!ujDWGJ56$imuRK!c?><-g7Hy_3F-#oPPuNmUk|5 ztL&w{&7k7$zlEuqcgG1#;~JO!P}(oHU#YM!OkI!DO4H|FM9Fk`J3jBq?=b#_S<+^?_((DGyw}0 z6#)+w;W6gc;;L`3hO7JP!)n>86Ln((e!m>tL>>mF2o}Hjkp*^wSG|IXyGaI2kVwXf zj*WBYvn-?lP@)#VB*ToW7lhcYw`*!%?dnPv5r^S@b?=VDq#hJ{s{FUQt?i6N9j6>R z2fbQ<4 z=u%$$`qnJ#HSEPr6CN1g=zYOi7O8QKE~YE}xDb+RQFQsDC@mEKE6R$CtH7gGC%K@` z*+3CYjdUxYeMnLcnOiH4K56H7&DM0%=LwB_Jx8ph;~Eb7g(Ep*^H2qD_QC?|eh)^o z$s2JshN6*`>JGP!a5$MXb=ImiBL-I`>w3^qISahOE0WGDu<(RBc0+#e_uPoqEYKs7 za5D3&T%klru%FZ(c>)&w>AVN*$)e%#CH6)ht9y)wg&{u>waTZog$>7wd+`i>_f!2l zh{`lu|NTg~`uw+<&QB?O0D1@%A?(hLR+6x}>Ey-fe1{H)RHwH_yAz-&JeBrqoaG-R z6k1d9v>zt)AZ@};Yq)2UBxE{^q%Fs4FL`L*KC!rXXQ!YghBmk~diqWAOt*{aNb7(t z*}?)l1O(X~Ao_L$CRe4$J$iNQVt#(#--pRMXs3lv!zi$%H@xqjQ0trqKW_~8KYz*N zUwrgo_`k=(^L3p%8Z3!$OCcqSJ;A7zy~SmDibb2(bhyaJQKvTou{O58FD`ij|NI@_ z7-FYqyIhq}b1q6un)g#aR$6+fcW(v(^v)BCp!4{43?h7HjYcJ)(T-$e9O zt~8Ev8XdCfZPx)5T&_2pqdeUa(leVyGJp?IbfHG$(>ZsmVfSYmri5lYAuw{#8jF;Jtf3c`Q{!TKXeziw#{YZ)P{GMqQseU1@Y5#r1hR1o0?YT1x+K+c8-T zw+{f&cF3IOpQv^qu>ws+-l^*4Rvj($ns?cSK|o!Fp*$9 z60OKH>_EW)MF7OWAK{(|6%2sy9JM-85v|l9VgW~|8xF`V(Q?LF!Q#UTNw-#Vhsrq5St$EbdFG>(R zTM*A<&2Du(gTd}t??(6tF(n^U;d)eyC$ALY6*p#2mn4|_it*W)d$@P5@>E$A*E z3$;g|IdtLzk60pbCo(hg))5?nEt))j&lG0}J*P{0l^ddHg`X)ku`8`JnYxyZP ztBshWLm{pci-GSwJYHC$vY3GLl<{Ob&b_-&1Z?M6kAz8>eb;|jFO5JG#j$_lH(kp* zPd$vU9CaKVp-J?szBJu+5{q9xu9}-)$Z;=0`Asz;49mZ*I7W+h`WAkTGksGT@Wy~x zbCoX`DGEqt6tY6TgkL9mbFTY|>8Kn8VCv?bAM(mC{7n_SCKlD!Z-reOucDcX##8xq z*1G_xnLR>Ic_x6OS9G(cdNc>drgmcRa(GL*rIk%FU6Pl2i{?YAZ%+LqHb4rm_370~ zG}_T?EOcLW*oZ4TpqLPa6$sqT6|x2J!7wIZ+R_0gKZy< za2E$4q%##|6^q{6d6>dhD)Lml2KQgJ_U(RR^t8u^3UK&O>8xLelcY}E5!R5H3YW*> z0xm6?;ezko%%gz;AR42O)R5X{Ee`{pI#_|S<@-G`#R}P&x|m8u@)1f<3=J*xxDd{AkKz`vi1o7OI3Cq5ka0@`wevH27tc4 z{;1*SsGeBG015dkkSV)`XpKx0iraMA=5dV4bRQ>G7ZDRSsL970@1E}}RQw^A%c@GY z;8@~B?z=P?2d+1fb6F=6g>4HJlX&f2B8v^}m8&%+tF65e*9M%5d{p@~fcxhQJjXM& ziB(eBXMCh+LxWHoBEy-)x?w$8*VCsPf74%c^OGYC8i-Rz9MNiBtVlETT!_GzF*_fD zS^itIhq12Rv;$si^{4@;M=#_AiJ6f3+uP9!5` z=#2>{ppQVE02^^^_a|1W?dX#@x?N$qGfcrIm7mVSA5U4nE0-G0RC!FJD_C-*Y7b_z zlc|D_G^id}Z+Myk{=iPwhmOezBNT@Y9Y%;@@7I(pn_%J;DS26-^z(RB7dID{3huo% z`P(t4=xpz9VGF#$(`GvhYt?}{k9=D`}Rv5cHh?NG184=O4jWI zF#3?X(10;^e3SZ)`(x-Te?Y14fX*ym^>USNs`i5On7=vf4zN73ws9>VKRK1S1OR!6 zmT7{kjPBZ2cz)UL}nR!WE8KVej9#*^=aE|8Wnd;1vN!csWyhbmaH1 zyPr2q7!0%uij(VNgX!&3L5KzjkK+)y-ZL7+NBj2eFYHA3)ZeETUS+BVthSXdW9f`1 z%AhiS@s67MKId+U;^PAqRM~Ml)C9?spcmq~ii_Q2J3@wS^~9=rVJAFk67uk?x)z!F zqAbEG-`rAnn}OJ#n|oFZk}9xZmisb#4GtX|+o^1ia#>$dV|r-O0Jv;={Jl|d1?WJeB+6;eu3HL;mFC3_fCGNNCU$_h- zuf9j06m>lz@ob_Sod8N;%R%CQR+s`v)}6R{$L{ZfP@@av9-}I&^B1A-3iD{-OnGDOGLKD6}E$Nq`GHIfr97YJLUoO3bEdgdy5IV*L( z)zH29AmBx1m~DuaCeDc8sv2?Kp7pqA@zy1E(VJI<(S?M;&AGaxa`tvWcwX3^v~0x= zYq6IXj%#vwL9Q%+@QY&b#%A5vAyvbH{gpH7bIqHj|1#ZwAs{z`hHlyai`-ffbYvc{ z(%)Ixj#$uE?a+533D#`2%wkV)Fm`h7|5aD112txZg}(lW#`2)IusAjAzH4F2-q+4x zyn_@V1Giwwz)X1zO$RQhV6XeTEi8;JG;eKdNvjgWxV8d?bEmDIV(78Ny_D`GTlYU< zd&xb`_b}LnrL})%eQw4JK~?Pa^^^|AW2(Z&+&?prU{3B(t*>r>##GVwRm>A!WRg_| z_Vq=24_QhcW)7dKm2(isk(@a}C+%K(qYpnX&yPobZ{8B@lyPv*@b~)tZ+D5K)_*vs zSi%bn*Q$u3Yf(7^HyTAm@C9+#Nqgcr+9Ju@jPA;r5EXdjy`B9KI=<~zFQz3|HvWUt zhG4)AA*Ujmn)DVsBVr-d!tKr`z`qAq<9Wr;NR9p&=lYy|#J?t?9NojvwMLc?esT{e zLCkUN&7Ci9$`j?oeu9KBOs=KX-Z$^KykEqiq})3q5J{Fn#DeUzkNGo4k)PoGq@AE1 zF_AhF79o2XNvmDm%ayZVUYV`i)7guplIlre^!j2j!y<9(h4)^_J!YZuy<7s`l@vI#4DBU zW^oimG!6P9s>6byhEd=6+uu9Z^p9UXS2vcLMoZc5pV)NhzlPnCON|mh14=#$<1rnp z3~(~i|KRH>zk#YJ=jkAi zWGy{v^#IGL#A7I72d-o&d5rk_b zsExL}2;KH(=W3R6MRdfOx}Mdgnbq(ti2lAVDjZ3mO;VoBQ>jePMD>y3Y^oFIav+2d zA`7jlzI!aT?TT84?6Sf{mz2-XblOpbT6^e4?~i;6WO$2@zDAXt1w%cPf7a+qqQ}w^z%=wZq^<;gA5;BTncx+ddXL?S26K&e-pckclsv~ z61!Xay1EDE%dNgR+NDcYkJ>bOlp(fv(d{D+CUZB?oFEmrXw@ivrs3X=*&7tG*TBu0 z=^P=D@V2ClxFpZ=U?^5|BQ`h*Uu{$TGQ*r_5O<2V%Lrj-cfdSWN5r`QIP_@Yq@H(r z*`<#eps+vuDmHw2;-f&>HSOi`Hy5j0rIqw191VMTQs(L}rihe)8_j1+?)|BEvyEXy zn)>&k2wz71;yh9t*TY;Z^o4O$y9jS!R(EYEQdwW)-tfgQm8&Pn_g|TV7A;#lGa8SN z$E$k-RAG;i|7qk7>Lp0XVCt@Ja1>N3^p8CBBG&D*bDW$i<#Va`%(kT%y)Sht?YxmMdv60_Jj*%wurCg{wY- z<<-b7rSYNA&f`J&%8Z3T;y^>71E@z!XK#{G!er2%xI5$pVT9cdNbmmzjp%C((w-X0i+YD3VK?A_cm0}9I@{dj+ zs4p8YrEM92E@kL>0N?}@xjYN!keR^ujP!DGFB;7=nKpcEfQS3As{Zz6d_1Ma z@;>C`xbvNf6kyimbfEcnuCF!d>a!e070K8=+^*=Z0?V7ZEEcP+W~}NY6fr)!RVkuO z7I;M|)9lv~h)w=|*nFA$7)1rOr>=nWiQHI|VAr`O4dPE*5~IzUuA|93CkK*N;G>3^2_C$P2)MQG)~0hhl6U zn9Kx=KS|G|&r?ax;)z42wKi5(T$ZS-zokp%#oyfQCo5I!2#y{T8+5v;QT+~EIHz>@ z+o^?))ydbZ;uKkCHc=cIbV?U3iScf)852HdU)giAEe-G3oT#3seidCjJN^7>b-UMv z*xYLi>-8zT0)Dvk`P=ZYupL9c^(DX_sP@^`-GsqYfGf7Y49gQ~m#Eav{5TDGeEbP< z%0$D0zzpLM5>ZbU$SI;8IRaXu1*14-oUM~A{0-Z>QuSkKEsC^>O-X&Oqy26lCS(p| z!JALUbcE5|iGaS5!iI}NqJ>#hw;JL(lyKswR(i6{i9wgQ2OJTkpe4o>7n z0~HL-SOfM&!H`+Y92!T46;h_>O9%D0|B z%L_ZfQMID|o7QJ~r6t(p!;TF=mqwVz;E<~gzQQfkiuhGZ)W?jGzP z?yA6T{$816+~~5ob9sZ}7r2422!u?^P{`8r^qx zipiX)SfTNAT({>{t;pp;i`y+-4DKaatNAJoD^mGIu4CFI3yZ|9(JFnPBwP8S)<(@j ziH_qwJ=#3^jWaxQ^2}6R3|7&ghnECtd<1@}-|2KFVr~z3z1!63oVuLjM*j5E+f#3m zh-fYi^$7xb1h`?HJ;di9(3W3lf;X_EPNIQMh+6~~-#@ec(&*-`-!CfUj%rq*IIY(Q z4N}rZ9O`l>wUGIrkySn$kczI;!mIT0+d-K@S*jJ3jq*Csy0CQD!sglZL4b%!Ud)ze z-_zZ;%pQJy&k*{2ll)A=RWtogOt@BSARuGzftwVH!$D(SCn}1a= zSL&`ORP?RpMhrwf&e{I8H2?D2QSyS`Llvr?hqwQdu(kxMwbnlHswt$&m`t9SeJSEW zM5sV~uHux|muE)SmTrAH>C+|I`&2m(iB~2i4}_BjX5aJdKCnX@SGp$=mW_)c3-tR; z*>^RZu9e6r!jt7wd4X6jug7KpQ1JWO6{FNqL3{4luU3WejxhIe&0&?)`2#S&%)&=H z_LE9?hf`jD&1-}9;+~`6)FyJFcG2c2`VC1sRZQ8fvw=*SB}R-K+hTiu=I=MrT~tEr ziQI=Sbj)skwU5?OHF=_&~qn-VRMUVmM}EDvIP0J<+&Ha9;>_T zEnHV(K_ahn6<+%||JQbbiI2)QX?fRNR*T1YR5Firx@&k zs`#_wEM~1icAGlR$}Qb|J25%^34w_`R5i+5UUe$|!nqgMMHln8miy~DE(g<_s;Xj5 zH90?@4HO`ZZmD@{BM#vdpkLOKs8mPVFy}(%R6ptkMMWez%xv<}{$m!km9BtNx8}9{ zeT@4nv_wmS;t9tz&{cI#KcjrrjFK2lK`4zoR#A5C<9B9Q+I_lp+U|Xs(D!i4C@BU( zYI~XyiV!EvHLhQ?6%sS+m^ZWl9v|~@Qj{FydqPNrj1L}96*N6;tFTD`0fw@;4eUuy z{BP~Jz9CIib$&MvEM|wn1F9U(&VW&Nii>iwVt}lf7aJV93W25z6_8wY7Fr zg-u0Wn<+Y58KPIikg)V!chU~P@duC&Ap6kfL!MImUbWj5ZJPk8VxGw{4y@urDn4gz z<=^YLy57X!0l36?tc69Ms8eY;OBCuYdwZRxOXl@=r@EKtKEJGRZ*icxKop*zTh>W! z8%jVSoGPMAx_Cg~zPEP*r4g}8}i81ifE3FSkTa>cCz=3 z@vAj)S;^R!N%pN7)U8mF=M#p~m-sP+ITjuFJ-imu5}&6^v0R86JNmQ@26khr1JdFkS`&mMkqpBSU?}5w7eOtW`n@C7b0D>tf-pm z$hS0?H93SA;yJ?O^7+Dj&40hn^d~YKashOMTza0Iu0?IUFVHylGZoMnBA2|x->-KV z4&9Rycmapf2y&j@LmtBIWZTwfp)F)&bMc8_JLaL$dj+VpwSSXQf)j9YOpbk;V=9z; zXSjEJ=JYq+V-|onANT|AAAA05k^gPjI&ptd6q(tCGnE|CF|w%ybX4lU=w}C##Aq$q zI@k3UHzxBn6s75x2DMPbXr8Z92jTFSN~a2`f)#}m5}KlVkLiU0PAhXxVx~2SER?97 z^7&lg-=!7T%Ih!uGU-$`Q6cyJXLUMTyJ$EvIcPEp@-}~awR!WTfSuxrcwWl$R7rU6 zK(DEEkZq3d9qOx<#)OF*t4SS z>=MaMkS*myuhYs1=2HuVJoQ*1Q{ zh1Q0GFs+BCoH21Hh_YQ7?o%hgL9zCaeUVHB!Xd?KSahjW_RObqX$f!^>t-14=#Q?} z*829`v^2Hcf!ThgbG;13WTIy5zzbW|PKlh3#+Lj&GtO@undPh6T4`$TN0`;c1nm4Cz!pE} z=u^)uCWJ^8l@hS`Z76&sn2mIS%i z?NaG3y!W6s&Ubxz)ak0+?T67`r>DO2(>`eR2K^d{s{eI@sM^Q=l7$=$Za&ztZM-=u z;XbzB-$2Z@cf0**bs-}rl{IrQ=PWrw752tm1w~5~mGMUX_UXj_#mHPoN*=~sy*5J? zYPJ`!qD}`htF&5KC$XB8#HCw)c)X(4Pjd$?&%X|r&BaCKaQ>MORea29>S?$<)3V(xbkaKC{+xVTbYKTb{C)=Zq=;?=e_FLu9)CuES3 z=MY2wRS+>ocAs>w$wxrcZ7VXE+cb}8cUEa%m=+r+v`~$zH#Ne4&>b2tjaK52D2Re+ z6`p6xabEPkmSJuXXt3IPX*(|NyLL(!dMQld63oM}xnABkci=j;ndz@lG_bPqdjzUW z@cJ9f>*$xYdRO>Xk-m%OmI9iXKN9E1vOWd+o?4W62TNSj5me4vwla?{>&W;Zo|fg4kZ-8_bGzSi zdXj^eN4m`A1X|%NuB^n=f^6_r-ZG2bR$~u2$CsG?Gy82r{#bKOkEc>+shVR;#<_Jb zZMMR$CZ2`W>S{~NKyiZN9P{oWvwGJ_uUw2bN6#P7rksk z`9YH+*DBLrC0uNrIg60Rh2Y#&Yk!=vAiS2=6!X7XZXJ{IfzSxzA0sGh6YE9%12>yQ zH7p3sfgan>>m^X^$@561%_cq`xlwLUEsz)-aZS0G$M*?fVVn9yj3nhM$Dhk)(+2iG z;KzX7o0m?lb&JR9+O$tQR6pG+vJ;;k0D}P|5BUi;6-}mEBm|7lqMOO|pSWisCMa#4 zYRvmZyY%6=d9PMj)NN+@^FYn_cPFObvQ|`9<&D?$K6jq-xTz~|^J?_$ugBEZ2Q9zj z*SWx)KVc>ULX5HIAG@e!Le6r7LoURV*cK6?KC~HrHUFLLnQALnBZ;5;X{jX;D{B3U zAsEFcGuv)U6oQXc4sUK6C5;%x!GUTk>NVYWVDEXc61wn?C|9g&BY;x%)@wjr25TYZ z=}ET*BQqTd_5VI4lMPC~ykVCsIv-HUGd`EyAx&3mxd4kFOSsye>;f)2YoF$WV&MW9 zvE*N>iwiZ69^HL@Pi;+SR#_T>)BK1Qe2un{1E9laSS}}$h{v4R#IST8Zg3}`-}LCp zA61DAm#P&36lJ2%Ibb~Bs4NzP&}S$VH0eJB^UcnE4>y|Gy5=Vol7#gpP1ieb*TsN zu19p*oL2j3Q5PV9D)S#~*e=QL`81X`_u#+8@pHa8uaw>96gbJ7Z{@*vfq3W!@*r4 zEjp(Ra|EfSq5LY+l0&aHBzdi^BZ@KFU>H zodn%XZQM<+ZyE=GUZE3+iF@$(%#onP3%vxtYz{yW!`X<(1{pvAa-w2zXJ4%CSM@nD z!i9nb-&L9Fx)2!+4c;O~|Ey?F< z#Cw#t-NXunbf2*%bTJuO=gqrT!XLe?;1ihZd+<$MjlEDMAC?dsy=qk8J;L==Ny+<( z6`O*P%1JF}W-qQ5)IcLSe+NEM<|);a>OxR6F;<+#&}6cj9o4&un(PNs%&+5tm96#m z3_0xdAFj5n`7~b5Xq-7vDSbknJ=kce6xFz%R%LEsai14vcT1BHyWZ=?IK~a|%hkd0 zr^P9I*Q1da84cxo4EF!xO*6&>tEeAx;b(eoIF{hUP5|Z=^qLF#)CWmg^)w&Px`DHl zFL&4Hg^4AxSqfDFv!VQjquUEtl3uX9+nz5sH{Km&TRgb=3n#`J%*wXr@3y?PZ4ckg z^jY?~Hq@xscu({iol%N;?#p`hirMmty>B?wSXs$dS2crc;y`@22KH~GbUw7`4}D!r zT}q#Sqj7F&;L-6<>TNOm0SCf8`6a1~BIVmr;4JAau-+~68O({>;rCyFZr5_`FA2W!$x5DViGQ5_YF65BEllBbO#hrRK!OJw+m&A>d{FQQdeUp;1Q2e-GgO?9OX zN6U@vG@GN1~0VPPS>y;FKXKg5kC&-1LSnTail_ zpL2~Em|qwe;CnizA&(Th`SR9nChqOzZ>|MW93UC^%qsm=fA&*C4O`|C72481y>Wx* zNoxGJItdW+iy<(jszp=)Gu@(G*~`uJB7N)nrK)}^7haIvm;*GYrRttuHVkzw|B?S+ zXrX&7w5mq&Kfr)CQnpVXF8@Nb`;CfOu$VJYTpU_>^+xoENr$0eIc_i?vKNF`*U)Hf zn~D7^%*R8#?_Uz>*G!qV#hK)h(8uz@*EH;sFm&Dy@#o16VGjc{-R?J?<@;cpY-c+# z8r&{!mJtdRL9FTaeFLP+ZXzDfZ_wz=F%w#hT-c z+Y>1vt3N;g;NP#uruuyepYY2ruuUaPq71o8s{FEVW}sw>N}+uCnBqc7kVv4iHOaRG zZB3FVO=TMMeVz|zwodToD^H#2R~e*)gs{AnZy6w8LwZ3`!6K?B1oK-Z`7!o64cK+} zoa*rBIrcdgLlYd^HOpa%jSRQhmb!bhVSig9_J&^cb*}ZyVQ8ao;kQpP$F-@~&R0AP z&fBNFv=0})8}Fxu{yYserHtzi(W#@Ig#8zO7MgVp$D6T!ol*b@WG79CkU?88lAtXW zbdyLa9CI7AYA7NZ0f^B7QSffa7sZ3BfaGY`i78f4f5&&^cS_-??s4tHOTAj}=x3Im zSQcJAbxCOdtmQ_^gtv#M zP+PZ6Br(PCQXc$NpE{2;otC0Ig$ud9)XX zIsaa?xO-C2ic#20hm2__q;gVLs}V7#DYTx06Z8xqdFkLbLwV8hNuTgMzW_mSdT9Tg zi0?IRTO84GPSubIal|y>?#%8G3LE#olxsYAMnIaS9@L!T=cMYn7X0Kr?_@e7AL=?G zoX&iDOU~Wj^FUY}K@6VVXM{u}0?5jN{+JpwTrr%yh|xU_dG;C(J`7hdC^!fgrDUYqnrC>{Aj(I! zV7EMFJKWEO)F5R#LYX2nmp8r#W=~#KoUE$9i={9(Z@R58qDn*f202NzRZJv}WONm00<2KSgT5?r>i8N02;o3y#|O{(=?7lcQ<&aW5(pqP6vz*0G@NcO~N zfFWD{&I6s!2Foakv#+TCs{ZXR1SbaPur{;3ZO&aoVx=e$Itrkj6X@~u5$G$1PL)&# zdU{Uo-%SYhonHDRB?JaXK}`ytxp&yY^f_|5MNUwMIcq9Hnx}nAtzYVK#RA@ath(l4 z^`Ya(ysMOEU&2!QDdg#%=lF5L-S*!pOe0UB=p+YizuNjRw?epWD#&5y9sp_GW|DLxK0Gk3101TRj> z4i_WJWXSnKyGHl(2=t_;nr(%rtKPB{I};#hHCL)}QsB}Qm1^41KS-JD?J5n-NET_{ zmhH@HsHcX!(*;5FC`*|TZVt7#Bu!j0org_i$p%0Hpj0;9=3|#W|8>rc4;|cznklro zNfkbs5k-sVhJ=yUplvWIju#t)Kj{?hb^}Y%v(VO5a#%ZhISNo{e;bs*>R^z6LEv{UuFVS2^oSlJK42Gt%qCq zWF&7gH#TFf$1e2jD}S5PvX?I-3S)Vk)zy$-^1Lv>b^h?*YH&_ZNHCk4jgM|Q?>Zt# zV>CRU@1mRiQ56*mCWE9(Qms`IYgq%+(;U>rx`rSpxh^(ATAYti<;5)|VDNTjUq!nb z@O~atqe&-Pkxh^gfpTw&HeUvl$?(r9$X+}qhFp%()DKS7?1&=Ug#r^Xd4B%hfHPS3T~jFRfjsCm2b14Yj+{o&m%IW66Y6 zO0xciBpMzyw@NGQMJ-{#LJxg>Uu?_u&0n+TTU0KhO+fyLm6zCo3yQxAgEh-Pn2nK0 zT$`w8VhIIyh^|EoW!I1Te1!8r}qMTFxn62f&i(+wl1e_)bP<935 z!&qP%J2Y)yJWm4gp2U{*5e^+mC19M?h2jxV4UV&<`}8nBL~{#x+FBQ~c+7%c9%nt?S2F_rTYi+pn4$b(6TsBr=vTk|+anJC1{X2>0pnv?X6;@p z>Fs|fDkBa~9+u7?_R8*e3&q09KaOl~q>Dy?1E;SD0$$jdqem)SyC)fLs$JI~&EZ#@~_gK_td{((|$u1kkJ4_p&SG5ydr(w#kH9?BqE54z8b0LRO< zdKZSh6VucCM;1`!2mS5)>FLY`Q8r5L5^JyWC?evrPveHEW@T=>=2!I>sIbiaveP5l z!IbB7x8BI#eOpJu{9!vZc29Qq_I{BPil#=t9PSpT;JX41J|6>6zbT7rQDE=sHS7&c zC6ZU`z#Qs|%MTR1GS|g|#ExGqjWq=3oQzU8vnUI{X`O~4 zJUp)2W%zz`pAJr4%%iQulTTsh67g_JMN1^qGWzz_0b*R9+t^qOA(Nr^>)fm5F_E~d zX;kO}?@$*Ok@xNqKn5lb&m)Gy%opz1@cc)9WOF}3NT79ln1{OS#VCOtWPuf_Rgz=+@{fMpE-J!#gqe`f_-w9vCgm2UAq(V;m7(}01F>WNNiS7l?6p8^% z>?+d*R6EL~-W=nT_tNO_((5DP9BY`3a8KrL| zQSoXi-ev%!wDMMzadUITn(6LH^8DV}hg`|HHMp+&>~F|);PYY%U*u(N9PCHEr`~lE ze1{kJZ(;WE7c+f-Dc7g{C_Um$YnydUXvmk`!@ue1j74A8{4I_BiG!UnRzHK`G_RSD zS!J)6V6m&+Z|Nu_icl<<$jrVyo6V3)r1IL#PC37hJw@h#xqaNp=nxmjk51P$vfDP4 zf-y(TBN#8+* zBdpz?y4%#gOKp&M^fN7wMGa!q46;I0;wzGLLlB^NG+ypW!u-zG>V%Pv>74JkxS*%p zLs07n?{RtCe-KYf%vOZjE_R2tpzFGXpGGS^#0}Hc>t7d_ol%6ZFv17j0~P0IzzZ_Cx|*q5g*;4ab8tLO z;K%p(@*!IV^cWp1HtKHBJ%IJ_1b*-Wnc|8Zy|(z7oh~h6HOQ;i%El=vIb*mTvBP_I zC!~Y(NC>6arb`2Q76Ss~^bf}`@OzXX^~a;RzSau_3KW;Ds&^_mGVby)N zQP;u`4{KOSqO+@w*V(Npb6(z1rhP2=%{JFl5T*R%7{6G^siFduVB2a`$Np2ZvEasq zXJy4p_tFor(Wg6m{4^mp9+hzvKNcD;M+R596ExAQK+4hzA_}DWEh;kyY(iW7sF#E} zL9VRyOS8)Spx|2re=aA%Ca`v-Gq=Nqla}}tI=X}R*)yKXe>=HHf{+1^-S0I~vGB3F z-l-P>7`-o3z_#)^+UdFBo&8`5tkd+Y4_dLCm(|A=rVqa|^tthQw3tVRJP-o_yc?D+ zXVidox)w#=JMPeuB#uDh3Bs>wYm?1vB;v$?DYP`1c?Nk^7E?XFW%6;E1Qmy zI%@W}vF}FrcJ{VYP5XGjiu1e6yVqhAOZv8&z4uqP4O97`ietM^$kT?Dl+UsNh$8jN z)PuNsS~Ksoj1BPXwbNFW0oBU*q=Rca{7oW zKmdmaFD1&cSt-T623j?K+6iD5kz0qP$J!3p?H%2y_J??S>d{FzqX~DBSJ-g?9+0I* zwqBiVAahcw(#S1Fe}o;kXR56cl7-#`X&V_^ai-x+D|Eh##d30sZv;$3PxVozo}7k8 zCaXOMkHq0Uoj!y6l*9z#;pI|wLB#rWNkyyiTNzuNkeQ zUdp;1A3pO?3L%c~8f$b4j9j7CDYpM7s867QYv2mv<5UM+6{4B~zF+$y}h}Im_M6;eHnCkbj;bDUp~-)j|Eb7v60fb`&%&;Dqp3lCU?M@8JytK7`y->zf`q2cy=Fz2#8Is}foMJ9AlA z@$yNVEA5xU`cSU68J1nByttpOk6mv2uI8i|GI*DI_6o^q0Xe8z-~EHbkY;<@vdSQH zf_LtorQWTp%FCUd}x?hoRZz-(r>Jzxrmp$W?isN;=HC}X^w-|Tpueqn+g8}9fW>5#I_j)ELu_Q^bB86A zrqgs2J6>FpLb-VPKI)ugR{54QaQ-`5;$iA zeORJv zy(HNWZhRJdyEDd6;Q7e@%ydnMjqkgRYQ&xib>rPdtml=)tBwa7mEnInMI)x}5vFJC z9kL)uaR86MU_cmau0?oWcuDF_2{uOzEzlN3Nb^78S_~FE*!c5mf8{?u)2~MT3TOFX z@c0r)pSQW&{kUHvEuJT4DMyR#+m~k#ez)lM`X6njw*xj0!Z}=%_NIL!u8zcuJ#Bli zN5#&E{~QC~{?8z3K9TTdB_Y|$C3})aojwMgT&EV(DZhE#&zYK*ZTQKwSgPM=E*2T) z_o1n$Uw4RiB#U|$^hO1M?&=rVKEl}gnim+HPm5Z2A_I~qC*K&OgIU9zm1iYi7amvJ zd&Dzwf_Z(YtoqE9NZGHEaguX$YPvK>$$dMSvdcvUIiNCQU6ajnWie z;g7NBhaxGfLZ(*wf$2cqw+dIHzX|9)!_T{~FO7_kSoUMiE{O^}2p9MiumLKQW;}o%9_q z8`)esj#Iq1d`^Mqf;dk+Ra_07!p(z@Vh?YW zhyw}Q+mJ5zRM47B0Q-j*z0g;t`_9Q33&p#B_;nv1_1`V0Ml6B^Pwfq06`65g3ljrV zxv2*#R+4IhoE{+nL~W+1ZWG$YB@tvk8iy@VMQi_6aP|p%C^@UDL)fk$OVSw)Ve0L= zBknP}lcDOxLJOdt8voC7FBQE{Y&9%FYorpXWW8P zm22l8Drn6L=q}#5s`K=2mv?IMj89`pT^}=idt(fY>HnmLcz6c*Oj-6-*xLN|;o-vW zgDhC!WYg@>yxQvJTo+wS!r|^vM0ej#aDii#*qrcPk#0SzKs7(+9S=SuQr9+Hh;8_K zS-_OMpM4&SQF?IIT|7X0&;Vs*+<~8u35vRr*awd9!2Rgsv}#c#GB!p9wXgt*Cuo*? zp?Uoq+hhS+JXN~Pz|(`HG?25G&Vy`QyK2(fN@7@B3%(y?CD8qfCA4*Wc{w2w z!ZGX^;?=dwO8sE(Go)OV=ca)xlVwiHjeO>9Y6j5s1d%&ditTQ^2nzu)lJu#^v7(5649 z0pl}D;mg(UM#pLcM9#}-sKpDHd#?uipA`p*ZlZ8zXvfj$eXB$fgsa&~fbiw}(%Giw zbPgIq--2ET0K5_s6Hktoc{;#Nahj-W7P?Rog_j(}=t}HrIRZIJ*p96NTOn8B@&}2Z;%!+=4Fyr^Vv4 zKj&5k(xa##_|#sSX>0%fy&(sU_5e>SKl+3@IT8VMZt+rH!I2hUG2NPC!RpV}EeQMD zlbJr_-U+qkGyFeBMsimBi+Zq{&+Jv7U890u-tk%I)gHG{d zbP5&mUoLt^N$QUB^r=sSV$F*&R$u#DnC{A}H&cs(gF)9l_22N8FIOC_sF21$4}5Gc zYtRts!Gb0)d0Z#BdwKDOd49a&?@_(d2uI{QJ?m(y1PSqVGQ53OzQtlJAr`Aj0#X%V zuNB2KWieJtN~Ax_1pO|pE+R0=2aPN%z2QjF>A)0$U9Np9P3WSg+$5G%!881BRB~G~ z_&Xs0YE9{&Kj~|Mz7j?d0hE&ahYPXKk@u~P|QnS zhnjwOMtBE^EQNFs?mV_lPjXLe&f_fcX6&>%%(k^%v#Z=xVVC{whg*LhvG0Z=-mRaX z!D62r4*y+eW-+*pbbt6zzUEZ0W3ageys`7yT#n1B%Y=H5hX(FBRb4YDlH$&ar`_%> z!>$uAV}-7PYhvFVC+C4U9;0(mW_07O1_R zSqH?z#E%1!vNeY_p8%S8*mVp@cHpVF#L^VR!~`2T*VawSqkM3!VO+;o+c_+foKUY8Vd8u#hV93n*zZy}pw{^lxNx_|P9 zVlrWCtXqZ8>}(5u6k;b>^*6h`UP^5$X}(N=9NDuAtWt~{Z#@APv-%c<@pPASe5jBgZV{bv(B__hmdZ?%*C#Gccsl|YaIh`@+9wvGTy&eFL^U}WqYcG=)s zn66Z9%(Tf$QBvzEGY~UhS-pUzu+4H_C-}q~5d?|Ti(QKBcN^OBh0e1j)x$l}!@c<= zjW9O#ikoBj?nKtF(}9V6Kx$B88AJksHC?8^Sq2SB7T$TRtK_RE^5@R<^mz;id4QX0 z_1X@o+3Y6y@%kl&frbO*e`~Cp#Oa!VITcNUMI}B90F%1yE2PyQq5YM}#>7tf4LG;8 zwKcSfId&0U`A9|`b)UunWMZUriA9c2Hpf{;Ho8?18)?aLE-orEy|fCnO zrgF=(qz{!|ncIfO@xnuK8I`tDh&bIMj(|7k$#o6dswaS41#r)M&m~dVx8HER89C)Q zEcBQiKRhdZbMRjlmKV%u6f)yc|`&I-3L^~zU*7cbp z%&mYYdOsv3<(ZH5Z}v0%Yd85PB>J9g7^0MW`}y zUSb?9EW89{d=(c50UBCsO>PDyvDGMVBZ6ZSf^*W_1c1OeFf7#=E!^{(l0Ngwuxxnj zo#F!@!;!!)X=;t|lD{yA(rH>ojbB1!YEFvN{z5=bk4pI(8(V^jymq)wgS~o}d$`fl zzzWU=FB962PPO9~)`LYhRgwa8xHAA&uz4KmtQ3kYuv{8e*~e)6zu$kDdvwV_TH|pB zDvnFcF{b==R{&AmNi3u(F66IAmmEfpXX#4qu;O_>O^`$gzZO<&;nx${8mK^-tx=4= z=M5mIkbpAqKEdOZ_onIkG20h1xZ;jJYwoc+iG1D4vJUuXEJ9nN&-a%q5ur=o?!(%* zZA<)+*3qNiCW-VMBoas>BYQJccZNhw&Boz2L0QU8qiAQ*iGBG(&mUGE z-aFC(B;N79b7F0{h^xY|1%bXWdPkEzWDY*qdnw4`dM`c&a*8ynf}OLeSSXrJ$~wKtTYI^no3Do>$4wQ>3@LyNCfa*C}QM z^o-m-`#q|mz81RH%f4q}miJA?KvEBVC{%3052Y6%t`VoXPt>1k=VfH27qO%0x@%Ap zP?1n@QK++_f9I}7hP20Y>4mX$HT|rR!eBruKxsZK(c$0dQ20@nhFybC4*9F~)gB@H zF8=01Rcwb##?TFe^4GV7S=B+8cuX6>FuS2!S_2r9%zboW#Qf|(M#P@+p^tUht6U0D zmA%m?J`T)Ik-bIY{&Ug6{+4r@G^b_}-M|>Iz7=I6h*Qi0@M;1^@25O@TO;`Nl9CA} z(rfUb#!Z3;I3~Oq(n@sQvlclf@H;eDd8uw}bZP-ulF-JwsDt)Vmxbw@XI(t`uwiM3YYu5_jJc5M~JAp3X^-dq;|%6{H8tL~Wm0@$9(2#yV- zUr)u0C~THr0uNC-LV{U$R`xni<`Na)U;vUwEA9JPEwSi{#UI<3`Xm6juk}tJRDXxJ zoFc4c!iY&s1)i00CgWffEfifHcDSeNg^j7<=QmN;Xyxy0IcZ#qe)o9z9F07;u^Q5T zZvL*vtlW@s`|hds{pFq{*8GG0fXx!dLDOtv|Mu?Q#=nrOgZnGOubLm}5Q01WTPD<~ zj*}Myz5hd51p9rc_AehPAvPhcTod{DAn`cns}EJ5xE{u&MRtLIDC2|jUn^Z7f2aPyPK@IA{;vh?Lx+u}^Oc^D(YilY zn=>!O>YLOk|09^vIv(TUu#%}TrVW;~*ioddZT)l1Yj2qM^{*(s`+t8y(cZqE29Lwe z)M`qK&HBDVRTBQ4d0tG_p?JG%{vM6qRh0%(zW0Wv6fl{((VH zoIUzh8NRbEtZ}%@$1fIZSc!@UARc9QS|;B)w~{VFK8@__NJ-ZQC_Gl)9Y18WAGRON zADv{wB5lAUytyMZfWLah>n@Rs*2frvz^OUl=Z?jT-fz-=d8P*CJfv~I?%lW?hr)z!BLKB^qBMo3$5ilhnQWiT<_Mx=JfU9 z*c-8tQ|${I7RQ7Hp&YT2G9k=);V=f&J^N`i8NjFHq$MnvqoQu^ZoOi2xiR|MRNatn z)>AxkWDz>t@nx(>HA6Gg^&0*0+yz5=SsYNGibPb4M9hIVKjHbkYZ|yl9w$eNp)C~q z>9roJLSwE$hLs}9+&JgEo2uCqb3%Q@!N1^$;E?^jyUkt%g-5c|?=wRyv|VTAdxFs) z1o^=?&Ao>;*TY*_VdtGS9^;E>V1*PbIbA3+D3yvPZ^Qg~wB;zN+MLJEDh)vW%dVdw zCtFC0pId6)K5lzys%?LjEss6;wpfx~FZV@)Q@}5nwXr;ZGC49Dtw#@?ZF>+F7J3lU zYOmNw&+yy5_>2*JVi||+VamV-E=VZ^Iu#0-WiEBD2jx(~WXkWkR$ub{miC>7jSnBo z(nPM=Fgl)_D%P&0FRa79H+UC#59XIl+BCI?1|My&)Nfm)SjE20Vb>@KIZc@LFZ#*$ z`$u~V$D(`9DI{)cukAv4BUWX}aTw%fXj-+h(r;Y3Hls1?dmb0N?!M$va~)WK*Q}D6 zfx8*EK3$Pgm0uQ1$rUfPUQ5RFOr0xD{kYhTBUh!Rk{w5gD!{P8GFP8eEdv|~I?miY zcq^Z`(6`$lbXJ)Ru$nfD75b8vZtset6|zmEVUqsxfiN1Xhp{sv@TUualaTKP{X<>M=L<;rTkv*hGM=m%#~hM{*W z0+5oItfJ)f`Rt~@kv-K~qD0pfOWwA(`_5K@7h`*2A*7-jiN(LVB#h#Yxee#$$PUAEOsSRe(Tt9B7=<=>C12#P>&_!n)-U^V=X)9 zYNpQh1q+xJlN%zKvj>ewn-TvUN^YBDB@`zUPR<^EZ&^Ef|4KBx)}ey&vL(7$*|m0p zfdAP!bnV}t%k8aUVMxE zrRr==C;Mg3DCvU06JmoG*_6Aju=9)aD@Eme9j7_QSEQX&E3!P&9xb&W`W~)m9JS2W z6(w^DFc#c$SmljQ+6A1qF%jG2v!WQ^h_?EbD7-xvk?!nc{{hF9q%RCtG&PSoA72q; z62s@@Di%0y&+9_;4AJP>@R$gJFaqqJ7;=y~%L0(duC2YpbkW1DCDE*^2}M7G+zGCm z5(=ic@^NV>Jz*%?#f5(`FkoVrKd^o;3RqYH~`}P zMX!Gq?T1Z0_&81F`DPap^8B*W`9dnz+W(5mAP1r}ubu}>vIqg@JE#1t`y3k2>nxax zdg~R6oDAObKKeIuex<+=#Y25l;%4yH|gd`<+d!#6&Jy&1mF`)}SN2ilZ z21eAdFJW_Tvjgmv;wQIV3aU&bEh6JGCTy-3!Tzl7{R??pt?OCBiOm*hb#H1_Yo%Xo zv6(%C>A-ezXh&0jhek|ckaGLVuav|CVad{u-IJ>wH&LMFyMyI2IA=-HK;UW%%53dS zAZbxgjXASc*{D~g+Rzr=Gj>U4#)pM;T_s*zy)`?zvhp}qQ^Ct8JuGyx{pfI-{pm@3 z$*lU6EoJ!9lVY5NSybyj8L|84VwSjl`^@T+>Z=#)D%~wC(Yo!~!yN}s@?%r7&oyx` z1J-q%I=xfK?F0OVgc<}?3Wd@^Cf4z{Bm{Lwp+fZHby~#@u@WZGMQ& zXCn!O^N!!*Bc6?vx|r1F4^>1wF9}_|RaKRqep1=U*z)tdTFV{zA!!ov(iI=qw1kY+ ze|aaaUxC)@c!eFO@VW?ImO%*mFpkkuqV@B#mq!GrE4>-xC@nbCvFK?yB zglA*8FM*l6jX6c-+076ImL6bFZmj8JKi9*>i$X{ zXgF2Zb;&g)yjkK~-)n(q9+uuq2W=z-V`dbLU#G!HhOX4-XdZ&@H z$;Rnvm|te=16J8^Qsd^S_K>69`SiP=8RHX;;mx)k(`w1m>b06@xAj9qCVMbRPMQxw zTk6NGh+x{>bbtSNV+*7l1-&;Q)Y?e#C=r=>(;!v4C`{`Jny9O{cfz3!P*Bq3=?l?9 z0DN=aX@927AKpof;1XI!trj6O4Hr2OSB^*f8JnFE&nsXllLnmxZzQ0%}rnhPj zniI!gK>uYEUqLSv}f#N}ic`#RxMK1-r2D?1=3q1|* zQyO1i2$=2(jQ?OwFN z1foq+=@L(LS)^Lo1wzOn!AvGh2iwo3Xbz z0x@fBxWV29jH?&_EKbk-SQed!T2d)Nph7(L>b9v+eXkY~0X2jiy z2iq*1NTY*srLEO~k0%wN`8um%xANH^Ud%wxxe7Rd-l}%vG~Wg=qbCX>0dPROiA^+C z7_uiNiB~;O4XXa$+fqv$8F=j+`m1sLDSpShR(0_-8M@l>OQ^iDguD%xfn;(ErV$Ow zu<(@LcU_C;pRiR#ik@K%`g0eBeCmm--2Mq1kW!1^ zAt?12Ve#po-h z^n7G72fY_V0z&h%sv+jFKZ6IizOn{n#TSu-)BwRr)l5hYE+nL3Oz9W!qy4l4hy|4x zy%vT9iL=8>@|6y>+y70L^u5v&gmY9LQP zF$I}ol>6&)S}Z2eZ3#~U<0sYDC{zQUo%1F2i2nd+vlQA#iNd-N1NrguXZd19T9urR z*~&vW*&v0jEw?i_y6T>dsdrz7A1iyS&aXEg)FwXHSNy<>>$gfhH(z)5{tXXR8&R*eB)33tuprjyXDw0sR3GETy$G?fRS0g?sc}$7#-hq8Zxj$W-o&AFJ)^T6IX;sPyJ2x- zHpQ{r&oXmN9T=D4ohXoF_sR#!dlJyg=^SMpv=fAxtM@q$=n^|DoSw1=H?rMm(_-5?nF#F)P z8Zhf@E3BO5fz4U^to+_%Q8}ran8D?~=*jhrHg|#yyEctKYay$|FNVK=snshT(+Y`< zwh}JM_NlD=|6pmmhbLINHMMhG{4n&WGh!>3?e*gVnsbE&%+s4}D{>tio- z)c6Z&)h8M!L(;Bm6Q&2RmU`_A(z=8NxXAz{FFDX!3nTM=V**k(QMM==ag@GNBUYPM zp6{Pg?;Y>QSq75g{Jw#+ozK|lN#arrYge0IyIkxsIdBXhUh{muHX3{!A$~sXqJMml zJp~(RxgzO#iP@AE0`Qb)Egwb`GbEh_odoYJU>+4aE}rHX28#Ly;9SL62v;Shjn4O_ zSNAF_bJ<)5``LDhj;e{ueK%j}A35%@G{6TO@{Cg}D=vdneyO&M4HAlSav69Fb6pEH zN!WzNplwxjwVi?Vt)e~yMgx0A!YxW!3imFG(sr2~$LkaqU$;-;Qxefhe2+t|w#tCAh_sys{ z-OH?I%{8k$SeLng;Z-R_-Xl>~emD0eCNJxaXB z7xlqGBRm!iO*G)dBC_55a<^WB2Z)lnwq#RUzEGM} zen$%xLZW+C#^lce85dy#Sg>X4rW){dsp7YlCa0G=8Cl7^S(Ip;=?z@_URK24PSGRX zAJ}YNlhR2Oi6-f{i-FHCnI_kjPeo>FRCUc#r3g?OA@;Za1P>JZ8$!t|OIh&2Ig`3% zOA#_+{Tx(WRlT|r0)Q%p(%$vQc-w5cm!5fACw;TNOp}L$)EPTYBWCW@4)&R^EUCo4 ztQ-^pAkgN=Wg6R}_uJMQD}4+99k6C+6B!Tw1N?4n!F&GKneO3o<8=@psX7=Whoz|x zM&{oB2R0z>^u$(^Qi*E zW6f#YwQ&UMb$0r^2IL>8r zD(f^Y|FMUJ;to?Vg(7r~Zg!?Ul>Hd^81tTOEFSDl{?Kcj9wwrMm~FMKiJ}p9`t$%% z--~=2f(Ecz-3fHSJHcx`Kwa+ODV?>)=676>*&?2{9}MVfB3}GuyvVrq4-Jk52IDgp zOaYje(beNf2vi&@5V*+;;sj}0&3jQO0a70mxQ6XF9(U-)$9)>(;S&S&J@DDr24J5# zWgw7%{#S8R6C^>OM{Tj0ZqfQ4{T&ZKleQnF*ZkZN*&{W zN~A>FSqFB;MHT3Z&uxZ-``se;6xsX9iUBz~Zg9A!V&KylN?^7Lw@K?Iax|wiPS04+ z*&?f3qTsDu5dCkh!_jwE1k1*C2jV1H5SSR&4#l{#bzRL9D9LiI>tgDhuZ7-KJtL%6 z_g(5)Ife_HMRKs0**wEGgX8K1mgu~oz5d)oMY>#iyd06>icf(e8;U#y^zinF(5vX1 zr`QH%;p5dALhlbQK{Q-uwyFFrpX!{U0SrV;~@?C zr&zKze|~qmlA+1P+vmwV(=r>v=ArN2YHPSGEp-)!R#u;!k4~uxbmT^U;>2x;^PgoF zf&*J{{XU%ok~y%5jDM_s_m*1;H_U3#Wpeteor$7u?xspTZ>NT=>|ozwVtsSH^_u}!n z#$JDE++nFzum&;#?_z0#IX>IM5P4o;`)Yh(e|>##c9+dL>K@;j9uh2OOl$A$HP zSscqv-M~bN@A#E*?%jleHRU|Oaq80|UI|^A*+n;~YUqCWLGj`KVSnsq8spU)zrUsn zcVl82C3|)SW;hedGoyWqrLvB`X$q&-)K^XaTggP4*-K9uozqM>!$*6GM#o3mi=7na zRZubd_h&8!_T2b&PS@sUHCKpm8Q*fu7{r2Nlwc3!LfVKazY3c0ib{f6C-QC;blm?n za?Hf`IMMLJ)WMNMR@++I;hRG*CfJgj00=!{X)96epS|2Lb8VVtBrIQQ@_;}0lcUbi zSEY-}9-qZWI&5;(oKx(_*qbGCCuNvT{&VX2`L5lhj-6`<^Vx_0wH+M2m&6Mnqm_xJ zqeGe!gQA2zVjSP2EcMW9#(Q`F?aB2~$iIa2nZu&5X=4M)vS+Q;VdM`}3Ep2$fMlRR zw?IcPqL+%LqJv4@Er6`Uf#TTs+b3^72ikMM{+A-|n|s(FIVVGW_XqES3xyGnz>t55 zF&{gafHM9QLg#yosreGA2?@QmJYJss%l`S7nheC?L!pL(LUILiFsDr~w;~ai+QYIx z5z5y!8$NJ?at_10-j@x@4Po{$Ekct^xI7Y~mk7`Nw;6__Mz(#)>$M0lz0rw2ooow6 zDyKh)JxpFj%xy*o}Wa`G&Z7dKqy&8V-57!PO z3niZhPTX#3JK%=K{2f_6G1fR)$nGHVK3$Si=*4r(qbJck#FbrJ?)j~~mF9E1n~NiL zt6fJ$=J$_MG=HlN$($l=t>>woN9E$=1VgXvy?5PTpXxv8Ow-xlKF_}1A~6Sk#J}W^ z%jN#qkQNMb^CIwIRbDtcyX3pH-F;Oyd(UVscE9aV=OG;ZIfoD_Z5HZ2T%K@5m}FsU z&i}M3W)10{AKDy{W=q-~ZtpiH3hD8{$^yiAJ@vg6w|1pOb$fezB9F*&0w)<{vO5%^ zQ0-ykG8yfiVsq=3qBVEo$UU3!B6zA1d-ttNeV0cOKS>YRN5;oSjSHe)gb?)}$zrAT z^bvf5XFt>~ih5qLv{&--Sm(zH3a8-LRILhIrC zgK$W>XRP`;%@SB5Z#8csEUsH!OV2v0nHibanU1GOtTjrPe;S=$zGTt+NZ!2I|1-q-I9j!Y-(+!65rSVWENR8RP*sTslKeaQNKJ zjxCMcuzV+H&f}pTgQZ!6t?TdZ^*wdQcxelj(!0JgwXv*Cfuc*2PI^kaqaG>Ow+u8Z zJ&V!RTE3_<6~LB}?~vn%q>ov{(@$O^c;6gN=}p33%Pe}!R$E=@c^ZQTOTX-0FnMEF zW>#9JoXl)ttIUfG))r}eZ9*W0@3x#`&oRqlj{qJO=KnE9u&-i8WF6bNzCK#n|0c1JM5I@V6m&MJi_lyTBxnosY zTBdBkbnK&Vx;FZHHb-4Mz{)rukWHgkjA$uuf~dhT2P1vg`l+U<+tjt1_lMnpAVnDid|%IxO{uhw*TXBZbu#9yTOAk*>@1h;ETb$ zQeKVm_>5MJ!#X<7WIt5x z@%&2$ke2e8;BOetd?7$O$%A)@)$}0OjAKYCJ+M{e!#R3Vv3$5-0*ntT=WF-jBo+yk zmH*vhLZ;9t3&Gpb*D-18E9ay4U^QNh=m`piRJuwc11uQP{>}~;1K6#BbQ(wNp!_PA zVqJd<&|_RD@)!m#^2m4Sk(HE&>UUf48}T0f;y3R6F3Yj}cj}lq5rImRH#iAkHkQVE zQFlk)4_eitzK@K=SrdV2l1HIe2?7-#Ef&*-GGa{k?PZ2Pad8CP)ITLzB#^60zD zRc+ZVB!!W(PrhJf>493l%%a1v^zH!~Gv!2Z$(xhVq!_aGasd(GF zgtX0`Yv;wY=$Buuusxe$8bDYk>r*&v7AqUDH3dt>(BPLwzb5T7<$P7319OP&n?J{ zzqtbm1<{UJ4xDYDB75gOrgKB--D2*RTJmVWa%u9N@%19j9w%bUmg*Hfo`P{XX^dcc z30<&1viaK2EZWkSX4kr(Ro#A$T(YDS=vj>v(QALFqd;A?tc--E!*uUk;U)qVin5k7 zCI_%qCwnesoZmoQy~gzj*hj1<-d$oLLR<^@y3O=g*DncvJfVKV62}F$>~8%I*og7!cIfnT2h~J51nw_yCA(=HIK7OWTro`xlFU$U5#D9WdH7PFH8U#e zG&TEhFU;zvr&9R9d^bdi1_}4MNf8t^w3R+g8Qa-%JgX--WMo>6&pd6p`jQw12-h=c zuUoCll5^CQ`HwqJcUJkd0mv&0y_+S`-^qt}Y1tyX9Y^qP^u&+1JWAZ^3TT1r!7b~{ z$FP=>Q85QU7iJtL22~xb;8pGgO@YyabPL5uHz3n1U=}rwE?^LSBZ*dP5MdM^0tzE& zE_~We?JfN+-1VdXWxvylE9SFjm=K})_--^u0cok1G2>);#A^3wj+8!B3%P;IG@! z^wkLo(NL!8Wm7bf8dCWA*nP?XKY~EE13Xt|G6{wpE||oEAgPWq(X+TRU(qoq&W(Cf zHyLj)XKBaEj$P$U?qWIqtC6QMKE|G3Sr=p~+m|MKw#Jo>Ch9nL!j0P_6*P_1F?PAf zUq*3aFDsW5?<@;BRIgiNy(}Cmt{XTt_8Hhrd{*vw)_cMz3+|axQ^?yt zsg+1S15co%@r1rG`)SOV?FKwx<5J0`KM!5=$Up$8E2_~ca07$?ROh_SD7wj}tS572 zxRJb;M&@-mAFu19I;|};segjZZBxU-XPQ86cz~DUSH-B*1p0<4=-g9z5s5hUp<693 zQ+La$>Dp#u<=(k{`!NU)jB?GxM2M%E9>GiIX4&e(ey|twII)IAHhpcbw;C;RNiAoD zSJk^!1VUo^FSQimVCgi81B&@gNl#vrQ#a_clj|xT-A7b3jPIE~+uPOIoof3x8hcu< z>TPLFU77lOIn5#~85sI|9v_VG)&Kqx&=Ig@z@0<(!G7$);or8ywu)?%98TW-a%HP? z>YN*SKV*M>YpeRaK;UGi6$ns4b#Rmf7ngjU07?7jNeCv&FZ$w89EnPT?yqb1 zR7p5))TRt;7hQbhc3-+AsS@);Dj z80Zm-{ODk1w{52H&78~DIapj?-rB0KM~hT=x3HOExZI_v#qphu4HLpaeeBjXU;rmp zyLyrf(Cu{HF4gj+vD!)MeaNi(sE59noo97z(0NDred~kMWPVC7g{3soJ$E^8miYes zD={CfpVdJUbbui@+hsnt1FXu6nFKCYijx}$F-7` z3301CZ+aqxH%`>_=A*^nE*cj>X(Uh#OEq7XFTwkMik+5?5?BEM2O(kFWpXa>I}0Sf zNMFbrUAu_txR(G-;!V2&#n`j_aGCjVQ?LG)Yb*!QsU9-|3WxorEBsu}N}INJ^#}KD zcI&DI;(`F-82^=a=Ki`fLY%$(b1`bs@xEur#>U82lyo5{6GWEpG_CI8x+0GpSh=dTpq(5zsz45HjH){6-FkjastT%DS7* zjgGeiqJdIq_<}OQ;)}}tH~;14?DAf|s~%1`DV-OJ?#MZf8nw8jkE{lXuHZW;hP^<# zXgP!5b&2r3)ftPXPTrkp8f$V2FSD^L2z2}ot883K%7P4y zTJ03qG#0gkAZKXfFSIT2mqv(PiZwOSvklNvg(8_CHXpl3C&KlhiyVtbaQ zu#>0jiFKt?%l(~PXA(YlOqLUb(xjA-NQ$ACwgcz!fN~zS^Ix)K^W#l9?7$S5l%C`D z*}2=V$Njupt%cD3SEepvy;KWr0bD>3VYJvY=JVgLQ?#!gjuW)M4O6_PFU%Z9Li1^f z%-OXdDQW9k)SilIS{NoWrm?lP?PX`)i&BH^Xm(0k{rTR{Etd$hdN-G7@cDOSat7Fx zpRV@sWFt35nn&<;uM;0k^ZU;c&R>LHemnIvUk50SZf$7|ypzV<+5ap>h3a>8!Xubg zHrC_nLrMM-22H15{OV4jHnNw4U&Cbza}K((!pCs0Ew#`@<$fpJO%N?jSqwg2`}e*Z z+=KeyMT?z~aJOBMVcCS+{ZDf~WgG&lYJ2ib8uhuqqo?t!lhDEjeTa>6*$FuSSpjdo z42S1S1HmNI&*cwy%UAE{{i{o_RSBI@Lmv}JL&itXC@t>KNv1_93k3D6IG_z>+4I)TYtro^$E{&bhG*|S=X8vxxD4Y0$Vt|)}V>{ z0sn+Zy|SO~tU80zr`kH_UoLGNEIwGcm80PKeEJTPVLMg)JbW#v)=f`lTI{;R>iV9~ z5q_UMo_dapfbuNxq57S!T7QPGKRI@YgBY|wEY zk3M$ZynR1g+XcjJB<2;Stzav$fd zxtPF$x|B@#hXG9xyyKE%==pHp@!u;gtfZ8?lS+L6H;Q;V;9UXG@+58eCb95$7OeNL zg{+LS3bI_)h(PGk*kQp)={Hfu&c4f*mrbMEqbkV`$BfeH-ZxzUfNvPTd74{4Jgx*` z<5N`_QALtaWG31wqrlgvI+!^4aMA1P@-J$)%w@zD-lbV#rUS!CkosT=j&?ZlTLaIl zbN8@wX(@YdF14#uSjNr6e!-p*=Et;dZ0bTAJ4Ia~&C$v@&$SD_ z-)MFG)VgUCqefO=;2&+=2^6s+0WA2$=FW+VRh=4y3JT)|XD3VO^P`(s@rDS7D((>( zQU&m&zeB@-(BUY9hBsF)y2sHq?G0<;vm%!g&(>2}x`^~;(^+(pB_YL#wOl(sS_ zfA{sFr5jeJGT-FzpKq@`qC1N&KjR_%rpSB+sTyT8zzCD=5IPH3=b5lg3D}D8}-51+?U?g z6(hDP`?yJJiA9gvXdRp4CiH20#N4%xrTLbfO7hIxIx6IjN#u8rOCc9tR!6~JkvN&h zM;I>$o*H6TOe0MpT&ZwfBy{3SnHTljyl?q(^+d$4%rkI^{AAIY5xdtu*MCOlK)jH~ zx4&p;4FK_A-1qgxM=N^E2f>%*E?72+p4NzaL${c7Ha5gvmbtIzQJe(e_=U2hk~;6{ z!?faQ2ukW)nWTl)0QpZ4yBAQPdd&aAJZCS|H_B-J==jhvG^8ZSz6c+yj$ZW@eNut)fiEgy-fc(4z0d!Rmb)~TN3^pyaRLw(-VR!_V$r{f|&3o`~YcVSS1>77* z4y$M7HXk8)dUx#hIPY>p+Mn4zqP|XF>=#A_>)+6t6m~NJ+;6t*tOgn}O zbli=M32_YYwJKNj5`3Q0QqK%ix?HIGb73JnR3dg`^l&+EV|(ifr#Q0nPM~4f^a^zC z->!Lmn!tu#zm%5NJnc(ne^5I!DBJ`ODBDsgY|gz*|M<#BXH-jG!dBZ=LeXq$S@Qf( zw8HQ^O`MVRB-AaxI7~qUsdX+VYc@RtFrGW0F^H4esT)h$_qhzfIrCkxrSi+ ztquYKAAe-^6pjl?m3Xi;&eleE3&^Q?V-WZz{Ul!0P$(KzUG(7BrMg_wu$V4UL8pe+ zAQ0v$;~jAn0H>Bdk`{A^WWY=0+l{?D}2uwP%JdqVwmp z9$^vm=<<}L!};4&xbNbgZGLhWRu-xO-J8dOI1^C4>X1HU_7Y7Nft7d`7udmr=?FHJ zdFxakIXIjQ)FZ>6>z}hVpYvcz<&*f6WV-WcK?l9*D&%hW#c@r8C4b@Jmi-M$mR z6T~tk10M&Nf32!Dg#;!(B8h16D>M3QGl4$IFU4vy;7hrj(@yJ@$g?OF@>L0p^wSD6 z>L)*z0vPUhVRmvyb?+YZ8U_W9H|!4sq!+EM%%efzO4uhLf=f^eo$w+1RQ*Jq>&wi~ zpjrl4_809za$ONZB2*qfS)nkcuW_yE4uWOWJE@}ZxI?!IDTCy#Y1Y?kdI3Ajb#+dS zS@QQNiC|2At$q8~__@jCQ01O3Q=t`Sl0{-^!uj%@<>P5LPbIDAy?6z}z`kS+Pff+c zZ_Pw4`404cWGP!sbd%}nKys!3GN)=V-R>vHsn^_gI5)&`l>Xb=FM9b_7$i{Bb=3&8 zuXK=oIM=qL#95;hevzIt1)wr?zh8Od@=15zWaV9l?1d)+O-|$cw@(O3OL#)oem2g` zpgLl9|D7Q1MKtr>JJlm5s66_q;_!#k{Dtk-{gr0(p2*ZWO-+&aOwzMN7^0@oE+?@s zr^p!Zu>9**O8>&r?XmehdGTL`!*;axyFd@T_yQF?M_Z}2O&QVpmUqN$P<1}9LvI{l zf1D{pVA-ETNm}=6z!y)RQvyCVs{ERc>GsguO&CG+*A?3~QV^CZmg% z;issat~oB<`oNZH~aK}JK zL6wHCrT(oR)gx$q>y7hLk14RKuAzdPz5%2CIj6(78Y_m(ee=*_TUXa^l=jku6PB%Q zaDj5Yg>t^89(uF4$9ke+AH+7u((9E+vcl>^9JhW-7c~KMxPz5P#80D-GoBX+%XXyO zAA6uO3i2Ok7w_&rp^JWH`91M{{KCv1g!&Nk5VC5bD>v)@uk=|JDX z;-!tq)Ze-Qi-;6;xTL(1aBw}st>x~avw^jRVPX47hAQRKuUyb@uw;MM%}}=D#71%j z1B*=t2>gO>+}>U~_*kthSej6CedK8+n5w!ovL$vT%@MInzql{^6DD!FbjfDTZRI;G#sYWgoEBkk6Eo&0~8 zE~#iy1FRqxR%+g(Xj2{E_DnNjUau4PQE9{Er_u9|4k$P;2$TD|-(#o@Var$VA{7K5)x9#W@h-?q zGzByW)(o7HJDH?#XLo&idwDx}A!T7dD*p)R(=_}H4px#=K*(!{Ox`(i-r&Z#PU=PI zgWYuiKp2aYtWiyn+mPZ-EPk0lvDO%WZdm)BIPUPZRE;pToFGc;jYHm<>DEm6q6L&f z0SzSE6AkV9&m_P#3{~ zUF!`N@sT)#F;wn`{Q&&YAb^!$|FxfxA~IC;Z_!cl3n24FlF^oVw?ap;94zG3l^K|PZd*PJE{GuuOTS2w0=88Q*^|8Xnw$$@kT|4h> ze5hcmQTmKiMYeYw$&d;ys1+7` z7jh}8(3#?+;7RnT-CQUhYCBlt162jxuTgd<>fO54set8mVfgO`=hJFkF`C$ri3K^?&H(@pUgg(-41Zw<+V*$Z`$hiOIn+>WR!VWq5<-rV#f9PrK&UwkeH*+z_ zwU`$2^-FHliQOI!j&HqsZ+Y~MYJntMkd9~{FoDo$7%vy2Y5z%q9@9_~Ko8e%dQNlb zPEmH}BRH3Z%|OdtV~Kh9U!-(+96rD{dot%!hO{_Hx)CCsI&ci4QX^Lk_oq`Ud#3U*1~g|Vq_S%NbD*sO{BV9HIw3j8}X-A3eiPd z<$R3(euRK*pYhzxekxJGPw_cs4Vq#G!iF{rFSB2E`+j&SW!fGug{PiH2{Kb`0CFK` ztl{%1hV7ZMk3{j6sbls1FG6w*!6YZB>n9!vGZ8!sZM74DW93t@k})X5G&|bu=7s%( zD-<)Kxaq+9#_r|q?ajB5>X@r~KKlrGeCmebc?32$wCoRwzsS}|ijC~xzZVvI&vb9* zQ1bAFsnZBW6gqFmc&Ie;uyM3P!!Nk8kMyCIcU< zjeXmy^<4rs+qyi_Fe5nTpY`pWRC908J$c-IU&xq6=9Yw9(Qmf@gxUf+3VcIpHzxmq zVJOb)R(-3#sFCWQ;}BBu($?)}VEQ@6pFcfmU;J$x9PF%r+`dbZd+lH|+M~f5uPgsU z#^dGRU)bvHt@ab<{@Uy`7`YOi8xU~CG@Z(6TOy@b`tc?j)@=54UTBvB?mdu+w#J_WnDaV1N_LQHevb+C?AcIzy^X5i2 z-pi-5FJjRqx8Rc?YTv+fy-fMDpD@V~6GD=l-u>O5)sE^I<@hAz7e%qs67#rWubGKj zJ71$f`#asH%LA;daNWwEGqLq;Q>n!*bj!iXoZ51g5uch6*;0NesKm^1uI|;tMcZrg zn~d#g!s4iFYf^jgMUlUA%UqqDsIK6 z`#Jii^S`JuzcKahYP)_pf!IcE`ccf}Q?@&&Y;<#T-^BO76=J{w(6sGf`(WaY>;19d zlSDdHGEg!!XJKb&OBK3s?o0pv+vgUH#(5IhqUhn>q5?T9mOgk`Y;8cS0v(KMi4xN>a(0Ve$ zl^~*mU}hJ`P7HFvaa703(sQEhGaZ?S=x&D7g=-)z7eu-Pfu}!i*QAe40i}-Ganm_O z8FJq#u7GSan;DlG_%lG2>cg}9@r|SFx`oH?iuLaOL z{)fw>u1l{i$E4v#{bKZoRXeRV`Y-F6$Hg!&b+D>k{P^@ZjCHYQ-#H~Q_NDoJD7}U4 zEr^?vM@B2izl{9sd%D=(iW`9Wzz;$aheUd+RYQvfH!uiDyas#e~---EW&5KIA z@lby}hmTL}=zEjQBo8nEUpGDSI1$13xN$o=mdUnI8oh6dZm{=a=Tr-Q_&A(z6Cs>;8 z%+YEYUf2Xn5Z0bHnzq}cFgc=UT`^ibF)`XT`E*+{CSpu+|AFLF$2rInh=~V7e#9;K z$Y%%-zn9mt;{Bc%H28cdm+wxxA#;sFjgQq5U2T(_qG{*l9XZCgqQ$!+w|hxGmzjD5|)oHrHfWTYI4V-m0&o|;A_p@h!;h_$ApkVH81a6vfR|ji zAOadngu$+2gO}%b^wUBMbap;b<7f$H8fnxOm*UG^kRDU?WJB65nW9Q&uq~&%FHHWHnrL4I|D;-+gzVdo5D0&U zoDGbbz5sb)IHRUoqjH~@%j2Cc?$dy^BM>4nevafY!I?gU9MzU5sN#n2HSp-a@^|$` zTKHB^lE30;1lsQpM*);Pfuj}K%dgdb!h}?lZq`=?!8awm!T{is58tX2@n+ocXlZPt z83}{%A{e?mtJyV8pu3G@5?4%1$_Ijz>TJ9M%!TKZ;-wTr^(O0Dgb&E=!w_XbFvkZ7 zb9Ko_yQSLD^JWL*VILWhP2LC$9H>h^zpQ3Ht2R2mZAM4G%fjIh4VRd@OpB+9rW?J6 zKKxrXdbReSe3E)gM@GxbDuc=5BCX<$S3nnv&pfv`xL>U0Z$4s+DD%OjKnUL@@HW8% zrm~^b#>W;Q#SOhf`EbHsQ7GIC@}?31qh8nR&N*0!{adpe+V z@?M$VK9&Y@jIi@9-_qt_D9ZyERP0LF`{I|ugf7iVS0u; zKCjZwXL5!AKb~HE7hRiEvMttwK*KMqN9fHjEJ(IZ#tNZrs#I)fc36^V_R#*n^@ILv z#QSap0uL!KG|}Q;4f?;0)!6Xe<$kd`SGAf-2$7OWg%gLxUQ4j&*7s_erKcXRKy(Rx|naXlcp1$u@-y`@@w}PRN`F$nqX*6#2S3_@-fOwtY$B5 ze3|!)-rbp4I;d6X_g~@>oqg|#F|c;&&*{YuF&qE;H!`w2(af1A;S`g38y{Tyya3eg zbG}3Ycz)LCsyEZ}%R8IJnEoBsFyGDu5rLcz#r8a5U}@N-IlM~sEQUfRp0zzbUhKas zcAb_TY(|@m)=JF*00q7S^t|>CuYDGT%fsW~1(Vf~jtirU60)`a7gb`|b8MZ%{UQIQ zCGqm`TtVcNRWp%%+CUzSKysR%iq8{a!N(h3m)1eUUO`9zW9PR3gAY+ zRrN1_%08(oB%J%0QZbaBRxjYrolbFW||P?@q>&vB;UPr zCwhOGFB#*p_@AqGn|S^1#FDRW*vOvdkVa2laVfE=-1`>v=&#Z^&IM)wF1(Od!>lvx zDx1}ac9ZD>73w`;y}H;5dxwHNxAn*Xri=Q_kLNz%dX}rJ{{Yg){bU{W%us?%?9xmcNfCHf@Ck5 z#N&O12WN%0!vxS3uPYz z?lhkYm;ECDNlXwra1RuB0#&|9`qQ}@dv|y1EDYu@b9-vufg}0@Y4rth*fnEMOu%@q z*YQp9VkJUQy^58-Z+uL2D{_`*&`x}u8SQ{YOVto<^0QV){VRkd%2m@0dyB9OWmj`c zAvVjl3HIEb9_6rZ{y=7jvzU@Op7Mu-0P6Nxh=_{J59M%FKj!H|WZk|+O7ve6V<VvTb*B zxCtF|_ii2sXF0Z$|6xIW)drQ{3B;lRs|(G!x^BzU&$nmM(06jJS(FIXRajy_r9)IS zpnR2+guH*w!^5KV_RYdZjo}wv1d6Xbh@~hBa&G6t#5f zly{O-r~w#}UilD6kvIN0d2Q`KT1jlP2ohlPTAEW?^sG1XV&R!vCHJZafEY9{aN_6h zfVsI_%`Pl{{~S*hlYuDoT{^tr+yH5R7%`F!zxS`>2%kFKH#LFM;9TKp%5x#`ND>~D ze1Esjbt;bxHu|x~;)j^tboR`wnv#lV9GmBi_9xhFyR(4@zXP9EbQA^%XARqs(TL7_ zx*1dwAnR1QDo?6m_=#^GFCLsk0jy@4%Jq-UC!-PNHK{_hPmCGuD@Z5m;$!=;nS;&Z zwi77y-p)Z3V_?8V+g0kS8KmmFS811_YwUhQOKgfthFwPI{!*YTPD^F)_Vje1;pWug z=F_L0bV2eCJZ3ByFNa<$lp%T zyUKM;njjQ9B;Nx;0%|IsO&A9ma3Jv>`j6;*P#!OkH=gJ0ohZ)%Ll3c$Gs;@=&KaXQ z;Gi+3z`(%rTWD~a`RGC@2bhzOgpyW1<;d_LYAocXe$zgBn?O;Us)m?hdPK8Jw-nv8(3UIWwOGMDI!0BIel^mgApN}_Ub@sZ33|${e5_Vi&ohov zC5!>;fq*i^fYGUL49e80?iuYg)Q6AmFAB=Jqv{AfBR?wkUSrD#9VOngF!VG{qq5MN z&cyb7=n(Y^Sh;V67U-LGu(B}`zUoS*VB+sTO3$i@&&+v!QAn@`2Ku{B=>kpD9isxH zU$(_WhW3_H?;~{qs0fIll=9=Ceh}&lr;?0eLjH%fv8M~c`+7AlgOZT$eCO+txg^`G?p#Ppsnx1SvEhD22KtDSdv-`K2^}Sr5 ztY*|3=rm|CfA~pfxA<`A=|QN?8fk|XEcNL@o%}HLN9ly4@zuda2xOj|1a3RrUya${ z>hE6|deasi9TmgV6+JN}FPiZu4~L%2qliywacK0l;U3oW8{%@+SvB7e!V)1Ddk+4M z(4;hBKILahxg&0tOLaMimbKM`nFe3=_Efy*D7~GB+ z3ay>S?p3SuH2Cc=hBOYSr0U`Wbi2w_Ed~5T?zC-qR^O99K3{-c$njPA5VOo#%Q}X` zW7TUi)s+f0WozfKm+&{Q|DFFLIP5KW&yEg5hUDLajI&0a7#GDo&t;mL8Y0nX-0N-cDo_AGRBWepuK1L{^1^r-3Q`CV@QsPON&lCn#OKNB>5tap zgzf|M(B0sI>hfFF${xer3-F2Q1qaBLE>gSMaiMB)dofPu&-AupLC=0hQN|i4j5^JU zb`A~AsYk7fDn4A1d>{w6a#J3D;mP)Y1P5ztrQ3-jI}r)!mcTn&z>+JM)9F=3Q1>PI zLWqSyV*ynPi|}Gbuckw1`KtnRW=Z5kC^dh#wPk8r>uuov&fex))WrdEqiWTwvJ0aa z{Lozsto_*@um1GliVpHQM%C}tr=rtD4zC3{fFnJOF~*a5nZ??)?vvXRE8Bda2O4j z_<&ISX2zhez3Ytu*EKwIF@gfABS%a6eDZO;n?cNEfRQ0-Vx)h6 z?uA2-i@CWTJHhbIOkj@!)745l3-2D9a5-slpM0&I%HI9gDKOL0HZxB{zs<_N2u|#dY)A;xck}6B zTqjk}m8Ur%?~c4arbc{WMC*^P!%hsrLT@F^l7MPQ7Uc!w!AVsjED`V6zyzo zhV3?-NCqULC1%zAIYAc$U+O&=a7lC5>av@Hv^gt>4{NC(<;(G7NMD!1B>amRV}+n9 zG9!GQFGNK7K1A57zlZ_Cdb<(yhpNbNR>R;Kj$f(>nUU}A%P8lHS56EPd9xcQGBh;m zeYS%J)ewb3GBahtzvQYr=)r9+^CIAH0qDtkVRuKX20!N;3g+zHU6sL+`Ck5@2EV5| zgRToA?df>9SHIXWC+D}mwJ@qOSX|r^uEFZ{Zrj@%5r|#8m{n9r``2$CULqB-(BH4x z!PKi*9$8ogtuIYPE4=Zk|JmR)ak?djvv;UL3agl!0~BzwB$lB`6nO1 zPNXAK6%wt8K@~;j9j+Yyn2A07yTgv%4?lT6%}Fo=3ZIlhlkIH)K}_8gz2j5>hEOMr zy5I9hRJ2?b{9i8mW6!Qx0|s&<;MWy$lWM3^K|!SRjpsps$HAX6HL%vTB<&uNiCcWk ztLaERLAnnqFP|bSS8}r-hQ#LZ!iz=)%b#f(*-^>gReXz1gK$Htg+3E?41U<0WvY@q zUrI12W4lI=BkIPbxH!vQcb8V>lmrkts>Hts6T7>$_RH_v{wC|ZjpoFrC8-}Eo=0U6 zU$R80+AU(Tq9x3idME5Bm_P|yen@%kEiXsWWk?{ILbC@7E~hy#S(fO_z>DZ& z4KC#iUlXI+=qEN4X zqSu}6$+7A_U4ZbllwAJm(Df)0eH4)OFRv$d=jq;grKfLn_6|1EW?b724+``)!kz}} zEHC@tmNwbjtMI zP-T#aph)df&qU8bZQ8D($@TtCXC?NZMqA?Wr7Q8XoT%D|jvh@2#c)W)!8dy5?gc^Y zaP%aQM6sE0I;~QQ1*V5Qu_hTNGBq^;A@ZI?p9|Ugb}?Lp0@QPz!Gv349JLp&_ZjgG zGLrKmjJGc*|Nh$g9zp`(9#D3*=^g<8T_&C&{Uvd;j=iHrlEPG`-_a_?0vbwXDZT^W zjR;;w?G2^tke+2^oJp+qeEBiUd_{Rtl!MNx>m5Piz0ngAr?0TV9B6hPjFnUFAIk3| zZum1v&SNiF+|&?e=iZnSqQ8If@V^-jDz^V{K4c29tR9f^h%Q+luIFy4cN|`$nl0ri zWxXu@!9J8kdH_x2lo`IW`^cryc_~+W*_`tZe8^w$8)RGr#o4&2k*Jegaby)VwV{tT ze+7`du#K#&r-&~v(!MZyG0``dndikqH?+B|ZABK;ou0Va` z-Ntj}jDI;+;kL6)R}2MBI!(>rvO%bPlOkcH%-?!G4*LD zvC_|v5HK|kB7!xP^2|n3@KVdyNXr+B;Nw@qU*6}Pje{D|0H@w(ORHmDDxScS)sVfR z`7l`A3hH69Fc4O--z<6XA1AQ*@9DnlLFXZ@q#}}f!+Wm}ZxF*d=QqsTl8}I_7-hfD zHZPlO)e7>Tn$@Yy$L_y5*xLFx%s3?(IYwQWk9QUZsbRl@)h@_b!5eNcmA!)K4lcU5=O^Pperg!5;d%m>Z< z9T16>nL>LfOq_xh9?iaT@){RR;XZsRPAUshvD(!3rwfu7?+m^PcQly zpA`?p#T7~`l2Q6ORwtPr50Hid?R}$ri9{dlZQ}_i=@Moo4q@>n$lF@ z5V#;VOK>*Fr1n(3<7%|R5en zEWu~JT>s+Be9KKgR#0Ty0UTrw95{Qf_y~k#`0Ttv*Y|qFr$~v8yHlL@TBp{g-y$Yt z;3Hjv*C3GKYZvzNZsCHXuc?>l=2zJp(suJ9pC&7^Z63!PGcI4n@C|O1ZBZc7h2@5X ztL0zc)wu@Nr|Cgd;1&og{PvQx=JUa*q~lw!j<|o|7dpyM_yH!y@w+C!RFMkBoi`YE zZm%&q$#$$fbCsz4ot0ZIwBnoE9}|ZZ_8^)L3J~>e$qraZU^dYq0%n{k|(H`3pcvZpYe$CGT`qMXJ?an|C3p z3-xel4-@e&}4F=d0@~l<)Nny%lftdC=e=& zd;(meho_Zx-%hcxAyV#uisN@!7p{xEf*565)V`v>cSr}ClE5^zh4%X1Bg`cDr_iw~ zndpOkfu07zrZHoF{IiszCe|6#?^*bB0(bt^=ALwJPMXvnWZrZ#E=wSQ%H)Mn0127# zjy#LL6SAD_z-LaRp$?+lo~Vo>1lbGyqEcXwN&FrI`g+;L$<0y-8C3RizWq5MjO$5V zGC>{u)nPaTFoo_X@uvr%zlR>zvLw*FW22g8(l#`$^=;CEljM}AQcvRZeN$D8)gzd6 zsC+~j4(e%O@M{`|x|ft>08SL3y9vFe->9(hvXV3*$kmmiGu<%g-+loCIYK{{#Yb#s z#H7lV1Ii!WL8>ocuF6Cm?#zpJfNAnhr|?39k{nl0tm{8D;OgU5@)iThVrW z{D<;q(`z>6fo6jK;D#F^-B?YALxhnMRy;v^GgB9t=;Nyr_N-}e9h+IQc_=ktEQ zUe9O3`*4a3xWTx^hXP$`@}m*FVIQ?wBd8(9Tg`4T7(^MdV{q416j(mb4Mbs0I_3r; zgCpN4%^JviCw9!U`LE$tjHyrgGZ#LqI5OL~F`-JH{NW%dWJQ8gnUS2BJor=RGQYpi z621MIt1U2tz@*O}JPK;&Z&aH*&{FD{KE?*mEIA$Y4Q&-n2n;3M4{QJnzZ%BI#n4DmrxS&h2A^>EEFRF1 zw6<`perp?9CLURlR7hsHTexc_DNEd>I~u_SzEbd*lR#%;X+S^evmGPaa?$ZJ zJMcL4TZFCjozbvAX)Ptk2GS}&&A-l4b@)XTc<|80B)P}XlA`pD z{==v(Fuo|&EGSY%c5O+eMZ_n#?K_Lz}(`6(4;?sWxl*Pu>bLIk^nIJ$gp1#DSq z460D|P_lav?r7UO;xkBJ8&syR_|g&fS58R#4l26EzirZ(d(+#jv$}%;GS)?px;SL) zuQnh0PE6@fO_QB7E>d6^%J+|=9>{oV=ikM~Yob$@|7M&H+nYHHrLCu{dav(Bg&nNV zJ-E=8Xj+BfZWxvCZU6Ql{X_&I(QJQi!6|HaH>E{nxA$;LKQOR?3V$@QN=hfe`&_0& z$GkB7R6IX`q%2S{u5AVK10o5par`^#ckr!$M0FpjdX5B^`~yIUtJp{S>=e$l;dxpx ziZK76UUiLO(!rVd{T>{TO?*%vn%?@gMmdGZye*Oj$Gn3l&QO{7&vqNX-~790F9RLW zW|s9Q)yTIROSxY8yQP1E@y!@ z&+_W1Xz!~Y=Q1(qlPmTQD;HLqGiS@RwO;!XpG6w=ChuQ<_%XExg~XC^&;D}J+WO#E z+F}@kKhl=p{j&ao^$Lq6&xC)te#C%K3+3|d%k%X9oVD|yCDV;Pq8<`xbN?9c8{tJp zS*H;NCkU(E1n2v>o)Z4&sf4`Df$U)eJeY!AaC!MyDxjkWcB2~M<|tv=F`+j#JsBs4 z35U>8ZD4PqV=MjnUEh0|YrDI1%Lh$<#JKK$->C+>w{$4tMsROA86XGqXR5i*hKdV{ z+F4UVT3VuUbqEBoUWq`+tG;P{EWai2c1abNZvQGLK{075q^i8HM`UVWH*5`OaJ@?p zUE$GCi-0D#95(9T%$pS-Ii+I5=7M1F`1bn0;&WS78e}J>GIW3GjP^;^Obw_dQnke9bOw-7BsY8d3chObLD2=l zs~BP`gjUuGM><^z8t|_iu2rIro&td5{W47R4|vW zZ7$yNRIQCI(bk;w)h@l3q9$Fbxbe8Dq9zXhR>odnX5G2XgVguIOewh_)fStdXq%Pw z<$Cg*Nf9iA?GoRGEDCCJP+46)>ruNJs-N&$;~2Ta{N|-vkNu4sbt?AuvzsVsqoQtU zxszh^?@kE$2FuLvvktauH7!e$&XzyQ0mpX5$QB&eS$QM8`mKup=(R>2qHn*v%dLk< z!+yo*f>>;SdM~p(BrxZkBT1?EU@%0DYhvme_SAwi;k$V6E4a7pO1Zv9!UsHohjWBs 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

    1N+QyIg+7+VSlb|OCi^4o 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�lzk{ghU@!fW>>#m0payJ_uZiM# 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 Ze*q2nE;|5(#NYq`002ovPDHLkV1ht=SsVZW literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/bottom_gradient.xml b/app/src/main/res/drawable/bottom_gradient.xml new file mode 100644 index 00000000..8ba12ecd --- /dev/null +++ b/app/src/main/res/drawable/bottom_gradient.xml @@ -0,0 +1,21 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/bottom_menu_border.xml b/app/src/main/res/drawable/bottom_menu_border.xml new file mode 100644 index 00000000..2de75eef --- /dev/null +++ b/app/src/main/res/drawable/bottom_menu_border.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/channel_icon_outline.xml b/app/src/main/res/drawable/channel_icon_outline.xml new file mode 100644 index 00000000..f40f031a --- /dev/null +++ b/app/src/main/res/drawable/channel_icon_outline.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/download_for_offline.xml b/app/src/main/res/drawable/download_for_offline.xml new file mode 100644 index 00000000..cdceb5c6 --- /dev/null +++ b/app/src/main/res/drawable/download_for_offline.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/edit_text_background.xml b/app/src/main/res/drawable/edit_text_background.xml new file mode 100644 index 00000000..9996e25e --- /dev/null +++ b/app/src/main/res/drawable/edit_text_background.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/edit_text_cursor.xml b/app/src/main/res/drawable/edit_text_cursor.xml new file mode 100644 index 00000000..bebd2432 --- /dev/null +++ b/app/src/main/res/drawable/edit_text_cursor.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/foreground.png b/app/src/main/res/drawable/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..3dced0f09a7c9c7dacaa9e65572dfc2552c40d00 GIT binary patch literal 104860 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 T%4?U#Er6Pmwqk|+v*7;+pvy47 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 00000000..fb1640a2 --- /dev/null +++ b/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_add_400.xml b/app/src/main/res/drawable/ic_add_400.xml new file mode 100644 index 00000000..16eeaf2b --- /dev/null +++ b/app/src/main/res/drawable/ic_add_400.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_add_to_query_16dp.xml b/app/src/main/res/drawable/ic_add_to_query_16dp.xml new file mode 100644 index 00000000..b55e8278 --- /dev/null +++ b/app/src/main/res/drawable/ic_add_to_query_16dp.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add_white_16dp.xml b/app/src/main/res/drawable/ic_add_white_16dp.xml new file mode 100644 index 00000000..7de8cc9a --- /dev/null +++ b/app/src/main/res/drawable/ic_add_white_16dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_add_white_8dp.xml b/app/src/main/res/drawable/ic_add_white_8dp.xml new file mode 100644 index 00000000..155b3607 --- /dev/null +++ b/app/src/main/res/drawable/ic_add_white_8dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_airplay.xml b/app/src/main/res/drawable/ic_airplay.xml new file mode 100644 index 00000000..eaec1c0f --- /dev/null +++ b/app/src/main/res/drawable/ic_airplay.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrow_downward.xml b/app/src/main/res/drawable/ic_arrow_downward.xml new file mode 100644 index 00000000..763ca127 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_downward.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_right.xml b/app/src/main/res/drawable/ic_arrow_right.xml new file mode 100644 index 00000000..388bd2c8 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_right.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_aspect_ratio.xml b/app/src/main/res/drawable/ic_aspect_ratio.xml new file mode 100644 index 00000000..cb30bdd0 --- /dev/null +++ b/app/src/main/res/drawable/ic_aspect_ratio.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_back_nav.xml b/app/src/main/res/drawable/ic_back_nav.xml new file mode 100644 index 00000000..faf8c291 --- /dev/null +++ b/app/src/main/res/drawable/ic_back_nav.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_back_thin_white_16dp.xml b/app/src/main/res/drawable/ic_back_thin_white_16dp.xml new file mode 100644 index 00000000..05e5cc29 --- /dev/null +++ b/app/src/main/res/drawable/ic_back_thin_white_16dp.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_back_white_24dp.xml b/app/src/main/res/drawable/ic_back_white_24dp.xml new file mode 100644 index 00000000..18567759 --- /dev/null +++ b/app/src/main/res/drawable/ic_back_white_24dp.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_block.xml b/app/src/main/res/drawable/ic_block.xml new file mode 100644 index 00000000..3e3b57f0 --- /dev/null +++ b/app/src/main/res/drawable/ic_block.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_brightness.xml b/app/src/main/res/drawable/ic_brightness.xml new file mode 100644 index 00000000..1e8b253c --- /dev/null +++ b/app/src/main/res/drawable/ic_brightness.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_brightness_1.xml b/app/src/main/res/drawable/ic_brightness_1.xml new file mode 100644 index 00000000..1e8b253c --- /dev/null +++ b/app/src/main/res/drawable/ic_brightness_1.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_cast.xml b/app/src/main/res/drawable/ic_cast.xml new file mode 100644 index 00000000..2b040de6 --- /dev/null +++ b/app/src/main/res/drawable/ic_cast.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_cast_white_20dp.xml b/app/src/main/res/drawable/ic_cast_white_20dp.xml new file mode 100644 index 00000000..ae5963b7 --- /dev/null +++ b/app/src/main/res/drawable/ic_cast_white_20dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_cast_white_25dp.xml b/app/src/main/res/drawable/ic_cast_white_25dp.xml new file mode 100644 index 00000000..bd90bc78 --- /dev/null +++ b/app/src/main/res/drawable/ic_cast_white_25dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_cast_white_32dp.xml b/app/src/main/res/drawable/ic_cast_white_32dp.xml new file mode 100644 index 00000000..e382055e --- /dev/null +++ b/app/src/main/res/drawable/ic_cast_white_32dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_chat.xml b/app/src/main/res/drawable/ic_chat.xml new file mode 100644 index 00000000..21e19543 --- /dev/null +++ b/app/src/main/res/drawable/ic_chat.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_check.xml b/app/src/main/res/drawable/ic_check.xml new file mode 100644 index 00000000..9b10800f --- /dev/null +++ b/app/src/main/res/drawable/ic_check.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/ic_checkbox_checked.xml b/app/src/main/res/drawable/ic_checkbox_checked.xml new file mode 100644 index 00000000..ba2339b3 --- /dev/null +++ b/app/src/main/res/drawable/ic_checkbox_checked.xml @@ -0,0 +1,31 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_checkbox_unchecked.xml b/app/src/main/res/drawable/ic_checkbox_unchecked.xml new file mode 100644 index 00000000..418be588 --- /dev/null +++ b/app/src/main/res/drawable/ic_checkbox_unchecked.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_chromecast.xml b/app/src/main/res/drawable/ic_chromecast.xml new file mode 100644 index 00000000..dd934026 --- /dev/null +++ b/app/src/main/res/drawable/ic_chromecast.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_clear_16dp.xml b/app/src/main/res/drawable/ic_clear_16dp.xml new file mode 100644 index 00000000..f73e1da1 --- /dev/null +++ b/app/src/main/res/drawable/ic_clear_16dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_clock_white.xml b/app/src/main/res/drawable/ic_clock_white.xml new file mode 100644 index 00000000..41b952cb --- /dev/null +++ b/app/src/main/res/drawable/ic_clock_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml new file mode 100644 index 00000000..a356ef18 --- /dev/null +++ b/app/src/main/res/drawable/ic_close.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_close_thin.xml b/app/src/main/res/drawable/ic_close_thin.xml new file mode 100644 index 00000000..a356ef18 --- /dev/null +++ b/app/src/main/res/drawable/ic_close_thin.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_code.xml b/app/src/main/res/drawable/ic_code.xml new file mode 100644 index 00000000..451c2e86 --- /dev/null +++ b/app/src/main/res/drawable/ic_code.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_code_red.xml b/app/src/main/res/drawable/ic_code_red.xml new file mode 100644 index 00000000..69b9b792 --- /dev/null +++ b/app/src/main/res/drawable/ic_code_red.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_construction.xml b/app/src/main/res/drawable/ic_construction.xml new file mode 100644 index 00000000..694d5f5d --- /dev/null +++ b/app/src/main/res/drawable/ic_construction.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_copy.xml b/app/src/main/res/drawable/ic_copy.xml new file mode 100644 index 00000000..a0f1df78 --- /dev/null +++ b/app/src/main/res/drawable/ic_copy.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_creators.xml b/app/src/main/res/drawable/ic_creators.xml new file mode 100644 index 00000000..e2f6410a --- /dev/null +++ b/app/src/main/res/drawable/ic_creators.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_creators_active.xml b/app/src/main/res/drawable/ic_creators_active.xml new file mode 100644 index 00000000..62d2e3b4 --- /dev/null +++ b/app/src/main/res/drawable/ic_creators_active.xml @@ -0,0 +1,21 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_creators_filled.xml b/app/src/main/res/drawable/ic_creators_filled.xml new file mode 100644 index 00000000..afe50718 --- /dev/null +++ b/app/src/main/res/drawable/ic_creators_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_creators_inactive.xml b/app/src/main/res/drawable/ic_creators_inactive.xml new file mode 100644 index 00000000..df783339 --- /dev/null +++ b/app/src/main/res/drawable/ic_creators_inactive.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_download.xml b/app/src/main/res/drawable/ic_download.xml new file mode 100644 index 00000000..b542e84e --- /dev/null +++ b/app/src/main/res/drawable/ic_download.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_download_off.xml b/app/src/main/res/drawable/ic_download_off.xml new file mode 100644 index 00000000..e500166c --- /dev/null +++ b/app/src/main/res/drawable/ic_download_off.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_dragdrop_white.xml b/app/src/main/res/drawable/ic_dragdrop_white.xml new file mode 100644 index 00000000..0aa73a1c --- /dev/null +++ b/app/src/main/res/drawable/ic_dragdrop_white.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_edit.xml b/app/src/main/res/drawable/ic_edit.xml new file mode 100644 index 00000000..542d4892 --- /dev/null +++ b/app/src/main/res/drawable/ic_edit.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_error.xml b/app/src/main/res/drawable/ic_error.xml new file mode 100644 index 00000000..b52e1a5f --- /dev/null +++ b/app/src/main/res/drawable/ic_error.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_error_pred.xml b/app/src/main/res/drawable/ic_error_pred.xml new file mode 100644 index 00000000..674d803a --- /dev/null +++ b/app/src/main/res/drawable/ic_error_pred.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_expand.xml b/app/src/main/res/drawable/ic_expand.xml new file mode 100644 index 00000000..aa4fb395 --- /dev/null +++ b/app/src/main/res/drawable/ic_expand.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_expand_18dp.xml b/app/src/main/res/drawable/ic_expand_18dp.xml new file mode 100644 index 00000000..338c3df8 --- /dev/null +++ b/app/src/main/res/drawable/ic_expand_18dp.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/ic_expand_white.xml b/app/src/main/res/drawable/ic_expand_white.xml new file mode 100644 index 00000000..c942e0ce --- /dev/null +++ b/app/src/main/res/drawable/ic_expand_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_export.xml b/app/src/main/res/drawable/ic_export.xml new file mode 100644 index 00000000..5c9a70ab --- /dev/null +++ b/app/src/main/res/drawable/ic_export.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_fast_forward.xml b/app/src/main/res/drawable/ic_fast_forward.xml new file mode 100644 index 00000000..9686bc47 --- /dev/null +++ b/app/src/main/res/drawable/ic_fast_forward.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_fast_forward_notif.xml b/app/src/main/res/drawable/ic_fast_forward_notif.xml new file mode 100644 index 00000000..44a9b0dd --- /dev/null +++ b/app/src/main/res/drawable/ic_fast_forward_notif.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_fast_rewind.xml b/app/src/main/res/drawable/ic_fast_rewind.xml new file mode 100644 index 00000000..1a093389 --- /dev/null +++ b/app/src/main/res/drawable/ic_fast_rewind.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_fast_rewind_notif.xml b/app/src/main/res/drawable/ic_fast_rewind_notif.xml new file mode 100644 index 00000000..fe6de961 --- /dev/null +++ b/app/src/main/res/drawable/ic_fast_rewind_notif.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_fastforward.xml b/app/src/main/res/drawable/ic_fastforward.xml new file mode 100644 index 00000000..11d1a358 --- /dev/null +++ b/app/src/main/res/drawable/ic_fastforward.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_fastforward_animated.xml b/app/src/main/res/drawable/ic_fastforward_animated.xml new file mode 100644 index 00000000..0c3354e8 --- /dev/null +++ b/app/src/main/res/drawable/ic_fastforward_animated.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_fc.xml b/app/src/main/res/drawable/ic_fc.xml new file mode 100644 index 00000000..bbc5e8fa --- /dev/null +++ b/app/src/main/res/drawable/ic_fc.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_filter_settings_25dp.xml b/app/src/main/res/drawable/ic_filter_settings_25dp.xml new file mode 100644 index 00000000..c9b055a1 --- /dev/null +++ b/app/src/main/res/drawable/ic_filter_settings_25dp.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_forum.xml b/app/src/main/res/drawable/ic_forum.xml new file mode 100644 index 00000000..69628ae7 --- /dev/null +++ b/app/src/main/res/drawable/ic_forum.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_futo_logo.xml b/app/src/main/res/drawable/ic_futo_logo.xml new file mode 100644 index 00000000..a87e04ef --- /dev/null +++ b/app/src/main/res/drawable/ic_futo_logo.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_futo_logo_text.xml b/app/src/main/res/drawable/ic_futo_logo_text.xml new file mode 100644 index 00000000..ad86784d --- /dev/null +++ b/app/src/main/res/drawable/ic_futo_logo_text.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_help.xml b/app/src/main/res/drawable/ic_help.xml new file mode 100644 index 00000000..9326fd6f --- /dev/null +++ b/app/src/main/res/drawable/ic_help.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_history.xml b/app/src/main/res/drawable/ic_history.xml new file mode 100644 index 00000000..57dddfab --- /dev/null +++ b/app/src/main/res/drawable/ic_history.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_history_29dp.xml b/app/src/main/res/drawable/ic_history_29dp.xml new file mode 100644 index 00000000..bd2b2b23 --- /dev/null +++ b/app/src/main/res/drawable/ic_history_29dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_home.xml b/app/src/main/res/drawable/ic_home.xml new file mode 100644 index 00000000..277cdf13 --- /dev/null +++ b/app/src/main/res/drawable/ic_home.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_home_active_25dp.xml b/app/src/main/res/drawable/ic_home_active_25dp.xml new file mode 100644 index 00000000..bde553a1 --- /dev/null +++ b/app/src/main/res/drawable/ic_home_active_25dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_home_filled.xml b/app/src/main/res/drawable/ic_home_filled.xml new file mode 100644 index 00000000..93bd9979 --- /dev/null +++ b/app/src/main/res/drawable/ic_home_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_home_inactive_25dp.xml b/app/src/main/res/drawable/ic_home_inactive_25dp.xml new file mode 100644 index 00000000..5a1e27db --- /dev/null +++ b/app/src/main/res/drawable/ic_home_inactive_25dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_image.xml b/app/src/main/res/drawable/ic_image.xml new file mode 100644 index 00000000..85385258 --- /dev/null +++ b/app/src/main/res/drawable/ic_image.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_language.xml b/app/src/main/res/drawable/ic_language.xml new file mode 100644 index 00000000..dc53403b --- /dev/null +++ b/app/src/main/res/drawable/ic_language.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_link.xml b/app/src/main/res/drawable/ic_link.xml new file mode 100644 index 00000000..4f80ebb4 --- /dev/null +++ b/app/src/main/res/drawable/ic_link.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_list.xml b/app/src/main/res/drawable/ic_list.xml new file mode 100644 index 00000000..0b57b8de --- /dev/null +++ b/app/src/main/res/drawable/ic_list.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_loader.xml b/app/src/main/res/drawable/ic_loader.xml new file mode 100644 index 00000000..02667f31 --- /dev/null +++ b/app/src/main/res/drawable/ic_loader.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_loader_animated.xml b/app/src/main/res/drawable/ic_loader_animated.xml new file mode 100644 index 00000000..bb84663f --- /dev/null +++ b/app/src/main/res/drawable/ic_loader_animated.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 00000000..c4d19196 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_login.xml b/app/src/main/res/drawable/ic_login.xml new file mode 100644 index 00000000..532bb678 --- /dev/null +++ b/app/src/main/res/drawable/ic_login.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_logout.xml b/app/src/main/res/drawable/ic_logout.xml new file mode 100644 index 00000000..050e5a53 --- /dev/null +++ b/app/src/main/res/drawable/ic_logout.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu.xml b/app/src/main/res/drawable/ic_menu.xml new file mode 100644 index 00000000..760444f3 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_minimize.xml b/app/src/main/res/drawable/ic_minimize.xml new file mode 100644 index 00000000..91d6d03f --- /dev/null +++ b/app/src/main/res/drawable/ic_minimize.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml new file mode 100644 index 00000000..e27952ab --- /dev/null +++ b/app/src/main/res/drawable/ic_more.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_more_inactive_25dp.xml b/app/src/main/res/drawable/ic_more_inactive_25dp.xml new file mode 100644 index 00000000..3a2510a8 --- /dev/null +++ b/app/src/main/res/drawable/ic_more_inactive_25dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_move_up.xml b/app/src/main/res/drawable/ic_move_up.xml new file mode 100644 index 00000000..fb20a55c --- /dev/null +++ b/app/src/main/res/drawable/ic_move_up.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_movie.xml b/app/src/main/res/drawable/ic_movie.xml new file mode 100644 index 00000000..af934aae --- /dev/null +++ b/app/src/main/res/drawable/ic_movie.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_music.xml b/app/src/main/res/drawable/ic_music.xml new file mode 100644 index 00000000..52d5ab27 --- /dev/null +++ b/app/src/main/res/drawable/ic_music.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_no_internet_86dp.xml b/app/src/main/res/drawable/ic_no_internet_86dp.xml new file mode 100644 index 00000000..5e9d34d9 --- /dev/null +++ b/app/src/main/res/drawable/ic_no_internet_86dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_paid.xml b/app/src/main/res/drawable/ic_paid.xml new file mode 100644 index 00000000..085d07b5 --- /dev/null +++ b/app/src/main/res/drawable/ic_paid.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_paid_200.xml b/app/src/main/res/drawable/ic_paid_200.xml new file mode 100644 index 00000000..e1f4eb74 --- /dev/null +++ b/app/src/main/res/drawable/ic_paid_200.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_pause.xml b/app/src/main/res/drawable/ic_pause.xml new file mode 100644 index 00000000..a6b14ed8 --- /dev/null +++ b/app/src/main/res/drawable/ic_pause.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_pause_notif.xml b/app/src/main/res/drawable/ic_pause_notif.xml new file mode 100644 index 00000000..d267e9fd --- /dev/null +++ b/app/src/main/res/drawable/ic_pause_notif.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_pause_white.xml b/app/src/main/res/drawable/ic_pause_white.xml new file mode 100644 index 00000000..a6b14ed8 --- /dev/null +++ b/app/src/main/res/drawable/ic_pause_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_peertube.png b/app/src/main/res/drawable/ic_peertube.png new file mode 100644 index 0000000000000000000000000000000000000000..ff9cf03076e236b04b9421963e4f51d85dd0f2a3 GIT binary patch literal 7197 zcmeHKcT`i^)=wfOzCt1i>~6 z=rc4CbVfuSm7+e-Afh0mVk~12zxZ8Vmpuse(Yl7|@X4$SC9##1x>* z1x+8wFa~Z$NY%-{0LaFlsUf{Pmw=wE-FapeMin zK!TaS^nUcVg*}k-1J;lTXWoGe5+-q6d!Iq?nUitgiQ{u+NdqTt>Y_JJs{D>%NRB#ZLm6T}5 zU?(v-cCthfykZC!H<^gR5^$stlM~Mu(lAe2PGKNEn}%6L3C0JDyf{3*f4Z2nAU!04 zl`dd8u`zD0nl3UbAV}m$8IUY7K`5chXqZV}Dv)6{4g*a>qyicy3Tz~=Bryjf+mY?? zSRWZbm56cGgj~dIE;ZcS_cH|;(J(xzR7Az$($doG(nxkm;&>dv$;kNNE@hutQ(+OB4kMf29{nK34(M11DpM za0EL%E-?}Jt%pSFlL|;aC-fgZBoQk_99%d@l9VE5aePuaLaEue6l~U4e^H7!VRAZb z7LJp^Nd!;{h)Vb_Bz)w)dcY-!=O>CLy+EJdh#VTgA4zcgV93@H~l&@e%KVT$Z8Lj*sOvp~v# zt4XkTBG}uL2m~_T$$?Dx#(N=0ECD+a=A67dC*9#?p@PYP!ZP4}3J50kU@lZIF^3^d z5=SH@CD1TmuidZaU~rt+3@O8#A>{zluNQH|my6g2Po&~MB>>X}C$ah575@zlA0EgB z;K7pn^Cckuib>U{6SaVo{Au)Qn82SrN)R-8D5wn9rzuDnsT}rXo`CC!+oAG4!$$K%-99@e`?|~3Hauc z0e+u!;PL`@A@1vC__=2Awevsxd|r$HVFW<^Bgo&X?~ii*DA(Vrz~2J@*j+!$^|vbU zx4=Jk*Z*6&H2->?;t0W0P#Sny8i~(J1}|D_%s@YH#031UXg#nMXf#CrizNtzmI2(5 zh+Uo3LbL_WH~^W%ZuaDI>Dgq!~bS0tPygH^x%P_f^GPAzMa{GaN>t}1TQ~|gsFnvc43UK?-7t@Um2>|snlpc!b0`9{R!mWh`#^}7~ z@3vPTFm%%0JkoF8aUbT)3`{S7HU-)IDn0fJS7!?BHHVH9S!HF}4q7@S`)2{n)#A9j zM-c$y$x~Nh?_AfdgFdf0!_yfgXS+7l-F@LvT?G=U2P^to|BHxLW@VAnP32Qi7}ym? z^E+T(Z`c9>eKw%a)0(8WiGr2r%GtWQpTKXYJwE(OM?xCtNy9yg-qc-`<*sT_-0;i9 zs@kKv<+~o|W8?z;npZB1b=H_2Fn<}4K6Yj4C8?jyXv)zrt9FM8#^aT~J~=AKC=HRh zRhnJCm@UIsX+3ruyNlE`&~q(b`llaIxW)BAB`lW+O}`9tCA(d8T2`n zJEKau>1vPNy1F>L1wX!-Vn=>9T1x@+#w@?ilsZ42pL>j-$Cabi>J}erIGcXIt~6ce zcJXYLSA#+2Y-FK#gY06ixUG6`Tl1J~ z`62_3rEDSix-YXZDVBydQv#5(whp5dpBXdSI$wJ4nG4-+FPL!(^CyWusQUEnps?mv zXUe&_t`F1`JG0y^)azyM$C^+X>eVGmxzB93nS*ao$Tbhdg7FxA=2{D()~x5;TIW~k zwKnMnNDa#;{Q32yPOI7;OVi-X?X0#L3e&`dX^v z=oRSWzVL3f`a4T1XKRQ1bl+PTa}z3g6TqAKLTzz=h4%6@i#%H@?9WqF6UqJqI(4J# zQauEl$EL|W3hPdl&s3<+gHrUt!1S5C+YXK77XTNUX8AP5TN3A4llieB-R+cSA4cUW1u3%ki+|<0os)=FGeNK+8 zO^wsZTcna0W?ZYUSV4epfygx}Lz(S|IQSdc`IsIqJ~D!W2^;fmgWI#!k$ z!}3E9ueGT)I_Hg}PkxvVBGd&xw2}+>?!_1GDrhTTJ=J<#`O!n8^Ok$DBz==C;S|F~*EHA`>tG=!QGL8tAYnu5PEefeE&QVaN)B!yW>+wLaBqPgiEC zzxiWssQcSh50?l6L(l!=?VHP)B-< zq6)iWqcSJ7-*;tR+n|2?tk&)N5qqa%fAgQ0O?~@vZNP%!8?!Fn8*cpYR(cU|XRPP7 z5bhoK#t_Jkj@LVj{?t}wQA`9~ZcY5m+7Xm{-Klj7Zoz}D(dT}tPj0p5w2mYeNG~b6 zJEQE~mSv3TAS;%-vYH&GX$D*B=U2A1wNVl%UB{Lk$*qciJ~f3MA7g=dado{&j}?@; za^?Hcmb51sJ7}XN0&R)BddTcpLdNN!rclKFbD@VCb3H3Bu!%dtDj8p2Q#-PkVwCp* z*EpQ};dt$I#JiUQ6H|I@uL8H=&^3ccK79m!Kzaqs)-A#EW{vpI_peJI|D!i!N08<5 z*49?z8{4t2yNmA~>2ZIQ@bTDn*)0RqX=s&RxBtWFM&ZCQZ_Kl4{c?`q9nryq$~azO zh3Z}XG$YonP|K}1z!VIf8~CjRL7^J6@!p(Gv(K*#K^V&GijC>9N~Ln?T>bpf8z;4= zo~vXYQkEAT8$UYh#eM`{zuPPgR8kpXP3h_F?S206a9ufv)K@Y~AdkO#{q2;F;iBX2 zg9YpwW$B(hZgFindJoV1=2>~P2FyXY3PKc!TvB#dJAm1(t#5EB3z<0Wzwr6;D!y@e z=DDa_i;76X_!^!8eL=qWkk9`4q$fU#4NE@E&@sBZTuA5`pSLq8HTTS&H|Nivr~8V7 zJEK@>|GenolOt1Kn|d)U$-`KCh^DKEJYbq_Z`9t>XNgu?FRAi?2mLCsygm^On3`=V z7woDi%z86IHnZ|f*lb*F^{nIrHTrP{i z3{;lyJJH>F0G1XGSXyf-3tsp(MJYTTdAd!zn!zca%c}6GK(Q{Zt*+S_#Leb+Lr2@^ zv4Wo5zkh#8LAKf&o9L?6*44&$Dm-qBL!t#MuRM6+4C^h9R6i3rp0wC{L088}#rjQe z^!otIt#7mD>*_Y0r086_eA(i)vVVQf2JpZzXvoT-P^s_qs%{&p{9;8na_G1j3TIGy zDE!ciE4Nx#CWrMvVIIFlJG;2KO+b^uib?|ZZGZiEU}Me(wKbYEj?|y&;k_{Z-6QAp zh)I2K+qMgAQJ9|Y!P|QY+^6mN22}3mc!1HD|tj~fqBJFTh*y@;7 zq0QTD4Xj4($bW-RBHT>?uS&Hh>&C6l-@Y%rM-9%OuX7wmt iS>G2Tu{vEiK4%_2`sYi*PVd2UJ%a8N;$7>xEc;)`S_91h literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_person.xml b/app/src/main/res/drawable/ic_person.xml new file mode 100644 index 00000000..6b390557 --- /dev/null +++ b/app/src/main/res/drawable/ic_person.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_person_add.xml b/app/src/main/res/drawable/ic_person_add.xml new file mode 100644 index 00000000..8d33a6ec --- /dev/null +++ b/app/src/main/res/drawable/ic_person_add.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_person_search_300w.xml b/app/src/main/res/drawable/ic_person_search_300w.xml new file mode 100644 index 00000000..9285fa41 --- /dev/null +++ b/app/src/main/res/drawable/ic_person_search_300w.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_pin.xml b/app/src/main/res/drawable/ic_pin.xml new file mode 100644 index 00000000..061e46a1 --- /dev/null +++ b/app/src/main/res/drawable/ic_pin.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_play.xml b/app/src/main/res/drawable/ic_play.xml new file mode 100644 index 00000000..4bd70b88 --- /dev/null +++ b/app/src/main/res/drawable/ic_play.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_play_200w.xml b/app/src/main/res/drawable/ic_play_200w.xml new file mode 100644 index 00000000..d77dc277 --- /dev/null +++ b/app/src/main/res/drawable/ic_play_200w.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_play_notif.xml b/app/src/main/res/drawable/ic_play_notif.xml new file mode 100644 index 00000000..211218a6 --- /dev/null +++ b/app/src/main/res/drawable/ic_play_notif.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_play_white_nopad.xml b/app/src/main/res/drawable/ic_play_white_nopad.xml new file mode 100644 index 00000000..b3e0b907 --- /dev/null +++ b/app/src/main/res/drawable/ic_play_white_nopad.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_playlist.xml b/app/src/main/res/drawable/ic_playlist.xml new file mode 100644 index 00000000..add0b474 --- /dev/null +++ b/app/src/main/res/drawable/ic_playlist.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_playlist_add.xml b/app/src/main/res/drawable/ic_playlist_add.xml new file mode 100644 index 00000000..00af4d6b --- /dev/null +++ b/app/src/main/res/drawable/ic_playlist_add.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_playlist_filled.xml b/app/src/main/res/drawable/ic_playlist_filled.xml new file mode 100644 index 00000000..cb393c70 --- /dev/null +++ b/app/src/main/res/drawable/ic_playlist_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_playlist_select.xml b/app/src/main/res/drawable/ic_playlist_select.xml new file mode 100644 index 00000000..cea13914 --- /dev/null +++ b/app/src/main/res/drawable/ic_playlist_select.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_playlists_active.xml b/app/src/main/res/drawable/ic_playlists_active.xml new file mode 100644 index 00000000..064708f8 --- /dev/null +++ b/app/src/main/res/drawable/ic_playlists_active.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_playlists_inactive_25dp.xml b/app/src/main/res/drawable/ic_playlists_inactive_25dp.xml new file mode 100644 index 00000000..21f502ea --- /dev/null +++ b/app/src/main/res/drawable/ic_playlists_inactive_25dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_qr.xml b/app/src/main/res/drawable/ic_qr.xml new file mode 100644 index 00000000..ed6f49ce --- /dev/null +++ b/app/src/main/res/drawable/ic_qr.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_queue_16dp.xml b/app/src/main/res/drawable/ic_queue_16dp.xml new file mode 100644 index 00000000..20c10efe --- /dev/null +++ b/app/src/main/res/drawable/ic_queue_16dp.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_queue_add.xml b/app/src/main/res/drawable/ic_queue_add.xml new file mode 100644 index 00000000..b900c1b0 --- /dev/null +++ b/app/src/main/res/drawable/ic_queue_add.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_refresh.xml b/app/src/main/res/drawable/ic_refresh.xml new file mode 100644 index 00000000..8704ce8c --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_remove.xml b/app/src/main/res/drawable/ic_remove.xml new file mode 100644 index 00000000..0ac6c716 --- /dev/null +++ b/app/src/main/res/drawable/ic_remove.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_remove_white_8dp.xml b/app/src/main/res/drawable/ic_remove_white_8dp.xml new file mode 100644 index 00000000..fa90362f --- /dev/null +++ b/app/src/main/res/drawable/ic_remove_white_8dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_repeat.xml b/app/src/main/res/drawable/ic_repeat.xml new file mode 100644 index 00000000..6eaf6711 --- /dev/null +++ b/app/src/main/res/drawable/ic_repeat.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_replay.xml b/app/src/main/res/drawable/ic_replay.xml new file mode 100644 index 00000000..1667ee5b --- /dev/null +++ b/app/src/main/res/drawable/ic_replay.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_rewind.xml b/app/src/main/res/drawable/ic_rewind.xml new file mode 100644 index 00000000..94a30226 --- /dev/null +++ b/app/src/main/res/drawable/ic_rewind.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_rewind_animated.xml b/app/src/main/res/drawable/ic_rewind_animated.xml new file mode 100644 index 00000000..6e01f01e --- /dev/null +++ b/app/src/main/res/drawable/ic_rewind_animated.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_save.xml b/app/src/main/res/drawable/ic_save.xml new file mode 100644 index 00000000..cca193ae --- /dev/null +++ b/app/src/main/res/drawable/ic_save.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_schedule.xml b/app/src/main/res/drawable/ic_schedule.xml new file mode 100644 index 00000000..d1e88768 --- /dev/null +++ b/app/src/main/res/drawable/ic_schedule.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_screen_lock_rotation.xml b/app/src/main/res/drawable/ic_screen_lock_rotation.xml new file mode 100644 index 00000000..908a0fd2 --- /dev/null +++ b/app/src/main/res/drawable/ic_screen_lock_rotation.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_screen_rotation.xml b/app/src/main/res/drawable/ic_screen_rotation.xml new file mode 100644 index 00000000..c29250d5 --- /dev/null +++ b/app/src/main/res/drawable/ic_screen_rotation.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_screen_share.xml b/app/src/main/res/drawable/ic_screen_share.xml new file mode 100644 index 00000000..753fd3cc --- /dev/null +++ b/app/src/main/res/drawable/ic_screen_share.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml new file mode 100644 index 00000000..33324ba8 --- /dev/null +++ b/app/src/main/res/drawable/ic_search.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_search_300w.xml b/app/src/main/res/drawable/ic_search_300w.xml new file mode 100644 index 00000000..c8c8f09e --- /dev/null +++ b/app/src/main/res/drawable/ic_search_300w.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_search_thin.xml b/app/src/main/res/drawable/ic_search_thin.xml new file mode 100644 index 00000000..33324ba8 --- /dev/null +++ b/app/src/main/res/drawable/ic_search_thin.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_search_white_16dp.xml b/app/src/main/res/drawable/ic_search_white_16dp.xml new file mode 100644 index 00000000..2406148d --- /dev/null +++ b/app/src/main/res/drawable/ic_search_white_16dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_search_white_20dp.xml b/app/src/main/res/drawable/ic_search_white_20dp.xml new file mode 100644 index 00000000..391a626c --- /dev/null +++ b/app/src/main/res/drawable/ic_search_white_20dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_search_white_25dp.xml b/app/src/main/res/drawable/ic_search_white_25dp.xml new file mode 100644 index 00000000..321be04e --- /dev/null +++ b/app/src/main/res/drawable/ic_search_white_25dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_security.xml b/app/src/main/res/drawable/ic_security.xml new file mode 100644 index 00000000..1cd5d515 --- /dev/null +++ b/app/src/main/res/drawable/ic_security.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_security_pred.xml b/app/src/main/res/drawable/ic_security_pred.xml new file mode 100644 index 00000000..409f286a --- /dev/null +++ b/app/src/main/res/drawable/ic_security_pred.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_security_red.xml b/app/src/main/res/drawable/ic_security_red.xml new file mode 100644 index 00000000..390a51aa --- /dev/null +++ b/app/src/main/res/drawable/ic_security_red.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 00000000..274c3eda --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings_29dp.xml b/app/src/main/res/drawable/ic_settings_29dp.xml new file mode 100644 index 00000000..aa3b3eb3 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings_29dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml new file mode 100644 index 00000000..f4721095 --- /dev/null +++ b/app/src/main/res/drawable/ic_share.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_shuffle.xml b/app/src/main/res/drawable/ic_shuffle.xml new file mode 100644 index 00000000..098d5654 --- /dev/null +++ b/app/src/main/res/drawable/ic_shuffle.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_smart_display.xml b/app/src/main/res/drawable/ic_smart_display.xml new file mode 100644 index 00000000..68758978 --- /dev/null +++ b/app/src/main/res/drawable/ic_smart_display.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_sources.xml b/app/src/main/res/drawable/ic_sources.xml new file mode 100644 index 00000000..bab6553f --- /dev/null +++ b/app/src/main/res/drawable/ic_sources.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_sources_active.xml b/app/src/main/res/drawable/ic_sources_active.xml new file mode 100644 index 00000000..16bb72be --- /dev/null +++ b/app/src/main/res/drawable/ic_sources_active.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_sources_filled.xml b/app/src/main/res/drawable/ic_sources_filled.xml new file mode 100644 index 00000000..be25d0ca --- /dev/null +++ b/app/src/main/res/drawable/ic_sources_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_sources_inactive.xml b/app/src/main/res/drawable/ic_sources_inactive.xml new file mode 100644 index 00000000..cf448d30 --- /dev/null +++ b/app/src/main/res/drawable/ic_sources_inactive.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_stop_notif.xml b/app/src/main/res/drawable/ic_stop_notif.xml new file mode 100644 index 00000000..719526e9 --- /dev/null +++ b/app/src/main/res/drawable/ic_stop_notif.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_store.xml b/app/src/main/res/drawable/ic_store.xml new file mode 100644 index 00000000..8212929a --- /dev/null +++ b/app/src/main/res/drawable/ic_store.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_store_200.xml b/app/src/main/res/drawable/ic_store_200.xml new file mode 100644 index 00000000..5ed83537 --- /dev/null +++ b/app/src/main/res/drawable/ic_store_200.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_subscriptions.xml b/app/src/main/res/drawable/ic_subscriptions.xml new file mode 100644 index 00000000..5e43e1bf --- /dev/null +++ b/app/src/main/res/drawable/ic_subscriptions.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_subscriptions_active_25dp.xml b/app/src/main/res/drawable/ic_subscriptions_active_25dp.xml new file mode 100644 index 00000000..3fcb8c3a --- /dev/null +++ b/app/src/main/res/drawable/ic_subscriptions_active_25dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_subscriptions_filled.xml b/app/src/main/res/drawable/ic_subscriptions_filled.xml new file mode 100644 index 00000000..7bdfd6b4 --- /dev/null +++ b/app/src/main/res/drawable/ic_subscriptions_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_subscriptions_inactive_25dp.xml b/app/src/main/res/drawable/ic_subscriptions_inactive_25dp.xml new file mode 100644 index 00000000..1b359e9e --- /dev/null +++ b/app/src/main/res/drawable/ic_subscriptions_inactive_25dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_thumb_down.xml b/app/src/main/res/drawable/ic_thumb_down.xml new file mode 100644 index 00000000..8de5f492 --- /dev/null +++ b/app/src/main/res/drawable/ic_thumb_down.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_thumb_up.xml b/app/src/main/res/drawable/ic_thumb_up.xml new file mode 100644 index 00000000..fdcf53d4 --- /dev/null +++ b/app/src/main/res/drawable/ic_thumb_up.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_trash.xml b/app/src/main/res/drawable/ic_trash.xml new file mode 100644 index 00000000..d9ba29ee --- /dev/null +++ b/app/src/main/res/drawable/ic_trash.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_trash_18dp.xml b/app/src/main/res/drawable/ic_trash_18dp.xml new file mode 100644 index 00000000..c10d6930 --- /dev/null +++ b/app/src/main/res/drawable/ic_trash_18dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_tune_300.xml b/app/src/main/res/drawable/ic_tune_300.xml new file mode 100644 index 00000000..f41b20d6 --- /dev/null +++ b/app/src/main/res/drawable/ic_tune_300.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_update.xml b/app/src/main/res/drawable/ic_update.xml new file mode 100644 index 00000000..132729bd --- /dev/null +++ b/app/src/main/res/drawable/ic_update.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_update_animated.xml b/app/src/main/res/drawable/ic_update_animated.xml new file mode 100644 index 00000000..1c35237f --- /dev/null +++ b/app/src/main/res/drawable/ic_update_animated.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_update_fail_251dp.xml b/app/src/main/res/drawable/ic_update_fail_251dp.xml new file mode 100644 index 00000000..50c7784c --- /dev/null +++ b/app/src/main/res/drawable/ic_update_fail_251dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_update_success_251dp.xml b/app/src/main/res/drawable/ic_update_success_251dp.xml new file mode 100644 index 00000000..9b10800f --- /dev/null +++ b/app/src/main/res/drawable/ic_update_success_251dp.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/ic_view_queue.xml b/app/src/main/res/drawable/ic_view_queue.xml new file mode 100644 index 00000000..baa532a4 --- /dev/null +++ b/app/src/main/res/drawable/ic_view_queue.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_visibility_off.xml b/app/src/main/res/drawable/ic_visibility_off.xml new file mode 100644 index 00000000..788c4520 --- /dev/null +++ b/app/src/main/res/drawable/ic_visibility_off.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_volume_off.xml b/app/src/main/res/drawable/ic_volume_off.xml new file mode 100644 index 00000000..6cfb3622 --- /dev/null +++ b/app/src/main/res/drawable/ic_volume_off.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_volume_up.xml b/app/src/main/res/drawable/ic_volume_up.xml new file mode 100644 index 00000000..e80c216b --- /dev/null +++ b/app/src/main/res/drawable/ic_volume_up.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_volume_up_1.xml b/app/src/main/res/drawable/ic_volume_up_1.xml new file mode 100644 index 00000000..c641bd5d --- /dev/null +++ b/app/src/main/res/drawable/ic_volume_up_1.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_watchlist_add.xml b/app/src/main/res/drawable/ic_watchlist_add.xml new file mode 100644 index 00000000..e33063d5 --- /dev/null +++ b/app/src/main/res/drawable/ic_watchlist_add.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_web_white.xml b/app/src/main/res/drawable/ic_web_white.xml new file mode 100644 index 00000000..629fd413 --- /dev/null +++ b/app/src/main/res/drawable/ic_web_white.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_wrench.xml b/app/src/main/res/drawable/ic_wrench.xml new file mode 100644 index 00000000..2dcefe07 --- /dev/null +++ b/app/src/main/res/drawable/ic_wrench.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/neopass.png b/app/src/main/res/drawable/neopass.png new file mode 100644 index 0000000000000000000000000000000000000000..1ce49de168a00056292213f1d7b686660e86d27e GIT binary patch literal 25194 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�C)Dk~8-$-+xg;AP-m5q(N87+esug3Pi8C`?5- zkx1#v>2X|>9oGVfaFqGqPI)#t|Mo7kwxXpxOLkvUEifN=M9}B-g28zeJ(4@E{tAZx zlMwc!Wz|2k(VIlaD}aNUVs=_t{Cpt5uE63>)l|X;;60QTVM|)B-JHzj%%{x&kzn|iurA)DvF#Zz@^siz@kppRPD#h5MBe`0^TxIKeW$J zeqU-*FYY$0mqI1NuE1DeEJ8(=6hf-!c~M}ZPBqtc`b?Pz1Q-tduM;(un{fc)GvE_o z3Gg+-m%xfr+y1WMydlYz?6`SZv27Sz;xsMX8ev;t7%&21i%y;ulReK(e&xL8-{rZsQz1d;JDi=V5SAcJ-t$B|XR6P|{j<1F;1{_F{`{E%oLS&$V0^+FEH%?5zQ)Xcx#Ge(PMH!Ln;Looaz?lU`0M7%a zE3KGKt)0D`na+0TV_nA@;13*c-k&d!%C9}aGt5tcU2ztrGOx80(Y%?^{_W473%*SB z>v&LlHXL z=JaaqT#DZ0rZ2M!+ZHU+o)S@Ut>sdGrsfG|toDm^(fB}zO3q?ZU60lz~+%0G5~5?$w! zAbIlEBFqA=;}mmwp+TjvOCw%t4MjKyI0=H8lKHss)Hx3Q3WQq(Zr5q%>OzG`m#7UY zmsvd&js;G~$)xNY(VfZrTKzjSFArjei{C c0X+@!|KRH0dNb)~4FCWD07*qoM6N<$g2KW%!2kdN literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/neopass_button.png b/app/src/main/res/drawable/neopass_button.png new file mode 100644 index 0000000000000000000000000000000000000000..336a8b52118b60c353f745ccab79f6b334eb916b GIT binary patch literal 24412 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 + + + diff --git a/app/src/main/res/drawable/player_progress.xml b/app/src/main/res/drawable/player_progress.xml new file mode 100644 index 00000000..946119b2 --- /dev/null +++ b/app/src/main/res/drawable/player_progress.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/player_seekbar_style.xml b/app/src/main/res/drawable/player_seekbar_style.xml new file mode 100644 index 00000000..de717fa3 --- /dev/null +++ b/app/src/main/res/drawable/player_seekbar_style.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/player_thumb.xml b/app/src/main/res/drawable/player_thumb.xml new file mode 100644 index 00000000..5f6518f3 --- /dev/null +++ b/app/src/main/res/drawable/player_thumb.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_outline.xml b/app/src/main/res/drawable/rounded_outline.xml new file mode 100644 index 00000000..86dda574 --- /dev/null +++ b/app/src/main/res/drawable/rounded_outline.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_square_outline.xml b/app/src/main/res/drawable/rounded_square_outline.xml new file mode 100644 index 00000000..aa6e8fd0 --- /dev/null +++ b/app/src/main/res/drawable/rounded_square_outline.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/stripe.png b/app/src/main/res/drawable/stripe.png new file mode 100644 index 0000000000000000000000000000000000000000..1a7ec0d393b78d72c1f10a2be0c1b9c9ce056a10 GIT binary patch literal 1825 zcmV++2j2LJP)wFayx(P;32P)$HgH1UC`(MWu>RZ_JN6pab1{xC+`LMqt$ zNHA)m^oPVm_~#?WL{Y2dC5;7vvb!x$TS{rWd++fZ%H6iGEv2F0%KgY?=W%A{d^2ax znVAwfaNxj!0|yQqIJkh|{+Y+i8@x#}{!JE{N6ZNnClHbdbp9hE^5#cXlbh`FD7i(8 z#PH<=KNX>eDNeymzqwNr@2K|s+I3#p6{#7`86t8o#WAojH?$oNO}Fz=avqo41MbNb z=V(9pSCoUI|1lhL@rnydj+yr|tF=gNM%JQH#(5zJrQ6RgLx-$xbx%!2=2A~M^L7+% zrBTL+$uaQ>G36mR;8}ZieRKcWTM;YXhN)X1DRuv&&m9Z)p!M0ofaAR1k@#Ttf>=7uIM85{Fo^S1h?4>4~}mrue-r z&SPnbH(;<=(Q=bc>koY_#hGvJk><8J%u=sBM!sE#k$0lL zGAhgT8u#$*%+$;~Ybqzr5jN+e>zz?`#M-1Ho>`u7*KN>ees@~dO0i{{tRb3 z{bi;~QI&RboN$zAM)Gr*+j^ zYR}qOR32Pym({Gy_!cw07nV}-{@Y{QbXj^h@kMqU4TZ}>ckBG&n zf4<41(^+FOw!^5GAsNC<u_-S^CO;pE4r##Igx(r_vBG zNzKIM%!O4HELyb9(_4xh%XTu%pKUo<)vfsNs8}>Z=j$F~6ZxNG^%>KlpvNgaDrqqc z{S%mo4z^z`EZ(>w$362-?8A3J*9DuZ@9x$%F64R?BlgfJ!ga#k z70n1GQF5O}`)CWs&IYk;Dk+g9{b4a;F4Ev}js-VyF70t!dy7f~)l2-^P2=9DqzMBy z^%u>#a>02!N?`?>)~=nAC2)VU&&34$!?MhvVeop4y~ZvpSk`)l zvdmn}-1W%*c~W6hl}Ffw$to{~+JSw#8@jAQRVodK95(;{_f}5Yg8kM&y$z!mMr-;aoxK7uIlRCZY@_>7AVH=%`^ghuqyix{49N}9hz zSP87D%G{3X$8EWCy!&*>xHk}L&L9`tEJZR4%Y#o9yu52n-(bJG3rhow5hrTF-VLFv z5`4K;1!aM8whWWFu*&Y|{M%CW=kD(D_H{|hP3*PnVDVTg{PVTyD3#?6Rwh>}x1$6N4*@gv1C5SI6)x_rCse3YC%AL4XS2m$@@6QQ_* zzH`b}mdInPR*eruOJj-Zzt!dCo&KuypN54ejW}Bn2M!!KaNxkfrHOw5=_8tfaBdob P00000NkvXXu0mjfq6d1S literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/tab_border.xml b/app/src/main/res/drawable/tab_border.xml new file mode 100644 index 00000000..590864e9 --- /dev/null +++ b/app/src/main/res/drawable/tab_border.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/toggle.xml b/app/src/main/res/drawable/toggle.xml new file mode 100644 index 00000000..53519ee6 --- /dev/null +++ b/app/src/main/res/drawable/toggle.xml @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/toggle_animated.xml b/app/src/main/res/drawable/toggle_animated.xml new file mode 100644 index 00000000..fa2bee89 --- /dev/null +++ b/app/src/main/res/drawable/toggle_animated.xml @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/toggle_animated_reverse.xml b/app/src/main/res/drawable/toggle_animated_reverse.xml new file mode 100644 index 00000000..07421ea8 --- /dev/null +++ b/app/src/main/res/drawable/toggle_animated_reverse.xml @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/toggle_disabled.xml b/app/src/main/res/drawable/toggle_disabled.xml new file mode 100644 index 00000000..e773a8c8 --- /dev/null +++ b/app/src/main/res/drawable/toggle_disabled.xml @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/toggle_enabled.xml b/app/src/main/res/drawable/toggle_enabled.xml new file mode 100644 index 00000000..ba96eda4 --- /dev/null +++ b/app/src/main/res/drawable/toggle_enabled.xml @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/video_thumbnail_outline.xml b/app/src/main/res/drawable/video_thumbnail_outline.xml new file mode 100644 index 00000000..ba0de6f8 --- /dev/null +++ b/app/src/main/res/drawable/video_thumbnail_outline.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/font/inter_black.ttf b/app/src/main/res/font/inter_black.ttf new file mode 100644 index 0000000000000000000000000000000000000000..5aecf7dc414ec9b807d93f6a5b8d676f5d1f0279 GIT binary patch literal 316372 zcmcG%0bEzr`9FToy`Rthd_b}fib_QWib6?Biil-~hKfbTCK@$TR8&;7Pohba^i$Eo zphg=NHNK6E6%{qNvE?=@R<_)tjeVnyZLCq_Th!RTR<2M!_k8~E=bU@*=Y9~=zOVmp zPLk)I`<&-I=XsvvNAd8^{u7q2L*Dz0BSYj$$Yj`fOKIS4GpD+?AC9Q(nauT@-c*Wve^ z4cpiGXU+WRzZCV3JVn`f-{y7Ye(DYQ>A81v+3lO=U4GvjMPV_DI^*@yjq6G-eQn)R zMJ+gj=b5DdO#aV#uK@p1{GMF8eOJYrvQ-E1Jg6uszb(6E!@8*YcRCa`?Kpn#+P<#B zA05MV{N9cBi96P9-*_@A_IgEqzfVy-WBs?3?|Qmr?oLJh=$N9EJnrAQ(O;_9-h<~o zXzx+nEZYb~w?zjO4Zn?1&Q-=M%A(k$ST^!+cGRxVYa{;}0af9OH=BN{9rW`H@l*a= zQI%{AIpC#!#wg0VBu0OxvZUA~wZUjno7Ew;*=XU}KBLQ3I^5t@h7@k=b7Uzr{`GA2etL!+_j zDVdpbGp2iVeL~#CQ3t1H-Sf-U-zmKD!PToDyfGv1+PU}}9-rR#)BUSzSFWsGg`e$g z>FmpvF}7s(Wx3XHV#+M?y!WXAHJ{){x3?v_3vf&vhJTy8-7$Q{YOilsdK|+{wc$J1 zV-ikyyy5ndc*1NcC$`x1*zLVzsrymjvEIZmJUU?ayVvtn7*BMo4d3tSR(mWw(G51- zuhyG%4v>smaPJ`dK;k#*op8OzlO67NjLT}T2fT%B$j0Aj!9B;+c-Cpd>umTZN<2Gk z!-F>bQ`Q`AKgMUnKVu3L^bkBL_NftAuKLFhA zCtljj(s^44yK3{5mDeP(ur9fpQ(0>Q&t=_ph4*iWZ%Dd5vl=AA>_(9Ch|xl%ggOWw zRV=i8s&Z*0DdQ)_=~Ji7RHshSC!~-eo2<^6ovA_NiL|++la}6f*QR+luDE_y%BA?% zr8nHLdPZ`3Va?5Dxrt#aJw4eQ9=c%41-a=9HavRXl=E`!vUXbXt00q_e9tI!Ojb(N0TnW-*fKc! zQ%e>{ed-CzEic%n)t}6+ zHTo-DrCx<+^CtP%-FY&DRkJ2uEN63!FPenuN*am2U2C^2HsAppUgD|_!-Ykm_L@?K zm*PRrc;!L`JS>k*n(Y#+q-VlpbwZr#f@?9G*30QrlVYJy^v|hNtF(%b*;P+&`0<{q zp1=?J%YWyk%vD>v`1V!&c>i+$Rn?_~US;10jko^dj`%qD-akFq!cRWT+P7@ZEy-Yo zGjF(R?MBcp`WCClB0;;K9ThZI8TCXHXqWIB-7h4qimOhzcfBsHUmC{H<5gTKXx~AY zJUmp%GvF3nt0++&7~MHak}^Y?3-w8wqU)0;B+Z>ZE_qH8{IRnct*e69uUb2e@p$$; zyYf%0jJKV6{CZA)#e(|duQJ~QLs<3Cju~QNFldW8*6$Fp%22;2=uJ?j(I94OuyW+T zOq?@&p*qj4tGa7yVzLtJQ<7;&<2*s5Q=fC=zWmx>ZP?gx?{(k2X@0bt5xwt2-ufQ@ zfG^?(^Rf;W_bOZZ`NM8y>CTmxR`VaX8~anP*|OMm7ys-Z_kGI#$O_nM_Rc3;-{d`z zTu>0z=`B!Vm3c(N+zhaA_QXlCQ>F`ICY%*j18=h%etp%gxs%skkp1B1uS8_mf3u6e zt3~l#SLVL5U1ug4Osq7DiD{6eURE;N<5> zxL>Vxz{Bm!t@hNfjXy;f{X#oMKg3EN^aO=bzt}SAXPihq$&-jp@Q z=p1v$hpgZ|c0PNBX?zIwYVcS5=bt~Mjg+vvSqvnM&*0DTXZZ|B7`xb-7EG5e*sKZz zQ4jTvRwmF|ADfiu8W-zRUCbtJs@4!3)MD5I_SW!8o@w-TH~i_=^1n45@hbn}b9o2; z%>57RS%2)0e>}Dx<_-oSI$Icoc-sH0ly)FiS_Lx3r}mI%DZQ`(F5l?=xmty)^VG## z7t{|IjMlxW)b+yOED-Wo9y%;79*kDKu!IusXC)F2O9%!)?j*cM57?H_0r#%=03G2s z0uk-^sEH=LH3EKs{BA`HRZ_oT#{}g(*nh!dY2wM++q^J$ty+W8r6x%J8iQ(jnCV70 zA97__Y-T-S*3+`*RfgMGDkpy%7XO@Mu+-*ww70xI%h&;4Ys0$<4v9p2Js0$0r;C-z z{!`^XVl9x)g;92(=L*GJ3j`j0lagRDI09ax9CE-(fo1!xq2mrXIqVX?MLDFT+4yNf zBz$|Q-_f3YJqh2T$c>X2fS6bb_p>J1-sslL#KcPYZCb!$1n3OYQyxK2z{bB<|FNUr zRvW%w?-LudDx;7XXTx_Z2z6NW$DD7$y=z^g!@uE1D2Cb_tr2(*cw$WWPa@!To>CKz zeudLWE3HCHzz6kk}Xs{9Sq8YfMN z8;gDB^px0%*k@vIY0urDnzp^|FWXZa(8d{{Cld{wMW5chTIh&A;$l!5_DUlzK z8}g;kl1$Hlan!{Ey)5p*1?5XpmM35HtpekBXHB-dh*doP$qx!*&*k}A)cFOgdG*CCPIX; z_v7sqDJj<5`-Q(XJVd>V4>9ivtU}?T0!5WkD|jAjMtMqShm4!EkfhZ^N79gS!bVmJ{#Qc`U6V6cnk%;=`BX;D4VLGvd2qc=nSWAGa- z*-NG*t8?elN+|!t(V%GAMuf|I_#}V$h=oT@yLb2Qd)2g8`NIiqY{dr!7Q&OA_dfdQ zz3MT;_W{RBC{}JU89Gf8es6@!lP(;wC)8a+Tr5Wnau?MmT%IQw!s+CQrJ0UcRFw%o zN^dwFu`#gGaE6}HI_ZtDBQ_?-gjWl=?TC#DnD8S4lfw}k(_}vB7f&ol4DUy`L={sX z>Y%{&o#Pa@CU~EOYsmussDQ(9i-M4e_K5;+J8poJu_KZB#z)4TDURypuyxHo;&$R&%4EQ$;S~!1wrdWcq%r#>LD*Fu@H3i z5R!fH@(2!5zTc6-7NHcHW}BzKQ2qCQ++&z)N@^ z!9zYIbW(*0mF>6kWYNAD@TTZ8$jIx8kXL6=qDkI@L z_#XwPH82d=w3w9Y`fY=xrB#AXGR3mBKh!0#jHIVr(!*Z{_N@;3_qyH}?aM{4wDXpD z_Pc#@=M8wK*vU$Gz+EqQvPM14uHC+dBpdY9hCUK}*46jL?}9$G(+Mx0{Bl{I&I_t3qhk>+C36I;@Nfu>j&IYak3xE2HPhCz_OCd%LH5O?V+; z3UBvxsa>yYQYUP!L8B7$jW*GT!^*hJgf)3xg^Yx?1pAw26MkI4g?5BaGn=8)%sy== zT~c3PXU7B{+bRK0R!PDmwv%EeOb%PRWzS40NhmZe4-{s5>0hGMVkLBrnSb+K$A@EF z$9W^dIN7X&Rv!8()fJN_UKQ*2M?{joC5D1W0xDFEpini}keE2LBM=~3d-xC(P02HU zLfxBCk6x#Dgj)-N6j=04p$Z}Hvd@6szC?);@C6t~flxHrerqV-(Vjvp6249GI^f}c zw}-MFaN1WC*SC4#LRZdh0~V_UNS~TUfF#3iZ2>Lq({*DJfPbk1<)Dmn1Upne((RHJQD^pZv$` z{1>k>RQ*S{m9?tYU=3(X)!XmGe_plJ+W14T7p`tRvUTh5u=o69EQ7iL|YK_ z57%kRG2<9eRT)+ED!LpiNZF$nqxe8Ne4TTnMc$kV*JnCj^+_z;kG&0V0xOI~=3z0+u#D2zUbWhT2OY14HNl@e%@B{|<@ zV_9lbbMY;Uc(Imalp4o|z?uOyuX6W!g}n08tVO(NxI;w@i04_%0F!hvXyLuM2SL2s z3Z{Y4+5)d~0(Mb0cMl34LANlyC0tfIA!9+Wfp-$Vhd~Fd_FlgQ_Y7(0OE|RK*}g`{ zdvGV32u{v{z>|VNt;DyTODl-nvGYCTjvGvTtxmXpK<@#4D%H8@l^Bjj(ZNM9{BlR- z@&@CEXkhMKJ{VG%Z@AqWB6&9dNHbabJ8(Uy2mS2oc!b_N3iYU#6UhMXJ z%q2(SAsZswmn&7H;@K3DMIbK8_I@?pRNVRq{594V6$J>6exbNUmJm7N`T?L844{tvA999=KrntbH8qtdoR?*8C5>_%wQdptnaC=#g z(H&zX@N_w&@>P3Y1K{sGF|Uz)#}a13cd$&yJkh+$_S=A{D-vO+sTRhrzR1k<=J3z?0{$@q;#cx7SQL8=Ai&$qW0qBXg7o*ZhmE`MHV%uQSOX|r?7|+U3y*I~Oihqr#eEX6VHfj8shScBZ?&+&OJEoR3co#GT z)6c|NBBVbm!J7kDeiGF=ElTpKNpojso{1PF7SsO2Ke}&q!P(IRzW64R9+97#n)jp4eTeZ&;PS#Df7saYJ9?}{Kw~B~md(sXGw~B}*Jl}?IRC86IjVFv> z6cN)L8oii9sSO!t)(41W3X&@*O429kWP|2S()F{IN8VcV%JlQcEV-N=U02H3+|=_^ z#%HHioKYxG*59{p^^fm6zk2f01#{+}qYX_+p%D?~!YP0=4XKReOqlalnX}|ftIZj! z%vr!=Y!2IH)B-*RS!l^&@2|WGD)~kd53MVb^Hy2(NIX{AwS*%pZ{m4L4+#4t@i*D< zmw`Xo!cQAXv;A5(HVC7@zoGX@ILeGMgI0UHyjkLR*>J1un&2grU6=Unvg;yYo{(KP z;a1tTz(d(}6K<7VOZ+|?Zk1hE5)hyP5k#L8`L?zHx zkjeIraR5)54c}%~U!Z?71~%L(Ll@Rc^n3Nleg*$@8Xug^9`GSZF&X@RShU>C;r-!B zuXvJRKS2zpiZ+=jt%@r23OI|SH?YYBt$1S~)F0g?pQyFs$pdl|7WzA$)S6Fn#gm6Z z#p0b~c#>>l>l07vc$;`Kh$on7>K`j1_45etu=VaI!oS?q}_g1C>fXJPP{PMD&=!2xviFb z#AJe5TxW5@XcEP>yh}2~8n$rsFl_ve;d+}X=D}`bPz<4N#rQ7?dnvZa31L5A686Aa z3Oj?qYmMwsBT8lqdED`4EpT>{uMQpI-MpA3461c!5P$FvQ|ieOB0?o5ryzulK1_j`lcNbLG@`)r0)K?v`T* zXQ#8nDL3aA6eT0yc_^5#9YVhp%cET&DL8Ff(8ZUHTaDo^7DOHUXg0?hv)aw7hoMHN z6DZiV{Lp~M1$CN(F6Fk!qfb^RO`5DtLNt&{jx;M8I8#kYNmk~ONq|L|XY2BoXFq*xIb%op{XEG3!^{85RG+)x(XBUDEM)A8Z|&^l$M_qJz0LM~HpI5R z&fI|#|HVr$op!~n>E|XV-}e1q@BS?x_|D@DIMxIfr>&ShYuWts(ym|=j=jdFH9y7Q z{tcvG5G$m%Mo3eMa!g2*gp=bd;aimq2}kxHgR|kg8P+8mPq=-#-98Y}evg{rXiw5F z@%veyiN8LgeGMfVZTwiUWP8uco^(dR7(u6SLj@ksfTu#OA~>|gCA5p+q%FV$ZL#~2 zlY|A6o<~kvQl^{_&qT%TL7lf5x=m8$?4>N!=FMBErkHzPHDC;~*V)#gmGL+D zvCf^}x`MHV6*q2cSmjcE?615W6KL?-ql_(o?6YTD)>U4v&E3BGrrQ>}1HWOHKGn?s z`r0x6&nwc-o4;(<>=kK?18aDQzxeofSp08y|N8s4B_~gqyZo|CmtH()4K17q3wJ}n zE+v~NP8_&glbCrlWlD0&cu}4(Hv>gxQ)8LAGmowM4J-Muo^4@ekMOKVKC1c^f9~Bo z_{TTbUHe4#w;oHp@`hWlZezvEep8xTn&n>5!}8kKKYuHayYTrF5B{A0<6uTAtDZdN z)(!J+&YgWeOJoI?FTS>R-N_@M2O9}bHuMax#7fgo$kUG-4Mwk(2-34LC3z4XG{Y1= zDyCvL%N0{AUKa*c!pjvi83P0CY`;g%akMAVk?s9VCQ*>&h-hD7aiO2ryrIY4U$pmOJ?zs#4UgPXHq-MZV2C{H$0G1Fn9%P9` zxrbTJ;?7pn{?2#u6Z{Ll^KWWe!9#0{>kE*W==+#I_w%2#RUf+(hka`wD!>C;n?nOp z`QB8-f`x6RV^IhmWKJ6uw>Occm-0_f9=zu~BXQJjQvaL({s+g)jueg79i&H)FAUWW zg!F8(%7|!K4tR+bClzoqNV5G_B_Nkc0VhKu;agz9(ro-?Hhde)a4Heg~W) z2@<}8&33@MZ1_&fFWdMj!XVpkgu$Z-LzU4@(_+JKQz31ceFWS?*IOu;#CNbo^kD(}<9M{brqyYr}$bM^(q_FXbeXrzm z0O2ayZ3=M}<9bTSk3&wP@*Qx8Tt(#zJhFWx9+DFYx8=lWB6YCgf*!=XkA()jbx=DE z$4P`eL6~?H<@45kQS5FWO*34Tyo;r4Rio(z)H)l^RRH*5d>2u|1b>hyY4#yJu*)bi zyhaf;xX9g1MKfT7XfC$@uM0L<+y87jbuGkD!o~I<=+m(w>;ZZtWYz&Sbs#&L%G6q; zSuJDnK9FQP zBz%+FCM0eoyhO`&z)6E;`z>0o&|uk~)&&XoyQBuE8}&2<8(!n>NB>L|*ZaJ(h)xU& zW20OXL-0P|#|ki-MzzwY65}zd!OkYHVzkl?89_xYHkw?BPGejJm;q6_$$l@! zMehC}G{j`PeT^qhOonJL%!0%d@TLj#BJq$}knnA6mk2Vz)j%MLSYg&`j+Y7q&UA*iFXKa1ZH%n?U!!9fU}~*(L|fv4|B0az`8?oR z2=d2Cx0xj+DjLp)WWr&`Na`?#R93>4!VvKscG23k!vk86@D6D}9lWova_MckG)C$} zvcl@y!&y32Cfaaa^v#rNqeZZc>LsVlbkY7m@NKe-k`l4Ht|xx`&p&DT#RrczYHh)M zeOEAb+w%|A)~%dhI%m!uM#~M=k5%8m;@ugnjSrrzNK~PuARyH?t6jN zkG-|$&i`6klzaV#Xzq&sKAZhNKVbUdbw)5|=G{-{Uvg0GAa9ODL2Kh`}L#y zezJ2xRP=>&(_9O(mn^IO*SD`b@Zz@N{+`ua(^*l*`lW#=-`E&;^reeZvva@=L7+DP zE)7WuprDuyClQwL9m;(UIQen{?&(D+BazzUB%DjE^#n(bmD)q1YV?Fr-}{^O9x*7Y z#m4t8yb`jig03i9DP%wIDSa=OKT2I%} zI!tLOk0|oi*>8Ok@`etZ6a~DLy^z>GRq7=+F#?}Ok>|5xJr?~AzV6i@2E8tm-&H~& z7Aq_M4?=OeW#X#}%XdaDBwtl}-2PD(8!>MpBqzA!Jy$;#>bCho7Dd1jbb!IK=0cCN z;Sur@b=-n`$9dwFcpHx;uR2cd+VdafvRdl_Pi}}jTq)hHHhjN+LDz{r@nRxJ-*~)5;w6&Z)1c{LkL>(2X}f zbd&r!@4D;e;qO4{GHsUrS-4iP=>OfAMM#>^q0x13HO9!H;8rBWH@0a|| z+zf0z4!yw6KU#LOM@v%|ZO_TMgAy|G(ubQ?@5~ZR12f1c zP_Z&VT(+}(lbHk$*zgj1ZnY<-i}tEg1Tm=xGbhshZFFFV@^Pd{U~Z-k8Ka&lKlvzv7DRd3oFB&)*i;io9Gj3UZ1LUVrxr7IRVw zuGqVbwcPlmf9I1o-0mm&@U&c-_}*2z#j+^qqUF+rzo&GC;j~-|IF`%QFtuUm zRtxHBrsdMaaf}s;1y0gxwZ?Lp>Yx+_RG^5KOY^NyLWN?vB#OFdxisOQD*Y0h7y*_` zNs;HXY%<6r`HD=DgAew&P*LpQH!U*~yZ2mAwpeB)9xO8wj%B7>;vs&MX*b~!e5Pf_ z#52xQDwdf@da%so+kB&C#>5ivG>BzJwr;iI$Y+UVM#3#BvCPB^EU=gD4q9&0x*TK0 zGGo$mzdE4wS-cVURm#?_EIZtu?6KMYKGq@ZaS`mUW30GU5AL*u$7{ET-R*_ z*V3gt=oNOQ$5-5a{XyQ0lDE(K!PaGaR|GZ1|KyEQmu`3x`cd2gu!+g9apK~iqX5X{oS$+ccdrHpFF$l8YhLkPM98I*A?62h(UVHFaB7P zaE``HHTSsmnS3CEP}XAcZYTJN8I?0f(k}R?`_)!4({wEbX4Hn)=s6P3aauAAM|JZN z@Eu_v6wepHq$Wy(Dz9up@+~Q{gc0kK>eBotdE?73vyzkVvul6GxH4vUQEu6S1!cKc zuDL>26|IfGnD7SMI5@~QzVRgg)6v`auY7vnzNc5tza{@o{&q81TYy^<5Jbnqm`o8m z)Rxn+OPRG9y;FA}*vkjB#N%h~8&~RdL}!1|e+CkeFY+V*lP2n}q*%B~nRFAArUi!hBd?j$^i!29G37VQY2sbH<k`m(*EBuGxBvfLll<*R&fwJMWoDf{sw+6#d5@p%i&ss)p zT`pTwpEho~I18xIcSIk;X8<;HGR!_}_)zg$B zB+2@q7UOW+;CvB`aO9#W5J6&jW@kt5-1pa~ZdQ}C*Uh@<%EXKBAAaDB&X3WNa?2l{ zVO6|xX8JfcS)X&%v5zE@oEP)bVX1<4h~M(@$ifz$eHK`)#&J?5pqZA~1f^DAaUZeYWl8*y{w<-FLJgxx>4hf1TT5o7 zuF~VQH6-j6WBtO+S?#fYnVypoaKiOHYK+8hv_`-Wc+#wXW8x5RHT%`x(kc8Xgqh&V z7-fnw8`+&$Chh`YSoqJ5?|PTo6wJ~KC`2Ot|fa}Gd~mzdU;Xg7G!jdBj$t# zK_o6glT6dP1@SJWh?kBHfOxu~b0I~R)=}BuJX*rBjYff!xYGyQq_Nsm4L+5Oa-N+{ ziOSM)7fxd=ankvjE33EDg?Y_?Vpp&+-`@Vqa$IYDUuX5uFiy(_OHhtfaU%E3BX63N z6gBDM()RE2{PsaMm)(1Kg1mFWABJ&O*Y-pc_rm-57ipC3e=0 zs?9WHyC4MaoLO)rTw03xj`Sz>ETcCrq3o`OP5fWa{D_UOUvSSY2|hK|72v+7KCXP5 zKlpJSANcgAm#T4-ufKEa`l`hno+vuZjv7aUsaNbS+_W(%dDHsB-B(m*Upf9cb~B3+ zo68A{7OPcU;mt<>^dE#?_#OYa_L80NJeQm~In%#7a7PMDWPQBxn+Nl^JY7`$)TaD{ zLLE9C3r32$V0hfmVDndCx^G2r6{bfx3SSai(8@mN|Mf}-Tk~%jENIq>*`n=BuJ%U; z3x1XG65BBJZ&vba=l;LsYRCQ`N<-^>k+%xH&xYJNU6n7P;B*I4&XzLcuOp+EXQox} z8X2!NebFKajwY6ZNMH0z=(ilwp|fM8<&>V0pQf);Bz&6^7$s~yMUq-_IRMi~Q1Wyd zZO^7iZ1834t$miigHf5SvGME*Ln-JWaqLjOW@3H)8<%W->-Ly%`y3nPVBpt!EpQN7^}Uv*pn!Z!wL>WXWQ^1caLlj zBk#m>K$pR}0^=tU@H$U{*&h9h&AFN>@yZMxc3 z+>~Qh0>+vWJ1u;dp5yh{ccSuO<_7o;J;i0*^Pn^bv=DOHc!J`pTOs-Dtk6JmqaLoHk0 zNY9^~KY2x_p7!#|N?gUYD{9Q*%oFXRiBM)YRYb`oBAzZ=>n%|Ea%h!3Cw&%(Y_s{04SSMCjD@ z8voxqEx79YR_OHmD=`BlR4XEO3SyB)+5d>00bG%^xXQ3zm;v?J3HsRi!R%` zJlOM92jgz^|EzoSMHF<=RRmqWsQ;>i!(?I-JeUMbrbv5YiEiVEC0uV1YD#cyc85qE z#gqvXBwEAtJS!AX!bv0~+|QcB@TgrD+;f8Uv0NJuZKWmlatHQ+)f!7hTNsO26lCk| z10v-PvL3|(I8iDHqJKie#(AJE6iR?~Xft}$IA6x6&p!FdJhOghW&Nu`8DZ`^aU4Aun?KAK0TYtx8Q!Y=)o5Oj&J#4cUj3Dvk+UF zwIw$Jw8Z#k8_$^R-(8xsF;iE3KYM%QWEm>-&75iE2b*+-_kQQ8hj{R5YdWBIwSp@N z;7T%GX?ZGF&Xz9XQSmeH-XZB)c7Ugz4V@AE($mk1XbQgeM^_7>#6*gav=mBY%VgmM zl4Z0b!wMu0Ud_%N7mI)T@TMuL$L=wDc9NVjm;WRFR)d;4eQA36-I;1?GruP+x7|W& z^MXwY|Ha~8JalO8iiQv|>pAr~=QlL|b_xRV$qWFPCtoTC^wgD=n8lBu~rL!q;+I zH1k6fp;FPFl2fvMDesllP)1)w`};hF!h=QClM`-#m_phKtPTQ?up4R{YKFx%jU@ff z-BPWZ%Kvq1Q<9)OUg<5bg+<=#O z{4}-4y>LxoxK68vY;VB>zyn@b?U5qt;;0V_buiH}D2Xot`j197_;Xj>oTO82)ByGD{-F3IMoq|kOahPl`EfgpR2AiLFO<9@fy zL=syr7n>J3oPb+qE+N_^Y8HF-8dnwJ!KwkcV>$#}VRTiA5HKGVuExeuK?YrKE!I~I z`WfE!=HxjS#a(#rhUL0)qVu=?Yg84t0IOG`d?JKAvnzkp&k-yaG6e z-cA03*&1#?m~cvGv}%v&kUlOjqEjv3{GxzR40NHB*r7{5_yK*u@6VXy?QkEbD?X%H zs;_C;AVplwB2-b_nsO=;k!s@{J&W^OIvT+S{iv<3s-j_<>?q++DS3JXOt4rHM?6Qq@XVF6x8J zjJn3)F|47rya_jZ`B|A-r@Df@1`;MiidxL7gYnQpi7ElNc1?y$!^l^KC&hqgM2gnq zD&t2g;XtLv0o5*}Q_UF;8a+GOdbSoGn3Z_tMa!xg8>r?@?pho_Z!`SCI@bV8wl)Oh z;*RJEU8*lGlXlOiN|3ZkhFSqa(M**N5XY84bT7I3{EHd8c>4b1&+~zy=53!d<+IXzUf<5x zZ9NYaz1LaAXn?GR$K>W`PL5fyd+$+}TC(`QUAen|zNz&02Nu_V-sb6$+@>g>8*>B0 z^tMkbZB>v?_LD&m2oV#rfQ!1k62^DHos9?ZOXTgP z@(we(T+ADf^p-1GgAr$QXP=)Koj;{`J+31EnBk_hu>c=x$~VkATg5cFXx z=<*36qT|gxnT19R1Ox4-pb87`2$Z0;3Mi!sp?>|NiHA|KRX(EwHh?d}A;`|IJu;bub_el;}}> zuf5|({>n6R30SgZ3@)B<#RLN-yLOe37M1J*V>Dt6 zJTTFzCgK;dN#|j8J#){V4mIXW`-Tgdjk^xB&qRT=TT$J@%{?ZBtVG=3;z|1ou?li zQ4C5fcIFup=V9klB2aq6x30P9yqwv4H}C6xdaZ~(okB_ak{PN_g&DM!P9L2v*}oX5 zkCv>B2#$ynKI`~9tx%RUI|6(Z+4(+y_l=ypmZne3n|1wlqmz)B_-aVm=Z#_QQDbtl zPfr3*@V+M$c!4BAYz{Ku+ae9^Ntw40UyQ?f7nCb`uxgqIz)#@`(RA62luNI=_=0_p ze}7?TuzAlrZ@%TP{_CBrCZzm$)6wSjqf;3OTsCjsWtU|#cJ8bjuQ}d*{v`>A*2N+D-}lxG^z7 zs0Zb;Y6XukipUg;8-`BJOISs9Ct1nMFZ0IFmu||++O+hVtyx)H?GnQ``1b|}`S;#n z4QpPgxc&JxYo5Qo;)OMeOXQ_%5%RDvNpu#`kz$&hDRjE0l(R{7y+7$_3^o(w$~zObyrO@c3dRT@&hFt75F|;fiL(&>3b9YswYEyG)nRfCdb5M z<+cQzD)7zr<2+fS0$<{>>G8I^rD>7yLc9GdXz#V=#nEpu;4T}UZMQEnFF~XPI%dp* zBXyo19xp}8OgaOeQY+2x*+uDm2}e3VRiyLFh!1rB!GwPjO10DZO_a_z;h!pPb~?Y7 z()j|8biU6@=hu?)7Eq+~Ayx3X!!+R@E8@i%VILfF?tnYA!jmCJAX_^}kRhy$gbOP} z9HLva^jsTzxL0tiB{`(Z@Wpm$NLt}g(cYVLl!)$TCG-EMu9(p zuNEB|1%5xat>N~z1V+{4YQ)x2+J9R)=D-tf|FAOTXkTyB^Iy1{rbSp;xhOdMu94)*u8kam*m%cPDcZsMN6g#q%P%?NN_&K~Mnjs3k5_ zEsTzHawVK5OM6YHB}6!+2r-(crB;&nH}Y^t?8w6%wQ6$A$ip4p7-w-SdGGwx;~hub z*^cua;A|sY8rsWQmA3Li!LuMiYg3DAjTXayMpD#XBfeN27|uC6iYOa;#M6eG2a+iv zwkRnBAHo)2_e+_slaqI5_TZ|uUfhA7xu$k{?8eKNUXBwcXX2Nx9PgME#as0_RlW3! zZ=VIJm}x<(P8nqbYlp`x;UL1>y zC~S^VlY_^cr5_#If{{hdECyM}?uaUD(Y=Q_Eh=eI)qH9(ij(ydM%G&E^$1X&LQDkT zBkHY@S{Q{clkF3jdg*0GMBz2+WvSdA!Z{B9{-p|iS&z0Oc0xI_;2y^;abwk36<0Zp zM=*XQ<4|+o+p8!h- zd41^>qO!dJSB1B*CRRLLKZ47Z*rHdWs76Y&3D7t+e&h=54y=I?vC z8A0iNfBq}m#}c1>4HiRKrN9lQMQ!eE~?V*&+D)@Tzo3R?Bp{=YwsShAdx^gE3CBl?l2 z9@4{+W;Oeit|?B4*eR%ozAYg=qQRxA4?%nbKw)0r4dKbmBnP?s=?g1du*>{jC|3m&VV(lI|BV{7SIJB{8fUk#x}~sCCAm|8Q?v(Os8cbotcG^6OpR z7rRR?9214JPws@gT*r)RblEK}Ov3t#G7tM>tCZoxhB{I_WcX;*xe{^iMqx8)u5`Vm zm)6XgM!+$H2z(|JGzO9NWZ`m&dDY9QP6_k<3z}~mhezM*eplGtTL5qOl3O9$@7H65 zBa9t3+LJw&aKF-N+Ua^nuM|LqBni6?xNyIzJq1t!hkf5JKLHdv#8u~e2%x&~T;%p$ zgQsYbEQCZk3~AAfpY2_+b7s?&YtnLdFJ)4gC!fz3_~Mt{8P+30a%MfU5krqePR2^n z7+I(EbXrYvbZ%-AItQm#(}+z?k{Kc*YBW)>$*@Era*UC}5Ye3whCwlr)E$JO&k}|( z5tcCcAPlKO7zAk$hGd$o(94+IFb)z1M7=SI{*8IdHZgW(Ahs4G(Dva=>Km(^W*)1u@B$@9Ol;5Z*}cl=*yX2*=9IXBNj4yi51%f^4= zsyNFrbciv;DhORJnmA4l0f{Pc3|c$;L9n5150tE$y`^qm!>SLPAA0+td3{Jl{U4am zT6_7)J1@>l<3meo?)~*zR;eYQc_cWsM&-# znNXbJRC-b7|5B#zy`}sfch66mtESR;t-dEQKAQja&##=^%MPXA zv~cYfR)5*5%p(839VhFE5S)BY03o#d!Ky0{6@wJIaB8v!l3b3kH}1=&Ds5SFiNe&u zAF`|8{R_)};lk+`F59wY*?s&dKUv(fz@JjNWy!YdZoT!oX|t!56y)x5H=HbWH*~X$ z9z5_x^YZOQH_xAO-RklucGmo-;rz+0Hfqws71!LfWzWiwi*1XL5T`Cqjl-`iipT_oE96WgiDq(MfTy;Zq(zffPebw$AOL4fY`urlUYR ze#Gc8MQ?ibnpYUROJ#N3&+FWG3BhAk5WF!ZQt)hH_RuY-E3vPP9aW^$wBw@*m)hNf zD||+mCm8HBqtLY;HyBLUU}9kccbmt-phND3DEBqt_#R$@QX<+1Nd7FizDME01Ph&u z>a*c~B}<$vNHgjq+Sj<($oB9@c82l59}&)R=u@NB9UX?_`*@S7JtD%;X(W*?I!i;j zxZ;`$LI#oSI<=@e1w0bgIg=8ANF&ca+4=-d9iU92ymSc)pqsQ}+RsE*RbzkxAqx7Y zYj+EVl$tA;gqPXyZAv$ut0S0x{$}Fs0SQY6%IzqRNC&a<#(+rxa%4! z5jq|-_2AZRPfm@&C;bwGP0MasxbT)`guQ#!h7GHX7>q&*4SJ|7fC6m7oU_x=-#L`w zg5f-sq4;D{A4}v-qfu?Mm6l>+Dig{Oc+Xe z7l~ts@;f16V#1;lZTPJWE0sw}x5&rH_8VEHa0+UXdFh~{QWMW@8YbALJWNY@m==+> z5Ll44$Pifz(W`JdBs|KKETaAr9&cgyj`5VpUWo(IVfZ=t#2;mcq_|o9!hS(V*{}_7 zweaAoXs_Ha&;^+e>hJe9%Jw+C2>r0z*Ljc-AUsI8I^p6If);)U{q638aKBOg79Q^! z&`DVl)D1@9u|8cW=oh&I$>o5jR`5Zni;;gF94qEbKw7TG%BX>?#A zWt8x3=50=mxLFp;Xv1stLG;4t#GkMd9tbyrOiAPCjMp2Cpb;>FYR-^vh(ap567*_A zk&Vl#F;2PT0wXn`x{P+Stu_>-RFOa{5+TiEFx$*KFMT#sB)mqPtY@hHQgF4Fb>TCJ zAVOphtdV0;Jp`vDzB8r;)c^sbwa3>3q6)Q^V6oP8Qqi)4N71Q3A`DcE=(JRg6JvBB z#~4Ah_$$Whb+Lfa1LVd4kfUTUpcV+^K-z*1nu5jX=%|)uqbG5f=(i$WNmC)aC4rRi zO)M}fyoAWH+mjD3+xs<_grl+sJh0(49`ML&FVt1Gzg6{%VI^bg%D^+Enih=*voIqX z-6LJvH>f7_5_ZVw<3Uv7!lNyP|CY>p4aHE{0FT#T&45@S5YGOO1i$>y_b{9cy@Z$2 zKHq8&0(Ak$mst`1{v8NF(i#>QRkN_T^w9zAh*NN{Gxp5nXk)`X38W>c!C$bRi&AIL z+PSm&&yVH?_;26miHRw*FT?MDZCnz-7bB+_ItrUhg9o!R#&ZAVb%76zF11@*fB77I z4D*U_@B3JteUi>#_|P#_RKx{k0ZHb(d15}37~CD!yS8;vMb52X8y92rtJy<7h_caj z$@e)oIxakqRmYE=d`W_yGu-1!JrUUd=564Hz=%qQ@#1rj2!D#QlnAVvqh7&TyKws6 z4vZ{Z7`=P%@safka)_RVo?YTA2NA?DN-j+l7irVwCAxA-|4f;7$ItIrUUPZDlyu*O z$z%BJEBH$zy4Lz1II_t%map(IZ}jxUlQUo8AuM*p26?yz5yM3|3C1G&hLC~1dfSO2 z)_*z{YyE?RCwfO?GbBi|nN;OxLgGqnvw#T|u_1zE%iKfqCR>w2NT|F*R6)sb*_Cr8!D4OK1PLF zLuHJ1HGRh8O!a0boX;!b$6h!i;Y7O&U$@ptWk6>-cDbTD!G)W!75XAnfvCWja99r$ zUdj?3@O&G-ksU!M%$j8-p?zu=Kg z71_@Re{?!gG&*v=`Q+Nk3og&rlX=B?7mg;BU`- z>AV6)^w#1jKyUV!iz6b7(;QrJ)v|*8owJb_nSW#E=lt`aFU+RArFoN)5jjNhz!Q0+ zGfg-+f@yZVOfXX{LlQ1xO+*yUi?^jz|Km=|t2kRVY&sm3U9%mhmq?Rp>(EGbcbO)v?+(1BOV zJSA1DY53%~qQ>JE0Y3`;>iFlzfEvFekeM0SVGPjO5546n{+Esp-rLL?mT%9@y7gKJ zbQRDi1HBspE%rTnauC}A`2ixpRV5pr8VCpi;6jpDi2qN`R-4r!wfSU|8sJCq6$v0mJh-DI8V+u-ewHrF6c%mSNGOM_)EAc(Pj`!nC>HEC+eKr2; zzi4Es6_2ktZh-$r3;%%M{^pzPUUt4(XtdNFExqQ|HnmW6!;}(qlZQYmQZNV@&BcLm z#8T-}XCdJpoC!&a6`@lRkfU=?lVVF)@xhntnUC#!_g%J=`RZRj$clrFMfWcEAFW#V zEF(4Z_h6Y??d}5^O;tGMY9W9$M6|$6sQg7XZD0|9WE%hwMAMCB>Z7iMFvFwUf z{>$;zxU*-7o)gE~68!lO+?G~w)kDRlh(O_kGWa%#7?v6k!+MAgrr`-Zu;>!=NxXPc zXMG+QPpZr(S>lQHdE72MNvCgqfJy!0$#+5v=$juQdQvK%P(cpZ_o(@Vro!xp$$lP3 z#*8TFFbXkw)>NcK9ngyC&G3{Mg%MMN7A~K_eb7u6_ZWpVHCP-z6z~J?R)P8VMq$LL z@us^}w6I5y7S7RA3p4=THW$#s$pvbWC|cOuKno`~h`vtI!sZHEIJrVNT%v`^9b5}j zNNR);yf5GfaO*Mc{Yzks^SlbF*UF4>^MvOkK8k_k1X8U;@C^AADubpvmt?i%<%5W1 zl|Y?#9=&V*L7`4s32%bV;lq#*y!$SHfFHxjwvSofCLilCx@?VV_mf7c={(!FiMM_H zd;SzDAG-;Je;P!T${BunjKVDuNtLe~j>~~_-ATJx2n>is@OTT3I zk{4Lair${sj0*jC~r%M)LjrmA1Ox2gW#nM9|i=G)kc$8=#pujg}I_U&eX(Y zO5Thck3~;#4o(^^xGB{LiufLUb=`OPi68Rg{Lr|$o1U&jsH7^mV9J~eF1YZ*3ob}c z!i~s>_#gOu{^lzG&$j;kS5t5N&cE4q7UfzoG_a`lTl3Oq&YYcfZ|#*>Wvj57(5f>Q z4k6h>x}M)!=W*CWEb~*RR=JykwOveQcmL^6{0^Ms?q8DZP}W>8hSxm%DpqT#mb?#~2tsd|a)>X9vg5 zzmCUg4QWm6l9O7=iPm5#8zg|Mo)@+A4}TBb1^6&fBXDb2_rwZ{!^hyX@G+8@r^*;O zIe-(#_39zks2yU}YO8SwpU5iaO~XfcojlMwq`0!Rj$ogb7|b0$to9j+m}7hl>zpPU zlnZ0PV6VPi+q#hx4!?cmy}G1T|L>J2xpu-c-R+7`a6V)-ZbAR9^(Iq>vxxem#G;-)RN-=O1~RfxCW0c5el3y39GWYRcz z0_GLTgqrBWL1!;M*z9BXb~E+|Ucrwww7J|rdos97jn8KT(1}*wp!PBUFM0DP{1blr zf3x}zfAjl)8HwnzM@v14Im4*4F;UH;H%Kc^29HC`i-_0g1F6|ro9#fetJHY4Q%y8# z$wxZTsu#e2&JK2I{~vX4A6Hee{g0ou_dXBkQF)Y8k{twv6crT|6cq#%6%hp$4HFd= zkrWLTm6VK<6pe}u3l)`&ij<72)TJ&Jd8w$#sHmu@xJE@?u2CYKGko7O`|zOI{q*_1 zzP~?yFFnASJ$q)&nl)?Itf%n>8pQEhMMJo1#)jsL@E;-4RFW9CF3{Q8s*wVygd!wg zYw+a4UrODm^tQQSLE0>S#n-jU-Jd-muux)`WAN-2DMR@5i8}&?Y>5jW$d)-+mFD;{ zf=hIA8|Xu#hrj!dZH7Xv0k;Hi`tEyr1dHeF`n%5}I(zN2Yt}rw*4f&w_q)V)v-S@5 zX!S)(WuSq~S{4vh`sjm&GiM*qeDqwjRDLzvkSR#$#`DL;1eqg3ee{kLq++T?m``a>xL5NDIzsFXr^f+@3R zB-+0jyxjmh?UNMni+groxcXaNI^rimfkkaN=;C<$8&1Z2`KU`sBaxArjo>OE zGfpNdiS&8bXm(@Hc~`Uhwm=1zEaZYK&|!<}ZApWn8eSJeg;_8LWa`A%58nmcd4qgQ z=I$=;k#ax)9E)Xa$wYipwpta*r*_Rl*b|OD!QynFLYBaKc4q)=+n*&(F3`z2-e6&R zym|YC&afW-OAq==Yn?W_d-v~YU+F$%p%>i`q8H%trepZ;?cbzrP+2*)C-uPU&7})m zASA>B&aELf(ak$5_Y2*)(9h1Sq*(LJgEqa&+FsDP{kO4EsiK{Jj68po<5{FNlYPSO z|7hXO0fMm8pAS>d^Ol@2Fn#Eb-Zr&aC7~BHyhNUoqFK50G z)J$glS~@PusuBdpg9DPPDV*u*DlKW&NXb)q>FA?J0%sh~%sCt&Aw%-}lu1L8-1y)Z zb#R4fVvOzjTB+je!`gcI&^=c#X1wux+MN9f@6*ES|F95Nlg?^b_|GpAzfcLzNd43K z0Z8)}IL$ik0&G2nKJNts6BI1_p-j@G;tO9E;y4eMz`+w)L_o^j9wuB!aMzoybyX{K z=Hd=+Dzvu#zzT8y!IJOfw9aTLni;J{x-kDuaZ6IhvFImhOvC3CaJsX^63fA`w&&?afYgmYkr*VLs}}sRC2@4Q+U|1VcUFl= zd~s(6UkC6-{GKI$_Axz7&e)>%{>up>%+li@F@>C;=1o`{holig?RUQ4(f?e+`( zohC$5C|&A&iJki%gWCPmsn)}vZJqT%{MYQ&GqMkrFrNB36E{l*VRily!UXnO$-e2$Epk3@y<9Lywu-&Qre0~GV^ALUrybQD6|zrZ z?sK2Cl4cY6+!qmC|C{#5Lx-VGSkXD^UCJOkI{ho{sQ-aQu(w$=RR!-kc<~rpcJ4J# zGxIZfn}Ais80sGfR>2)Y%!)>K;DRH7;v$AS)K`LmI!qLPw>r35P4tu#DGGWliRfoA z%r^EpTmBW{z>`k#&!+7?zk(?2Q2k?bUawvh5!?v7gOT#w*tBGEqv1HG} zycefWTXjBz+%^=aO&xTbGu7PU#o^(SzXeMG@?cEgwB=a1Hj4oF+~cACp18x+SDKR% z8NGG-+^j{AGYc)n+7@g5`F}q&`5Tt`Z!6pXfs_hyK5*64&=un;mC_N=kCp3YKRE69 zxkazApN4nv(5%T1XG2mKz?oGhL{>4te zeuUiKjM|qvYVdrpwTc5YfPSRiUm|1izoOG;1Z`guw|&Zt?fMLBOQYm`HR}S+{pt-0 z`GGr zT{{Cw?}N9@2}+8YGO&QM3-j}bJ@~Gf-lZhm3AE~Bn)E!74_}vd)j$~(t6wPQ=X6Zd;87nsehD$ z)N;6AHyOIe1sxfc@NxH!?@ylhjPp;P_l#FO?;dY&`#t0SllObZ|0nPF;DJBy8E;2Z&T}#4!bddmLNcSnTcXI&5-hcH8Uz0eyFQZ-3%B_AtJd`ED^p+}gwW8<(`Pe!cYFZhk^V8kFfugo@ZC`MvYda^f@~tpqze$i9^OBx^cP0b%i; zOYX)E6Yc5xvU>6@#ER!8dn4?G;6gF(+(id~*h`*Gb&{DWRtr3f%yJ{LRaQz+2jE(k zCcVKBk0`ZCV5%d!qh`Sugk?j~ICXD8kRf3Uh9^69%RoY^^a2;7Qi9Uq_NC|61f5=% ze3vt+IEOQ((a8CF4i# zc=4V|8{W=Yk-K_)K;W7OBM-fixaq^ZK#zG|6Q+VD1(br*LE;pgE(j(a&=jEO@jXL0 zx{irlSAYGCz5C{!ve5?p z;tTBa56?X7j{$^Nc8>t7O{ee?NSaVUZr_7oE06#)e#4?PAe$;E;N{O~(OX1I zPkz2v_8z}FcSX+I8z$ZJ;*OEymy8YwbgBxdo;ty6o=0Hbhno`LI28Hdnm_=^g(;%| za3V+T2pH~cka3aN1Ay^cVswhk|FprVb8+``1e9jQm1pA&xR=%42eamZ@2 zzW=G^C&;%alY@Jo`Uf>}ed_c5Os$`%Y3#}H(6|1>nua@C0H95YznwyzO5i1)E{z|_2#}4nevVH6RgFE%Cpvs4B@gZV;LOek(U2j zi&mr^2ku34g44v9tW?z?s1K< zd&pOnsJcgYv!}gdyxG&Jnb1{cgwvPxY^TQJn)vg z@zDM5b=VLx5yz5_V?*HXX73!QOF|wJm=*GnirE92cWmVx4lWPz^6s9*Qz(mX2YYQb z_~4A=B36$$+QACK&!$cel4_SC2XS_GwI$Hkd3QD|jr1ye>9a4{nb(M3qv$U_rTMR0 z|3iKWq1$kLPS_E0m!DG0GAPTy;&61{fBrmeBQum>ZnW#w3ihCdNj|yx0Y~4o)_w4Z z?_sQ<%C1(q5fTngurcm3-}}c;5b>yicAQ)2;aUG-zan_Z;<;ct%Q2an zkmIJywX0Rh6f=>+SBox5)23Q}0_n`LCSc(Ozc>Uf{**yZ+4&HwI4dwZ0pTn6h+UGQ-wsW+rP}F}0<*nQc)HdFY zUBEKhsTcLRye{e^T`c%2$}@mXgB=1rZS6e8ETWwf)Nc`#q_%&+)=m!UoBDMH}L|j$pA#3ZN{5GJXIbi6NyB4L--5L5J zO^Tq>u~Yo|!DGVh-i|w@B+4kaxIO#;tT&O_vqIdc;MT$KztUNp9wA9Av%drUuq_3! z@4=JJ+N2wZo?V6bD~mHZ#9QuBZuaD<;vhf{CZQwY0x{nzaC1HPdVz+6hhY2LYqUkt zuVv3q_TRFtv58${oz1!j!5$p$Vg>t&6uV!g} zL#K?MAIciz-Z*yV+5_H}D%KLTWnsz=FV8U-GZX%KF0|a|-ceq&9QV%u_AT-zZ>+rL zE4l`4Fr@@lOpq>RAK^b9=RHvbM-3$)0WrojyG9*09{jE9~EJ+-BL7E`lpO zCr%&2vRz!8ELc&w$OJJDdlHUgG2Nk~xWZmN?nqv)&Nkgxw;I{SA-9y~|NJqTPRyW# z?6+6n{N`@gx&hwNp)2BUrD1n2nZ0(QEI~`X7zqdy}Sa zIu+%%xL{RWVM<30$WinzmdAa3QDUo76H47!Zc8ZDm8n7tG7|OS9Z=g*k5Odfbi*Y; zj6&RJV(eLl?#7jkLMG|H6h%sOHTWc?l{KW7OjX}HD*NW##i2&{ly8^=0AUe z|F`UyjB_+!=X{iXR(t=}1tt4TDLZGH{qNZub&6fvQa)~n--z%*Aup3-mB~VDU&Ex7 zgN)m>wP285Lhb3#t@RL;he|NktDJIpx0cc0rT`+$U^$(k*lV|pPh7d?qer;n=hYvs zvFA#0x#JxhR>#B*gQ9EMqUFgx$^=WLRBx`O+5d1$+j8rzPkfZ-R?FUc@ggm5H?JyK z292zFaqi;9xzb)lVibV9p@+vEzMNp-%__*HZ|JQD{DXC|9llg8IS=+5GtqCPThO*; ztdo5VI!U5@i~Y#y9wf6=yZ&@54FrY{bTw()(B2TC+Ony_;Ub6oodyes9qyf?al&OS zuA^>^N}8yc{XD7JC&FjZOYHJRq{}=760~^Fn0?#57veHWXW0_l!Y)5~j(z&+HoAqz zf*9`MT?k&5%{j3yt==A`qGQ2{)zT!9<5(@_`XpY;^-11B_EF0@9~R{xs9?eOZY)1R zZi27gSbk803}QXKa{jEglZO-_oZsG9|6y=3F8iqEz4Z?x-(yexo-yg|9eu%w_q3~}(|vG&|83$f(jj@&VF;v{Xi^Onu)m*3u|7~lb+ z|DG0AtiQrGAg|6_Y!ORc`p`_u924rXxc$mImlMXuP70aXH|;Q%@{Oh`nY35kqn@d~5*KiY2BAhijM@*4S`&_-1n5 zjFzt^c^V4g4do6#jm0Nd!x{Bq1ogg7X)q;J!=!c38}L13IqX+vruLxqPb?r>@WgtS zwLX6#)H3<&O|I`#?AKq?g7;ip-eYIKY-G>BH`rR=FX6XBIidf~5BF!kx1(Rb9q(oD z|8Qr2tg;+ys-X$1VyI@=b?d?pDhPL>!50YA2;cb}$?(9hMW3{-br~^XC{>M|6JXx( zD?DZ|;quiX>_4(6;@Y63vAP{Ao3s9-!g(*eR5Ca4@Y&bboMEqbJn}4UB5$eTYN|$} z;Yb-b2;G|aowf*dmG};YqpiprZ4=BUIu3UqEF5IGoF<;|Dtz~n;8M{TwsOLfC39C$ z_8xY%_9FZ2q%1w4y^k8VJ|M{_*=H9&V88F7>=kn-LoJY?(;Q^KUfWdi{;4DIoBib{ zcKE$#D31DnN{^pD%{F`rL>E8v9wq-IIa-U4oLc_eJ)5r405~8BNQHEDR9&ZXK3}J{ za=uPQxm~tW%IW+MQ`B;JM-CDj8ZU=tQGvH3-`Kjx_b1PL#`!1Dd&VoC zcf;S?e$Tl7j#D&Ijak0$Gwfcv?#Oo<2-@ai6tHO_@9?ZmRiSgAdE~ z8Rryt>HPlFXIa(9m)Ua_RWOJ;CrS~Lo8z_Qd58K$0 z1+xlunoF0qlnym)dO9xW)fJQ#958tXnTCw_@Sg(oqeCP+>J!hQv{=imC}D}61tQD3<8U%i-3{?4_lfktMdyvoETvMIN%A&Pn3Re= zrV&me)y~#>XI3ry;O>(5>DA9Czr-%Sdy!R_Q|;tAF(G$M_oT2ezo5uz<5k7#)@MBJwXp^FG|aUmqfnydfL;H`{M_zh5AssRXN zle7_C1-2IJ(V|iHju8%}9FwqzCcu+09n;?m@X_GbXFVcSp~M>@TufCzP;C3P$g}v^XBpq0=+Z=OnNHH3FYL5N*t)PLLif-> zNd@eyclP{VH=p8eU%4an&b_nHf#|do9rB?tXC51ep}ek6kJ66cf^-F;8F|1B){=ue zOfHCVgmV^-jTi`}*J2S4PtV1mBl-M^L0=Su7V{>`a}4o)%4Ka5?g167#bClHx-T}uvcNyS zG6)|fjG`&qTE~j5O_ZH#ZP(^V8YxJw;zQ{DsB~lZl4$>JwO_pn^rEsZ-abD7a~C`` z3iG=7r_@<2RlG@Dl2pf!Kax*(lj`j8lQU-TUD2SdNK0D`G(PHX1}{Iof=z%2bY71$-8jf-y1R zFu6UQ_I`%n+NmY2j)t8%L&NIQuGL}|+XL1dh}~4Ok|pYV)9Tn)XU?#%>e8ry7pFeI z(u$Y>T)kN=OrGu-J=PvKdOG3ItrJOzY*#&S-9D82vM1bl(#Pjk6yiK&qKE&SamIcFCyYy}iBlWGEw7Xx!tb$bmTx?1qsP5zdX#D#mG3U=5x#J^yfILO6{0T*dy= zS*Rc!KuPEj?3YFKtHCRO;#}LW#>`Cl?FoHbfOBmLl@o})PfO|&0#6f~5_c#uIQ0k+ zRAZq#RZ>08rk%*3$lmgcAewy_+k_|IDg!vT+n@7W*P2kSC$U5v9(1>Ur6pzyyztu;!2gxng$NdxdFg09-c z)uR6mekImoPi=n=zrGH>cqU7Z4Sqw`HIofL|8LsYHSzWlW})JzPy4wD+=~T%95f@Y z+XmH5@TQZI*S#}J&DaOZ$9|`I&pyLmYQM-neukRd=ZB>vn1-w*0}D{nyys0h`1Ml@-et$WGyRJl zd;1Z(?NU&}B+&lT(WVteYg$<=tbd9o8s=?eH5J>lt5Cyo3gjQIe6TsNa$s(B&r`fD zrSR>o*A`r1#V`QHUAaO_E?i*GUWrff^-YP7-!yLACb@<$1<8-|k84e}lzh2~R=x8M zJJxiW9j>L)B~PcNorqajxNTd}V$g3i7eg@rBBEj?_i{uk)d^V9*vCZTRQw6)MA6hH zt&*gqjg-yOzhURk5!@%jzm#e_qN#{1EM7@W@SU)fmF@m2x8TZt2)D@lgXVKsy3P&F zJK+??*)A7Ac|dGm0TctzgBeRh4}Gm{{=T$J6MWBHNm_m)NebeJ8p)aR9%3K;zU7CH zV=2IA={>Q@JN@LA-cu3FwrfBa&YW~?UBFF24-Hq3jEg}yAU#4Yd}Q;!nYyPcyM5M{ za7kWqc58>PDgZ#uQ*e-dbY;o8=boHS0e*LGj!oR|Z$-m4!2qlZht)(eZ#dteBZuKy z;liQ%V?$juNh|i!o=F^xb8@WW&5T3WrmRXCIWlEc#L@}&)IRb9_R;xPR$P98mbO)r z?>llXYa?S)>Xt3>MJdTeclpKorW|Y9`939GZl=|5XI;?{T#%{}C+f`Qj5RLs#)S_v zxBEb%5RpN=M9>blwI26te+K~|_Z(IXZV34C{^b52@=tf&RL5X*sJeW8a=Gr)lpry+U5kv$%nifH-da(6B1%s-R@8C2SH~d6it;p|0LQ+$*?H;d=qodC*;V)=70^J(`#qXZqsMa z4!}Z5z>xLal{E&sbKxG{yGSs09%n9$sqiH0^*Prm0dVb2J_vSKt)28cJNl1Gy6cxS zbZd1=N0FRBA$Ny|riMz>(iR4;2*ZTj`UZPlX?o%)tEEYn(XZ0#_FpLRV#<50;X)?) z&R7!|m>9~+=cLUrPaReG(|y>3lVB0EfastZ*#7^(Cpi@{h5w^VGLAgD2A~5*3b?FI zaN=x>XB!euPvmaTlY-@r{M(g%ptVmn0B^aKoB}##fO&i{iSkEBTh@o+qa1zcoH0B`7E*V(MK} zbP{gaW*2x+YIEOwnLGBxJhOZEGckdiqTgj76+wE!o}uF&-RjATpJFs2b_(QZa0C92 z@R4FE5m(XHLmL#&Q+t@L4RS$8xi+E$vAC9CRs~nzXt}ggA-~RQC4e;$L~Q;Ot=6FZ zc4#zs`yegEdw}^mxDR(i=HB7Z*vkU_m?5o7^t$$I^*V2s(;1;4KZRL)qJlNLyZ0xv z{sfKv;B@Di75#A78#8C@9k9<_m?GxfI&22W zIuAL%eBtMazC$n+=-t#LQ$k;k6O6wjG;uvN=b~7U0k@X5PTmsceCw>+le`-mBto{a zS(_%i-9BT={M8Z;PPv`M>^t@`Kdi-dnA$16`& zDCv1=CDD?veE-Rx5u)7NU0s-YgCJe_ccBjbTDKWS)!~>{CViwg(!aY-! zcAOrfH|(2EG=0ws2H$fz&Pw-wMD#h^yS~#NB5A4ry2yxhky+#%yI9k}K0Xr9!+$W8 zI1I|uHksVms&}76{3EM#*X~W3Fx|iXSa`CCbQb2ihXL_`@jn$A}hf!)L-^E!oQ}U_oG4@^U z->m0m_OnJf!|$sKZ2$xa8+tVuv_~<)QBEAyxRF@Nzh>06T{CJ+I%2gYJl~2G%YTAE zV4@uqET(L(X1-=6u*ql2W=!+o((IY|@EZ*o7kuy1Z7^TiV zbto>Uh1C~aX0N<1YtOJ7wc{hM8?J^Zttuw;LY2=` z#qp1dFz$egzXX|c9&Z*!g!ZvbQc^dJ@oQzMw5V@*p^>+=F;GpcX`$%XX3>zFm>S zaIKA7XLSw(Wm)X4iBu%#Xv260D5(NV7stX9mM165rgn{-0{)nQ@!)9Cztq+?wA3Np z<3UnGejPX%Bq3;#`j@md^>=G)qGpP=r+AQlygiWLVr{Y3uu6MYVXc8O-3qS&u(99kSuey__)7=}7=cs(qpQExrZZZay|qPe zvBr^}2Fav5LZdEyMH7pxmQi!Zdk45tVrPjG+gU;-ipf%uTM$v$&u6BuoKj8$9?NIH zoSzwG9u~=v5|U+FxCu8H94gZ`X70a*{K<%QLVhB3xWR_ttGI-!!(=U%L^dZR>j}1( zBSE66_y|6ztkJe`S63r2N`q8U z2p8|H#Wi6(ZV?nFL>?a-fp$msII7lOSB?W)z$g5BG{P<3(N$??rP9y=0Wo)n5co{ddlK61{_$ez^3K4wAIr^uO-KR8c;|H-qrgIv>3V~O`)5O~{zmUCrYn(R5* z@%<1nh(kEL=Oad+P#B|Z8gyXYtBgH~v1Txf-Xb8=!{r%f0}uSu6K-6%KJ-Wn%{1RD zG}xK>UNP8FS7T|;lARmHPuXm((M?#oJ38m})oUvE&D*&wu%G1DZ}<1C z#RHc31Sj}0U#c|@UbKDYlwEIFTU^7JZ=JdAh2_g%*cO_)EX-AM8-MmE_OGMJC9tsl zDk2u`kJD{GpM}(wVCOV$`3#n#XiB0N3>kbbK5SjM=ca45mwsDs4qi1Q{^W0`WBP2w z&*Ny!Ef4zO-sK&F#jVAM)J5o^Ew%W{wH9BNqHD9}TN|`lbu6>SjsJjVu#zE9gpO~g zN!H3ii6EOHn(3N#a0x_6p`(}oL<#IkQS=U;oJ1TQ^)85~BL^P!0jusr6b^-6dYC|r zy2`z<1FLEWexeeQlq2BIhH89=?=vq(45mBK_mkxEib2)tC+^Fy%>C2;w$mN+a5hJKDaK#DG@4-uv2#XB6 zWaoCxZ%bYAs)n*NpUmB~X)a5M&(4l_^POqA4XDkxaz=TDDNv*q)}(vD8^2<Bb4K}Y>J}q|SCidHpI17}_STbrU04jnmE(3?k0jlSiS>uW>Rk2iXXQ)_F zpwqY)DeW|l2J;B0iA$}i*tzp1dntELI1XLJeqEUAUT8S}2H~JKy88e> zLBPd-{$=>2_@e3}Yb$=h=I}p)sfWDDWlolVHL3frzW}tP+0|98KL|EGh+CnjaG~QA6;}j^#I>*7t6Yv8|ZXo)C9!Ge+D;;itDSHF^A@^m9%}dBVFH!8{0nmog zx3qv3QvL5&nKwJjUSLDJsES5Y=tpF{4;-lKQ}$C9PPCX-B@F}zYhDNl3>I%v(yVnx z7EJB@xQLa)_40nGng9wY0hsWAK}u8Tf4dnUqMy10w{bjz$|+m2;8MuYZS&@B4Yszv zL92fGoz}m#iv3tAn<-aHpT22Z#QfB$)6y5$!&C1lYx>8wAK2wL3ymxcYlHU#Ia4=6 z3ylt^G)cj+_x~Rmt|Wep)W-p#b|SEVJ2!Yh62^+)yeSPCIkOvCm{Mug7@^$@rcx~; zEv>=!ESgnPRfRUQog4mgp-TM?jX;4D4&{yQzAp6;4heNQj%4n#%?*gVEXoxv?6D6e z$xS}KaS`r^pgDUN#Xau9Owrl|OOm(Ix250$-Tn;Gd+d6gV=wE zXC2^i0p+=KA4MQ!gyb|)y|dD$&yw_F6H+x3+$kl?RPyGQOp`D8W=1LMUh#ti|&scHGjkO*sMrQTpVZ@*~a*Cy2qA%p-J|% zL~C0u^|}C_OCju91@M}R5qXQXG#0{%m}C(c>yOj*jcz!9G;}z1pX|BVfs?#k(~i6< zd+HxJ`%Mk4`u1B|Rnt@gcKS8j)jogctXVtf<5JdHyP{^MOq-T6GyLvp)9!YAk_@N! zTsq)vKeY4t6P>w_bQYF$47GPYaB0tJ*8U`A25+7Pe zIC#wz5D46>_3qsh62b*-r@^);9+gZSCwV}g;#32ToD;x=`#N_a@fst^#V=9tRWTpz z!aLuw;u;6Z(>Xh?^c0Etc$xi(`KYBWSJnC06Eihwo|q4V+vY257i&S_|CPgPeJU(1 zWz_T+_^jR>(P1AA^Zs9Fx0ztKoI4D(v0EU?fjc`Wan?F#w+$yZEk3d>l=EF}#_*Y= zsVp#Y?`#|Ev*0xkuG;QZ)Id}5|5=KUUQVXxiOpib$@=gysUCT{0yrCUhTT@Mu}+Q3 z#40PL%ua6lEd3<)eWD~5-CqX`j}%DV&@#hnf&#?&B3Z`Ze-A z{XWaTT+jAjmOMjuL`Q884c{Iay(3gAO8esD6sg7G5)L~855XO9wjN_aAIPwB?~Q{SU?&7aV=W+=Xqx}=fn00+-2jjUzcqoQ_% zhwq326x!fMn*GvK?4K0vVqDIkc z2O+ZqmjcfLLgZ{j4f}=sdmfh6Nb%j#hkv*2C3ub#KE5L~M9(u0>9o$?{QEw9_ZRgC zoB8)fv{x&7aMfgsHy~%~gG3FJGp_-^eSM(#ev65JUxMYa-e5+HxAwr8D_R&r0ip%O zn(Miws?)lOi%}sk=o7?uK!O&k@Ej^kJRd5amjDKf=;3w@Qlt*iS6~4K_u+D&0?fGH z#=TrY7F^-TCy2+;Vt!P%CQ`Y&<5q0=;OG%@ZEj$1JQsH?nQCW_kBG!Yhhf|1$c@%| zOXbwO4Tou9S;@712k&Qb<7Q1<9lG|t=hji#oT3LuMvvUZ+RO!{J9f{Dc@bpxSuuQE zBza65HD%YKxXubWHRRP_atcU#@=(j6hufF4x?yoMCol9_{nBrboMY$NFL$|&aG4(h zg8HJXQ8t30Rqr}n#X`fl4VYV*21>Hgj|KRVDeY;Zr_(k*i*w*^8kC%BctL1#z&4EA2+#yobJ1~ zvtq}E1A!x#ZjSb~CiI0CP#Mbvw6NI%6aKn3(bGPG4&q1>6Oz*sE`T%+9_MlecpA zN{r&PB%My%SS-=}J#vC?(OcWbb&q0cU<3P^k7DQf1A(LBw#@hKgLBMQH*R?}IHVKY zoq+yrig!-nwq&b1qWYPJ@$EII_SZnKq08mK~C+99J+1f z${uL@;PL#UKqD1qvO;ZJ&m3EU$#;8T?Lp-E9~KEbTFp`;7(^@cRlpXx$7+ndqm#R? zA3Vq%A=pV$>)T&QZVL~to1A!NqqXUaw`CJ;hOwdlYW6Qy%AR<`az4wXOy+A>%TljJGafn9T~B!0@xv40!~ZEI)_Rt!z5%-v z-s_0#d5<(c0JQ4*_T?(15YA+o2F>rjF!`6N*C^WRiS}dqwC@9V;8gVmDUQFUE$(@Z zXB1VzcZTu+iklc*Tn8N$*J+C!Z+y-b$ny|1!Gk^g%?fy~u(fgUBTKXQ{fmY@nV#ku zTE@=Fi=Jd({cB%#DGZWPWucyF>79jCE*mWmJyuZv^I8{{I&ouc!`z3rgmrqlto^yZ z;IW4+VOt)a+Yqy9qLf5%Vh06cyh;wnYXd061-|A23Wz>Je+JkHGNb@w0(>`A&{Xeh zCFkrz`~Jngeo8<@QI_YQBKLpod+02T=;s7P@M4O9h;4(2awl$z5fCX2*FhA!aiTS! zyU~M_R9bWUKaKpcQJTB3VxcdVWJjp8xoXEz9xU-i0V^0JSct1UHjJ-mp6v?h)I<<5 zju#T_L}mqB6Yc?4I0NFwG2xMfcepwbCl8$;E4OyyhDsi?zDXKPaWs*BrPZ$zEi3IR zdMtB9f1_)0)8>s07vKSS_X}^w?`SG?vZoAb_$^~&CJ=3{`VTVITG@ftdx-A4Mt8St zZ>$K(ocDEy8x7gcGZzHbWLCp`T$T;w|fC{ zNW)7WTO~{Z;Nn=izj3J=RfpUy zhXVq4dh@{^>1R7-!kdGKZsT|ZQy`yR*nUPxo=_hnZ;hjZqlBxB3@%#RZzbUztNs}& za~}Me0?wT!|CW73`&!t$XSZKEekajUcJ27n?Aj5cBczvasUtIjU>fVl^ICE5-){G} z(BumjDDcO|4q+3&%kCbOf_&vprGdLe>iX71|Pdvw(mm{oA{qnBV5AX(4 z3u2kFrwzrzh|IYH1@Wub_(zJc1lVO-;BEiMexxGUKkKACR%4gz;qM#_XKTbZ(VI>2fp#T>N9<1?=yX6dvM=p{WZPM`fGl} zH(MWQlRrZri$ovklrA?b&0H}Pg3LagDcU4Xgz@aC-oCwh$IgFl-~LZZ@BV8iO2IFB zUqs0Ajjux8z!@o+n)Bf6n%OxgIe88~*EdIsD8vJdELTd9YkQ%GQKPbFWS1#ben)9V z_x$j6S!%-fOcspq)tYAQ6>Yj|NnV3vjF6qxy~l%3sOGsY&7ni2d5&ITr4$`Uxy_G~ z{_I)OKMLvx)hoH3*ujv$u36{U(-7XoKkhMy*jyYf;i_Y%Xf*K3^IeAybvRja{nD1awyPDDli(X2i^YV>Awt&T86W(m*!eqxn?}wsXj)gj z`Ak4s#B2LoF|Jfpju!{1WV}L}M5T&q#7Y#ahiX_p{G+PmAk@V(xlSA+QUI4;n@i@^Wf4?ctW?UumP%B|Lv5r0ev1Hdi$Vk4&ZsY zmsXTxa9gzas=n8+_r31t_|2d5diqTKzOM5SNZV2k%Ppo_-u-o*Zxb(V`$Olv+)FFU z(dahOIB8OqOesm7jToH#5a-vPsAS5SmP|B?hYCbbK)ypXx`Q_gNV0*LB4jQ?Bu0oH zI|z3za0CGq8T!qQF|iW+DdWhLn# z?vb(4``NBWgS})#?-|%2y|fv~(ZunQp$!&z!*gwV&vTTE=dg3}p5QNO=h%gP64-46{QZSP zTp#>8_35)OuIu|ke?8)*+P*#IpygsRL8q}x;gkmqN{x~cJp;qt-+5L$?H=(xj~Qi+ z&KdRhirb5iOUY8Lau`b_1Ay-5u*=OH*J_DkK*@FQU^ID}2F0u`#(o83+8W#t$K(I} zNij16OOcFR%!idRuR)4)#yuf<&Qcs+O4T&TmhP9f6FgwB-@CY{Ql?~NDa=6alxJ;g zkW!ox=vrefcIF*x8uZTEVvx&zK_>ep@CcNnExksR9}wjS_}#7`$mTAKu3VSJ!NNY| zK$Bd&$e~$VYl*bCM|4&+HNp$cz{*VlCS16|1F8DF5rP-={;v3`EAQ}?%k1qfk=j}* z+FAn8BJc`DP`kARLuu9oi*97DZB*ly;LnK8v=01LZ7u((eFjq@vgS3yOC+-tO_Z|C z8{glK%?q#&N7wL^1hBn*<7K${npp*@F9Wd^c)dkq(UobluutLG*L~P((N=c^XshKa z?o?n6;6G8J1(25M_+P4*o;$hA=#TJ0%8EK`HS=jwU(^M|n<1;u%eyv%)B(a##& zYf>v!T7tCI@%%NI{CGd4X^|3iWr`V9#Ri}&>j(ff1XCrzt zTUt?3TGV~>p4qzf)G5WD@7tZVYU#GqO3u9dqENnd>oaIooxf-S5%?XS0ge&=5%|aC z0f8#^xs0C5YUNgoJ;s7;D7rdrotn)Ow>b%gnwqQ$%0HpO5grzL`Y+FF*3~?oc=XQ6 z2_wT{{>n|M&0l)@?x|Bp&Ymr?)IWbFr@co59-KaV%*YLEs@RW@PFpy7$nq7|*qfDN zN~)+q_oY%K{10$8;F>=HD2yI`0ClY~to-oU(ib+4o$WVui9)${YcCyH`QqA1)5lMb zl_ljp8uRqbRd-BUx8pr_>FKCt7vS#tc@Ji}hV_UCAT7gs%pw0I`IL$-HqjAeO0THQ?nIhyrDLE)G0~mpN^=kO?n0 zsxOD!K;mS&p2lS}Xq?yWrSZld8gJ4y>5XbTYyL>%O@E|uO(9@Oz#IS9G%jS*o6@*I zR69^-z*xCPlHbKuLn#O#&qK)(K6;c?Nd>wuwTUQsQpE>pkW!{hv8~k*c#&v<6Kz>% zq*BHjmDrY+6z+)v0?@E@EtltjjFr4fN=F_`r);1U(y*56wglJcMyX=9w#&60Df}=C zTdKHq1T}bGD`$P3sLKTp+XN7PE0O9O+Psbwecf-Z{p_t49*Nv3MvZ==l?1J?o=*pV z6A5McegaFgEEPmVSj%uRPd1gXEQ`HG4=;gQF8=Vl4{@e+mNT^lfY1T&IceqL!|}si z#!vk1B$b<4Lm>sT%0luqQ?Qvl3t{dKE@Ta6Ig=7<$ds2yrW#6Mg*B`>FON0XutKg% zph|6lE*Qvs0oV!FD4I;HK-H*o3$$BMFkpPD)j%h*vx>7AZlH1Uke#@p7)K-95uTrF z6tD5P-M~*h!b8i5gvgYzBqJnjcU<#D-|^#p6Uq5l^C8n|_EkehMgt8yeVT^xzt~r& z3rYLbQ>^3oan|wFQ>2sgq;IFCM$StK%pc=3cC7Ukaa4-7wBPWWXsQt}96wGvTLXxr zmNnl)&3y+9Be&zqu2~p|?KFBrfW{;wAo2ijhQ>~VcrDfGQsd}47}qy&eS|;K1F138 zLnY~#$F3fJ;G0JVuye+Bp;OmR#sAQCMv5Bn$TtrhzWSJS>*wq~_9Zt^&@kHl`4_Z@ z+=~dI%wMt0FCO}(Y|YxTU$Xc8{I_-Zt!VJZws-dg&YH4j+O&03X9eQ!%x z5-sLZLPq|RnavkRg@^eh2Hi-6U$C#9xd{fV9^WpB9Hr zGq)5%`_)gV5e5TnLN^V@Wx_Ai13HDGmd#-Lc z_s&w`5N=wp<)3f$oj5%3_OS~e-Ym)Gk39B5>j>|maKgWBSoFP*KT9r+ncd)JHfl{- zDYtpK-agn(*Uuq4CUFM+(Fh{*%z z@R`63?83L)iQ~lup*3dCxjG947RZPEDjUhnu~Ejd%W$EzzybTcxR`x~&g#n0L#Ebj zIE^0cY|%SzLc(-`6RwMg3-{6YK&DD`<7#u+&!l$NjM&wIG_3*uYis+_#l_NiW__Qc zthG$?72V~RHS*C=E3?lmF3#+p2DLwNv2yq1djDZ^IdVk;YheT6e+lh77AJJ}*G+e3 zV@5+zP~h~2hU6z>W1mdE$-D!IvIyU-3A4)<99)*R?7)0bK&IAB-_O7!XE}ZS5ddws z=;`VqWIO22phTZE0XfTW0Dyj}dgYa>;fBHs^8589@cK(<# z^A+R%9}cRe2Y=X4v18_s?Rlz!Bd=Vp6zN-`TJb?AxH^aH0=-nh8bOzwp*!)^?CKaN z`m0u5E_utD3zjTjXSw?JtKSb8(xn;X5?s4$>w+bd(-)fDiywRb#?22Xg?p-@}?%s8;wje3@tKF=nKIu@yZg`ANP1<$h z#IB_D;$pNOsc5LSp9%b&2U~zW;IzcUCHe8>8lK-WV0N z5i(62%3=+XF#ZTh1T-&{uBx*zkU~}PpNX?~e=7a>>nv~PjPND8Bx}ABT)r&1w$d6V zWh{uf6A)yARdEHD$d3~OpSm8cCqH_@i@r4dGOIdyl7cQ%HT0djR7*B@3Mwa$XSsCL zj&rys3ynndG=lfI|F>{O;lh!OMyUES1)V&}sxDhKa8zuU(<%2^_GS4i?8|4Y23s>n zkwgSO6`C0us4YYK#8dKsDxTLBUgNI(hE)zDlE=duJV}kJl3`VyG;YL*Nj4WR;>kpY zkAO#?4$)YH$4}JOF>hKD?KWp(;DTY4;hnKH)ZVyajmZ$dnx1A~R=&eNcoyE68#OHmNoL;t)YXA%aVpJO2VOd=qs<$?Wb6g!6h`9)erS@H4K<>C;6GoCT-iu$%YYQ zyr+5@atBS{Se(8r-czS6cx!jjZ~Go5DhUd)lZX3yP9AB@88m&Ioz~_@3QIJ{oN8f{?e{m9&KTriG`iZYr3r6VQcy)or6u>l zFVD_!aWW2a95n6hH~U{>7b{Z!(HRw#7!;K?!+nIEF+>hL$iM2UCnrwxTfJnMUBIA0 zBZdS$7&R?%vd$2-EojgHqtVH3`KfaD{`vh^%9G}#PIeq<95isy#Le^mMq|q#y7KpA zYeK-C{!;>nj5ZF!)q=AFmrtCyJkTT1Z0GOj;$@mR&7ad`BmA2_0Ug|~fhPx0;L0U2 zok%wf_iP6qRv?DWwe#XaN-RUZ4zUp71(%mz9zAN$3!7&?v>@rBcQ(Vox&uL8?8L8M zu^0dTEUZf2OYaHrPoBT{oNPoVdvSeByCLSxWcK;jnvaEWc3wb=@1mfuqh%2f-u zEp@d+(Nu;ASr|&zTpu2bZ@`fngyJGcvMN?Tb4!5pZDXb`TMR9bpZ|`Tp2P0+pF7JB zk4n{W*p=(FH55RR107i!VqxcEELh39O8|uH$)G6;5eF0}S9ju!KyISFl){Fw@`xH| zL=zR&OA%zRLlm3bV!;)#Eqq`s0nOxeRp_0?3D=c7*)M?!j~m5rJTIss_J|Ss5d&fJ z8$7r@Mdy}cseGD!^ZCx5pVRG6pQNGn85#BL+mq8bEgHAzKi2&bY6lo!M=n;2<0Z7xO3k22PP%18}1V{H`tDq zdRpI1u78vj8-_kGVB%b_fph0O51JP&w}c-^T9Xm#rt}-=W;Tr+KVzu*_zTld#_X=00llk0d~b+yPfifGnP(~B8pow?)|B#2^eQ3#*pcLv%S1#`vryCQJk&|s;o*1ckcvzjYi)~7eeX?cmK3DQCPF&0s|SYuN9hRbQc38M zj-;+?X*bp2)j?`R6GrXjJIq5|MljJIW{e&oE0$6k>qj~&*bT#nlt*pb78SK+3nDk$ z-+oa?W|=08=Iusp6jB#L>%kRrF|_UsdN@?^C}7UEA=^=rA8Eh`BV(=2ltN~>bx?&? zp<;@)wlHUHY)1()&M4L-6-iWumY|d3lh4I9J3NPdR>*d`EKI1nl5LWm>kU}!k@Q>~ z;<%vqSW3c?Ax6(MGx5z6nloiY!JvxT7v5I9I6Ji&vK6AzzEv1ByjR>+9N$I`; z^TVxWw&{vRZ{5@N7bHncm!eT6$)4Hb%O@Vq{Iw%NWbrS%cmGn1CA7QcbkNM9ZZoF^ z&$?}Bgxqrf&xM6Q->?28(~Q)aVVgrkH;2tkoqEeU zd4hO+BNu`-NoA+qG6>jEzR^A?dGWS4(y)$}Z3rA_kGvdULs&$fkwM@?63(H{ArsPm zJO@TJDcm`WX}0VPN}h=*0>OwtWO4w+K}YpCtIm55TZp3sqiJW<=-ES4Tt*KKU(F}D zEFp1-3#+#qv?|Klh$;2OT$lj1H-J`i1pz#pDgb&S&u$KZ)Vh!r{r$LuFz#P0muJlw zImaicGPiVZ_R8h&hizh+R)a^+9vT#2bh%!@a2LFn7>qQjm)8xA?_}tbpb;b zPcMebgfq7`ORBHQz*_C>W!=NibJ~>K6qu%aXp`@!*vd$WkDygFWzJ!E7|X`$3KV>+c|7*lb7c?-+QaTcP2wOop@R!CtM4ZrUj z&A6dY-V9gx@|1^YfQuZIS6E2ttR79}6c*yX2T#_3!Ix2_wT&6cht=VxY9ohPaCKGj zE0Uo=yj8)07dLtTAKu;vAgU_;8=rIUojWr~hKfo?3W`96ii&_rIv5x#D$1y+h@&E+ zqN1XbqLHDZVUe;f7U^t@ip3fl+fq?Yg+?tI>r$~U+fqv{wbYWa3Nv#L@8_I5gD6t_ zec$izmoanaoO{lB&cEk8=lMH*<-oY;)wGzyPjYBxaJ`T**g(AR6(;E=ZzWkMvX>nE z9V$x1asn;M0EwRnt_!#=5M}m*rve%x=1>!)Q-H@TX@qO~8kOQ5q3t&k*l({4KXH?Z5uoOkkU*a#Tb_^z2W*-1vv| z#FI})MY+>DR2~ERq6C#glamoJPR1!yIARVnotj53{wbJ_h!Me?6f!LB`4Y*LQ(|J$ zQq3hL^Or4{K08IE7U7v6j~9Nq`*=!z@v%;^b;ZkD14fRXFnS8{m^^CqgkZl-B}uD_ zS7G6}8=lHcKeqzo9D^RVVklfTZD1a;snK$qHn_lT&!>%IxYDX82~s)n6WVANg)v`! zIdRVsBlsYh(T_q@F}z1VaAo55Y&sQzsL*E>{`ibUQVp9U=B`g!8oJ?rYBh>^O0k#w z&?C#UFKs^DCbq6B-86daoM{n>0lwiv39YLdHFe{g8T&3X&88?cSE+A;QwKh9F7pBx z<=4Hf?rb33!-ayhtEDN$r2m4bD{P4g3&_aeiaqUGv?|oZm&skrwBlnM6h)+Ot(5UDKM@|czumzJCf$-R8JEw@E7Kk?+c?XNuBSsXt9p|SHrLgtN5G>w_=H!m6=3&hrCze(MC z_FnzHM^`7GSe)?Os?|sFqj)>eCh_nZ@9!TzbZEH$q%hL%HD>sPiTnd&91B>hFvdPS zlJ0nB=c^t&u0^UMLC$kyCSWe2G}K?nO#9PIYm(0VWq0A%PkA70@51R@_qvB(Shx4m zmczD^@Ff#cP2r0|_4jVmH%&hL;~y^Ed~U|tms5Q2Ke%r8%+%L*aAT5|HVk^VqSUo58Q zY3uaUBuLsh3glN2$t$Noa$(c`p4t%D@6IuPDu)#>S^KUn~tlZo^< zo~CP1#}%Pb^f6>HoWa;+*rNTe2n()X7aC=_C9>yGrWNK5U6M_%m#~%aqBmYyAT$yW zgo^X%NXFp8iKLXKYr>Ln-nbd@2H!0qN~TyZL6gD}3$v>bT7wD;wCPfsY;niNcqd3G{eQks(Y(9er_f{snoQ+Q);NJ+O-69AK6a)^)FNu62!M6PnG&IR zN@@I^Pn;_Kz^L4}C`O=w^h_%(mOMT@`iKE$4 ztC)p%{^3l4lv=nZrXi)tnS!+?CG&T_cBaH&9hdNwZ@N+}sE3vMXIDM>QXx-icBVw3 zRHc}McfNI|M4)CkLPE-SUG?NkrM%{UIa6X$s*0Ctai$b28KR#gq15-z6fY%1DC8+W zI8&}D*`kl+jd%X-Oz~E-g-txA)tOR2YcZ+}o*!K)pgYqDlgM9ZDGbAO2ICzc6~nmH zew-Ml?G}Q;n=zfuQmPcEl55e!8My{21wOlO`Zywlgbw;G8FJ$W8A89gv2^|VrAs$$ z@Pev6;%t)PkW^m>HQPYkkDNOG^iwB_@`03M!R#uKj0{t*?B}G^OfscpIPn*U6p{XA zb}L?s(rBRm9S2uWnr#zR6U19|8je+&lWAsB6`HgnI>tn6ho@{)Z2*9ycA?9UkFM+gPOtMi-1qL1sXO@;3U61&CG85)$~5QX9%%ANIG*I|8#F3lVta64z}S(#=A#P|jxJif z9n{$qDXWe%&Y=;^&~$X4Q)fH-u{d*_I-3}V?PMga4Oz5ZXoh=f5^N!oP(({;jXQ0( zv}6hmd|(pL2X9mW+U$-#+?6&9Km1viL?R5S6JpFO=8s)nQ-TKOE1}<8Qf;!Al<}4(7ICnJ%%Em>SIj{Ac z4D4DLxQz`G;+DPn{EFl4N3BGAKdl_NI5-6~L($T$zhAfK%j2^*u9`b$PFUam+dRhX z{PawNvf=H+_Yd5-c-q5BXVNlXOCeHhMCjb1p?;&+vSSQOp-NpbE;Ic+(yUa8dDKe` zbSLgGGG`hzY<2@NyAJ?XNcP$2^iGa(25J}m1!_#4e72^;3%-2(*t+GLx7HlU`Dp9b zr76evHXaRqbi}gg;8~;MOc62B)22_4_K15rDJ|b!@BYN%ghTV@9ZFdI1b*bFB|RP2 zKHx#0(8)u_`vnIPYryEyW5^`;%&=j5;Gv@7+Qow9q33N1~r~?q{Py4p#a*uI@!mW z5=G0!X8w+ES4uHZA1j50$x9h&xs=XJjc}$U&|=A(r+^9Ne5Y875}~6dgB8M4!clV- zf5+dM5`|KMyyk#zDKWeh&P84-yVHn+AQkmI0z}7P&IZNXMiP+%Ok!i&Upbw5VmsW~ z>EvkVI|VdEs1Y-eG6N}nvrL^Q(Hdn%P;2J!+O@X$p(ICY<`EdaS#%yt5MQ*3kO$;I5W zIRwyBz1XfxR_k#Q<2ho6BM%EC0a&LE#-i%EQXpo+f}(<7XhPV9MrIcprc59d96Cq> zr>0Gxo;H=%=V!}aUy{hZf!ApBfdjPpT3`;Dg(U@lMM9ifJ`xctx#0oa2=UuY+=0Wi zJ|Zm&6_CJ#hh(pM`r2Oq&&hpIS^DS@|) z_y(*pzF9uXga6i}{0Is1G~`FYf+k1r!SW?=dr-p&*dIV}Cr5DIPaLFf->Bs!cclBv zQ+fL%--vKfRJ&hK+;YIppeKM3cnJjyn1{jaqv2*Dlql}nj zZlrpDcWRLkmxRlyWO8S|h*Lo8y`}#7Q-4X{a_xl|>Nk_bN%LTgylCDeTgJBf6SvaM z;juh@;%n-mL9)~KH5|xk$YEax8h%Kdv^gyzEh0T#Y)L~Z%gDL5FX!41NaY#ZIv(Y! z89H>NsZU0?j?CAr(5(LD&S-z3Gyhd*gLi*^c+Wd&Y47Yg{P`~OsL42O&YWq+1!Py+ z+q;f@_E_epPwjqZBN<>aM$Vi6$iwq*es%Xjc(H2`9(#TL`qv*z%Y0+~`Zt==!ee5> z*++5O-P6U^pC23p8=Sr&cMQ%J&EuLU|GyugUm8HdR?Gj=0P0ikKH&dp{NEl~+XubO zg#uPN^b{mbP-i{cm_Yc<86Bw3Nam(h^~!4yz-IriZ{Sui?!%$wM9aa(C?}buLTOX` zBUk@70uimHu@QDJh&mxbh~NM=Y1atl|HXwpytA!OiJn=P{Sml=VZKCs>twwyk(fE| zYUxvPmwdsQAJo~>CxqvNtHjH%XXUjwbb4MM`EIqhQIow<_azP9H+>kn}pW4+cU{d zdHU&;)kls9_c(bv=533XBeG81IBz+#{RTCMY!yey%B%9R8@apVN5QdP+Y%KDtQlmbNOHB~glgAO_E?&x;yK!EWe{i^Lt^YF_H+>vgFthHOK*mRopMwg|=--sSam)Cb3SaB3 zLVfjJDnx`VNt?wt4&O4`EQc3_Uk)_&8#zn*P9Mm1e|));9pGj{JMn3FwWjrU1wt!$ zT3DXGAyFUr{syv1Tk z-*W1dY$-@hEnt5d1oD(EVu`^Ad-yH};A$9mRc%HvFa6b$CBIs#eumAN6NW$8viRAA zgl8A4pH(Dg!UWhTu}@U(qfZqrZne;o1z$pi#Kq^AZ?Ad(n~RPnEO~a(qGy+c#Lk|G zKYi-&A77XkAD_1npOs|B_;HUC5CNx~8FG&z{G=nx@? z79}0jl?h%-b0XA1=c|9|rumz4=+0KSnQzhLatSNMjBts>yl_J$3p3Abu!-ZT zcl?L+iJnZVSEdp&F>px00ApaLQg?XoqKk)z=PG#|C2{REM;h^gJJn>pk^AZwIvQjv&F!j=$x21v0d_q{Ll@s8Z>QW= z+hLtVq3OZ#5e^T!)wvUdj&`}6Cj1gAvbFpdsEF7GLtu~$wY_`L!P|C~A`vs8b#@#5 zRNYmZmgo~N-?(uZyGlcBt7R`*24f>u&Y!u;aH>`fwj~o1sMw((583bXO&b%5?Dc*} z$NSUPteK`Eb}zX_dC|bMFwh>BIs7$_2=kHPuu12rrWt-4mI=a!cy|&)rVGNsc)6uW z4E7Bj+K``PN_6k+gyLcaZP({vlGe8~N}AZ*{n_7>_x@|v50`8{z4 zWOI=Gr9q~yuD)Z3s($`)HjXS0SV z9@_Lyy}pd0j7L}mrGTm8#P+`9H!N>`z<;otzO3}e3(qgkh})C@i3M$Zq0}h}np~9M zVM}8m+Jls2n7VrM4d9CnFwj$imoLU93a}@TJdgPY;l=s7`-F!VrFbT~Z`l6gWfC}g z!l;Kt4e6*;N@+`H_HFoG@wmWr##DM}D+?jVLPE>rII8ovFkmE#RKdiVbGdhh>@Yh6h?kRU} zpv;(>6xM>^c2EQ=&f*m_L47xL+x^tE&2cs(l=W!Og^MvSIW(4f zYHIkrq}@b-?v4_rxYM&U2>u7ob(C}lVKL~HS7}V=&m3@euinlhpE(|xAxB{-3vD^j{#u~is8o_4P$pOU8DF^Xw+dOctCMuGLwpPrdMhM}- zYJ0A6S7u|;{M9St7bLBK!?+f$C%iv;+FFE|_I6STdxY@3Z=ZVV+dTF~y!NgS-|*OD z8^YJ`#hI4Z+2mfwJ*B;l1*X%?l_@5zpve%{TI^rNEL~GpAdUVe9guKU~z0(U$aQ`nD+tk$d6w9 zSRL_#fs$9Q%l@q?xlnw>PF>VOL+IzhL`lu9&LzSP-#_ND3nbTmS%Ige&QtrwpfUK5A*muhlr{*M>zi4%IO ziGZe_VOa0%H-p93AZUYWlMrWfXeFhLcuSMtAVXlbZ^7RX`(+Fjirn+k?L`dftjLW@ zLsYLtyzgkmi?{Mmhu2+O5xW5E>LScw31Ksdg3MzK1MTJwxrCtBMFAsOs{2v@$BaWyn9rTr!(Ma zx^^yu2@BDR88otEm~9V(7G9q|a~MS{@us72j*TMTq7|{Vn#gAzndCEMh*q36qY%Pk zh{NbGBD`Q71>$5C^&iz+MSb+$D{5=+y&{Qi4Zfzd9A!dvINIQjp8JySQ*`r;x`$o+ z#86FhZkEG(r;=oKn=<;O7OqEnZ7YJ=cf>o-zVa~ zYlbi<{RRyb6*1>4`x&(TB=``cUy1Ljqo|d;kBiNQam?;)Lr5RzvO)pb1Fpx0I;LW% zhmg;J=56FN+T@xct;~ujNd}EU;G~p6V6A{X&^0&cO}9BylGLfy7T(r!DytOu8nf7^ z4z$o9r>6&1F-TPz;}%cA;07(E+-VCA#Kj$0P*`}0JYwBkMd>m7M;xg8m1V+dp^R9N zi+6~{O5eJ4jMA#jiU$YeYh{@NBN);bP1STYrCMdd1_&}pRpW=n$~0)j&#Gu88X&^4 z!ov9nS(|d2@)7+8KEo2R&89`_*Lh~IODtx>)f+ykdxzUL*i2u!Q6fwAzV$-rb?XjPA2_RFqffDNs9qZL5%iW3rNyGSX!mBOk; z$^{p+8Y4EE6)CUXO2M9BDSvRKU>wv^6}M9QkK*+(3ITltJ1Ed#lIyXPH1P(h9bUeQ z#d?x%s}O^sK$?sw;`uf!COlgcs1KHFa38^C2woYW?3b^Nz$msoN`3iCTwGk%B>EAc$2+| zrUQA?cySv;OeL)O_Bqlw$`J+x5Hf$S3lO{)iGAq=hNjy;Qvqs!S$au%86$HGEcR+Y zgUBFjFP28JHyM1&%fdooAx6#!IYK?}01pO+=DP`taT)YAYGbvrw(|Jx+v6AP*s+Mj zxzj>}=B9>t?pUBE3MO_^blKN4%y$Mt~KB3d*EkV zy3@BnuPxujhX5g1ItG=A%H3iRJahJ{&@BgRaV3+>v5kiOj(n-n?!(?9c*5Mc>fj1l zD{%o~Ml7&LgRv28o>^!TX1D7LwOuG|3o%8-#YNg|_GxR9D*DQDUwh0wI$c*HvPiz} zo?BTU;ap8HLzxj+004zqaQs|D~ba?*%+ot46PbzbNrcE#pskM)f zfpJ{fe(2+4t7gGFC5_F>HV~NJw_Z?9>hW4%eXvrYlnHU#aC}1r-I1!4@!v)@U2bKm zAX2R?6)NidZ6OA~c{-M4_i9n4>}pw*95%;~;=L{iY*x|Dal>%~i==xIl|Eo0Eg^LW z;M%YNok3)uBh??yFQJt~5T}$`t(9WA3!KWzO0kr~0@x}mtx9dT9O8tzs;aWmW@Y_Q z%21%PMC}SoE^(z+RRI^Yb$Yth=vzRt48vMsZW6}A3rLt|3JV5-`!Q#5G3gsjXK{}V zb@`Nq1)|7&5-@8;cIkwfDCzcl0CPs#xaVVpo7#0{Pu3MUK-gWAHGbiELdM6B4_N?K z?l@CSJ1Hq6Wo5KNFw<}vhi7|PyTi=V9Jzuyl_v=iyRKyI`FPi^k5SJq;yylp!UWT} zai$3q;>U|o@IYpv&Af!Uz1@K@pJfPO63&u|7fU83xV*OVWKd4Gx;p?YVm1S=PWR|+ zx8D3$y+bqqy%gg45@&_i#&czR50cI5D~kq>#x% zHkqs(xK$F~a#3E`1;l1mEY6mgQw!!v6J>Ga5PtG7ixvJ`%vOvp2((svqmT`DGp|)S z0OOG$Qpcd8LAdOFp$sQp=Y{KVo4ZRnpph|Lb%q42v?-dIn#Gzmnr)h$?EK|wXQ2}& zOk{%iTU@3IU5`vgKVl%4*E_}aFso!-Ifgy(=p@DmpoD6450=y*U+~Tu_tBqcLt#z* zfpUWe5ws<=9BkabzwzMt^Xm7ZzaKc0)0m6sZTtV8TUx4q=Qie?Iq>&G!SlwR=gu((qW!rO+kN-6tRh49UxR&J|f?l{!eh3C#- zL9~ebb#;1AMk^xtw4kG3iy2}D{aQII?kD#%Z5#Tvummfjg|HVHYaI)lspA;5t#YF+ z>{bEU%*@5@BfAx2rsIwJ5cnly;jY8b1_3fAT0SxVy?OOy(O>jG?+9`PiJt$ZrL|;s zToe6EQKGIo`hQ?^O*9}kjl7>s*o8&P)+zgw_la*irrY+4Sx^$sbL2Qaf{7Qp+<{KZ zW({_)vm|$PxYAh)CHujt+0$aEn5ASmss&kmn^w01Yu25T)S)f?lfItlO z8xQyDaJQjhZV@!XM6IV96NtI6A|C^*HiD;dOf*_QCELh-r%tkvhM(3UVEQ+wh$ZRl z-}dhP+u5WQXZ|M7A^wUFy+-0iBZKx7d$CS^@f5Dt+v>>PhOzOcQ)82TvvNphhZ9Wike;dNhJ+?)lraUXe*WB8c73PZ+A0h zUUinx4@5G@SC~PA)XyMD%???#=`uSeYyEB4lwGr^Rk(E|aqx>2heLx@D9%M@)6+2N^Hq*f}LZRJ{jVUu#y zc8wV_LYP~tR_3s>svTCS)u#*Ts1ST=!+U(eIw?qS+c|A-(B&AWwl9!}c6=p;Fi1tS z!=g6gBKGZOXJw5}NCiy=@zPi8hhm?zGlK1Np;9m3ydmk~uhR>Tl|zJPX{h}xbZU_1 zMYkxlEZ|j!KSUS2_D%Bmjw`%L4Vt`F7I$iA+3rmWIjl|FNmfWY>TK2vPV5qHK^skc8MLgp zyh|Cbtz;w9aYY{QBeb>iS{m0_PgEMt-g%MV!T+Syi3yp(@W?RiW=Aoqb?+i6>eA zyH_?Sb@H?U8Rq|?bFmqlul9oD{0eT+!au@sHqD0O7@Desd5 z;u)eR7O+Ai^z(q-wV$t#q|x+8P-g5y!qU}eHC=s2l7i{G$~$xzJ@wvuWF`4hh$Pd< zG<<$ZR=)QhJw=Bh_g&(K-_H_%ct5&^_-e!G4g4F1r)#iq^GD8?j;x<4-qi##I$uYH zy;q4owK}YQ()$?s9f(~6e)gtJV@k@(O8V5Y&$hrmje7jo=A$zh9M-PnU|a-@2Hma9 zZi5S5c`*t@d25he!ycfClt%TEGgD0MCC3D#fV(k=9Av$hkPog_fqD2@#@`kw0*^1 z+R@%C?dqkBhcvX8oc`kVUUCMAOMA)j6u;^vXHc(D507FQ5*@viIVINhlJlEhb6)H<=eND)6!(@RzajL3#pE}I z9fO`ZF6`ek=Qm;wAA_DbF6`eUrwjY{%yD7=o;hAp_TAedF6`g4j0^ks%yD7=o;f?D zz+T#MVgH_G_KK~&lyPDIo@HFvzh{mM`}fRoVgH^vF6`ek$A$fS=D4tb&m0%_?~&7m z{d?xPuz$}S7xwR&(_d`9`?zTbIB{anGA``jGslJfd*-;Xf6p8j_V1Zxzh{mM`}fRoVgH^v zF6`ek$A$fS=D4tb&m0%_@0sJm{ylQKuz$}S7xwR&F9YtBVTgfSJm^QpL4fmFIOb#yU*XbT<#vu3k)yRZ@@k1dp8WiCx`)!*+T=rz zo;=PxJsDfi(5p2y^rQ2ne0s*Lbx+~o$WcNJ;rb!6zvROJ)Qgfwrw@ep>_?4T4${G0 zhIvFPDG{JjM>N#a03M?&T`$UwE>*mHArzPVnQwoMj(iVCT6n(`q?C>s*~ywjq)VDM zbfOZSVB&Tr!eTsUmEr9CmQ$e*nWoffLmVNJ*CH_iUY_%8N*M|L z=p!)QNhG}AB7g6$N4^CtNn@i;{u_~HD` zDy2@UWIR7>;-hP$eK&Soz51VZL#M8ePCW6`OH13JV-tse_3evawvk3Q0+=zQl5d#8 zXFz5ntruiVXAwr@IRq~D0MJ8NxW7nW9y(Ty5q+C`NM9{R$EC{xU2`5Il@FapYM}Ym zwECizZ0P)wm_H(Lr?-1o;?7B-n-@aIF&|11ZOK`aBlj+Qrho|E}f zbx(ZtY$b7f`Z%?f%z1QF!i?!lM(0yU$eNk+k{(hyJ)JfC7txPU7ZK;0Z**_t~5GnJ3F+n;E{|8;A zuY7))Ui{C&D}+Uf=V^nOohqnwvzqWA`q`Jh;)?JFqLlj@NJeGpxNrrHWmVSb~)^2 zQ6Ib30pAzn2f+^$_*xBm?7A+bxgtjB3 zV$tM`xo63PRgG07;PkqGSI-ZN*fgstCfz8=aLE>h1?cE()+161HgzalfD`@U4P($? z=+0u_3?A@bLeos+2=Q&3vNUYi7CSO5U_!-MFxwNsAVa zWzf~scM^D3rlq1`4t%ry60Dd}>~MpMt}e0hBL)$h2CPFL;HY{n3-8B81d(hS@edQ( zPpWss2n~uuXmrGAYnfkR7QsmkHG&$AP>w5f%LZb^>cpDft-FXdB5HO)2RRShQVTip zc<^5XxOiniXb8ls7#Q6E?pPIGqIDwExkhT(fRQ5Pt=yStm53DYW6*9!^IK^8r zNa>~jyO9rvQ5gREjc<)uRj(D_?H6JL=#%*(UXO5h-o75;RSiPod zvi>@0X(JmxJVD>Tk+Em&vU3^KBjemMN0G>k$1{~e+2j@nar8tDm`#yj0uCBD2n;;lX#S#xJKam-7bF(RIQ!Pg~ zEH3%Tu1GnIiw)&dU26E(^v#-o&=+n#A^6RH^USIvBL%(EKJHhua?T}k z3W@#)qG@ZCb|F_AY_AG`e94mzvi_eLQfBeZLqT3*B5APK>BR&;@0myb_+-A6_bpj( z-1&0-`DB+vK?Eu?kFP*KvrdS2I(5$ zk&jL2U=6f7uozUb$^+D)i%bEv2)C)~M5<#JDp_eG?714H2s>R&+p*FYI2`0WSlxPZ z&aTuXm2BO5-n{C3`qq*pq0mtP*G(bp(;iG;qND%)Bl$Dj#VsPA|MU}mkiPU;`}z$X zb@gq@$!*}@OBjU+Lm}qO2MPd-0MrECxL|IOIdI?zZlX9;Al%Gm0+a0%1oU=#%f2;6 zD&`>&a`s0aA2uX)jcJI#zFe|oRa8VwUsUW6X8P=>eF|Xim+wGd9l*b{ClFxwNYe%>ty-gm7%7e{Qo(w` z*8^WIlAqxB3H^2RW%~Z>q=Ky+LvUSPS*6{R!N_2dTI`-|)v)jGX>0{CjDiEJl~Ly6 zm%tEXIaU=>=%u)YET6vAaM|;Zp zHLI(rM$|jnwY7QIpF{Kw5*t4$!epDVvUyr9>NilWy@>D6d0SzYqfh;6_PtvRKexCxfGUZeHxu7{mqQ5I7o+Q(n z{~cWb&9)+?2r;HX+!O%6RD#wA0nI%9M`Lq~=&kQSrhNdFmwr5g7EGT-gnD3s8z4=S zq*(ejUHeyp%V8!lo!xc3JT7R*-!82E%j;8O1|RvbX)}o@A2xqa6TJULUV(VbgGL3$ zKK3Ylx00CnV_PCUhxUbI?+1o@eoZ%02jXHn=$0?Bh)e+FSNxjji$%n)iQ$|wZc-X` z^(ugo;vjAkIMN@NrHSfossC-HC_;rGzzBdeNe2#3olA3T=r>23$#cZ*KVI$8ty^{BiggZ?7z;<^g6q1iNJ5Z$~2i_N>rioWf%#U7iRi5f?byp z@)fIqAw@NW1%4qIWivql!MB9US4hxTd)amQ@nR)v;h}j%sxU+pAHg56qH_R$m(EV4I*MWHbTR!3t+!IBR0;JMm{i0*TBlOjKr}pN|jKvuz>0XNsRTN;|eU530wxw1t<*4TFlyX$4rl)EgjAO?5 zVeUn4fp{OUOnB?Rb{AmPx5p3?7-53sFRj@tKtqOY?@e#b>)ICbbU%x3&5b`!{ zJR;}9t3r$zxcafjR-3b6qmDRpfl>uLX2K7F#`|U}m>89^72)K$X9A^yjiDom`p~M* zM)r;^@TM7~gw@X(9WrBd;CCa25KK@|Ef9!1^EnkmAh5)5+-x{&NoK4i{B~h}nE>uuU(JO<5=&bCe20>CBuuym4{+H943f?Hh>Cw(u zg%ea=+L5>a^zU$#Cd5onDqDVHX?`VXDO)&g_LB0%BJR)FNtVF7D7dB%wM%bSJHX$zCZ%!PwtJBam3pOIxW6 zah3yj$SJ1!sXl-Ix8YIeoEgWl4~g}9Dd-BKID%iwWWoux*Yq= z8XD|yh|=7gl@k*!sT)gHOi6iS224U6G-ma0%vitx#t@Fk>;CXkfrY6*q%G^r#e%Q` z$R7bDBC3Lo0;_g@oACD+0w#v?$j<%YwCavaJnkIazvl>%ZD&vma_hlP&F}YD?wL1u zSZ2fTU+p}o+(?HLDu=j6d_-{Sy zNn*`=w21!Tlk&TD({tBnr#w4n*$eOrUps%9L~?MKzXp5uWQ{w~Xv%f9IxoJAyW6qc zvZBC6No2+!FrOI7)RBMTHMj_| zkb2j5iM5UT2!5qXJ?7uhRx9|FDh-m6CzdI7)uM-^wN~&eQyP@QyS$0Tfm5y zv?ciM-|2YKJq2>6WHXclo(C|LzJnOQ#_8`i;0|9h zVkL`&0~uRsO<*b*!_2?TJ!GH&|1yB|t+!4^#KmvP6n(vdy-L&5 zqGuT3vL#MiBi890+}Oi+438TPDjwmA{TS1A5UgU0hc0}McGO;__FpgZqs0L^Kal;n z5c-jQ-yhJ{_$*&Ob?WlvKC3RBPEJ0339d;zlp2}@30poduFkf1{(a6RcNTwvvX}-1$yq#OZ2wpJsoyY!VVBghwDsK^m?U z4MbwBGU{N@IE@Fx5vtkE6K5T25QYVQYe-qXY3aUfvpE|BWK}eBAbh?sx|!2l69$j~Um~W`dYUar zj#gS}B9h<^P2McWnEY%wT;RyatNP-`o$ z(k6B^3W%tp1j8Lqh0;n(F^iyJB8kh}P~k0aLpY4Hyh}@Ntcs0wPLc{EmE%Lz)eximlDi&l_0kOUnTxrA1_rK4e1 zm#aMKR;fL)uAY=rZ(0Vg83ji+Z$6ZlmX@zKmXvrPntYx+iCevA52G>BjcU1)7V0B- zH(Yhua{xj)K*$FO5fv3)2tJhVfu&%ko}HPTAl=rHaWfGIeTW=q$kdxTUhwzx8!=px zbOE7$VD?DTV0}X_JxiauSb@I0wySJA*-9?|L=12_vBoUNOFPFCD+IpIaRAX zo_Sh?fhWk82$J7I3aGWZ2F?K`7U@c^K^(=Fyu2KeP1*X1lI!I-eZ5*Tl9lttVt^x! zO0^uVBKb-!(F2)SbSCx5Uwf>qmf<3X#>2Q{Ih`1YdMYd7 z2!Rv`ta7!W*rKVrkq#7q_=sz)tnW-=T75ye6fb@)MQmx#_4V_eld5Ya4`Ij~gUpbjGW z5sAGBCHZ2!PVBYdSXASKy3*(0s5gOJ{sh;`NRhi zLj$$x#HYvm#HW|{HRt7}!f<^$J9fca5zmv1Sx6bAe1~o{XbXg5n9reY9t4?rh8w7B z7`tq`XkC|7P35q+p_0lbaGw2b-fNke%@o#*yymaaE%UaMD=B2jnpKyIeXi$ObQWe z_~iyHEyW`D0Rc4}WHx969Dyz#IYf^*Q2OIwVP^uD5eyQOr!5L1q)9BJSKs=HzIOI3 ziTUvo8T+zGy~(omYZmqEzqkqkC|{$ty!XlCFNB;1viMRSZK+xOSo({zl&o1VrUM>H zDG|-2iQj8EC*>B!LSh9^dV&69+UDt=0l|~z$CD-+jIdfC>fK3!LAQa$@c`fp{-eMHO9QqoxjZ_;V9BbJ%u)s zz($hsf`m4(mJ%G5ViX*orRv<38htAmT$)I6qOWeSlF&*#C6msbNR91z*aX$u5IDFu zV`U9w<(c<4wyb@-o&VRG8i4~{`0q>ZhFwD}*Mfb0PB z1I3|}FnzxK`hkltr+1wqL@-ZrA(3^6202uOt#`MZh$`p~l@sj|LXg+ACB_3+GF~AI z+i#GC7ml|c7_P^frDSvcSvw>@|T_Y-rtbK_kKq| zto5#;wQmgZ-mo*pOLTZAo?g5Bx2fL8t47Jfx>w;>?QuDuHXEwJUC3x}!eSTgm-N}4 z4emoQhN_=PphpPdxnzpbufE-$6$vu2Dtd;FTd{xtio~28a)UiA&&hEsB>N~BX}cCL z-o1PA;$5z9&=y9VM;5nSOe-@J=T;%EldXH*1UG1V$eoT2mD8$g*vjs1R@pNQhpg17 z5pfU~;~-Au)%&1&SKT;@M|OJ_T}PoUeCKj==I-T7cL}kX=B0c0E;VO5ZXM7%M7CQU zJ$vQK+0m=bF|$`D3uc7bV<)vc8na;ZD&6$I)L54wzN6;A(&YyaFI@`bWA=1#`O*V4 z|JGTqGck9XrgWEE&26H#xL7^XF>;TfYepW+P@S2? zRzTSQc;d2M!W^=9*@{d;GFL3yt8Z&=rireu#h6z|$0V;rhicKG0%wQV(qlRp4nFztSKPX2tj#q8l@ zSB!NyFgC$*Bh7aU`oFGNZoG3v@ror)rBbDBf--?iEr25y&b0z!0JFeXsDiY=6iCJj zuS!u&aZT3TtkOp)QCyf`$%XlOLIsH9SpBOsg!i7Y47+V4g8n)p*F5vg8vK!`(a%>8 ztyp>R;K~(;#5w4nV2hHUp+SZQzAIG^)7^^0&fJUq$64Ia;BYv)iZisPfuDV#K+Ygz zq6!9mwcvojC_?aIK_6k1nWKOo$wG+V3x~OqzX*ar6Cj+GOVn>eGro1g*?R)gAZ<8H6tO^gS3yT zrFE~h(zAbfjVx`mlHiXz&MWocJU0>Vtn}@h6N}T<7bhWJN!rP#T~|ozH_c@AhdI~D zDjUcqKvsj{wX(3eEuaPzqq? z$@~;W#6w@s%VMG}hMgr*I&t&DgytD6)k(SiMdegmBGf-%q@8d6E-k%6-^#z^-CjZIEdie~Hz zk~}9qv}S|gL0{3!Q3y0Y(9@k}$?_x79hFLnr#s1!<(bpus1AsLXfqlN24Rtb2K~5Z zq$*}-ZU>k?(_kWwRGU_nG$N#NSm?LGBsFZN|8Bc%-w7U}{zF8VAXRm={8+>GY4hie zj)t#a9J~w3;D~LLq=Df9t2cre&o8Dw{wC$M%$Gl%G@Dk^s`k|@hmHyzZi>-u_|>sh zv_J*B-@Pi>SXzICCSizkOh#jo5mjd-3~DYTAjTv9P6|lXG8D9oK2p~pA_<9gg0wzq z#3N%SZ;hf~(esXWXqb+C^#TdFK;WVC+Jy_W;kmVc}r|OQ`u} zGP;WBtH^_A>1XuH@)c7z#nARyTaAi?CcT-lDC0PMl@?N&51y;A?)HYfFQOhgvuOuK%LDW>k%X* z?(4j3IZ)@vIirRsOz?nwPJeW!kN$2GXCo<`*DS1aZS$u&y(!rB#+ zrjkeZCobLk>M1;+y`QukS^6;ujA&C)KV<)ju zp{xes1_m{rA2}m^iHf(t8{#;YQD=T(d|0@CAk1A3&t#9QWgF55=8=p^gY**?@X>f?&(DXO*vj28)_umRkF_Wf^nds{g{Q0Lj zS9gt1Pa;{XvL-(yA7R2v92HRzxHdyX;5y2gqauEGO#mG1AKNdkXSfFS^geuHQ9WBLBUc6@c~uwVy+dRhjX!>or}U*>^GfQEE?H+;5k^u zR>&A-Ii;DM=IJCv>jkhGS1kx50?ON0|{EX>!*mOLdy%OHuV_XlI<`VM=*4IZid5l=Af0e??IQj4`ELNHS* zf_tq7$<&b;wkOqV`hwu5+K`zO2MrAk_VZ1gU0XMK>GVOVdvG5|3`~n$h%-}Wc7&>) zx75PfLJ*KL^$+NIOL!06Mc9UHpcj@d%gGWuS)anA-l?esWjn@TciyzHHq{e^9rVzm zWvfxhEnT?S$8+K86>A0Tne$}KTmK<5&zz=je_skbrx88pzp%AD@r4y8Qw0$Zy-#0m zDF1?T0}+jRY_C)Q+B!96=-_eCqJj~GI{b1UrURf4 zSEogj@5pBp0FVsWe@sV@rKH>9}5G4p}(CJve;k1Sy83(E@$Q z`|oaTzDO>1aV4=X8y*JMu=iR3<<6^@M?ZlTQVA9_s7t zegJ1#@l#?~2}1lMMa@rCtRiIRUyeQd={7=ZS*Ue6I3{-gW^-0cUPET_m)o|ypQV^c zIvTJ$JHbwSml$C%(=X?aSN+4_bP!?yi8UdWf`Sj9n&3-9mWC!2wf#tz*1c9uL}icK zkI|3hXVN0$%we<{QOwLmEjjNxPVin}&#*~zP7d?v+NI&#jkm;M@=Dn2`(HQ9wy-cqgcK6rxW3?bJ*Ho`nW= zDM`He_J9Dm9u6c&+prV zo1cAl^VX-I?pc9zhsNl0fFR#Ng-{)1%4A*XOdCf{-=ul5Te`-XF3TGm0fybG1Sx5q z7RDWnb{%B{`Pmj=w*p1^jvp|r?}!->Z8<~CG=Jz&B9^>TdFp@CVV+i_t-u`a+ zZQhRa@&n8PnGr+io$U@pXV8@4hbN;S^|O#J!trYpg2OQ^JCLb!uLKh!fXj_#9TzP_ ztc<%7s74UHD(Q{ECK*y0G1=o<^knwn2^Ur?J%i|qh8@)j*ap}q?$T{`;(2fm&U$h&+%l9|g|RNI1% zd+P-}>0J#F>~rRp&sUDJ7l@OUlQe=f0rw^-l|l?D1}TErN8$qeAYv8|+fy8cQWRXUOcEACiYo6G)bZ&qwsvVLoffC$PQ9 zX1jaw7eU_;^W|sgUmNFcp0eg_a!}%_m5ySONs1C=FH)vw%rK^Dq4H}dP9$X@p|gU< z_S5V0mmXQ^Rp>c2ZVdEK?I}{8a!p>3eZJG42v=*OW}ae|y%6Ve0Cf2#v0Vr8E8HwX z*LorpDqS`&(qML&jTk;y9xQ^R5A2R@#WF{9vDt_QyUU#{#VJxmYyJrVvwry!f$Yqq z75NVGII$3NmR|aM%vn)5o>6BLNbBFszdk0AV|i^Pq`DesEi?hBm#mrdyQ2r+Utc$O zdQ31adHc$tKWC<=#dK;W5}$W2zPDh({71C4E>jo4U(op#^iLD{v|)WlVi{uEuqJSq zCv5kRpCHN@b5KyKz5`#kux;@{fT~udUI@lDIpSpPc)@t@L(?AW=cSWICQfZ&ZPmj~Q)!wkBtN%6Y$PEQr(r2(_wSEM z@laZ{Yor{;(T-;-<^9D$%t&(p%r%+*l?Rl1$W#A-P=DJc`j;zG_Tk9}BD_&7G!aWF zJx;$``RzB>43fR|)HB{q^{B5%_9Qi2^Hrs7z+KkoDY*ie8@6WIlX@UvOMpJ!7Rwg2 zKHd?LS=F=$|h8og{0RwQUucqy- zdB*gJ{1(~{y|O}Di0e==No6keHKia;qI6X%p@u8E%HtTs!yQVJZ$BSDm0ARAh+B4P z_4HA?gv1dai~ZSM-_z6dVfsCIKs$ffI>py7WZZs`h!R- zM6>Li@*k{7qESZWb^sUo1mp-B z(4WbEyXMZ_9y#raWc4P?ufNyHeLDs5FLa;nGJSnBR72Ct9~w6~eZlNaQ~2c-3=ekQ z9r~uS?>+j3Wh;0W_B?$<=RVHnhr}-u%T&vFkmV{vpTs)6R2IIKaUT&Ur=hdJ%?N0_ zzg!BXkTOV)W5BPp{vYDr1}v&-Z3CXQ_ntk2Qlfxhkbw+8DJt==q9Tokii$D{D(Wbs zlA)raVUnRykz#U+43iWUm5h{XO6rhtl8lOqii(O;RGeN;an9isTuvuFRTz4rQf*8SYi{V)$}2Zv&Bmz!ujLR|VC2srQcU}_qxco>0Er{70N z-iqaS1Eb!tJe^6}rZ3-NXn^pm$w|0mq|3xatCeHaZM4Lt?N{f&4o(eV)Fb<9m^$_0 z2bV0_V*o@-;|seALI&d~+OjuakFoj1Jx1fMIa%$ztpr|Un^7kik@%}{KmGZY@*j2yx?5A?v5Zo( zii_xvufNo>p??c0lykxVvrA3%;LGM|@u7aZ!KLb|b?0+z-HB5G;er1h)NO6BcLW@&b;d%> zk%7FfnS};Wf-jf)WBmYQ3=NFpBaw`;LBrPcLj%R+NogYlDf}4idHWYybL=>| zxl_u!R44n|h#gB95{fpU&D>Z`0p*iqEtVw;K`lUHVdVq^S2r=iP0Z!URK*}hgf6r7 z0bAon2buI`ZS)`alAy$hlnhV@UZ>4ZcO95PPQ$Z*r2A)bghZSsGoJX@Y5Fmg;R?qs zh>pA?nA}VMfl=JH|J$AETcRF00|XF^+>G_F{!;iYN%?vcb7pM%nm+q&;V-YQ2g~gK zdg{p8J2`4g`p$3n*w^umX>XZOci{{7g8^wo1h8_V-@g(&%{!J@iBIG5K_V1ra8v8^m ze_FPFJR#%PFI%d;%aDT*8hdM1H^_!pG?8`RwUbqEzfBLfe@72Ak?KVi+qRW2NI9Cb ztzr?%gD@nQg@#tFJ)?B-Jiw2I={Xm?jCtx0gp*k;ctk9?55H(%v0dPm!pNy6JzUSh zQ4yX1j|f{+a{q(@5rS;#Gg6Q(Uy$^-2|{--ymjc8N8k!4GZ!sHKDLLHo}9geSX0ul zo-ZK9j;G$F7tRr|X;+ZXa&HSG3YoP|I35z3+w}Znlcp>rHRNn*%JHh#q%=}`j9zRn z`i5StV7}&pf)jr(EGsJGYL0k2xVNUVpnyfQR*;F`6n#S`Ryg3Q4vqQL^_cB9yM0(g+MTKHkK`ThzrHuU7R)~M^ePhem!HTTCt=H-`}tM+;Ta)x;w;n36KlK|oX)v_Ya0pN zf034d=iNb%zOk3+b)XNSQ)L1GWTA)S(5+xsvd}{>v`;?A^1)S0Hwbf^Z3C#zh67f* z7^qUjOBF`yO%)GPd7OTI{zF>(63~+cTkc&##A3ntJgxtbeuXtAz}Cfi8)g!NuDQ9Y zKy_nnmena*kQ-~y?44~Rd&wsDnYC>!kGlB%wZXdTQc7=A##I3D4J=ELe2 zgGE43j&aMIFFd{I@XMIn7EPN{TGO)A)xaL&{)3U;v!`hfu>b>PHAD1uZ?G}6M?y|L zvooij4Wp;1YgXHg2=9i?ek*vdVB|QZKj1w>h@Jl^lSzOtiW$Fvr!_a*jRLFF<{$9@ zhkr-oN1m&(U)Js9eJWL}7V;%@2xH%#q@;us5F9%}_0&{i22C7@2Yh(*j-2yrn z=VGC&)fnAa*jKc6W`AgfFijOSkSKI*XFGC_9v0{iqVq6kvtqyS7LEok466RQ8B35h z^U8?S)Y&UY;eGT{(?$B(F;Ohg?;>qmA0*;Y+H&!I`pbQ!aK-G2bArQH0+O#L$@}O} zmp4|ucl==2=^yBUcTXYH-{2PV_=ywrt`_<;tvvNES@*-~u7k&yo!+wXGPwcMTZ7I5 z?{9w#LS2ZMy@hsK-v$q`B$g|75tZYd$Fq%abz|}?!|f9#5saCte#T5NgfY~r&Rh#| zGrCd5S_I=kS0ej~9WGg@5M~!5V*w+m6KkVT)X=2nH3|i?sZq#nVA|-7YCc8By}Ai ztzBx4(YMaNZ*J1PvyW}wdK{JVrJ5}_(M-W}Vd@mY2+{x#UGFIaxgV*?YQRm? zj|OlEoLmKtYle;-No}Te>)M7a{__nZ=S>jUpynVc&mI5) z_u{Io(WBO7fsiyhOU)h?8sr^p$Kv-m@eD}|Inr0V)zkmZ!Xv`Id2iK(hk4)VtB}0r z7wBI;jBKRmUm;~F4<|<^7N#LFROH-;7h*wZCuw4#)WDdhw+{_uL&)$LcMdcK1t$qv zBLZg5bHCNY9JPDqpUM^l3XLSQ$BEU0eDP^02T|eJ5IJok z8s}mV1twYaNMH=;o+Q2j{;%c=o`M&Du4LeCLo`^uI=5>3{0HXoKQEAk@#D>e|3%}? z(sz?WB#wk#>m+q}a-F3JQn>ZhsjVTAkscK^-L>OJ1%7oC898!&s-i560pfUHFY57#p#l1q2-r zz8?a@*mh);XWKCy=sS6!m^c3(4~KL<|IVie_PbNq3TM#vM@CI1fW@w7Aa96~e0$z+ z0@85YVDnY`z3+YwI`VNBLO!wfh^KJ@7j#cOBo3()>2S&83!?8+NKz zP8aWi>j&$2IoW*!B4xkh_#5Hi#C2JH;CEIZ?DxT?{m1Lu2ad;fR(^DIEI}j_9QG0? za%1fE*Y`6{5Zeu7FG%nc;G3l$(#VZI+54tS=^GjzG)*U!a_OZySK<&|AUBlWMBjZ{ z!s*hX;cw*bMdaCVx)-Bj(2Q{+Cg|ax?sr1H*9z*5ZLP5lM%@eZ8B28ITYWxeJL6_SQSXLXz?@^;_9O52=cj&}(l- zRuvt8GZYf(3>~c=Q2s@|6mm{DE4Y(G4h_#yC zsPU%P$yC|*ReJdHKWW*;tapj&Tt00|SUq9F>I9NFD<{D(E)>gO_UyFF14ORSo+bOv zmVxX$Uk0+tPSCvryWIl$mtYP(g8t*>3E%|=3X>DW9UKTwCH=mK_l%biI{0kok!fPQFFK49TH~SH+$k-5;}2n5;EqQ z^U6QIOQLoD0Xsh_06FYN>esibZzl#I#@P$dz>okQ#-{((H#MmB+ozxYwslzwkf{1A zpQ+#RKmYl7N@{9~^PR<}%5_o=_Y&KDStkH4=UT>A^}phV8H7`e8>ap4l0NV|wsYd@PCcca0u|23opQC+2uT7={S2ekm8GOFHcnKNL8TYSVbBC3~#Xjv;&Zy{GL zB!#7=I9-m~fS3Xvw}>PW+?yRZP8?w58vL(X=6ivL>vf|jS{JV(#3z(CZF&1pS_Qe| z=L=+96~PK*X=ZBb$c*RjR_=z$fA9M}Y6B^_HDcH(*UVcppZeh$Oej-dW>ER&2dCY; zB!1_?rK1vq&!2nh{3?G8&)$e%^ytgSfbHIstbpp#Q=@dNt?;a9))elPb!w1CB0(`Q zD&0br3fU+G+u(k^m2MjM+g*FTNi6L^VC0ouaUGKyi{^n`$}V5^q$`Ys7LT}!P}q4v z(g&;yK;Z!x1Gbxh&t!4_z;oCx!M3k6)*F|jJf1*DfSOE0d5}(rFNc^Xi!bG)uvGdL zHw?rlhEN3cbJp}NJqp6O$-MN*yYC#HME|wFsWn;Osdf*r*v5z2vO)HCrDy3sW^4nO zEqvVSyAW^y$1=(5u}_b2^N?xb@VJM6DyKSm%GLC?iI-a$qphFeh9`UklZz`{noJSN z9ZMbbVD0fTfY)iC!{_s=0gN%&=fk6;WyS5$LyN~LblW#ikfAR@@n>8kt!Vk2 zK35B`_`_@0?Kb%Z1PX$<%)Zlqc+>%@oSOap2fadOe;W4+@j3**L9^m$pzoZuljzqw zI=1X9&KA?gg-;Am$au(1WG~bv@I6lU8wLw47^MW`P&4^7f2Qoo;{Riupb4kD!77Xr zOQ>#DRol*tyQ7Cq4Q5V9onl74&R8-!!Y8pPvEtjU`n0as|MicIj$x5(m;yj$GRS;vWy}*b?0UJo(=-D3?=yIl7dZo5=xsM1bQ0Lv zXoxX|urV-tq!5Gt9x-qkwV%Acj=o$lb4^mbfAm$?QT04XDIGjPQ;$vUU%~nk;L~l;^n#rTa zlP0or?c}XnCi#t5{Re<>PS(B(2yaHL-ssWhus9aEXC|ReEc{oPcs+RA$yiW$F`n4< zc-vljZY}U?_5`6Db}*m9E;l`#x?~SyqxW;Um+Z+WzNrUo32PQFjU7FOwl6AO6}#G+ z5ji@+rO1hxFPy(|BSX*IU2QLH8WlR~wyAE`>TLhf{?s>g=!8D(2|`!ck8*0YK4M7kbX!;?-r~H^FO*vC~1)@Fl4SS0}7qoF04Y6asGy zX7||DD_2F1N%#U@jkGJ3lK$KlRw?8M<_YMLga~RDJK+hq>$0skpT`SztAV%|7FP!2 zAAY$Ygt|mv5doYM%hulFv=PYe7f#4sXID=OVL}rai}c-X<2_CH6wg>>xv4F}bK=%r zNgEO71w90~-BIyuMGkPw-9OkLJ_w{wl|&C@^f9@OgW>7~=fL%Aj!dJnRe6O@kW(-9~YnGY~VD zzXlCB`FB{O~`GlVgfiU7w*aZ(a(6Vf_;HW$P^PCTAx)U1CT zx#RxTBRYj5GHGk->NTSX@msStb?YSMRw1OZL2Wh0tJVgDF1nhhxgVny^n0K>$Di?- zGDRrJJ+>h8KpCvK~XHhiRJ3YCkO!J%1fNa|^dL%jORADZHQ zZ*m`UThE&hd|kNnpOvdu#3fEi7=FB%KqTu;hJN(oLT8tfR?0&a;iI|g$Qiw9tF z0WMA4&cPg|?;e<=u$|9Qj1yn4H)BuD{z4~bVHtX}2DZ||`g)Sv2;sKNl}abA={Gi~ zqw%o*08q9|@7B|W?DWK!=CZ$-Ya`V(m)t%pGoGjUnM}bkh-42pnGiuJNm4Ut-jnFr zjn#ib1ugx7>?60WC`+YJ(_l4iN%^{pgl&@+hE&*&uFwg@54M;NR*i@mM>BATMw_CC z0l?8(5X7z6gMj{;j8&W%3I2WLUrF+}TgZ^O`*YU6|CIkEBAugT-?!uiTB?3!)cssH zDE=1*vCn_$VyYD2&kDzOA{H`?q}R>5f8U(!NovPiWYymkl37Q!p0~`zHj^9W&5|A%weaA{_*?iznag{k1u_EhQtlq(tY-0 zx}Sbk>rX0!3#vcPeP9lXcSNn!_rRq0V%>c;yRx%%Ab6ru{3KSSm>81;#~CjeD_2%0 zaoap|Uifqq0k`EC3ICMLB(wiX2aO9QcaRmTT|Mt0Berg>Ala7ULm7*bZ<#c@{M+r9 zey)cQ*e{>d-}J@b=ofV69+I>6)Uo9o?m{`5)Owj|;xV(1br!y+m6+-ne==NtN^V!X zBUjC+p(l>6=_Vcr>PTo+Z@21D>x~|hk^(RNgLLMYVcS<9UDWVpfT4+!fP zT>?(bo-$uVbITU>Png-kdM01aaMg3rVSF-gr0h;&H*P%0UZgBy?OaQ$3n>^>!jZqB=sXmL0o5@~|!+sU*Saj~K8= zh-|O_=>b3Qh5J5u{CTQ8A{e2hwjg^VcC%PRM5AvzJ$7;DXJq&j#xKeG&o0oTw8uB+ z)Z*9A`)+<}e#Qw5{-wb6JAJ<=D=vISAN$hy1pWTAofpY+qVu(#$N>A;_rBAqi%+q; zR_h0`l{A~}^dl@BS;(T41Mwj+pQ0bIeEOx0JcWGmD^(MvBK|9c*B&B?c> zuiDV2#^MS&KuBZJggJd`C1e9R*HGU^ZHc5ns8J%3V6RfBAqDyl!P}wJck}=+g~u`< zbB&!r2?ENrc76dj&xC!(R*#@oBJdjg6ux#5NKsv8^F2$4zMw6LW~A#S!0RYHUrc zzuph}a)Ajdwu>gU*?~VTLI{fp!58p=Trc|xVa{J$6l`r3c;txM)D01PBMbyYPlZ8I@+^j3cTJ_5wtM16819$GV+g}2n^v{z7V z%&)5VR&|I-y;4!wfq4OSdeiTAH7Om~6}qgbM^e%2haHewSFMtbeTl46r+YcION6Q> zO>e|bJ!&c|U6}wa;@fqdvabZD&y^A|O9;WR@hlkDSjtVIa&8L!mZ2vGkO+5Cj37c> zQ5P}V(~IM(VZ3J35}{PhBw4`qGI0SrT$D|HcC;7>zNA3b8aM^i(d?oxHIrJUomfdN z4Jq$z&7~FXm2o-0J!D!Sb|+ELK@*!G!<8ud;+PuHEJI>*TRUl7YffCHP9V!6&iM%R z>e%x6SBvKu&9lp`9kTrBj;%+IY~69RCT48#ZMOxFjiD`w2Dje%(4IA;!^20L!ouK2 zP9tWqON1+42BW>F0G+|6Pt>~^jM%aRnt|ZHVv8AD*go40;Yc+}bj8Sep@PeV)$H_Z zuR6sxtT-2yW+bdxk{*3)0$Xt|DqR`7X4T4wF$raEtExBLGWwSB6I`v&Z)R(8T;&Vil$d2#u@dW(0i=;VH$)=E(gwjMy9$vigi|!A@p}6THrfb* zJwLV=?PN!=^Qxs{W#4bsl>O&bONBVKzW=P(+Ed7Zs!*9=yJ}BSSEE))t#~%9tc+TW zk}`@G`!Ck0b-A=bGSLPEhqaIphuS9iu@}nXrj|;@SC@tL4yjm;#}TmMus895Qbini zA!)E6?YfTsOE8l@5ZrP=M}c~~!gTO5)iwofu9PMbiADb>Dr$EY>ozu^L7}n*GCD>5 zhqDs$B_-Z_LP>0#5T8sUWs49CeAtzQ(q`JtJ_8G=2hJ7JSIrr-Az;BW#%%f}69C?9 zHA!veB6e&Yk24Xxlrj>_>nDlVq_0+&m)qq~I^S67#4Matp(}7jFl$fxV0SN7H*M|W z^q4VIA-_sepI8~YcGb%8Tc-=WUD1#0+G^!GG@U5GIL~iphz0*pVi7_txJy?#iEbZU zi>m~3i0Rs`^m=}GHJx4M$<@Z)|CDP;dE$RpsRz4XP}neLQ>tQqs>+ML`MtlNC8grAg`y8Fgq=1YJ&WbM`8o5 zGSg0yo=Dtiml-M@k*X09`xOIi4v|d=jP&yIBr!@uTN}+Hrlb~Hk_ZbTj25;LsVuXN zn9G4S?T}-YYV`dZ(5^H^E&xqz{h(AoP>tB=2PVU_ZJsR0mSkm>72Yu+dIn%IFFBUl zT-u$oUYe7HEw2C|N>}5Y6rxLH@rLzQ7HRX)rBl5mRCyH?ihDq$U~~#&2$S{N-LUyG z9hk#r&Rx+ ze(dp0d2IRq8nW;)>m%o0^q)3vT!M+rGlxzKAYu5W=p3;;y8MxI&mq)Z`(@ni0n|2Y zXVQs{iTBJZ&kCAiGEE7JOAeeG^4pCQQi9?E7&gmkp!sxiz%T;h)d4a7y@d`7$**yw z(R^cP6OC&^owuv;VrE~qY;d`;ujEn)>L^+E7MqQUu(e>jAgaLM4+~8eVkkv~WBD)S zlJww*{z>nne+9*Kd*`k|l5Se`1kr!^Au$}Z7!xxtR%EO>^~UmBR;-vhb;SxWj_Toz zVr()IXGC8S<1QD%uya5Km&xeIQ3tLLOX(K}#-m2nRqs8}zcg~3Kl&7y>{9G6C9pn9 zU~DOEuqEu!^D1m^Mo&uP9Vi}ct_49l)f~bP#lJ@-^L@%~na}u{S zh@Z*eC?!!5!GV3czZ}{3n&%LQ=MZ1Xniv;8GBjp{dF1x38~43@!4OZvMvwC|8M8_r zeuy?&=K1^QlBxN3jP(h;IoK_I!j}8)+zL{4Por#AYg}^pI)eFlaC%k%FWftF1#y1@ z9M5cFkIO5W`7dXrJhS^$JyEj}Ej=Y_0T$`Dh2z)Uk`q!cTQg6u@?P`8#tYA7m3xOp z-Z$NQ?yA7x;;>}QChyBRO8X$@`)770$gx~P&?(KNv^dPt_(AaxMLHjj59#ASTUxCE&~Oe>YG8D}@UR<>3ZM@*lz zIMj7q+Qi!;(jT}3EshnoW>7UR>?l@`{zC%{C@O0dOr;>|MgbKPr9C*d%vWmQZEHI4 zWpDtoTYSf_W-g+rcwH*22=s70JV2N9UP>B{f zhi*r$uT?ba=+}vS#gbL=+1xsgZNkLVi`gu*1~k<*7j*{2mm$(%v~F z&@_E%;(;|m+UX=kbO}#5@&TGeClY|QK*>kDbM%Cm2p)DhJjNMih_N6fT@g&8tz2;i=JAPp%xTcEglFSMJ)T=1BjBGi4-ilN=s&3WuMMa zi#3EIN)%KJQ9>)K!cmntirJZ7F%cr1CkB6av9~5CM`>#9)8*&&;eop3HZ(w{A*Zz! zN_@FGfq+CKSMp)YX{<3*Q?Bf7s)cxU6Sa^kF^sQL1)UAMhGHqtLBf?zkWfoa09PFM z{E8i_!?HCZD0bAu2NvJ?dCexltT-fNVxl)<6VqG<`(`x1Y~yYbi6sy;Fy5qQ9P`fY zk85kx0rqZ4rb$W;O+v6;t5kH^)zFM6C=fpdC6tv2fvk0?qkNnO#Hrt(Mmrf+l@ioJ z@-Dl{UDT>Tf}Sn6akQ0`Gm&BT!m*&f%ZPO3X8{DdG7zCrD1v?z9PgSpoALJqM6wHI z#Je{gA;US}(w_ulNbFECWW1$7pI6XRz(OT`VIAGflt|{MN;DUmBL9Ez#SuP<8)E8v zgE{c64b5C3p2Zei#Tn5CnSNl8i}V32*^zF$8(~c=)9-!xIziKGmuE1wzUS$OLTBpw z^{Ej4QfIzFR&;o?Aj?N+`zIN@HlMb5FRI+IXiYu+X)l(ydr&4L$~1~G*i-!bEeR*M z_!|HU#SFxN%^lHTIf6wWoH}F8!lAq2z@-!c;F9{X2O0p9Rz>qX)&?vvKENfigroX;C zc17F}ANk1EId_g8ZW@O8ob0VrN5+jD`$XKK7Z#2H(b|_3l+nw7P2by8ZQnF%|Dh~F zCfVqEaS8ih-fb>#K<=RuDdg%3#Dh`x+dNbH zL@fI2{(Fw8SSAE7*8jKUQjFMFhgcX^WdmjKLKFB8Q7}>#(!TtGAQ%%{cEW&_0?Yn%2XwVyssl73{L7p)5MSwZdreK42W-Xu%E z^lK%{-k~-G*7(gjnInwCf+7v=KfkX9`QaSeB~svGbn1EF0?aYOVP$~O{zLOg>YZ%)%#0&{4F69bV7rylyQW`(9x3=Q=zJ15b6XwpICZ<)M+_Udg#nkz;Cll8S zeAc$pcI>m~B;QA=yRtw3yys-al(}=K^y#i_@8XAI^XzGI6BA^JSh#@B+WhFFo1+&k zQg32gq8BVs=*GwS7Mh^_lp??Lt9@{kLN`D9_(mM1GhWIAdgL&4af{|gbZ~F@A6Vua zESTLRL=qem=ml&PL@p8oRc!YN#9|;I!@%%TPj@kf7?e=A>Y_}+Tr81h8xjpcR}A6G zw7tl?wYS#R;AU4W;CdBHh)VS%-MKAR;#(wRrzQ}Tn+iRetxB4;&aJYylav)AcUKi@ z_kjJEl}#Y(M%`> z7>zg&V*;>1o;=D{L3v`P-qj_Ydmk}aPa7Df`iBts8)L34z~Gng7aB_FkEf9qx#R#D zRC$s*4m`Y$kWCLhkWEOwaGR1QR*IRAlF;vJz9T_JMf7ugpv{jG(_fA_Dyyq29d__; zh)E|pvSp9H95BfHQJ!#j5#JGOJ@VX5)xjvYr2@7!_J z7|%2{X$=rCD_$M_aEj>o78hv81y~vhMuEGEho;BHe#J$rB**S^s)rm?XA)mXr(`L~ z*vJr@qxal*Om;Xg;Krhv9sO4!y-7e^GW)7n0@vx9X>_CL#iC2t8xP=#2?AewoXFp} z{SWM9$}_jGC?sUU>{z65m=i}x+?+}Hnq=B~d?6tdlO{U<%vgE{czecii*GG6nUWJn z-8$TD@SwqNfkDH4hTh=fGSa2Yb<&)WktX-S(qQ9_{-eD7yoVWv$v&Rk_K&A+#u`*G zGsls5&fpGN6?2)IUe;bl3d)VpMx}*B7fN)^eHhVzfE4yMak>r&SpGogdYXmgLcv5W za-pM+CSuUb0dk-1+=Z~H!Cn(h7Tk&itBkBZbq<^*9cR%EoQ3!Y_oRb<4?$GB!HiTT zwKz*R&XPND79>5q+)aGNOtq<+okd~0WIE5H3Eaa0!`2`@1wd@e3-W}xT3S_B$aV&t zkyu}k1M+Zy2hPb3aQDXnxU%7LUab(D2l*Og*iy>Mt~vnAGu0Id;LG3Js2@{VKL8+zZQz7Av)ZKWf+ekxVMz)HS zxl2Zk@eekMEe9VeD9+BEKOPq@gs)-fphS))Fr0-G+F1NKSLfEMmOoOCSV=3-WD$~e ze)}h62-$M(gJrgiCIl<*STN@vLe@ov+;OwX&uXZgx-Td5$l||$@zh86&+)GDTVAo} zv90q{GbY?Hq{Q1$3 z`0?aPMR>h5)+YxbVND%r+x?2oQdLk{ z$Kbpv(eooR=7(kY=a^0yz0(HJLu)*T4X2FE4$Ee!A@%q*&bK=OINzwz4Cj;J2eDGS z^FE?sRQ(_BgsYzb#^Po*H|#uy_tyOWJ|+$cy%h*qVh+cwY&0GBAc1do8}5K% zHy|_dj-q!$R{-1k@mYgrI|pme1-&{#$!mq+Wp!N*W84IiJ{|eX;YVI0#Pddn#}FUM zdIx5m@SDfRj3DNlw{P39kC2yNH{?QMM%Va9P+Os=UGK`V#vvNB6D44=rKmFuEx|GW z8GYWv`?&Hq!OG{Qv4n29<68I8;elcVD-w&D>!-Xp~H>p+vFrvlJq$Dk5!4#r^Z z%YqwOCvgJG!@Zb5lIG@NmJL?Sz4oL7pXY9T?Vh|>)-0&FXUU#LgOZ6YC2cVwi<45) z$dW&~wAp^s}ru9&t|N%Z!p@>EyFt3BqSjS2Vcw=>|=Xnmkhw7Q|1BaVv%}#|b&}<;SWXdpWNxdepqJ z^%3($7h4If2}^<-&;tOk_n=ysI!-CF0I+Bb!7(w+;#b@hp^H{j=9bm?Hr0~ILAAX# zQX*p0a>|SATL7Y`<+f~WFWa&z(egWl}<|vW1Kt81=UWI zYz{+geZ7#aA%8tq6vGeu^)HH{yc9qp0kY62YS%HPqg5 zrA(Y5mgBVmQ>S719^r1#jAERGkE=>%->)12P8_( zdG(trF=pV?nH zu#!s+Xe}L$5etMUv@(2BE7f8lle*bSm))Y)v9?LvE zI7BhSg*`?-60iq};gNf>+%| zz?p{;4Dt4zowh*nb1s=P4 zc!?MQ#>QrU&;FP^t$nkW&K?n;%M^2Ey--Xur%sp~ZnZ>M{d0oLx56E2IW1dXKa*YI z!5yBnS96D_(?HTj6}14ZZNQg=mAtlA)!LjvjmOB54HKbaf8ewmrNKVKV`B(&C#gCy zao65~=GS`*nqMIvmnRs7tp1#p$L3TGUT1aabIWvRCgzXeXC)EU7m zRs?(bK~rJTwBKy?2%w)Sic21t6289UMrv=bhfZBRa%A|(kc3q?-84ps!sskQfjybm zpooQ>AIjto$OkP5(;DwqSPj4?xeCVU$?j?d*Y4iaiwJq`{A+cDT&SIckt-%S36C#Y zUq`aD5kld{tg~a^{Ot&{%{I+;o4&(on)O=sAv6vH2XiMn9iR~`^A)TOZjqzudNG2t zHu2&yDpuw%yOsmgSgGVoao2Z%_J)GUSjYzo=Fxy}^I_x2gcvrCI3MKbk;zK!uKk1LT3Hyw#*{=c;t`b9NIXi8jveYH6q(#PKGcSvV|0uqJ^EeYdQKOGfkk1#OusVAeM zMrbrtF}OFWyd{Ajm~=r(naDPuEMFV#;9PqVR*P+o9-H*E=*1$CrI+-*c3vNCrwVi zeP`7oa^ZX}EdSSP=R~7{&zZ-j=_>_}PT&PrR%s3Xck~1aik8gduhp|J*N4*4E7EW7PhSZ(ohs2x=y7s3-Sq3Q=Kd&~ zZs8NQYq9ADHoLl|J(V#sWaRh>ZkEGq!o$bYXI-@|#e1XLAZ9^| zu@EY+d8!|ABgty{sIF{NX{;%yvy-k7tgw!|;AO&aDkTG7;j}%t=>^@Dge+cxc-W!@ zw<1Xk1e&O}xcSyd!5ZpzKoo%)m=#zv5c#dhEZk)Da@4sm=OWwZ#B)ns{587oNS~An(9M z{2`UVAR(yDvQ*oE=9ta~lFyoA5DJfF;Ye6L90CiH;)!ub%Y%fxO)I{rq91>A-}}Jq zPPByP{rin=WbDR_J2u1`g%;&;RVic$z_A4vn=Y9l%gd4T?tO32vmYXT?dSKr zL?)4g-<0p7|Nicgu^VkOvZma+u)hp#^xdbNWf)A(6U(P2nlrlO8~Wz^W!9pk33nx# z?_Q4v5d!r4QPc@*8Y8%s3)EYLCmKLIL;42-C^S((&D>x_FUc$n7h;tqWgzIfCPR{U zBspA7zc_n_n4UjF0-rlWTc3|z8kJ+2xFm9uXqS?p7*(nd?Jc97`rJ$HPZOW2Dss!S zC+Ro#)3p6GsX|ghYv%aH@fmmGdHSf8hCMPX)-YZucXt;7m7qm$Fa{=L6~s7m5_knO z`Y}R+#5LdLtL=0T;IcFfV%aH^R=}yAH~m@K25Eej)8-Tjewnn#6RQxUo?m?7XDDYj zC~ZQP*m>D9CN*Kry79}*87ryfH1R#niuW}Ay1Kqb%IeX5PkZl#mxSl?HkLpS0^TH@ z!Q}`!t$9^IHSp)#F~jifI|p2%rF?sebNh#UdtG0b*v7YKVY}>d_5^DOlLJ9%l=9n>_Tq%VgrG+$R`{mM_}mUl zGv#_SJGi7ySxC=dwh!;x`Gr|`?%jLmoqL4}?Ne4^VavlAF-zwsk2Qrh?_M)@acbI_ zk>Q3Arhc2cX%jdn*%rKnhS-e9ktlS==(uH1;2ji6U5D%~#BilkPQy;e?e1qs@H>nss@j7QH{_Ppe zzdM5;&QLUPhA4OB(iO30V?~IG5^5??0DB#ox^{BjEOeL95K`p}w^(y=+P)0$QvdDI zg(yWF&cWzeS}A^ej^;md4qYL*u3gCf>c!V9*Q|TcQh3!wMTve*yVt}lo;z=R@K}5r zo0__4j43Q_@3O2!KbNrhjK_UO`6PzLWgJ9?Rx_>6&E4md=XqCH7V5kN*HQlPbwdAd z*MWaFr>_gcV;TB9>#GJA40#AM6Lec-H?fA-72G@8GuK0glsWW*g2Wp3z#`T3ZsMPm zxBMa55<+Txi;D}RxBHixUc}+|;qdzF9$x=n4kvDSQf%_m^KQ$Zl%ByX&_NyU1%3nO z@M>C-yq9#5plSV2+t>g3eZ_^-*N?=b4#f$eICh;A!29=KPB8GCUHEgxg<&z{&dD{O zIBDzjOU$1C7>U2ub1=l_W`*75ndiMn*!G)ee$|8iV;AcDKVAsqFCS<5lP=VrG*YuN zL@%F>GgoDkL{cBD9yKm2S~7TJ1D}Po8j_Sneec}M2NhuwMin;B7~R_2Y4WC2Z7h-a zK!U;Ks*^kOJYH`Q6OmZx8bg*NizLl;97urnLVe!X+#wRd)f7C{#@ zEZALgUz^ z%fM22`)L`xrIzdJ574j3&EzwZnp(DkzE8LOKu@U3pQ<;K`Q(k?ND~Y;t~hF|2w6C3 zTSCRU@+Bx&DYzRU%(O+hxM_>`qMi~oik3c$Y(j+Qb%+mFlOXg3Y9cKa>`L9y9XpSm z*pXWydOQgV#EBg{pENXI$~We#ot&4DpRa^4PC~}60zCLK!Pn>~werc>ohi+5ZW6MC zAOfGyj|NNQO4|Eq(cj;DXT!=fbL^R9ncf8d|b5y;f4vXOqo(#P`X2^#x+H4(~?Lb##bgF?#6P9Y4?IG7< z54LAMzGF|hR^OrRX)x-uv_180e2*4wPf2ke(Sq&lhz=3w*46jfgm#0CA7R(7D%lNC zUguX;oWCeiBR^N8_Aa+;d&=$5F_d-Xn0n%260=$v5OPgHdnb4ej6h!Um8`?~%se?7 zaQy4-<#Ks%nGiza*57~s`t`dB+1>Ngz{m9ipQMO^k608IQ)Dt{lsvzC*RH<%?$vAW z?SGea{h#{Zanmh*HZk3h#H&2iP{(dfYLK8PjYkkl5GLcS7QP3nL)*iy=MP7+iw$f~ zkW!m49wdp9P;Ri|=6M$v%4wCs+FK!|quD`nLuwcN7?TvDi40-ma*zfMrL^^be?@lX3u_6g&*{coLl-(N3!+$rv;QU(gN36;5HHP8 z$7k-Y3S_M0AQNkH-6D47d`=HHeM~lXbd$Ajs2vA(A|aWr!uknGu)jyM=qJ!`r{Da%thk6w zA#*EoPc9GadtmyFG zlZ|(SK2HskA7lykuK0P=)?$QQ*Ow5JHwp13ekHVl#FfxiZ@HKx)jEnx9JM5g z*3>G+#SYje7&AcZyP;TW0#{ZyeJGPu*E(vy!20#R;W6nnA0qHdPD;P>RVU$*UHJ+r zB9AvZHjN@4BW`jb{u};Q&%X`z^>QVnvcIdp(6Kpy{RXKfe4`)GDB4HJ%1_Up{S?6r zefat=HbzjDkc>~CeeN^1QAZ<@(!;qc+xqEBLR1>%OUlPC-M`Z3=9_(1?q52VefIXd z^4aHRKX103S{uo_e-^#FapSv1|D*>XdVl?I7=giBMy3JKm4%Nu(7*r)x3Hump=fh-b?xr6;MmN2yqh+YEyV=Qw{nh0R++bOan5kAO( z-|r4YNoQhhpHtkKmY2UF4Vi#i9&Xq`7QOW>4MixG4|!R^bkj?Z&|YANO;m@RzcW>` zpU7nOCxVWe9{yk(Nxb-Hg1n|{fBG=_%%%HDbuYc0wkCZ637(!uG^o#^yNo2hrlc>( zI%3>UldWAX$p_Sf82UN^9WosnR&cN~SAsgoKsJW54HTTIGx354xN`)KfnO{uqUL6k z(rF0T!GEgHm{#{1^QprOtm$QiKCHfpe8si(QrUWX$d z>x%bvF8Sq;(c@YX0$NM#j}Y({jM85Y9jus|@bsb6Pa9N+=lf(UpxP0nd>hsGFe~ zp%9v0AI}@lqGot12`0qs&-npYgp(K^E}KdA;_v8N&9t3vX#Si$j>umm;`-EGRRnug z*@?-M&Q>jWqL@65?{&>(-|zkxI!$;!i6npi$7}S(505;xQ#oINDQ?U|@AbM$pb+Wm zFuhr^JduE>v*36Fe**6+@FYN{MCfj!g-R@J1)aW0iKT@tLKlc&q3|i>O6ixAWP5L= zQXs@41F58ILsyLC_J|K$TQgxf?#>G?vFW;H=*8Q0kD(`{kk`Jy zD?<&K1=@Xx_{39!{Pw0X8#$jn7y+Xs<}DZ|VL)Z0E%P$-F3t0-I^&5VBHaD4ojW=( z3ge&~oh}A{P>uZhtEx@#Lsi+o{~ig8KT+#i!mROkFKg(mbA0u2cUOP4HIs(^YJ&t^ zJES_2I)5PNNH~)f`H=Q)JwT+Bf^RAGwyGVQ-??w=JNX?Ard6}zmWQ`wQr-5i&aOT} zM${nfT&v~PlXn(%myz_}*Y$UF&LzS6t6HV)*Q$D2dx^Y2ZptOYiM^+#k)C-w?{ivr zMq!Uouud$0GWpS$EG=#eRwbuR)l=7KXt=$NY;$DHuI8aBb=6rHU!4G>!5bdJ28cHR zf1egJ6f9tH#GpQnXuKktLyH{7L~jdW0cBWmgD-ejPcB`c>7R4 zQkI+h`Yv_IezX+nS`99r^_h*Ou*-Bv15j;D#+J{S zj}vka*+?sx2p|9};M?&4Yt#@!3-}ELgK4XR&+Lb3_(2I#O;>(F-1fjPeY&0`=P!0O zw=WCvDB8`Lv}D&(3FIYzq%XV_Lec{ZeAoXgUR&c6A;oR)=e68+iTucUW$n_XRR-()Ud@TASnm0z}AuI{U0OV#;N!zJ+T*kH&g zp$IbYxJ#tcWfc~0aH@KCT+Ppzi8gFyAy7r`5@0t z++Me;TD|(v)3A zLl67kfKc{!5?6;PsaBx`X^D}h0P&ix2>Ftw1a9s3e-C2cc?ejdl8*CPk*c%<1WUa( z*E0=t5qdm`o+0{DCALe*yZ z%BQyN#Lz4@C@hB~69*Wd5>0ZfS;Xq^GkM3$JE&VTy}VrqmmK5}YjJU@pTPxCgh4)B zR+O0?k{)m=vr@EnEkZbubToR;yZ_Do^<>DtN9i|o8U0t>MKb18z_K*kreF!mipDtA zLpn%?x7_<@dbTa8mA>%f=}DwShKBz6r1V3}5)Xrc*e*A#*)FU~*i7Wi7S)?yM!F#L z<57+*$t|U%xfJV;)>6viQn8XVcJtuCFWIw`GX;r1Q>SlNmR%mkcD?Qp_Jg`0$9`B) zWDsAq2|9n+QpnwigyAr(Fe^E&IV>NDL9^|I3P$7DO;SnI*Tj5=X%R2Yp6Lt4b-`dvZxGa4IWG zyNeYTNoIGeN;)g*#_N|9G%)3OfD>ckx{e)sH)F^QczUkLprRtNzup4UZO6*IeLDMWqRrApCF z=yKklmFb~De+rk9b*h`v1wu;*1tP6WRMYfm^ze2f<2V}9R$Vb~NQvMe)ba+#w##y6 zA_D|W00i2hJrE?-^1U|*Q~2K6_JV=!nL;9d$3M&?EbZlj> zCvX7jliZ5eG`tq$wFxf=a0@rQLhy>mD-Ew~ybAFu#|uwHXu_+5wI;m0@e0EWg%GWH z<>FP0S0!Ezc(vi>Kt;RZ6@nM``+vy06X+Au3gntorDk(0U?kC!k|P%1wjRk zh=Kw#h!PbgG8rX6L}nsH2oND?z<^9jKxC3hW(6TCAR-6~C<;QDG%5-zD3j#=cUR-# z_?~m#bME(k-@12wto7rmuC8J4XYZ=6+TH24Q;>mCm=1>Y7?>C54s^heWP!Jw0{op- zfU%E23$R>(Tj;UBcmN+tlBs0h; zYOoh_=nXgFEbqwF&{FLD)kR^@1$ za=kGcGq4&v@GZEqDxb@cN==*)x!Oe>$kWxW@g#=f6>LEf&WKcu0p+cjj1**Gxkx3- zSBdgfY6x0yjl&a-W2Teg+R_zP&TXi@s*$T| zX%WFj96u^HsL8o5s7FKrW(&=hPlk!>dCf;LYq1pOnC{!#rVP>;0z)Ori_^IBix zJDe4{=6Wm@Y= z?QmTXl&cQqsM8pld%*WdpFY`Zf2PVG2qy1kc?Ce#5s|Mv{^&i;Fd{P z4EoS52k@K7tg;eqxoRljVV)O%Gj7P zHr^?6JNdbt{M=4{ZYMvtlb_qk&+X*r_HR)laz}Ywk7gi0cMQNwn2Qbg0w+Y0UBsaQ zTH{F!!z);V4{;E`b3M@kqd@uZTqV+kvNxgZO(=U4%HD*sH=*oJD0`DH@iTXcK_uW7 zu*_XUunAw|v`EwL7z*;&l>9X%e@zd7{N3FU&Cwa;`EHKCyJurP_Tr?-Js#q5E85|C zOu<|D2#0W%yS^(xe`wYZ^xI~KMVfQ0HlK#&_!vKk+^bOuH-Ylr+X(|O5%jHlDf_+q z!0~(EF0f7u)@i{yEm)@o>$D&bEe3&nx0s6!A}y&~OX}8=y0xTkt)j6Mg`h62ibd`x zAFWHFI_hH?wt}*@rmwZBh#NuQX+uuh&hbVi8NK^xD z-Qi(81@h2={iLv;ln+E6?l1C)4eI+S`*@UnJX#mck&1!H!M=NJz!x|nlJ25B+JbgXC#UK3 z$8`E*Px9Zh8mL208n9;%kguN8uw0~96zCVd=oh`{7rj`&7yY6a{h}9rtry$x&Gvg& z2G8}TjeC#5Ea5U4)FXqwk?|TP%l(#QA@5}MqcM9Idr}$2!-?exS6R}97KYg`7dF@Ym`jglG^wIt+up7tu z)j=C&L0Jc~?m+f4C;^ml5P2KKeg?gR&qM~3hrzVVkZ~fJr9j;?>!TIAU=YS*KHkHZ z_*vw+$3%uwzoFD`DES!1eugC@1(a(T zj-IcFmgtI18DPW^|opW)PTIQt*I3be=Y1Ncp3L^NuET#aDa z5iC1`WwSM|1bNS<4YFH+dS(}ijO-&aiu#YD{-db>DC#(heUE-lB&P|!6?uVrzCeF{ z;a!n2Y-1C%YVL}Wb2#`x>e8lCWi$ON|kGWmG9 zGuUoEImxGgO=O=F_lZnm`;+Jglb*(KOvN&6#bKP|7LYu>LY`h30@`izotT9+*okjI z4yKev5}KkTsOuE=GlgZRyaVbs<$IB-7AoRK+=tE>j0sqP_rY;AmE&j{{cKu1Xp?CJ z@Dk>Na!j{D*``yr>6C5y%XkwXfV#aJi+X4Y%Jb?Tk=JU1a?hxP7cmFxK-p$cwgSpl zPyuz(9I04_t)QP2P^OubY33v7gE5#TGOHTsYqRKUv)KOY^uO1~;&rSQnOz#y(HIYc zZM|^^o&a^4L)*=v56$72oAaT_T(&irem3teJc@o82kJh*D~4h+DBJvPpbbc!EQm)F zP`(A3cnyU(Eb=D#dXt>INgrNF9T(n+`@sGdF@F*5x`^^DV!w;afgCN~EwY5NFX;$! zu#`4lIvt;iyj2mD=dEKR%Q&8vQIBOjUS0-0@s-GmXry4B$jYi1D6%RD;%WtQvN{I^ zSR=BAvcGL%vdBBN@G1_8tZk2TBJ0TMyA?qDyi5DMn+mq|ZXV`=y1u&?w9|SIY-2sk ztxrP^=3xtpMK*Yd$IT$$8)&-?FM>X?VY5i#bD%%HN8R72Oz%_Q_cwx^d_a3_yc+Ch z6WiIub~ZIc8>Ar%lSMXHMmn-V-ZrlQ?X~$RE{J?cK0i!CQ?Q>82ZQxKTm|;?;V&Xv zVnADLX^E%7wztr(TUcfb%Y4MK{?Sw{!xj|bjL25j+gc0BNI?d2P=GbqiDHqD1r<;i zbfRr6yX`HJ?bo9j*#35oz3sn?e8Rcs6J7^)G(tP1Aq$hS6kAY)Ga@@)$x zPV%smJnW>bJLylKGXK*PBA;~^*~Rj^Sbi7F?_&P04CJ5yYp@f=B2-y+$Ajf|ry>gl zC`6IS9zi@Bg6-^KJA3j(KGz^`UzElT=z)M3A3-0yg7qR_v+u9J7db%xIdC&LE)Hw}`#DJY z4)(xGks=4(uu0?)$HbwLB8Q{U4`)P<@c0OQ<|yrPbg9TO`opnZBHtunoyfO!L4Pe~ zo5jmTzPl1Hi+mr6K_D+b^u{kD$CE^U>;M}6r|NK9p58sKL=?1oUhBBXx0e$rBd69F2`PetxJih|0dtn;y zX59_)dyz6;WZM@D8MsV_i92x^zlah(SIl3bDF!?g9~Dw!KKjufUC|e#FbQ+83Y&$m ztik8_NtDGp)_o`f-+iWIz_Ru|_*Rs|S4BAkQGmUo0t{~k8eoek_YqNi@LqW=&zH0* zZ!7kL?b1zDka7jnL`Cq{b;QHyhnaW}yo(v}qo~Lrs^dk_34e=m)VjS3K zX|_@NH&HRQz~2$Xq=I^0!SYwcgU{SvL3yv33YNd(9c;q^kb^SS&;h+M8gGO7SJnah zxpFf0h$>62%Esb;EJm>?#tPL{l&u`?%9x=l_Y{VJT$iH^<&KIf&wAx)zw#3>8?iHU4sClqxw(EA*j`=w zL|w{Sce|(?8euC=iMo;P+(bQYq780Zj=lIpRK06(H!?6B^y7M``TPQ-{`Kj1^(jOB z#%K-7P@g>3r>yl~0lBMBId6^!`M;TZ-b~qVrp`BW%rv+TcYu5~=z@Nr{ToaHb!qS} zcHj_x71fZkHzY?5DN{po(y$def&ScZ1SVoGJ{5Hf%iPiiZ-~0J7M{WvqHcQ>EZZm% z3qk!G?Fa4ASWpH$*Z5{M$0JC`P~>42R$w#8QRDAL-F_vigZkc1eebvyD9$!stAf~Y&ALEi2pZ+EssA56e9dA@255t}$3?ZH z&D+sW+D!-j?tzNvhF7o!=R~!yjlQ67K1e@#kQ_a@4ktuC#C{&4zjcT~6SPNn&<8rO z&kn59;iRY(3uRFQ4MF>*bVM&q0{coij0>V3<`{dp9v;I;EX7x%9%0{)WQuy!LshiI z5>Xwif%0~wT^=h9%JJAfQIBsG)rm5s(g#up!%S}y^+Zjy2JQ01DN$W0PZy5kE|W!d zy%!uSUFp-^C}X#eM0LLlv{iR<@g&bb*%#F7$=^h!(U;Q5UmES7_O&R+X4F#;;5Bf( zKXq2r(~&qRsz+1M$9fFJ1T4g66oGB0lb>|@PCCb6dS_7AbaInU{nM#S`cYgI)w2R_ z1oiJpu6t%-DpueV6pQL*Ar`c6uQqrZ*`Qp#*5EVH{=EaB5BI(m?Lm9=&cQ6K!{?y= zG9r+O+mQnLR7Ni5pb%f-l&EK-Q43Ab5&e;e1=xrKI3uc086@Ezq+&4gu^3x$7#Bo6 zTMl(`AG%^FCSw`4;TutX1r<>b_ahC%F&(S06W@#KXQK)lq8-vP3I%uvyYZ8#{vN8L z5gtMY#$YzqV=sOYH6RK#kPOZ(0~Ue)HGqCF;IycL4%&jc4{C-^q6SBRZ4bT?O~8H! zewg7b;d0f<}A$S>Vdo<^y(HtYA>!TG`i^`#VIro7!d7%M* z7d560J_LPv43Echtc~SZ8aogiFJntY&6y zTu*Ej#n_E{@fJ|d7l-3*Q7_RyUV2ragbb(>0lr^TW*dSDzV_w?sMzNgbRueQSmQLkMG%JJGsQ8PHN%%D6o z=zj&*fWA=h64>|5yYUz(+f0_7MgN>NNYv}>=XLtyY_>CdJILqkA4R>Pu~5{Us@NoI zZclt7YTj*V1-8Ywh?+N2)ci!$LrZi9Ia|;MALEFqH|hUx(nl9oLKaR4Uq^u}Q61Cp zm8iuPk%ULV{uZuKNhv=w6| zig6OP;dfDm4~lw^HhYi${2t|c?>ABJ^Zfe_aUVK@eZD^lZ0~*A_We(A04)1K098;2 z_oF-dV-)C%A8ZE8Zls3 zr3={37V5NxI(_s2PK(+~oww3Yw*D;YWnZ7NuTNK^NYrO6_ZjQ$Y6|+u?rNaz_mI2K*NXar z<@VM<4hpds#W*AC%ND{P?V>T#@G@wVuYM4k?51=r;%M2Dv=g4EsbCQTL)}!{&)P6hH&q3)VkG{SL9uLsLNA4|C2s z%=(Al6?LREsPB>6Fc|FP=#!W&>X?9S99tyn8}|E6C+rdR?Ui^KFJUuikK$yokK*m3 zzPlE$iTa*%`S%}+`k@lGiaJi;IZoYvWd4s6Fbm5-pZ}>BsOL|t_tOfH%agV7CB75& zGdcaaJg!GGP?n#`*UzU!{qi8ziu#rKE8F;WK4`aJPl!5I26utJbBgVr;@CKS9mx0T z1vnz=H~QRfZ;AT72B^cC)}qc*$FnO%oof!-_&j-F97J89o)<=92B_N~L9|1TsEg$2 zVn2|N5^_+|9p}Un^ZV`R#Zr8yt(q^E#aD1zi^b9vF%(~kWj6rJI5Wfw+yMS0GB8mr zw-#>2WPAfYn(viC3-E(h!3S|%tca0#6?{)d#C9A3+l#yswebjE#`|JLF)xZ`qYjEy zDgoU=o;ZoA3}={)*|?0LoG3YTSla*o7a( zy0SH9;yba*Qungtq3jD-i#;e2>#C*r3>U;IR{{L+X8GorEf!-1*3}u9fOR+^R>itv zRca5ue6mtsuzaO_v0~Y8>`Aez#Da3iImiWNj$b8KRq9%mJX9m!)!q{;u_}6j{Up*3 ziPRzSqFB|-;|7rD>RDjj>c5LsgU2-)NUKMJztYV$~joiDA^UHpjW}(K`F<@^7A=-Klh=(^84yHvaAZWi4ZV zj7~n-=JVf+E@Z|&&2S?hn4@o*H6-$sN;c6el;r>KxLE8JmQ?c!FP_0}rVEkt@0agi zuOC?;$(M6eB>Qq+1xaEMv&E%(USG)!u8>R<|Am2}=VBx~Vx6?`i2qngW^}$}8-cz5 zyOgB;gKg)TdBOHFosrIDewsHwVxi5dF%ulyZGF`~mDQ%RzK$eUStdq%s{gUq- zjnl&++Vr-IcENWhziok%b{&D{O1GB=`;UDNx`4OwvCV&j*Qli6UoGb?l_K+e#BR!2NWZ*Hu+iT?zWllWf$5HyZ^P{C zuP!|wv`hA4e=t=BM(i!&d-O{VMkJM-jA&VMJeX9%`H^zwNbIHMFOQK6CDY}Y_|rg+ zZQ9#B4su+08+ks3=l>mjIVX55J6mGtKW+X~ULxz#fB&VUe|etcBsiV*i2pm8ef*V| zRdSMC{`p!M3=4c zJcf|hM1&tlmJQ9%=BpbM`MIP-H`Y8?66KSe@H%OimN!u+Us95xlS8tI^TNz@p}$N? zS*90a2#-7KG-<*#^De$m9Pqd8nC+DmAp37Z^D^k8q33uV?qcw~*`{HZF9@VbE^U9A zlCpn!d@5X)B9_nA`%Bu`!t&JBX?Ss+(@3U=%W*9yEFt_xU~@01Cx zXVxiT*#hYwS|>Ek@&zSlz%>Z3VJVUuW~MKH9Bv2lo{#2|8>~QVQ_}wO9RIWFh>n!K zh(353x8C1BzWllW_34uI%eP_n^;efa|DQ~AecQ2A3fqgPPK7ev#J}k1Zlq5aiTRPA zOnal``bx5iwYU!A>&;|^&Fkvlo|8IEN7)-o-XZqBY(DFjEVYlbjS#l+=QQgaml*qa z$$H|mm(6F{lBHZ@H4n)O$4ig@jp(l6+|`lm%f6CiXMso`O5%FTOg9Y8XP#5Vf9}#_ zKbHJr_bu7YF`at}*KgBZ`l-8(znr>k{{Q5(*~ec6=1I)uf&~)$-wEVN;@|m(Ys&C; z+-SD9@$a{J>A8P5y6bq&4GXQ4Ez<)=Uvjh%+*5@fUz&ee^q6LuoB-Fu;p>V_FG|(& z)^QEGLXJno$QpidBAaW{u3VSL1~a(si!CV(?v$pH=_Pw3YnAMX;7g2o?LQuoTe34^ zW6927;eWr3*miLUsiA3EmbZeA36-ri+eKGCtf3GC-+e9q`>p9PG47$l2lm7<w5vBjUyU0UYy*iusd*}3$8YQ7hv;`B-W=KN$y`EZ=>$I!f; z%s;~8baX{3kIxVX66azsGO-Z3+~d~^YGEKT@%CO4QYxsml$_5YnXmroCznG_jeYg^?R9W;voq9o#eiH2pT0>#5v88~SY?eI?0154l~# z^9xwsOcUwrxsf*0YnT^Lzt3VjZMdF7#8Sp5l6fvk+6S9T8;;L5v_~$-t-0RF3Rd8F zYa?0Qw>31!X@J)db05ZYrmxm=m&!JW@f$Z!X7e@o`J7i$I3M=qahuYy9J3WG za0}HM;xgoio-5@1P$;8Oglvq(rR8#&&csG6q@Q-w#j?-cDf>)}$dY{lj#uuV(wI(T zI@3Hh(cUX3QOn+|%KCUM+#Ax;MDFtn|9+XjdoG`P#qjCCZFUZ?;eAz-ovIp{X{VvA z3(4ZOvXC)V@!8zFhvgB!34A46P|S3}Uzh(^%UTy?p0!)jtc8+c)swDvBb=0ZTtB2C zmFLrRmaGVfte`ztu>Td8KeqNthLa*0%*)XkGQ%s7CG`I#X8KR&IgS?|oB2z0n&kia zTvy4r^FlJXx5?*vGJ}3p{1?Z0e7SYu$7Z>0*ogmloxnmRPC5hDjp*z7lI3L4@0Y5w z2CofSyncRbm}!os3}OcL%Lta^adQl2oeCWPvz6^kmc16QPh7{P1h(*H(|MfVcFU`g z^?03GAw?V$@!nq5-dx#`7FHzt>AMJ=l;iaM0w-7IxG^f9I1_K_$+FHaR22|!mf^K#nq92YId=Jc zx=^e?JU*L!qsX~7L`I^|jgfk1&q%Fv<%#tz4N(J;g1+; zdhP(vC$Zk{(DHI2Mw*`=BaO~BVwr5#o5y_5VywzgS=_&-a;);n@u-b_a|4gfxlk|U z`9hxG?VlehTWDEfp7|HWHD{@oygp`gyzS?;#vgy<9IqrIOO=Z*ROKoYFHI9unU~7E z)JxM5Synwngw-?aT$${zYa+`HMV1kDSd~LG)Bo1C|GKT z<{px3&!V6$7r1ZBcFvgBPp&QbgHy&1Rs?cnAbr-{L*#L;%8X9uS}vYz;dIH2*v36! zHv33nygOOhk({s1eXzSmMHwtVFQTq$&-KX}y@jAYceZ@xBmETJpXM4i_n{%CMt~qh(Uf1DFD0R=JN$mOQcBU22-=D0Lqzg%* zdvtSOPHsYVW?yD}-^>ra*JZ{43Q9Ig(z#5&lHvkoEO0&5lJn*y6GM+L-LEc`LX%sr zL1M)_lg>JxxyEqvWo}505(|J&pnlff8NNABk`og{LQ&;6(|nfFzx zkC}F6%Ngo-oOs^MBM(i@x{`E$9d-LqqAnEjcSwoOQPqfR-Weux9hiHGG(BG!;2z7A znf||&J~+?Bz&84s@EXfDEl*O&P4_?_Y3&wp{nb}utTdS}_0APb%k#XWb-qZal$^4& zrI6RK>V+_?60%Q}mhHygn~wyM1*%v+r|uFh+LkT$OB3=66II zDvRSK+q7Abb!qqJT!-d!|C7o!Q`d-0ax+jyx+~;>Zd9^D^IE00NP1W@F`2sL7<{`> za^z%=6W-_LH7VJA?!k=rm}7|fq4(2Eb1q?CvN@h2c#oIyl|S#}%C(6{yK~%d4b$-4noz9A&k=Gz zPQOd!J-FTEa)wh(PM6Ala}Q2EIo@fXKilAJ2GcFsj~&wdd>*;Vq)cgl#aP${+B$Sy z77-=WOT}=%pT+fAU#UyJ=b2-Zd-ZtsxsbM*!9I7=Z|3oMMZ{hogv+$j3JoJ+z+>YYJ-uWHGLhSdK3q_{S^8EQV%sY;CSm>_fK92K)o5lUt zN$%Aa$~tmkj%SmTG2}9aJmr|z2-DX&950b@t`Qpx2ti*sz~eId>GV(47MIG-7#vpCP`Oer>zzBVV+2D7D@z9r5=70-Qj zBhDK++@Iz%uGrU3;(d);GR*WnBwc!r<7Dim_G5c{c}+U5GgYmS9DZpsm)|0~prXuu zJ=YTT0t?k-`oB5Or-zQKzN%y34By_kpX-BqD$3fzctS0vi=;rW;l7^7`Y5l#`Ld8P z&7-D2c^5bj#R_f2XEWvWOT^OTC@XrlDjPaqE~H=7Vw@;Na-*86YMdXBm&#+DvMl|Q ze#kXPab&y{GQTLg4d><}Re^2LPG?V+I>YONExY*|wxtsK_dB^a|1QuzXIqBEoMqqU z-x>OW`FEi#(**{7c%eD>1{ilXI9H_x3ix6~UPCxXJj<~ml6>wc*Y3F{|Kwp>Vnpb%U6$89)s>kpS2;sfbM=9B!Ya{KbQ|4CKcS!11N1OG zM(668`jGvqUE*9HeMjZHD&JeVRpmC7+f`1f{AlHFm8VxOsJyoF>A2EySH)G1s~LAk z+}&}l;yT1V9QSD4V{y;LjgDIqw<2z1+^)ENaVO$V#a)b#jISBrDgK%GLGi=lUxAYn*CPQsLg1qp8@ ztV~#&uruMagxv`T6OJW(oA7;At7@QX>8fR_R;*g3YVE4ctEN`%R!vuPs#T~~vs&$H zS=B~Y8(VEhVztDy#2ty>SD#gVLG|U;S66?x`g_$stp0KJ9o4_7exe57p;n_&jVEe! zt5Hz%%9`bCK2)=7&77JiYM!edsC{Ma*rd#)Vb_(^sW4~8+>-fqilivAsCLnPMXifE z7Nr(FRrGXGR?(!Q=4>dT{_|W5ry8h(+_QA#LF7nHIWIvwQ@B7py z>!cQb@1w0w)tz-;Jy>UxJ|_P9_>B0z@q^>D;>X12#TUfy3uRfz zvJ=X3xr8cY`P#qB@*@eE33&{3w#s6GK{&jg`?|5 zzM1GhI`%B|@rELiW273y-{hWa^Q&M{YWjbmWI4-yhk-ypNA$9O-c+ zjdF}V{K4UNhZ`Qc_26=|hfDts1rPQ)bXC#Oq9a9fi?WNJFKTe`or9|njyX8u;OmF> zi|jkP@8f;nefj*Jf%~?9QMF5*Cw8RHtZ_JgV*J+$r$nkwz}afS#3hL*MXL9xo?e}L z^Bsb!hEpTXOqE^{xio@L$g8eayw0#YbIw`8b+x#hY2B7?JC|2Hx4WC`c|o2t{~{_P zHmr(2&wpg5c`xL$e^HM!XEKhKl2R2*apo#T{z|nkHN4beqVavHU!o(TL+4wwVst3K z(M|n$*`_2qnP;=ioajGW#ys->nP>cGFU<+h=T##-m40c;#OTh^U8B24r$_gS?tA(C z|2iGbdH&Fev~r@I(oT$1#<|id>y&fKI~AO(ong*!=S63N^O95Fxr^72JNbo)_R>jS zka03oHmRVBRuxn=zPUVE^;gfSp=zv}!mn;^;tP#;s6FaOr?Jz%(mS|3;+Tf3~2*6-TYx9L0e-MS0suMv8-o~Pf^tMqo~PUn87i8D1Y z!MVvkVgKy3v`;#%ox(txv($OV$#v#A?VKfnp3d9OWapUkO`xkD?W}i32l_cjowd$5 zevhYJ;6dJ(ad zny2Qg1?mg^rS4;uQfDn?mA5?WN-Ng7&wA2Iv!1fLSxc;?)?3zmySjBoN9j^JS_ie~ zyrLuY3%u5io>SCy=<-6_^rD&E?yuCp$xI#!9go)_<%t#himF0Jm> zG3q{jh3cnoQNwgAm8I`j&+FDIS9e$A^pomEou*#WPpPSTh?=G|-8c1M^`;)F7IJmB zNRLyC^^0nWen~CWdFmtmrrN95@{5i?>Ce;&y-S_cyS)xpvfSeiweEBmdE2C&+-EhH zj`E;8(t5}Xm{BF(OB`eWA}{Il)>QdIwy1K}HfxX9*6X6`TYsoNs;Q22U-Qz{VBOeV zre4v{xMRFn-A~Q*-sUI5=jc)TclRyzxn84A>rdQV_eHOom*`yKT;+Z2-5C7F%k-Y} zhI(=GxRvToR#mO7?lg6W4oI}c*VkC9R0~~3y`{&iWqN{Iu3uIwbiP`tCt4$Mc)O8M4b-&95!2QTOYsRBK&M4cBed2;Ek#(v#F`{ff%dPpj$r zId6~mxy-f-yf3T{-d^uZbxQBxi+v-chxLqVqsyzdx`Jw_uT~G}is~!9&ic|iuLoG` zysx}{Qb*ca%~XPVU3b$vz5U+Ta<|&(9gsm*cNJ-^*G=_ZYK(5CiuBu71$Vdmxz)sb z*1FefZ$0Q$@Me2+?Phj!`#$?#>s@QCcb%JOuXOiX)2zwX6l<}a?!Ih&ZtbIF{i)vT#d}q~x?V%Co?8-_;}zNWxZ}M;)+XyScdBmU-QeA1b+S5JPk5hr ztMx$Z1+Thy*gN9Yu%=tDIy=2a&S%b7-Yl=CJI6Wb9C8LZgWU^G5BHom&Kv91_Zql6 z-Ot=z?vB9IfoHu3yjtFTuQp%3{e*X`Tj-v4PkAqTxxs_pm0p(DHP9=N5qKuhKhPu4 z&3nNc;Uzh{oqbM`ceA(I+v08YKJq^Fj(OjB-+INKB2VU|%^FDCDa=-Kzc?-Qf_prCbn-~1n+vxT8 zZgJo9279eN*Sp(0>y`J;dFQs>AxI-IJelZLYu4`(%jvjnOAYt@sj`Myi&&U4N~*=>uwrKB#7^ zL%K-q)`#?A`AQwJYuR_(?d@Ln3wBNWPP>VHm)+EEXFp)~u+zQf_E4{z_oO{cC)ru{ z^Y%!4ls($cv8U>l_H^z6dfQ|4b@o{MwO~zmpSMPz(P#BJdzv>aSSxr<@LG3)x71|> z+nwmObmw}nd-r-dRvT-J>J_XVObT8XtRoMoYl7F?*}=NO8-h2=C>d=hx*yq9)Fk;< zid8AQyj{V*!riJGx*yw~)YTWHnfrk*k&(RBYpDk67F*i^b*-#b8SX}Rle^jd&<@%W z?iM@JE@hXoue8hBSJ~z4tL=(*CF^Q?mA%?tW3RP_+3W1}_6F}R`#pQJ{gM5#ZeZ`! z$yQtYQ~iPcnO(q&unMjBtdHzN_F-$1 z*CKe6-e`aB&hUD<+w3BDly{Z8I(Xb&6a3L#8T{T|75u?{#cSif?mch2y1re_{?X0% z?r|6ETipV$w>#6zaA$da-AP_6?;UTGH_)@a;lX;r`oWvIlP?T5un($1x{?0Fe%pP? zt7FI7-`L82*L^$qle)_Oz~)_VK0UFC5vOgk$lh*Ow!gLG?C;zcysO=X-b!~#u*h8+ zJfuFeciU0+_ja_s*Dh^;X~$S|?0EYJcbNO5H^^NZ{MmNw_ubLigKe>Ns_HN$+3pp3Ems-d~c4sF8GW4PVj_Xm65Jny{76muZem_H&oAh zPpiIOk6^>#E&3z9NpIDg^~d@{H_QDgcx&)BcX_Z_U$oy*yX;-TM#09;R%e^D-Pz^r zarQd0Vm)Eg28^l{=oskfl}5gCrzxo z)lnX`9+PydkMy*jm0ng~>23An(;59`td%2^te52tYo^SxX31RZb(v*Nma{rTBPEvLn_fa#i+|^Rm1vC-JmUXqvj_-w5{rC zN43|L)Pp)!J)|qE4!VjOpl??L^&Ki(w^Jka18S6RuSV+!)p*@QP0;D;W!+Qd>t1T2 z?yX+cL)B|~n3|!pRDph8tSMh~ZPWAB zcD-1AqL-+hdYSrEFIW5YyXt4XNByEdSHJ2ntT*&OqN}MW@cy&RZu?9#VYoI)94U)drVCiQK zk^WYu46vS)f!0vjW4$e(Tkr7hMx1(B$E!#9Wrjy}Rn<{fQ;+FH^|-FCI_VlJRo7Hc z=vu0?zD9M?*Q$5*t7^S|O>NLKRG}_V@9CN9eLYKkpkG(}^#=8|E>s8fd+MNmKR6_q z8GJ4{G&n4nWnW`oYuC1u?Cb10_VspMsm9nvb*aJ6I@OYEPdaMSsF+~xkYZZAGbT% zsrD0gXS<8t)$YdoFYn5F*&v1Tp1dy~*z@f9_5%A&d!fC^UTiPXKj{$K{{NSYEWP74L$$lj`)gEq-uqX3lE5+(N^}YH*Wd^4Orw3oP zm)k2;Pt{xhsE_F1f-{0My*s=+o%YTH&V$ZF!6V+M&Qs3Q&NJRfr)$?&qB1y*8E2^ToR=JUIq+EEai@>-tkc)&?+kDTy2qR`&RB1hbE9*M*U#JK9(TWSzi=K7 zT;tsCBs(`d4V-$x*PRW{qfW=*Y;Sb%jo_T%+~B<6{NRG%n@+#r!r-Fd;^30t(%@Uc zWr2L>i1VrQv-6AdtMjAtlk<5XHc&ZGB~T%7wR6fj6R-l2!4=MV=b}@>FZif{4)7CI zUKK|<+KIF;I03uFKH~)K^NwqEu}|CQ9NTe%EA6w6*nij;9V@shxZ3{R{>}b1xW@79 zQ%;0aD)@Hr9XH^*fhPi~ftk)S^@7TARyZr2<-x;&hXM}=9t}JaNC|ZCrg#P3Yu+R; z-<#%5^~QUXz3JWrZ-)1>_lh^sd)4dX?ex}q*YaD*Z+Z*7vR-#@l~>N&5ttWvC$K26 zHn1wNF0d@HoO|V!feV3>zzoVNfe3!>s8pb0ppthmxZFGHJ?eGxI(n(WS-}^B6N7odSAr9QQ-ZGr3*3wD25*BG zbiZ@IcYkpAFeqKdEA5r?+IjDLk9+I9NPcZ{jQg_};YGW}?tXW@`?a^;{lz`xdEOP? zV_uZ^p8KnNz&-12_uh4Hac^~Ra~nBdx+k6IB|;syuF~5=W0KP5jDL5_XN%2PSV&+z zF~R5DK&dJ?PooEAhgk_r1KVs#&%-C}sc$9t9hl!4EDR*ka+KS#jm&uzqOea<3c zZ69OQeA36~@UDybDLcbyORVE_GKtswoK{4$4?e}?L=$fUqeM;_BHuR2SwWm~#9Mt1 zBS~WR4QDvfjCH_yk=WShFybP&`y58b19cL7#eq_>fPT@^tX=UZ;o|U4hy`eAuTL8IwnR z>POOFX)>IJM3W;p^NFdxKzHI3K4toBXP;%(?c%dcUc34% z>L%TMmYHYPhc%M;q|X{dO!HZ$?>*(S<`SRwSsxI4_^gkK=|1y&#nRJfoh0`1S-%r| z`?O2U@M+WUp7CkZPx|=u-Na{o+SH@3kN2)c`uX$-qG>be*~9@pJ&!ohr{5wD^66E? z!9Km6IK=0e{bu?c(??9(!!iALsLz>7Ebs-)v0(Zl9CK{V^4TYdulwwuiKa|&On-dC zXPcbN@j0dsnw-KZB${#~U~+HT7|v4S0-y5^@lBtTOEi4~j#+P!&oS-0*cTd2Ucxj2 zrcKPY;Ji&Vd4V&TxD3mgK1N*O3z)vK(x*ogP43{VC$9E6X8syqz_f|kE_g3mxEcs? z))K>-OPX~#=`Lrp| z?>^@hqFDzzf@tbMeZzcER54*N{wd1xnOAQ` zp9ncew0%N8mE&Wal&|sigoezRMf(}n)5NkqbIw**`K%tq zaz1nJW+WxVN+(wEnRB_i+Gq77R`i+kx~k-}dJ*X#hU5_|`xqDI>u`NCo*3t2tXNdM zPbLr(e2gE9s_K)MiPe0JDT_+&>wJt`i>l)@$F#cM$5=LBlj}3bx4OX>dSCiRrs0};-Q=_0 zC)Nuy?O)$#Z6@9vX4{X_H1iYd?{5R)}ez+kF=2 z40VUkokC3ZSzi(F3^Q%g#AliF)LmgFA5DE0$CEjKg_zvj8f&pJnJ8D^Gk<it*i>Q{pB_Rq$FMOC-F$o|LzLM!T+^S;{-6gF%{~k>-)t9pB=PC6g~T2{ZR(O9 zwuso%r%hdYg)Jub_Gwd>jIbrdXMEb!rBB#W;ps=5arf$%m5lvo<6EL}e-bH*a>?CohPn$k$+8&;1XVc!Wl8L5`VeS*uaGz_| z8{xC=Bxd_ulOK~u@a_;_2kHx5-;ZV*q5Fp%ronqhqF(TsYjHKkXRi0nJ%Hhw?U^=! zW%{DYJ9xK=uk`epYfNSG3wIvT7hZJToqcz=qo_wJwk{S$_~`g!Lg7gf%73 z^yx_AET3!Ed)?=y6K98+JiOu4jfr!7?lR)sFjEhcBWTk%=lfifp9Mbt7TG^G=!3c+ z(+k5)Sxg(ldz)zby$6N9i^I(Jzw=opm*0oAB>v#jR}znhwIcrL(`AW2`P^~D6Fx72 zc+zK?Hviej`^0=brO%8Vs9$}&cg)vO`o!etw2$|b`8rCUxnES~c!0N?cqXha@vM*c zo%xDNpO_q;_win|Fd`pf#)KHD53#BcFNSp`miTmSzdxBiC08)bc0*huGYu(2)M2!p zW&6xH1n&&^dK2ldhCEF)>l+#He6ov3SwlEptq7mFm$M?nIBrebhuZ&sVkw`#iWu!P z*D}1v9-_+;Y1hy_2RS$Q5{7O=H06eQEi(6DhHgtFN1;5eBAV?&Pa>La!JJFXy_}(6 zAy)92*9_}wpEh-@=riYUtCCMYM~nsi)Z0TO?}iz7u;RiBi19w}3nG0eqyw=kssVYd}8WUGtA_&md}g>S=WR;L%h~!uKO(0-q6&|O7fX&K4Xqn|e4D9QH~P%^z`Dt&*AeT5an7*nhtW^1n|*o!u|b$wz9DX* z?q)l;`oxswHlLVsHVSJ-Z0s}lJ=X0$^SWoo+6>)|nC#O#iFcw2+uu*T%V)+rd6zIm zZ6un<;PVq=-Q$x%M6*AmJDU4K_nh}KZLG(AK5e$w!l%uCIKPDMS;r7t`Lx-V>EBRA zMAN6C-zJ)NGE7^TI>R-`h-nYFpA$_R7}UpV@AIA|J{V@o^bky+GS8>@_)Y}jozM{e zUPE|CG{l=rd=wqI{xM~H%x9ZAJ?^v3HamsAOH2(LOMJrTnSRvS=jIW+`0SO$u0D4! z(X@>*4JM~gVlvXg789TH+3CcmeeTP|9$_XA<~hS`vuD^B#9ltn)UP)(@C%*^`;lo`&Nz#{K6g5?pHG`U+u!HDN;G{Jp4sO>pErbP`U^0WeR>P=C7-*3Xxbk7Q{s4^Hf=P)=fx9E zTf(bK%=dY9i4%Q%zlB(nd|o}`D?Yb`IN2ANL!9FC%yBf;XWv7d=5x*dOO`PZB zZ?wdW>xGzhFgb-+o%kjeGJTl1$mi7{nqy-LOr4hcoSnq~hq$)@*P`m$fTv5vLQI}> zKoAq32{>R9=NStdJ19YrQot_kL=ih)MHG0ASFsQqySo(;EbP|T_P^J@*8uwUfBo0> zpL5-F&z@&y$J%T6o>{jL_zsYni;?*lU($6O;lBlzya6Ifhm=Vm>H?lkgc4tgAK&>-Eq(1sWc)M{Z2*3Th(7>7OGJ`i zl21Ui30T4dLW!@$2?&1xzet4NgC%}IbPo7sB9gN53K5G8UnQcI!LJd~MDXhb-f3d} z^#u0;e#fo&o{xo{mKI01~C1i?vQj|h$d!(TykI5;4J*ioD7bL;8<`> z1dzX*5Wy+nWrzT}EpzCC;6(6pM6?^Y4H5MRFHZzVgI6Fz$WG?b1i|s(m5A_nF!Ejy z{s?YMgujAUA;N!y+Y!;G;8lt6A@FLz>PX|Q;5CR4I_s`U1gC=6BBFD_?FqbdWVtmW zLfznY06M~-&B1FE!CBx=Kxf2x5_laVI2+uB2u=sDO9ZEZ*CV3c!Rr&@`(TtSLHHaP zIwuGhfj0uW!v8bC8xtY)&8-u`WN=DEgTWaQNLq3tLK$-lB6=9yjR;Xrq-_?2C^v2k z5&a9?gNSYf_aq_-YZD@T4=l0khlV|l*gliV{tBeA!P~hCGSM10a;&> z^&vrUAXv%|5J)(pi-6S;d>Y}O4pvxFX2mbSngu?Suq5x!ChTG0a|oFOkhxKV55SUt zfHxX^9$|F=Pa!Ok+4;Z)z+1qDge7UZh>&r%jOzrJkaT_gskVdmlLuN z!@Yu#wGsCpge^L9rQ#j%RfIhhJXJ9td^I6+ukJMp$**e(nX8jNmO$o0+!=}m;F*N| z3izMEb%5M+Jz+_DZy;p8#1*{I;C3L=rieMBTWb3fsUPCcO55Ih%n5PnVs zKLk9A^Hac&5uW79;|jz{`kn5RxJUBjDZ(2Iewy$^zn)QS1b&wA&H_J2c#?0=6S1WC z1tNSK{33yG2$*?unG1J+0$wFN(TmrJNYe5;0A0p6@2P*tlTh+U`~vVkwB^ntLX;m@ zbOpdW_m(UA0feHnq7y*W54?Z~XM!cafk<@cYa-eP{0$ME4*r&qF{Jw)A$=qFdqUPF z+#dj>Bi;b~Ghyut{)MpD1OH0I8-sr%Jc;w~z@K=w4!K)^jl-#6N6`uF5~1V`ZZfEW zeM08Wy#NUD>>_YP*vo-qAOTSByk!(Cf|n&i}xwpJR{DVvdmc&z@ z2P7TdN`$o)cx57d7~GbSw0Wx#7Ie>Rr;xNtoCK2A)d;%xdsq;DfI^3B9c z(htbom6rlJfO6~=3W-BELdKn5cg4M6Nt?*{en8R&M18@VD5R`zs(1{%nL_e?bA{x~ z7KF6V-j<3Nz`Y1pxHlp59bO;Bi(m;4a32P@DqaFN2={hysdyDE`6_rF*c#XdKsofb z1-1iF-aM2mgL&ZXfgOSOft>(^?@R=vjuXhZ(?i)1L?YAvijTp&5$?g@-GM!D|6K5% zgp5VJy@0)eg}^?9jBCAp30U>9y#0t+@@9X+6&)C$cnv&|2(JPUA|jM!Z!i%)10JGS z06u^SPX!MpBGKz%M0h9oKmyi;EN?gw&H;}g;xE7l5#b%+kwp9{coY%N29GA<&%hEV zAQZWcB_ffl$Os6p0Uts{CxFK(LhyJ(`gh(0#oFLQ6%wDri0}gN;Y1|)e*_WU1U{0G zzMgj!5l#gkO+*r}V~Fr-@UcWBaXgL)r-7wB0Qg3Qj6 z!6y@u#Oo9S-yEyH2C8Q7Q zO(vwC_Rdq>1)f5La_{*>)D21bjUaLhhb~0Ys~VZzMut2?L1k0E<2W;mu%0ya4C3h*02M6ZnASnLvJ*d;&tz-^Ub>gB6y@_zA^x;3o-7Wc!rj zdGOOjcmnttBK!(0`3l%w!1BC6o|9()nM?3qAgmPpA|Y!sp2!`rGVsfUvnE*b0I+(2 zUnOKs#(RyhP6xkE$ULhjc_8Qjh}?ll^zJPp6rFmTfaQ71n@5C_5AP88Vu$6;Cql`y zcZpcyzd+FuEP5mO9C(kgHvzv-z{Zc|eL&cofA^mjk zdm@|&{(%U^?;nAGBP>bdPedqX0uL_ajZ-gy*`@2HU<-R}R=VlnM zZcK;{w%L;Km$j_+eF-1>*nTU*M_Sr%4QzvFpo8tVC;aEZI}$$pZ@&}aLwDNmLio^^ z_PY~4bfx_sz@G32y3l?f!bkqMA58d&d;1|o;DZkUhGOg-fv+HZ$gurX!iV0szlZRl z7wsPaAjfzMu=pkEJ=C&l7Gbpo+l12&><||8q~;P1WKi=6OU@Awfdd_@1%xH%A>lN@ zh@ZfM3~DhU>s7Ucu#oQBGK8!z)gWtu1zFUVBb>d#Z3s*5S)Onpr`igHCHJgIIQxQE zA}qOQWy0AHEa?QSQQ%bwSlgjIbpia>985ToAV8NjE~~#cSOO*~40EA!Kg4)`PG`mOTmQUGOG^EppwI za29|!BW#iJ=7h{&*R~*Rk>8et^F6p1VT&w#6V4A{(R;uaxr(j>&W~WxZ@`wcHVBz- zu9bu>>E4QvdFR^Jge@{aJt2^J=-Re~E%Mlokoo9ZKf>-0MtvfX`P$kJguNShM?&Uq zYdaD4?%VVP60~kZ`5U4kzpj!6OJ)%Irae{SUB&2grP7O~L@|E5YI~Aaj>B z@e8o80?YG&I}t499I&T?4BKAMpE#o951{TTRILgoT%k~YA894zSpWL~f)X#nh}z!Gmj z=Kg9Y60*0fb`l};fHlzrzjwYv#x9QYnW z_EXgEC9Lt_`v^zme?MVK`FntneHOL3gmozRK|=Oh)E*+N!@v&{vj3v?2w_Pa9wnS( z!H*G^#NlzmIS%{;VZ91|l92WAnv^%ddJQaP43IVQnv^TR-V-ck32;SL&k^=s;O7Zf zWc31J?+t#DkoCISON6}-_+`TF2!4gI_XSIw0C#P$!~?MR1HVqVoxpDp_Ws~E3AZ!& zEy5lEew&arz}h^*9teJiaJzu#6ZRnRyM(L_))o-l?LSfIm@(Kv$bAAjB93a%+l!<#Azc@Cv{xxV{~DHK0AN9}8{&V-tUO)qS(iOZlus!tTK=4k$9=QK(F!WL&du*Gaa{?Rr)3hI9 zBY&C(5cVSQKmdMtSAmBEW8uHtFJS{R-`sQv5uX4?ITy$rYZJ=0AU+X{@+^>f(56F) z_$2UQgv{+V9Ztk2gT)QVnsC#RM0^VPC_?75nnb2Rd@A@DLgt5>M0Nna&0sZ61i)Sf zJ{>q0vdY1efs1i{fAA#$(r4WZo(jMZnOkZS+>AItr~ONWPL4D2)ZEiqfMeWi*PRYe+;0k+QKNO0$alU0{9Bg zUJm{i_#XF*Tz>@qjq}^UKN0q5@Xv%j7W@n0iXXocuEgax!WBP$Ct}gnKZqE0LNk)u zybQ+Rh=216M1;I+UXh4;gI6N_{@|4fe^+o@U=@t_cLTR0VuahgDiOU1UJY0s_aGme z*C6~uz-tl_^tO2|poV+KfSU-|p0k<}x#kYIem%G&;qL)noACDrcOoM3voqlz2VRHp zq3g|Eh^QBMVAB3~ecjx_fn!q>rl34bqeD-ob>Y;F*~_+1jd z__r0XHR9GCyba+aJ2>$_aKVW;@zYll^0`_05<{gRXH1JNq?$F8UV39lE zOIr2>_Q&-b!2^ik0`S3vFFGLk0r)S1Cjdv{d=dC40Q&BK1D3o&Uir}5=Hm(fHSk2> z4CG52u*mvsoQsZ30?xs?=+tDw{~mlE;r|4lLIe(2@)!u9-^~&~Aif=ZA>pH}G+zW< zia7iMzKjT5@a4c2cs2k}C88SmYQnd{(+FSg7x@Cdge7tWyx+hgJHQuzW)Lz@*F2Mu z`M>6W5`GN6j*vOO=IaSR1m8e7L%H2p4+Lp(_y}&pV)w5LnRZ4x17_h-eCUBoSQ&9z{gwgGUq5#o#eSbRl>w z5&Z*6e(+=>_$OFo0R(r0CCxzaFR-K&2qc~&Qy>ukC7nPZdVUdr z{0VLaUk4zLf#}05;1-3>Ac9}P5;npKz6XnpfI!mz5)p`huMh#s zZ-@E7yC{PW_ygcWoI_nZEFyx>z@HMq=h*6o`vm?&V3a?B|2P=sPayk#IxbK6kAPPs z{D;9S5kAt~aSg(M3|u4p2f-T<{-fZn2_JIl2>liKkYh*aXUCy9pNt`cO9Wqn5f?%5 z6?iow_yXLS2)+TY19ZVX{{|y0LGUAZA`yHIK8*;z1)oI(-+`Yaf?vSU13~b!WpzTG zAc$@PBaWR=9zwZ>Jnn>h!Uzl*3c`+-)p;r~4bQ#>Mm`ICl;h6GdqIFa>x{e>1jwt- z@JA551iq69UIQbq1?D>PytCxHT!%gh0)*WeIwS~Q2G1pe7r~IVAb1swd=i-JkK!CK z*O51!fdKl^`Eepx2!4w2p8!8i1aE>RJdw#8;ExD@Pw-bn=vfxTZxDO{MqaHG;Qo)n zn-RhL;6X(2A$TYez`u1MH$m{aWp!DZ2%ZJ6N(9KCF1r)K0`LJuun0U6IGy>@1@RHY zhY24utanct^s4ek|At z*cbjtSo;xf3ErP@#lHcBi+orxkZ_>`3y`M^MxcJ!8+;Jqjt7qfCL+CSfX@dm!ud(y z>A(z}-wU1z{1fNMvjxbf1vlUv`L^Ij0O|0N9}AFYg1`gcOauvd7J$48mIb41Ew~Nm z5|*S{%0&|x_4oqlTChI&BO(|J{*H*=1%FS(cz%I|4MZP+p)-PLA29M#5Qz++GlB^6 zS|ITQB9X^0M2zyk;8!9(2mBimUkHYN2;#}$KZscJ^iLw5viS2nR(vVWZ6dxB3>^@} zQ^76~!jFZJiy(Xe3|$mNat@spL@_u7z~Lug$W9Qx3SNeYBpk?55JATmE=NSmgWC|1 zToX4C?E+qbh#ms3NJRUC<#_;hORR+}6On||mWV9yDnxW9xE&FG174MgB#tO6f=I$& zod{8%EJPj)!g=5|iST~#T15B`xJE>By@?3F1veAnLU0EHHi4~$9f?TNu{IIy2ku0` z=90CrGZ8%qUWW*wM+>_U;e7DALKb3tpdy?gMWCv>^T)f_ng);~ZGH1+YKP zCENkPV4U{^4IFR?3&57XIuD?yX*wDCIdXTt#)wn+_BnNGxXfGHneWnb8o48 ze5<>)K(7Z@JG-5phgN@kLp{d_8|{gD9^>n-r|bDLIRB@fFT2$Fa(1)*gkEoB70$!NwI1(!xBdi0h;aIU~wlb@3&}{X` zm7%z@4R$0Zzk-`D#~NPVzxVi4{hY3+j04#e}~9%tq7rEaBGPs~g1 zV(n^e4{W;BlN(t}p4{EUdOX4yi%^>X@+{-E5&Rltb%TF(Xx>m92mcp-EiHxJa8EOy z8)F>+Uiz02Mhb&)bpql#2)_?SdL`Z?ac!(M3`dcaq`m9nvt4k9Tszb}-P`yp@*a!{L^Ah=e8F z-qz0Md;&D8d9f_0UE`4!(cXjcgh*8KdK6gn@jyssM?Bx}|J5@~a&?J5Es@3n2!|Rp z-fG49c*J!yQXp3k#rXuBNzO@`8Gz7nsYOXch^6AxIKT%6Y;#Uqw-k1??x z3Q3Md?ugGDAyuNY;-Bcrl02J$>yihO@+DW7$UtIO-SeNtZY$%zlo`>w15Dg1%@}X( zhB%5w{xt?8{t}kNN_1iv@?P|60%9u?KiGsk9KQ|4e_#9}S|hqP2=_HxTm8+KgH68e zYSMln?idPx#v7l+ha++KAZrhtRdwgCCN#-iG|B+}{-|4X&B@;mG5QvkZ9BGWd$w-} zc4$YiUz6C&*vs0>!H&)H_6qij_Dc53c3XQDyPdr%EZeMZuVJrguVuHlYjzXbv<`Mh zdu_Xu-PvBp?gG0A>)GqW_RWU&Ms`&&%)Z>d!v2SSrG1q>)xH|mR<3~si0Sr>#rAP-uy3^gW#44q zY|pZ9v2V3+vv0TWuxHzM+H>r??7QuI?0fC|?ECEp?78-X_Cxl=_9L(l^qBoPYy>@N zKV?5{KVv^@KL>k3FW4{IFWE2KufTH9Yxe8*8?Ynumi@Lp&wj_AZ@+6Vuov3z+3(vQ z*dN*-*^3r`m;7`43;Rp^EBkBv8~a=PJNtY42m43+-}X=T&-O3&ul8^D@Ae<|pN<6! zK91u$p5r@#6T+5H>?F=I&a%#OP8(-=X9Z_PeDQu|r>(P!(+;+NR&!Q&)^OH@y`T0@ z&1rI)oeoY%XKkmG)7e?a>Ef*GtmmxnY~XC@Y~*xxHg@Vx>SRvt6izp%yVK(IaC$nM zIGZ|~Ih#9M!1hrur?=C`f&Bre;grr+&epKEvaPe7)6d!7*}>TnmMwOMb);Rz28*-1 zvxl>%vzN2CvyZc{1M4!de=^V+t11M>$73$2iA2$2rFv>k}tBCpjm>{?e)Vvj6GM8L-22mUFf<$vMY4 z*O}~`=S*?Vhh?Sx$Cu?fmQk?>eNvFA|tBtC& z-AS+(c&b}y~>>m%Z<}uRq$F^?wR4v zgpIE2V8Q1G_eS?$?oIB^uo!WRd#ih!d%JrFYskfQ8xwnN_Xjp6?r{R^xzS1_Zu(X}m&)eSH!Q0W> z$=lgj>*%=I!q7;qB?|ujccfNN4EkKCv2k$cPa_bdgo)-Jr6f452D&GK&XZWZeg-W_Tu=PvJV?;cpnxle56cynPZ=OI{|dBl4ZHgg{L zo`Bt)r@W`VXJB#WIq!Mz1@A@gCGTa}n0eKE4c2qsfQ1>cpfk_d(0SKe;4Spt^WOJ9 z@IHk7nnm8n-Y2j{^O^U#_l5T*tiyfnedB%Wedm1-iw!@*I>S%i&)zTIuikHBvjO%- zE!cN(VBx{@eLwI+Kk{SPZCJ)%)?ZGo->s-y{Y>-!t{8~PjhUHy$=RWF78irgd z8@BBF`mKJ$Fa53jt^IBMZT;>1ez3l`1FYKZ1WSCoz=}qHv7-Td8GHJB`Fo4S3x7X< ze}8~K&>!Rv_J{Zf_(T0+{(=5*V`pO|Z266*r49cOwW)C^t!juJEZFxu#y=KzH;(rw z`X~4&`X~7(`=|J)`ltD)`)7#N4Or2e1S@~%`jh?hVC(OE*aW=LzsSEB7WFRmFY_<= zukiojU+G`vPxY_%r}@|T*ZR}_8U9Sz-n-6N_P7yt1aIObZ`?myu_39CI%8@oNv`Oo_=z-Grwu;B9w zZ1}w9zYaS-Z~AY+^42_9^O+C3UJGE^>pfWZ_&_ZE`HTFI{ZIT){m=Z*jg615V6WmE zWAWpAwOjF%SgwGTkKg>?7hC#pVC%z!WuPDk#X1nIS}bGiTC@q44^{|P3|0zO4%!B* z1nq)V#af8iyHML9O+jg08{FK|M%=EXac* z=oWMjT7n)y&tQ{a(_ph;^I(f$%b-`#JLnVi4O)XnPzGBCTL;?&+XmYO{eta-9fBQ$ zor0ZVt#DV^EZhzDLiT{IkiB3rWS?MPv6d1H2nGg&g2BO%;DBIgFf2GQ7#@s(J+qO) zs9-c~mW?%5%f*j9@10kz5yCAKU;dBL9Llk(+~AuqJY=+9jC{>mqZ4yI`^89#|{6FStK=Aeb9G z7(5g_96S;{8ax&}9y}2|89Ws{9Xt~}8$1_051S<~!d}VCuw3#gESJ0SPTr*rNY#-Lbrm#8e5OxgL4m*XN!*#+g;kw~^;rihQ;fCQxVb^fu zupXvi7Up3Qb_=_QEn$z!c1gH-xJ9^S*emQE_6hrjtzjc9!>z)t!)?NC!|h<{bo+3J zaK~_`aA(*&-8JkV?iTJI?h)=8?iKDG?i21C?icPK4hRQ^gTle#knn(TD6FC$7!D6d zga?Hq!%^Yra7;KhJUBcg92brcCxnNFhlPiSM}$X)M}hun;nm@^@EWn_7tRQ0!fxYr;q~DS;f>+H!kfaI!&%`i;jQ6q;qBoa;q36va87tv zcz1YDcyD+gY`i=GOM4H74}}j`cGkit!Y9M0!l%P$!e?Re?s?egd(l|wdnJ4|d@X!E zd?S1lR`1@1g_w6>4d&f&LAWq{FML1zAp9`=C|m^FF`pO3Uw z;rFoU_hb0)@Tc(S@E2I!`wiCj{s{k+NzBNJT-ZkQqW~83U{)43XqJJ!ndPE3uu!uC ztk0|j8-Q)2RbX#tRoFpX9aan1jMjqP!dlb>8-*QUr4W`SVSR9&s0*wJt_LeU8$=sM z8%15CjbXnqjj||@3RoQM9<{)lVb5rjXwzsjvGE5RNxh=pQJ<)9)CyaDC9ENC4SROm zM%zXGV6|-r*zns47B6>!b;ACz8@PM4N3>_OSF|_m?CcAhIQzre&Oq2J92^aa4uDO) zVbOtN)h{|I8X1j(&BZa%*y!Ns5ZDeHA5DM-rNg4bVFm6;*akdWtzRAw+lD7ZC&HfL z$9B5iChQ}g4J(P~z#8FX*dm$&D?}H-3eiQdMRW-)6qS0gFX~0?@;-Tlgp}DLxKcici9t;?vPHu=Dd=^gOH{y$DN(FGsJ4<;&=G zv3V)BEX8_dG(UPbS`aNXRv$lDY<=;Q=+o%4==11{=*#G<=lxw~3dJSBO`PSBh7T+s3QJ?c!DA)#BCT zHR3hnwc_@1EpCdN;|_7h7zT}F*iekS&_-cpi7?)nHVEUKcK6~Iv0NvX>cl!-yhXfa z+$-)K_lf()t#KnRc#n9`c&~Wx zc%OLRc)xi6ctAWb9uyCbhr|cOL*rrbf${KoM0`*@G9DF=j>p7f8f~Yd^M~+T@zpXSL<7UwY?SJ7T+G$4KOR33KN&w2KOH|4KN~+6KOeskzZkz1zZ}03zZ$<5zaGC4zZt(3za7tu z-+{f*cjE=|!uY-T{rH3U!}z0kQT#FNS$-OS7JnXp5q}wf6@MLn6Mq|j7k?lB5dRqe zJN_yDIsPU7HU2IBJ^my9GlB7_#7W%5OZ+5A!X!%KBuSP@mQ9vR+9b;-DD71;SbV=4t)=Sn;Hb^#1HcGlC z8z=Q7O|m3Uilkf8J!wgLBt4T&l1-D%lFgGXk}Z>7N$;dj(l=>M8cCUKm291ClWd!8 zm-I`vPj*OlOm<3kPIgIlP5LLhCA%kkBzq=%C3`3PB>N`&CHp4>l7Y#fWNy$r;I+$yv$S$)x0*gcz>ExN@+2pz8 z`Q(M<#pI>r<>Zy*)#SD0_2iA@&E&1*?POl^PBK4vH(8J@Ox{c0Pd-RKOg>5$B_AiB zB%daqC7&l>Bwr?9C0{4sB;SUIju}y}7b&@Kp`0t%m8;(~b$2tImc4}=jXJNVb>5#Y zcGJJKZ!7O_ZIBzhFVp)oy+6yk%kOD}*V9ICb066F4>taR^}gOcczWq1UsV@k}ZIdS04zLmwHxlhsX#7&jub!5g?!MI5dZFCc z$Qkj~@cJ^J>U}jnl|QM*BUL%3d@fBjUn#G8R>>=4{x$d<;zPY^@Hx1dpN&-Ip!(9F zozb~SJi+EUun8Az@&jzr4>s}#YxyWNznE``2jiJBUMMyycV36v_z!0O zr*zi~)%$|=6x`ISg7p>jkm<>LQXY9#zExk!O!J5SLTxpFGU`K_Y5ru?i!#&lPQ68b zF+D|Tgf=x2O8sv+~#c4)HP_^}f8`VEFZv>3|&czP=S3 z|4^=se~_n%2e^_K?@KFxa?N-8k8;HOv#Ok{J~p&`radb;GG4jX54qO=Wv=PYHQiOc z%X%g)a}z#T)17O&b4_=vKHsX(x2oJ)RlZuzi^ck7{DVFj|G>sSu<@^fic9K6uqiiS z)!$V0m-Pg$tNyaSfLryKpezbZGGhEIJ)bFJZ1ui@74 zsn2k0_{=}JHGJkH+>}SX(0t9c+^{^=^A;0+z0mxu7rZ~snSW`6a&9pH(+2AcF#Rdm z?nC|5f9f;pH@Z>4OnZ|upX+JWj%dC%7!Lfa!ZFXmt<%~&q$t@O9v%6hx5 z{f4xXzos|srscRwcSGafpu97-*C^kVPp0)jru9dr^+8tY1O10Q%>8MD{)4qXW_^Qp zP=9AXDs3>nV53K1lYX$uRm*du)x-QBn&QQc@c>_)xMv_G1$euA7;UfoPQ;8uBcGjhpN)-PbnDN}u@cgtBW z`5eMw{-=Ddo_436WL1BN?zGWe^R>ICue;`3cgC+^`wqP{J#F@_@)dO zZcRVKMYXQswrG5{T%}t6(pIe>*#6X0^*_^oXjb(n`Fz@FQF*bP!>#$&qUmOR0KHau zR`o9R3)fXH4V6o&`J?^sI`stp>U}MgQ@xwUvkJe5c@EXO=3@_ymyW9%?2jV9G=4g+ zX|P@wM_It9v+OF2SYq@S{JHzlW{$>0ceYBkPVSUh`{4l;yeb)Y2o%J1_ zGuOcyud1B&X8IbG3+nGGTrGF4EEgI33wU1Rqw%U&^=fa*BU8Dg>QBmYjdUCNq5Guq zD=Rsw9xV=6{n7TO(Eekg?M_j(Q&qj%;QeVyy(w7E(0?)d3^v!nMsL6-J;*+jFJO}% zu+b;5@egd`1vc>ooAL!Vat9mz02}#%P5Qu0SDpP3xHWv%J8*0G>~~-|uHm!)0k?+F z@(H(w&;A448a~@8xHWw3Kh&#wlI69Y_Ne4(7~}tnRUg>T#jr~6tNKq`o-?f<>kX|B z^!}9NEp&fX-*vvEq1SUAmlxH%lh)fQ`^o5zvVF>`dPeJsjQv>nt9q{UGL6Ol>vj5r z@hIbs4!>!aGS$Ph8b53K&sg7M*unVLyHgMAEYI~!`)ze?pX)65&{yhhT7{$I<&^z* zbo;g3v%ik=Lw_@sQ>Np-2A@NGwcP4>y`kguhK}nRIOHbl4&EKR)v|7hcHYeF$U5~f`br@SyXvSRXY8>&c+mjObW4JWsJz1!dTB3FR(tZ zlOLI8URE{g+IXj|q!7Ml0^ z$Magab?}pE=b&D7&Kl--JkLfo)j?Id_&M!7RQ~E@VWx#BtCXLU4aj7zT-fno2S?M4l<0JtFS(F=QJ&j)ETBr)`M5fhbT6Zn9W%GW9p>PPt`zU#5e`l$9pJ(?VHQR@GpR@j$qm zKRPMUs48I=iiUQY!CL9FQwz6N`mFTe)=Ho9gIgbmAo*fCv|ql>Z$5ys``~`C6wx56SbPr zFO1F%XFAx;Sn1a@Ehibv3EV2TO0TLxJpE4_^dD^GgmPm11*?47c|dUbJ39$*YdxTo zK@E1sab2Hh=Lel=lU}fS9+Q2HPtHmiZ9O~BrA{W4nxCakzL(5b3?A9pDK#HT?c5fc zzgY{*U!jAPqN+D_&{b$Bv#x`Tf{Oyk56xHVE#$!RSZH~vSNf-e{;blYs?$I{g8piL zRP~F7muaUq)6QweK?3gAdaW8nRrQQ^>M?p`{<85zyj0(`)7@ZrxNhVL|1_W3d2ZCJ z!6XMKa2x-?T5j2Cfm_Qh+Xc9_+_K$(Tg$E1>-9?B9L%5|Ww@!fKdH(g)%c{^8PBRl zn{EthjXr=?{tORplTNf_%+HjISQt!dI(6_^=imzY$MTR>oi3e3%k;Udht{uEJE)zt zv}y;dP6X>ugs=L`a*XCz^;ajS>#ApU9gL+_d&k8z%$+mdnJ%)_t3?}~46C#Aj=`^% zW3A^>tv6EDx3sFas!1iCj7qCXC>>;{I=IfXbCp${BtDP$t9)CuyizZ5-Gq;FslT(l zz^!`5dIoNlN7asNy;|2nW~P%rjcU+V4Z^DMdQ|dY`ADnyaM28dV$}oY6WkiUPHNVx zLAx&UHMqEkH8A>{Yv-+Am2a(IQZ8nqU8jE5b&!)XUeMbrT=P5nF&ZxGV+@8>E~Uz? znw-*hrqJ?RP%fA=WO*&B&a@7i3+=xY+AkLgRC<*Q`5z*@Np2h4mZt3``y(m@xmOC5xl z?7!lE=3iNrM-J*Sc4R*|*FkSyl_O1mq4jH_@+}rmR-5_|V@vJVR{dKYv=p2?!`QLv zuNb-GewOD#`xS+@$A$Le3!Stmszou?&qDj%h30?3MNdrnFuw~OBo?|@R8;+aog^-_ zy)SgKxX}J@p^M6e_5%u?L@u;HP*ifzd@gjcw$S`8xY&w0f98Lon-U7GcMIK=P*=UK zm#RmV{Hy%Z%?c$aDbPJO`2wcCmb$23>SRaBMI`*ra#w19uhdDjQtORUC(}x;KT4fc zD_P%Q4Z-9eSpCt>4y8`|l)CAm)XASxH$RljhqUA*5#}URk93kP*L=&X$tUU~)_|B_ znYM$O_GdG#Co*lXOI?gFbr4?aq;je4e5s4$r4D*a)u+7Llu^a2(tj>uVNsOzK&gwe zrHn9QYR@(oop+$J}Gt5tz^A{Zm{;hSiiun^3}!8QWxP%9bA{X z7+-QS0gJFEA0SVyPdHe|9FUHebR3#ji?+JhQEI=n)Je=z>+O=`8q8g=o-TD!tJHB! zsgu5?E`F5Sk1cf(q}0WaQU~|Bj$iXWs>j@1fc~kY_!d{qK_FDNLejxzNRoQv0Q)PBxe9C#I!NVwXCJSZY7J)Je-y zC$&rM2j@Delj~%9u8VrPj!Sc0EGl);q11j_sf!P#_SZ^XgebM&R_gpmsr|Q7C&Np& zC#auQe!5AiWcz~as!!VgE_KsVsgtWE+er);R33{LiB#{{E@OU!axZkgp`e~%E>N#) zJ6LGDRaA?p+D|OBUsvcjyx`{%Ad{`C91ap+d*~g^uS6U1Tj(&fThdR>#?e zPAV6=s9I<{Tj(TPq4`nhVt%3h>Y`d))$wUjP1b6CROooM(EfIzlW~RC6NQ$uLdUZO z$F1l;Y5P?zp6a-!sQOoxTy;EB=wyAN19x@5 z-d9a(QjWOK=p*hk`USVC_u!zN7+w?!cCLQ1^ei|>fr|_@3pL(hD7r3td zQ=P2WNpw$qu(kFUE1Ee*!k~0vmaNO}xM+ z9t|BIgH5`@CLUlDZ?KUQ*n|%@@&cQ1!AAaI4VU#4+!}s09<9cy)p(fW2JC%fdw_y% z#=lst6vo;J^>uN6s2=O0K&F%XV74zRS3Jjj*G*>FBc;Dkv!=tE1;qj&6OHcC;?#Xj%G~mZg6w>gG00GF7Brt+Q%XQrE7( zswmd~R3zHPTdd;jN?f`Y%?|CBI&BSEb3Xe-n})IOJWSq z^2G_0kd}57OF6QoenEgs{ZcILLAJD`=)3f7O9hIUm|v(7TqZ~0M$@qCrV@vi08OTD z1Hye;18`X$ZY`-SY0wl+K3fQ^MraMfl7`<^Tw201tyweesq3;CG)zl6H5g)5&B|4b z`I1Cn7--ZR#Z-$2yIWWa(qhJH6>e3>s{5zY8aay(BB#}wHdl++Y8F=?DjZIaVJk7K zyJ|V0DkjzPgzjO&k`k+-x^|Q5+U!=xWV+Jb2L)Rr7_)$+|24EJeu$QBH4E$?i>;MO9=njda$ zDcQ3?zGzQ_EgXI~`2yDJlOr44XZ!(I`KtG^XNv3Qez4Y0I>KsY&l}qsD*rV9*<-~* zmxj-oE4Z2eb&f>gpNVhUO8MgVDqQ0quA3?VY~+CZsW0`a3S#~?s-mtfGj^d;o9i5b z!>w}V2pn#eD@WjPt6Vul3%AOZJx#b(uGAldSNTVMt+OYN`&3Tsso_42H%G>Bn{dEJ zE?|{=Ri3CHxL*0A@~*ZEu!n}JS<3vLa++P1+FEv~B^ICBHH%7HV+a2q`U zs~osu1Gk1>&9rl5*{HJ|gEf4XJGeD`9T8VO@>Ew`RL&@O#$QZOGe31@0PgBK^-@<3 zaNWcU?WpPzd&X$uv=-HFiM~dG-FduEU$23i^^x}Iv5UpX7b}CT7jo(;D(@c4OAqdV z5ktof9Y11x*CAs}%aG{^E^F<(to%M<#Ha&?RzGSXfv1|ex|b%?J!x5M??k$X;I4-b zQU6SSphIrr33-@Daf?YeCP~2X56P&fG!|SdZv- z8??fzf7J@9)}OlF9t%{AS4-JVTIn9zY^5xQSidvbhT9n3ma;oFadgNOU|ca~5)Y;< zyRqhlo3iMpWBqR0GIV2&jNd7{?y70sSaYIhLrv&bUDq*uHyyk8(ScDPjYl_@cq}2a zh3&?}m zV_)potlUgrt}S4y^3XW}+{bik$%I?)S2^gMUZxu*!KzEzb4zt@MAym?KbE|dHF}n^ zMF6t|cjH=D)`uJVvGqV(P`yvrFt9|u`cQCA6whfqbPZkiFyTEsw!GC|Car;Rojs3~H7WMzvFD*LjKOWv4Q9)$ zdye4No-{|M*e|F4Fy824t3SH-pXvroeL)1z8@&V@xqvl(d8-+(AelN7 z5uvMv)Pux9Qr?>`4u0~!bZH0;x=KR@!z&nG!SD)(S1`PS;S~(8V0Z;xA49~$E(^RVTT56v^dQCuAk~Cl8kI{Ba^B670+gVIsrv9WlqpYPE z6NS95(CM^-<2qbt{4)(N)7N^lYUNE=KvUg0ROk+jyxNJOJ1z^ISxB{?SyVffbkd;E z@qWtI4%1L3pOG)xFRc8p25YKhkq;DZ3-Bo34 zDr3yzX?m;5R@0a3%vr7r>p6=9h8;%S2)BAp{o}MFK|_WN9W!Cvhy#aD(9sE=RTa`j zmb`j1MrQ$Yc6<<5l{Gu3h=W!=^*+lFIda^HQKLo-nJ{R?DEQlEv3tajY7kUZdwQx8 zu4_hlRfAGh5KNUc6%mtwXjZ{#4axox?l(0TY9Ot_sA{;*8Y$yQ7h9Z|l~sFN?TyKG z?JLtgx5eVUVVYchkv`R$E30NI*iS?aQi+8shD|^m!LbjHJvycz0oIzP@~7GZple*2 z_BT?UX{z>eWm@BAT9ag|iiP@L=*T(Ky&0LVEvD=jVJfn!IaNh;jXGn$4Mm75n(10x zUhN^$wZ~ldw4eiL(v@qG&KJ*=n;H-!HX~omkn_GmNB9LNnXz_9Ipw-$QdE1Yw8qO- zKDq7<(f$UOSeTDF`;%CzN!yMbJ8tZl5#y=MqlONvbQ$#$11YMFacnN244Ns`0=CvS zd8ItMu#;9BF16EIy@ilf3trry+t7`q2LtZ$}i>MqXXu%oZ!P3SAeTifa0ig$Dj>GgU3HGM%WW*xcs2 zaIAw02)SzR`CMMrpBm3pl_*u^NvnEUHymclg--JpT8J_o6rlfL26g(@HR55Kb<~&4 zr@Y#T&1MSn)%sr-J~Q2*l~qb!ZQRnghI3unNVPMqZx7;SKb5z3zEkEauA6)Wn@R-C z`di zNOihdH$p<6RA1P6&#Db$xo#-P)xTWdCe^_s+5uKZxxPSB==5=_8zD1Yh|jehNwtzH zblNu6h26Aj6WHz{f3#BOpcL(;(HoQxBQN}}dZZgE@@m6%^|q8&7MbdKR?U#<+d)~i zFBdf}L)u>d{_S&vz`nFS6y%klx-Cb?e*UB)f%7eZDkX0K- zwOgK5{^<(@S(U!3d#D>xGu^P5>Bg2!HzZ}cK_t@+d0DmLUKg};U9rn`0X^3ZAh~V; z$#r8$uFE`m^)_f#p4l(M{sYP@*G4PX`Y6|p+c}3XxKHy<8|7TfOJ0=^UDnB~#z8lb z=Q@1Jbz@7e!-ia!i}I@f$8rokWuutuM(RO&b2$8>&EL`yVbdFz|M7ek!yRC zYdxE5y_Z)T@pNN$uJv`U^>nVoiCi}dK=Y9DkXWv=x^uKAp6KIfXxx#n|TZM4<= z%&QHSnxBQrsnC_+LN~w{y79cI-X2yxEL0B*4X@Df3iY?3zj&$Ilq>8jH{~2`+9R;Z zZ?LIf!KPgSn|c9kLp>!`zpqzgotumL=Kw!S9-W z)^~7g`gJ3HtG)%@svGKC^)2XD-B{nMZ% zxK`>B?$h*8U*OjCXudY|Yc>u2icLc|UN@*`=?j_}p`zY^{;lfS5FDVa%Z}bTLn~F_%SXDRvgU$0` z^BkD{5q&`pZuUF$1v$9cKhd}O;Z}dEjY+H*ah?4ceQ^l!F#3mOUFB*yp3@cBSZ4AC ztos$D6Gkw~h#>Xj`PW|c`+)SswEe$v0qwPA} z+TYS;;8d4^TeM%^%KYrzoqE~3McqBQ{Dl3iCOoi_E7*hwHgW}<@W4i{U=tqLq#tb3 z2{!c**vJ=b3j>92OfIU)a)yikhzSRSF7rJ0l^Org4K{K_zmEPD z+Fs|?w3D_!g|<67NsMV7_J0cPZxqy9s3rZ=$!|>Ww2JK!%l`9EYk9C`!Ty=~bF3R+ zw`5aX+uZsWjyKz@ST-z=oQ~s}_Qg0}YEQ*+n*Az{uiM|@_=Ei;jz2lQEgQBZw#IQ= zr$3InJA-f>;+%xzDbA%hUhZ6ut*!toL3Q5>Igp2qQ6=XD(4a{E{|>=A5( zV?Xy09AP~G$0OZSaXj6<9Y=i2AIH1g=Wu+{#Z&ky|64fDbLZhW-<^-+doE&yFYf<> z;~(xHu!?1QVjC;)La~qq8)Y`WvcDpZZN2t5;#>MSuI;UjV;8R%j(t4D#B22$IBw-3 zUHCTs&N%Ms!9p6oaepk1CwM30c)Eww;hXe`n>WY18^?RS2XTDZdl<(@y%%wO#d{OS zc^=}1uf{LL@qO=o96$0F;rOZd6^?(xrn8N&!FxF3ldw201Ix>{zk-i=;!E!xas1H# z(6aGm@D*_E5FpO@=J$FyZWwHc_}9K@C5{PGKh;`-c1CI3OH=$)4rnTgl7d zd^y-IwDFbXwm7zf&0ibeN?sMe;%mt`$5)bZTsuO}%J-3R+$h=@$2`h$>=E_Au}{>B zBff!*<95+@IPMtj2p{mRV;uK~-9uZxcZ~C4ur+MUSB`NU2fM~LzG@74;>*U6C%$fc z6wZ&2j>q|l(TO-eFG6|27m6WU`8F|*_!cpa_rUhCjc*V`4)_l7T-^B(EG^pj67i!r z$G3-Z{x0lS+W5vWv?cmEhP}8rjS**j&$qyNOT0PGw~P@wzNvdO&QFMuJNRDi`8Zw{ zUxwpdu*B`+TelD59ACA?`AhL@IKCObgCo9Qi|Zf9ALEGc)#8Zn)tdT1>VyQA+;*^> z*0NuJtIeoE6UM-H9Bd6*7HSRHAH;o*Wu5je4YYX#ivSnB{{FK`*_A}g}4wqCa`u->#jww{J9q|>aoQR{pLn?ql~ zdd|JDQ_^Zb4jUa$+fTS}dY$a2{Z;%`oK^8H@pGNkL=&C$p^29|8;A}%seB{c$?$#f zYn%dK2fxkf7Tyuw>+})5bGF5ItA{xKCEuLgn_2TuM0r5uhM$SNdUpaFI$#<2V1MpqtO`Ku)vhrrmfsz}} z@c7vHSZ73he0;of5YiX{$xP%)a*;^UFQ@nV|K)$ybpDT;{P+*Z+Z2 z2KH*Rl0H*C`@iBD?f$!`+BCP>q|Ht<+Ksv7KmNBFHly9r|7|Xq(Qfqo(er1_?sx6d z*Z-&U<()B8#!OkhzWgq7r#YWGquu5kF8}42DgV>6Gukzm)qmSPr;lrUT-)QOkK@0b zwY{S4{8jc`WeTbr{!dwTtK`LYg9^MdNXgEO;hhfWukA3WW5w@adDY_m;kA#%2S%%76CL z_WgIcB5$+H6@UG&(!PEF?fXyblwZ5c71fm`Jo<>yM{M#z{!(%JCdc7?{^%pR?a=M^ z?%TFl@=MD_J$m(Q)AJScoBS&OxL_&=@F%w7W?nb`}+_x}1X*9LYSHCK4){{dew zaSiN>d(bi%$dUiE2F@Df4C+6m>oBw0o}WCk*YsJVKOd8gIRyXlAvj=1v3BaS-ts9%oyeEO`TemOcidL{F}*3s)6 zU7ykJ=*uQOHR-9D$4z?bn2(QLcVg?rR^ek|$PdSH_9UdPdW#D)1=#%PDhm@ zX@{58WKyDMw43zp#S2W?M$b!H-K(E7ZA$lPy=KgoHc$G4(k`OBAhjq7v*jFRW-8k# zd6qh&b#*W3$1I%pGFsYhI-Zl#GkqSO!x>7LGR{qlG;^&Pm!Tepj^K&e@C)S_|Hq+k zdLGaV{qSwj58o2~@Mo+^)^qs3%X$I*@w=_Jt@o@)tPib4_~jG)Kac+TvDORrrOvK) z!`O$kU>|aS*s~ny42B)c17O#2S&aLyfHlf1VLh=G))Rk%y8H_3hgUj(xQR0rb`JYE z|H4>*Yv%*l9USL;Y%B$?2up$2!Rp}+u-{i=tp5b&0iMCwegG`){XguT2YeL8{=jE< zXRgqscajSw9*7V{@3coE;Svu?2ep z<@tO*-cxh`@9gE0OL7UJ=+pNn`F!{G_I77yXMQuk-~48FW&>*q{zAj=BE4#!b(wC> zTjOcX*SD^uHQ&&RvSwa8>l)hehg-L?65aLIQdX8*V!g*|Z*Q@t-aD+`)=_@Ue^=Vy zU&wB(+4h~>ll9s*u~wd5v2AZwZ2OTF^M2yLH*2;1BKKvTHdFS|Eppk9b=exp{;HX3 zE(fqCTU$AZwb-)dFum5A9LZX1z2qoXTk9?NXSKDEJb=~K`pW~=Ks8Vv#ENT!<-x4E zHbfqxS6!2bvg+DMc^K=ijgp7!mDl7EdgV2F6f3VCE{|cQwd3V+th6>p9~Sk_UyQl82xYFEoMSViqxd8S@LO`gRHYLn$TtdKTcUdhU6Me;hm2AV8n z?XzX_E>=8SE+?^y*?KvdmCHVp53+LEmvRd0mTi;|sh>P;q{I4R*>V*tisj0eSyL=1 zUtv|TUh-8|4;w1qWqq&%WrL!er+PErfihIW4&)mFFo zRXe>7l4`Hp_bQ9_{YaIg+xMy?D;yoHIXR8rHs)G}SYEGii>k$QBFRQ6r=CdNqH^Da{Jo=luBH^ej6OB2~ z;g^N{vJfHEzl={r`L&Q=qxx6(sLj#BvZFd|ht~8eQ6g50sCY@(=F=jquQf|V)Z8G# z*2#ivQ};}46z%GAwt7t1>T!4qo`dIM2`m-1r>}^5`aypf00UtV42FCd0z;vI7}YUu z=H*_mc{%OoxCk3LWlyk{&v%4vO(gc$!7U;znu(i@Nct17+Hs`(nU}D?6js10SPgM_ z3ChyL{MCr0(nN( z1dN3JU=$n)N5Ro>3>*u8gX7?M7!4=D7&sC1|4DE%oC0IvR5%Szhcn_Zyan&TyYL>YgRkHlq^c3@292Q!G=*l+99lq2Xa%jI4YY-J&>lKK7Gy&Ja-bu0 zg3izda$$D}LRaVpdq8*C6M8^T$b-FLZ|DX4z`oE2LeLlbL4Ozk17Q#hhD+d5hyeL! zTn^-&aRpojS3?x8fotJ9AYY9efPBTr7RI|DHg1BO;Q=I>aLpkw6o$cY7yK7g z5Bl6mFd3%8!{ER)m<}^wCOiVjhFb@bKY07Y=Bw};ybf;w`DKz{=G(9q$TRa@cn{u( z_3#0F2p_@6@ClT_2KW>{gU{g$Adk(jfqXW@o*0h&*0%1 zJUoMk`+2rdi;)jr($h=&c~?L&tb~_<^zt1CC!iT_PvooF30uuWk1Tg}NK(6a%g%~sI!iP*K1evmNANLx!nKDH#yG;7kSI`- z(J`}23)Ngcb-0TYQFPO0bkk z=5YTf6OOe2o`Qw&EWDP8TCWqYH~3t`=bP{r`)|Wqcn98v_h4P3P%fc7EQPmWEosz@ zx=W|k!HEEMnWIvrdVDJV_`DjTa1C4w6FB#Fs8mZ)D^;tdI5sB{*3w*~d`(C={(;!z z-O_WSJmpiVuAg2%t$xzfQ>HytjZdLl2vN7R@3a_;p6M~w@{%#Oe?(r@C6CI|-txEj%q~eaqz<)pX`&E;HACZGUWBzYP4Ogu$*v9;= zR=x{rDBlIt#Rb&G1=Pg_Wp#03SzTOFOT9&n^){@9ci>%k50Z6p0d;W!b#Vc8aY2Td zH%5mwcKsYzQ;X@E>>9W>twyUT--Xnp1)@uO-e!z>GEKKBraKxl(LOy^+6J#NW_7LM z({nYwhA&Hxq|XSg7DfZvqKSKdtOxXYl&5jfdA17i>oE2YPK;IsB8ZQu%w}+PTp>Cx zPu)ja&n9o?Bm(83XsclRL-A=VrcxYI_otTC_Z0dJyVf(L8%DAnqxEZ41Pp(o(5MIX zp#e0+su`6CoBP9&a14xrli*}H4X%Tt#6%sMwOb-=4NHXO?xaf6Dk!MrHJd<+-9$>< zURpw`M9W%Fg}U|Ro0$lUwnqKZq8+~Dm8F}pFKWM&epZKPb$C{XXLWd1XB+Np*YK?A zx&Qmdpr+KWdkkEkC@G~SrU2i_5d2m{VHgaD5ik<=gHdoK90foyW4UU83VKkfo zW8g&4ez23^WH<%J!l`f?oDOHenQ#`I4d=kQa2}iw`u_sB5H5moa4}p0mqG+CgYj@Z z+yFPi1egdn!7VTiro#-F36H>|FbihG9Ki327tO|tX5&S(@uJyy(QLeEHeNIvFPe=P z&Blvn<3+RaqS<)SY`kbTUNl?atHq0E<3+RaqS<)SY`kbTUNjponvECD7Av3_RstSl zyl6IFG#f9PjTgi-K9?%o=U@zDkdci)hFZ6*B^o4%V9|pic7zBgi61Ws1K)&Hcv+<(Yc+qUUXf|Fn z8!wuT7tO|tW*gT5`HC0K#*1d-MYHju*?7@xyl6IFG~1wUW6-vtjc=QHlJKh8<`5VP z!(cd!0K9d0)@(d$Hl8&b&zg;A&Bn84o0DNGJPZy@gXu5>X2K(YyzFQT(lZoQ-$R#ye-@owM=I*?8w{ymPi}3+(|}!#iiYEona*r~x#DLtq(w z7^JO-d*Pw8@zB|L=xjW6HXb?~51oyN&c;J$)}9y;5193VG%=xjW6 zHXb?~51oyN&c;J$a@OCbXIi>bwSxG$hmEyg{l!G4c^fhaZ5pQm3SSp!w?8%Wkb zf6e}ZE^f`DbyIJcMru^}yNLv=5uZ7$&WQ&8DkX`72UmhQ1!!uO@1J(tV* z#CvH{U(#1<(^qPnaiGpLsmFCaEJC`sRrjzOlSI^*3=hH-cnGG#!{ER)m<}^wCVtY& zu@9HUKC_QC8T+cf) zI6MKm|K0EQjA>8r0(c5io?h;*b)9O(ST7^qMpCg8}m5e?f0(coUC2i2Khpkv6;iWdY&m^e}SH7Bs_qG2e6q7f5%?vKbN-2 z)C>I!J~Um+{6^1@1rike6?&porCij$7oLW3O4!4$Cq~zK&0L?;I?*Ln|NklKbKS=_ zhuWe(v5KRW;=HLCN1w&f0&#R$932)%hsDuhadcSRJ$p}AvyP#yd>No&X|b=N##!TP z+CF>^fPrumRA^qCK0&Q*ZTbXl`UGwI1a0~RQ`%PRD?N70Mq}ud)A~wlTbuqtoBlzY z*rl|s*0eUUw9S?HyfSE8S6|VZQLPL?OJ|MPt4gf(Xq{B7^~jUjV@)c#BMBboGxHN9 zbzS8raLP1p`V*uyu8qB*HLiY&aM@E-9$%Hxy6I2R#(fILj_7nXR>5j`4fMDYZ6hRO z7%jJ8bb2P!GMP>#Wn<#B&37`g8l{wNh9BV&gwgr*-shoR*^0`XZ2l^ z>5#muwwJ=?xu%~tlWkNMBW)wOZQ+_?^?S%&)U{A5=i1JdyJT9XN=t>zt=X!~WtKB- zmu4y{+On&Vvl#nYwvweIX}zV(QMawi(JZdk<+UP*cTDnij8YP>%WH)k+U0Uc46Dgv zuQFM5N4U9`b7sj8r`h)DWi?!F1VTziE8F(r?Y539fhqw*Mw+FLCtyFVo@)mkpPRJvZIR9#vZ=}C`qxU5#HYhF}usie11DoamA zErvF$COz9*UnXm=n(kD!zHIHv=(dz`Jt8LAMoQ*bvW=8{?zA#nuDw#FVCyw<^>SO2 z&dsE;^mEs&b5rGaTWax4WxD-VYoE+oELleDk}D&yRrZ7ZU7N+`-Rsl{XIkXi5kB!%OfhJt2+K?xn}* z>U&j;sD6k2RNcpYv_-`|k|VqQo$S%(}rpQy60#B-|>kn`gGi0Bx<8Sq$er#Kk3^`)_8x; zH%ltlc>f!3m?Uew|Gl?NW>GuMhGcuOw$au#d;gRD7S%ofJHMAI)la_7Z?BZqc=qr0 z7K_`$@l&IvzsZuU;ePkGS&S2f$=k>lNKoJbFZjR@^`Jg9fQHZrc7w*y1e!uKXbvr) zCA5Op&<5HQz!*3Y z!f+Ct45z?YI2BHV)8Py_6V8IO;T$*@&V%#8h6~_AxCq9<#c&B+3K6&r#>4e+1KbD` zU?SWEx4<-*4l`gTJOYoxESL>*;4yd{o`AV959UJ=JP8ZnDOd-h;LA-oHY}p{Ao`Q^e3Nq>`$f&0vqn?7|EBHnP z8TAxo)KidAPeDdK1sU}eWYklTQBOfeJp~!{6lBy>kWo)TMm+@?^%P{(Q;<LtiD+L*?6lAngklvdh zHfIoV?TIs~p?ffN zAAPOy-qlA>O~0#;9{o%W|Cf0?Jgs-vrndjRMucha`=7s`UV9yW!_RJk1O+~*4~?NI z{2AXCPw%65d*yX%GrKGrM(k;9Ni1?bt#64uw+C`2pO3;UcoG)CQ?L-8hG*beSOkmV zId~qHz*2YtmO%`b!;7#2ieV+Jg4OV<$diO8xlVGOd>hunCXvTmEJeHvP{?~3j&G#M z^PMj8nEy1-UjiGjx;`=O{i;Vqx_y8BAYf!We?Hq z)YHot2RjYc!w2vod<1%Y>=yd=ZiF42?_^i4waa#Rs1b z)n*tm|9rL9?>0MAM!)tSRBMUjT@OW>CqmoU)(68xs?K0my-;6BC7n z6<&a+sX)&>L!ZP^)Kd>~1dW*bg;q#vq;J&q7A>MTS-;JHCGX@j;|Q%BJ&$8R$keFm z@)6VeO_4&{0fmHFNIRgAc0eKRfI`{iznWdX$184%xp>yeG&)=E# z=y7-gR+nz3AHw80ERcGnIe5Se%zq@9IY2OTfMDhTL0SlA4iJrCH)sr`jc5wZfHV>< zpd~PGl4uQWpe?k6_Rs;cAR7XZ10A6gkiMb|#$f|9EBu0|vjMX!G=b*O$@SER zRROiwnaK5dV3#`T0vOK>3s=CEr6tHl7}*FT8)0N4?7K5z`|bg}#6G;lVqyBs8r5di zm{3~c&ZDE}{CJ(1WSBe2`hdksO7J6piZl)_Es?_#$I9WQg>nRpg#BO?><PymO);k2O7hI80I7tVw8!G;UqLbwRV!NqV1TnZ7m3@(T9a0OfmR}rVHNv$aH zx`yvJ5I3GlPGSEcmKhbLez%!Bz*1W&>OcnTK6 zGt@GTK+lEIlFzn~oGHt$G*gycT12iDF;_D+qd$08=rNbK`Y;#5AJ_S zcg;nAWtyvS0wvZ%c_~1W3X!BjspkUeji#7Kz1R*pEtEqkIm|}JFZq>h0&NR0Hox)B6KajR@g!!w3R5}?kU&RX12s!T<(QO91ap~O0eVi8tBFw*u7PVI8QTaF z5hunGBqC0%MV(?jhq~eahcpzh<`=c4=%_70S&y7&rWaRFzrg34ByTnIS=M#6qD z3igKs;6OMC4u(VEP$&Rwb;@Wy^))3Jn*^&!VueYpFo_i=vBD%)n8XT`SYc9IVUNJ0 zFblBmBxx!i11vF#B_`*>JeUtf@FXmNr(hxJ$NWX4Z@5~Wa1Z6Yd@fb?Gt5$0V;x}X zGJPv7ANSYP?FO@*CKlPl}w)w6cq@K`bj>ijNJT~_gplxqt_J8Tgx1|*& zq+1@4>68oz3;Ag5mflun<+L$lqjY;_67L62h6iB^JOoqWVQ_#q42|h917?afRoXl5 zTNS)LzlOKx*YNiI8rfN_A#e2T_7!NMa&&RUWySM+gUzn+K7b2f4Y){Ag+=b?o-O`BL|HEaosyO3z!B?193gz>I1; z=$FyH3ej{&LrL{HF>kD{)yGo~u7E2+&mL!J?L$jbw|t4cZsqDLsL97upIiY~;wbQP zU7YLUTo+gK6Pwk7gr0kLwAagcGY{z>CH;t`FU+4oN=nMUzKMa6@FbD=ie(4&+PYbpl)CXFzMnl*_ zs{9-N1HZtp@Eepuf<6P@e>eaRgoEH_0U_n$`w?6PgJ*W>2pdmDZ-Jmfvfu_(5nnMd{39XIQp2ci0npKu^enynu?@zOQc2c>Hyab(6v zTM|D>{3P*{#7`1GN&F=7lf+LFKS}%~@sq?)5#{3P*{$gMnv$5uEc_U1Q2jL-@3XHbHLbb6_Z7ft93)RL# zwXslbEL0l{)y6`#u~2O+R2vJ`#zM8RP;D$!8w=IOLbb6_Z7ft93)RL#wP`=Dm2dIM zT-r@-Qx8-|#?DY;< z?pf&nJT%IIuJ4GJNU4qQ$f3>Y(B^ci&D(u5*W3!aojIp8|9{DjL(i6=XG?sPQ6KW- zI}4B(J?}NMliEJ=&PU$)$#-FLc9^rnLSer-sulVgP0Z0KN29J)SAJA$dCsBTgvNGg zGvW2rXJcV$|L0}f;YlpOlURT!vB1DPXuJe3!z=JAyaunsJMJjVJmWn+*TMU+9zK8% z;UoAMK7kV00H4BV@HKn`8<{yCkF)hC%mS=;i+&IbZ?lEB*_sRZK&<&t1WyBA4tF-= zMSL!X=iqr*0!x9Gx3vsnupC~56;KQ-VHM!Twq6C|gy*w>6&UiWcs~p9e1|3XlbcA{ z0;KXxq>|RHqIFCAN~no^(*9Gl|FEWFSW_`YTTeX)&%+W}inqQmE%ScR9|pic7zBeM zABMnCD1dZt>U>797P+HX!@iM7$?0%sq5$t|0p8UDysHKN5@x4ILbSw~9a(Luqu7`H z&c}0{Z#)Q7;31d_4}$~KU^>iznLwJ5-wyfhkf-_h1oFw#eDXA(Jk2Lh^U2eE@-&}3 z%_mRu${7?_-Lj!0C zjbJxu3{9XZFmDZg1O@aF6wpUdKp#N?eFO#c5fsozP(U9+0eu7og8GX-f&%&o3Pb>M zpd)mG&d>#NVRr~ZSLgVQ(}fKt(hd}F>8!0S#q4O33Tl+!TfG)y@SQ%;#f1rOl{{O7+Q%bj2- zX=}V)Q&IOLQ!E$d>;? z3u}*a0UG#B+RbN?pXc#;KA+=&_n2w(*|hm=HG#anJrQ#K$n*k{$K}0-)G0P~icOx6 zCeKHc=cCE<(bOq6b&8GWFTvfOCqzBh6Ee(v5{B+|DUfGV`mYB3%Q{T#3jCq?o#~$=?0mw`XRh%T5k%WI#?sGtG%d`dzUWpoK_n3Alp1Y ztLBul(ot!2r-v%sP;a7^a;T*oYAJ_W3J)rH!3WF>PA%n7OF7h14z-j+Ekz$QGzNNI zsihohDSBC<1<lJfRu;9CLoMY{OF7h14z-j+E#**4In+`P zwG?lLKv(Dndq8*C6M8^T$b-FLZ|DX4KyTO=`alT!LO>~ zQA&E0k{+c@2FSkv`lxQBURP3vSHWsv)fi(9ya}utW3XzB!KyLnDfitH^puUBve8pE zddfym+2|=7nXr)w8=0`tQ#N|aMo-!3DH}ayqo-{2lufPbSQ~_{9}DrDM8orUPD0CE&SUj@)tHu}o;K96;_#GSV>;El7A)}7Q&cky`-pY-%o zt_poq_|9CAery1&v}Bn=b5rhh8OnEUJyGVp&*yHomSVRa71X0bOhK|jNLC2R3L#k` zBrAkug^;Wek`+R-LP%B!$qFG^AtWn=WQCBd5Rw%_vO-8!2+0Z|Ss^4Vgk*(~tPqkF zLb5_gRtU)oAz2|LD}-c)kgO1r6+*H?NLC2R3L#k`BrAkug^;Wek`+R-LP%B!$qFG^ zAtWn=WQCBd5Rw%_vO-8!2+0Z|Ss^4Vgk*(~tPqkFLb5_gRtU)oAz2|LD}-e6zGWh` z9a5Bs6y+gBdDQHA)a-fG?0MAedDQHA)a-fG?0HC77zqm_VPPaJjD&@eurLxPu!IB> zCXg_Jgb5@}AYlRt6G)gq!UPf~kT8LS2_#G)VFC#gNSHvvgs}--_dWaoo8j;9Bm4t? zf`7u#@Gn=#hsv~do+}5`97v-`lg2!W6r#!Vv_zr_@{p4}q%u#PNqu>q9$A4@GWthL zOq!Gmq*NfKVWd>|(`zXWBc*vrsn+;;NNFBYnunBzkTxxdp5JlnhqC8) zd;atcBKxDE4uAvUAizT_4uL~~`a&EA^cIUF;AY-7y^YV?;SMMS?ketryMg*b+za=? z{nUTGjmCx^AuZ~}~h6Cn&I!O4I}$H1dw;L$Pg=ool(3_LoJU1E`*C<99#@oaFpt8lblDm=24ghvjOYCpbchV*BDR0T$l&*p$MLY1@IIsgs0&d zcor7HVxYZdJP)+jjHU1bEQ1&tBQw?5~2=5Qmoli`(2x_{>FY{sjMopW$Dy1^x~HfnVTP&i{?iQb^EuA;16=ERdi8 z?}5eYz80(dTCDDCvAVCt>b_P!41*Cc683`w;UG8|4uM0V01ku0;RrYqj)J4%7&sRG z2FJniFd9yPF>oS;;Uu6XfgK&djt*c)2e6|9*wF#(=m2(f06RK>9UZ`q4q!(Iu%iRm z(E;q}0Csc$J34?J9l(wbU`GeAqXXE{0qp1ic67k{5IedAHk59rRtR9HI?{^{uLrtj zpofC?s2=5H%QhU#f=<+?y1hSyIiPjFGS4S@KFRY*o=@_8lIN2=pXB)@&nJ04$@58` zPx5?{=aW32!fWt4ya8+A9e5Yk z!w2vo(4!?k<@0m+625{>t~Rojz?wI}1bQ!aH9NzVmZETBkJ+BAih1 zp+jTn&=@*2h7OIPLu2UB7&h1p+jTn&=@*2 zh7OIPLu2UB7&h1p+jTn&=@*2h7OIPLu2UB z7&g>=#0SFX$xeLf>6rM;(d{jrwiY6MMux>iLX7u#1F#oVs!gNxx3xD@pNWiTGDha2EVm;e*uCT6L*g-=F5QrE<(YvR;3aq5~lbxoYQCQe-w zr>==p*Tku7;>?jFm?I}5X248%1RjN1FdOE;WAHdU0drv<%!eX)5*ENyun?YxXW&^_ z1dHK0cpjL!Q!sOGiPUnxp5-QjT2$)O@z5|BFv2w5%0je z@E)v#uizVI1@(*Xje1ZY8bCwfoe}2CiO_969idaAvC)~&E|3emLlC+`H`qgTXTF>W?+QkEk2S)3tP$qRi7;PI zg!ytJ%$E~kzMKg2zC9*Cm{;^=`mdLWJ-h@%JM=z%zTAdVi0qX**XfjD{~ zjvk1k2jb{~IC>zC9*Cm{;^=`mdLWJ-h@%JM=z%zTAdVi0qX**XfjD{~jvk1k2jb{~ zIC>zC9*Cm{;^=`mdLWJ-h@%JM=z%zTAkI8M5#|Ak&>D>x$g^=dAk)Sbz-S-iDn74< zC|m>A!gYYmGZ#>Vxqu?f1r%W}pono3+ziOQaVy*gx5FJ!2zSC=a5vlo_riT}KRf`F z#8_i8JP1?ZA(#peg9FoGI?RBX@Q4^-JPNa5Hq3#?;Bj~Y=E6Lf4@K}KEP$tAAv_Jw zz_YLj7Q=J!JS>5w@B%D@7%YbuVFgg*(WWb+O;<#luE>a&(h~tM!z=JAyauns8;Om0 ziZ@H6~N9LZaV5pxI( zg<&upMgaL?k{>4dVUiyv`C(3i$uJcj1_!3WbfAthX99JUNgc&ogb|ZEg1MO@%*_;G zZl(xxGeu154CZEvFgH_#xtSuoeHCGDrU-L0MVOl@Vy=e|;6wNbK88=A1UA5@@ELp# zUjX$Cb2CLu>Kf)|ikRQQM%V=3!w(|Dyi5`1Wr|=!7F*oI;vU#Kaf`5N*A-d6^7)&H z;FsFS>>Uv)L4h|BX9kZ5p0h33L~&;Fh%i!dEVFq;7_BYgG*=n=sR8Oz*F5$0}+ zFn3b~+bJ&F^4T7!udt)yvr zt~!wG4~$!yiV#)xC`!v zd&F3EFTdXp5Ab~wOlJQfKBvON;J`GFO^2EA2;U!tS?te-BA#`bi11E$#532p%QFw= zLy;)(JPF0@uY^^wn(t_S?94cJX52%*d5{ecvcWs#5fAytXwb2qEh55uS!QjsxmH z+IB^FHaF6~D`K9k2=in`m?tYjTd#<=UJ+i-jm()9Va}`w-p-9a$^`RfMeurV+-2?U zvi7jz>TK;LHtw?a(E8MzyR1DdtzFjME^BX>wYSUK+hy(jC9FM00lkB-`dxSr*1`L* z9`IY!eycRTi1wR9`^};K=Fom~XumnM-yGU+x~Ja3UUjfn9U<|M=P}AM4^McW`*x10 z=#!-fp%8zNsYlj<1O*=Of)D&q59$NGRd^G_coV~T6T^5D!*~;Up9t`l;!O3;m!!=rIHXVGs<4e0qERrA53?Q^fl;MZ8Z_#QQWw zrVk%$D>#)AIX++nj&(iUKrh}LKA&}a62jK&d|v}^0%Ha&#tc~W2;n)-*Zl{ia6lc# zr%j9K0vON!6>ud)Id1~L>$lnq`Fn-YuN}zFJm(3ZPPjF<{D!N zna`3E<8|kFWF#nOM>#vcEKEm-Nm-jtkNgaH7r9^@NLi2F?aX&D8!x|pJC1UdaSY+~ zggeVh*%&NaG;(E8k2z%=2~Xt^8=J9-Va6tE`5NtxL7l+U+|HPr`}myAGw690@$#s$3*@y$%G#c(*-W{r#jksb|8smTHMGKH-0dLtnknWR4Mb()>-M70% zn>v-Ux|XmjN!#=iRaQdi2{gqTZeND9^f6xKio&1$9B9&_V zV`Q%Tm;9s$td>8@Lh{_ND^98{?25B$+9SQ)KlRRBgxdNI6o1o*e4aE$#1F8 z^xx9=(|=EFuJ|S4Y+qcN6-%V@p1piGNApU5)gkMYO%zwjdr|62CTBKg7WQZ|21%)L zuL&fN@RqG8#j0|TIf)R`;_h>_FnPwVZ4D_v%Kdl#lBKrvf!a!xD-)X&jIq{nOnhFK zm{v$fUCz#UP9R;@E6zab%j(I@zgL`D>mK!4^}p>aPu0WOc}G$usCtO#&+Yh2&~8XL zx~wPi_%Em$1m(NzuX3%EJk#Je(m!=drO%3(CQEyTM3oj*oJgOnv_pshp|07O36MwDwFaP80{PHj5JH#w`Zuyba4iZ(!diOi69#`abh@y5>*)p=a#W{>r~v%gUN3@Ry$V;kuN(dZ>om7Q@+P{N+>%XNOZ@8*xnE-%K&x2qh%q5 z^=Fk|V`;MN{;tpYWB#g2{5IURZnc!N4I$QgYK4ZvD;lGne`|vE7&8Br79<)ce@Wz5 zwYl{1R0%FEWWO+3Q)BP8uLv!5eZ`Tw?WMJy*VXNc%x7h2MVFsgwbfZ#RQ}U8>?F0R zPKm^lZMdw`shQGNr)Z^<(!OM_Pt#7wxSM!2bvW@;YLB~Y)>5zo@>>xUdpjqbq*kx$ zeX6Oh&V0|zdV&^CBpKnVS8bmU#qC{+YRO3quapovEear7^=PH6+?iZ~aKZy-Md#mHK^M z^4e70kov3~q7l&FGsmOJih z*MF(c8#MUIw<`c zUiBijcz4s%xBNbp&o4h*w!+$jr;4`uwq2JzJF`TVMl*#~ zC7$7mbGAoL={!{TcR49hlIkzbd_~Q%ui9crUSHMLs|Z!w?c4rG z3%3~QifRo`*vXkv-C+17%+Uh|8I@D3l+|LAf3BTgqW1}tod0ce@_6NK zX`y?DyRH1o@BJ+?rKUSnexjPr`eTo$yu>@N=d7{}CiCczeWtD7&9yT(Yn!pc(y7!V zSHdq_@hI1BaVExXJr*^ck!6)6aVhmN;?Q7Ep$x}Etzj|CV$1{1}&a?=s-}(1HQhVL9 z^X{Ly4|(T>zbmA=r$FMfI-Q%Ums6h=7sc2OmwmO%_sR~qzXh2A-YFX`87=!Eui}IZ z*QEXUhwcWGzUV)r#gt5k#LAtM9_adfQA54IlkR}e=CARW$+NUQF;8r%>s2YcD)ad% z4W)n0+0!8~E zwqE&1ou8?PLEk&^5BTRF6^k9XPhbbG-L6X$?`-w5c3VB;4>`fjzZTnftG`6vZ{MTs z&{f;*nc%MJWvvcUv_tXaZlCR70Dt1kb${uuzyJLdpcS{1{!;$Q zWA0^+HLo*!nKzjKHjglWF@H7VW~nvPeA#-`nqysHJ#NLVi>+6!b=KY1dh0{Wu}b)# zVf|upmSo}(Yqqqc*P0{!vYs_hHk1vmBH36rwVssCWjkx3%#vBw64_A>vzE${a-{W% zJV#z>mB?%4_12GaqI}x=Sw1VDlbz)f`LPVj4e|?lp!`aHCkx~z`GY)431!M-m0$Vg z@v4DpEJv%Rs+l}VwNlyg6xCl1l;^0yYOp+C4N*g-t%j@N@Kt`}yi$!*CUvvAP2Qpk)xGi#b-$V{?^O?}>2i{qsb`V;oW6x~Q6RL@4 znP-J+=~?IbShe%~-SeaB=-J}gqB?tT^xmktcyIFFq;kEtcyCp^d++exp}KnS_uj9% zd8c@%s6D(7dmmQay>q;C)SlkQy-%nf-ud1lmFIodyIAe*ecro7_4cmtzO4FqU-iDK z275Poe^B|p7QPm0n6HhmjT-Lj>jh?_hPX z?>OIa>JZ=QzSGsAzO#I1s{-F;zRT3%z6rhw>ImPRzPr?szI%N4sH1)N`R-H4_#W^% z>R8`Q-=pdz-_yRQ)mYyK-v)K6zlXnvI?dn9-%FkD@8j>I&hYp14^(IROZ*$ux&E*H zU#s(lX`Dq#7$B4whzHNhEc~NHGqJxojQ6S!=U<8=_;1aO`lH!Dff@7LGGqQ_oH?GE z>%Giee~aiZZler!qCDL%+B4t$6U~36X zT*;BEm_fX~aSQW;w=?c%X7Fal1I!KH&X~lE-}Q}0nDe`y@hG!?*EeP{H+MZ_8MATM zH;S2$yPmPqSS`94aaRA_!+673E3%AtjE$n9v5A?lI~!ZfR?KtMn$?$lW*f5|`|Zta z_5)^DRx91Z?9R4_If(6GbA&L>k>+8dg|0h9M{12@#R1eDq?N8a*p4-?<=X4a>p1fU zbE4>O-elfHm^YhuGh@;{%$;qRlg-J5_Mkb1BM+Gm5zbWR$~Med=4{cxoMS#NdYDg` zbJ?F~&J%ufzWF5k3(TiDzL43E8kmdBMeHv&m$1LoT*f&uGsgaMb2-OfWY+Bl<}1vv z9WdWA-y-Dqm{+@_xz1e2neQ{dc1QC|^Go(OvmTUT{%HP4eEwnngZTVpZsEwk&3|*w zFRTXTr~dnuBc-hPWLO@{BN|vLYmA>_-fYuUffn!!rdGp(6oH|pY9Y-d|@L|1C$BDPOj3q{a++IpI6pRu0h z*G1MM;YW-mU5ANPUNCL zmNLiq3-SeVv|PrTO@@rgnDEQxtlJcjFS2rzDObpq?5~om_;t06^Xp5j;$+B|S;xsQ zUtuLDL%zydPJa0st2yP$*ICcWkZ-V}lV7f3O(#RXDc|JyTk1=WNS%CNu4n%P)_n5I59LQ956$+mXn=0}l>N_G`^iMheZe_jvHnw@{91l3TF7tY zH$3~d@>{O@PJYM!Cb@}c{GNH!+o1`65G~M!LUdAwGDT~x5k*ThVnfkPHDdnszG^qM zn;57XGl%+q=*Xs`r)tJL>Vwdft;8tRnmN>asy57^-dD9#?Krc&YR{1l%%eU?WvMK2 zlFC-u;$(Dcf3c4mpazJ2)j&0nee^5)L(~xVhpM6Mqh&>Jt!3GwW!bW(E-S?xrVbZP zwU%Y}IkYVMXR0&BaCMeCOAJ8Qo-2B(^VE49IiGrZfMQ*HF+yu!_R+o~q_wZ;r?oHJ z32Fi%OjHxO>SlGb7_4qlw}>I?R&^`;x2fCMFJzwfR_Y#gFK6Db?q~Y|v$VHTlhtJQ zA5yIUs-~&w?9Ws)**?PD?5)%+HH-bn)Z=1L^@N%$`k=q(3)ah0MMA0tY61ICsi*jL zv0BWp &CFJm6}-O=kWiM`az>SeLFdPTh=jz`D8CXP|BtJj&a{tfkpI99DuYdGgk z^_Cc_-d1mmzhMEq%aQlg`&_kNtrrJrYd{=_HSn<*ran=hh=Wy$DiMdM4Qc~NK2@KJ z6R;A#5c{hy)t4e$eWkt;~b{b^}}N262kE8<-;=yFr|a z-OyEx^mOy|;#aH(k*%!J)wj@Lr zmIT_$yOLSxclWOHt|H9U%tT+`8~4Wf^(AJbukU@C`RF@nYl9VBH+eS+pZ9w&D8|)Xq$s`usJx6&B1YO4q<9*L-f;jhUlj447Lw2 zzkQZ(if;;AtPRe=+8|7<4Pj_&LzvpyU>|FP{eJ#_qQ1XBE1l)~2lxkwEdN0NKoQjT zhuBTqAH0@e8ixuK%R{-Ahk@mB1eNKLSQrKt#(1{ZV`CWNX8zk_Yuv*AZP*zS%Y7yL z+O}wo{Z4)4S{SWacLv?&+8M1`cV->?+S+LCS{toddFCsQXq%%oHvc!ma4nC{SRVVZ z-`nVoJ+Ut~hk?y;F&2ilHjFfD!@$}|*%^jwXEbyziFU3fk?mR%&9Njt!)p23_=fF9 ztcnJ%Rnf+^D!i^$VPREdGvj0c8^Xkf;Ev`HEC~ZkVhl%^1Jkt?ZW3KwE5XD{n9Q#a znh#=0Ou;^=mu4R{aqWW~*FI?C+6VPq`=E(yALL*kJR_Qz&tfGEb*+RPtc2$|vcy~} z+M6$6E7Wssg(j}8kb|x8BEPOMSMcj9^Ch+~V>cMC-O#~&8@s`9?S^{T4b;xA-B8c` ziuE)M*Ltw99{xc*f5KLVC`8y1%Kb z`wevewW6M@<9)7<_q#gY@9Ow0SI1|$I=%xsel^yB*67OB=zcW%I?>8{A1!XUT717U zE&efUdl;@RALZ)ucIfio&@l)V+SEdS8`2{^qFEA4whGP_|&j4@0(+tq7+z`rb&>_l9iG+8>6i^`)!t+oA6}6G9i+ z1)Z0R?l)ZBZ@RkQa&`ayuI}$Ehf8ctc?2swTCN4Ko75J7C2x>7h`#bhc_SfIwFVAw zt%3TkHQ<-exT}mjD;IOsbJzs+}zwZt4~-I!5bn!`0u0tG`+Q1pU32tG@@i`g?CzfA>-?-BnQ9qPbc7 z1kJsdtGV}fHFqypb06kv?tNX&y@#v0yStiu4_9+{cQyAOuIBEpMyL^Jvi;P4Z1-3D zv!!*0H9#ww_-HNO&eh_L)G=uBmTI&b&781Wmp4);t5Z0lHF|$nqvyFAy_>7i`@0%F z&(-JyT#cURYV@9H^lPzNv@UPt>hf-`F5k!1<$YXTzK_znypOBP8@sx^k-AUa$Gx;h zZ{%w9#;!(h-B)E*L$dWY98lk?cO_0yZ3gr zd%)H1JzVV`P%o$#uxzxBXAM7e{DH2H?~0Cp4Xv-W`@ybu-`&;jUD57uqOIRT#}7lt zuN7UG>-E8|UO!ZQufAu^m~>qp z@C3x+o{pZ5;s{rxADO1b+qzo3t!Emm${3!xp82AYr-;>L8heU8tJq)7Dl(1BwD@`t zH3Pc*@1mjh3JAm1=!UD&rK{10xLVvO)8hC8IHy9FH}uZ)&O@(jjo!f3;?2?G=qXo! zOILq4boF<0^!LkUdfIaJwCM}@azsb;bPrL_*As1Rx!T%twYA^X)|RWS%`|Q8FVog| z2nbE<>`uO0eYc91zT17bv%S-Ir)cN9%XgP(?&|V(t}Zvw<+M3{lYI~JtJdhveGjop zjp2LP_b~eoE7cfjI=-W;<1JUm8?KJGT)l3&dcC=;*E{&H@n3`1*V^6hYIjRlsAyYi zPticoVzxS43)pYuxzsb>bA{(xUn}47zR|uDePRD@{>J{6{?`5_SOZr^=5X^w{tv>FcanLqc?xauLv%~rJRj{k$-GGGRgO(XBVLate9@d?7Mm;0 zN6~w)n6u5-Xe&QvuD3$w)99|DR&(^#{#JYIAUuCLx_xTxM{D#(j!m={TF2;?rgc4S z%-1>gruCjRMYkpK2GWW|i+yVSY&~LavHoK{hj*|Pe;?BqTQ8tocg>NcP}gf`%Ec^Iv{74jHr`nWt+uH}EUuFK^Ky8f17+Fzf^lc=q~ zlqXY9{~%A12|SEv(7=^=66>pm@?6hY&sb@DPV<~5FYuhILm*T{L^>%7;=C%qHA6XXJ2w&YWktvlpG?_J)z9Qr4d!O)t0ba=j-b0D&P0*>FX)i>k=kE(j`om=n^J3=<*~# z^DY=-LbZ* z^&~NbFpbVSx$>{%#wvYn`Znu~sz;K) zRyyv^6QPgiUY~n2A)lAUdmhRCx@@!Jxu0`YJeOOQZ<9xU?``sq+jN(lXwHqq^YW}H z+ZzMx*xs6T7uyH2rm~%x^%&cttY_FR$$GJTTf;MUJ~?+G=e(Y^mb^&rukUmS`$y|M z+Lmor$+n%J9)8-j2XrnVoH}o_zRco{pxV#R`Z0^Kr`c<=N`*+KUUuW`mKF=Ewql;?#>%|$$NGDKKt0ze(t>7Fd>i09?SNuZ0g+XOR}$Edu>PF>(8E;eLLHG zbP39SB6~skRc@TKCuchxcekmh(6J4)4m7QGo8|tR+$PUV->?1JD!(RUmAxqY1!7X1 z{W9A%+3yO`Wn%V+?0=d~UwZcU**~%UPe4?Ol_$`EBgt(l&cuXcoon9adOF`lI#k-` z5>DkK?m6e=UdZut0^RgBxnJ8hkQdmu%9&Ggr*mXVV1V8x_iNjBSr9mqXI~JYoR)8s zN79d{A1U7t3=NEOui^+PklYeZ`F{FwcVA0I`5ie@S%+5F5r1Z_T0L&*+P`8ya9kiv ziX^wE1DN~(8dcSihu(@JCd1TwRxz`7N zCd{9@{Ab6uIcAQJ8ntX2_*L&Gx7ClQANgbUtH&oTZjEx9r*bFVhRn&$>CRK6wgGoP zxlKRLe(vU6(y;bzmlwJelfp0L?5(%S{p#E5zg9hx6Uxyt;D(ShJO>?-Q;>5s+tE4L zU^!>xoR|8w?3{5qmvh{;G|GxjyEy=-FtbL2ERG^=< z`ZgzCx8tPAmu||GZ##!_UgyZ#ob{EDS3k3+Bc0C4r3Pk8-CJcp{Ydhh-(y?%yCl#2 zzvjLLzN+HdduI08j}ww}PEI0*^W5j;DME~hG*XHbky4DvOQaNeMx=-|g$QW~A%uuX zC?awxQi_yPq+aAArCdt66s=NJq!cMdM2eJBN-0vxMWl$|f31D?Aw2Z<{@U;Rn*8QJ zYp6K64#Gz6mr)Bh6|jd5cZj7lTVk?jvclyj`zI$8O|mWHd>ZY{ z!;^!P!%!Zclc%sUIS!mEN-fz)sa+A;GG+_fnROSBDjtj6uY)v)Z^?Js7p=e0ZEKf9 z?Ou2`Ige|>J>l9o*K#iR87G}$n|fxkP5sTm8;64muU`tHF*{s#-h#1gLD^M#^f8x-3Vn&^Y>jDC(XY~ z_A35ymbw;?d4>Pl?7hI4J4&)^h|Cq3k6<{K_1FXRiCZpr^2Y0PyC z*XQF&UAUh1pd8FE%kPBL@<`_``EE=n{{K5fdD@~pE>hWA(%tiWf#YJAG(9kX2gy2>9@>`T)w-wADr6!Xf^YdM$GbYhOrpNNJO zv<886jY~V}6kVjgbQLyT!?`LB!jHo7zHa_@PInQfyYw=h@6aolvzl=&;|9jfjN2IJ zGja<53n|VQHQLG2dl>f`I&h>l*&GG1DA8Z9_=qUj|W@ zaxQ-hEI=z7`JgX1Yx|p z0>1~GvxCjM=m_1;ae&UAiL=w*@?-=O)f}gQFhvh!nrl(e0Pd^;tiDq_kS)%$3abhy zBXn`WGLC-|^vZ%4fR_pzf$Iu3BHut^YtUN?j)5LrIE-QzM&Oc#CE&kOumd!wQ&>l# z4vmmqpa&Or1GTr{0O&na@p{DPzK^~ z*;46TQ?|m=!j5h_SC*}?Gx%J#!k&c{p#QgJ<53iH^Qdez0z0Qg&M57)n!`cpa*pHR z1)}-s!%q!_uEOd2g&{FZB z;-Nrioi(AjnsO_TVJp25tiAdY^JIC=@wB}_x-km}|j zag2*Z8yAT-IUTc{>A@UlFiR~YT3<-CHlAp_y^pu_v~A4EBaUYv?9*$`+Z8&ru zr*oRqIZd>Al4$cJ)2o?2O0?x6+QN(e2+d@ECeuBb?m@JwBU&|3jv8|`^f1sJDOB%B zw9d8gaE_j{%x}m13dX*~@eE|C*^G0EBbRb5<`TyaPDsb>w*Y6s)16smS`zK4k;7jnIJFu#B}y3L`i0gqGP ziyVhr4R6;|97n^|OjnbXagu1`B-5O)v6|^_%qkQrb?&HO%20wew7$7nuk>Pw_n!M0+Zj9zmf7UI8T}S;NEp zj?7^_7_2LuQ`UDF8lrXX+q$Er+& zTuiyk#S|)+e>@l1;2?+g{rDhCQuZ<4L-Ax1NB6O89}YcFDa-RLyN+{-FlRS$JWOj$ zORl?I=j6gUo}`#Mx0?1UrKZiNP^}7M0Pqt=3n({yjQ3Ww^BjjYCRY+iuOn3BDIKkd zLKW9SEoPc~i(1N3-8fD;^C!USs#GDSJytK!^%SZ+8K6CVIp%SeTFA(H!@Izg%Sz@9 zRSl$wjj~DT;`)J#r;`3%bYUG zU2-4RN|;m0<*DFW>?HJTV>*{{1Erv?;h5DJ9i_+SG;QKQdlt~qPc@IpTxM+{g{m!t zf8*S}-0mnF#%-&LQ#(p%Zl=`CtxTV0x`F9eDW4nT^T^U?v^A+Z^uFOqLbG|0$ zYi?ss1xXnPnSOy&*w1NlsmzU3a&t3ljx!G^aD4>9tI+ zU|hwVl{BiDD`Dvi$b9Zg<|?LNpg2YkqCJ&Ps}aWo1~kkq;u%jIjpJy12Wf{nen$r> z)LKon-juNhF}d#ACQ8ky;q+^W)@E}$uTo5H2h-!3f0*j19c7v|qYvfOhH_i?<(Pem zzBs~*x@dTJ47iDrV`@&!DjHQ3>shPe&`OqdTDF|hl-w4c5kz}NaOiTPwKAgRVxr|z zqIH{S-R3xhIkgeQ(FSwOl|)M(@jT4&M5v!@V>$j=t_!!6#@bWddp%{;3Lef&>&2<{ z;*=vyk7c?G<9x;{=2sD|FCkiAOtj=XLMvf9wpl2 z=m(z;qO^P$=uG3SK?DnWS1&{4iDN?b=vgsPZWuqZdV<^}k|RfsuT~RA6V{I&Iq4y} zZS?3ztJIv)lgnF-0(O!|fuEBnfaUTu@D6!y^yFL1z^k+QZ7KobdHWm2b?%k)u7Q$A5kFc$Lc-Vzehb_UJ@a^!16A6V&7?T&Q-Fs3aXG zeQm-RRX%A_*{!N8u!HIW#5=jbj;imZ$3{(3gC;#zHAxMvCaF=i4^8~K8Y>==yW~E3 zNFI}?@ILP)WhkF&tzz0aRfI2YOI0V;72nA8!CSq<@TJQbxCwBR;p*XL!OeqP1h))s zm0If&Y9qdJ*{*h}ed>@p=E+s3)H!ttZ_gT<@2daS+O_{Nt>}vXQmvELRqLts(FWmL z{$bh}Z34bdt;f5*TeW%GB5fJIQCW+3fVXPf@dofd?T~g%JEffzLOU+ByS_Nk_O!eA zz6#qjvHyMd+8!P`M5*Tt+IP6;0&oPrdXV_WFatPB4+2Nyiw3*_swaUD>1}~y^bWv> zb$VlTtlk~?HSQsO8QU}Z8EHi#MHXW_#(s=nVI1e7cS?tN#sa_U83P>ZsRRz=(EAw& zGWKU208~toKL~H93XBl9;1{-U6Eximx<9bL*OeJihIK%d<70aw~sK$w_b7h z=6J-#byfi+;G@QT?b+z~nRzKb<$-X|suaK0k@wJhvBV$hr0dFgW~JiKa`>z_iq|=P z&E?R$rmo*`e4}s5Z%X;=QvQ*Yf8O!UR6LV(hjfhzj&BWf!nqZ}*LJ7)F~>Jk<@HuL z<uZ?NJVeFRmg?c{~d|EAajCLFAAnf+AP6##k7_SAj8Mi=-$J zMWRHs<+yid_Q)KZIX<%?b789$nX9uF`>I=QXth0aOXlvZwXG($+Mju})rrjWtp>Gf zY<00!Wvd05zO2klJIl^`HM2uj$E*Xsx&F$mURkGoEB*C>kk9aK@h=W^3e3qGnpNeC z`1blb_)hxQ`#t`hf&N)dzFz)_Z!mxK&9q5WjAufPfim<60gv%uh7w5JTxm@I?i(5A990xB0iToAp#G!H)R9_Euo zDI8`}!FF&6qws9pEx|rQ1%1IFaHvrDN{j4eAkX%@gS2~uFDCiwl^~72_zJERBV@2B z*fv-i>;ReG_&P2G>m=>WhjG;lSDW>plEYO`T!mq?q`f&7S3PhQh1HTcU(na z$)wc$no!wQId=BB92+@_9DGNJoMi9~l+PJeZb)%zgiJc)*Mlhzz5ou+%0^5`+z6fw zE`Su()ES>{cH(8v6Ee>kAu>~(MsSX>9C2i@EgaRCQi6RAT#(gtw+Rd4Y>5P6wwZ*}a6GUE=%)P-28e zQ9^thX7n7$zN!t?9>kn!xHdu?sf|jdKU8=FU9SB1 zH*gMxsTiF_Mj&+czu)PP#i(IW`0>r(%|<(;y-{l1Vw4%T8Xb(=jL#Y6Mkk}AaXU&f zPWbWts2|^r!Y-1Yi?2m*gsq1*xD%%8b^Pt63Y!@zW%=~kz9N*h@R-ad0)E6*M$Cwg+Mck~P)&E;Rr+=uQ*FVxP=zr5M>L2Tu z^iK?7NJGJVjK|Oo!!QlY@ERFLD?W~{%_VI{gjip>=B*)-TgiNz5RXs{r!XdL;S=1qx@t1CTT|sDaV4~ z@xBhOJAp%SFpG=92YrXb3Hp$Cd9aRox07733Tv<~@W-~`4aF1xBzOZ`@OpuF2)u4- zo(dihb_91fWV2rl(p=RbDq57{t&2hfL01ExLp(|k zV}8(zI8T1)nRwY}AvZg6g3AnRXBZLJXQM}EZ=_JTUf@ZxDPyj1eDGLTR+*52W8lG0 z@$?&O=|8*H-h&is!F5VlX>Y-GC1!5P>|}TdbG7Wu?92$-F}nuWltOu`M3^(6FOP~0 zX71-x+zN0j0*52rnR{o7i?$8C5qKklSvOL+3tSc0U@pcA^&CnJA6%}1*$CcYgf1fr z+}<2_HlEQkkVIT(21b2FnuEa0MBIr)rAlVBUgrDW#SJCr>DJ4u0c^dDZu z@w=q>i@{%v|90Vd%D?zrjGw{e2MbXn@*Z`Ij0E;C6B;#Haf}8tChO-2eV2 ze-qK*r*!V$FLaE3{2$uIAzy$5YV6;NkgfdRKeT~EzL*MGiIA1}FZY*+oLz~#5u*8H z{+OY#<1qsHGlg`}rnvIx$HDK3I39;aNFS2%o$#H2r3vZ7D$St~Lj6|xcH<0F`d}Z$ zx7(o+lG3_ADGYzo`S)!M<#Fk{LyF3c35lQoeM@{x+Fax?Sj^Z> zcCg4o2L53M3sy4r>+g^aELh4osJ{mr8NY>XjQ3&j>UXe<@q1Xs_=A32|0CJP)K4;x zIDbshpB4URSkd@EKMlKBj(txm$-{GwDS(kjreI8$i@3il{ zFW_tK7uc;y`bvDIzK*`me#_U**E6s@upZCbR|EU-v^|bHZGPZ_ufo^gAMg$K4f9pv zIex~UhiACqw*vuxiN6dxKonl?gyTtG=I`e3}?-dy6o9&zD zTZnOV2ky3Si^bwuu|#}FEEV4s%fxeHx%$WE)1SJ|`t%hR_5j>f$j$CLM5xeUu}0r~ zy%PSTvpD^ya{fQIX85$VsMd78ur4)|*`cVyV=ii92Q422) zFAp!ExUirEduIsu>9-(64=)VYhi8T7g=;81whJa;XXSp3w7AB4_cm;ckwv~6_?qnW z3G9gU!!Dc|o*k|UH-txo$A`y+Cn8)!ssSV#)N?_%)?xh*d0N^_u>90dAE4ilRllPd z3l^cez!KD*WE+`z3Z;?ZZb*f66F>eyld*JZc;4tGxy*(y!}V#0^muwIgptjztzl)LZ;aeIvn0QTS9ugtvxwg;#{vh37z{heR-RA?ykJ!g=9jxLvqYxLas*=#|hLp*^7k zp(CLap);Xbp}DB#_4hicmJ8T!)ThO`yc;61gF!nb&>QcD6rrCLqWw$I+eVlp&C%#{ zu6C}_F}E`2)371;y!sH@yLQjzaydwguL2SrCj$3?3n zC6Tg7d8AvU7xXzeG9ofI5{V?m&C&7E$bO^W;qBYUR=-lX{=B z<6aE+4z0LixO-w417mq`Sfj)ua4|R=E(uovR|HoA*A}iFTq#@`TnD(0aGl`F;X1>0 zf$Iv_4X!&}54fIiz2JJoRlxOu>kHQ(ZXnzsxWRBk;D*8tgBt-i3a%1v4BS|_ad6|| zCcss}O@yn4n+#V2R|{7MR}a?!*9g}HHw$hy+#I;MaP#2i!!3YY2)77sG29ZkrEtsO zmcy-pTM4%cZZ+HsaBJY!!mWc_54QnsBitsq&2U@bwu*Aw6Ivd6!S;nVgtppwp&b|r z4u+0}PCMg-ourY%ZbxH?b72wA2dVY2r(OF@OF@(oto5$n= z9+L}UO>&}WFH~#b-a2F6ZPT|je<;#S-z zbi!0kbgEChDBdpK0eoP*Jl-|lLl}hd-toTiL2;b8#|OrT#7D%(#K*@c;tEz$$VN)M zF+Lkt4e?p=x$y<@#qnjhS`=RzUlCs&UmM?mD_Be+%Oq@<#E8EV-x1#x-y1&=KP=4n z{`jHz(fEn@>G-+$MO>eWpO0U%J$8m2uv-h$&a{KDqY|?V?6!6pu8Zt;b_cuM?rQh2 zd*iyB-P5kH``d%0e`1ULI4ttlq*FJz2m9Vr-IDqkf#x8`~OTv3~eEYEQ!a_imy~o~fAA${v6ZUCX zfAHGJ?Njzy`$9s%^l)Y(n8-uCj6@*OIuS`E6D5h#M8`yDq^j_YzMK8&dIS0rW{Spg zJRdVI$4Ycd^h{JF`X>fcIQDkGjPO>8u4Jo#EESNQ0->S9rj6-e$?;-|6pJSqW{r=m3I9k z=Bi5nGv1t5`YBkBR{94<2zc5E0~_?mfqyk(z`qy~;2AoTfOh^T4Gv9dFq<_P)L+Eh zJbr~9=~?|NybY|WBV0>O|1W)?fwcfmcYlL@5=|S#wbbxYe2;J4F{8QJgy`tR}f zg@)6u<3$4e;~yKN+pzM^f>6a$Pi>Xi{y+YH6zUu%`PG+Mntmxfgi27m&U3 zerR3l1zzq2ncNF!g_!W#)ref82^$i;Xkv z*Y&eJ^KQ%SL+g;6xs5!SlRCAf^+PRCt=w!;ZvPgq;On_3WO zfnNgs9JLxw1@8v^J!(6g58efO1?j-J9p`N(&i|lCoVK;YsT{@?oCHFjICr}R=X3Ys zx|sBeGq_uER(Bt+OGv*sk?VkyJG|qH^9q~FjrB@fobB}jCyY}?^f8>_-HfxoFM|`o ztQxI`6TS90`MU?4D9#+w198Gvij%+!aAG)tL~q1-Um4B?`+yV2X(W0k&i-!08DT$h zBc3|#V27&<#^wslS36@K*)vvwIqP7|B`ag&FmIh4tBW9*ADIAZ)lVp*M?2(Z*GO`I49~>WszGz^rh7)?&;>8o5t>x$G zKh%FH+OXBzkTK2}hxcwmurgJSr$;ZlFPm~UB)RwHnWg%z5$)j~O~ zmo1djdewq0XzL^EBj82r5>6A97iTP9%ZqoryqWX{y*KEM0Xup-0`K&819tcJ0`~S| z*U;P7i&dz1igyZdvv)Ia3v4D!?@li?;N9)r2mFJVe&Ya|f}?liTHy}1unPH&SSa4X z9?P3pjXWXVg&pNT$szR11agbkL+c@bp}nrXDR*e^cnaj3o>O`&bt6Xc8`SOk4SHwA zcF)ylvi+?pjsC`H^$^+AR#S{$8hccY@f+hERcHLx_=B2moG?C6vyHzPAE|j}z4?^- zHd*#nKZIr9XVnX2(^suCmzmG0m(1^*-&a2}pErM`Hkdy)x2Tt4&3C7I-5PC;R&SI2 zUA5QRYyDdN+WL*PU;V~9XdP7VScj}b>VP-yjjMOP32#Cj^cH)I)qCEXyf>*s-uB-1 z>bLlQq=Pz)U!o{izoTEGP`@Wz)#{jcs&}gTgLj&DnmX>C?wzh+g&t?X|6EP=(0fBu zuoJJw;qDr%9>Hv}K~2Wa^Ydy7Yp{tm_&96u+t_8^sTQ*ySFs*nU_HLbdfdo*+`@W% zjrI5&*5iKG<001LZ&{D;vmTGI9zS3`en@)MBNROJ2^r&T%9YOP6++Ko~~21bTqhizh{E;1`J zKe8mUGO{+ZDe_8WXJlXGaO4Dbv@S)hXfPUy7DdaVow0Y;CptJf>Yue6huOg)%vsK0 zoo--O5W>u;6l>S+m<77v9c_| zda?^vk$tgtti*G+#%{!Fagn_oE5r@H(FrTE-id)& zSB*_f#F}bWVt!&tVr61&VpHOk#LmP%SmQd8I13w6Rx$|7O-0GFWang$WFOc(8kHQM zoSbY(&PgszE=#UXu1{`BZcpw`9!MTdo=Tq2SFqmFI^WK3o8K|NYksf%{`o`m$K+S# z*XB3n&&yw&zaoE4{>J=m`ETU!%|DcXJpWAo#R8)sP!K98C@94@_T39AV3&48!MK9z zg8G8l1q%w67OcWo?wi9M!(GBX!WGyN8j3xb@!@Lh2{nc1Vh?^Pc7)c1H-xu@U&W5l z-tfWj(eO#^30;hMBAJm~?4lM#+C@4>x%$@F*d9WXUk8GpetKBbSIOlpm7UONnugDTS{U^$sFteT|?|^Oj9kPOc z)kJ;^bDuxUXYm%@$7+F!!7L)z{5;&39E5*-Ka7Fh4OrQPmb+!c&h~czH=pre6_KQ!pEf zsv1~OFH-fen%+@8Zk1c*>RV(xUCo2-^n2CQR$r^1de(ZtdO&@Lew{!qwH~q_Qs1Rt z6jIADhnk?CqdAmXX-&1JsvnR|b+yWxVa-s_)2vFZw!UY5PyNvPzV*C%f#z1~Kj_zm z)H>@wt)HlutT(NlYJ;`Q+NCyH`>cKHKdpDHchx5AJ?lO7Q|qwxJG{$kdu@DSmh={> zm%Sz4615#OvYXW}@TsMqON3)Gu5J5xL9w+qx;^s5DGm-n0ATJ=kBowrW?%3JTP zS9`n--Y3-C-Y30J;^_j*?RdJtayyRf0_+<+GG7fq!!sX4z&zMIz zOuqwoJHGN!dO6d#GJP9jdh}cM+Ym82@{N3onxHy+YS z@rZKbJ%?1BctkBlsVs~U3-DBa4&%TN>8uy`G|il-y!7YLG@fCWc2$VWp>LC%I<}O% z%)6`uM-MqTA#~zjnNv$Hz4`CPxiZ$3eEQz%a=a^JUL7JZ2W%Pl%GlS2@%ILwA%Pj| zRsWg8-htnMOTDdDr_-QeO@k8{|`xyrSHJs|w75VOUKfcD^Rmykx190b2 zynZP@PJ~m}{qS3F9!duxE;S%cxuKMv6FLBrF6D+&st%9RbtxwllBrOloKT|NP&Z|y zn+~PYQY!G%vTn-6b3zf83MI-(nJ72ZO_@%=-B34Wq>+|&Qzl+N<5eCVUP~cqcB%0i zG&eHk4@mjLQ+~gcKMel%lqOw@WE>tzITXn`6iGUiD?5^NBi=~P&!Jq|k$B(3kr;`R zx|AzRI(2vh(z2vem*>io{#>3bJ3K8*--0-y!_%_E)AdO2&s6aYl#u4>OKs{2Jpp#zz>(Gd{{Vff2uE45=!{Z!qF~4;;J$53FW< zjBzsK6vkS{sf;y@-(;*~oW|${8fFG#E5=MloF0IW(*qz*4}by2Y(|_UfRn?B^90a0 zFt%pAkr8JL;J0B6G2(0i9GoWrah?Fg2?H?BXfq}llZ^R{1&oD^MU0qS zmGO&=-3c-7cNYI2PD_m!d2X|o5Kk>-JT97nP56FL88ak$qfw*KUIzVMGx!?x2F?>i z8~92Ocj;5?4@mj_kPhl=)CknksFUbjMvX*|LYlt-Hp}h6netb_X>vF56MXDZ-wPA}zkyA(^9x;J%rP4A#Mh_De5;If6k5$1rBSVGG~7pMkz!!2hWpEC z&~SgDb)Ey(Xx|4mYs-PP+V_AB+6v%Id@GMM1;$3>NihUCMGOWuiwA&B;wzNCSPgoP zSP5(tl+Fx6tv^jr>oChQl` zUGp7aqg(=zf zog#hCQIuMvqBfeLNVn4zwM(<2HfmDPEz*1!bd7os*oYN6)%qZCiaG>rrZpU59_CuV z2)ai5ci;@|hrnste*l}cwZJBg`h2an4*0m74y=(gfz#y-V54jRPLYkksq%4Pvupx3 zVNVui(C!6YqxAz$*X{#0YJGrHw7$Tp+5lj))*sk}cOof%9q2~vi9ll-rT?Th4LC)k z^rvZ*d$U##Y|@Y~`bK-uHS!iZy%LYkpStm~dpOC)=&eS#oYqXyNXW)0@Nq5_T&Dwtfo3x(+ zYqghw4cZo99jt~yR@(}ETzdsLQ&D@@s7=7>*!Q7+xe?f?UINa*Z||c0)C;JyrMSl^ z>M^zIC%^`^9$2TSKh4B%86zD*chq$8OJKd&18fwO;|%dOaH`l2oQAh1Q9|)6V3Xjx z8ozjq)Wj}eo!AY0Lc9f>DQGO1FMa_%O+(rvaaAKopVP%dz6{CUE#5aJ=f?BFci~!b(iNFR?39J*;Mo)-Iz?tIf!1=J{Li(rzPFIw2y`nT5 z6{Ry<)uMe*EA(6QFA9CwJfkSD8LAriq(UDy|EegRsftQCO-%td!z=tX$$d=ofRJqDaFs!{J5sPSX$*NVyPH;5_h&lHcsuS58F_G=}L z!!?rH=bMt+ZMu9BSTCt18zr^g3`zavNl7g{RiahV*Qlp7!yb<^z9pXr)=KIf4RQ^z zPEzl9LaqfqF1ej0wfuZ-4zNah3iwU!+ra7Cw}AE9)4)cp88|~*0(?@V5pt^b4D}!l zqo|dkVO+H`wFN+*Mq|h{?YqEcZ87j$+LORqZ5gmZn+vSdmLleZ+I-MYX!C%NYu^E9 zi$^I6EvtKLMvtK8-z^_AXe+qvl;`M}7jqDBlro0b0U49u@ zFYf_1$}a(D$UeZS@{7P}axk!2_6Ih}yMeXxE5HWX3s@&BfKSNoz?oVWutv)Uep71= zoUR3c^;!_vs3oZXXi4fnT0Zq3%?JJrt$=!tb_3|C+KsFk3-mO+wnv%?fs?JZ0o|l! z0&6uJ*r3J0|ElH(U8hAs57%-)KcR&|Z_&J6j~*qz%6_dJ%6@}{ zZ3OUV%D(WYp*{oPH^GN}0_tIM5c{=qAbipu-fM?HP2vlF_;tcSPBruvH113)Up?M^ zR_6PH>eh(e9@G#!e5f0Cq);n*3m*3*m9+``N~jO)iJ(5%PeDxtY48a;{kP^|_Xq6_ zt0~HuhaD@_M}7pXk$*$_2j$1W_v9tuAw^o4E-wJ<y_bQcbs-nDniZ!mN49yC)u(DMK=q5$wsZ~^$21RXIr>HJZ;4}awR8)({6_tOU zLd#(si9jp$&_|U0TG&RR>jttm!S{0>`?aw1K-Uc-#Qsd!R$=Yq?FH68));qUS1(I^ zL44Lq<1X0)?`FDhX0E#YZYFm5vsL7pcVNhh!sWMMGPG8B6UKcHX4d65VYKts0|uTY z*jphS$vA*4eyuY4`UxYA}QaAKN34R6d!vbDc^}dQg`Cho)3j% z4h)BcYW?!?E=2VY~~Bjr2svFDMxcH(2dgRg1Vh~)dF;?v%dbM3^Z9VF-4 ziH{u)zQ+DW%6H;pZzFZ>#K*pd@p5WqbAOr|nWhG$so`m=Uz&1dNn1`T*gr~Bt}N-y z4RvKnZw`+%=TfdL?VGtgSC%yB@?2TcqQfISx|AzRx^#K2Ea}tXkw#t0l_jmZJXe-9 z>+)P#(yqfJ{koJZOFDLWuB@wP+G%rSiE?C#a%72eWQlTQiE{F#y*XFb)id#2Sy#`* zb7fsUBUW0L`Y4r;c&;qX~)|U0LkYr6gQEQ>ZKJ>X~*7-7-_(b@Fia zOrdVRuAYhK*4NcDYLS-3K3ysmSI-pc=IiR2*5XdSL^=5q<;W7{nRY&1Sy#`*b7fsU6VH`(^^CBztgC0@xw5XFiRa3?dZv8_H(yuJ z#B*g`JrmEBb@hz=)3UCfiRa3?dM2JL>*^ULPRSDG=$R--mMBNhL^-lVIeJDN)3UCf ziRa3?dM2JL>*|?yiJkfq<>u?^nRsr#uAYhK=8H0LAA&~HvItM}Tv=Do6wj4)^-O!x zZoaObiRa3?dM2JL>*^V)rDa_`6VH`(^-MfR7P+R%Oq7!^QBIkOa%72ebVZaa>*^UL zPRqJ_CY~$n>X~@1tgC0#F)i!rnRu?Ot7qc5vaX(?m9(s@XX3fCuAYhK%A)+-9?)Z2 z*3~odTv=Do#B*g`J%f{$b@fa7%8M&rq zT|E=em38$@JXhA$Gs>Bkb@faWXX3fCuAYfEOkDj1!#th7!37udM?te^AXXSt9v)kbeMvEC f{Df-kQcBFI3GpWrScwTfFIJMibrqkS6XO2>y5>{E literal 0 HcmV?d00001 diff --git a/app/src/main/res/font/inter_bold.ttf b/app/src/main/res/font/inter_bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8e82c70d1081e2857ada1b73395d4f42c2e8adc9 GIT binary patch literal 316100 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�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

    6=3(=QnPeuLDdti0n3-y(nd#Q;7fbjCmF@fSxxm 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

    ~n`n;o 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⁡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 Ag#Z8m literal 0 HcmV?d00001 diff --git a/app/src/main/res/font/inter_medium.ttf b/app/src/main/res/font/inter_medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b53fb1c4acbe100c7a91f07564b7f1fa2d5bab12 GIT binary patch literal 314712 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/font/inter_regular.ttf b/app/src/main/res/font/inter_regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8d4eebf20665d5ae746c622a2bd42274b54d2bf1 GIT binary patch literal 309828 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

    V7T@aTyAqtk8R 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

    NU^a~Y-YT{n`0xQzR2cZRm6M*N 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

    qEIEEraMLk+K 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 z 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� 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 z zL$qdD;hhc8)?wmXqnRGfG`B#jHq#d}|3c2oZOpunIOcuC!P{ztzav_?^`FQoXE3s? z)DlXgCUKhIGtGCRTE?8;Qa#oE@Z^ejSeZV?HTSvr!`I;ZD5Y7S>H3`OJBroZy7XgA zPZnw5Oy=0Hh@;+S+{3Zxa}c|aV)3N|BhmJGMBC>vy_xBB7dhoBPPvNdc}&kET0PAr zKSUfghdF%p6}KOC8NCIgxm7E-KK(h(NaCx3oNFeb$?wakffOsh8V zH#l}U$Bv_Nj8}+*cbF*6jg0qm`unLg)z;-Rr@M6$obEpm-sS*}5#MhF{T!!U$7SBZ zoMpt(LzzC0Xvx=F9%lM(N+}00UBx(#4-78C+B6PnMHCo_jCICoW6)tW^tg%D~oBCp}m?o_H5=%V0s+WZ!n(A z=}$7vJ&8S^>8X6gHN7SQ~Mn6xn`gu;zvQqOo7oU+TWBzk=?rJmB zvp9VkmzKtDiM6U~M)ZkAzI3FV#{35u>vCyzneNLyZGWOAUlHZ@z;4TILM5y2ga-Ha z2KO_n4V7THbz{3iea9)ehg95_3~rf5CGpJ@lpgPT0@am#+_qeYE1BbG+E09A7?(Vh zXtO299;W+F@tvsN;`3F^zm-s~H!`O?)7^v#K5Il146M&}vpfsKt# z!1Il*z$V5H;04BRU{hls@IvDtu$gfLc#(0e--z=X87F|}N!fq!FK?Aj{~>qWF8u?B z_339G8GJ{-!7_(2k1;;vuG@#n!aIibA0kH##g1%vC)K3!oxdD1KsLMc&PM0SOM#7L zYaren2R4x%@4Tz;owD1VcMZK$_8d;R`j*`?><&3l+-7Vxwi~;R{l;Oub$(J>(l2Yt zs5&MK@m+X{Y$`9sms}n2?s+eKk2L^c2*L=2F$fb7rXkEin1`@PE;EH(g)gzz%gu7T z+%5N;Ir6YPCQsrWZ%g^l_}5ZD{zp~eY5x+{R9&jts1B+dJWljd1Jn?FLp=s>q_0)e z)GRd*Us5f@o9b)Tdc3E;UF}x;)nRo=2z5ZHD=zOEHyd7iUHiB>tn+o(#?9yX%9fxt z7~*F0TVQW|n`7WhSoA3P5;hCi58vJ3J!PE)-hvT=AqMEiz*{xF*E~?S0{)WA>Bz_$ zPXw6e`$J?f&DJ_R5Kvps0F@7I2Ybn&lgQV+u)k`=FrBs326e~a#7lvv(N`)o6`Yz8n(_lmv0YtzhlTK zKo0P6#wKm?4Ha+Gb5nde2JsiA_+@T>vsY@{(=FG_XHH0^pWyOYZj`@y)jrolUz*zA z=x*^%D)X(%)OLAlyEnBx?r!Z=I-6vNa;+im*6HQO^Hl_2ZAtN??$%D#*Vo>ypC0IL z)&A4Bnyv%# zDgKree?e-yhmR+5uN+8GM@;Eb_%1NalxfZ|7vL*-nuo{`St3W&LSLANZ&#xtE|Q`^ z6pA8IpVMBJ-a5T|`r!2P^qDmlq%Y2x?H^uaWsUXeYtpx5EUPiB#*XxTH4dg9uhFeW zMU4|R`q!9|?$1b1k7vX)UQ2JB(IjK1e`=tAM%#=d{)K@t!92g^UlW)eY#N-L(KBPH zKkVP;Z|px5SP?J-n}VG)D*bH(VSleclfeE!`=AI8fycogM2sYXyMeyd(Wn*O5k9^r z@NFZ$NeIA3$rLql7PWC6b#NvjoJ#~}6T|r=xQzHeRYul-q?GLcNGUmID}@^ez(5aT zphv*KuiDR`KA`Gn(4AQIGoS`f{S2tjQ$K^1qqlF6B~$gIsew~JgT}N~Klnwd_A{uT z^nOOxK(f`cDslactd3$AV+L>neRGvH71z_qqCVNk8bp>~Nf=ZPwg_2h{Uy2B;_hKS zb$JnireXC8d$G_-HUUR&&~)-O`PUK{L%;92J!dm#2&V=n^VI_IL#l3A^= z7lyx`68o1zW)97cXCBLr<47VKU%TQ+M%G5u&+S!erZ}a-NV@&kO(_n(Q_q@^iIm7u z8$2Ux26EAvy8TlFH(lm5VdT0!M0$!-0nT2|M;s%oJ_4ODl>{#^*dzJuIE~X3oP{aQ z4VGbDRMy}^*9C7jIdnRBw% zWR_>P&+M4lKNE7v+%FtlgDdf+>CL*2F6ZkZGxvep8r(McVzn*4S9Pxv^k1+ioo!|Z z9EqeRgPrNPnU|*ajm%16qGbGoi@83bRJwxTF{Ggyo|od}2HDC5r!jMk%(g;j7Pp?atrRZn#jY^Ix4Z`DWjO_kqM_<}7? z{|CzX2z65_nv1kx-kJY^+aBxT46{T4U#2#&8d~RACDyrCBkMe?v30(6q1DW4YBjMg zKurdT0KRe#;Ol01@6tK=rnxpelGMSSaE~6vUkJ0;eb zkJpv@UX3rngUtd5vW6$dkWCdh^wWQ&dQAjTas=N-$B_FRU82v${dTV1$Ue{hr9H^L z4c3ng?iQJPjs7FNVl=ldwpv)1SeIItSuL&0tyWeW>niI?>k6wi(#;V$R?LdSw?-1* z;TBkR;aQ`eRrFIlq+TZC;xlnjd@c@&FT`Q-r8pw~DvpY;#Mk0);+XimI4-^s--_?V z3Gux+DSkjlW*E{?m`v4};I+}i%3!I8%eYL)q|BEEvM%iJdhp*?48Lvg(j?D;Eq<yf*3uQBW71~^0EL+G+V5g%`39S zMsPRe&J-o5-I?w9&h3qR7hj3%{`wX@K;Me{_)$F<-)zs*kL&sRH+li?;Dz+9Honwe z29F~zTd$DE5q!1$y0xCZTDIP_Hd=34@cdEh$Uk+2foD6$*^iLjMwI-!*Xrl*KX`eP zxeeiA=ZSwpPXESPPj7Re{xkwSjek^?{9n&4I0f z?SY+v-GRNZ+zti~2aX1g1-=cO49cJtObhyhS;1Q9bE3gyuuvH1cE^#I2UZ5253C8i z9C$6TA+Ra1C9o~9Bd{y5C$KMY0DZxcz}JD}ffKL-&7c!Z4+eud!Q5at7!MW%i-HY< zje<>r&Ct`e47Luo4Ym(<40a865B3c94)zZY3=R$s4Gs^M21f*)DTJR#DcZ+$e5~gITk3)@G(zfzRsM? zysV*_L$Z5j@5~-jCoOAK=J4$1nPoXeS-YSiqv(Z=eAqVG`=J$FZD(Zf0FSIR#3hd- zZoV)gG&c*IC3`jAwl}hA_RdSa1l%P_(&t3A3b`6#+y{; zLhQFkuCz!i<_PRJ7b?;yIRjfRClXDTXBUBAgni~@x6E#t90E?u6o+z^2$fxuU6Sm{ zX^dzVcwx|_<7jS_(!h#~x}1w#PL?0XZk7cvHHg1}YLqn;W3U$34y?xO35|aUysp)F zZNb|OUdt+;%sPo zgViRpM7SCAP*d2HX1E}?BryRL+`PmvvP%)@hbxi!S{B)=7EzJH6mNM163Fr- za3RuBdFb=AoUp6;A!p)cevN#S!Uy?0;FAwM;)+bP$jns~i_jK4gFGME3*0SuEGwsx zFoOHR!#2g!wae(AS%z=qjLb4{-5U0fufcwQSZ>M8WT*#oRc3l-dKlL+vlRPOLbFtj zU^AdC_YHSv?!_r?dvMza_k>$9_p%fh*EYB@xG@Z?8zo!;t_-fUXJdqV3^m3UTs{N4 z0=zwloktY7?Ktfu=x8IDL|WGZqqbt$-M~vn+F?YcYG&1%=l+B9VkkQ}4}3a1)IA7q zUcu$`4=v*KEmHj1;LpatVQ3ojTc-Hs;FqJ{$O(;Lerxbm;6&gAYG_!Oa!u*DSA#DD z`z+i?R-s3Me+~GCwH(2Ze47HBhz9>!E@x$6rG+yObU?f-0lNM&FxM&vx2Z=VJ>3U} zg|;5R{U4|dR1yt-O6GwbLZk2Ff8J(}xdb_I#(}knSI-1#4N{K$LPKo zu)chXMvm(^uCKo@>Stt}KwOGOTuK)H6~gwfNd4RR+tgXirCudu%@gp=@+SPdyrnn6 zd)sFCb=gAR+Q^R!{_xcXUoJcJ2jr;@{#Rdef$x@2^#T1E zd2rQ-m`9w?QuNv4zksioFZB_4Z*#Xt^-=wm{;P+mT`$z`XY95!EVqBob|ZU^thpT6 zaX)3-Rgd@VJZ~GZBCgGsviyviJ;k+w-5HxRwq@+h*psn8<50#?|DKHF87KV*{H8z6 zf5d;>AN1D>2+YOMDfkscxduQftG=`{z3ks{t^CBfsX!}{tD>yw!yCc zN&ad6ndnD1;BI?Z%odM`IpR?iO0o!`S01&KReF)`4JZ80K8F118*E6WL|f% zRImQ28veUhoPN=m{_l+$er_!Jb7M#~NZqMM^T@oNRg97QnfCuc9=lEOMtN1Ef%&J6 z1Ac0BHScV;J7!(ZsT$GUY7eX$)nPEDFar`o{}F;V4MYEi{Rp)XsJ}7b>k4Q+2$Z&E zW6nJ659c7Z83J@EQcPof6sU}8YM{D@=7HE7F#0LX-qeCGopT^d}u?SHkw+^&t6$4F-jN^9FzKLj*)6zJO2g}3{=!Bim zGuI|Y%>{O$-G}*EI^V8q_r?(xAi!2b4W)6Mwnj_jm7!Uoxxl#;kK=WsGr*ls@6@Q! z?9lws3`z?>U+~I@c)zBnxjHm6G$u45G%Zw0<+1lV0dGLpqo>6_#=GZZT8#Wg*5rNi zCMhr@(h0M0c4$(lG*lky9U2@O5E_Pfg;IkkG)w1zuBG9l632AZGw@H=Nq5oLW7O|T z#)03m7VuYg8F}z#oH{Bq@5${#u+KhI?vx*6u9uD* z*Tpz)L)}oMq4iDRCn4$ZIJ^|kq@I(j@jU8fcmaGJzRus&Yedb6jKsqm!~4Ut!pp-0 z!eheC!yUs};lj}2un4UUZ4NC6Ee}nGM0bmEh8UQ^pqUcLjb}p&(a!4P`WK6|8GzRIwt;py;6`FBX~$8#>H>OoR`T|kE7L->xwOek9XahU^|)`F+#2)sGg z!HjrecxbpRTp6Ago*iBgUK(B%ei`!G7Tz5`5I!0{0R<6^dvvA9BTgbC}xOaG9I2=xj29d#$5s~u9(I(MLqiv&|qdlVoqC?>)rZPG$Iy<@`x-_~f`f_w*q(!7Pj@C8O zGtxiO2<7HPqLHFVWANKVI!1bcJ0wyXsfbLC%!n)S?2jCYd<*Bq z{%CGA8EqJC8f_VE7wsDD6&)BI7Ty;=6#g3LWk%A&yCD;5J5K_a%B8|W8(IdxlF!NK z;OBC+Tn&GaYvo#awR~B^+c0~V)M#6;!}-+)xdGGdZ^}2(CfA^);fY*i2f|K-T?iPZ zMD`%;Mc9Y19|87O1Qvhf5W-=EBM3(kzD9s;8^Jvu`4$2FTLk?ZjhdpkgQF$_?k~Kw zWkhK-m5zXZFN%AIMqE+cJyG<5(Od+KQKDglC_)?|iBN!0h){%3AE6;a2|^=;#t2Oi znj$nqXpYbV;ZlT_2(1uWBeX$ii_i|CJwgYBjtHF*x*~K#=#J0>p(jEwgx&~!5&9zx zKp2QH2w^b75QL!!!w`lej6f(wC_@;9Fb1Idfx0k`M!)o0?6thjc{|WA9m+cv5}~wE zPHb~18Y+tI2sIA1i0uuv33Uwh2=xyQLC;+gni`XJPUcy$v^;-atyorGG_Nqasm^hn z=h?3jJu9r%hj5=9=RUcB`{cUtc{)s-BV@EUuCTEqouf#GCBs= zP~sk+44jPXCZp4F#bk6Au9b=&i=K#~cZ$JraSYEbVrlHHNyTzvd9gV5qp^Zmee`-4 z<9@vyqqDB?uy-GHHvH+r7p@=P_w$Vg(5xfi^>z%r-97?8`me*g?K|+a_r*{4l6lpv zxAXJ``Wk4hY5D=T2Zxq=7`~IAwpLp&T5nsMg^n(Zu8gimzqB4SFuEzaHHuLhVRUzN zU-TeoVDt!`3ur#0m=p60O_(|pP3IFUj5Ulk1|Jw}7P~aoT3Cd!cCn7JZZSM@k9Cdp zi1m&Qhz*Vn!ydd7#o(2QeG*x*^4Nsf)Yy#J?AScS&5F&9Er>0SEsL$h-cs^#ME;Fp zc^p%Xpi3`*`Pg_js>(f9%7D5P1zEpF!{xi|@o0HQ*1NunXh$j4cS&Gs1Uf z`uHBpk2jYk0U&p^q2sp@2Ph=%>kuEI}Ow>w*6UjtTq9oBI z(Hx~p=;$kXJ64yYEx}T>=0iWGol2ExnP`(}pXi+EPVt!Ay&CZ~5|@%sokYh(w?xlG zU({hpVtAqqC0W(281K z3Rpj#V7u95E#W!X4f-ZggZ-mtp(V}{*=T{!h`6=NdI8VgVOyo_D)$N6uk`{EOiWEQ zPBi=Jx+TgJ6L7v8_1}PR>0`i6`tQKE^>N^4{SELP{Vi|{-a(fb9e)pePoD&C(?0MgSH07M4W>V|EZ{B+mZ<)-g}E)g8?Pcu{gIUh{Mf<_j^1l!0RMt{ z5~=rD0pO<=ED-&fg|!6qA=s)?e_`bU55vE?)L&Y8z#~=&Sg!8{eq}|0f3?EEqx4Jy z()mXc?3t2aCQC3&KMmVFcA6YXSHG(p*C`U$j@*w)qut(w^8>d&oO*oIT-+VHt3Jkg}HA>X>G+QaAb zGr0bA4hFXZlUo6K)$fF>ORd1itstFSK{lU54%a@xt)Kz5qJO40MvvzgC9ss^@XMaG zlcJG*6UHCs!D@-)8TOm{Yqq@W^Yx)|NCUn`CTvpoY-v1l4qq`E50&s$GhwTq%U4e0 zuE4d!6TmiD6W~g^f_N5q73jz5s^O{Nm7sq^*ACAIuK>M(WMEx@=WRBg|3Qv;+SU+H z<}J4&C+o8jr~8v2|6<*3~&|U6t@Cy(y2<-4#Htpm8JC*C4OqZE#1{5m%x0C$Itto~mOl zi9%fn&(_5lBe%yfiq07C!N5Xm>m>>$`Rc!nz4Z5E|Ms_|rClL@gSPf7v5-BwKg+({ zSHgdLC$ZTaXHF1*=9OCh!d}@w;k8)4XV2;<$?LB8fjp&SS_3}P4TF548%jIcF--Dd zZfJP9{gYuixlSF!A&<~T8hNcXYB+Z~cN!VaBnMuS;g|MFBj7A|mK)j5PUk}-$5-Gh zG-~8pHGV^}h8+ocz)n3EWfdfA*f*d)i&q z?Vq}@s@sV__4Y!2^qr}95UMNz?~NABzohKR$I%;N93rm$ud3VF(`qNhc~+@~Q5M?l zTHq`8E5Ntyw}J24SV_U&W$y-lY<~>gXYT_Zunz!_*k1vU*?$LqXMYET7h^o1G943W zI}R|-Ndu-k=|I2Z2WC20TgbV{xd?cja~-g&1N#r&wXwR4bCZMlGI-g(892hhxX>wc zU~f4UP6cp{gAtmu)J=o@C1FekJ)4TS*EMFAZ z#McCPnXe_Vm9H(Zoe#5yzK%YOLVY8BBY~@ZtAT6a+1v1K@<9T=Exzr*Pkpo!2PB0+ z@5a@@9qeEf^1hfUKENEyTNsT@6d%Id_vc0rT0g^Bqgt!h#vAHQ^_Hr*+_I%s_< zC&AnMH*%Ui#(qFPO#Y|kQ}93ih+IORr{!{cp8dFd#(u(nLOyFhX+Mj1@t?ET$XD!F z?M?Dcr=QbLzDHi7iO!) zKl_S(#d5dr9N#(eBVQw5W4Qw}SxL3l?I0gSA*LM_}gpNjZ`wSjiH+mnHZxW|=q1*(}FJEXO4* z$ER72t5}X}SdOo=9N%X-?qE6YW;uSua{QR(xR>SlCCl;eBu8bC9F-(FDn)Wcmj^j2 zo8+h*lB4pG990_0QPm(hs&ta0${;x^Kgm&Lk{new$x-Ey992z{qpC%6RJ9Ee?i%ie z8Ma~JQQ-;U>ESuyh2dr4=fms5o5I_}d%_1XqjfUkM6x2`NMWQ=q&eo!Iz+lh`u?Y8 z<6s@^hRt#m<8%vS^E_BaB^bN5f;G?$Bh^8%;juBXNf?#Rg?`2i1xBEoG2+}CI}|$> z7Z_vaU}RZ@@nj2(B0FO2*dKbfG+u$x;;i_5j1X7G*J4z-HNFda^$13CCOpsPVpP^J z(G(-Hc8RVSR}D-I!ri{l2=&Aq%;3;)%n4P7reY3$E@p(5 zhE|5wgkHmp(6-R7(7w7=z@C`82~V7GRFMNG!xWcLVWTj7oZmMKt3r z7Guu4pIBlKz|8mWaGt-YvRq=AslH7174={vj~Ca#PF^Z*!#wS?;vt&v7mwg=%eCTh z%+S6n7GQ4nW3fnn30vk>*iKg(xme}k8l#T7R$XsI@to@hqaNOt{Dn~j?LW*YhGjj$ zxCkpCY%to>+B?SmuzkKT9>H65-^;$%B5RTCXHT~ul>P08?1%6*=Nx;EycH{HJSGR) z3+>;^LH0^}r5sE?{N?TTc6+-VV(+kb$UE#0?GNQp^6D>tW&dFRAcs462~XbT;N>Mb zg4Vi{BVi3iWGVdo7s@g4?cYS+>ojwk$@|H}znlgS|JTX~osLc?`G|9abAx=8*3gi1 zom-q+^Uj~0Kg$=LJASp7FlO<6Rofz-=?)S)AHu9Oy|K{!;G5*kdBm@;w`0Ji4@z3m*!20*3WduznL-F5-;_ z)=SJI?1isTCFcE_1Di2@9@FO|rK!);=Od2Z2jcRKTyO=xDM;QdK zW);r0HZn~(*Ls6-17-qdV2`$sW4oMFJj2`vs2t2Tq8w|Pg>vw|B=X{&$)y}W!+JiI z?gi9s2L2D==ydk<$1^|c)l@on`?&R1D!sd%%khuv8L6!s|2S$+@l=~lUb$72$D>qk ztd|buqPtVDrv408+8Hq(hrUg6 z&#`*GQ@m3$aOIGVCxmYL(~nf0OK<*rX--ddI-kC`I+gD9lxM~W*nrj3o}T*0as0i( zS#rQ)J>ySug8lrXe|5S1bUfLpKhAf04Sz0$u%1FIy`HfPP~oXA?a|iT@5K93QRI1CPay`n8Mb1<#QEn_z zUaVI#%B_l}+EOXlR^|0dCY~FMxKu1rZplP>v0lma^xKQ|N=6w~dA*W}*U5T~dxzIk zNRpjvy$;FsNo~8Nwl}A?ol@Ig*sf1W(xWJk%cEQ_MfqHca=Mh4w+|nUczyVIF6HIz zgZDk$9DPtzkMi=8OkG}=s=OpqkLTqj`FT7q@6A01yt_U5X*H&-3^=Bm6mqrZ03 zk;Fa9%S#gXc;0bIx*pHV+p8*Xud3trs>)0A>TZr+Re7;4nx1Q8wgx)qO2#&fS24C_ zyqd8c<28(!c|m%-#Q^NUi1i{tcVxud8lXEfc46$wcs*k`#v2&%1Oh34!PtZGMn-(k z1pZBon1=&>Gh=VYK8$@C`!V)syoC|Z=#c(a#(|8#WE{kJ8{=Tc+Zl&2V$E&j8p`-9 zMm*mG2k*cGhcn*AID&B`V;SQ;jHQfsGmc^$&FBMKb{b<1#&kwJJpdn14}f@j01Pr_ zGU7=BIN6MNo&dTgV=czojCi&HejUap2hS6Lc%A^n69!<6G0vD^Ofu#(7BJRj zEM$Zwj$B2I#fJ5rv@*oYA`2jDbjJfE=%;{}XO882jP#t16~>0zY+ zFJ^4Pco}0$#!DD4WxSlR6(Rcl=Hh?hX{q%z+cwJxp|zxSFIGU6Rwce4l-4){tkqXrb1Ghaf(VPRa9D~dJ;HAEdq{L&jLrQ70mwzSgO7y4p!cC zwbcVDit6O@P%EaWC-f*UmU=yxNBxyYd9lRzK!#I5nv^1O-+8E9V z?G0xEzBNTs%1Vj;$LWjl4so6XPQ*A!TGQp%z*2lKO#HtAD{1BzvO=F@Rp5?ju~0dcZP;`^zdHzM^s@ z)v!X|1iVl71dfzc^C=Rwg{*KckjYlirD_|nLj4IiPHhKHQSSjO)%$#2zXn|@9t4iT z93)bT>A-Q~A>c?c16Yaq0=jD+1y&eyfRhZ8;eEzj;7DU0aEkF5u+o5>Q8&s}DyIR* zNGiEP-VYonQL_Dwd;oN%r1D2elILVerBz6}M&l&OcC@7HGDXrgs+5o|%KQ*?sr)mr z0wZ)f>s`Q+ayM`ajp2}T51;kZpi9;7faBCtz|rdWz$t1Ouu@T*FH_5b_Znk?rN(&R zSYsTp!YBuhgohJp-DBJfoMKc0D={aFI;d+wm#R*{vFbWth3WttsX7AhQC)yjRA*o% z-if62qd-?+P6QHDRQ`QxG;pM%@<%H=`V=(=SgCMaw2gB>mm22+E3h&%Nw@?!(r65v zVw?x8G#UZRjPp5tL(t=mslZZ$q*{R)9J)FzZ3EIqUb+@j47v)H@+7bnuhUblH1Iz8 z18}5Lz$r=sE0qB(Qzn%!kAohs1^`Rd?YMSp)h|I;s3D*aVBUw$>sH`6H3&FT4F*n8 zw*f1ShcK!v#dapP71*M+p_V&<W{!m^%Ag5y#g#(Yk;HFCg4Q17I?2(2OKZydY8)Qfnzc6 zL+x@EutGiq9ETO6asA{HoUQjjqu| zaVK!RxC1yHzFbHiLxE!@l{`jLnH7@CnIy|_eUC`ATl=pPZP-34=~(0BaNvCsZP@-w zQaSfXs^Mrk5;#TD)vuIvtTI^&ESETx6>Wwp~z1(Ne z6~4#7RYhB)mNEtYc%*f|@g%U!pw>}tECr4-sC7&ZGd~K)<3sWVCt=I7Q6{-mmTh zmZ^Ebay1n=O3g*eo78mB6V)`}z3NeL)+p+Q#;Zqw(+zwPPHn_k$=eF!Io_5TD|kD~ zScB~-ob8L)jz_vS$W>~z1Kw?12OMi$4IE=!1FSHv0**5}0Pis_2aYzn1E(0BftALU zz%t_(z;dH4aFo#=IMHYY9IrBfr79D6x2gpktAfBWDhpVl64ZWFlG=~Tr}m@#;Ez)U z)N)i!(D$g?EExy%XuP&ZlF0)nQ`G@osnUUEDh@1HQSfh60nnpV1oX`+8}vjK0=-80 zK#x~p&}&sL@D-y6&K@nwxRJMIMo-?B8}N+){&=G!wxe-AU9hdh79IAy1>vp=}r@R}m6X_&FX`54~-OO5YP{x0Kt;Gd0? zz}=FhFxL1MIL7#ajxM=3mlE_5iM+UDaV*@i2GWd^s590Bn_X#i>{=`8M*RR3ubR}TG17*ZJnc|>?y2Hz;OUryd8 z_1poB-FMT|43fL=rpF8i?_+)=wRO{DZX>ntrpLU7 z^-5}FbA1)nr;6%QMcrIQb*iGgyd*8R6wDu0QC?n>nHTHjCAqmglAK3*d1>Cv<9T^W zf*#MyOHy=sBu9_(@{%k)o|l*8>GDXT9_8gFsd_vwFG<$pd3i~?E|28vQC?n>vB&fB zdUB?jHa9O(ZeF6?yhOQqiE{H22L$A)%cHLuma;8}CxSpJe=bf)7XPiY< zUd+>_O7Y}OvEFe#In!9&JuXr1afx#C66GG3C^s)r?r~`z)6470nRs4aPtL^i@_KTn znNKgTCuib$c|AE3&&%t{8F5v4JvkH4%j?ORcwSyl&NR>99oLgH@w~jAoQdb<_2i7> zSLOBOOgt~ICuib$c|AF!#;LqSxpF4T%}bOkXQJG^M7eUtIacNM`b>KDxiB{!Bd=<~j>&cnYd3ilK)10(-Tu;u#^YVIf zCZ3nqlQT-I%InFQcwSyl&ct)`;;5-Q6XhP4D7Vf;xp|3lWkr;i*ON19T$R_8Gx5B< zo}7v2<@Mx@bF9ki$(eXwUQf=%^YVIfhE%HZdU7V7m)Das@w~jKKVJ{Xu_~`8XX1Hz zJvkH4%j?M*oT|K@oQdb<_2f)EH!pIgj!Tp)XQEs=6XoV5%9S%wUS3bmIBHd1PtL^i z@_KS6o|o5?GwNBD*ON2xyu6;AiRb0@7^)-w;)i2G{C~TppSA!1 literal 0 HcmV?d00001 diff --git a/app/src/main/res/font/inter_thin.ttf b/app/src/main/res/font/inter_thin.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7aed55d560065b7a5a1975121e0b13ac25e798c8 GIT binary patch literal 310516 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

    _l*o+mcntCJeTvro_NJ}L$i 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 z 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 SMb literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/activity_add_source.xml b/app/src/main/res/layout/activity_add_source.xml new file mode 100644 index 00000000..ad96c703 --- /dev/null +++ b/app/src/main/res/layout/activity_add_source.xml @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    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