From 309a57f5a17c5f238144ee897b30357cc0f8ecaf Mon Sep 17 00:00:00 2001 From: Koen Date: Wed, 20 Dec 2023 10:56:16 +0100 Subject: [PATCH 1/5] Added icon based on the system key when a polycentric user has not set a profile picture. Made managing Polycentric profile most robust. Fixed an issue where latest events were not always being shown in relation to Polycentric profiles. Added copyable (on long press) system key in Polycentric profile activity. Added tutorial videos tab and flow. --- .../platformplayer/Extensions_Polycentric.kt | 13 +- .../PolycentricImportProfileActivity.kt | 81 ++++++---- .../activities/PolycentricProfileActivity.kt | 68 ++++++--- .../platformplayer/dialogs/CommentDialog.kt | 13 +- .../mainactivity/main/ChannelFragment.kt | 2 +- .../mainactivity/main/PostDetailFragment.kt | 33 ++-- .../mainactivity/main/TutorialFragment.kt | 4 +- .../mainactivity/main/VideoDetailView.kt | 143 +++++++++--------- .../polycentric/PolycentricCache.kt | 39 +++-- .../platformplayer/states/StatePolycentric.kt | 32 ++-- .../platformplayer/views/IdenticonView.kt | 117 ++++++++++++++ .../views/adapters/CommentViewHolder.kt | 15 +- .../CommentWithReferenceViewHolder.kt | 7 +- .../views/adapters/PlaylistView.kt | 9 +- .../views/adapters/SubscriptionViewHolder.kt | 13 +- .../adapters/feedtypes/PreviewVideoView.kt | 2 +- .../viewholders/CreatorBarViewHolder.kt | 7 +- .../adapters/viewholders/CreatorViewHolder.kt | 2 +- .../viewholders/SubscriptionBarViewHolder.kt | 2 +- .../views/others/CreatorThumbnail.kt | 33 +++- .../views/overlays/LoaderOverlay.kt | 16 ++ .../views/overlays/RepliesOverlay.kt | 5 +- .../activity_polycentric_import_profile.xml | 7 + .../layout/activity_polycentric_profile.xml | 22 +++ app/src/main/res/layout/overlay_loader.xml | 1 + .../res/layout/view_creator_thumbnail.xml | 12 ++ .../main/res/values/loader_overlay_attrs.xml | 6 + dep/polycentricandroid | 2 +- 28 files changed, 503 insertions(+), 203 deletions(-) create mode 100644 app/src/main/java/com/futo/platformplayer/views/IdenticonView.kt create mode 100644 app/src/main/res/values/loader_overlay_attrs.xml diff --git a/app/src/main/java/com/futo/platformplayer/Extensions_Polycentric.kt b/app/src/main/java/com/futo/platformplayer/Extensions_Polycentric.kt index 7737df09..9d2553f4 100644 --- a/app/src/main/java/com/futo/platformplayer/Extensions_Polycentric.kt +++ b/app/src/main/java/com/futo/platformplayer/Extensions_Polycentric.kt @@ -1,11 +1,13 @@ package com.futo.platformplayer import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.states.AnnouncementType import com.futo.platformplayer.states.StateAnnouncement import com.futo.platformplayer.states.StatePlatform -import com.futo.platformplayer.views.adapters.CommentViewHolder import com.futo.polycentric.core.ProcessHandle +import com.futo.polycentric.core.Store +import com.futo.polycentric.core.SystemState import userpackage.Protocol import kotlin.math.abs import kotlin.math.min @@ -47,6 +49,15 @@ fun Protocol.Claim.resolveChannelUrls(): List { } suspend fun ProcessHandle.fullyBackfillServersAnnounceExceptions() { + val systemState = SystemState.fromStorageTypeSystemState(Store.instance.getSystemState(system)) + if (!systemState.servers.contains(PolycentricCache.STAGING_SERVER)) { + removeServer(PolycentricCache.STAGING_SERVER) + } + + if (!systemState.servers.contains(PolycentricCache.SERVER)) { + removeServer(PolycentricCache.SERVER) + } + val exceptions = fullyBackfillServers() for (pair in exceptions) { val server = pair.key diff --git a/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt index 5ab51be8..d4a98f58 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt @@ -8,12 +8,15 @@ import android.widget.ImageButton import android.widget.LinearLayout import androidx.activity.result.contract.ActivityResultContracts 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.polycentric.PolycentricCache import com.futo.platformplayer.setNavigationBarColorAndIcons import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StatePolycentric +import com.futo.platformplayer.views.overlays.LoaderOverlay import com.futo.polycentric.core.KeyPair import com.futo.polycentric.core.Process import com.futo.polycentric.core.ProcessSecret @@ -21,6 +24,9 @@ import com.futo.polycentric.core.SignedEvent import com.futo.polycentric.core.Store import com.futo.polycentric.core.base64UrlToByteArray import com.google.zxing.integration.android.IntentIntegrator +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import userpackage.Protocol import userpackage.Protocol.ExportBundle @@ -29,6 +35,7 @@ class PolycentricImportProfileActivity : AppCompatActivity() { private lateinit var _buttonScanProfile: LinearLayout; private lateinit var _buttonImportProfile: LinearLayout; private lateinit var _editProfile: EditText; + private lateinit var _loaderOverlay: LoaderOverlay; private val _qrCodeResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> val scanResult = IntentIntegrator.parseActivityResult(result.resultCode, result.data) @@ -52,6 +59,7 @@ class PolycentricImportProfileActivity : AppCompatActivity() { _buttonHelp = findViewById(R.id.button_help); _buttonScanProfile = findViewById(R.id.button_scan_profile); _buttonImportProfile = findViewById(R.id.button_import_profile); + _loaderOverlay = findViewById(R.id.loader_overlay); _editProfile = findViewById(R.id.edit_profile); findViewById(R.id.button_back).setOnClickListener { finish(); @@ -94,42 +102,57 @@ class PolycentricImportProfileActivity : AppCompatActivity() { 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") - } + _loaderOverlay.show() - val exportBundle = ExportBundle.parseFrom(urlInfo.body); - val keyPair = KeyPair.fromProto(exportBundle.keyPair); + lifecycleScope.launch(Dispatchers.IO) { + 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 existingProcessSecret = Store.instance.getProcessSecret(keyPair.publicKey); - if (existingProcessSecret != null) { - UIDialogs.toast(this, getString(R.string.this_profile_is_already_imported)); - return; - } + val exportBundle = ExportBundle.parseFrom(urlInfo.body); + val keyPair = KeyPair.fromProto(exportBundle.keyPair); - val processSecret = ProcessSecret(keyPair, Process.random()); - Store.instance.addProcessSecret(processSecret); + val existingProcessSecret = Store.instance.getProcessSecret(keyPair.publicKey); + if (existingProcessSecret != null) { + withContext(Dispatchers.Main) { + UIDialogs.toast(this@PolycentricImportProfileActivity, getString(R.string.this_profile_is_already_imported)); + } + return@launch; + } - val processHandle = processSecret.toProcessHandle(); + val processSecret = ProcessSecret(keyPair, Process.random()); + Store.instance.addProcessSecret(processSecret); - 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); + 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); + processHandle.fullyBackfillClient(PolycentricCache.SERVER); + withContext(Dispatchers.Main) { + startActivity(Intent(this@PolycentricImportProfileActivity, PolycentricProfileActivity::class.java)); + finish(); + } + } catch (e: Throwable) { + Logger.w(TAG, "Failed to import profile", e); + withContext(Dispatchers.Main) { + UIDialogs.toast(this@PolycentricImportProfileActivity, getString(R.string.failed_to_import_profile) + " '${e.message}'"); + } + } finally { + withContext(Dispatchers.Main) { + _loaderOverlay.hide(); } } - - 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, getString(R.string.failed_to_import_profile) + " '${e.message}'"); } } diff --git a/app/src/main/java/com/futo/platformplayer/activities/PolycentricProfileActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/PolycentricProfileActivity.kt index b131b1e8..825ba3d7 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/PolycentricProfileActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricProfileActivity.kt @@ -1,6 +1,8 @@ package com.futo.platformplayer.activities import android.app.Activity +import android.content.ClipData +import android.content.ClipboardManager import android.content.ContentResolver import android.content.Context import android.content.Intent @@ -12,6 +14,7 @@ import android.webkit.MimeTypeMap import android.widget.EditText import android.widget.ImageButton import android.widget.ImageView +import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import com.bumptech.glide.Glide @@ -21,14 +24,16 @@ import com.futo.platformplayer.dp import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions import com.futo.platformplayer.images.GlideHelper.Companion.crossfade import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.polycentric.PolycentricCache 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.platformplayer.views.overlays.LoaderOverlay import com.futo.polycentric.core.Store -import com.futo.polycentric.core.Synchronization import com.futo.polycentric.core.SystemState +import com.futo.polycentric.core.toBase64Url import com.futo.polycentric.core.toURLInfoSystemLinkUrl import com.github.dhaval2404.imagepicker.ImagePicker import kotlinx.coroutines.Dispatchers @@ -46,6 +51,8 @@ class PolycentricProfileActivity : AppCompatActivity() { private lateinit var _buttonDelete: BigButton; private lateinit var _username: String; private lateinit var _imagePolycentric: ImageView; + private lateinit var _loaderOverlay: LoaderOverlay; + private lateinit var _textSystem: TextView; private var _avatarUri: Uri? = null; override fun attachBaseContext(newBase: Context?) { @@ -63,28 +70,13 @@ class PolycentricProfileActivity : AppCompatActivity() { _buttonExport = findViewById(R.id.button_export); _buttonLogout = findViewById(R.id.button_logout); _buttonDelete = findViewById(R.id.button_delete); + _loaderOverlay = findViewById(R.id.loader_overlay); + _textSystem = findViewById(R.id.text_system) 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, getString(R.string.failed_to_backfill_client)); - } - } - } - - updateUI(); - _imagePolycentric.setOnClickListener { ImagePicker.with(this) .cropSquare() @@ -120,6 +112,37 @@ class PolycentricProfileActivity : AppCompatActivity() { finish(); }); } + + _textSystem.setOnLongClickListener { + val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip: ClipData = ClipData.newPlainText("system", _textSystem.text) + clipboard.setPrimaryClip(clip) + return@setOnLongClickListener true + } + + updateUI() + + StatePolycentric.instance.processHandle?.let { processHandle -> + _loaderOverlay.show() + + lifecycleScope.launch(Dispatchers.IO) { + try { + processHandle.fullyBackfillClient(PolycentricCache.SERVER) + + withContext(Dispatchers.Main) { + updateUI(); + } + } catch (e: Throwable) { + withContext(Dispatchers.Main) { + UIDialogs.toast(this@PolycentricProfileActivity, getString(R.string.failed_to_backfill_client)); + } + } finally { + withContext(Dispatchers.Main) { + _loaderOverlay.hide() + } + } + } + } } private fun saveIfRequired() { @@ -128,13 +151,17 @@ class PolycentricProfileActivity : AppCompatActivity() { var hasChanges = false; val username = _editName.text.toString(); if (username.length < 3) { - UIDialogs.toast(this@PolycentricProfileActivity, getString(R.string.name_must_be_at_least_3_characters_long)); + withContext(Dispatchers.Main) { + UIDialogs.toast(this@PolycentricProfileActivity, getString(R.string.name_must_be_at_least_3_characters_long)); + } return@launch; } val processHandle = StatePolycentric.instance.processHandle; if (processHandle == null) { - UIDialogs.toast(this@PolycentricProfileActivity, getString(R.string.process_handle_unset)); + withContext(Dispatchers.Main) { + UIDialogs.toast(this@PolycentricProfileActivity, getString(R.string.process_handle_unset)); + } return@launch; } @@ -219,6 +246,7 @@ class PolycentricProfileActivity : AppCompatActivity() { private fun updateUI() { val processHandle = StatePolycentric.instance.processHandle!!; val systemState = SystemState.fromStorageTypeSystemState(Store.instance.getSystemState(processHandle.system)) + _textSystem.text = processHandle.system.key.toBase64Url() _username = systemState.username; _editName.text.clear(); _editName.text.append(_username); diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/CommentDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/CommentDialog.kt index cc9015eb..f369f481 100644 --- a/app/src/main/java/com/futo/platformplayer/dialogs/CommentDialog.kt +++ b/app/src/main/java/com/futo/platformplayer/dialogs/CommentDialog.kt @@ -6,11 +6,12 @@ import android.graphics.Color import android.os.Bundle import android.text.Editable import android.text.TextWatcher -import android.util.Log import android.view.LayoutInflater import android.view.WindowManager import android.view.inputmethod.InputMethodManager -import android.widget.* +import android.widget.EditText +import android.widget.LinearLayout +import android.widget.TextView import com.futo.platformplayer.R import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.api.media.PlatformID @@ -25,7 +26,11 @@ 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.futo.polycentric.core.ClaimType +import com.futo.polycentric.core.Store +import com.futo.polycentric.core.SystemState +import com.futo.polycentric.core.systemToURLInfoSystemLinkUrl +import com.futo.polycentric.core.toURLInfoSystemLinkUrl import com.google.android.material.button.MaterialButton import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -93,7 +98,7 @@ class CommentDialog(context: Context?, val contextUrl: String, val ref: Protocol val comment = _editComment.text.toString(); val processHandle = StatePolycentric.instance.processHandle!! - val eventPointer = processHandle.post(comment, null, ref) + val eventPointer = processHandle.post(comment, ref) StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { try { 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 index ded1948b..c2549c84 100644 --- 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 @@ -465,7 +465,7 @@ class ChannelFragment : MainFragment() { _creatorThumbnail.setThumbnail(avatar, animate); } else { _creatorThumbnail.setThumbnail(channel?.thumbnail, animate); - _creatorThumbnail.setHarborAvailable(profile != null, animate); + _creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto()); } val banner = profile?.systemState?.banner?.selectHighestResolutionImage() 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 index 3cadb47c..2af009ee 100644 --- 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 @@ -314,8 +314,8 @@ class PostDetailFragment : MainFragment { private fun updatePolycentricRating() { _rating.visibility = View.GONE; - val value = _post?.id?.value ?: _postOverview?.id?.value ?: return; - val ref = Models.referenceFromBuffer(value.toByteArray()); + val ref = Models.referenceFromBuffer((_post?.url ?: _postOverview?.url)?.toByteArray() ?: return) + val extraBytesRef = (_post?.id?.value ?: _postOverview?.id?.value)?.toByteArray() val version = _version; _rating.onLikeDislikeUpdated.remove(this); @@ -333,7 +333,8 @@ class PostDetailFragment : MainFragment { Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType( ContentType.OPINION.value).setValue( ByteString.copyFrom(Opinion.dislike.data)).build() - ) + ), + extraByteReferences = listOfNotNull(extraBytesRef) ); if (version != _version) { @@ -342,8 +343,8 @@ class PostDetailFragment : MainFragment { val likes = queryReferencesResponse.countsList[0]; val dislikes = queryReferencesResponse.countsList[1]; - val hasLiked = StatePolycentric.instance.hasLiked(ref); - val hasDisliked = StatePolycentric.instance.hasDisliked(ref); + val hasLiked = StatePolycentric.instance.hasLiked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasLiked(it) } ?: false*/; + val hasDisliked = StatePolycentric.instance.hasDisliked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasDisliked(it) } ?: false*/; withContext(Dispatchers.Main) { if (version != _version) { @@ -468,9 +469,7 @@ class PostDetailFragment : MainFragment { if (_postOverview == null) { fetchPolycentricProfile(); updatePolycentricRating(); - - val ref = value.id.value?.let { Models.referenceFromBuffer(it.toByteArray()); }; - _addCommentView.setContext(value.url, ref); + _addCommentView.setContext(value.url, Models.referenceFromBuffer(value.url.toByteArray())); } updateCommentType(true); @@ -489,9 +488,7 @@ class PostDetailFragment : MainFragment { _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); + _addCommentView.setContext(value.url, Models.referenceFromBuffer(value.url.toByteArray())); updatePolycentricRating(); fetchPolycentricProfile(); @@ -636,12 +633,12 @@ class PostDetailFragment : MainFragment { if (cachedPolycentricProfile?.profile == null) { _layoutMonetization.visibility = View.GONE; - _creatorThumbnail.setHarborAvailable(false, animate); + _creatorThumbnail.setHarborAvailable(false, animate, null); return; } _layoutMonetization.visibility = View.VISIBLE; - _creatorThumbnail.setHarborAvailable(true, animate); + _creatorThumbnail.setHarborAvailable(true, animate, cachedPolycentricProfile.profile.system.toProto()); } private fun fetchPost() { @@ -665,14 +662,16 @@ class PostDetailFragment : MainFragment { 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") + val ref = (_post?.url ?: _postOverview?.url)?.toByteArray()?.let { Models.referenceFromBuffer(it) } + val extraBytesRef = (_post?.id?.value ?: _postOverview?.id?.value)?.toByteArray() + + if (ref == null) { + Logger.w(TAG, "Failed to fetch polycentric comments because url was not set null") _commentsList.clear(); return } - _commentsList.load(false) { StatePolycentric.instance.getCommentPager(post.url, Models.referenceFromBuffer(idValue.toByteArray())); }; + _commentsList.load(false) { StatePolycentric.instance.getCommentPager(post!!.url, ref, listOfNotNull(extraBytesRef)); }; } private fun updateCommentType(reloadComments: Boolean) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/TutorialFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/TutorialFragment.kt index aab3986e..6d949c89 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/TutorialFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/TutorialFragment.kt @@ -129,8 +129,8 @@ class TutorialFragment : MainFragment() { override val dash: IDashManifestSource? = null override val hls: IHLSManifestSource? = null override val subtitles: List = emptyList() - override val shareUrl: String = "" - override val url: String = "" + override val shareUrl: String = videoUrl + override val url: String = videoUrl override val datetime: OffsetDateTime? = OffsetDateTime.parse("2023-12-18T00:00:00Z") override val thumbnails: Thumbnails = Thumbnails(arrayOf(Thumbnail(thumbnailUrl))) override val author: PlatformAuthorLink = PlatformAuthorLink(PlatformID("tutorial", "f422ced6-b551-4b62-818e-27a4f5f4918a"), "Grayjay", "", "https://releases.grayjay.app/tutorials/author.jpeg") 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 index 8cdaeab4..155099aa 100644 --- 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 @@ -1201,9 +1201,9 @@ class VideoDetailView : ConstraintLayout { }; } - val ref = video.id.value?.let { Models.referenceFromBuffer(it.toByteArray()) }; - _addCommentView.setContext(video.url, ref); - + val ref = Models.referenceFromBuffer(video.url.toByteArray()) + val extraBytesRef = video.id.value?.toByteArray() + _addCommentView.setContext(video.url, ref) _player.setMetadata(video.name, video.author.name); if (video !is TutorialFragment.TutorialVideo) { @@ -1264,57 +1264,54 @@ class VideoDetailView : ConstraintLayout { _rating.onLikeDislikeUpdated.remove(this); - if (ref != null) { - _rating.visibility = View.GONE; + _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() - ) - ); + 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() + ), + extraByteReferences = listOfNotNull(extraBytesRef) + ); - val likes = queryReferencesResponse.countsList[0]; - val dislikes = queryReferencesResponse.countsList[1]; - val hasLiked = StatePolycentric.instance.hasLiked(ref); - val hasDisliked = StatePolycentric.instance.hasDisliked(ref); + val likes = queryReferencesResponse.countsList[0]; + val dislikes = queryReferencesResponse.countsList[1]; + val hasLiked = StatePolycentric.instance.hasLiked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasLiked(it) } ?: false*/; + val hasDisliked = StatePolycentric.instance.hasDisliked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasDisliked(it) } ?: false*/; - withContext(Dispatchers.Main) { - _rating.visibility = View.VISIBLE; - _rating.setRating(RatingLikeDislikes(likes, dislikes), hasLiked, hasDisliked); - _rating.onLikeDislikeUpdated.subscribe(this) { args -> - if (args.hasLiked) { - args.processHandle.opinion(ref, Opinion.like); - } else if (args.hasDisliked) { - args.processHandle.opinion(ref, Opinion.dislike); - } else { - args.processHandle.opinion(ref, Opinion.neutral); + withContext(Dispatchers.Main) { + _rating.visibility = View.VISIBLE; + _rating.setRating(RatingLikeDislikes(likes, dislikes), hasLiked, hasDisliked); + _rating.onLikeDislikeUpdated.subscribe(this) { args -> + if (args.hasLiked) { + args.processHandle.opinion(ref, Opinion.like); + } else if (args.hasDisliked) { + args.processHandle.opinion(ref, Opinion.dislike); + } else { + args.processHandle.opinion(ref, Opinion.neutral); + } + + fragment.lifecycleScope.launch(Dispatchers.IO) { + try { + Logger.i(TAG, "Started backfill"); + args.processHandle.fullyBackfillServersAnnounceExceptions(); + Logger.i(TAG, "Finished backfill"); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to backfill servers", e) } + } - fragment.lifecycleScope.launch(Dispatchers.IO) { - try { - Logger.i(TAG, "Started backfill"); - args.processHandle.fullyBackfillServersAnnounceExceptions(); - Logger.i(TAG, "Finished backfill"); - } catch (e: Throwable) { - Logger.e(TAG, "Failed to backfill servers", e) - } - } - - StatePolycentric.instance.updateLikeMap(ref, args.hasLiked, args.hasDisliked) - }; - } - } catch (e: Throwable) { - Logger.e(TAG, "Failed to get polycentric likes/dislikes.", e); - _rating.visibility = View.GONE; + StatePolycentric.instance.updateLikeMap(ref, args.hasLiked, args.hasDisliked) + }; } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to get polycentric likes/dislikes.", e); + _rating.visibility = View.GONE; } - } else { - _rating.visibility = View.GONE; } when (video.rating) { @@ -1361,28 +1358,30 @@ class VideoDetailView : ConstraintLayout { updateQueueState(); - fragment.lifecycleScope.launch(Dispatchers.IO) { - val historyItem = getHistoryIndex(videoDetail); + if (video !is TutorialFragment.TutorialVideo) { + fragment.lifecycleScope.launch(Dispatchers.IO) { + val historyItem = getHistoryIndex(videoDetail); - withContext(Dispatchers.Main) { - _historicalPosition = StateHistory.instance.updateHistoryPosition(video, historyItem,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)}"; + withContext(Dispatchers.Main) { + _historicalPosition = StateHistory.instance.updateHistoryPosition(video, historyItem,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); + _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 = ""; } - } else { - _layoutResume.visibility = View.GONE; - _textResume.text = ""; } } } @@ -1954,7 +1953,9 @@ class VideoDetailView : ConstraintLayout { return } - _commentsList.load(false) { StatePolycentric.instance.getCommentPager(video.url, Models.referenceFromBuffer(idValue.toByteArray())); }; + val ref = Models.referenceFromBuffer(video.url.toByteArray()) + val extraBytesRef = video.id.value?.toByteArray() + _commentsList.load(false) { StatePolycentric.instance.getCommentPager(video.url, ref, listOfNotNull(extraBytesRef)); }; } private fun fetchVideo() { Logger.i(TAG, "fetchVideo") @@ -2216,9 +2217,11 @@ class VideoDetailView : ConstraintLayout { val v = video ?: return; val currentTime = System.currentTimeMillis(); if (updateHistory && (_lastPositionSaveTime == -1L || currentTime - _lastPositionSaveTime > 5000)) { - fragment.lifecycleScope.launch(Dispatchers.IO) { - val history = getHistoryIndex(v); - StateHistory.instance.updateHistoryPosition(v, history, true, (positionMilliseconds.toFloat() / 1000.0f).toLong()); + if (v !is TutorialFragment.TutorialVideo) { + fragment.lifecycleScope.launch(Dispatchers.IO) { + val history = getHistoryIndex(v); + StateHistory.instance.updateHistoryPosition(v, history, true, (positionMilliseconds.toFloat() / 1000.0f).toLong()); + } } _lastPositionSaveTime = currentTime; } @@ -2301,7 +2304,7 @@ class VideoDetailView : ConstraintLayout { _creatorThumbnail.setThumbnail(avatar, animate); } else { _creatorThumbnail.setThumbnail(video?.author?.thumbnail, animate); - _creatorThumbnail.setHarborAvailable(profile != null, animate); + _creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto()); } val username = cachedPolycentricProfile?.profile?.systemState?.username diff --git a/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricCache.kt b/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricCache.kt index c80bc8f4..931d3865 100644 --- a/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricCache.kt +++ b/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricCache.kt @@ -56,7 +56,7 @@ class PolycentricCache { private val _taskGetProfile = BatchedTaskHandler(_scope, { system -> - val signedProfileEvents = ApiMethods.getQueryLatest( + val signedEventsList = ApiMethods.getQueryLatest( SERVER, system.toProto(), listOf( @@ -72,8 +72,9 @@ class PolycentricCache { ContentType.MEMBERSHIP_URLS.value, ContentType.DONATION_DESTINATIONS.value ) - ).eventsList.map { e -> SignedEvent.fromProto(e) } - .groupBy { e -> e.event.contentType } + ).eventsList.map { e -> SignedEvent.fromProto(e) }; + + val signedProfileEvents = signedEventsList.groupBy { e -> e.event.contentType } .map { (_, events) -> events.maxBy { it.event.unixMilliseconds ?: 0 } }; val storageSystemState = StorageTypeSystemState.create() @@ -151,17 +152,7 @@ class PolycentricCache { 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); + val dataLink = getDataLinkFromUrl(it) ?: throw Exception("Only URLInfoDataLink is supported"); return@BatchedTaskHandler ApiMethods.getDataFromServerAndReassemble(dataLink); }, { return@BatchedTaskHandler null }, @@ -325,9 +316,10 @@ class PolycentricCache { .build(); private const val TAG = "PolycentricCache" - const val SERVER = "https://srv1-stg.polycentric.io" + const val STAGING_SERVER = "https://srv1-stg.polycentric.io" + const val SERVER = "https://srv1-prod.polycentric.io" private var _instance: PolycentricCache? = null; - private val CACHE_EXPIRATION_SECONDS = 60 * 60 * 3; + private val CACHE_EXPIRATION_SECONDS = 60 * 5; @JvmStatic val instance: PolycentricCache @@ -343,5 +335,20 @@ class PolycentricCache { it._scope.cancel("PolycentricCache finished"); } } + + fun getDataLinkFromUrl(it: String): Protocol.URLInfoDataLink? { + 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) { + return null + } + + val dataLink = Protocol.URLInfoDataLink.parseFrom(urlInfo.body); + return dataLink + } } } \ 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 index be41f217..984c6b4f 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt @@ -27,7 +27,20 @@ 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.futo.polycentric.core.ApiMethods +import com.futo.polycentric.core.ClaimType +import com.futo.polycentric.core.ContentType +import com.futo.polycentric.core.Opinion +import com.futo.polycentric.core.ProcessHandle +import com.futo.polycentric.core.PublicKey +import com.futo.polycentric.core.SignedEvent +import com.futo.polycentric.core.SqlLiteDbHelper +import com.futo.polycentric.core.Store +import com.futo.polycentric.core.SystemState +import com.futo.polycentric.core.base64ToByteArray +import com.futo.polycentric.core.systemToURLInfoSystemLinkUrl +import com.futo.polycentric.core.toBase64 +import com.futo.polycentric.core.toURLInfoSystemLinkUrl import com.google.protobuf.ByteString import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred @@ -38,7 +51,6 @@ import userpackage.Protocol import java.time.Instant import java.time.OffsetDateTime import java.time.ZoneOffset -import kotlin.Exception class StatePolycentric { private data class LikeDislikeEntry(val unixMilliseconds: Long, val hasLiked: Boolean, val hasDisliked: Boolean); @@ -128,21 +140,21 @@ class StatePolycentric { _likeDislikeMap[ref.toByteArray().toBase64()] = LikeDislikeEntry(System.currentTimeMillis(), hasLiked, hasDisliked); } - fun hasDisliked(ref: Protocol.Reference): Boolean { + fun hasDisliked(data: ByteArray): Boolean { if (!enabled) { return false } - val entry = _likeDislikeMap[ref.toByteArray().toBase64()] ?: return false; + val entry = _likeDislikeMap[data.toBase64()] ?: return false; return entry.hasDisliked; } - fun hasLiked(ref: Protocol.Reference): Boolean { + fun hasLiked(data: ByteArray): Boolean { if (!enabled) { return false } - val entry = _likeDislikeMap[ref.toByteArray().toBase64()] ?: return false; + val entry = _likeDislikeMap[data.toBase64()] ?: return false; return entry.hasLiked; } @@ -316,7 +328,7 @@ class StatePolycentric { return LikesDislikesReplies(likes, dislikes, replyCount) } - suspend fun getCommentPager(contextUrl: String, reference: Protocol.Reference): IPager { + suspend fun getCommentPager(contextUrl: String, reference: Protocol.Reference, extraByteReferences: List? = null): IPager { if (!enabled) { return EmptyPager() } @@ -338,7 +350,8 @@ class StatePolycentric { Protocol.QueryReferencesRequestCountReferences.newBuilder() .setFromType(ContentType.POST.value) .build()) - .build() + .build(), + extraByteReferences = extraByteReferences ); val results = mapQueryReferences(contextUrl, response); @@ -407,7 +420,8 @@ class StatePolycentric { ContentType.AVATAR.value, ContentType.USERNAME.value ) - ).eventsList.map { e -> SignedEvent.fromProto(e) }; + ).eventsList.map { e -> SignedEvent.fromProto(e) }.groupBy { e -> e.event.contentType } + .map { (_, events) -> events.maxBy { x -> x.event.unixMilliseconds ?: 0 } }; val nameEvent = profileEvents.firstOrNull { e -> e.event.contentType == ContentType.USERNAME.value }; val avatarEvent = profileEvents.firstOrNull { e -> e.event.contentType == ContentType.AVATAR.value }; diff --git a/app/src/main/java/com/futo/platformplayer/views/IdenticonView.kt b/app/src/main/java/com/futo/platformplayer/views/IdenticonView.kt new file mode 100644 index 00000000..fd380b73 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/IdenticonView.kt @@ -0,0 +1,117 @@ +package com.futo.platformplayer.views + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Path +import android.util.AttributeSet +import android.view.View +import java.security.MessageDigest + +class IdenticonView(context: Context, attrs: AttributeSet) : View(context, attrs) { + var hashString: String = "default" + set(value) { + field = value + hash = md5(value) + invalidate() + } + + private var hash = ByteArray(16) + + private val path = Path() + private val paint = Paint().apply { + style = Paint.Style.FILL + } + + init { + hashString = "default" + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + val radius = (width.coerceAtMost(height) / 2).toFloat() + val clipPath = path.apply { + reset() + addCircle(width / 2f, height / 2f, radius, Path.Direction.CW) + } + + canvas.clipPath(clipPath) + + val size = width.coerceAtMost(height) / 5 + val colors = generateColorsFromHash(hash) + + for (x in 0 until 5) { + for (y in 0 until 5) { + val shapeIndex = getShapeIndex(x, y, hash) + paint.color = colors[shapeIndex % colors.size] + drawShape(canvas, x, y, size, shapeIndex) + } + } + } + + private fun md5(input: String): ByteArray { + val md = MessageDigest.getInstance("MD5") + return md.digest(input.toByteArray(Charsets.UTF_8)) + } + + private fun generateColorsFromHash(hash: ByteArray): List { + val hue = hash[0].toFloat() / 255f + return listOf( + adjustColor(hue, 0.5f, 0.4f), + adjustColor(hue, 0.5f, 0.8f), + adjustColor(hue, 0.5f, 0.3f, 0.9f), + adjustColor(hue, 0.5f, 0.4f, 0.7f) + ) + } + + private fun getShapeIndex(x: Int, y: Int, hash: ByteArray): Int { + val index = if (x < 3) y else 4 - y + return hash[index].toInt() shr x * 2 and 0x03 + } + + private fun drawShape(canvas: Canvas, x: Int, y: Int, size: Int, shapeIndex: Int) { + val left = x * size.toFloat() + val top = y * size.toFloat() + val path = Path() + + when (shapeIndex) { + 0 -> { + // Square + path.addRect(left, top, left + size, top + size, Path.Direction.CW) + } + 1 -> { + // Circle + val radius = size / 2f + path.addCircle(left + radius, top + radius, radius, Path.Direction.CW) + } + 2 -> { + // Diamond + val halfSize = size / 2f + path.moveTo(left + halfSize, top) + path.lineTo(left + size, top + halfSize) + path.lineTo(left + halfSize, top + size) + path.lineTo(left, top + halfSize) + path.close() + } + 3 -> { + // Triangle + path.moveTo(left + size / 2f, top) + path.lineTo(left + size, top + size) + path.lineTo(left, top + size) + path.close() + } + } + + canvas.drawPath(path, paint) + } + + private fun adjustColor(hue: Float, saturation: Float, lightness: Float, alpha: Float = 1.0f): Int { + val color = Color.HSVToColor(floatArrayOf(hue * 360, saturation, lightness)) + val red = Color.red(color) + val green = Color.green(color) + val blue = Color.blue(color) + return Color.argb((alpha * 255).toInt(), red, green, blue) + } +} 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 index 68103c99..29980ac4 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/CommentViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/CommentViewHolder.kt @@ -9,15 +9,21 @@ import android.widget.LinearLayout import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.recyclerview.widget.RecyclerView.ViewHolder -import com.futo.platformplayer.* +import com.futo.platformplayer.R +import com.futo.platformplayer.Settings 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.fixHtmlLinks +import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.setPlatformPlayerLinkMovementMethod import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StatePolycentric +import com.futo.platformplayer.toHumanNowDiffString +import com.futo.platformplayer.toHumanNumber import com.futo.platformplayer.views.others.CreatorThumbnail import com.futo.platformplayer.views.pills.PillButton import com.futo.platformplayer.views.pills.PillRatingLikesDislikes @@ -104,7 +110,8 @@ class CommentViewHolder : ViewHolder { fun bind(comment: IPlatformComment, readonly: Boolean) { _creatorThumbnail.setThumbnail(comment.author.thumbnail, false); - _creatorThumbnail.setHarborAvailable(comment is PolycentricPlatformComment,false); + val polycentricComment = if (comment is PolycentricPlatformComment) comment else null + _creatorThumbnail.setHarborAvailable(polycentricComment != null,false, polycentricComment?.eventPointer?.system?.toProto()); _textAuthor.text = comment.author.name; val date = comment.date; @@ -161,8 +168,8 @@ class CommentViewHolder : ViewHolder { _pillRatingLikesDislikes.visibility = View.VISIBLE; if (comment is PolycentricPlatformComment) { - val hasLiked = StatePolycentric.instance.hasLiked(comment.reference); - val hasDisliked = StatePolycentric.instance.hasDisliked(comment.reference); + val hasLiked = StatePolycentric.instance.hasLiked(comment.reference.toByteArray()); + val hasDisliked = StatePolycentric.instance.hasDisliked(comment.reference.toByteArray()); _pillRatingLikesDislikes.setRating(comment.rating, hasLiked, hasDisliked); } else { _pillRatingLikesDislikes.setRating(comment.rating); diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/CommentWithReferenceViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/CommentWithReferenceViewHolder.kt index 6da01bd3..f1fc07da 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/CommentWithReferenceViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/CommentWithReferenceViewHolder.kt @@ -126,7 +126,8 @@ class CommentWithReferenceViewHolder : ViewHolder { _taskGetLiveComment.cancel() _creatorThumbnail.setThumbnail(comment.author.thumbnail, false); - _creatorThumbnail.setHarborAvailable(comment is PolycentricPlatformComment,false); + val polycentricComment = if (comment is PolycentricPlatformComment) comment else null + _creatorThumbnail.setHarborAvailable(polycentricComment != null,false, polycentricComment?.eventPointer?.system?.toProto()); _textAuthor.text = comment.author.name; val date = comment.date; @@ -168,8 +169,8 @@ class CommentWithReferenceViewHolder : ViewHolder { if (likesDislikesReplies != null) { Log.i(TAG, "updateLikesDislikesReplies set") - val hasLiked = StatePolycentric.instance.hasLiked(c.reference); - val hasDisliked = StatePolycentric.instance.hasDisliked(c.reference); + val hasLiked = StatePolycentric.instance.hasLiked(c.reference.toByteArray()); + val hasDisliked = StatePolycentric.instance.hasDisliked(c.reference.toByteArray()); _pillRatingLikesDislikes.setRating(RatingLikeDislikes(likesDislikesReplies.likes, likesDislikesReplies.dislikes), hasLiked, hasDisliked); _buttonReplies.setLoading(false) 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 index 10335eaf..a35d8147 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistView.kt @@ -7,7 +7,7 @@ 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.R import com.futo.platformplayer.api.media.PlatformID import com.futo.platformplayer.api.media.models.PlatformAuthorLink import com.futo.platformplayer.api.media.models.contents.IPlatformContent @@ -18,8 +18,8 @@ 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.others.CreatorThumbnail import com.futo.platformplayer.views.platform.PlatformIndicator @@ -149,7 +149,8 @@ open class PlaylistView : LinearLayout { _neopassAnimator?.cancel(); _neopassAnimator = null; - val harborAvailable = claims != null && !claims.ownedClaims.isNullOrEmpty(); + val firstClaim = claims?.ownedClaims?.firstOrNull(); + val harborAvailable = firstClaim != null if (harborAvailable) { _imageNeopassChannel?.visibility = View.VISIBLE if (animate) { @@ -160,7 +161,7 @@ open class PlaylistView : LinearLayout { _imageNeopassChannel?.visibility = View.GONE } - _creatorThumbnail?.setHarborAvailable(harborAvailable, animate) + _creatorThumbnail?.setHarborAvailable(harborAvailable, animate, firstClaim?.system?.toProto()) } companion object { 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 index 4ee6f00f..603f79d3 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionViewHolder.kt @@ -6,21 +6,18 @@ 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.Settings -import com.futo.platformplayer.UIDialogs -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.logging.Logger +import com.futo.platformplayer.models.Subscription +import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.selectBestImage -import com.futo.platformplayer.states.StateSubscriptions -import com.futo.platformplayer.toHumanBytesSpeed +import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.toHumanTimeIndicator import com.futo.platformplayer.views.others.CreatorThumbnail import com.futo.platformplayer.views.platform.PlatformIndicator @@ -107,7 +104,7 @@ class SubscriptionViewHolder : ViewHolder { _creatorThumbnail.setThumbnail(avatar, animate); } else { _creatorThumbnail.setThumbnail(this.subscription?.channel?.thumbnail, animate); - _creatorThumbnail.setHarborAvailable(profile != null, animate); + _creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto()); } if (profile != null) { diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewVideoView.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewVideoView.kt index 7d38b4a8..6f94c703 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewVideoView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewVideoView.kt @@ -334,7 +334,7 @@ open class PreviewVideoView : LinearLayout { _creatorThumbnail.setThumbnail(avatar, animate); } else { _creatorThumbnail.setThumbnail(content?.author?.thumbnail, animate); - _creatorThumbnail.setHarborAvailable(profile != null, animate); + _creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto()); } } else if (_imageChannel != null) { val dp_28 = 28.dp(context.resources); diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorBarViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorBarViewHolder.kt index b1338753..14322506 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorBarViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorBarViewHolder.kt @@ -1,6 +1,5 @@ package com.futo.platformplayer.views.adapters.viewholders -import android.graphics.Color import android.view.LayoutInflater import android.view.ViewGroup import android.widget.LinearLayout @@ -8,12 +7,10 @@ import android.widget.TextView import com.futo.platformplayer.R import com.futo.platformplayer.api.media.PlatformID import com.futo.platformplayer.api.media.models.channels.IPlatformChannel -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.logging.Logger -import com.futo.platformplayer.models.Subscription import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.selectBestImage import com.futo.platformplayer.states.StateApp @@ -76,7 +73,7 @@ class CreatorBarViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter.AnyVi _creatorThumbnail.setThumbnail(avatar, animate); } else { _creatorThumbnail.setThumbnail(_channel?.thumbnail, animate); - _creatorThumbnail.setHarborAvailable(profile != null, animate); + _creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto()); } if (profile != null) { @@ -148,7 +145,7 @@ class SelectableCreatorBarViewHolder(private val _viewGroup: ViewGroup) : AnyAda _creatorThumbnail.setThumbnail(avatar, animate); } else { _creatorThumbnail.setThumbnail(_channel?.channel?.thumbnail, animate); - _creatorThumbnail.setHarborAvailable(profile != null, animate); + _creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto()); } if (profile != null) { 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 index 750b4fc2..5c57ffa9 100644 --- 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 @@ -98,7 +98,7 @@ class CreatorViewHolder(private val _viewGroup: ViewGroup, private val _tiny: Bo _creatorThumbnail.setThumbnail(avatar, animate); } else { _creatorThumbnail.setThumbnail(_authorLink?.thumbnail, animate); - _creatorThumbnail.setHarborAvailable(profile != null, animate); + _creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto()); } if (profile != null) { 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 index d92e60ae..2b6350c4 100644 --- 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 @@ -77,7 +77,7 @@ class SubscriptionBarViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter. _creatorThumbnail.setThumbnail(avatar, animate); } else { _creatorThumbnail.setThumbnail(_channel?.thumbnail, animate); - _creatorThumbnail.setHarborAvailable(profile != null, animate); + _creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto()); } if (profile != null) { 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 index 1d9bffd4..ed6a4fbf 100644 --- a/app/src/main/java/com/futo/platformplayer/views/others/CreatorThumbnail.kt +++ b/app/src/main/java/com/futo/platformplayer/views/others/CreatorThumbnail.kt @@ -12,12 +12,16 @@ 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.polycentric.PolycentricCache +import com.futo.platformplayer.views.IdenticonView +import userpackage.Protocol class CreatorThumbnail : ConstraintLayout { private val _root: ConstraintLayout; private val _imageChannelThumbnail: ImageView; private val _imageNewActivity: ImageView; private val _imageNeoPass: ImageView; + private val _identicon: IdenticonView; private var _harborAnimator: ObjectAnimator? = null; private var _imageAnimator: ObjectAnimator? = null; @@ -28,19 +32,22 @@ class CreatorThumbnail : ConstraintLayout { _root = findViewById(R.id.root); _imageChannelThumbnail = findViewById(R.id.image_channel_thumbnail); + _identicon = findViewById(R.id.identicon); _imageChannelThumbnail.clipToOutline = true; + _imageChannelThumbnail.visibility = View.GONE _imageNewActivity = findViewById(R.id.image_new_activity); _imageNeoPass = findViewById(R.id.image_neopass); if (!isInEditMode) { - setHarborAvailable(false, animate = false); + setHarborAvailable(false, animate = false, system = null); setNewActivity(false); } } fun clear() { + _imageChannelThumbnail.visibility = View.GONE; _imageChannelThumbnail.setImageResource(R.drawable.placeholder_channel_thumbnail); - setHarborAvailable(false, animate = false); + setHarborAvailable(false, animate = false, system = null); setNewActivity(false); } @@ -50,13 +57,24 @@ class CreatorThumbnail : ConstraintLayout { return; } + _imageChannelThumbnail.visibility = View.VISIBLE; + _harborAnimator?.cancel(); _harborAnimator = null; _imageAnimator?.cancel(); _imageAnimator = null; - setHarborAvailable(url.startsWith("polycentric://"), animate); + if (url.startsWith("polycentric://")) { + try { + val dataLink = PolycentricCache.getDataLinkFromUrl(url) + setHarborAvailable(true, animate, dataLink?.system); + } catch (e: Throwable) { + setHarborAvailable(false, animate, null); + } + } else { + setHarborAvailable(false, animate, null); + } if (animate) { Glide.with(_imageChannelThumbnail) @@ -72,7 +90,7 @@ class CreatorThumbnail : ConstraintLayout { } } - fun setHarborAvailable(available: Boolean, animate: Boolean) { + fun setHarborAvailable(available: Boolean, animate: Boolean, system: Protocol.PublicKey?) { _harborAnimator?.cancel(); _harborAnimator = null; @@ -85,6 +103,13 @@ class CreatorThumbnail : ConstraintLayout { } else { _imageNeoPass.visibility = View.GONE; } + + if (system != null) { + _identicon.hashString = system.toString() + _identicon.visibility = View.VISIBLE + } else { + _identicon.visibility = View.GONE + } } fun setChannelImageResource(resource: Int?, animate: Boolean) { 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 index 06d43e2c..24028323 100644 --- a/app/src/main/java/com/futo/platformplayer/views/overlays/LoaderOverlay.kt +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/LoaderOverlay.kt @@ -3,6 +3,7 @@ package com.futo.platformplayer.views.overlays import android.content.Context import android.graphics.drawable.Animatable import android.util.AttributeSet +import android.view.Gravity import android.view.View import android.widget.FrameLayout import android.widget.ImageView @@ -16,6 +17,21 @@ class LoaderOverlay(context: Context, attrs: AttributeSet?) : FrameLayout(contex inflate(context, R.layout.overlay_loader, this); _container = findViewById(R.id.container); _loader = findViewById(R.id.loader); + + val centerLoader: Boolean; + if (attrs != null) { + val attrArr = context.obtainStyledAttributes(attrs, R.styleable.LoaderOverlay, 0, 0); + centerLoader = attrArr.getBoolean(R.styleable.LoaderOverlay_centerLoader, false); + attrArr.recycle(); + } else { + centerLoader = false; + } + + if (centerLoader) { + (_loader.layoutParams as LayoutParams).apply { + gravity = Gravity.CENTER + } + } } fun show() { 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 index 77a0ba88..4d2a7fb9 100644 --- a/app/src/main/java/com/futo/platformplayer/views/overlays/RepliesOverlay.kt +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/RepliesOverlay.kt @@ -6,8 +6,8 @@ import android.view.View import android.widget.LinearLayout import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout -import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.R +import com.futo.platformplayer.UIDialogs 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 @@ -102,7 +102,8 @@ class RepliesOverlay : LinearLayout { } _creatorThumbnail.setThumbnail(parentComment.author.thumbnail, false); - _creatorThumbnail.setHarborAvailable(parentComment is PolycentricPlatformComment,false); + val polycentricPlatformComment = if (parentComment is PolycentricPlatformComment) parentComment else null + _creatorThumbnail.setHarborAvailable(polycentricPlatformComment != null,false, polycentricPlatformComment?.eventPointer?.system?.toProto()); } _topbar.setInfo(context.getString(R.string.Replies), metadata); diff --git a/app/src/main/res/layout/activity_polycentric_import_profile.xml b/app/src/main/res/layout/activity_polycentric_import_profile.xml index 1b4eac0a..992395cf 100644 --- a/app/src/main/res/layout/activity_polycentric_import_profile.xml +++ b/app/src/main/res/layout/activity_polycentric_import_profile.xml @@ -94,4 +94,11 @@ android:text="@string/import_profile" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_polycentric_profile.xml b/app/src/main/res/layout/activity_polycentric_profile.xml index d69f1606..97c9af8d 100644 --- a/app/src/main/res/layout/activity_polycentric_profile.xml +++ b/app/src/main/res/layout/activity_polycentric_profile.xml @@ -51,6 +51,21 @@ app:layout_constraintLeft_toLeftOf="@id/image_polycentric" app:layout_constraintRight_toRightOf="@id/image_polycentric" /> + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/overlay_loader.xml b/app/src/main/res/layout/overlay_loader.xml index 0e7a561d..c16a6bda 100644 --- a/app/src/main/res/layout/overlay_loader.xml +++ b/app/src/main/res/layout/overlay_loader.xml @@ -17,5 +17,6 @@ android:layout_gravity="top|center_horizontal" android:alpha="0.7" android:layout_marginTop="80dp" + android:layout_marginBottom="80dp" android:contentDescription="@string/loading" /> \ No newline at end of file diff --git a/app/src/main/res/layout/view_creator_thumbnail.xml b/app/src/main/res/layout/view_creator_thumbnail.xml index b7bdc25b..4dad3e39 100644 --- a/app/src/main/res/layout/view_creator_thumbnail.xml +++ b/app/src/main/res/layout/view_creator_thumbnail.xml @@ -7,6 +7,18 @@ android:layout_height="wrap_content" android:id="@+id/root"> + + + + + + + \ No newline at end of file diff --git a/dep/polycentricandroid b/dep/polycentricandroid index 86cd96c4..7695198e 160000 --- a/dep/polycentricandroid +++ b/dep/polycentricandroid @@ -1 +1 @@ -Subproject commit 86cd96c41f15f7f73c091f61800a49f376a38150 +Subproject commit 7695198eeaeaaea4726712c460081c411ef67866 From c63a63cb3398be9ea5e94d833d887ae9da113e2a Mon Sep 17 00:00:00 2001 From: Koen Date: Wed, 20 Dec 2023 11:06:18 +0100 Subject: [PATCH 2/5] Fixed Gesture control distances for portrait full screen. --- .../views/behavior/GestureControlView.kt | 21 +++++++++++++------ app/src/stable/assets/sources/nebula | 2 +- app/src/stable/assets/sources/youtube | 2 +- 3 files changed, 17 insertions(+), 8 deletions(-) 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 index f48cd486..f500a87e 100644 --- a/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt @@ -18,12 +18,20 @@ 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.logging.Logger import com.futo.platformplayer.views.others.CircularProgressBar -import kotlinx.coroutines.* +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch class GestureControlView : LinearLayout { private val _scope = CoroutineScope(Dispatchers.Main); @@ -95,22 +103,23 @@ class GestureControlView : LinearLayout { if(p0 == null) return false; + val minDistance = Math.min(width, height) if (_isFullScreen && _adjustingBrightness) { - val adjustAmount = (distanceY * 2) / height; + val adjustAmount = (distanceY * 2) / minDistance; _brightnessFactor = (_brightnessFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f); _progressBrightness.progress = _brightnessFactor; onBrightnessAdjusted.emit(_brightnessFactor); } else if (_isFullScreen && _adjustingSound) { - val adjustAmount = (distanceY * 2) / height; + val adjustAmount = (distanceY * 2) / minDistance; _soundFactor = (_soundFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f); _progressSound.progress = _soundFactor; onSoundAdjusted.emit(_soundFactor); } else if (_adjustingFullscreenUp) { - val adjustAmount = (distanceY * 2) / height; + val adjustAmount = (distanceY * 2) / minDistance; _fullScreenFactorUp = (_fullScreenFactorUp + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f); _layoutControlsFullscreen.alpha = _fullScreenFactorUp; } else if (_adjustingFullscreenDown) { - val adjustAmount = (-distanceY * 2) / height; + val adjustAmount = (-distanceY * 2) / minDistance; _fullScreenFactorDown = (_fullScreenFactorDown + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f); _layoutControlsFullscreen.alpha = _fullScreenFactorDown; } else { diff --git a/app/src/stable/assets/sources/nebula b/app/src/stable/assets/sources/nebula index 863d0be1..01270edb 160000 --- a/app/src/stable/assets/sources/nebula +++ b/app/src/stable/assets/sources/nebula @@ -1 +1 @@ -Subproject commit 863d0be1322660c99e4d0cdae0b45d0a5918542d +Subproject commit 01270edbb4b6b4fb004e22fc529bf787c7f5be81 diff --git a/app/src/stable/assets/sources/youtube b/app/src/stable/assets/sources/youtube index d41cc8e8..13551ab6 160000 --- a/app/src/stable/assets/sources/youtube +++ b/app/src/stable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit d41cc8e848891ef8e949e6d49384b754e7c305c7 +Subproject commit 13551ab67fc8fb1899b5bcbfdec750855b154790 From 1dc6eee24284f1469c0798f34c3344a806a2c075 Mon Sep 17 00:00:00 2001 From: Koen Date: Wed, 20 Dec 2023 11:51:28 +0100 Subject: [PATCH 3/5] Fixed download button overlay not working in more button. --- .../fragment/mainactivity/main/VideoDetailView.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 index 076ceae9..51836b9a 100644 --- 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 @@ -783,9 +783,12 @@ class VideoDetailView : ConstraintLayout { } }, RoundButton(context, R.drawable.ic_download, context.getString(R.string.download), TAG_DOWNLOAD) { - video?.let { - _slideUpOverlay = UISlideOverlays.showDownloadVideoOverlay(it, _overlayContainer, context.contentResolver); - }; + fragment.lifecycleScope.launch(Dispatchers.Main) { + delay(500) + video?.let { + _slideUpOverlay = UISlideOverlays.showDownloadVideoOverlay(it, _overlayContainer, context.contentResolver); + }; + } }, RoundButton(context, R.drawable.ic_share, context.getString(R.string.share), TAG_SHARE) { video?.let { From 0154525578a68be71ed05562b2eb1d42c10db787 Mon Sep 17 00:00:00 2001 From: Koen Date: Wed, 20 Dec 2023 12:02:35 +0100 Subject: [PATCH 4/5] Revert "Fixed download button overlay not working in more button." This reverts commit 1dc6eee24284f1469c0798f34c3344a806a2c075. --- .../fragment/mainactivity/main/VideoDetailView.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) 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 index 51836b9a..076ceae9 100644 --- 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 @@ -783,12 +783,9 @@ class VideoDetailView : ConstraintLayout { } }, RoundButton(context, R.drawable.ic_download, context.getString(R.string.download), TAG_DOWNLOAD) { - fragment.lifecycleScope.launch(Dispatchers.Main) { - delay(500) - video?.let { - _slideUpOverlay = UISlideOverlays.showDownloadVideoOverlay(it, _overlayContainer, context.contentResolver); - }; - } + video?.let { + _slideUpOverlay = UISlideOverlays.showDownloadVideoOverlay(it, _overlayContainer, context.contentResolver); + }; }, RoundButton(context, R.drawable.ic_share, context.getString(R.string.share), TAG_SHARE) { video?.let { From 09902473221af83ee86c8968b7e5d522ad0a37d3 Mon Sep 17 00:00:00 2001 From: Koen Date: Wed, 20 Dec 2023 12:06:21 +0100 Subject: [PATCH 5/5] Fixed Rumble channel content fetching, channel image and show comments when sign in is not required. --- app/src/stable/assets/sources/rumble | 2 +- app/src/unstable/assets/sources/rumble | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/stable/assets/sources/rumble b/app/src/stable/assets/sources/rumble index b0e35a9b..7d4303c8 160000 --- a/app/src/stable/assets/sources/rumble +++ b/app/src/stable/assets/sources/rumble @@ -1 +1 @@ -Subproject commit b0e35a9b6631fb3279fb38619ecc3eba812e5ed6 +Subproject commit 7d4303c82a1ec06871f0717a606f5c6cb87e78be diff --git a/app/src/unstable/assets/sources/rumble b/app/src/unstable/assets/sources/rumble index b0e35a9b..7d4303c8 160000 --- a/app/src/unstable/assets/sources/rumble +++ b/app/src/unstable/assets/sources/rumble @@ -1 +1 @@ -Subproject commit b0e35a9b6631fb3279fb38619ecc3eba812e5ed6 +Subproject commit 7d4303c82a1ec06871f0717a606f5c6cb87e78be