diff --git a/app/src/main/assets/scripts/source.js b/app/src/main/assets/scripts/source.js index 73e8eb56..01dcde6a 100644 --- a/app/src/main/assets/scripts/source.js +++ b/app/src/main/assets/scripts/source.js @@ -32,7 +32,8 @@ let Type = { Text: { RAW: 0, HTML: 1, - MARKUP: 2 + MARKUP: 2, + CODE: 3 }, Chapter: { NORMAL: 0, @@ -323,9 +324,10 @@ class ArticleTextSegment extends ArticleSegment { } } class ArticleImagesSegment extends ArticleSegment { - constructor(images) { + constructor(images, caption) { super(2); this.images = images; + this.caption = caption; } } class ArticleNestedSegment extends ArticleSegment { diff --git a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt index 28c5484e..b24fa419 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt @@ -25,7 +25,6 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.OptIn import androidx.appcompat.app.AppCompatActivity import androidx.constraintlayout.motion.widget.MotionLayout -import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.view.isVisible @@ -851,7 +850,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { return withContext(Dispatchers.IO) { Logger.i(TAG, "handleUrl(url=$url) on IO"); - if (StatePlatform.instance.hasEnabledVideoClient(url)) { + if (StatePlatform.instance.hasEnabledContentClient(url)) { Logger.i(TAG, "handleUrl(url=$url) found video client"); lifecycleScope.launch(Dispatchers.Main) { if (position > 0) diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/post/TextType.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/post/TextType.kt index c1de57d1..c59b7e1a 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/post/TextType.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/post/TextType.kt @@ -5,7 +5,8 @@ import com.futo.platformplayer.api.media.exceptions.UnknownPlatformException enum class TextType(val value: Int) { RAW(0), HTML(1), - MARKUP(2); + MARKUP(2), + CODE(3); companion object { fun fromInt(value: Int): TextType diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ArticleDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ArticleDetailFragment.kt index ae098afa..0f210d8d 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ArticleDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ArticleDetailFragment.kt @@ -2,8 +2,11 @@ package com.futo.platformplayer.fragment.mainactivity.main import android.content.Context import android.content.Intent +import android.graphics.Typeface import android.graphics.drawable.Animatable import android.os.Bundle +import android.text.Html +import android.text.method.ScrollingMovementMethod import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -16,23 +19,33 @@ import android.widget.LinearLayout import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.children +import androidx.core.view.isVisible +import androidx.core.view.setPadding import androidx.lifecycle.lifecycleScope import com.bumptech.glide.Glide import com.futo.platformplayer.R import com.futo.platformplayer.Settings import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.UISlideOverlays import com.futo.platformplayer.api.media.PlatformID import com.futo.platformplayer.api.media.models.Thumbnails import com.futo.platformplayer.api.media.models.article.IPlatformArticle import com.futo.platformplayer.api.media.models.article.IPlatformArticleDetails import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.locked.IPlatformLockedContent +import com.futo.platformplayer.api.media.models.nested.IPlatformNestedContent import com.futo.platformplayer.api.media.models.post.IPlatformPost import com.futo.platformplayer.api.media.models.post.IPlatformPostDetails +import com.futo.platformplayer.api.media.models.post.TextType import com.futo.platformplayer.api.media.models.ratings.IRating import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes import com.futo.platformplayer.api.media.models.ratings.RatingLikes +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo import com.futo.platformplayer.api.media.platforms.js.models.JSArticleDetails import com.futo.platformplayer.api.media.platforms.js.models.JSImagesSegment +import com.futo.platformplayer.api.media.platforms.js.models.JSNestedSegment import com.futo.platformplayer.api.media.platforms.js.models.JSTextSegment import com.futo.platformplayer.api.media.platforms.js.models.SegmentType import com.futo.platformplayer.constructs.TaskHandler @@ -43,10 +56,16 @@ import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.setPlatformPlayerLinkMovementMethod import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StatePlayer +import com.futo.platformplayer.states.StatePlaylists import com.futo.platformplayer.states.StatePolycentric import com.futo.platformplayer.toHumanNowDiffString import com.futo.platformplayer.toHumanNumber +import com.futo.platformplayer.views.FeedStyle +import com.futo.platformplayer.views.adapters.feedtypes.PreviewLockedView +import com.futo.platformplayer.views.adapters.feedtypes.PreviewNestedVideoView import com.futo.platformplayer.views.adapters.feedtypes.PreviewPostView +import com.futo.platformplayer.views.adapters.feedtypes.PreviewVideoView import com.futo.platformplayer.views.comments.AddCommentView import com.futo.platformplayer.views.others.CreatorThumbnail import com.futo.platformplayer.views.overlays.RepliesOverlay @@ -126,6 +145,7 @@ class ArticleDetailFragment : MainFragment { private val _channelMeta: TextView; private val _textTitle: TextView; private val _textMeta: TextView; + private val _textSummary: TextView; private val _containerSegments: LinearLayout; private val _platformIndicator: PlatformIndicator; private val _buttonShare: ImageButton; @@ -143,6 +163,7 @@ class ArticleDetailFragment : MainFragment { private val _layoutLoadingOverlay: FrameLayout; private val _imageLoader: ImageView; + private var _overlayContainer: FrameLayout private val _repliesOverlay: RepliesOverlay; private val _commentsList: CommentsList; @@ -187,10 +208,12 @@ class ArticleDetailFragment : MainFragment { _channelMeta = findViewById(R.id.text_channel_meta); _textTitle = findViewById(R.id.text_title); _textMeta = findViewById(R.id.text_meta); + _textSummary = findViewById(R.id.text_summary); _containerSegments = findViewById(R.id.container_segments); _platformIndicator = findViewById(R.id.platform_indicator); _buttonShare = findViewById(R.id.button_share); + _overlayContainer = findViewById(R.id.overlay_container); _layoutRating = findViewById(R.id.layout_rating); _imageLikeIcon = findViewById(R.id.image_like_icon); @@ -449,6 +472,8 @@ class ArticleDetailFragment : MainFragment { _textTitle.text = value.name; _textMeta.text = value.datetime?.toHumanNowDiffString()?.let { "$it ago" } ?: "" //TODO: Include view count? + _textSummary.text = value.summary + _textSummary.isVisible = !value.summary.isNullOrEmpty() _platformIndicator.setPlatformFromClientID(value.id.pluginId); setPlatformRating(value.rating); @@ -457,12 +482,18 @@ class ArticleDetailFragment : MainFragment { when(seg.type) { SegmentType.TEXT -> { if(seg is JSTextSegment) { - + _containerSegments.addView(ArticleTextBlock(context, seg.content, seg.textType)) } } SegmentType.IMAGES -> { if(seg is JSImagesSegment) { - + if(seg.images.size > 0) + _containerSegments.addView(ArticleImageBlock(context, seg.images[0], seg.caption)) + } + } + SegmentType.NESTED -> { + if(seg is JSNestedSegment) { + _containerSegments.addView(ArticleContentBlock(context, seg.nested, _fragment, _overlayContainer)); } } else ->{} @@ -659,14 +690,86 @@ class ArticleDetailFragment : MainFragment { } } - class ArticleTextBlock : View { - constructor(context: Context?) : super(context){ + class ArticleTextBlock : LinearLayout { + constructor(context: Context?, content: String, textType: TextType) : super(context){ + inflate(context, R.layout.view_segment_text, this); + findViewById(R.id.text_content)?.let { + if(textType == TextType.HTML) + it.text = Html.fromHtml(content, Html.FROM_HTML_MODE_COMPACT); + else if(textType == TextType.CODE) { + it.text = content; + it.setPadding(15.dp(resources)); + it.setHorizontallyScrolling(true); + it.movementMethod = ScrollingMovementMethod(); + it.setTypeface(Typeface.MONOSPACE); + it.setBackgroundResource(R.drawable.background_videodetail_description) + } + else + it.text = content; + } } } - class ArticleImageBlock: View { - constructor(context: Context?) : super(context){ + class ArticleImageBlock: LinearLayout { + constructor(context: Context?, image: String, caption: String? = null) : super(context){ + inflate(context, R.layout.view_segment_image, this); + findViewById(R.id.image_content)?.let { + Glide.with(it) + .load(image) + .crossfade() + .into(it); + } + findViewById(R.id.text_content)?.let { + if(caption?.isNullOrEmpty() == true) + it.isVisible = false; + else + it.text = caption; + } + } + } + class ArticleContentBlock: LinearLayout { + constructor(context: Context, content: IPlatformContent?, fragment: ArticleDetailFragment? = null, overlayContainer: FrameLayout? = null): super(context) { + if(content != null) { + var view: View? = null; + if(content is IPlatformNestedContent) { + view = PreviewNestedVideoView(context, FeedStyle.THUMBNAIL, null); + view.bind(content); + view.onContentUrlClicked.subscribe { a,b -> } + } + else if(content is IPlatformVideo) { + view = PreviewVideoView(context, FeedStyle.THUMBNAIL, null, true); + view.bind(content); + view.onVideoClicked.subscribe { a,b -> fragment?.navigate(a) } + view.onChannelClicked.subscribe { a -> fragment?.navigate(a) } + if(overlayContainer != null) { + view.onAddToClicked.subscribe { a -> UISlideOverlays.showVideoOptionsOverlay(a, overlayContainer) }; + } + view.onAddToQueueClicked.subscribe { a -> StatePlayer.instance.addToQueue(a) } + view.onAddToWatchLaterClicked.subscribe { a -> + if(StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(content), true)) + UIDialogs.toast("Added to watch later\n[${content.name}]") + } + } + else if(content is IPlatformPost) { + view = PreviewPostView(context, FeedStyle.THUMBNAIL); + view.bind(content); + view.onContentClicked.subscribe { a -> fragment?.navigate(a) } + view.onChannelClicked.subscribe { a -> fragment?.navigate(a) } + } + else if(content is IPlatformArticle) { + view = PreviewPostView(context, FeedStyle.THUMBNAIL); + view.bind(content); + view.onContentClicked.subscribe { a -> fragment?.navigate(a) } + view.onChannelClicked.subscribe { a -> fragment?.navigate(a) } + } + else if(content is IPlatformLockedContent) { + view = PreviewLockedView(context, FeedStyle.THUMBNAIL); + view.bind(content); + } + if(view != null) + addView(view); + } } } diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt index 7833b781..c843ea9f 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt @@ -5,7 +5,6 @@ import androidx.collection.LruCache import com.futo.platformplayer.R import com.futo.platformplayer.Settings import com.futo.platformplayer.UIDialogs -import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.media.IPlatformClient import com.futo.platformplayer.api.media.IPluginSourced import com.futo.platformplayer.api.media.PlatformMultiClientPool @@ -46,7 +45,6 @@ import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.ImageVariable import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.StringArrayStorage -import com.futo.platformplayer.stores.StringStorage import com.futo.platformplayer.views.ToastView import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred @@ -56,7 +54,6 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import okhttp3.internal.concat import java.lang.Thread.sleep import java.time.OffsetDateTime import kotlin.streams.asSequence @@ -669,7 +666,7 @@ class StatePlatform { //Video - fun hasEnabledVideoClient(url: String) : Boolean = getEnabledClients().any { _instantClientPool.getClientPooled(it).isContentDetailsUrl(url) }; + fun hasEnabledContentClient(url: String) : Boolean = getEnabledClients().any { _instantClientPool.getClientPooled(it).isContentDetailsUrl(url) }; fun getContentClient(url: String) : IPlatformClient = getContentClientOrNull(url) ?: throw NoPlatformClientException("No client enabled that supports this content url (${url})"); fun getContentClientOrNull(url: String) : IPlatformClient? = getEnabledClients().find { _instantClientPool.getClientPooled(it).isContentDetailsUrl(url) }; diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPostView.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPostView.kt index 1d90e09c..4422a5c8 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPostView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPostView.kt @@ -21,6 +21,7 @@ import com.futo.platformplayer.R import com.futo.platformplayer.api.media.PlatformID import com.futo.platformplayer.api.media.models.PlatformAuthorLink import com.futo.platformplayer.api.media.models.Thumbnails +import com.futo.platformplayer.api.media.models.article.IPlatformArticle import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.post.IPlatformPost import com.futo.platformplayer.api.media.models.post.IPlatformPostDetails @@ -141,6 +142,11 @@ class PreviewPostView : LinearLayout { content.content else "" + } else if(content is IPlatformArticle) { + if(!content.summary.isNullOrEmpty()) + content.summary ?: "" + else + "" } else ""; if (content.name.isNullOrEmpty()) { @@ -154,7 +160,14 @@ class PreviewPostView : LinearLayout { if (content is IPlatformPost) { setImages(content.thumbnails.filterNotNull()); - } else { + } + else if(content is IPlatformArticle) { + if(content.thumbnails != null) + setImages(listOf(content.thumbnails!!)); + else + setImages(null); + } + else { setImages(null); } diff --git a/app/src/main/res/layout/fragview_article_detail.xml b/app/src/main/res/layout/fragview_article_detail.xml index c98f5daf..9476c0de 100644 --- a/app/src/main/res/layout/fragview_article_detail.xml +++ b/app/src/main/res/layout/fragview_article_detail.xml @@ -16,6 +16,69 @@ android:layout_height="wrap_content" android:orientation="vertical"> + + + + + + + + + + + + + + @@ -113,68 +176,17 @@ - - - - - - - - - - - - - - + + + + \ No newline at end of file