mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-08-03 22:59:56 +00:00
Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay
This commit is contained in:
commit
a4ff47d863
19 changed files with 442 additions and 51 deletions
|
@ -1,6 +1,5 @@
|
||||||
package com.futo.platformplayer
|
package com.futo.platformplayer
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -68,7 +67,7 @@ class UISlideOverlays {
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showSubscriptionOptionsOverlay(subscription: Subscription, container: ViewGroup) {
|
fun showSubscriptionOptionsOverlay(subscription: Subscription, container: ViewGroup): SlideUpMenuOverlay {
|
||||||
val items = arrayListOf<View>();
|
val items = arrayListOf<View>();
|
||||||
|
|
||||||
val originalNotif = subscription.doNotifications;
|
val originalNotif = subscription.doNotifications;
|
||||||
|
@ -77,15 +76,13 @@ class UISlideOverlays {
|
||||||
val originalVideo = subscription.doFetchVideos;
|
val originalVideo = subscription.doFetchVideos;
|
||||||
val originalPosts = subscription.doFetchPosts;
|
val originalPosts = subscription.doFetchPosts;
|
||||||
|
|
||||||
|
val menu = SlideUpMenuOverlay(container.context, container, "Subscription Settings", null, true, listOf());
|
||||||
|
|
||||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO){
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO){
|
||||||
val plugin = StatePlatform.instance.getChannelClient(subscription.channel.url);
|
val plugin = StatePlatform.instance.getChannelClient(subscription.channel.url);
|
||||||
val capabilities = plugin.getChannelCapabilities();
|
val capabilities = plugin.getChannelCapabilities();
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
|
|
||||||
var menu: SlideUpMenuOverlay? = null;
|
|
||||||
|
|
||||||
|
|
||||||
items.addAll(listOf(
|
items.addAll(listOf(
|
||||||
SlideUpMenuItem(container.context, R.drawable.ic_notifications, "Notifications", "", "notifications", {
|
SlideUpMenuItem(container.context, R.drawable.ic_notifications, "Notifications", "", "notifications", {
|
||||||
subscription.doNotifications = menu?.selectOption(null, "notifications", true, true) ?: subscription.doNotifications;
|
subscription.doNotifications = menu?.selectOption(null, "notifications", true, true) ?: subscription.doNotifications;
|
||||||
|
@ -119,7 +116,7 @@ class UISlideOverlays {
|
||||||
}, false)*/
|
}, false)*/
|
||||||
).filterNotNull());
|
).filterNotNull());
|
||||||
|
|
||||||
menu = SlideUpMenuOverlay(container.context, container, "Subscription Settings", null, true, items);
|
menu.setItems(items);
|
||||||
|
|
||||||
if(subscription.doNotifications)
|
if(subscription.doNotifications)
|
||||||
menu.selectOption(null, "notifications", true, true);
|
menu.selectOption(null, "notifications", true, true);
|
||||||
|
@ -174,6 +171,8 @@ class UISlideOverlays {
|
||||||
menu.show();
|
menu.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showAddToGroupOverlay(channel: IPlatformVideo, container: ViewGroup) {
|
fun showAddToGroupOverlay(channel: IPlatformVideo, container: ViewGroup) {
|
||||||
|
@ -718,6 +717,13 @@ class UISlideOverlays {
|
||||||
);
|
);
|
||||||
|
|
||||||
val playlistItems = arrayListOf<SlideUpMenuItem>();
|
val playlistItems = arrayListOf<SlideUpMenuItem>();
|
||||||
|
playlistItems.add(SlideUpMenuItem(container.context, R.drawable.ic_playlist_add, container.context.getString(R.string.new_playlist), container.context.getString(R.string.add_to_new_playlist), "add_to_new_playlist", {
|
||||||
|
showCreatePlaylistOverlay(container) {
|
||||||
|
val playlist = Playlist(it, arrayListOf(SerializedPlatformVideo.fromVideo(video)));
|
||||||
|
StatePlaylists.instance.createOrUpdatePlaylist(playlist);
|
||||||
|
};
|
||||||
|
}, false))
|
||||||
|
|
||||||
for (playlist in allPlaylists) {
|
for (playlist in allPlaylists) {
|
||||||
playlistItems.add(SlideUpMenuItem(container.context, R.drawable.ic_playlist_add, playlist.name, "${playlist.videos.size} " + container.context.getString(R.string.videos), "",
|
playlistItems.add(SlideUpMenuItem(container.context, R.drawable.ic_playlist_add, playlist.name, "${playlist.videos.size} " + container.context.getString(R.string.videos), "",
|
||||||
{
|
{
|
||||||
|
|
|
@ -810,11 +810,9 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||||
if(_fragBotBarMenu.onBackPressed())
|
if(_fragBotBarMenu.onBackPressed())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(_fragVideoDetail.state == VideoDetailFragment.State.MAXIMIZED &&
|
if(_fragVideoDetail.state == VideoDetailFragment.State.MAXIMIZED && _fragVideoDetail.onBackPressed())
|
||||||
_fragVideoDetail.onBackPressed())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
||||||
if(!fragCurrent.onBackPressed())
|
if(!fragCurrent.onBackPressed())
|
||||||
closeSegment();
|
closeSegment();
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,9 @@ class PolycentricPlatformComment : IPlatformComment {
|
||||||
|
|
||||||
val eventPointer: Pointer;
|
val eventPointer: Pointer;
|
||||||
val reference: Reference;
|
val reference: Reference;
|
||||||
|
val parentReference: Reference?;
|
||||||
|
|
||||||
constructor(contextUrl: String, author: PlatformAuthorLink, msg: String, rating: IRating, date: OffsetDateTime, eventPointer: Pointer, replyCount: Int? = null) {
|
constructor(contextUrl: String, author: PlatformAuthorLink, msg: String, rating: IRating, date: OffsetDateTime, eventPointer: Pointer, parentReference: Reference?, replyCount: Int? = null) {
|
||||||
this.contextUrl = contextUrl;
|
this.contextUrl = contextUrl;
|
||||||
this.author = author;
|
this.author = author;
|
||||||
this.message = msg;
|
this.message = msg;
|
||||||
|
@ -29,6 +30,7 @@ class PolycentricPlatformComment : IPlatformComment {
|
||||||
this.replyCount = replyCount;
|
this.replyCount = replyCount;
|
||||||
this.eventPointer = eventPointer;
|
this.eventPointer = eventPointer;
|
||||||
this.reference = eventPointer.toReference();
|
this.reference = eventPointer.toReference();
|
||||||
|
this.parentReference = parentReference;
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getReplies(client: IPlatformClient): IPager<IPlatformComment> {
|
override fun getReplies(client: IPlatformClient): IPager<IPlatformComment> {
|
||||||
|
@ -36,10 +38,11 @@ class PolycentricPlatformComment : IPlatformComment {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cloneWithUpdatedReplyCount(replyCount: Int?): PolycentricPlatformComment {
|
fun cloneWithUpdatedReplyCount(replyCount: Int?): PolycentricPlatformComment {
|
||||||
return PolycentricPlatformComment(contextUrl, author, message, rating, date, eventPointer, replyCount);
|
return PolycentricPlatformComment(contextUrl, author, message, rating, date, eventPointer, parentReference, replyCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val TAG = "PolycentricPlatformComment"
|
||||||
val MAX_COMMENT_SIZE = 2000
|
val MAX_COMMENT_SIZE = 2000
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -123,7 +123,8 @@ class CommentDialog(context: Context?, val contextUrl: String, val ref: Protocol
|
||||||
msg = comment,
|
msg = comment,
|
||||||
rating = RatingLikeDislikes(0, 0),
|
rating = RatingLikeDislikes(0, 0),
|
||||||
date = OffsetDateTime.now(),
|
date = OffsetDateTime.now(),
|
||||||
eventPointer = eventPointer
|
eventPointer = eventPointer,
|
||||||
|
parentReference = ref
|
||||||
));
|
));
|
||||||
|
|
||||||
dismiss();
|
dismiss();
|
||||||
|
|
|
@ -117,6 +117,7 @@ class CommentsFragment : MainFragment() {
|
||||||
val holder = CommentWithReferenceViewHolder(viewGroup, _cache);
|
val holder = CommentWithReferenceViewHolder(viewGroup, _cache);
|
||||||
holder.onDelete.subscribe(::onDelete);
|
holder.onDelete.subscribe(::onDelete);
|
||||||
holder.onRepliesClick.subscribe(::onRepliesClick);
|
holder.onRepliesClick.subscribe(::onRepliesClick);
|
||||||
|
holder.onClick.subscribe(::onClick);
|
||||||
return@InsertedViewAdapterWithLoader holder;
|
return@InsertedViewAdapterWithLoader holder;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -200,6 +201,17 @@ class CommentsFragment : MainFragment() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onClick(c: IPlatformComment) {
|
||||||
|
if (c !is PolycentricPlatformComment) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val parentRef = c.parentReference
|
||||||
|
if (parentRef != null && _repliesOverlay.handleParentClick(c.contextUrl, parentRef)) {
|
||||||
|
setRepliesOverlayVisible(true, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun onRepliesClick(c: IPlatformComment) {
|
private fun onRepliesClick(c: IPlatformComment) {
|
||||||
val replyCount = c.replyCount ?: 0;
|
val replyCount = c.replyCount ?: 0;
|
||||||
var metadata = "";
|
var metadata = "";
|
||||||
|
|
|
@ -169,14 +169,14 @@ class VideoDetailFragment : MainFragment {
|
||||||
_view!!.transitionToStart();
|
_view!!.transitionToStart();
|
||||||
}
|
}
|
||||||
fun maximizeVideoDetail(instant: Boolean = false) {
|
fun maximizeVideoDetail(instant: Boolean = false) {
|
||||||
if(_maximizeProgress > 0.9f && state != State.MAXIMIZED) {
|
if((_maximizeProgress > 0.9f || instant) && state != State.MAXIMIZED) {
|
||||||
state = State.MAXIMIZED;
|
state = State.MAXIMIZED;
|
||||||
onMaximized.emit();
|
onMaximized.emit();
|
||||||
}
|
}
|
||||||
_view?.let {
|
_view?.let {
|
||||||
if(!instant)
|
if(!instant) {
|
||||||
it.transitionToEnd();
|
it.transitionToEnd();
|
||||||
else {
|
} else {
|
||||||
it.progress = 1f;
|
it.progress = 1f;
|
||||||
onTransitioning.emit(true);
|
onTransitioning.emit(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -373,7 +373,7 @@ class VideoDetailView : ConstraintLayout {
|
||||||
|
|
||||||
|
|
||||||
_buttonSubscribe.onSubscribed.subscribe {
|
_buttonSubscribe.onSubscribed.subscribe {
|
||||||
UISlideOverlays.showSubscriptionOptionsOverlay(it, _overlayContainer);
|
_slideUpOverlay = UISlideOverlays.showSubscriptionOptionsOverlay(it, _overlayContainer);
|
||||||
};
|
};
|
||||||
|
|
||||||
_container_content_liveChat.onRaidNow.subscribe {
|
_container_content_liveChat.onRaidNow.subscribe {
|
||||||
|
@ -2359,7 +2359,7 @@ class VideoDetailView : ConstraintLayout {
|
||||||
}
|
}
|
||||||
else if(isOverlayed) {
|
else if(isOverlayed) {
|
||||||
_playerProgress.layoutParams = _playerProgress.layoutParams.apply {
|
_playerProgress.layoutParams = _playerProgress.layoutParams.apply {
|
||||||
(this as MarginLayoutParams).bottomMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, -6f, resources.displayMetrics).toInt();
|
(this as MarginLayoutParams).bottomMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, -2f, resources.displayMetrics).toInt();
|
||||||
};
|
};
|
||||||
_playerProgress.elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics);
|
_playerProgress.elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics);
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import userpackage.Protocol
|
import userpackage.Protocol
|
||||||
|
import userpackage.Protocol.Reference
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
|
@ -287,7 +288,8 @@ class StatePolycentric {
|
||||||
rating = RatingLikeDislikes(0, 0),
|
rating = RatingLikeDislikes(0, 0),
|
||||||
date = if (ev.unixMilliseconds != null) Instant.ofEpochMilli(ev.unixMilliseconds!!).atOffset(ZoneOffset.UTC) else OffsetDateTime.MIN,
|
date = if (ev.unixMilliseconds != null) Instant.ofEpochMilli(ev.unixMilliseconds!!).atOffset(ZoneOffset.UTC) else OffsetDateTime.MIN,
|
||||||
replyCount = 0,
|
replyCount = 0,
|
||||||
eventPointer = se.toPointer()
|
eventPointer = se.toPointer(),
|
||||||
|
parentReference = se.event.references.getOrNull(0)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,6 +330,77 @@ class StatePolycentric {
|
||||||
return LikesDislikesReplies(likes, dislikes, replyCount)
|
return LikesDislikesReplies(likes, dislikes, replyCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getComment(contextUrl: String, reference: Reference): PolycentricPlatformComment {
|
||||||
|
ensureEnabled()
|
||||||
|
|
||||||
|
if (reference.referenceType != 2L) {
|
||||||
|
throw Exception("Not a pointer")
|
||||||
|
}
|
||||||
|
|
||||||
|
val pointer = Protocol.Pointer.parseFrom(reference.reference)
|
||||||
|
val events = ApiMethods.getEvents(PolycentricCache.SERVER, pointer.system, Protocol.RangesForSystem.newBuilder()
|
||||||
|
.addRangesForProcesses(Protocol.RangesForProcess.newBuilder()
|
||||||
|
.setProcess(pointer.process)
|
||||||
|
.addRanges(Protocol.Range.newBuilder()
|
||||||
|
.setLow(pointer.logicalClock)
|
||||||
|
.setHigh(pointer.logicalClock)
|
||||||
|
.build())
|
||||||
|
.build())
|
||||||
|
.build())
|
||||||
|
|
||||||
|
val sev = SignedEvent.fromProto(events.getEvents(0))
|
||||||
|
val ev = sev.event
|
||||||
|
|
||||||
|
if (ev.contentType != ContentType.POST.value) {
|
||||||
|
throw Exception("This is not a comment")
|
||||||
|
}
|
||||||
|
|
||||||
|
val post = Protocol.Post.parseFrom(ev.content);
|
||||||
|
val systemLinkUrl = ev.system.systemToURLInfoSystemLinkUrl(listOf(PolycentricCache.SERVER));
|
||||||
|
val dp_25 = 25.dp(StateApp.instance.context.resources)
|
||||||
|
|
||||||
|
val profileEvents = ApiMethods.getQueryLatest(
|
||||||
|
PolycentricCache.SERVER,
|
||||||
|
ev.system.toProto(),
|
||||||
|
listOf(
|
||||||
|
ContentType.AVATAR.value,
|
||||||
|
ContentType.USERNAME.value
|
||||||
|
)
|
||||||
|
).eventsList.map { e -> SignedEvent.fromProto(e) }.groupBy { e -> e.event.contentType }
|
||||||
|
.map { (_, events) -> events.maxBy { x -> x.event.unixMilliseconds ?: 0 } };
|
||||||
|
|
||||||
|
val nameEvent = profileEvents.firstOrNull { e -> e.event.contentType == ContentType.USERNAME.value };
|
||||||
|
val avatarEvent = profileEvents.firstOrNull { e -> e.event.contentType == ContentType.AVATAR.value };
|
||||||
|
val imageBundle = if (avatarEvent != null) {
|
||||||
|
val lwwElementValue = avatarEvent.event.lwwElement?.value;
|
||||||
|
if (lwwElementValue != null) {
|
||||||
|
Protocol.ImageBundle.parseFrom(lwwElementValue)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val ldr = getLikesDislikesReplies(reference)
|
||||||
|
return PolycentricPlatformComment(
|
||||||
|
contextUrl = contextUrl,
|
||||||
|
author = PlatformAuthorLink(
|
||||||
|
id = PlatformID("polycentric", systemLinkUrl, null, ClaimType.POLYCENTRIC.value.toInt()),
|
||||||
|
name = nameEvent?.event?.lwwElement?.value?.decodeToString() ?: "Unknown",
|
||||||
|
url = systemLinkUrl,
|
||||||
|
thumbnail = imageBundle?.selectBestImage(dp_25 * dp_25)?.let { img -> img.toURLInfoSystemLinkUrl(ev.system.toProto(), img.process, listOf(PolycentricCache.SERVER)) },
|
||||||
|
subscribers = null
|
||||||
|
),
|
||||||
|
msg = if (post.content.count() > PolycentricPlatformComment.MAX_COMMENT_SIZE) post.content.substring(0, PolycentricPlatformComment.MAX_COMMENT_SIZE) else post.content,
|
||||||
|
rating = RatingLikeDislikes(ldr.likes, ldr.dislikes),
|
||||||
|
date = if (ev.unixMilliseconds != null) Instant.ofEpochMilli(ev.unixMilliseconds!!).atOffset(ZoneOffset.UTC) else OffsetDateTime.MIN,
|
||||||
|
replyCount = ldr.replyCount.toInt(),
|
||||||
|
eventPointer = sev.toPointer(),
|
||||||
|
parentReference = sev.event.references.getOrNull(0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getCommentPager(contextUrl: String, reference: Protocol.Reference, extraByteReferences: List<ByteArray>? = null): IPager<IPlatformComment> {
|
suspend fun getCommentPager(contextUrl: String, reference: Protocol.Reference, extraByteReferences: List<ByteArray>? = null): IPager<IPlatformComment> {
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
return EmptyPager()
|
return EmptyPager()
|
||||||
|
@ -453,7 +526,8 @@ class StatePolycentric {
|
||||||
rating = RatingLikeDislikes(likes, dislikes),
|
rating = RatingLikeDislikes(likes, dislikes),
|
||||||
date = if (unixMilliseconds != null) Instant.ofEpochMilli(unixMilliseconds).atOffset(ZoneOffset.UTC) else OffsetDateTime.MIN,
|
date = if (unixMilliseconds != null) Instant.ofEpochMilli(unixMilliseconds).atOffset(ZoneOffset.UTC) else OffsetDateTime.MIN,
|
||||||
replyCount = replies.toInt(),
|
replyCount = replies.toInt(),
|
||||||
eventPointer = sev.toPointer()
|
eventPointer = sev.toPointer(),
|
||||||
|
parentReference = sev.event.references.getOrNull(0)
|
||||||
);
|
);
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
return@mapNotNull null;
|
return@mapNotNull null;
|
||||||
|
|
|
@ -55,6 +55,7 @@ class CommentWithReferenceViewHolder : ViewHolder {
|
||||||
|
|
||||||
var onRepliesClick = Event1<IPlatformComment>();
|
var onRepliesClick = Event1<IPlatformComment>();
|
||||||
var onDelete = Event1<IPlatformComment>();
|
var onDelete = Event1<IPlatformComment>();
|
||||||
|
var onClick = Event1<IPlatformComment>();
|
||||||
var comment: IPlatformComment? = null
|
var comment: IPlatformComment? = null
|
||||||
private set;
|
private set;
|
||||||
|
|
||||||
|
@ -108,6 +109,11 @@ class CommentWithReferenceViewHolder : ViewHolder {
|
||||||
onDelete.emit(c);
|
onDelete.emit(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_layoutComment.setOnClickListener {
|
||||||
|
val c = comment ?: return@setOnClickListener;
|
||||||
|
onClick.emit(c);
|
||||||
|
}
|
||||||
|
|
||||||
_textBody.setPlatformPlayerLinkMovementMethod(viewGroup.context);
|
_textBody.setPlatformPlayerLinkMovementMethod(viewGroup.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,11 @@ class GestureControlView : LinearLayout {
|
||||||
private val _layoutControlsZoom: FrameLayout
|
private val _layoutControlsZoom: FrameLayout
|
||||||
private val _textZoom: TextView
|
private val _textZoom: TextView
|
||||||
private var _isZooming = false
|
private var _isZooming = false
|
||||||
|
private var _isPanning = false
|
||||||
|
private var _isZoomPanEnabled = false
|
||||||
|
private var _surfaceView: View? = null
|
||||||
|
private var _layoutIndicatorFill: FrameLayout;
|
||||||
|
private var _layoutIndicatorFit: FrameLayout;
|
||||||
|
|
||||||
private val _gestureController: GestureDetectorCompat;
|
private val _gestureController: GestureDetectorCompat;
|
||||||
|
|
||||||
|
@ -113,20 +118,16 @@ class GestureControlView : LinearLayout {
|
||||||
_textZoom = findViewById(R.id.text_zoom)
|
_textZoom = findViewById(R.id.text_zoom)
|
||||||
_progressBrightness = findViewById(R.id.progress_brightness);
|
_progressBrightness = findViewById(R.id.progress_brightness);
|
||||||
_layoutControlsFullscreen = findViewById(R.id.layout_controls_fullscreen);
|
_layoutControlsFullscreen = findViewById(R.id.layout_controls_fullscreen);
|
||||||
|
_layoutIndicatorFill = findViewById(R.id.layout_indicator_fill);
|
||||||
|
_layoutIndicatorFit = findViewById(R.id.layout_indicator_fit);
|
||||||
|
|
||||||
_scaleGestureDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
_scaleGestureDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||||
override fun onScale(detector: ScaleGestureDetector): Boolean {
|
override fun onScale(detector: ScaleGestureDetector): Boolean {
|
||||||
if (!_isFullScreen || !Settings.instance.gestureControls.zoom) {
|
if (!_isZoomPanEnabled || !_isFullScreen || !Settings.instance.gestureControls.zoom) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var newScaleFactor = (_scaleFactor * detector.scaleFactor).coerceAtLeast(1.0f).coerceAtMost(5.0f)
|
val newScaleFactor = (_scaleFactor * detector.scaleFactor).coerceAtLeast(1.0f).coerceAtMost(10.0f)
|
||||||
|
|
||||||
//Make original zoom sticky
|
|
||||||
if (newScaleFactor - 1.0f < 0.01f) {
|
|
||||||
newScaleFactor = 1.0f
|
|
||||||
}
|
|
||||||
|
|
||||||
val scaleFactorChange = newScaleFactor / _scaleFactor
|
val scaleFactorChange = newScaleFactor / _scaleFactor
|
||||||
_scaleFactor = newScaleFactor
|
_scaleFactor = newScaleFactor
|
||||||
onZoom.emit(_scaleFactor)
|
onZoom.emit(_scaleFactor)
|
||||||
|
@ -149,6 +150,9 @@ class GestureControlView : LinearLayout {
|
||||||
_layoutControlsZoom.visibility = View.VISIBLE
|
_layoutControlsZoom.visibility = View.VISIBLE
|
||||||
_textZoom.text = "${String.format("%.1f", _scaleFactor)}x"
|
_textZoom.text = "${String.format("%.1f", _scaleFactor)}x"
|
||||||
_isZooming = true
|
_isZooming = true
|
||||||
|
|
||||||
|
updateSnappingVisibility()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -164,7 +168,7 @@ class GestureControlView : LinearLayout {
|
||||||
|
|
||||||
Logger.i(TAG, "p0.pointerCount: " + p0.pointerCount)
|
Logger.i(TAG, "p0.pointerCount: " + p0.pointerCount)
|
||||||
|
|
||||||
if (p1.pointerCount == 1) {
|
if (!_isPanning && p1.pointerCount == 1) {
|
||||||
val minDistance = Math.min(width, height)
|
val minDistance = Math.min(width, height)
|
||||||
if (_isFullScreen && _adjustingBrightness) {
|
if (_isFullScreen && _adjustingBrightness) {
|
||||||
val adjustAmount = (distanceY * 2) / minDistance;
|
val adjustAmount = (distanceY * 2) / minDistance;
|
||||||
|
@ -201,8 +205,10 @@ class GestureControlView : LinearLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (_isFullScreen && !_isZooming && Settings.instance.gestureControls.pan) {
|
} else if (_isZoomPanEnabled && _isFullScreen && !_isZooming && Settings.instance.gestureControls.pan) {
|
||||||
|
_isPanning = true
|
||||||
stopAllGestures()
|
stopAllGestures()
|
||||||
|
updateSnappingVisibility()
|
||||||
pan(_translationX - distanceX, _translationY - distanceY)
|
pan(_translationX - distanceX, _translationY - distanceY)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,6 +250,39 @@ class GestureControlView : LinearLayout {
|
||||||
isClickable = true
|
isClickable = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateSnappingVisibility() {
|
||||||
|
if (willSnapFill()) {
|
||||||
|
_layoutIndicatorFill.visibility = View.VISIBLE
|
||||||
|
_layoutIndicatorFit.visibility = View.GONE
|
||||||
|
} else if (willSnapFit()) {
|
||||||
|
_layoutIndicatorFill.visibility = View.GONE
|
||||||
|
_layoutIndicatorFit.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
_surfaceView?.let {
|
||||||
|
val lp = _layoutIndicatorFit.layoutParams
|
||||||
|
lp.width = it.width
|
||||||
|
lp.height = it.height
|
||||||
|
_layoutIndicatorFit.layoutParams = lp
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_layoutIndicatorFill.visibility = View.GONE
|
||||||
|
_layoutIndicatorFit.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setZoomPanEnabled(view: View) {
|
||||||
|
_isZoomPanEnabled = true
|
||||||
|
_surfaceView = view
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetZoomPan() {
|
||||||
|
_scaleFactor = 1.0f
|
||||||
|
onZoom.emit(_scaleFactor)
|
||||||
|
_translationX = 0f
|
||||||
|
_translationY = 0f
|
||||||
|
onPan.emit(_translationX, _translationY)
|
||||||
|
}
|
||||||
|
|
||||||
private fun pan(translationX: Float, translationY: Float) {
|
private fun pan(translationX: Float, translationY: Float) {
|
||||||
val xc = width / 2.0f
|
val xc = width / 2.0f
|
||||||
val yc = height / 2.0f
|
val yc = height / 2.0f
|
||||||
|
@ -305,9 +344,29 @@ class GestureControlView : LinearLayout {
|
||||||
stopAdjustingFullscreenDown();
|
stopAdjustingFullscreenDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_isZooming && ev.action == MotionEvent.ACTION_UP) {
|
if ((_isPanning || _isZooming) && ev.action == MotionEvent.ACTION_UP) {
|
||||||
|
val surfaceView = _surfaceView
|
||||||
|
if (surfaceView != null && willSnapFill()) {
|
||||||
|
_scaleFactor = calculateZoomScaleFactor()
|
||||||
|
onZoom.emit(_scaleFactor)
|
||||||
|
|
||||||
|
_translationX = 0f
|
||||||
|
_translationY = 0f
|
||||||
|
onPan.emit(_translationX, _translationY)
|
||||||
|
} else if (willSnapFit()) {
|
||||||
|
_scaleFactor = 1f
|
||||||
|
onZoom.emit(_scaleFactor)
|
||||||
|
|
||||||
|
_translationX = 0f
|
||||||
|
_translationY = 0f
|
||||||
|
onPan.emit(_translationX, _translationY)
|
||||||
|
}
|
||||||
|
|
||||||
_layoutControlsZoom.visibility = View.GONE
|
_layoutControlsZoom.visibility = View.GONE
|
||||||
|
_layoutIndicatorFill.visibility = View.GONE
|
||||||
|
_layoutIndicatorFit.visibility = View.GONE
|
||||||
_isZooming = false
|
_isZooming = false
|
||||||
|
_isPanning = false
|
||||||
}
|
}
|
||||||
|
|
||||||
startHideJobIfNecessary();
|
startHideJobIfNecessary();
|
||||||
|
@ -317,6 +376,35 @@ class GestureControlView : LinearLayout {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun calculateZoomScaleFactor(): Float {
|
||||||
|
val w = _surfaceView?.width?.toFloat() ?: return 1.0f;
|
||||||
|
val h = _surfaceView?.height?.toFloat() ?: return 1.0f;
|
||||||
|
if (w == 0.0f || h == 0.0f) {
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(width / w, height / h)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _snapTranslationTolerance = 0.04f;
|
||||||
|
private val _snapZoomTolerance = 0.04f;
|
||||||
|
|
||||||
|
private fun willSnapFill(): Boolean {
|
||||||
|
val surfaceView = _surfaceView
|
||||||
|
if (surfaceView != null) {
|
||||||
|
val zoomScaleFactor = calculateZoomScaleFactor()
|
||||||
|
if (Math.abs(_scaleFactor - zoomScaleFactor) < _snapZoomTolerance && Math.abs(_translationX) / width < _snapTranslationTolerance && Math.abs(_translationY) / height < _snapTranslationTolerance) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun willSnapFit(): Boolean {
|
||||||
|
return Math.abs(_scaleFactor - 1.0f) < _snapZoomTolerance && Math.abs(_translationX) / width < _snapTranslationTolerance && Math.abs(_translationY) / height < _snapTranslationTolerance
|
||||||
|
}
|
||||||
|
|
||||||
fun cancelHideJob() {
|
fun cancelHideJob() {
|
||||||
_jobHideControls?.cancel();
|
_jobHideControls?.cancel();
|
||||||
_jobHideControls = null;
|
_jobHideControls = null;
|
||||||
|
@ -646,11 +734,7 @@ class GestureControlView : LinearLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setFullscreen(isFullScreen: Boolean) {
|
fun setFullscreen(isFullScreen: Boolean) {
|
||||||
_scaleFactor = 1.0f
|
resetZoomPan()
|
||||||
onZoom.emit(_scaleFactor)
|
|
||||||
_translationX = 0f
|
|
||||||
_translationY = 0f
|
|
||||||
onPan.emit(_translationX, _translationY)
|
|
||||||
|
|
||||||
if (isFullScreen) {
|
if (isFullScreen) {
|
||||||
val c = context
|
val c = context
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.futo.platformplayer.views.overlays
|
package com.futo.platformplayer.views.overlays
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
|
@ -8,11 +9,15 @@ import android.widget.TextView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
import com.futo.platformplayer.UIDialogs
|
import com.futo.platformplayer.UIDialogs
|
||||||
|
import com.futo.platformplayer.activities.MainActivity
|
||||||
|
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||||
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
|
||||||
import com.futo.platformplayer.constructs.Event0
|
import com.futo.platformplayer.constructs.Event0
|
||||||
import com.futo.platformplayer.fixHtmlLinks
|
import com.futo.platformplayer.fixHtmlLinks
|
||||||
|
import com.futo.platformplayer.logging.Logger
|
||||||
|
import com.futo.platformplayer.states.StateApp
|
||||||
import com.futo.platformplayer.states.StatePlatform
|
import com.futo.platformplayer.states.StatePlatform
|
||||||
import com.futo.platformplayer.states.StatePolycentric
|
import com.futo.platformplayer.states.StatePolycentric
|
||||||
import com.futo.platformplayer.toHumanNowDiffString
|
import com.futo.platformplayer.toHumanNowDiffString
|
||||||
|
@ -20,6 +25,13 @@ import com.futo.platformplayer.views.behavior.NonScrollingTextView
|
||||||
import com.futo.platformplayer.views.comments.AddCommentView
|
import com.futo.platformplayer.views.comments.AddCommentView
|
||||||
import com.futo.platformplayer.views.others.CreatorThumbnail
|
import com.futo.platformplayer.views.others.CreatorThumbnail
|
||||||
import com.futo.platformplayer.views.segments.CommentsList
|
import com.futo.platformplayer.views.segments.CommentsList
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.jsonArray
|
||||||
|
import kotlinx.serialization.json.jsonObject
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import userpackage.Protocol
|
import userpackage.Protocol
|
||||||
|
|
||||||
class RepliesOverlay : LinearLayout {
|
class RepliesOverlay : LinearLayout {
|
||||||
|
@ -34,7 +46,11 @@ class RepliesOverlay : LinearLayout {
|
||||||
private val _creatorThumbnail: CreatorThumbnail;
|
private val _creatorThumbnail: CreatorThumbnail;
|
||||||
private val _layoutParentComment: ConstraintLayout;
|
private val _layoutParentComment: ConstraintLayout;
|
||||||
private var _readonly = false;
|
private var _readonly = false;
|
||||||
|
private var _loading = true;
|
||||||
|
private var _parentComment: IPlatformComment? = null;
|
||||||
private var _onCommentAdded: ((comment: IPlatformComment) -> Unit)? = null;
|
private var _onCommentAdded: ((comment: IPlatformComment) -> Unit)? = null;
|
||||||
|
private val _loaderOverlay: LoaderOverlay
|
||||||
|
private val _client = ManagedHttpClient()
|
||||||
|
|
||||||
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
|
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
|
||||||
inflate(context, R.layout.overlay_replies, this)
|
inflate(context, R.layout.overlay_replies, this)
|
||||||
|
@ -46,6 +62,8 @@ class RepliesOverlay : LinearLayout {
|
||||||
_textAuthor = findViewById(R.id.text_author)
|
_textAuthor = findViewById(R.id.text_author)
|
||||||
_creatorThumbnail = findViewById(R.id.image_thumbnail)
|
_creatorThumbnail = findViewById(R.id.image_thumbnail)
|
||||||
_layoutParentComment = findViewById(R.id.layout_parent_comment)
|
_layoutParentComment = findViewById(R.id.layout_parent_comment)
|
||||||
|
_loaderOverlay = findViewById(R.id.loader_overlay)
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
_addCommentView.onCommentAdded.subscribe {
|
_addCommentView.onCommentAdded.subscribe {
|
||||||
_commentsList.addComment(it);
|
_commentsList.addComment(it);
|
||||||
|
@ -72,11 +90,21 @@ class RepliesOverlay : LinearLayout {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_layoutParentComment.setOnClickListener {
|
||||||
|
val p = _parentComment
|
||||||
|
if (p !is PolycentricPlatformComment) {
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
|
||||||
|
val ref = p.parentReference ?: return@setOnClickListener
|
||||||
|
handleParentClick(p.contextUrl, ref)
|
||||||
|
}
|
||||||
|
|
||||||
_topbar.onClose.subscribe(this, onClose::emit);
|
_topbar.onClose.subscribe(this, onClose::emit);
|
||||||
_topbar.setInfo(context.getString(R.string.Replies), "");
|
_topbar.setInfo(context.getString(R.string.Replies), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load(readonly: Boolean, metadata: String, contextUrl: String?, ref: Protocol.Reference?, parentComment: IPlatformComment? = null, 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, onParentClick: ((comment: IPlatformComment) -> Unit)? = null) {
|
||||||
_readonly = readonly;
|
_readonly = readonly;
|
||||||
if (readonly) {
|
if (readonly) {
|
||||||
_addCommentView.visibility = View.GONE;
|
_addCommentView.visibility = View.GONE;
|
||||||
|
@ -109,6 +137,136 @@ class RepliesOverlay : LinearLayout {
|
||||||
_topbar.setInfo(context.getString(R.string.Replies), metadata);
|
_topbar.setInfo(context.getString(R.string.Replies), metadata);
|
||||||
_commentsList.load(readonly, loader);
|
_commentsList.load(readonly, loader);
|
||||||
_onCommentAdded = onCommentAdded;
|
_onCommentAdded = onCommentAdded;
|
||||||
|
_parentComment = parentComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleParentClick(contextUrl: String, ref: Protocol.Reference): Boolean {
|
||||||
|
val ctx = context
|
||||||
|
if (ctx !is MainActivity) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (ref.referenceType) {
|
||||||
|
2L -> {
|
||||||
|
setLoading(true)
|
||||||
|
|
||||||
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val parentComment = StatePolycentric.instance.getComment(contextUrl, ref)
|
||||||
|
val replyCount = parentComment.replyCount ?: 0;
|
||||||
|
var metadata = "";
|
||||||
|
if (replyCount > 0) {
|
||||||
|
metadata += "$replyCount " + context.getString(R.string.replies);
|
||||||
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
setLoading(false)
|
||||||
|
|
||||||
|
load(false, metadata, parentComment.contextUrl, parentComment.reference, parentComment,
|
||||||
|
{ StatePolycentric.instance.getCommentPager(contextUrl, ref) })
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.e(TAG, "Failed to load parent comment.", e)
|
||||||
|
UIDialogs.toast("Failed to load comment")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
3L -> {
|
||||||
|
StateApp.instance.scopeOrNull?.launch {
|
||||||
|
try {
|
||||||
|
val url = referenceToUrl(_client, ref) ?: return@launch
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
ctx.handleUrl(url)
|
||||||
|
onClose.emit()
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Logger.i(TAG, "Failed to open ref.", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun referenceToUrl(client: ManagedHttpClient, parentRef: Protocol.Reference): String? {
|
||||||
|
val refBytes = parentRef.reference?.toByteArray() ?: return null
|
||||||
|
val ref = refBytes.decodeToString()
|
||||||
|
|
||||||
|
try {
|
||||||
|
Uri.parse(ref)
|
||||||
|
return ref
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
try {
|
||||||
|
return oldReferenceToUrl(client, ref)
|
||||||
|
} catch (f: Throwable) {
|
||||||
|
Logger.i(TAG, "Failed to handle URL.", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun oldReferenceToUrl(client: ManagedHttpClient, reference: String): String? {
|
||||||
|
return when {
|
||||||
|
reference.startsWith("video_episode:") -> {
|
||||||
|
val response = client.get("https://content.api.nebula.app/video_episodes/$reference")
|
||||||
|
if (!response.isOk) {
|
||||||
|
throw Exception("Failed to resolve nebula video (${response.code}).")
|
||||||
|
}
|
||||||
|
|
||||||
|
val respString = response.body?.string()
|
||||||
|
val jsonElement = respString?.let { Json.parseToJsonElement(it) }
|
||||||
|
return jsonElement?.jsonObject?.get("share_url")?.jsonPrimitive?.content
|
||||||
|
}
|
||||||
|
|
||||||
|
reference.length == 11 -> "https://www.youtube.com/watch?v=$reference"
|
||||||
|
|
||||||
|
reference.length == 40 -> {
|
||||||
|
val response = client.post("https://api.na-backend.odysee.com/api/v1/proxy?m=claim_search", hashMapOf(
|
||||||
|
"Content-Type" to "application/json"
|
||||||
|
))
|
||||||
|
|
||||||
|
if (!response.isOk) {
|
||||||
|
throw Exception("Failed to resolve claim (${response.code}).")
|
||||||
|
}
|
||||||
|
|
||||||
|
val jsonElement = response.body?.string()?.let { Json.parseToJsonElement(it) }
|
||||||
|
val canonicalUrl = jsonElement?.jsonObject?.get("result")
|
||||||
|
?.jsonObject?.get("items")
|
||||||
|
?.jsonArray?.get(0)
|
||||||
|
?.jsonObject?.get("canonical_url")
|
||||||
|
?.jsonPrimitive?.content
|
||||||
|
|
||||||
|
canonicalUrl ?: throw Exception("Failed to get canonical URL.")
|
||||||
|
}
|
||||||
|
|
||||||
|
reference.startsWith("v") && (reference.length == 7 || reference.length == 6) -> "https://rumble.com/$reference"
|
||||||
|
|
||||||
|
Regex("^\\d+\$").matches(reference) -> "https://www.twitch.tv/videos/$reference"
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setLoading(loading: Boolean) {
|
||||||
|
if (_loading == loading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_loading = loading;
|
||||||
|
if (!loading) {
|
||||||
|
_loaderOverlay.hide()
|
||||||
|
} else {
|
||||||
|
_loaderOverlay.show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cleanup() {
|
fun cleanup() {
|
||||||
|
@ -116,4 +274,8 @@ class RepliesOverlay : LinearLayout {
|
||||||
_onCommentAdded = null;
|
_onCommentAdded = null;
|
||||||
_commentsList.cancel();
|
_commentsList.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "RepliesOverlay"
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -4,7 +4,6 @@ import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.KeyCharacterMap.UnavailableException
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
@ -12,10 +11,8 @@ import android.widget.TextView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.futo.platformplayer.logging.Logger
|
|
||||||
import com.futo.platformplayer.UIDialogs
|
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
import com.futo.platformplayer.states.StateApp
|
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.models.video.IPlatformVideoDetails
|
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
||||||
|
@ -25,6 +22,8 @@ import com.futo.platformplayer.constructs.Event1
|
||||||
import com.futo.platformplayer.constructs.TaskHandler
|
import com.futo.platformplayer.constructs.TaskHandler
|
||||||
import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException
|
import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException
|
||||||
import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions
|
import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions
|
||||||
|
import com.futo.platformplayer.logging.Logger
|
||||||
|
import com.futo.platformplayer.states.StateApp
|
||||||
import com.futo.platformplayer.states.StatePolycentric
|
import com.futo.platformplayer.states.StatePolycentric
|
||||||
import com.futo.platformplayer.views.adapters.CommentViewHolder
|
import com.futo.platformplayer.views.adapters.CommentViewHolder
|
||||||
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
|
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
|
||||||
|
@ -87,8 +86,6 @@ class CommentsList : ConstraintLayout {
|
||||||
var onRepliesClick = Event1<IPlatformComment>();
|
var onRepliesClick = Event1<IPlatformComment>();
|
||||||
var onCommentsLoaded = Event1<Int>();
|
var onCommentsLoaded = Event1<Int>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||||
LayoutInflater.from(context).inflate(R.layout.view_comments_list, this, true);
|
LayoutInflater.from(context).inflate(R.layout.view_comments_list, this, true);
|
||||||
|
|
||||||
|
|
|
@ -272,6 +272,8 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||||
_videoView.scaleY = it
|
_videoView.scaleY = it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gestureControl.setZoomPanEnabled(_videoView.videoSurfaceView!!)
|
||||||
|
|
||||||
if(!isInEditMode) {
|
if(!isInEditMode) {
|
||||||
_videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
|
_videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
|
||||||
val player = StatePlayer.instance.getPlayerOrCreate(context);
|
val player = StatePlayer.instance.getPlayerOrCreate(context);
|
||||||
|
@ -600,6 +602,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onVideoSizeChanged(videoSize: VideoSize) {
|
override fun onVideoSizeChanged(videoSize: VideoSize) {
|
||||||
|
gestureControl.resetZoomPan()
|
||||||
_lastSourceFit = null;
|
_lastSourceFit = null;
|
||||||
if(isFullScreen)
|
if(isFullScreen)
|
||||||
fillHeight();
|
fillHeight();
|
||||||
|
@ -763,4 +766,8 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||||
fun setGestureSoundFactor(soundFactor: Float) {
|
fun setGestureSoundFactor(soundFactor: Float) {
|
||||||
gestureControl.setSoundFactor(soundFactor);
|
gestureControl.setSoundFactor(soundFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSurfaceSizeChanged(width: Int, height: Int) {
|
||||||
|
gestureControl.resetZoomPan()
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -104,6 +104,11 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
super.onPlaybackSuppressionReasonChanged(playbackSuppressionReason)
|
super.onPlaybackSuppressionReasonChanged(playbackSuppressionReason)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSurfaceSizeChanged(width: Int, height: Int) {
|
||||||
|
super.onSurfaceSizeChanged(width, height)
|
||||||
|
this@FutoVideoPlayerBase.onSurfaceSizeChanged(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||||
super.onIsPlayingChanged(isPlaying);
|
super.onIsPlayingChanged(isPlaying);
|
||||||
this@FutoVideoPlayerBase.onIsPlayingChanged(isPlaying);
|
this@FutoVideoPlayerBase.onIsPlayingChanged(isPlaying);
|
||||||
|
@ -592,6 +597,10 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
|
||||||
exoPlayer?.setVolume(volume);
|
exoPlayer?.setVolume(volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected open fun onSurfaceSizeChanged(width: Int, height: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
protected open fun onPlayerError(error: PlaybackException) {
|
protected open fun onPlayerError(error: PlaybackException) {
|
||||||
Logger.i(TAG, "onPlayerError error=$error error.errorCode=${error.errorCode} connectivityLoss");
|
Logger.i(TAG, "onPlayerError error=$error error.errorCode=${error.errorCode} connectivityLoss");
|
||||||
|
|
5
app/src/main/res/drawable/background_primary_border.xml
Normal file
5
app/src/main/res/drawable/background_primary_border.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<stroke android:color="#992D63ED" android:width="5dp" />
|
||||||
|
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
|
||||||
|
</shape>
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layout_header"
|
android:id="@+id/layout_header"
|
||||||
|
@ -65,7 +65,8 @@
|
||||||
android:id="@+id/replies_overlay"
|
android:id="@+id/replies_overlay"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent"
|
||||||
|
android:clickable="true" />
|
||||||
|
|
||||||
<LinearLayout android:id="@+id/layout_not_logged_in"
|
<LinearLayout android:id="@+id/layout_not_logged_in"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:id="@+id/container"
|
android:id="@+id/container"
|
||||||
android:background="#77000000"
|
android:background="#77000000"
|
||||||
android:elevation="4dp">
|
android:elevation="4dp"
|
||||||
|
android:clickable="true">
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/loader"
|
android:id="@+id/loader"
|
||||||
android:layout_width="80dp"
|
android:layout_width="80dp"
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
app:layout_constraintLeft_toRightOf="@id/image_thumbnail"
|
app:layout_constraintLeft_toRightOf="@id/image_thumbnail"
|
||||||
app:layout_constraintTop_toTopOf="@id/image_thumbnail"
|
app:layout_constraintTop_toTopOf="@id/image_thumbnail"
|
||||||
android:text="ShortCircuit" />
|
tools:text="ShortCircuit" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_metadata"
|
android:id="@+id/text_metadata"
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
app:layout_constraintLeft_toRightOf="@id/text_author"
|
app:layout_constraintLeft_toRightOf="@id/text_author"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@id/text_author"
|
app:layout_constraintTop_toTopOf="@id/text_author"
|
||||||
android:text=" • 3 years ago" />
|
tools:text=" • 3 years ago" />
|
||||||
|
|
||||||
<com.futo.platformplayer.views.behavior.NonScrollingTextView
|
<com.futo.platformplayer.views.behavior.NonScrollingTextView
|
||||||
android:id="@+id/text_body"
|
android:id="@+id/text_body"
|
||||||
|
@ -84,7 +84,7 @@
|
||||||
app:layout_constraintTop_toBottomOf="@id/text_metadata"
|
app:layout_constraintTop_toBottomOf="@id/text_metadata"
|
||||||
app:layout_constraintLeft_toRightOf="@id/image_thumbnail"
|
app:layout_constraintLeft_toRightOf="@id/image_thumbnail"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
android:text="@string/lorem_ipsum" />
|
tools:text="@string/lorem_ipsum" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
@ -107,4 +107,11 @@
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
android:layout_marginTop="12dp" />
|
android:layout_marginTop="12dp" />
|
||||||
|
|
||||||
|
<com.futo.platformplayer.views.overlays.LoaderOverlay
|
||||||
|
android:id="@+id/loader_overlay"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clickable="true" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -177,4 +177,22 @@
|
||||||
android:textSize="16dp"/>
|
android:textSize="16dp"/>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/layout_indicator_fill"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/background_primary_border"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/layout_indicator_fit"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
android:background="@drawable/background_primary_border"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
Add table
Add a link
Reference in a new issue