mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-09-08 02:26:17 +00:00
Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay
This commit is contained in:
commit
56248bf4b0
33 changed files with 522 additions and 213 deletions
|
@ -1,11 +1,13 @@
|
||||||
package com.futo.platformplayer
|
package com.futo.platformplayer
|
||||||
|
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
|
import com.futo.platformplayer.polycentric.PolycentricCache
|
||||||
import com.futo.platformplayer.states.AnnouncementType
|
import com.futo.platformplayer.states.AnnouncementType
|
||||||
import com.futo.platformplayer.states.StateAnnouncement
|
import com.futo.platformplayer.states.StateAnnouncement
|
||||||
import com.futo.platformplayer.states.StatePlatform
|
import com.futo.platformplayer.states.StatePlatform
|
||||||
import com.futo.platformplayer.views.adapters.CommentViewHolder
|
|
||||||
import com.futo.polycentric.core.ProcessHandle
|
import com.futo.polycentric.core.ProcessHandle
|
||||||
|
import com.futo.polycentric.core.Store
|
||||||
|
import com.futo.polycentric.core.SystemState
|
||||||
import userpackage.Protocol
|
import userpackage.Protocol
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
@ -47,6 +49,15 @@ fun Protocol.Claim.resolveChannelUrls(): List<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun ProcessHandle.fullyBackfillServersAnnounceExceptions() {
|
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()
|
val exceptions = fullyBackfillServers()
|
||||||
for (pair in exceptions) {
|
for (pair in exceptions) {
|
||||||
val server = pair.key
|
val server = pair.key
|
||||||
|
|
|
@ -8,12 +8,15 @@ import android.widget.ImageButton
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
import com.futo.platformplayer.UIDialogs
|
import com.futo.platformplayer.UIDialogs
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
|
import com.futo.platformplayer.polycentric.PolycentricCache
|
||||||
import com.futo.platformplayer.setNavigationBarColorAndIcons
|
import com.futo.platformplayer.setNavigationBarColorAndIcons
|
||||||
import com.futo.platformplayer.states.StateApp
|
import com.futo.platformplayer.states.StateApp
|
||||||
import com.futo.platformplayer.states.StatePolycentric
|
import com.futo.platformplayer.states.StatePolycentric
|
||||||
|
import com.futo.platformplayer.views.overlays.LoaderOverlay
|
||||||
import com.futo.polycentric.core.KeyPair
|
import com.futo.polycentric.core.KeyPair
|
||||||
import com.futo.polycentric.core.Process
|
import com.futo.polycentric.core.Process
|
||||||
import com.futo.polycentric.core.ProcessSecret
|
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.Store
|
||||||
import com.futo.polycentric.core.base64UrlToByteArray
|
import com.futo.polycentric.core.base64UrlToByteArray
|
||||||
import com.google.zxing.integration.android.IntentIntegrator
|
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
|
||||||
import userpackage.Protocol.ExportBundle
|
import userpackage.Protocol.ExportBundle
|
||||||
|
|
||||||
|
@ -29,6 +35,7 @@ class PolycentricImportProfileActivity : AppCompatActivity() {
|
||||||
private lateinit var _buttonScanProfile: LinearLayout;
|
private lateinit var _buttonScanProfile: LinearLayout;
|
||||||
private lateinit var _buttonImportProfile: LinearLayout;
|
private lateinit var _buttonImportProfile: LinearLayout;
|
||||||
private lateinit var _editProfile: EditText;
|
private lateinit var _editProfile: EditText;
|
||||||
|
private lateinit var _loaderOverlay: LoaderOverlay;
|
||||||
|
|
||||||
private val _qrCodeResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
private val _qrCodeResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
val scanResult = IntentIntegrator.parseActivityResult(result.resultCode, result.data)
|
val scanResult = IntentIntegrator.parseActivityResult(result.resultCode, result.data)
|
||||||
|
@ -52,6 +59,7 @@ class PolycentricImportProfileActivity : AppCompatActivity() {
|
||||||
_buttonHelp = findViewById(R.id.button_help);
|
_buttonHelp = findViewById(R.id.button_help);
|
||||||
_buttonScanProfile = findViewById(R.id.button_scan_profile);
|
_buttonScanProfile = findViewById(R.id.button_scan_profile);
|
||||||
_buttonImportProfile = findViewById(R.id.button_import_profile);
|
_buttonImportProfile = findViewById(R.id.button_import_profile);
|
||||||
|
_loaderOverlay = findViewById(R.id.loader_overlay);
|
||||||
_editProfile = findViewById(R.id.edit_profile);
|
_editProfile = findViewById(R.id.edit_profile);
|
||||||
findViewById<ImageButton>(R.id.button_back).setOnClickListener {
|
findViewById<ImageButton>(R.id.button_back).setOnClickListener {
|
||||||
finish();
|
finish();
|
||||||
|
@ -94,42 +102,57 @@ class PolycentricImportProfileActivity : AppCompatActivity() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
_loaderOverlay.show()
|
||||||
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);
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val keyPair = KeyPair.fromProto(exportBundle.keyPair);
|
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);
|
val exportBundle = ExportBundle.parseFrom(urlInfo.body);
|
||||||
if (existingProcessSecret != null) {
|
val keyPair = KeyPair.fromProto(exportBundle.keyPair);
|
||||||
UIDialogs.toast(this, getString(R.string.this_profile_is_already_imported));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
val processSecret = ProcessSecret(keyPair, Process.random());
|
val existingProcessSecret = Store.instance.getProcessSecret(keyPair.publicKey);
|
||||||
Store.instance.addProcessSecret(processSecret);
|
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) {
|
val processHandle = processSecret.toProcessHandle();
|
||||||
try {
|
|
||||||
val se = SignedEvent.fromProto(e);
|
for (e in exportBundle.events.eventsList) {
|
||||||
Store.instance.putSignedEvent(se);
|
try {
|
||||||
} catch (e: Throwable) {
|
val se = SignedEvent.fromProto(e);
|
||||||
Logger.w(TAG, "Ignored invalid event", 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}'");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package com.futo.platformplayer.activities
|
package com.futo.platformplayer.activities
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
@ -12,6 +14,7 @@ import android.webkit.MimeTypeMap
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
|
@ -21,14 +24,16 @@ import com.futo.platformplayer.dp
|
||||||
import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions
|
import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions
|
||||||
import com.futo.platformplayer.images.GlideHelper.Companion.crossfade
|
import com.futo.platformplayer.images.GlideHelper.Companion.crossfade
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
|
import com.futo.platformplayer.polycentric.PolycentricCache
|
||||||
import com.futo.platformplayer.selectBestImage
|
import com.futo.platformplayer.selectBestImage
|
||||||
import com.futo.platformplayer.setNavigationBarColorAndIcons
|
import com.futo.platformplayer.setNavigationBarColorAndIcons
|
||||||
import com.futo.platformplayer.states.StateApp
|
import com.futo.platformplayer.states.StateApp
|
||||||
import com.futo.platformplayer.states.StatePolycentric
|
import com.futo.platformplayer.states.StatePolycentric
|
||||||
import com.futo.platformplayer.views.buttons.BigButton
|
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.Store
|
||||||
import com.futo.polycentric.core.Synchronization
|
|
||||||
import com.futo.polycentric.core.SystemState
|
import com.futo.polycentric.core.SystemState
|
||||||
|
import com.futo.polycentric.core.toBase64Url
|
||||||
import com.futo.polycentric.core.toURLInfoSystemLinkUrl
|
import com.futo.polycentric.core.toURLInfoSystemLinkUrl
|
||||||
import com.github.dhaval2404.imagepicker.ImagePicker
|
import com.github.dhaval2404.imagepicker.ImagePicker
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -46,6 +51,8 @@ class PolycentricProfileActivity : AppCompatActivity() {
|
||||||
private lateinit var _buttonDelete: BigButton;
|
private lateinit var _buttonDelete: BigButton;
|
||||||
private lateinit var _username: String;
|
private lateinit var _username: String;
|
||||||
private lateinit var _imagePolycentric: ImageView;
|
private lateinit var _imagePolycentric: ImageView;
|
||||||
|
private lateinit var _loaderOverlay: LoaderOverlay;
|
||||||
|
private lateinit var _textSystem: TextView;
|
||||||
private var _avatarUri: Uri? = null;
|
private var _avatarUri: Uri? = null;
|
||||||
|
|
||||||
override fun attachBaseContext(newBase: Context?) {
|
override fun attachBaseContext(newBase: Context?) {
|
||||||
|
@ -63,28 +70,13 @@ class PolycentricProfileActivity : AppCompatActivity() {
|
||||||
_buttonExport = findViewById(R.id.button_export);
|
_buttonExport = findViewById(R.id.button_export);
|
||||||
_buttonLogout = findViewById(R.id.button_logout);
|
_buttonLogout = findViewById(R.id.button_logout);
|
||||||
_buttonDelete = findViewById(R.id.button_delete);
|
_buttonDelete = findViewById(R.id.button_delete);
|
||||||
|
_loaderOverlay = findViewById(R.id.loader_overlay);
|
||||||
|
_textSystem = findViewById(R.id.text_system)
|
||||||
findViewById<ImageButton>(R.id.button_back).setOnClickListener {
|
findViewById<ImageButton>(R.id.button_back).setOnClickListener {
|
||||||
saveIfRequired();
|
saveIfRequired();
|
||||||
finish();
|
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 {
|
_imagePolycentric.setOnClickListener {
|
||||||
ImagePicker.with(this)
|
ImagePicker.with(this)
|
||||||
.cropSquare()
|
.cropSquare()
|
||||||
|
@ -120,6 +112,37 @@ class PolycentricProfileActivity : AppCompatActivity() {
|
||||||
finish();
|
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() {
|
private fun saveIfRequired() {
|
||||||
|
@ -128,13 +151,17 @@ class PolycentricProfileActivity : AppCompatActivity() {
|
||||||
var hasChanges = false;
|
var hasChanges = false;
|
||||||
val username = _editName.text.toString();
|
val username = _editName.text.toString();
|
||||||
if (username.length < 3) {
|
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;
|
return@launch;
|
||||||
}
|
}
|
||||||
|
|
||||||
val processHandle = StatePolycentric.instance.processHandle;
|
val processHandle = StatePolycentric.instance.processHandle;
|
||||||
if (processHandle == null) {
|
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;
|
return@launch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,6 +246,7 @@ class PolycentricProfileActivity : AppCompatActivity() {
|
||||||
private fun updateUI() {
|
private fun updateUI() {
|
||||||
val processHandle = StatePolycentric.instance.processHandle!!;
|
val processHandle = StatePolycentric.instance.processHandle!!;
|
||||||
val systemState = SystemState.fromStorageTypeSystemState(Store.instance.getSystemState(processHandle.system))
|
val systemState = SystemState.fromStorageTypeSystemState(Store.instance.getSystemState(processHandle.system))
|
||||||
|
_textSystem.text = processHandle.system.key.toBase64Url()
|
||||||
_username = systemState.username;
|
_username = systemState.username;
|
||||||
_editName.text.clear();
|
_editName.text.clear();
|
||||||
_editName.text.append(_username);
|
_editName.text.append(_username);
|
||||||
|
|
|
@ -6,11 +6,12 @@ import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.view.inputmethod.InputMethodManager
|
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.R
|
||||||
import com.futo.platformplayer.UIDialogs
|
import com.futo.platformplayer.UIDialogs
|
||||||
import com.futo.platformplayer.api.media.PlatformID
|
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.selectBestImage
|
||||||
import com.futo.platformplayer.states.StateApp
|
import com.futo.platformplayer.states.StateApp
|
||||||
import com.futo.platformplayer.states.StatePolycentric
|
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 com.google.android.material.button.MaterialButton
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -93,7 +98,7 @@ class CommentDialog(context: Context?, val contextUrl: String, val ref: Protocol
|
||||||
|
|
||||||
val comment = _editComment.text.toString();
|
val comment = _editComment.text.toString();
|
||||||
val processHandle = StatePolycentric.instance.processHandle!!
|
val processHandle = StatePolycentric.instance.processHandle!!
|
||||||
val eventPointer = processHandle.post(comment, null, ref)
|
val eventPointer = processHandle.post(comment, ref)
|
||||||
|
|
||||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -465,7 +465,7 @@ class ChannelFragment : MainFragment() {
|
||||||
_creatorThumbnail.setThumbnail(avatar, animate);
|
_creatorThumbnail.setThumbnail(avatar, animate);
|
||||||
} else {
|
} else {
|
||||||
_creatorThumbnail.setThumbnail(channel?.thumbnail, animate);
|
_creatorThumbnail.setThumbnail(channel?.thumbnail, animate);
|
||||||
_creatorThumbnail.setHarborAvailable(profile != null, animate);
|
_creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto());
|
||||||
}
|
}
|
||||||
|
|
||||||
val banner = profile?.systemState?.banner?.selectHighestResolutionImage()
|
val banner = profile?.systemState?.banner?.selectHighestResolutionImage()
|
||||||
|
|
|
@ -314,8 +314,8 @@ class PostDetailFragment : MainFragment {
|
||||||
private fun updatePolycentricRating() {
|
private fun updatePolycentricRating() {
|
||||||
_rating.visibility = View.GONE;
|
_rating.visibility = View.GONE;
|
||||||
|
|
||||||
val value = _post?.id?.value ?: _postOverview?.id?.value ?: return;
|
val ref = Models.referenceFromBuffer((_post?.url ?: _postOverview?.url)?.toByteArray() ?: return)
|
||||||
val ref = Models.referenceFromBuffer(value.toByteArray());
|
val extraBytesRef = (_post?.id?.value ?: _postOverview?.id?.value)?.toByteArray()
|
||||||
val version = _version;
|
val version = _version;
|
||||||
|
|
||||||
_rating.onLikeDislikeUpdated.remove(this);
|
_rating.onLikeDislikeUpdated.remove(this);
|
||||||
|
@ -333,7 +333,8 @@ class PostDetailFragment : MainFragment {
|
||||||
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType(
|
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType(
|
||||||
ContentType.OPINION.value).setValue(
|
ContentType.OPINION.value).setValue(
|
||||||
ByteString.copyFrom(Opinion.dislike.data)).build()
|
ByteString.copyFrom(Opinion.dislike.data)).build()
|
||||||
)
|
),
|
||||||
|
extraByteReferences = listOfNotNull(extraBytesRef)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (version != _version) {
|
if (version != _version) {
|
||||||
|
@ -342,8 +343,8 @@ class PostDetailFragment : MainFragment {
|
||||||
|
|
||||||
val likes = queryReferencesResponse.countsList[0];
|
val likes = queryReferencesResponse.countsList[0];
|
||||||
val dislikes = queryReferencesResponse.countsList[1];
|
val dislikes = queryReferencesResponse.countsList[1];
|
||||||
val hasLiked = StatePolycentric.instance.hasLiked(ref);
|
val hasLiked = StatePolycentric.instance.hasLiked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasLiked(it) } ?: false*/;
|
||||||
val hasDisliked = StatePolycentric.instance.hasDisliked(ref);
|
val hasDisliked = StatePolycentric.instance.hasDisliked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasDisliked(it) } ?: false*/;
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (version != _version) {
|
if (version != _version) {
|
||||||
|
@ -468,9 +469,7 @@ class PostDetailFragment : MainFragment {
|
||||||
if (_postOverview == null) {
|
if (_postOverview == null) {
|
||||||
fetchPolycentricProfile();
|
fetchPolycentricProfile();
|
||||||
updatePolycentricRating();
|
updatePolycentricRating();
|
||||||
|
_addCommentView.setContext(value.url, Models.referenceFromBuffer(value.url.toByteArray()));
|
||||||
val ref = value.id.value?.let { Models.referenceFromBuffer(it.toByteArray()); };
|
|
||||||
_addCommentView.setContext(value.url, ref);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCommentType(true);
|
updateCommentType(true);
|
||||||
|
@ -489,9 +488,7 @@ class PostDetailFragment : MainFragment {
|
||||||
_textMeta.text = value.datetime?.toHumanNowDiffString()?.let { "$it ago" } ?: "" //TODO: Include view count?
|
_textMeta.text = value.datetime?.toHumanNowDiffString()?.let { "$it ago" } ?: "" //TODO: Include view count?
|
||||||
_textContent.text = value.description.fixHtmlWhitespace();
|
_textContent.text = value.description.fixHtmlWhitespace();
|
||||||
_platformIndicator.setPlatformFromClientID(value.id.pluginId);
|
_platformIndicator.setPlatformFromClientID(value.id.pluginId);
|
||||||
|
_addCommentView.setContext(value.url, Models.referenceFromBuffer(value.url.toByteArray()));
|
||||||
val ref = value.id.value?.let { Models.referenceFromBuffer(it.toByteArray()); };
|
|
||||||
_addCommentView.setContext(value.url, ref);
|
|
||||||
|
|
||||||
updatePolycentricRating();
|
updatePolycentricRating();
|
||||||
fetchPolycentricProfile();
|
fetchPolycentricProfile();
|
||||||
|
@ -636,12 +633,12 @@ class PostDetailFragment : MainFragment {
|
||||||
|
|
||||||
if (cachedPolycentricProfile?.profile == null) {
|
if (cachedPolycentricProfile?.profile == null) {
|
||||||
_layoutMonetization.visibility = View.GONE;
|
_layoutMonetization.visibility = View.GONE;
|
||||||
_creatorThumbnail.setHarborAvailable(false, animate);
|
_creatorThumbnail.setHarborAvailable(false, animate, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_layoutMonetization.visibility = View.VISIBLE;
|
_layoutMonetization.visibility = View.VISIBLE;
|
||||||
_creatorThumbnail.setHarborAvailable(true, animate);
|
_creatorThumbnail.setHarborAvailable(true, animate, cachedPolycentricProfile.profile.system.toProto());
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchPost() {
|
private fun fetchPost() {
|
||||||
|
@ -665,14 +662,16 @@ class PostDetailFragment : MainFragment {
|
||||||
private fun fetchPolycentricComments() {
|
private fun fetchPolycentricComments() {
|
||||||
Logger.i(TAG, "fetchPolycentricComments")
|
Logger.i(TAG, "fetchPolycentricComments")
|
||||||
val post = _post;
|
val post = _post;
|
||||||
val idValue = post?.id?.value
|
val ref = (_post?.url ?: _postOverview?.url)?.toByteArray()?.let { Models.referenceFromBuffer(it) }
|
||||||
if (idValue == null) {
|
val extraBytesRef = (_post?.id?.value ?: _postOverview?.id?.value)?.toByteArray()
|
||||||
Logger.w(TAG, "Failed to fetch polycentric comments because id was null")
|
|
||||||
|
if (ref == null) {
|
||||||
|
Logger.w(TAG, "Failed to fetch polycentric comments because url was not set null")
|
||||||
_commentsList.clear();
|
_commentsList.clear();
|
||||||
return
|
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) {
|
private fun updateCommentType(reloadComments: Boolean) {
|
||||||
|
|
|
@ -129,8 +129,8 @@ class TutorialFragment : MainFragment() {
|
||||||
override val dash: IDashManifestSource? = null
|
override val dash: IDashManifestSource? = null
|
||||||
override val hls: IHLSManifestSource? = null
|
override val hls: IHLSManifestSource? = null
|
||||||
override val subtitles: List<ISubtitleSource> = emptyList()
|
override val subtitles: List<ISubtitleSource> = emptyList()
|
||||||
override val shareUrl: String = ""
|
override val shareUrl: String = videoUrl
|
||||||
override val url: String = ""
|
override val url: String = videoUrl
|
||||||
override val datetime: OffsetDateTime? = OffsetDateTime.parse("2023-12-18T00:00:00Z")
|
override val datetime: OffsetDateTime? = OffsetDateTime.parse("2023-12-18T00:00:00Z")
|
||||||
override val thumbnails: Thumbnails = Thumbnails(arrayOf(Thumbnail(thumbnailUrl)))
|
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")
|
override val author: PlatformAuthorLink = PlatformAuthorLink(PlatformID("tutorial", "f422ced6-b551-4b62-818e-27a4f5f4918a"), "Grayjay", "", "https://releases.grayjay.app/tutorials/author.jpeg")
|
||||||
|
|
|
@ -1207,9 +1207,9 @@ class VideoDetailView : ConstraintLayout {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
val ref = video.id.value?.let { Models.referenceFromBuffer(it.toByteArray()) };
|
val ref = Models.referenceFromBuffer(video.url.toByteArray())
|
||||||
_addCommentView.setContext(video.url, ref);
|
val extraBytesRef = video.id.value?.toByteArray()
|
||||||
|
_addCommentView.setContext(video.url, ref)
|
||||||
_player.setMetadata(video.name, video.author.name);
|
_player.setMetadata(video.name, video.author.name);
|
||||||
|
|
||||||
if (video !is TutorialFragment.TutorialVideo) {
|
if (video !is TutorialFragment.TutorialVideo) {
|
||||||
|
@ -1270,57 +1270,54 @@ class VideoDetailView : ConstraintLayout {
|
||||||
|
|
||||||
_rating.onLikeDislikeUpdated.remove(this);
|
_rating.onLikeDislikeUpdated.remove(this);
|
||||||
|
|
||||||
if (ref != null) {
|
_rating.visibility = View.GONE;
|
||||||
_rating.visibility = View.GONE;
|
|
||||||
|
|
||||||
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val queryReferencesResponse = ApiMethods.getQueryReferences(PolycentricCache.SERVER, ref, null,null,
|
val queryReferencesResponse = ApiMethods.getQueryReferences(PolycentricCache.SERVER, ref, null,null,
|
||||||
arrayListOf(
|
arrayListOf(
|
||||||
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType(ContentType.OPINION.value).setValue(
|
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType(ContentType.OPINION.value).setValue(
|
||||||
ByteString.copyFrom(Opinion.like.data)).build(),
|
ByteString.copyFrom(Opinion.like.data)).build(),
|
||||||
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType(ContentType.OPINION.value).setValue(
|
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType(ContentType.OPINION.value).setValue(
|
||||||
ByteString.copyFrom(Opinion.dislike.data)).build()
|
ByteString.copyFrom(Opinion.dislike.data)).build()
|
||||||
)
|
),
|
||||||
);
|
extraByteReferences = listOfNotNull(extraBytesRef)
|
||||||
|
);
|
||||||
|
|
||||||
val likes = queryReferencesResponse.countsList[0];
|
val likes = queryReferencesResponse.countsList[0];
|
||||||
val dislikes = queryReferencesResponse.countsList[1];
|
val dislikes = queryReferencesResponse.countsList[1];
|
||||||
val hasLiked = StatePolycentric.instance.hasLiked(ref);
|
val hasLiked = StatePolycentric.instance.hasLiked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasLiked(it) } ?: false*/;
|
||||||
val hasDisliked = StatePolycentric.instance.hasDisliked(ref);
|
val hasDisliked = StatePolycentric.instance.hasDisliked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasDisliked(it) } ?: false*/;
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
_rating.visibility = View.VISIBLE;
|
_rating.visibility = View.VISIBLE;
|
||||||
_rating.setRating(RatingLikeDislikes(likes, dislikes), hasLiked, hasDisliked);
|
_rating.setRating(RatingLikeDislikes(likes, dislikes), hasLiked, hasDisliked);
|
||||||
_rating.onLikeDislikeUpdated.subscribe(this) { args ->
|
_rating.onLikeDislikeUpdated.subscribe(this) { args ->
|
||||||
if (args.hasLiked) {
|
if (args.hasLiked) {
|
||||||
args.processHandle.opinion(ref, Opinion.like);
|
args.processHandle.opinion(ref, Opinion.like);
|
||||||
} else if (args.hasDisliked) {
|
} else if (args.hasDisliked) {
|
||||||
args.processHandle.opinion(ref, Opinion.dislike);
|
args.processHandle.opinion(ref, Opinion.dislike);
|
||||||
} else {
|
} else {
|
||||||
args.processHandle.opinion(ref, Opinion.neutral);
|
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) {
|
StatePolycentric.instance.updateLikeMap(ref, args.hasLiked, args.hasDisliked)
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
} 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) {
|
when (video.rating) {
|
||||||
|
@ -1367,28 +1364,30 @@ class VideoDetailView : ConstraintLayout {
|
||||||
|
|
||||||
updateQueueState();
|
updateQueueState();
|
||||||
|
|
||||||
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
if (video !is TutorialFragment.TutorialVideo) {
|
||||||
val historyItem = getHistoryIndex(videoDetail);
|
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val historyItem = getHistoryIndex(videoDetail);
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
_historicalPosition = StateHistory.instance.updateHistoryPosition(video, historyItem,false, (toResume.toFloat() / 1000.0f).toLong());
|
_historicalPosition = StateHistory.instance.updateHistoryPosition(video, historyItem,false, (toResume.toFloat() / 1000.0f).toLong());
|
||||||
Logger.i(TAG, "Historical position: $_historicalPosition, last position: $lastPositionMilliseconds");
|
Logger.i(TAG, "Historical position: $_historicalPosition, last position: $lastPositionMilliseconds");
|
||||||
if (_historicalPosition > 60 && video.duration - _historicalPosition > 5 && Math.abs(_historicalPosition - lastPositionMilliseconds / 1000) > 5.0) {
|
if (_historicalPosition > 60 && video.duration - _historicalPosition > 5 && Math.abs(_historicalPosition - lastPositionMilliseconds / 1000) > 5.0) {
|
||||||
_layoutResume.visibility = View.VISIBLE;
|
_layoutResume.visibility = View.VISIBLE;
|
||||||
_textResume.text = "Resume at ${_historicalPosition.toHumanTime(false)}";
|
_textResume.text = "Resume at ${_historicalPosition.toHumanTime(false)}";
|
||||||
|
|
||||||
_jobHideResume = fragment.lifecycleScope.launch(Dispatchers.Main) {
|
_jobHideResume = fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||||
try {
|
try {
|
||||||
delay(8000);
|
delay(8000);
|
||||||
_layoutResume.visibility = View.GONE;
|
_layoutResume.visibility = View.GONE;
|
||||||
_textResume.text = "";
|
_textResume.text = "";
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Logger.e(TAG, "Failed to set resume changes.", e);
|
Logger.e(TAG, "Failed to set resume changes.", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
_layoutResume.visibility = View.GONE;
|
||||||
|
_textResume.text = "";
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
_layoutResume.visibility = View.GONE;
|
|
||||||
_textResume.text = "";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1960,7 +1959,9 @@ class VideoDetailView : ConstraintLayout {
|
||||||
return
|
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() {
|
private fun fetchVideo() {
|
||||||
Logger.i(TAG, "fetchVideo")
|
Logger.i(TAG, "fetchVideo")
|
||||||
|
@ -2222,9 +2223,11 @@ class VideoDetailView : ConstraintLayout {
|
||||||
val v = video ?: return;
|
val v = video ?: return;
|
||||||
val currentTime = System.currentTimeMillis();
|
val currentTime = System.currentTimeMillis();
|
||||||
if (updateHistory && (_lastPositionSaveTime == -1L || currentTime - _lastPositionSaveTime > 5000)) {
|
if (updateHistory && (_lastPositionSaveTime == -1L || currentTime - _lastPositionSaveTime > 5000)) {
|
||||||
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
if (v !is TutorialFragment.TutorialVideo) {
|
||||||
val history = getHistoryIndex(v);
|
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
StateHistory.instance.updateHistoryPosition(v, history, true, (positionMilliseconds.toFloat() / 1000.0f).toLong());
|
val history = getHistoryIndex(v);
|
||||||
|
StateHistory.instance.updateHistoryPosition(v, history, true, (positionMilliseconds.toFloat() / 1000.0f).toLong());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_lastPositionSaveTime = currentTime;
|
_lastPositionSaveTime = currentTime;
|
||||||
}
|
}
|
||||||
|
@ -2307,7 +2310,7 @@ class VideoDetailView : ConstraintLayout {
|
||||||
_creatorThumbnail.setThumbnail(avatar, animate);
|
_creatorThumbnail.setThumbnail(avatar, animate);
|
||||||
} else {
|
} else {
|
||||||
_creatorThumbnail.setThumbnail(video?.author?.thumbnail, animate);
|
_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
|
val username = cachedPolycentricProfile?.profile?.systemState?.username
|
||||||
|
|
|
@ -56,7 +56,7 @@ class PolycentricCache {
|
||||||
|
|
||||||
private val _taskGetProfile = BatchedTaskHandler<PublicKey, CachedPolycentricProfile>(_scope,
|
private val _taskGetProfile = BatchedTaskHandler<PublicKey, CachedPolycentricProfile>(_scope,
|
||||||
{ system ->
|
{ system ->
|
||||||
val signedProfileEvents = ApiMethods.getQueryLatest(
|
val signedEventsList = ApiMethods.getQueryLatest(
|
||||||
SERVER,
|
SERVER,
|
||||||
system.toProto(),
|
system.toProto(),
|
||||||
listOf(
|
listOf(
|
||||||
|
@ -72,8 +72,9 @@ class PolycentricCache {
|
||||||
ContentType.MEMBERSHIP_URLS.value,
|
ContentType.MEMBERSHIP_URLS.value,
|
||||||
ContentType.DONATION_DESTINATIONS.value
|
ContentType.DONATION_DESTINATIONS.value
|
||||||
)
|
)
|
||||||
).eventsList.map { e -> SignedEvent.fromProto(e) }
|
).eventsList.map { e -> SignedEvent.fromProto(e) };
|
||||||
.groupBy { e -> e.event.contentType }
|
|
||||||
|
val signedProfileEvents = signedEventsList.groupBy { e -> e.event.contentType }
|
||||||
.map { (_, events) -> events.maxBy { it.event.unixMilliseconds ?: 0 } };
|
.map { (_, events) -> events.maxBy { it.event.unixMilliseconds ?: 0 } };
|
||||||
|
|
||||||
val storageSystemState = StorageTypeSystemState.create()
|
val storageSystemState = StorageTypeSystemState.create()
|
||||||
|
@ -151,17 +152,7 @@ class PolycentricCache {
|
||||||
|
|
||||||
private val _batchTaskGetData = BatchedTaskHandler<String, ByteBuffer>(_scope,
|
private val _batchTaskGetData = BatchedTaskHandler<String, ByteBuffer>(_scope,
|
||||||
{
|
{
|
||||||
val urlData = if (it.startsWith("polycentric://")) {
|
val dataLink = getDataLinkFromUrl(it) ?: throw Exception("Only URLInfoDataLink is supported");
|
||||||
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 ApiMethods.getDataFromServerAndReassemble(dataLink);
|
||||||
},
|
},
|
||||||
{ return@BatchedTaskHandler null },
|
{ return@BatchedTaskHandler null },
|
||||||
|
@ -325,9 +316,10 @@ class PolycentricCache {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private const val TAG = "PolycentricCache"
|
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 var _instance: PolycentricCache? = null;
|
||||||
private val CACHE_EXPIRATION_SECONDS = 60 * 60 * 3;
|
private val CACHE_EXPIRATION_SECONDS = 60 * 5;
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
val instance: PolycentricCache
|
val instance: PolycentricCache
|
||||||
|
@ -343,5 +335,20 @@ class PolycentricCache {
|
||||||
it._scope.cancel("PolycentricCache finished");
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -27,7 +27,20 @@ import com.futo.platformplayer.resolveChannelUrl
|
||||||
import com.futo.platformplayer.selectBestImage
|
import com.futo.platformplayer.selectBestImage
|
||||||
import com.futo.platformplayer.stores.FragmentedStorage
|
import com.futo.platformplayer.stores.FragmentedStorage
|
||||||
import com.futo.platformplayer.stores.StringStorage
|
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 com.google.protobuf.ByteString
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Deferred
|
import kotlinx.coroutines.Deferred
|
||||||
|
@ -38,7 +51,6 @@ import userpackage.Protocol
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
import kotlin.Exception
|
|
||||||
|
|
||||||
class StatePolycentric {
|
class StatePolycentric {
|
||||||
private data class LikeDislikeEntry(val unixMilliseconds: Long, val hasLiked: Boolean, val hasDisliked: Boolean);
|
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);
|
_likeDislikeMap[ref.toByteArray().toBase64()] = LikeDislikeEntry(System.currentTimeMillis(), hasLiked, hasDisliked);
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasDisliked(ref: Protocol.Reference): Boolean {
|
fun hasDisliked(data: ByteArray): Boolean {
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val entry = _likeDislikeMap[ref.toByteArray().toBase64()] ?: return false;
|
val entry = _likeDislikeMap[data.toBase64()] ?: return false;
|
||||||
return entry.hasDisliked;
|
return entry.hasDisliked;
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasLiked(ref: Protocol.Reference): Boolean {
|
fun hasLiked(data: ByteArray): Boolean {
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val entry = _likeDislikeMap[ref.toByteArray().toBase64()] ?: return false;
|
val entry = _likeDislikeMap[data.toBase64()] ?: return false;
|
||||||
return entry.hasLiked;
|
return entry.hasLiked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,7 +328,7 @@ class StatePolycentric {
|
||||||
return LikesDislikesReplies(likes, dislikes, replyCount)
|
return LikesDislikesReplies(likes, dislikes, replyCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getCommentPager(contextUrl: String, reference: Protocol.Reference): IPager<IPlatformComment> {
|
suspend fun getCommentPager(contextUrl: String, reference: Protocol.Reference, extraByteReferences: List<ByteArray>? = null): IPager<IPlatformComment> {
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
return EmptyPager()
|
return EmptyPager()
|
||||||
}
|
}
|
||||||
|
@ -338,7 +350,8 @@ class StatePolycentric {
|
||||||
Protocol.QueryReferencesRequestCountReferences.newBuilder()
|
Protocol.QueryReferencesRequestCountReferences.newBuilder()
|
||||||
.setFromType(ContentType.POST.value)
|
.setFromType(ContentType.POST.value)
|
||||||
.build())
|
.build())
|
||||||
.build()
|
.build(),
|
||||||
|
extraByteReferences = extraByteReferences
|
||||||
);
|
);
|
||||||
|
|
||||||
val results = mapQueryReferences(contextUrl, response);
|
val results = mapQueryReferences(contextUrl, response);
|
||||||
|
@ -407,7 +420,8 @@ class StatePolycentric {
|
||||||
ContentType.AVATAR.value,
|
ContentType.AVATAR.value,
|
||||||
ContentType.USERNAME.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 nameEvent = profileEvents.firstOrNull { e -> e.event.contentType == ContentType.USERNAME.value };
|
||||||
val avatarEvent = profileEvents.firstOrNull { e -> e.event.contentType == ContentType.AVATAR.value };
|
val avatarEvent = profileEvents.firstOrNull { e -> e.event.contentType == ContentType.AVATAR.value };
|
||||||
|
|
117
app/src/main/java/com/futo/platformplayer/views/IdenticonView.kt
Normal file
117
app/src/main/java/com/futo/platformplayer/views/IdenticonView.kt
Normal file
|
@ -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<Int> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,15 +9,21 @@ import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
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.IPlatformComment
|
||||||
import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment
|
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.RatingLikeDislikes
|
||||||
import com.futo.platformplayer.api.media.models.ratings.RatingLikes
|
import com.futo.platformplayer.api.media.models.ratings.RatingLikes
|
||||||
import com.futo.platformplayer.constructs.Event1
|
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.logging.Logger
|
||||||
|
import com.futo.platformplayer.setPlatformPlayerLinkMovementMethod
|
||||||
import com.futo.platformplayer.states.StateApp
|
import com.futo.platformplayer.states.StateApp
|
||||||
import com.futo.platformplayer.states.StatePolycentric
|
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.others.CreatorThumbnail
|
||||||
import com.futo.platformplayer.views.pills.PillButton
|
import com.futo.platformplayer.views.pills.PillButton
|
||||||
import com.futo.platformplayer.views.pills.PillRatingLikesDislikes
|
import com.futo.platformplayer.views.pills.PillRatingLikesDislikes
|
||||||
|
@ -104,7 +110,8 @@ class CommentViewHolder : ViewHolder {
|
||||||
|
|
||||||
fun bind(comment: IPlatformComment, readonly: Boolean) {
|
fun bind(comment: IPlatformComment, readonly: Boolean) {
|
||||||
_creatorThumbnail.setThumbnail(comment.author.thumbnail, false);
|
_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;
|
_textAuthor.text = comment.author.name;
|
||||||
|
|
||||||
val date = comment.date;
|
val date = comment.date;
|
||||||
|
@ -161,8 +168,8 @@ class CommentViewHolder : ViewHolder {
|
||||||
_pillRatingLikesDislikes.visibility = View.VISIBLE;
|
_pillRatingLikesDislikes.visibility = View.VISIBLE;
|
||||||
|
|
||||||
if (comment is PolycentricPlatformComment) {
|
if (comment is PolycentricPlatformComment) {
|
||||||
val hasLiked = StatePolycentric.instance.hasLiked(comment.reference);
|
val hasLiked = StatePolycentric.instance.hasLiked(comment.reference.toByteArray());
|
||||||
val hasDisliked = StatePolycentric.instance.hasDisliked(comment.reference);
|
val hasDisliked = StatePolycentric.instance.hasDisliked(comment.reference.toByteArray());
|
||||||
_pillRatingLikesDislikes.setRating(comment.rating, hasLiked, hasDisliked);
|
_pillRatingLikesDislikes.setRating(comment.rating, hasLiked, hasDisliked);
|
||||||
} else {
|
} else {
|
||||||
_pillRatingLikesDislikes.setRating(comment.rating);
|
_pillRatingLikesDislikes.setRating(comment.rating);
|
||||||
|
|
|
@ -126,7 +126,8 @@ class CommentWithReferenceViewHolder : ViewHolder {
|
||||||
_taskGetLiveComment.cancel()
|
_taskGetLiveComment.cancel()
|
||||||
|
|
||||||
_creatorThumbnail.setThumbnail(comment.author.thumbnail, false);
|
_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;
|
_textAuthor.text = comment.author.name;
|
||||||
|
|
||||||
val date = comment.date;
|
val date = comment.date;
|
||||||
|
@ -168,8 +169,8 @@ class CommentWithReferenceViewHolder : ViewHolder {
|
||||||
if (likesDislikesReplies != null) {
|
if (likesDislikesReplies != null) {
|
||||||
Log.i(TAG, "updateLikesDislikesReplies set")
|
Log.i(TAG, "updateLikesDislikesReplies set")
|
||||||
|
|
||||||
val hasLiked = StatePolycentric.instance.hasLiked(c.reference);
|
val hasLiked = StatePolycentric.instance.hasLiked(c.reference.toByteArray());
|
||||||
val hasDisliked = StatePolycentric.instance.hasDisliked(c.reference);
|
val hasDisliked = StatePolycentric.instance.hasDisliked(c.reference.toByteArray());
|
||||||
_pillRatingLikesDislikes.setRating(RatingLikeDislikes(likesDislikesReplies.likes, likesDislikesReplies.dislikes), hasLiked, hasDisliked);
|
_pillRatingLikesDislikes.setRating(RatingLikeDislikes(likesDislikesReplies.likes, likesDislikesReplies.dislikes), hasLiked, hasDisliked);
|
||||||
|
|
||||||
_buttonReplies.setLoading(false)
|
_buttonReplies.setLoading(false)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.bumptech.glide.Glide
|
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.PlatformID
|
||||||
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
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.IPlatformContent
|
||||||
|
@ -18,8 +18,8 @@ import com.futo.platformplayer.images.GlideHelper.Companion.crossfade
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.polycentric.PolycentricCache
|
import com.futo.platformplayer.polycentric.PolycentricCache
|
||||||
import com.futo.platformplayer.states.StateApp
|
import com.futo.platformplayer.states.StateApp
|
||||||
import com.futo.platformplayer.views.others.CreatorThumbnail
|
|
||||||
import com.futo.platformplayer.views.FeedStyle
|
import com.futo.platformplayer.views.FeedStyle
|
||||||
|
import com.futo.platformplayer.views.others.CreatorThumbnail
|
||||||
import com.futo.platformplayer.views.platform.PlatformIndicator
|
import com.futo.platformplayer.views.platform.PlatformIndicator
|
||||||
|
|
||||||
|
|
||||||
|
@ -149,7 +149,8 @@ open class PlaylistView : LinearLayout {
|
||||||
_neopassAnimator?.cancel();
|
_neopassAnimator?.cancel();
|
||||||
_neopassAnimator = null;
|
_neopassAnimator = null;
|
||||||
|
|
||||||
val harborAvailable = claims != null && !claims.ownedClaims.isNullOrEmpty();
|
val firstClaim = claims?.ownedClaims?.firstOrNull();
|
||||||
|
val harborAvailable = firstClaim != null
|
||||||
if (harborAvailable) {
|
if (harborAvailable) {
|
||||||
_imageNeopassChannel?.visibility = View.VISIBLE
|
_imageNeopassChannel?.visibility = View.VISIBLE
|
||||||
if (animate) {
|
if (animate) {
|
||||||
|
@ -160,7 +161,7 @@ open class PlaylistView : LinearLayout {
|
||||||
_imageNeopassChannel?.visibility = View.GONE
|
_imageNeopassChannel?.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
_creatorThumbnail?.setHarborAvailable(harborAvailable, animate)
|
_creatorThumbnail?.setHarborAvailable(harborAvailable, animate, firstClaim?.system?.toProto())
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -6,21 +6,18 @@ import android.widget.ImageButton
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
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.R
|
||||||
import com.futo.platformplayer.Settings
|
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.api.media.PlatformID
|
||||||
import com.futo.platformplayer.models.Subscription
|
|
||||||
import com.futo.platformplayer.constructs.Event0
|
import com.futo.platformplayer.constructs.Event0
|
||||||
import com.futo.platformplayer.constructs.Event1
|
import com.futo.platformplayer.constructs.Event1
|
||||||
import com.futo.platformplayer.constructs.TaskHandler
|
import com.futo.platformplayer.constructs.TaskHandler
|
||||||
import com.futo.platformplayer.dp
|
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.selectBestImage
|
||||||
import com.futo.platformplayer.states.StateSubscriptions
|
import com.futo.platformplayer.states.StateApp
|
||||||
import com.futo.platformplayer.toHumanBytesSpeed
|
|
||||||
import com.futo.platformplayer.toHumanTimeIndicator
|
import com.futo.platformplayer.toHumanTimeIndicator
|
||||||
import com.futo.platformplayer.views.others.CreatorThumbnail
|
import com.futo.platformplayer.views.others.CreatorThumbnail
|
||||||
import com.futo.platformplayer.views.platform.PlatformIndicator
|
import com.futo.platformplayer.views.platform.PlatformIndicator
|
||||||
|
@ -107,7 +104,7 @@ class SubscriptionViewHolder : ViewHolder {
|
||||||
_creatorThumbnail.setThumbnail(avatar, animate);
|
_creatorThumbnail.setThumbnail(avatar, animate);
|
||||||
} else {
|
} else {
|
||||||
_creatorThumbnail.setThumbnail(this.subscription?.channel?.thumbnail, animate);
|
_creatorThumbnail.setThumbnail(this.subscription?.channel?.thumbnail, animate);
|
||||||
_creatorThumbnail.setHarborAvailable(profile != null, animate);
|
_creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profile != null) {
|
if (profile != null) {
|
||||||
|
|
|
@ -334,7 +334,7 @@ open class PreviewVideoView : LinearLayout {
|
||||||
_creatorThumbnail.setThumbnail(avatar, animate);
|
_creatorThumbnail.setThumbnail(avatar, animate);
|
||||||
} else {
|
} else {
|
||||||
_creatorThumbnail.setThumbnail(content?.author?.thumbnail, animate);
|
_creatorThumbnail.setThumbnail(content?.author?.thumbnail, animate);
|
||||||
_creatorThumbnail.setHarborAvailable(profile != null, animate);
|
_creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto());
|
||||||
}
|
}
|
||||||
} else if (_imageChannel != null) {
|
} else if (_imageChannel != null) {
|
||||||
val dp_28 = 28.dp(context.resources);
|
val dp_28 = 28.dp(context.resources);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package com.futo.platformplayer.views.adapters.viewholders
|
package com.futo.platformplayer.views.adapters.viewholders
|
||||||
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
@ -8,12 +7,10 @@ import android.widget.TextView
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
import com.futo.platformplayer.api.media.PlatformID
|
import com.futo.platformplayer.api.media.PlatformID
|
||||||
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
|
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.Event1
|
||||||
import com.futo.platformplayer.constructs.TaskHandler
|
import com.futo.platformplayer.constructs.TaskHandler
|
||||||
import com.futo.platformplayer.dp
|
import com.futo.platformplayer.dp
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.models.Subscription
|
|
||||||
import com.futo.platformplayer.polycentric.PolycentricCache
|
import com.futo.platformplayer.polycentric.PolycentricCache
|
||||||
import com.futo.platformplayer.selectBestImage
|
import com.futo.platformplayer.selectBestImage
|
||||||
import com.futo.platformplayer.states.StateApp
|
import com.futo.platformplayer.states.StateApp
|
||||||
|
@ -76,7 +73,7 @@ class CreatorBarViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter.AnyVi
|
||||||
_creatorThumbnail.setThumbnail(avatar, animate);
|
_creatorThumbnail.setThumbnail(avatar, animate);
|
||||||
} else {
|
} else {
|
||||||
_creatorThumbnail.setThumbnail(_channel?.thumbnail, animate);
|
_creatorThumbnail.setThumbnail(_channel?.thumbnail, animate);
|
||||||
_creatorThumbnail.setHarborAvailable(profile != null, animate);
|
_creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profile != null) {
|
if (profile != null) {
|
||||||
|
@ -148,7 +145,7 @@ class SelectableCreatorBarViewHolder(private val _viewGroup: ViewGroup) : AnyAda
|
||||||
_creatorThumbnail.setThumbnail(avatar, animate);
|
_creatorThumbnail.setThumbnail(avatar, animate);
|
||||||
} else {
|
} else {
|
||||||
_creatorThumbnail.setThumbnail(_channel?.channel?.thumbnail, animate);
|
_creatorThumbnail.setThumbnail(_channel?.channel?.thumbnail, animate);
|
||||||
_creatorThumbnail.setHarborAvailable(profile != null, animate);
|
_creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profile != null) {
|
if (profile != null) {
|
||||||
|
|
|
@ -98,7 +98,7 @@ class CreatorViewHolder(private val _viewGroup: ViewGroup, private val _tiny: Bo
|
||||||
_creatorThumbnail.setThumbnail(avatar, animate);
|
_creatorThumbnail.setThumbnail(avatar, animate);
|
||||||
} else {
|
} else {
|
||||||
_creatorThumbnail.setThumbnail(_authorLink?.thumbnail, animate);
|
_creatorThumbnail.setThumbnail(_authorLink?.thumbnail, animate);
|
||||||
_creatorThumbnail.setHarborAvailable(profile != null, animate);
|
_creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profile != null) {
|
if (profile != null) {
|
||||||
|
|
|
@ -77,7 +77,7 @@ class SubscriptionBarViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter.
|
||||||
_creatorThumbnail.setThumbnail(avatar, animate);
|
_creatorThumbnail.setThumbnail(avatar, animate);
|
||||||
} else {
|
} else {
|
||||||
_creatorThumbnail.setThumbnail(_channel?.thumbnail, animate);
|
_creatorThumbnail.setThumbnail(_channel?.thumbnail, animate);
|
||||||
_creatorThumbnail.setHarborAvailable(profile != null, animate);
|
_creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profile != null) {
|
if (profile != null) {
|
||||||
|
|
|
@ -18,12 +18,20 @@ import android.widget.TextView
|
||||||
import androidx.core.animation.doOnEnd
|
import androidx.core.animation.doOnEnd
|
||||||
import androidx.core.animation.doOnStart
|
import androidx.core.animation.doOnStart
|
||||||
import androidx.core.view.GestureDetectorCompat
|
import androidx.core.view.GestureDetectorCompat
|
||||||
import com.futo.platformplayer.logging.Logger
|
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
import com.futo.platformplayer.constructs.Event0
|
import com.futo.platformplayer.constructs.Event0
|
||||||
import com.futo.platformplayer.constructs.Event1
|
import com.futo.platformplayer.constructs.Event1
|
||||||
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.views.others.CircularProgressBar
|
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 {
|
class GestureControlView : LinearLayout {
|
||||||
private val _scope = CoroutineScope(Dispatchers.Main);
|
private val _scope = CoroutineScope(Dispatchers.Main);
|
||||||
|
@ -95,22 +103,23 @@ class GestureControlView : LinearLayout {
|
||||||
if(p0 == null)
|
if(p0 == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
val minDistance = Math.min(width, height)
|
||||||
if (_isFullScreen && _adjustingBrightness) {
|
if (_isFullScreen && _adjustingBrightness) {
|
||||||
val adjustAmount = (distanceY * 2) / height;
|
val adjustAmount = (distanceY * 2) / minDistance;
|
||||||
_brightnessFactor = (_brightnessFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
|
_brightnessFactor = (_brightnessFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
|
||||||
_progressBrightness.progress = _brightnessFactor;
|
_progressBrightness.progress = _brightnessFactor;
|
||||||
onBrightnessAdjusted.emit(_brightnessFactor);
|
onBrightnessAdjusted.emit(_brightnessFactor);
|
||||||
} else if (_isFullScreen && _adjustingSound) {
|
} else if (_isFullScreen && _adjustingSound) {
|
||||||
val adjustAmount = (distanceY * 2) / height;
|
val adjustAmount = (distanceY * 2) / minDistance;
|
||||||
_soundFactor = (_soundFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
|
_soundFactor = (_soundFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
|
||||||
_progressSound.progress = _soundFactor;
|
_progressSound.progress = _soundFactor;
|
||||||
onSoundAdjusted.emit(_soundFactor);
|
onSoundAdjusted.emit(_soundFactor);
|
||||||
} else if (_adjustingFullscreenUp) {
|
} else if (_adjustingFullscreenUp) {
|
||||||
val adjustAmount = (distanceY * 2) / height;
|
val adjustAmount = (distanceY * 2) / minDistance;
|
||||||
_fullScreenFactorUp = (_fullScreenFactorUp + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
|
_fullScreenFactorUp = (_fullScreenFactorUp + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
|
||||||
_layoutControlsFullscreen.alpha = _fullScreenFactorUp;
|
_layoutControlsFullscreen.alpha = _fullScreenFactorUp;
|
||||||
} else if (_adjustingFullscreenDown) {
|
} else if (_adjustingFullscreenDown) {
|
||||||
val adjustAmount = (-distanceY * 2) / height;
|
val adjustAmount = (-distanceY * 2) / minDistance;
|
||||||
_fullScreenFactorDown = (_fullScreenFactorDown + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
|
_fullScreenFactorDown = (_fullScreenFactorDown + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
|
||||||
_layoutControlsFullscreen.alpha = _fullScreenFactorDown;
|
_layoutControlsFullscreen.alpha = _fullScreenFactorDown;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -12,12 +12,16 @@ import com.bumptech.glide.Glide
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
import com.futo.platformplayer.constructs.Event1
|
import com.futo.platformplayer.constructs.Event1
|
||||||
import com.futo.platformplayer.images.GlideHelper.Companion.crossfade
|
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 {
|
class CreatorThumbnail : ConstraintLayout {
|
||||||
private val _root: ConstraintLayout;
|
private val _root: ConstraintLayout;
|
||||||
private val _imageChannelThumbnail: ImageView;
|
private val _imageChannelThumbnail: ImageView;
|
||||||
private val _imageNewActivity: ImageView;
|
private val _imageNewActivity: ImageView;
|
||||||
private val _imageNeoPass: ImageView;
|
private val _imageNeoPass: ImageView;
|
||||||
|
private val _identicon: IdenticonView;
|
||||||
private var _harborAnimator: ObjectAnimator? = null;
|
private var _harborAnimator: ObjectAnimator? = null;
|
||||||
private var _imageAnimator: ObjectAnimator? = null;
|
private var _imageAnimator: ObjectAnimator? = null;
|
||||||
|
|
||||||
|
@ -28,19 +32,22 @@ class CreatorThumbnail : ConstraintLayout {
|
||||||
|
|
||||||
_root = findViewById(R.id.root);
|
_root = findViewById(R.id.root);
|
||||||
_imageChannelThumbnail = findViewById(R.id.image_channel_thumbnail);
|
_imageChannelThumbnail = findViewById(R.id.image_channel_thumbnail);
|
||||||
|
_identicon = findViewById(R.id.identicon);
|
||||||
_imageChannelThumbnail.clipToOutline = true;
|
_imageChannelThumbnail.clipToOutline = true;
|
||||||
|
_imageChannelThumbnail.visibility = View.GONE
|
||||||
_imageNewActivity = findViewById(R.id.image_new_activity);
|
_imageNewActivity = findViewById(R.id.image_new_activity);
|
||||||
_imageNeoPass = findViewById(R.id.image_neopass);
|
_imageNeoPass = findViewById(R.id.image_neopass);
|
||||||
|
|
||||||
if (!isInEditMode) {
|
if (!isInEditMode) {
|
||||||
setHarborAvailable(false, animate = false);
|
setHarborAvailable(false, animate = false, system = null);
|
||||||
setNewActivity(false);
|
setNewActivity(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
|
_imageChannelThumbnail.visibility = View.GONE;
|
||||||
_imageChannelThumbnail.setImageResource(R.drawable.placeholder_channel_thumbnail);
|
_imageChannelThumbnail.setImageResource(R.drawable.placeholder_channel_thumbnail);
|
||||||
setHarborAvailable(false, animate = false);
|
setHarborAvailable(false, animate = false, system = null);
|
||||||
setNewActivity(false);
|
setNewActivity(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,13 +57,24 @@ class CreatorThumbnail : ConstraintLayout {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_imageChannelThumbnail.visibility = View.VISIBLE;
|
||||||
|
|
||||||
_harborAnimator?.cancel();
|
_harborAnimator?.cancel();
|
||||||
_harborAnimator = null;
|
_harborAnimator = null;
|
||||||
|
|
||||||
_imageAnimator?.cancel();
|
_imageAnimator?.cancel();
|
||||||
_imageAnimator = null;
|
_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) {
|
if (animate) {
|
||||||
Glide.with(_imageChannelThumbnail)
|
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?.cancel();
|
||||||
_harborAnimator = null;
|
_harborAnimator = null;
|
||||||
|
|
||||||
|
@ -85,6 +103,13 @@ class CreatorThumbnail : ConstraintLayout {
|
||||||
} else {
|
} else {
|
||||||
_imageNeoPass.visibility = View.GONE;
|
_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) {
|
fun setChannelImageResource(resource: Int?, animate: Boolean) {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.futo.platformplayer.views.overlays
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Animatable
|
import android.graphics.drawable.Animatable
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.view.Gravity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
@ -16,6 +17,21 @@ class LoaderOverlay(context: Context, attrs: AttributeSet?) : FrameLayout(contex
|
||||||
inflate(context, R.layout.overlay_loader, this);
|
inflate(context, R.layout.overlay_loader, this);
|
||||||
_container = findViewById(R.id.container);
|
_container = findViewById(R.id.container);
|
||||||
_loader = findViewById(R.id.loader);
|
_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() {
|
fun show() {
|
||||||
|
|
|
@ -6,8 +6,8 @@ import android.view.View
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import com.futo.platformplayer.UIDialogs
|
|
||||||
import com.futo.platformplayer.R
|
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.IPlatformComment
|
||||||
import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment
|
import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment
|
||||||
import com.futo.platformplayer.api.media.structures.IPager
|
import com.futo.platformplayer.api.media.structures.IPager
|
||||||
|
@ -102,7 +102,8 @@ class RepliesOverlay : LinearLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
_creatorThumbnail.setThumbnail(parentComment.author.thumbnail, false);
|
_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);
|
_topbar.setInfo(context.getString(R.string.Replies), metadata);
|
||||||
|
|
|
@ -94,4 +94,11 @@
|
||||||
android:text="@string/import_profile" />
|
android:text="@string/import_profile" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.futo.platformplayer.views.overlays.LoaderOverlay
|
||||||
|
android:id="@+id/loader_overlay"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:centerLoader="true"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -51,6 +51,21 @@
|
||||||
app:layout_constraintLeft_toLeftOf="@id/image_polycentric"
|
app:layout_constraintLeft_toLeftOf="@id/image_polycentric"
|
||||||
app:layout_constraintRight_toRightOf="@id/image_polycentric" />
|
app:layout_constraintRight_toRightOf="@id/image_polycentric" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_system"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="gX0eCWctTm6WHVGot4sMAh7NDAIwWsIM5tRsOz9dX04="
|
||||||
|
android:fontFamily="@font/inter_regular"
|
||||||
|
android:textSize="10dp"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="middle"
|
||||||
|
android:textColor="@color/gray_67"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/edit_profile_name"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layout_buttons"
|
android:id="@+id/layout_buttons"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -91,4 +106,11 @@
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
app:buttonBackground="@drawable/background_big_button_red"/>
|
app:buttonBackground="@drawable/background_big_button_red"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.futo.platformplayer.views.overlays.LoaderOverlay
|
||||||
|
android:id="@+id/loader_overlay"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:centerLoader="true"
|
||||||
|
android:visibility="gone" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -17,5 +17,6 @@
|
||||||
android:layout_gravity="top|center_horizontal"
|
android:layout_gravity="top|center_horizontal"
|
||||||
android:alpha="0.7"
|
android:alpha="0.7"
|
||||||
android:layout_marginTop="80dp"
|
android:layout_marginTop="80dp"
|
||||||
|
android:layout_marginBottom="80dp"
|
||||||
android:contentDescription="@string/loading" />
|
android:contentDescription="@string/loading" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
|
@ -7,6 +7,18 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/root">
|
android:id="@+id/root">
|
||||||
|
|
||||||
|
<com.futo.platformplayer.views.IdenticonView
|
||||||
|
android:id="@+id/identicon"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
app:srcCompat="@drawable/ic_futo_logo"
|
||||||
|
android:background="@drawable/rounded_outline"
|
||||||
|
android:contentDescription="@string/channel_image"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/image_channel_thumbnail"
|
android:id="@+id/image_channel_thumbnail"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
6
app/src/main/res/values/loader_overlay_attrs.xml
Normal file
6
app/src/main/res/values/loader_overlay_attrs.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<declare-styleable name="LoaderOverlay">
|
||||||
|
<attr name="centerLoader" format="boolean" />
|
||||||
|
</declare-styleable>
|
||||||
|
</resources>
|
|
@ -1 +1 @@
|
||||||
Subproject commit 863d0be1322660c99e4d0cdae0b45d0a5918542d
|
Subproject commit 01270edbb4b6b4fb004e22fc529bf787c7f5be81
|
|
@ -1 +1 @@
|
||||||
Subproject commit b0e35a9b6631fb3279fb38619ecc3eba812e5ed6
|
Subproject commit 7d4303c82a1ec06871f0717a606f5c6cb87e78be
|
|
@ -1 +1 @@
|
||||||
Subproject commit d41cc8e848891ef8e949e6d49384b754e7c305c7
|
Subproject commit 13551ab67fc8fb1899b5bcbfdec750855b154790
|
|
@ -1 +1 @@
|
||||||
Subproject commit b0e35a9b6631fb3279fb38619ecc3eba812e5ed6
|
Subproject commit 7d4303c82a1ec06871f0717a606f5c6cb87e78be
|
|
@ -1 +1 @@
|
||||||
Subproject commit 86cd96c41f15f7f73c091f61800a49f376a38150
|
Subproject commit 7695198eeaeaaea4726712c460081c411ef67866
|
Loading…
Add table
Add a link
Reference in a new issue