Implemented delete comment support. Implemented Comments tab. Implemented replies overlay showing parent comment.

This commit is contained in:
Koen 2023-11-29 13:43:40 +01:00
parent 8a35cd0e82
commit 3bf73ed5e8
45 changed files with 1164 additions and 68 deletions

View file

@ -32,7 +32,7 @@ import com.futo.platformplayer.models.Playlist
import com.futo.platformplayer.models.Subscription
import com.futo.platformplayer.parsers.HLS
import com.futo.platformplayer.states.*
import com.futo.platformplayer.views.Loader
import com.futo.platformplayer.views.LoaderView
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuGroup
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay
@ -137,7 +137,7 @@ class UISlideOverlays {
}
fun showHlsPicker(video: IPlatformVideoDetails, source: Any, sourceUrl: String, container: ViewGroup): SlideUpMenuOverlay {
val items = arrayListOf<View>(Loader(container.context))
val items = arrayListOf<View>(LoaderView(container.context))
val slideUpMenuOverlay = SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.download_video), null, true, items)
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
@ -501,7 +501,7 @@ class UISlideOverlays {
val dp70 = 70.dp(container.context.resources);
val dp15 = 15.dp(container.context.resources);
val overlay = SlideUpMenuOverlay(container.context, container, text, null, true, listOf(
Loader(container.context, true, dp70).apply {
LoaderView(container.context, true, dp70).apply {
this.setPadding(0, dp15, 0, dp15);
}
), true);

View file

@ -90,6 +90,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
lateinit var _fragMainPlaylistSearchResults: PlaylistSearchResultsFragment;
lateinit var _fragMainSuggestions: SuggestionsFragment;
lateinit var _fragMainSubscriptions: CreatorsFragment;
lateinit var _fragMainComments: CommentsFragment;
lateinit var _fragMainSubscriptionsFeed: SubscriptionsFeedFragment;
lateinit var _fragMainChannel: ChannelFragment;
lateinit var _fragMainSources: SourcesFragment;
@ -205,6 +206,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
_fragMainCreatorSearchResults = CreatorSearchResultsFragment.newInstance();
_fragMainPlaylistSearchResults = PlaylistSearchResultsFragment.newInstance();
_fragMainSubscriptions = CreatorsFragment.newInstance();
_fragMainComments = CommentsFragment.newInstance();
_fragMainChannel = ChannelFragment.newInstance();
_fragMainSubscriptionsFeed = SubscriptionsFeedFragment.newInstance();
_fragMainSources = SourcesFragment.newInstance();
@ -282,6 +284,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
//Set top bars
_fragMainHome.topBar = _fragTopBarGeneral;
_fragMainSubscriptions.topBar = _fragTopBarGeneral;
_fragMainComments.topBar = _fragTopBarGeneral;
_fragMainSuggestions.topBar = _fragTopBarSearch;
_fragMainVideoSearchResults.topBar = _fragTopBarSearch;
_fragMainCreatorSearchResults.topBar = _fragTopBarSearch;
@ -916,6 +919,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
GeneralTopBarFragment::class -> _fragTopBarGeneral as T;
SearchTopBarFragment::class -> _fragTopBarSearch as T;
CreatorsFragment::class -> _fragMainSubscriptions as T;
CommentsFragment::class -> _fragMainComments as T;
SubscriptionsFeedFragment::class -> _fragMainSubscriptionsFeed as T;
PlaylistSearchResultsFragment::class -> _fragMainPlaylistSearchResults as T;
ChannelFragment::class -> _fragMainChannel as T;

View file

@ -15,7 +15,7 @@ import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.*
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.views.Loader
import com.futo.platformplayer.views.LoaderView
import com.futo.platformplayer.views.fields.FieldForm
import com.futo.platformplayer.views.fields.ReadOnlyTextField
import com.google.android.material.button.MaterialButton
@ -23,7 +23,7 @@ import com.google.android.material.button.MaterialButton
class SettingsActivity : AppCompatActivity(), IWithResultLauncher {
private lateinit var _form: FieldForm;
private lateinit var _buttonBack: ImageButton;
private lateinit var _loader: Loader;
private lateinit var _loaderView: LoaderView;
private lateinit var _devSets: LinearLayout;
private lateinit var _buttonDev: MaterialButton;
@ -43,7 +43,7 @@ class SettingsActivity : AppCompatActivity(), IWithResultLauncher {
_buttonBack = findViewById(R.id.button_back);
_buttonDev = findViewById(R.id.button_dev);
_devSets = findViewById(R.id.dev_settings);
_loader = findViewById(R.id.loader);
_loaderView = findViewById(R.id.loader);
_form.onChanged.subscribe { field, value ->
Logger.i("SettingsActivity", "Setting [${field.field?.name}] changed, saving");
@ -70,9 +70,9 @@ class SettingsActivity : AppCompatActivity(), IWithResultLauncher {
fun reloadSettings() {
_form.setSearchVisible(false);
_loader.start();
_loaderView.start();
_form.fromObject(lifecycleScope, Settings.instance) {
_loader.stop();
_loaderView.stop();
_form.setSearchVisible(true);
var devCounter = 0;

View file

@ -4,10 +4,7 @@ import com.futo.platformplayer.api.media.IPlatformClient
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
import com.futo.platformplayer.api.media.models.ratings.IRating
import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.polycentric.PolycentricCache
import com.futo.platformplayer.states.StatePolycentric
import com.futo.polycentric.core.Pointer
import com.futo.polycentric.core.SignedEvent
import userpackage.Protocol.Reference
import java.time.OffsetDateTime
@ -20,16 +17,18 @@ class PolycentricPlatformComment : IPlatformComment {
override val replyCount: Int?;
val eventPointer: Pointer;
val reference: Reference;
constructor(contextUrl: String, author: PlatformAuthorLink, msg: String, rating: IRating, date: OffsetDateTime, reference: Reference, replyCount: Int? = null) {
constructor(contextUrl: String, author: PlatformAuthorLink, msg: String, rating: IRating, date: OffsetDateTime, eventPointer: Pointer, replyCount: Int? = null) {
this.contextUrl = contextUrl;
this.author = author;
this.message = msg;
this.rating = rating;
this.date = date;
this.replyCount = replyCount;
this.reference = reference;
this.eventPointer = eventPointer;
this.reference = eventPointer.toReference();
}
override fun getReplies(client: IPlatformClient): IPager<IPlatformComment> {
@ -37,7 +36,7 @@ class PolycentricPlatformComment : IPlatformComment {
}
fun cloneWithUpdatedReplyCount(replyCount: Int?): PolycentricPlatformComment {
return PolycentricPlatformComment(contextUrl, author, message, rating, date, reference, replyCount);
return PolycentricPlatformComment(contextUrl, author, message, rating, date, eventPointer, replyCount);
}
companion object {

View file

@ -118,7 +118,7 @@ class CommentDialog(context: Context?, val contextUrl: String, val ref: Protocol
msg = comment,
rating = RatingLikeDislikes(0, 0),
date = OffsetDateTime.now(),
reference = eventPointer.toReference()
eventPointer = eventPointer
));
dismiss();

View file

@ -351,6 +351,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
ButtonDefinition(4, R.drawable.ic_playlist, R.drawable.ic_playlist_filled, R.string.playlists, canToggle = false, { it.currentMain is PlaylistsFragment }, { it.navigate<PlaylistsFragment>() }),
ButtonDefinition(5, R.drawable.ic_history, R.drawable.ic_history, R.string.history, canToggle = false, { it.currentMain is HistoryFragment }, { it.navigate<HistoryFragment>() }),
ButtonDefinition(6, R.drawable.ic_download, R.drawable.ic_download, R.string.downloads, canToggle = false, { it.currentMain is DownloadsFragment }, { it.navigate<DownloadsFragment>() }),
ButtonDefinition(8, R.drawable.ic_chat, R.drawable.ic_chat_filled, R.string.comments, canToggle = true, { it.currentMain is CommentsFragment }, { it.navigate<CommentsFragment>() }),
ButtonDefinition(7, R.drawable.ic_settings, R.drawable.ic_settings, R.string.settings, canToggle = false, { false }, {
val c = it.context ?: return@ButtonDefinition;
Logger.i(TAG, "settings preventPictureInPicture()");

View file

@ -35,6 +35,11 @@ class BuyFragment : MainFragment() {
return view;
}
override fun onDestroyMainView() {
super.onDestroyMainView()
_view = null
}
class BuyView: LinearLayout {
private val _fragment: BuyFragment;

View file

@ -0,0 +1,306 @@
package com.futo.platformplayer.fragment.mainactivity.main
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewPropertyAnimator
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.Spinner
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.activities.PolycentricHomeActivity
import com.futo.platformplayer.api.media.models.comments.IPlatformComment
import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePolycentric
import com.futo.platformplayer.views.adapters.CommentWithReferenceViewHolder
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
import com.futo.platformplayer.views.overlays.RepliesOverlay
import com.futo.polycentric.core.PublicKey
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.net.UnknownHostException
class CommentsFragment : MainFragment() {
override val isMainView : Boolean = true
override val isTab: Boolean = true
override val hasBottomBar: Boolean get() = true
private var _view: CommentsView? = null
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
super.onShownWithView(parameter, isBack)
_view?.onShown()
}
override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val view = CommentsView(this, inflater)
_view = view
return view
}
override fun onDestroyMainView() {
super.onDestroyMainView()
_view = null
}
override fun onBackPressed(): Boolean {
return _view?.onBackPressed() ?: false
}
override fun onResume() {
super.onResume()
_view?.onShown()
}
companion object {
fun newInstance() = CommentsFragment().apply {}
private const val TAG = "CommentsFragment"
}
class CommentsView : FrameLayout {
private val _fragment: CommentsFragment
private val _recyclerComments: RecyclerView;
private val _adapterComments: InsertedViewAdapterWithLoader<CommentWithReferenceViewHolder>;
private val _textCommentCount: TextView
private val _comments: ArrayList<IPlatformComment> = arrayListOf();
private val _llmReplies: LinearLayoutManager;
private val _spinnerSortBy: Spinner;
private val _layoutNotLoggedIn: LinearLayout;
private val _buttonLogin: LinearLayout;
private var _loading = false;
private val _repliesOverlay: RepliesOverlay;
private var _repliesAnimator: ViewPropertyAnimator? = null;
private val _taskLoadComments = if(!isInEditMode) TaskHandler<PublicKey, List<IPlatformComment>>(
StateApp.instance.scopeGetter, { StatePolycentric.instance.getSystemComments(context, it) })
.success { pager -> onCommentsLoaded(pager); }
.exception<UnknownHostException> {
UIDialogs.toast("Failed to load comments");
setLoading(false);
}
.exception<Throwable> {
Logger.e(TAG, "Failed to load comments.", it);
UIDialogs.toast(context, context.getString(R.string.failed_to_load_comments) + "\n" + (it.message ?: ""));
setLoading(false);
} else TaskHandler(IPlatformVideoDetails::class.java, StateApp.instance.scopeGetter);
constructor(fragment: CommentsFragment, inflater: LayoutInflater) : super(inflater.context) {
_fragment = fragment
inflater.inflate(R.layout.fragment_comments, this)
val commentHeader = findViewById<LinearLayout>(R.id.layout_header)
(commentHeader.parent as ViewGroup).removeView(commentHeader)
_textCommentCount = commentHeader.findViewById(R.id.text_comment_count)
_recyclerComments = findViewById(R.id.recycler_comments)
_adapterComments = InsertedViewAdapterWithLoader(context, arrayListOf(commentHeader), arrayListOf(),
childCountGetter = { _comments.size },
childViewHolderBinder = { viewHolder, position -> viewHolder.bind(_comments[position]); },
childViewHolderFactory = { viewGroup, _ ->
val holder = CommentWithReferenceViewHolder(viewGroup);
holder.onDelete.subscribe(::onDelete);
holder.onRepliesClick.subscribe(::onRepliesClick);
return@InsertedViewAdapterWithLoader holder;
}
);
_spinnerSortBy = commentHeader.findViewById(R.id.spinner_sortby);
_spinnerSortBy.adapter = ArrayAdapter(context, R.layout.spinner_item_simple, resources.getStringArray(R.array.comments_sortby_array)).also {
it.setDropDownViewResource(R.layout.spinner_dropdownitem_simple);
};
_spinnerSortBy.setSelection(0);
_spinnerSortBy.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View?, pos: Int, id: Long) {
if (_spinnerSortBy.selectedItemPosition == 0) {
_comments.sortByDescending { it.date!! }
} else if (_spinnerSortBy.selectedItemPosition == 1) {
_comments.sortBy { it.date!! }
}
_adapterComments.notifyDataSetChanged()
}
override fun onNothingSelected(parent: AdapterView<*>?) = Unit
}
_llmReplies = LinearLayoutManager(context);
_recyclerComments.layoutManager = _llmReplies;
_recyclerComments.adapter = _adapterComments;
updateCommentCountString();
_layoutNotLoggedIn = findViewById(R.id.layout_not_logged_in)
_layoutNotLoggedIn.visibility = View.GONE
_buttonLogin = findViewById(R.id.button_login)
_buttonLogin.setOnClickListener {
context.startActivity(Intent(context, PolycentricHomeActivity::class.java));
}
_repliesOverlay = findViewById(R.id.replies_overlay);
_repliesOverlay.onClose.subscribe { setRepliesOverlayVisible(isVisible = false, animate = true); };
}
private fun onDelete(comment: IPlatformComment) {
val processHandle = StatePolycentric.instance.processHandle ?: return
if (comment !is PolycentricPlatformComment) {
return
}
val index = _comments.indexOf(comment)
if (index != -1) {
_comments.removeAt(index)
_adapterComments.notifyItemRemoved(_adapterComments.childToParentPosition(index))
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
try {
processHandle.delete(comment.eventPointer.process, comment.eventPointer.logicalClock)
} catch (e: Throwable) {
Logger.e(TAG, "Failed to delete event.", e);
return@launch;
}
try {
Logger.i(TAG, "Started backfill");
processHandle.fullyBackfillServersAnnounceExceptions();
Logger.i(TAG, "Finished backfill");
} catch (e: Throwable) {
Logger.e(TAG, "Failed to fully backfill servers.", e);
}
}
}
}
fun onBackPressed(): Boolean {
if (_repliesOverlay.visibility == View.VISIBLE) {
setRepliesOverlayVisible(isVisible = false, animate = true);
return true
}
return false
}
private fun onRepliesClick(c: IPlatformComment) {
val replyCount = c.replyCount ?: 0;
var metadata = "";
if (replyCount > 0) {
metadata += "$replyCount " + context.getString(R.string.replies);
}
if (c is PolycentricPlatformComment) {
var parentComment: PolycentricPlatformComment = c;
_repliesOverlay.load(false, metadata, c.contextUrl, c.reference, c,
{ StatePolycentric.instance.getCommentPager(c.contextUrl, c.reference) },
{
val newComment = parentComment.cloneWithUpdatedReplyCount((parentComment.replyCount ?: 0) + 1);
val index = _comments.indexOf(c);
_comments[index] = newComment;
_adapterComments.notifyItemChanged(_adapterComments.childToParentPosition(index));
parentComment = newComment;
});
} else {
_repliesOverlay.load(true, metadata, null, null, c, { StatePlatform.instance.getSubComments(c) });
}
setRepliesOverlayVisible(isVisible = true, animate = true);
}
private fun setRepliesOverlayVisible(isVisible: Boolean, animate: Boolean) {
val desiredVisibility = if (isVisible) View.VISIBLE else View.GONE
if (_repliesOverlay.visibility == desiredVisibility) {
return;
}
_repliesAnimator?.cancel();
if (isVisible) {
_repliesOverlay.visibility = View.VISIBLE;
if (animate) {
_repliesOverlay.translationY = _repliesOverlay.height.toFloat();
_repliesAnimator = _repliesOverlay.animate()
.setDuration(300)
.translationY(0f)
.withEndAction {
_repliesAnimator = null;
}.apply { start() };
}
} else {
if (animate) {
_repliesOverlay.translationY = 0f;
_repliesAnimator = _repliesOverlay.animate()
.setDuration(300)
.translationY(_repliesOverlay.height.toFloat())
.withEndAction {
_repliesOverlay.visibility = GONE;
_repliesAnimator = null;
}.apply { start(); }
} else {
_repliesOverlay.visibility = View.GONE;
_repliesOverlay.translationY = _repliesOverlay.height.toFloat();
}
}
}
private fun updateCommentCountString() {
_textCommentCount.text = context.getString(R.string.these_are_all_commentcount_comments_you_have_made_in_grayjay).replace("{commentCount}", _comments.size.toString())
}
private fun setLoading(loading: Boolean) {
if (_loading == loading) {
return;
}
_loading = loading;
_adapterComments.setLoading(loading);
}
private fun fetchComments() {
val system = StatePolycentric.instance.processHandle?.system ?: return
_comments.clear()
_adapterComments.notifyDataSetChanged()
setLoading(true)
_taskLoadComments.run(system)
}
private fun onCommentsLoaded(comments: List<IPlatformComment>) {
setLoading(false)
_comments.addAll(comments)
if (_spinnerSortBy.selectedItemPosition == 0) {
_comments.sortByDescending { it.date!! }
} else if (_spinnerSortBy.selectedItemPosition == 1) {
_comments.sortBy { it.date!! }
}
_adapterComments.notifyDataSetChanged()
updateCommentCountString()
}
fun onShown() {
val processHandle = StatePolycentric.instance.processHandle
if (processHandle != null) {
_layoutNotLoggedIn.visibility = View.GONE
_recyclerComments.visibility = View.VISIBLE
fetchComments()
} else {
_layoutNotLoggedIn.visibility = View.VISIBLE
_recyclerComments.visibility= View.GONE
}
}
}
}

View file

@ -224,7 +224,7 @@ class PostDetailFragment : MainFragment {
updateCommentType(false);
};
_commentsList.onClick.subscribe { c ->
_commentsList.onRepliesClick.subscribe { c ->
val replyCount = c.replyCount ?: 0;
var metadata = "";
if (replyCount > 0) {
@ -233,7 +233,7 @@ class PostDetailFragment : MainFragment {
if (c is PolycentricPlatformComment) {
var parentComment: PolycentricPlatformComment = c;
_repliesOverlay.load(_toggleCommentType.value, metadata, c.contextUrl, c.reference,
_repliesOverlay.load(_toggleCommentType.value, metadata, c.contextUrl, c.reference, c,
{ StatePolycentric.instance.getCommentPager(c.contextUrl, c.reference) },
{
val newComment = parentComment.cloneWithUpdatedReplyCount((parentComment.replyCount ?: 0) + 1);
@ -241,7 +241,7 @@ class PostDetailFragment : MainFragment {
parentComment = newComment;
});
} else {
_repliesOverlay.load(_toggleCommentType.value, metadata, null, null, { StatePlatform.instance.getSubComments(c) });
_repliesOverlay.load(_toggleCommentType.value, metadata, null, null, c, { StatePlatform.instance.getSubComments(c) });
}
setRepliesOverlayVisible(isVisible = true, animate = true);

View file

@ -37,7 +37,6 @@ import com.futo.platformplayer.api.media.LiveChatManager
import com.futo.platformplayer.api.media.PlatformID
import com.futo.platformplayer.api.media.exceptions.ContentNotAvailableYetException
import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
import com.futo.platformplayer.api.media.models.PlatformAuthorMembershipLink
import com.futo.platformplayer.api.media.models.chapters.ChapterType
import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment
@ -52,7 +51,6 @@ import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
import com.futo.platformplayer.api.media.platforms.js.models.IJSContentDetails
import com.futo.platformplayer.api.media.platforms.js.models.JSVideoDetails
import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.casting.CastConnectionState
@ -60,7 +58,6 @@ import com.futo.platformplayer.casting.StateCasting
import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.dialogs.AutoUpdateDialog
import com.futo.platformplayer.downloads.VideoLocal
import com.futo.platformplayer.engine.exceptions.ScriptAgeException
import com.futo.platformplayer.engine.exceptions.ScriptException
@ -109,7 +106,6 @@ import java.time.OffsetDateTime
import kotlin.collections.ArrayList
import kotlin.math.abs
import kotlin.math.roundToLong
import kotlin.streams.toList
class VideoDetailView : ConstraintLayout {
@ -578,7 +574,7 @@ class VideoDetailView : ConstraintLayout {
_container_content_current = _container_content_main;
_commentsList.onClick.subscribe { c ->
_commentsList.onRepliesClick.subscribe { c ->
val replyCount = c.replyCount ?: 0;
var metadata = "";
if (replyCount > 0) {
@ -587,7 +583,7 @@ class VideoDetailView : ConstraintLayout {
if (c is PolycentricPlatformComment) {
var parentComment: PolycentricPlatformComment = c;
_container_content_replies.load(_toggleCommentType.value, metadata, c.contextUrl, c.reference,
_container_content_replies.load(_toggleCommentType.value, metadata, c.contextUrl, c.reference, c,
{ StatePolycentric.instance.getCommentPager(c.contextUrl, c.reference) },
{
val newComment = parentComment.cloneWithUpdatedReplyCount((parentComment.replyCount ?: 0) + 1);
@ -595,7 +591,7 @@ class VideoDetailView : ConstraintLayout {
parentComment = newComment;
});
} else {
_container_content_replies.load(_toggleCommentType.value, metadata, null, null, { StatePlatform.instance.getSubComments(c) });
_container_content_replies.load(_toggleCommentType.value, metadata, null, null, c, { StatePlatform.instance.getSubComments(c) });
}
switchContentView(_container_content_replies);
};

View file

@ -218,6 +218,59 @@ class StatePolycentric {
}
}
fun getSystemComments(context: Context, system: PublicKey): List<IPlatformComment> {
val dp_25 = 25.dp(context.resources)
val systemState = SystemState.fromStorageTypeSystemState(Store.instance.getSystemState(system))
val author = system.systemToURLInfoSystemLinkUrl(systemState.servers.asIterable())
val posts = arrayListOf<PolycentricPlatformComment>()
Store.instance.enumerateSignedEvents(system, ContentType.POST) { se ->
val ev = se.event
val post = Protocol.Post.parseFrom(ev.content)
posts.add(PolycentricPlatformComment(
contextUrl = author,
author = PlatformAuthorLink(
id = PlatformID("polycentric", author, null, ClaimType.POLYCENTRIC.value.toInt()),
name = systemState.username,
url = author,
thumbnail = systemState.avatar?.selectBestImage(dp_25 * dp_25)?.let { img -> img.toURLInfoSystemLinkUrl(system.toProto(), img.process, listOf(PolycentricCache.SERVER)) },
subscribers = null
),
msg = if (post.content.count() > PolycentricPlatformComment.MAX_COMMENT_SIZE) post.content.substring(0, PolycentricPlatformComment.MAX_COMMENT_SIZE) else post.content,
rating = RatingLikeDislikes(0, 0),
date = if (ev.unixMilliseconds != null) Instant.ofEpochMilli(ev.unixMilliseconds!!).atOffset(ZoneOffset.UTC) else OffsetDateTime.MIN,
replyCount = 0,
eventPointer = se.toPointer()
))
}
return posts
}
suspend fun getLiveComment(contextUrl: String, reference: Protocol.Reference): PolycentricPlatformComment {
val response = ApiMethods.getQueryReferences(PolycentricCache.SERVER, reference, null,
Protocol.QueryReferencesRequestEvents.newBuilder()
.setFromType(ContentType.POST.value)
.addAllCountLwwElementReferences(arrayListOf(
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder()
.setFromType(ContentType.OPINION.value)
.setValue(ByteString.copyFrom(Opinion.like.data))
.build(),
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder()
.setFromType(ContentType.OPINION.value)
.setValue(ByteString.copyFrom(Opinion.dislike.data))
.build()
))
.addCountReferences(
Protocol.QueryReferencesRequestCountReferences.newBuilder()
.setFromType(ContentType.POST.value)
.build())
.build()
)
return mapQueryReferences(contextUrl, response).first()
}
suspend fun getCommentPager(contextUrl: String, reference: Protocol.Reference): IPager<IPlatformComment> {
val response = ApiMethods.getQueryReferences(PolycentricCache.SERVER, reference, null,
Protocol.QueryReferencesRequestEvents.newBuilder()
@ -284,7 +337,7 @@ class StatePolycentric {
};
}
private suspend fun mapQueryReferences(contextUrl: String, response: Protocol.QueryReferencesResponse): List<IPlatformComment> {
private suspend fun mapQueryReferences(contextUrl: String, response: Protocol.QueryReferencesResponse): List<PolycentricPlatformComment> {
return response.itemsList.mapNotNull {
val sev = SignedEvent.fromProto(it.event);
val ev = sev.event;
@ -338,7 +391,7 @@ class StatePolycentric {
rating = RatingLikeDislikes(likes, dislikes),
date = if (unixMilliseconds != null) Instant.ofEpochMilli(unixMilliseconds).atOffset(ZoneOffset.UTC) else OffsetDateTime.MIN,
replyCount = replies.toInt(),
reference = sev.toPointer().toReference()
eventPointer = sev.toPointer()
);
} catch (e: Throwable) {
return@mapNotNull null;

View file

@ -1,6 +1,7 @@
package com.futo.platformplayer.views
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Animatable
import android.util.AttributeSet
import android.view.LayoutInflater
@ -11,9 +12,10 @@ import android.widget.LinearLayout
import androidx.core.view.updateLayoutParams
import com.futo.platformplayer.R
class Loader : LinearLayout {
class LoaderView : LinearLayout {
private val _imageLoader: ImageView;
private val _automatic: Boolean;
private var _isWhite: Boolean;
private val _animatable: Animatable;
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
@ -24,18 +26,25 @@ class Loader : LinearLayout {
if (attrs != null) {
val attrArr = context.obtainStyledAttributes(attrs, R.styleable.LoaderView, 0, 0);
_automatic = attrArr.getBoolean(R.styleable.LoaderView_automatic, false);
_isWhite = attrArr.getBoolean(R.styleable.LoaderView_isWhite, false);
attrArr.recycle();
} else {
_automatic = false;
_isWhite = false;
}
visibility = View.GONE;
if (_isWhite) {
_imageLoader.setColorFilter(Color.WHITE)
}
}
constructor(context: Context, automatic: Boolean, height: Int = -1) : super(context) {
constructor(context: Context, automatic: Boolean, height: Int = -1, isWhite: Boolean = false) : super(context) {
inflate(context, R.layout.view_loader, this);
_imageLoader = findViewById(R.id.image_loader);
_animatable = _imageLoader.drawable as Animatable;
_automatic = automatic;
_isWhite = isWhite;
if(height > 0) {
layoutParams = ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, height);

View file

@ -41,7 +41,7 @@ class MonetizationView : LinearLayout {
private val _textMerchandise: TextView;
private val _recyclerMerchandise: RecyclerView;
private val _loaderMerchandise: Loader;
private val _loaderViewMerchandise: LoaderView;
private val _layoutMerchandise: FrameLayout;
private var _merchandiseAdapterView: AnyAdapterView<StoreItem, StoreItemViewHolder>? = null;
@ -81,7 +81,7 @@ class MonetizationView : LinearLayout {
_textMerchandise = findViewById(R.id.text_merchandise);
_recyclerMerchandise = findViewById(R.id.recycler_merchandise);
_loaderMerchandise = findViewById(R.id.loader_merchandise);
_loaderViewMerchandise = findViewById(R.id.loader_merchandise);
_layoutMerchandise = findViewById(R.id.layout_merchandise);
_root = findViewById(R.id.root);
@ -108,7 +108,7 @@ class MonetizationView : LinearLayout {
}
private fun setMerchandise(items: List<StoreItem>?) {
_loaderMerchandise.stop();
_loaderViewMerchandise.stop();
if (items == null) {
_textMerchandise.visibility = View.GONE;
@ -147,7 +147,7 @@ class MonetizationView : LinearLayout {
val uri = Uri.parse(storeData);
if (uri.isAbsolute) {
_taskLoadMerchandise.run(storeData);
_loaderMerchandise.start();
_loaderViewMerchandise.start();
} else {
Logger.i(TAG, "Merchandise not loaded, not URL nor JSON")
}

View file

@ -3,6 +3,7 @@ package com.futo.platformplayer.views.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
@ -37,8 +38,10 @@ class CommentViewHolder : ViewHolder {
private val _layoutRating: LinearLayout;
private val _pillRatingLikesDislikes: PillRatingLikesDislikes;
private val _layoutComment: ConstraintLayout;
private val _buttonDelete: FrameLayout;
var onClick = Event1<IPlatformComment>();
var onRepliesClick = Event1<IPlatformComment>();
var onDelete = Event1<IPlatformComment>();
var comment: IPlatformComment? = null
private set;
@ -55,6 +58,7 @@ class CommentViewHolder : ViewHolder {
_buttonReplies = itemView.findViewById(R.id.button_replies);
_layoutRating = itemView.findViewById(R.id.layout_rating);
_pillRatingLikesDislikes = itemView.findViewById(R.id.rating);
_buttonDelete = itemView.findViewById(R.id.button_delete);
_pillRatingLikesDislikes.onLikeDislikeUpdated.subscribe { args ->
val c = comment
@ -87,7 +91,12 @@ class CommentViewHolder : ViewHolder {
_buttonReplies.onClick.subscribe {
val c = comment ?: return@subscribe;
onClick.emit(c);
onRepliesClick.emit(c);
}
_buttonDelete.setOnClickListener {
val c = comment ?: return@setOnClickListener;
onDelete.emit(c);
}
_textBody.setPlatformPlayerLinkMovementMethod(viewGroup.context);
@ -167,6 +176,13 @@ class CommentViewHolder : ViewHolder {
_buttonReplies.visibility = View.GONE;
}
val processHandle = StatePolycentric.instance.processHandle
if (processHandle != null && comment is PolycentricPlatformComment && processHandle.system == comment.eventPointer.system) {
_buttonDelete.visibility = View.VISIBLE
} else {
_buttonDelete.visibility = View.GONE
}
this.comment = comment;
}

View file

@ -0,0 +1,165 @@
package com.futo.platformplayer.views.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.futo.platformplayer.*
import com.futo.platformplayer.api.media.models.comments.IPlatformComment
import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment
import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StatePolycentric
import com.futo.platformplayer.views.others.CreatorThumbnail
import com.futo.platformplayer.views.pills.PillButton
import com.futo.platformplayer.views.pills.PillRatingLikesDislikes
import com.futo.polycentric.core.Opinion
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class CommentWithReferenceViewHolder : ViewHolder {
private val _creatorThumbnail: CreatorThumbnail;
private val _textAuthor: TextView;
private val _textMetadata: TextView;
private val _textBody: TextView;
private val _buttonReplies: PillButton;
private val _pillRatingLikesDislikes: PillRatingLikesDislikes;
private val _layoutComment: ConstraintLayout;
private val _buttonDelete: FrameLayout;
private val _taskGetLiveComment = TaskHandler<PolycentricPlatformComment, PolycentricPlatformComment>(StateApp.instance.scopeGetter, { StatePolycentric.instance.getLiveComment(it.contextUrl, it.reference) })
.success {
bind(it, true);
}
.exception<Throwable> {
Logger.w(TAG, "Failed to get live comment.", it);
//TODO: Show error
}
var onRepliesClick = Event1<IPlatformComment>();
var onDelete = Event1<IPlatformComment>();
var comment: IPlatformComment? = null
private set;
constructor(viewGroup: ViewGroup) : super(LayoutInflater.from(viewGroup.context).inflate(R.layout.list_comment_with_reference, viewGroup, false)) {
_layoutComment = itemView.findViewById(R.id.layout_comment);
_creatorThumbnail = itemView.findViewById(R.id.image_thumbnail);
_textAuthor = itemView.findViewById(R.id.text_author);
_textMetadata = itemView.findViewById(R.id.text_metadata);
_textBody = itemView.findViewById(R.id.text_body);
_buttonReplies = itemView.findViewById(R.id.button_replies);
_pillRatingLikesDislikes = itemView.findViewById(R.id.rating);
_buttonDelete = itemView.findViewById(R.id.button_delete)
_pillRatingLikesDislikes.onLikeDislikeUpdated.subscribe { args ->
val c = comment
if (c !is PolycentricPlatformComment) {
throw Exception("Not implemented for non polycentric comments")
}
if (args.hasLiked) {
args.processHandle.opinion(c.reference, Opinion.like);
} else if (args.hasDisliked) {
args.processHandle.opinion(c.reference, Opinion.dislike);
} else {
args.processHandle.opinion(c.reference, Opinion.neutral);
}
_layoutComment.alpha = if (args.dislikes > 2 && args.dislikes.toFloat() / (args.likes + args.dislikes).toFloat() >= 0.7f) 0.5f else 1.0f;
StateApp.instance.scopeOrNull?.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(c.reference, args.hasLiked, args.hasDisliked)
};
_buttonReplies.onClick.subscribe {
val c = comment ?: return@subscribe;
onRepliesClick.emit(c);
}
_buttonDelete.setOnClickListener {
val c = comment ?: return@setOnClickListener;
onDelete.emit(c);
}
_textBody.setPlatformPlayerLinkMovementMethod(viewGroup.context);
}
fun bind(comment: IPlatformComment, live: Boolean = false) {
_taskGetLiveComment.cancel()
_creatorThumbnail.setThumbnail(comment.author.thumbnail, false);
_creatorThumbnail.setHarborAvailable(comment is PolycentricPlatformComment,false);
_textAuthor.text = comment.author.name;
val date = comment.date;
if (date != null) {
_textMetadata.visibility = View.VISIBLE;
_textMetadata.text = "${date.toHumanNowDiffString()} ago";
} else {
_textMetadata.visibility = View.GONE;
}
val rating = comment.rating;
if (rating is RatingLikeDislikes) {
_layoutComment.alpha = if (rating.dislikes > 2 && rating.dislikes.toFloat() / (rating.likes + rating.dislikes).toFloat() >= 0.7f) 0.5f else 1.0f;
} else {
_layoutComment.alpha = 1.0f;
}
_textBody.text = comment.message.fixHtmlLinks();
if (comment is PolycentricPlatformComment) {
if (live) {
val hasLiked = StatePolycentric.instance.hasLiked(comment.reference);
val hasDisliked = StatePolycentric.instance.hasDisliked(comment.reference);
_pillRatingLikesDislikes.setRating(comment.rating, hasLiked, hasDisliked);
} else {
_pillRatingLikesDislikes.setLoading(true)
}
if (live) {
_buttonReplies.setLoading(false)
val replies = comment.replyCount ?: 0;
if (replies > 0) {
_buttonReplies.visibility = View.VISIBLE;
_buttonReplies.text.text = "$replies " + itemView.context.getString(R.string.replies);
} else {
_buttonReplies.visibility = View.GONE;
}
} else {
_buttonReplies.setLoading(true)
}
if (false) {
//Restore from cached
} else {
//_taskGetLiveComment.run(comment)
}
} else {
_pillRatingLikesDislikes.visibility = View.GONE
_buttonReplies.visibility = View.GONE
}
this.comment = comment;
}
companion object {
private const val TAG = "CommentWithReferenceViewHolder";
}
}

View file

@ -17,8 +17,8 @@ class SubscriptionAdapter : RecyclerView.Adapter<SubscriptionViewHolder> {
var onSettings = Event1<Subscription>();
var sortBy: Int = 3
set(value) {
field = value;
updateDataset();
field = value
updateDataset()
}
constructor(inflater: LayoutInflater, confirmationMessage: String) : super() {

View file

@ -19,7 +19,7 @@ import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.video.PlayerManager
import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.Loader
import com.futo.platformplayer.views.LoaderView
import com.futo.platformplayer.views.platform.PlatformIndicator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -28,7 +28,7 @@ class PreviewNestedVideoView : PreviewVideoView {
protected val _platformIndicatorNested: PlatformIndicator;
protected val _containerLoader: LinearLayout;
protected val _loader: Loader;
protected val _loaderView: LoaderView;
protected val _containerUnavailable: LinearLayout;
protected val _textNestedUrl: TextView;
@ -42,7 +42,7 @@ class PreviewNestedVideoView : PreviewVideoView {
constructor(context: Context, feedStyle: FeedStyle, exoPlayer: PlayerManager? = null): super(context, feedStyle, exoPlayer) {
_platformIndicatorNested = findViewById(R.id.thumbnail_platform_nested);
_containerLoader = findViewById(R.id.container_loader);
_loader = findViewById(R.id.loader);
_loaderView = findViewById(R.id.loader);
_containerUnavailable = findViewById(R.id.container_unavailable);
_textNestedUrl = findViewById(R.id.text_nested_url);
@ -116,7 +116,7 @@ class PreviewNestedVideoView : PreviewVideoView {
if(!_contentSupported) {
_containerUnavailable.visibility = View.VISIBLE;
_containerLoader.visibility = View.GONE;
_loader.stop();
_loaderView.stop();
}
else {
if(_feedStyle == FeedStyle.THUMBNAIL)
@ -132,14 +132,14 @@ class PreviewNestedVideoView : PreviewVideoView {
_contentSupported = false;
_containerUnavailable.visibility = View.VISIBLE;
_containerLoader.visibility = View.GONE;
_loader.stop();
_loaderView.stop();
}
}
private fun loadNested(content: IPlatformNestedContent, onCompleted: ((IPlatformContentDetails)->Unit)? = null) {
Logger.i(TAG, "Loading nested content [${content.contentUrl}]");
_containerLoader.visibility = View.VISIBLE;
_loader.start();
_loaderView.start();
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
val def = StatePlatform.instance.getContentDetails(content.contentUrl);
def.invokeOnCompletion {
@ -150,13 +150,13 @@ class PreviewNestedVideoView : PreviewVideoView {
if(_content == content) {
_containerUnavailable.visibility = View.VISIBLE;
_containerLoader.visibility = View.GONE;
_loader.stop();
_loaderView.stop();
}
//TODO: Handle exception
}
else if(_content == content) {
_containerLoader.visibility = View.GONE;
_loader.stop();
_loaderView.stop();
val nestedContent = def.getCompleted();
_contentNested = nestedContent;
if(nestedContent is IPlatformVideoDetails) {

View file

@ -4,15 +4,21 @@ import android.content.Context
import android.util.AttributeSet
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.api.media.models.comments.IPlatformComment
import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment
import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.fixHtmlLinks
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePolycentric
import com.futo.platformplayer.toHumanNowDiffString
import com.futo.platformplayer.views.behavior.NonScrollingTextView
import com.futo.platformplayer.views.comments.AddCommentView
import com.futo.platformplayer.views.others.CreatorThumbnail
import com.futo.platformplayer.views.segments.CommentsList
import userpackage.Protocol
@ -22,6 +28,11 @@ class RepliesOverlay : LinearLayout {
private val _topbar: OverlayTopbar;
private val _commentsList: CommentsList;
private val _addCommentView: AddCommentView;
private val _textBody: NonScrollingTextView;
private val _textAuthor: TextView;
private val _textMetadata: TextView;
private val _creatorThumbnail: CreatorThumbnail;
private val _layoutParentComment: ConstraintLayout;
private var _readonly = false;
private var _onCommentAdded: ((comment: IPlatformComment) -> Unit)? = null;
@ -30,6 +41,11 @@ class RepliesOverlay : LinearLayout {
_topbar = findViewById(R.id.topbar);
_commentsList = findViewById(R.id.comments_list);
_addCommentView = findViewById(R.id.add_comment_view);
_textBody = findViewById(R.id.text_body)
_textMetadata = findViewById(R.id.text_metadata)
_textAuthor = findViewById(R.id.text_author)
_creatorThumbnail = findViewById(R.id.image_thumbnail)
_layoutParentComment = findViewById(R.id.layout_parent_comment)
_addCommentView.onCommentAdded.subscribe {
_commentsList.addComment(it);
@ -42,7 +58,7 @@ class RepliesOverlay : LinearLayout {
}
}
_commentsList.onClick.subscribe { c ->
_commentsList.onRepliesClick.subscribe { c ->
val replyCount = c.replyCount;
var metadata = "";
if (replyCount != null && replyCount > 0) {
@ -50,9 +66,9 @@ class RepliesOverlay : LinearLayout {
}
if (c is PolycentricPlatformComment) {
load(false, metadata, c.contextUrl, c.reference, { StatePolycentric.instance.getCommentPager(c.contextUrl, c.reference) });
load(false, metadata, c.contextUrl, c.reference, c, { StatePolycentric.instance.getCommentPager(c.contextUrl, c.reference) });
} else {
load(true, metadata, null, null, { StatePlatform.instance.getSubComments(c) });
load(true, metadata, null, null, c, { StatePlatform.instance.getSubComments(c) });
}
};
@ -60,7 +76,7 @@ class RepliesOverlay : LinearLayout {
_topbar.setInfo(context.getString(R.string.Replies), "");
}
fun load(readonly: Boolean, metadata: String, contextUrl: String?, ref: Protocol.Reference?, loader: suspend () -> IPager<IPlatformComment>, onCommentAdded: ((comment: IPlatformComment) -> Unit)? = null) {
fun load(readonly: Boolean, metadata: String, contextUrl: String?, ref: Protocol.Reference?, parentComment: IPlatformComment? = null, loader: suspend () -> IPager<IPlatformComment>, onCommentAdded: ((comment: IPlatformComment) -> Unit)? = null) {
_readonly = readonly;
if (readonly) {
_addCommentView.visibility = View.GONE;
@ -69,6 +85,26 @@ class RepliesOverlay : LinearLayout {
_addCommentView.setContext(contextUrl, ref);
}
if (parentComment == null) {
_layoutParentComment.visibility = View.GONE
} else {
_layoutParentComment.visibility = View.VISIBLE
_textBody.text = parentComment.message.fixHtmlLinks()
_textAuthor.text = parentComment.author.name
val date = parentComment.date
if (date != null) {
_textMetadata.visibility = View.VISIBLE
_textMetadata.text = "${date.toHumanNowDiffString()} ago"
} else {
_textMetadata.visibility = View.GONE
}
_creatorThumbnail.setThumbnail(parentComment.author.thumbnail, false);
_creatorThumbnail.setHarborAvailable(parentComment is PolycentricPlatformComment,false);
}
_topbar.setInfo(context.getString(R.string.Replies), metadata);
_commentsList.load(readonly, loader);
_onCommentAdded = onCommentAdded;

View file

@ -9,16 +9,20 @@ import android.widget.LinearLayout
import android.widget.TextView
import com.futo.platformplayer.R
import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.views.LoaderView
class PillButton : LinearLayout {
val icon: ImageView;
val text: TextView;
val loaderView: LoaderView;
val onClick = Event0();
private var _isLoading = false;
constructor(context : Context, attrs : AttributeSet) : super(context, attrs) {
LayoutInflater.from(context).inflate(R.layout.pill_button, this, true);
icon = findViewById(R.id.pill_icon);
text = findViewById(R.id.pill_text);
loaderView = findViewById(R.id.loader)
val attrArr = context.obtainStyledAttributes(attrs, R.styleable.PillButton, 0, 0);
val attrIconRef = attrArr.getResourceId(R.styleable.PillButton_pillIcon, -1);
@ -31,7 +35,29 @@ class PillButton : LinearLayout {
text.text = attrText;
findViewById<LinearLayout>(R.id.root).setOnClickListener {
if (_isLoading) {
return@setOnClickListener
}
onClick.emit();
};
}
fun setLoading(loading: Boolean) {
if (loading == _isLoading) {
return
}
if (loading) {
text.visibility = View.GONE
loaderView.visibility = View.VISIBLE
loaderView.start()
} else {
loaderView.stop()
text.visibility = View.VISIBLE
loaderView.visibility = View.GONE
}
_isLoading = loading
}
}

View file

@ -16,6 +16,7 @@ import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.Event3
import com.futo.platformplayer.states.StatePolycentric
import com.futo.platformplayer.toHumanNumber
import com.futo.platformplayer.views.LoaderView
import com.futo.polycentric.core.ProcessHandle
data class OnLikeDislikeUpdatedArgs(
@ -29,9 +30,12 @@ data class OnLikeDislikeUpdatedArgs(
class PillRatingLikesDislikes : LinearLayout {
private val _textLikes: TextView;
private val _textDislikes: TextView;
private val _loaderViewLikes: LoaderView;
private val _loaderViewDislikes: LoaderView;
private val _seperator: View;
private val _iconLikes: ImageView;
private val _iconDislikes: ImageView;
private var _isLoading: Boolean = false;
private var _likes = 0L;
private var _hasLiked = false;
@ -47,14 +51,42 @@ class PillRatingLikesDislikes : LinearLayout {
_seperator = findViewById(R.id.pill_seperator);
_iconDislikes = findViewById(R.id.pill_dislike_icon);
_iconLikes = findViewById(R.id.pill_like_icon);
_loaderViewLikes = findViewById(R.id.loader_likes)
_loaderViewDislikes = findViewById(R.id.loader_dislikes)
_iconLikes.setOnClickListener { StatePolycentric.instance.requireLogin(context, context.getString(R.string.please_login_to_like)) { like(it) }; };
_textLikes.setOnClickListener { StatePolycentric.instance.requireLogin(context, context.getString(R.string.please_login_to_like)) { like(it) }; };
_iconDislikes.setOnClickListener { StatePolycentric.instance.requireLogin(context, context.getString(R.string.please_login_to_dislike)) { dislike(it) }; };
_textDislikes.setOnClickListener { StatePolycentric.instance.requireLogin(context, context.getString(R.string.please_login_to_dislike)) { dislike(it) }; };
_iconLikes.setOnClickListener { if (!_isLoading) StatePolycentric.instance.requireLogin(context, context.getString(R.string.please_login_to_like)) { like(it) }; };
_textLikes.setOnClickListener { if (!_isLoading) StatePolycentric.instance.requireLogin(context, context.getString(R.string.please_login_to_like)) { like(it) }; };
_iconDislikes.setOnClickListener { if (!_isLoading) StatePolycentric.instance.requireLogin(context, context.getString(R.string.please_login_to_dislike)) { dislike(it) }; };
_textDislikes.setOnClickListener { if (!_isLoading) StatePolycentric.instance.requireLogin(context, context.getString(R.string.please_login_to_dislike)) { dislike(it) }; };
}
fun setLoading(loading: Boolean) {
if (_isLoading == loading) {
return
}
if (loading) {
_textLikes.visibility = View.GONE
_loaderViewLikes.visibility = View.VISIBLE
_textDislikes.visibility = View.GONE
_loaderViewDislikes.visibility = View.VISIBLE
_loaderViewLikes.start()
_loaderViewDislikes.start()
} else {
_loaderViewLikes.stop()
_loaderViewDislikes.stop()
_textLikes.visibility = View.VISIBLE
_loaderViewLikes.visibility = View.GONE
_textDislikes.visibility = View.VISIBLE
_loaderViewDislikes.visibility = View.GONE
}
_isLoading = loading
}
fun setRating(rating: IRating, hasLiked: Boolean = false, hasDisliked: Boolean = false) {
setLoading(false)
when (rating) {
is RatingLikeDislikes -> {
setRating(rating, hasLiked, hasDisliked);

View file

@ -19,9 +19,12 @@ import com.futo.platformplayer.api.media.structures.IAsyncPager
import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.fragment.mainactivity.main.ChannelFragment
import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions
import com.futo.platformplayer.states.StatePolycentric
import com.futo.platformplayer.views.adapters.CommentViewHolder
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.net.UnknownHostException
class CommentsList : ConstraintLayout {
@ -69,7 +72,7 @@ class CommentsList : ConstraintLayout {
private val _prependedView: FrameLayout;
private var _readonly: Boolean = false;
var onClick = Event1<IPlatformComment>();
var onRepliesClick = Event1<IPlatformComment>();
var onCommentsLoaded = Event1<Int>();
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
@ -85,7 +88,8 @@ class CommentsList : ConstraintLayout {
childViewHolderBinder = { viewHolder, position -> viewHolder.bind(_comments[position], _readonly); },
childViewHolderFactory = { viewGroup, _ ->
val holder = CommentViewHolder(viewGroup);
holder.onClick.subscribe { c -> onClick.emit(c) };
holder.onRepliesClick.subscribe { c -> onRepliesClick.emit(c) };
holder.onDelete.subscribe(::onDelete);
return@InsertedViewAdapterWithLoader holder;
}
);
@ -106,6 +110,36 @@ class CommentsList : ConstraintLayout {
_prependedView.addView(view);
}
private fun onDelete(comment: IPlatformComment) {
val processHandle = StatePolycentric.instance.processHandle ?: return
if (comment !is PolycentricPlatformComment) {
return
}
val index = _comments.indexOf(comment)
if (index != -1) {
_comments.removeAt(index)
_adapterComments.notifyItemRemoved(_adapterComments.childToParentPosition(index))
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
try {
processHandle.delete(comment.eventPointer.process, comment.eventPointer.logicalClock)
} catch (e: Throwable) {
Logger.e(TAG, "Failed to delete event.", e);
return@launch;
}
try {
Logger.i(TAG, "Started backfill");
processHandle.fullyBackfillServersAnnounceExceptions();
Logger.i(TAG, "Finished backfill");
} catch (e: Throwable) {
Logger.e(TAG, "Failed to fully backfill servers.", e);
}
}
}
}
private fun onScrolled() {
val visibleItemCount = _recyclerComments.childCount;
val firstVisibleItem = _llmReplies.findFirstVisibleItemPosition();

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#1A1A1A" />
<corners android:radius="4dp" />
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#A03D3D" />
<corners android:radius="500dp" />
<size android:height="20dp" />
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M289.23,649.23q-13.08,0 -21.92,-8.85 -8.85,-8.85 -8.85,-21.92v-40h470l25.38,25.38L753.85,240h40q13.08,0 21.92,8.85 8.85,8.85 8.85,21.92v501.54L701.54,649.23L289.23,649.23ZM135.38,621.54v-470.77q0,-13.08 8.85,-21.92Q153.08,120 166.15,120h476.92q13.08,0 21.92,8.85 8.85,8.85 8.85,21.92v316.92q0,13.08 -8.85,21.92 -8.85,8.85 -21.92,8.85L258.46,498.46L135.38,621.54Z"/>
</vector>

View file

@ -52,7 +52,7 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.futo.platformplayer.views.Loader
<com.futo.platformplayer.views.LoaderView
android:id="@+id/loader"
android:layout_marginBottom="15dp"
android:layout_marginTop="15dp"

View file

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/layout_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="12dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24dp"
android:text="@string/comments"
android:fontFamily="@font/inter_extra_light"
android:textColor="@color/white" />
<TextView
android:id="@+id/text_comment_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12dp"
android:text="@string/these_are_all_commentcount_comments_you_have_made_in_grayjay"
android:fontFamily="@font/inter_regular"
android:textColor="#808080" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14dp"
android:textColor="@color/gray_ac"
android:fontFamily="@font/inter_light"
android:text="@string/sort_by" />
<Spinner
android:id="@+id/spinner_sortby"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:paddingStart="20dp"
android:paddingEnd="12dp" />
</LinearLayout>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_comments"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout android:id="@+id/layout_not_logged_in"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:background="@color/black">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Login to view your comments"
android:textSize="14dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_regular"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingStart="28dp"
android:paddingEnd="28dp"
android:layout_marginBottom="20dp"/>
<com.futo.platformplayer.views.overlays.RepliesOverlay
android:id="@+id/replies_overlay"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
android:id="@+id/button_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/background_button_primary"
android:clickable="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Login"
android:textSize="14dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_regular"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingStart="28dp"
android:paddingEnd="28dp"/>
</LinearLayout>
</LinearLayout>
</FrameLayout>

View file

@ -70,7 +70,7 @@
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/image_thumbnail"
app:layout_constraintTop_toBottomOf="@id/text_body"
@ -136,6 +136,30 @@
app:pillIcon="@drawable/ic_forum"
app:pillText="55 Replies"
android:layout_marginStart="15dp" />
<Space android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<FrameLayout
android:id="@+id/button_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/background_pill_pred"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<TextView
android:id="@+id/pill_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="@color/white"
android:textSize="13dp"
android:gravity="center_vertical"
android:fontFamily="@font/inter_light"
android:text="@string/delete" />
</FrameLayout>
</LinearLayout>

View file

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout_comment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:layout_marginStart="14dp"
android:layout_marginEnd="14dp"
android:orientation="vertical"
android:background="@drawable/background_comment"
android:padding="16dp">
<com.futo.platformplayer.views.others.CreatorThumbnail
android:id="@+id/image_thumbnail"
android:layout_width="25dp"
android:layout_height="25dp"
android:contentDescription="@string/channel_image"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/placeholder_channel_thumbnail" />
<TextView
android:id="@+id/text_author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:fontFamily="@font/inter_regular"
android:textColor="@color/white"
android:textSize="14sp"
app:layout_constraintLeft_toRightOf="@id/image_thumbnail"
app:layout_constraintTop_toTopOf="@id/image_thumbnail"
tools:text="ShortCircuit" />
<TextView
android:id="@+id/text_metadata"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:fontFamily="@font/inter_regular"
android:textColor="@color/gray_ac"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@id/text_author"
app:layout_constraintLeft_toRightOf="@id/text_author"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/text_author"
tools:text=" • 3 years ago" />
<com.futo.platformplayer.views.behavior.NonScrollingTextView
android:id="@+id/text_body"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginTop="5dp"
android:layout_marginStart="10dp"
android:background="@color/transparent"
android:fontFamily="@font/inter_regular"
android:isScrollContainer="false"
android:textColor="#CCCCCC"
android:textSize="13sp"
android:maxLines="100"
app:layout_constraintTop_toBottomOf="@id/text_metadata"
app:layout_constraintLeft_toRightOf="@id/image_thumbnail"
app:layout_constraintRight_toRightOf="parent"
tools:text="@string/lorem_ipsum" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/image_thumbnail"
app:layout_constraintTop_toBottomOf="@id/text_body"
android:layout_marginTop="8dp"
android:gravity="center_vertical">
<com.futo.platformplayer.views.pills.PillRatingLikesDislikes
android:id="@+id/rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="10dp" />
<com.futo.platformplayer.views.pills.PillButton
android:id="@+id/button_replies"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:pillIcon="@drawable/ic_forum"
app:pillText="55 Replies"
android:layout_marginStart="15dp" />
<Space android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<FrameLayout
android:id="@+id/button_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/background_pill_pred"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<TextView
android:id="@+id/pill_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="@color/white"
android:textSize="13dp"
android:gravity="center_vertical"
android:fontFamily="@font/inter_light"
android:text="@string/delete" />
</FrameLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -127,7 +127,7 @@
android:visibility="gone"
android:gravity="center"
android:orientation="vertical">
<com.futo.platformplayer.views.Loader
<com.futo.platformplayer.views.LoaderView
android:id="@+id/loader"
android:layout_width="50dp"
android:layout_height="50dp" />

View file

@ -2,6 +2,7 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:background="@color/black"
xmlns:app="http://schemas.android.com/apk/res-auto">
@ -15,6 +16,78 @@
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_parent_comment"
android:layout_height="wrap_content"
android:layout_width="match_parent"
app:layout_constraintTop_toBottomOf="@id/topbar"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:layout_marginTop="6dp"
android:padding="12dp"
android:background="@drawable/background_16_round_4dp">
<com.futo.platformplayer.views.others.CreatorThumbnail
android:id="@+id/image_thumbnail"
android:layout_width="25dp"
android:layout_height="25dp"
android:contentDescription="@string/channel_image"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/placeholder_channel_thumbnail" />
<TextView
android:id="@+id/text_author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:fontFamily="@font/inter_regular"
android:textColor="@color/white"
android:textSize="14sp"
app:layout_constraintLeft_toRightOf="@id/image_thumbnail"
app:layout_constraintTop_toTopOf="@id/image_thumbnail"
android:text="ShortCircuit" />
<TextView
android:id="@+id/text_metadata"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:fontFamily="@font/inter_regular"
android:textColor="@color/gray_ac"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@id/text_author"
app:layout_constraintLeft_toRightOf="@id/text_author"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/text_author"
android:text=" • 3 years ago" />
<com.futo.platformplayer.views.behavior.NonScrollingTextView
android:id="@+id/text_body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginStart="10dp"
android:background="@color/transparent"
android:fontFamily="@font/inter_regular"
android:isScrollContainer="false"
android:textColor="#CCCCCC"
android:textSize="13sp"
android:maxLines="3"
android:ellipsize="end"
app:layout_constraintTop_toBottomOf="@id/text_metadata"
app:layout_constraintLeft_toRightOf="@id/image_thumbnail"
app:layout_constraintRight_toRightOf="parent"
android:text="@string/lorem_ipsum" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.futo.platformplayer.views.comments.AddCommentView
android:id="@+id/add_comment_view"
android:layout_width="match_parent"
@ -22,8 +95,7 @@
android:layout_marginTop="12dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:paddingBottom="12dp"
app:layout_constraintTop_toBottomOf="@id/topbar"
app:layout_constraintTop_toBottomOf="@id/layout_parent_comment"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
@ -32,6 +104,7 @@
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/add_comment_view"
app:layout_constraintBottom_toBottomOf="parent" />
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="12dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -9,12 +9,13 @@
android:paddingStart="7dp"
android:paddingEnd="12dp"
android:background="@drawable/background_pill"
android:id="@+id/root">
android:id="@+id/root"
android:gravity="center_vertical">
<ImageView
android:id="@+id/pill_icon"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginRight="5dp"
android:layout_marginLeft="5dp"
android:layout_marginTop="0dp"
@ -31,4 +32,10 @@
android:fontFamily="@font/inter_light"
tools:text="500K" />
<com.futo.platformplayer.views.LoaderView
android:id="@+id/loader"
android:layout_width="14dp"
android:layout_height="14dp"
app:isWhite="true" />
</LinearLayout>

View file

@ -8,7 +8,8 @@
android:paddingBottom="7dp"
android:paddingLeft="7dp"
android:paddingRight="12dp"
android:background="@drawable/background_pill">
android:background="@drawable/background_pill"
android:gravity="center_vertical">
<ImageView
android:id="@+id/pill_like_icon"
android:layout_width="30dp"
@ -22,6 +23,11 @@
android:textSize="13dp"
android:gravity="center_vertical"
tools:text="500K" />
<com.futo.platformplayer.views.LoaderView
android:id="@+id/loader_likes"
android:layout_width="14dp"
android:layout_height="14dp"
app:isWhite="true" />
<View
android:id="@+id/pill_seperator"
@ -44,5 +50,10 @@
android:gravity="center_vertical"
android:textSize="13dp"
tools:text="500K" />
<com.futo.platformplayer.views.LoaderView
android:id="@+id/loader_dislikes"
android:layout_width="14dp"
android:layout_height="14dp"
app:isWhite="true" />
</LinearLayout>

View file

@ -120,7 +120,7 @@
android:orientation="horizontal"
android:layout_gravity="center" />
<com.futo.platformplayer.views.Loader
<com.futo.platformplayer.views.LoaderView
android:id="@+id/loader_merchandise"
android:layout_width="64dp"
android:layout_height="64dp"

View file

@ -722,4 +722,8 @@
<item>معلومات</item>
<item>تفصيلي</item>
</string-array>
<string-array name="comments_sortby_array">
<item>Newest</item>
<item>Oldest</item>
</string-array>
</resources>

View file

@ -722,4 +722,8 @@
<item>Information</item>
<item>Ausführlich</item>
</string-array>
<string-array name="comments_sortby_array">
<item>Newest</item>
<item>Oldest</item>
</string-array>
</resources>

View file

@ -738,4 +738,8 @@
<item>Información</item>
<item>Detallado</item>
</string-array>
<string-array name="comments_sortby_array">
<item>Newest</item>
<item>Oldest</item>
</string-array>
</resources>

View file

@ -722,4 +722,8 @@
<item>Information</item>
<item>Verbeux</item>
</string-array>
<string-array name="comments_sortby_array">
<item>Newest</item>
<item>Oldest</item>
</string-array>
</resources>

View file

@ -722,4 +722,8 @@
<item>情報</item>
<item>詳細</item>
</string-array>
<string-array name="comments_sortby_array">
<item>Newest</item>
<item>Oldest</item>
</string-array>
</resources>

View file

@ -722,4 +722,8 @@
<item>정보</item>
<item>상세</item>
</string-array>
<string-array name="comments_sortby_array">
<item>Newest</item>
<item>Oldest</item>
</string-array>
</resources>

View file

@ -722,4 +722,8 @@
<item>Informação</item>
<item>Detalhado</item>
</string-array>
<string-array name="comments_sortby_array">
<item>Newest</item>
<item>Oldest</item>
</string-array>
</resources>

View file

@ -722,4 +722,8 @@
<item>Информация</item>
<item>Подробно</item>
</string-array>
<string-array name="comments_sortby_array">
<item>Newest</item>
<item>Oldest</item>
</string-array>
</resources>

View file

@ -722,4 +722,8 @@
<item>信息</item>
<item>详细</item>
</string-array>
<string-array name="comments_sortby_array">
<item>Newest</item>
<item>Oldest</item>
</string-array>
</resources>

View file

@ -2,5 +2,6 @@
<resources>
<declare-styleable name="LoaderView">
<attr name="automatic" format="boolean" />
<attr name="isWhite" format="boolean" />
</declare-styleable>
</resources>

View file

@ -687,6 +687,7 @@
<string name="allow_grayjay_to_handle_specific_urls_please_set_it_as_default_in_the_app_settings">When you click \'Yes\', the Grayjay app settings will open.\n\nThere, navigate to:\n1. "Open by default" or "Set as default" section.\nYou might find this option directly or under \'Advanced\' settings, depending on your device.\n\n2. Choose \'Open supported links\' for Grayjay.\n\n(some devices have this listed under \'Default Apps\' in the main settings followed by selecting Grayjay for relevant categories)</string>
<string name="failed_to_show_settings">Failed to show settings</string>
<string name="play_store_version_does_not_support_default_url_handling">Play store version does not support default URL handling.</string>
<string name="these_are_all_commentcount_comments_you_have_made_in_grayjay">These are all {commentCount} comments you have made in Grayjay.</string>
<string-array name="home_screen_array">
<item>Recommendations</item>
<item>Subscriptions</item>
@ -774,6 +775,10 @@
<item>Disabled</item>
<item>Enabled</item>
</string-array>
<string-array name="comments_sortby_array">
<item>Newest</item>
<item>Oldest</item>
</string-array>
<string-array name="subscriptions_sortby_array">
<item>Name Ascending</item>
<item>Name Descending</item>

@ -1 +1 @@
Subproject commit 839e4c4a4f5ed6cb6f68047f88b26c5831e6e703
Subproject commit faaa7a6d8efb3f92fc239e7d77ec2f9a46c3a958