From f0d27b9a80ff060e0bdc3773882e119e5a2572f6 Mon Sep 17 00:00:00 2001 From: Infi Date: Thu, 27 Jun 2024 22:56:34 +0200 Subject: [PATCH] feat: new video player Signed-off-by: Infi --- app/src/main/AndroidManifest.xml | 5 + .../activities/media/VideoViewActivity2.kt | 220 ++++++++++++++++++ .../chat/revolt/api/settings/FeatureFlags.kt | 26 +++ .../chat/revolt/components/chat/Message.kt | 7 +- .../main/res/drawable/ic_arrow_left_24dp.xml | 9 + app/src/main/res/font/inter_display.xml | 75 ++++++ .../main/res/layout/activity_videoplayer.xml | 37 +++ app/src/main/res/menu/menu_videoplayer.xml | 29 +++ app/src/main/res/values/themes.xml | 12 +- 9 files changed, 418 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/chat/revolt/activities/media/VideoViewActivity2.kt create mode 100644 app/src/main/res/drawable/ic_arrow_left_24dp.xml create mode 100644 app/src/main/res/font/inter_display.xml create mode 100644 app/src/main/res/layout/activity_videoplayer.xml create mode 100644 app/src/main/res/menu/menu_videoplayer.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 502600a2..77d1bcec 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -118,6 +118,11 @@ android:configChanges="orientation|screenSize" android:theme="@style/Theme.Revolt" /> + + = Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra("autumnResource", AutumnResource::class.java) + } else { + @Suppress("DEPRECATION") + intent.getParcelableExtra("autumnResource") + } + + if (autumnResource?.id == null) { + Log.e("VideoViewActivity", "No AutumnResource provided") + finish() + return + } + + val resourceUrl = + "$REVOLT_FILES/attachments/${autumnResource.id}/${autumnResource.filename}" + + WindowCompat.setDecorFitsSystemWindows(window, false) + + binding = ActivityVideoplayerBinding.inflate(layoutInflater) + + binding.tbTop.title = autumnResource.filename + binding.tbTop.setNavigationOnClickListener { finish() } + binding.tbTop.setOnMenuItemClickListener { + when (it.itemId) { + R.id.mi_save -> { + downloadFile(autumnResource, resourceUrl) + true + } + + R.id.mi_share_file -> { + shareFile(autumnResource, resourceUrl) + true + } + + R.id.mi_share_link -> { + shareUrl(resourceUrl) + true + } + + else -> false + } + } + + player = ExoPlayer.Builder(this).build().apply { + setMediaItem(MediaItem.fromUri(resourceUrl)) + prepare() + play() + } + + binding.xpPlayer.player = player + binding.xpPlayer.setFullscreenButtonClickListener { + when (binding.alTop.visibility) { + android.view.View.VISIBLE -> { + binding.alTop.visibility = android.view.View.GONE + WindowInsetsControllerCompat(window, binding.root).let { controller -> + controller.hide(WindowInsetsCompat.Type.systemBars()) + controller.systemBarsBehavior = + WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } + binding.xpPlayer.background = ColorDrawable(Color.BLACK) + } + + else -> { + binding.alTop.visibility = android.view.View.VISIBLE + WindowInsetsControllerCompat(window, binding.root).let { controller -> + controller.show(WindowInsetsCompat.Type.systemBars()) + controller.systemBarsBehavior = + WindowInsetsControllerCompat.BEHAVIOR_DEFAULT + } + binding.xpPlayer.background = null + } + } + } + + + setContentView(binding.root) + } + + override fun onDestroy() { + super.onDestroy() + player?.release() + } + + override fun onPause() { + super.onPause() + player?.pause() + } + + private fun shareUrl(resourceUrl: String) { + val intent = + Intent(Intent.ACTION_SEND) + intent.type = "text/plain" + intent.putExtra( + Intent.EXTRA_TEXT, + resourceUrl + ) + + val shareIntent = Intent.createChooser(intent, null) + startActivity(shareIntent) + } + + private fun shareFile(resource: AutumnResource, resourceUrl: String) { + lifecycleScope.launch { + val contentUri = getAttachmentContentUri( + this@VideoViewActivity2, + resourceUrl, + resource.id!!, + resource.filename ?: "video" + ) + + val intent = + Intent(Intent.ACTION_SEND) + intent.type = resource.contentType ?: "video/*" + intent.putExtra( + Intent.EXTRA_TITLE, + resource.filename + ) + intent.putExtra( + Intent.EXTRA_SUBJECT, + resource.filename + ) + intent.putExtra( + Intent.EXTRA_STREAM, + contentUri + ) + + val shareIntent = Intent.createChooser(intent, null) + startActivity(shareIntent) + } + } + + private fun downloadFile(resource: AutumnResource, resourceUrl: String) { + lifecycleScope.launch { + this@VideoViewActivity2.applicationContext.let { + it.contentResolver.insert( + MediaStore.Video.Media.EXTERNAL_CONTENT_URI, + ContentValues().apply { + put(MediaStore.Video.Media.DISPLAY_NAME, resource.filename) + put(MediaStore.Video.Media.MIME_TYPE, resource.contentType) + put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/Revolt") + put(MediaStore.Video.Media.IS_PENDING, 1) + } + ) + }?.let { uri -> + this@VideoViewActivity2.contentResolver.openOutputStream(uri).use { stream -> + val video = RevoltHttp.get(resourceUrl).readBytes() + stream?.write(video) + + this@VideoViewActivity2.applicationContext.let { + it.contentResolver.update( + uri, + ContentValues().apply { + put(MediaStore.Video.Media.IS_PENDING, 0) + }, + null, + null + ) + } + + Snackbar.make( + binding.xpPlayer, + R.string.media_viewer_saved, + Snackbar.LENGTH_SHORT + ).setAction( + R.string.media_viewer_open + ) { + val intent = Intent(Intent.ACTION_VIEW) + intent.setDataAndType(uri, resource.contentType) + intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION + startActivity(intent) + } + .setActionTextColor( + MaterialColors.getColor( + binding.xpPlayer, + com.google.android.material.R.attr.colorPrimary + ) + ) + .show() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/api/settings/FeatureFlags.kt b/app/src/main/java/chat/revolt/api/settings/FeatureFlags.kt index da014bcb..045180ee 100644 --- a/app/src/main/java/chat/revolt/api/settings/FeatureFlags.kt +++ b/app/src/main/java/chat/revolt/api/settings/FeatureFlags.kt @@ -30,6 +30,19 @@ sealed class MediaConversationsVariates { data class Restricted(val predicate: () -> Boolean) : MediaConversationsVariates() } +@FeatureFlag("VideoViewActivity2") +sealed class ViewViewActivity2Variates { + @Treatment( + "Enable the new XML-based video player activity for all users" + ) + object Enabled : ViewViewActivity2Variates() + + @Treatment( + "Enable the new XML-based video player activity for users that meet certain or all criteria (implementation-specific)" + ) + data class Restricted(val predicate: () -> Boolean) : ViewViewActivity2Variates() +} + object FeatureFlags { @FeatureFlag("LabsAccessControl") var labsAccessControl by mutableStateOf( @@ -55,4 +68,17 @@ object FeatureFlags { is MediaConversationsVariates.Enabled -> true is MediaConversationsVariates.Restricted -> (mediaConversations as MediaConversationsVariates.Restricted).predicate() } + + @FeatureFlag("VideoViewActivity2") + var videoViewActivity2 by mutableStateOf( + ViewViewActivity2Variates.Restricted { + RevoltAPI.selfId == SpecialUsers.JENNIFER + } + ) + + val videoViewActivity2Granted: Boolean + get() = when (videoViewActivity2) { + is ViewViewActivity2Variates.Enabled -> true + is ViewViewActivity2Variates.Restricted -> (videoViewActivity2 as ViewViewActivity2Variates.Restricted).predicate() + } } diff --git a/app/src/main/java/chat/revolt/components/chat/Message.kt b/app/src/main/java/chat/revolt/components/chat/Message.kt index e65acc43..3be5412e 100644 --- a/app/src/main/java/chat/revolt/components/chat/Message.kt +++ b/app/src/main/java/chat/revolt/components/chat/Message.kt @@ -54,6 +54,7 @@ import androidx.compose.ui.unit.sp import chat.revolt.R import chat.revolt.activities.media.ImageViewActivity import chat.revolt.activities.media.VideoViewActivity +import chat.revolt.activities.media.VideoViewActivity2 import chat.revolt.api.REVOLT_FILES import chat.revolt.api.RevoltAPI import chat.revolt.api.internals.BrushCompat @@ -66,6 +67,7 @@ import chat.revolt.api.routes.channel.unreact import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl import chat.revolt.api.schemas.AutumnResource import chat.revolt.api.schemas.User +import chat.revolt.api.settings.FeatureFlags import chat.revolt.api.settings.GlobalState import chat.revolt.api.settings.MessageReplyStyle import chat.revolt.callbacks.Action @@ -387,7 +389,10 @@ fun Message( attachmentView.launch( Intent( context, - VideoViewActivity::class.java + when (FeatureFlags.videoViewActivity2Granted) { + true -> VideoViewActivity2::class.java + else -> VideoViewActivity::class.java + } ).apply { putExtra("autumnResource", attachment) } diff --git a/app/src/main/res/drawable/ic_arrow_left_24dp.xml b/app/src/main/res/drawable/ic_arrow_left_24dp.xml new file mode 100644 index 00000000..3a96b6d5 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_left_24dp.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/font/inter_display.xml b/app/src/main/res/font/inter_display.xml new file mode 100644 index 00000000..cf677387 --- /dev/null +++ b/app/src/main/res/font/inter_display.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_videoplayer.xml b/app/src/main/res/layout/activity_videoplayer.xml new file mode 100644 index 00000000..fc3252fe --- /dev/null +++ b/app/src/main/res/layout/activity_videoplayer.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_videoplayer.xml b/app/src/main/res/menu/menu_videoplayer.xml new file mode 100644 index 00000000..02482fe7 --- /dev/null +++ b/app/src/main/res/menu/menu_videoplayer.xml @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index bb584711..986de0b3 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,9 +1,10 @@ - + + + + + \ No newline at end of file