mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-04-20 03:24:50 +00:00
Grayjay logo, WatchLater button, WatchLater download, Download notification dismiss fix, Polycentric open platform, Minor utility additions, Dev method documentation url support
This commit is contained in:
parent
40b86cb5de
commit
b370af9d91
38 changed files with 546 additions and 101 deletions
|
@ -402,6 +402,11 @@
|
|||
<div class="code">
|
||||
{{req.code}}
|
||||
</div>
|
||||
<div class="documentation" v-if="req.docUrl" style="position: absolute; right: 15px; top: 15px;">
|
||||
<a :href="req.docUrl" target="_blank">
|
||||
Documentation
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<div class="parameter" v-for="parameter in req.parameters">
|
||||
<div class="name">
|
||||
|
@ -538,6 +543,7 @@
|
|||
<!--<script src="./dependencies/vue.js"></script>-->
|
||||
<!--<script src="./dependencies/vuetify.js"></script>-->
|
||||
<script src="./source_docs.js"></script>
|
||||
<script src="./source_doc_urls.js"></script>
|
||||
<script src="./source.js"></script>
|
||||
<script src="./dev_bridge.js"></script>
|
||||
<script>
|
||||
|
@ -574,6 +580,9 @@
|
|||
Testing: {
|
||||
requests: sourceDocs.map(x=>{
|
||||
x.parameters.forEach(y=>y.value = null);
|
||||
|
||||
if(sourceDocUrls[x.title])
|
||||
x.docUrl = sourceDocUrls[x.title];
|
||||
return x;
|
||||
}),
|
||||
lastResult: "",
|
||||
|
|
|
@ -510,10 +510,15 @@ class UISlideOverlays {
|
|||
}
|
||||
}
|
||||
fun showDownloadPlaylistOverlay(playlist: Playlist, container: ViewGroup) {
|
||||
showUnknownVideoDownload(container.context.getString(R.string.video), container) { px, bitrate ->
|
||||
showUnknownVideoDownload(container.context.getString(R.string.playlist), container) { px, bitrate ->
|
||||
StateDownloads.instance.download(playlist, px, bitrate);
|
||||
};
|
||||
}
|
||||
fun showDownloadWatchlaterOverlay(container: ViewGroup) {
|
||||
showUnknownVideoDownload(container.context.getString(R.string.watch_later), container, { px, bitrate ->
|
||||
StateDownloads.instance.downloadWatchLater(px, bitrate);
|
||||
})
|
||||
}
|
||||
private fun showUnknownVideoDownload(toDownload: String, container: ViewGroup, cb: (Long?, Long?)->Unit) {
|
||||
val items = arrayListOf<View>();
|
||||
var menu: SlideUpMenuOverlay? = null;
|
||||
|
|
|
@ -56,8 +56,10 @@ import com.futo.platformplayer.states.StatePlugins
|
|||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.Dictionary
|
||||
import kotlin.reflect.full.findAnnotations
|
||||
import kotlin.reflect.jvm.kotlinFunction
|
||||
import kotlin.streams.asSequence
|
||||
|
||||
open class JSClient : IPlatformClient {
|
||||
val config: SourcePluginConfig;
|
||||
|
@ -662,10 +664,43 @@ open class JSClient : IPlatformClient {
|
|||
|
||||
companion object {
|
||||
val TAG = "JSClient";
|
||||
private val _lock = Object();
|
||||
private var _docs: Map<String, String>? = null;
|
||||
|
||||
fun getMethodDocs(names: List<String>): Map<String, String>? {
|
||||
synchronized(_lock) {
|
||||
if(_docs == null) {
|
||||
val client = ManagedHttpClient();
|
||||
val docs = names
|
||||
.map { stringWithoutBrackets(it) }
|
||||
.distinct()
|
||||
.parallelStream()
|
||||
.map {
|
||||
val url = "https://github.com/futo-org/grayjay-android/blob/master/docs/source/${it}.md";
|
||||
val resp = client.head(url);
|
||||
if(resp.isOk)
|
||||
return@map Pair(it, url);
|
||||
else
|
||||
return@map null;
|
||||
}.asSequence()
|
||||
.filterNotNull()
|
||||
.toMap();
|
||||
_docs = docs;
|
||||
}
|
||||
return _docs;
|
||||
}
|
||||
}
|
||||
fun getMethodDocUrls(): Map<String, String>? {
|
||||
if(_docs != null)
|
||||
return _docs;
|
||||
val methods = JSClient::class.java.declaredMethods.filter { it.getAnnotation(JSDocs::class.java) != null }
|
||||
return getMethodDocs(methods.map { it.name });
|
||||
}
|
||||
|
||||
fun getJSDocs(): List<JSCallDocs> {
|
||||
val docs = mutableListOf<JSCallDocs>();
|
||||
val methods = JSClient::class.java.declaredMethods.filter { it.getAnnotation(JSDocs::class.java) != null }
|
||||
|
||||
for(method in methods.sortedBy { it.getAnnotation(JSDocs::class.java)?.order }) {
|
||||
val doc = method.getAnnotation(JSDocs::class.java);
|
||||
val parameters = method.kotlinFunction!!.findAnnotations<JSDocsParameter>();
|
||||
|
@ -678,5 +713,12 @@ open class JSClient : IPlatformClient {
|
|||
}
|
||||
return docs;
|
||||
}
|
||||
|
||||
private fun stringWithoutBrackets(name: String): String {
|
||||
val index = name.indexOf('(');
|
||||
if(index >= 0)
|
||||
return name.substring(0, index);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,6 +14,6 @@ annotation class JSOptional()
|
|||
annotation class JSDocsParameter(val name: String, val description: String, val order: Int = 0)
|
||||
|
||||
@kotlinx.serialization.Serializable
|
||||
data class JSCallDocs(val title: String, val code: String, val description: String, val parameters: List<JSParameterDocs>, val isOptional: Boolean = false);
|
||||
data class JSCallDocs(val title: String, val code: String, val description: String, val parameters: List<JSParameterDocs>, val isOptional: Boolean = false, val docsUrl: String? = null);
|
||||
@kotlinx.serialization.Serializable
|
||||
data class JSParameterDocs(val name: String, val description: String);
|
|
@ -104,6 +104,17 @@ class DeveloperEndpoints(private val context: Context) {
|
|||
@HttpGET("/source_docs.js", "application/javascript")
|
||||
val devSourceDocsJS = "const sourceDocs = $devSourceDocsJson";
|
||||
|
||||
@HttpGET("/source_doc_urls.json", "application/json")
|
||||
fun devSourceDocUrlsJson(httpContext: HttpContext) {;
|
||||
val docs = JSClient.getMethodDocUrls();
|
||||
httpContext.respondCode(200, Json.encodeToString(docs), "application/json");
|
||||
}
|
||||
@HttpGET("/source_doc_urls.js", "application/javascript")
|
||||
fun devSourceDocUrlsJs(httpContext: HttpContext) {;
|
||||
val docs = JSClient.getMethodDocUrls();
|
||||
httpContext.respondCode(200, "const sourceDocUrls = " + Json.encodeToString(docs), "application/javascript");
|
||||
}
|
||||
|
||||
//Dependencies
|
||||
//@HttpGET("/dependencies/vue.js", "application/javascript")
|
||||
//val depVue = StateAssets.readAsset(context, "devportal/dependencies/vue.js", true);
|
||||
|
|
|
@ -755,6 +755,7 @@ class VideoDownload {
|
|||
companion object {
|
||||
const val TAG = "VideoDownload";
|
||||
const val GROUP_PLAYLIST = "Playlist";
|
||||
const val GROUP_WATCHLATER= "WatchLater";
|
||||
|
||||
fun videoContainerToExtension(container: String): String? {
|
||||
if (container.contains("video/mp4") || container == "application/vnd.apple.mpegurl")
|
||||
|
|
|
@ -4,8 +4,11 @@ import android.util.Base64
|
|||
import com.caoccao.javet.annotations.V8Function
|
||||
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.google.common.hash.Hashing.md5
|
||||
import java.security.MessageDigest
|
||||
import java.util.UUID
|
||||
|
||||
|
||||
class PackageUtilities : V8Package {
|
||||
@Transient
|
||||
private val _config: IV8PluginConfig;
|
||||
|
@ -22,6 +25,16 @@ class PackageUtilities : V8Package {
|
|||
return Base64.encodeToString(arr, Base64.NO_WRAP);
|
||||
}
|
||||
|
||||
@V8Function
|
||||
fun fromBase64(str: String): ByteArray {
|
||||
return Base64.decode(str, Base64.DEFAULT)
|
||||
}
|
||||
|
||||
@V8Function
|
||||
fun md5(arr: ByteArray): ByteArray {
|
||||
return MessageDigest.getInstance("MD5").digest(arr);
|
||||
}
|
||||
|
||||
@V8Function
|
||||
fun randomUUID(): String {
|
||||
return UUID.randomUUID().toString();
|
||||
|
|
|
@ -60,8 +60,10 @@ class ChannelContentsFragment : Fragment(), IChannelTabFragment {
|
|||
val onChannelClicked = Event1<PlatformAuthorLink>();
|
||||
val onAddToClicked = Event1<IPlatformContent>();
|
||||
val onAddToQueueClicked = Event1<IPlatformContent>();
|
||||
val onAddToWatchLaterClicked = Event1<IPlatformContent>();
|
||||
val onLongPress = Event1<IPlatformContent>();
|
||||
|
||||
|
||||
private fun getContentPager(channel: IPlatformChannel): IPager<IPlatformContent> {
|
||||
Logger.i(TAG, "getContentPager");
|
||||
|
||||
|
@ -157,6 +159,7 @@ class ChannelContentsFragment : Fragment(), IChannelTabFragment {
|
|||
this.onChannelClicked.subscribe(this@ChannelContentsFragment.onChannelClicked::emit);
|
||||
this.onAddToClicked.subscribe(this@ChannelContentsFragment.onAddToClicked::emit);
|
||||
this.onAddToQueueClicked.subscribe(this@ChannelContentsFragment.onAddToQueueClicked::emit);
|
||||
this.onAddToWatchLaterClicked.subscribe(this@ChannelContentsFragment.onAddToWatchLaterClicked::emit);
|
||||
this.onLongPress.subscribe(this@ChannelContentsFragment.onLongPress::emit);
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import com.futo.platformplayer.api.media.models.contents.ContentType
|
|||
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist
|
||||
import com.futo.platformplayer.api.media.models.post.IPlatformPost
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
|
||||
import com.futo.platformplayer.constructs.TaskHandler
|
||||
import com.futo.platformplayer.fragment.channel.tab.ChannelAboutFragment
|
||||
import com.futo.platformplayer.fragment.channel.tab.ChannelContentsFragment
|
||||
|
@ -206,6 +207,12 @@ class ChannelFragment : MainFragment() {
|
|||
StatePlayer.instance.addToQueue(content);
|
||||
}
|
||||
}
|
||||
adapter.onAddToWatchLaterClicked.subscribe { content ->
|
||||
if(content is IPlatformVideo) {
|
||||
StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(content));
|
||||
UIDialogs.toast("Added to watch later\n[${content.name}]");
|
||||
}
|
||||
}
|
||||
adapter.onUrlClicked.subscribe { url ->
|
||||
fragment.navigate<BrowserFragment>(url);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.futo.platformplayer.fragment.mainactivity.main
|
|||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.provider.Browser
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -118,6 +119,7 @@ class CommentsFragment : MainFragment() {
|
|||
holder.onDelete.subscribe(::onDelete);
|
||||
holder.onRepliesClick.subscribe(::onRepliesClick);
|
||||
holder.onClick.subscribe(::onClick);
|
||||
holder.onAuthorClick.subscribe(::onAuthorClick);
|
||||
return@InsertedViewAdapterWithLoader holder;
|
||||
}
|
||||
);
|
||||
|
@ -211,6 +213,17 @@ class CommentsFragment : MainFragment() {
|
|||
setRepliesOverlayVisible(true, true)
|
||||
}
|
||||
}
|
||||
private fun onAuthorClick(c: IPlatformComment) {
|
||||
if (c !is PolycentricPlatformComment) {
|
||||
return@onAuthorClick;
|
||||
}
|
||||
|
||||
Logger.i(TAG, "onAuthorClick: " + c.author.id.value);
|
||||
if(c.author.id.value?.startsWith("polycentric://") ?: false) {
|
||||
val navUrl = "https://harbor.social/" + c.author.id.value?.substring("polycentric://".length);
|
||||
_fragment.navigate<BrowserFragment>(navUrl);
|
||||
}
|
||||
}
|
||||
|
||||
private fun onRepliesClick(c: IPlatformComment) {
|
||||
val replyCount = c.replyCount ?: 0;
|
||||
|
|
|
@ -12,10 +12,12 @@ import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
|||
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist
|
||||
import com.futo.platformplayer.api.media.models.post.IPlatformPost
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
|
||||
import com.futo.platformplayer.api.media.structures.*
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.states.StateMeta
|
||||
import com.futo.platformplayer.states.StatePlayer
|
||||
import com.futo.platformplayer.states.StatePlaylists
|
||||
import com.futo.platformplayer.video.PlayerManager
|
||||
import com.futo.platformplayer.views.FeedStyle
|
||||
import com.futo.platformplayer.views.adapters.feedtypes.PreviewContentListAdapter
|
||||
|
@ -81,6 +83,12 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
|||
StatePlayer.instance.addToQueue(it);
|
||||
}
|
||||
};
|
||||
adapter.onAddToWatchLaterClicked.subscribe(this) {
|
||||
if(it is IPlatformVideo) {
|
||||
StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(it));
|
||||
UIDialogs.toast("Added to watch later\n[${it.name}]");
|
||||
}
|
||||
};
|
||||
adapter.onLongPress.subscribe(this) {
|
||||
if (it is IPlatformVideo) {
|
||||
showVideoOptionsOverlay(it)
|
||||
|
@ -135,6 +143,7 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
|||
adapter.onChannelClicked.remove(this);
|
||||
adapter.onAddToClicked.remove(this);
|
||||
adapter.onAddToQueueClicked.remove(this);
|
||||
adapter.onAddToWatchLaterClicked.remove(this);
|
||||
adapter.onLongPress.remove(this);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,8 +12,10 @@ import com.futo.platformplayer.*
|
|||
import com.futo.platformplayer.downloads.VideoDownload
|
||||
import com.futo.platformplayer.downloads.VideoLocal
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.models.Playlist
|
||||
import com.futo.platformplayer.states.StateDownloads
|
||||
import com.futo.platformplayer.states.StatePlayer
|
||||
import com.futo.platformplayer.states.StatePlaylists
|
||||
import com.futo.platformplayer.views.AnyInsertedAdapterView
|
||||
import com.futo.platformplayer.views.AnyInsertedAdapterView.Companion.asAnyWithTop
|
||||
import com.futo.platformplayer.views.others.ProgressBar
|
||||
|
@ -143,6 +145,7 @@ class DownloadsFragment : MainFragment() {
|
|||
|
||||
val activeDownloads = StateDownloads.instance.getDownloading();
|
||||
val playlists = StateDownloads.instance.getCachedPlaylists();
|
||||
val watchLaterDownload = StateDownloads.instance.getWatchLaterDescriptor();
|
||||
val downloaded = StateDownloads.instance.getDownloadedVideos()
|
||||
.filter { it.groupType != VideoDownload.GROUP_PLAYLIST || it.groupID == null || !StateDownloads.instance.hasCachedPlaylist(it.groupID!!) };
|
||||
|
||||
|
@ -157,16 +160,28 @@ class DownloadsFragment : MainFragment() {
|
|||
_listActiveDownloads.addView(view);
|
||||
}
|
||||
|
||||
if(playlists.isEmpty())
|
||||
if(playlists.isEmpty() && watchLaterDownload == null)
|
||||
_listPlaylistsContainer.visibility = GONE;
|
||||
else {
|
||||
_listPlaylistsContainer.visibility = VISIBLE;
|
||||
_listPlaylistsMeta.text = "(${playlists.size} ${context.getString(R.string.playlists).lowercase()}, ${playlists.sumOf { it.playlist.videos.size }} ${context.getString(R.string.videos).lowercase()})";
|
||||
|
||||
val watchLater = if(watchLaterDownload != null) StatePlaylists.instance.getWatchLater() else listOf();
|
||||
|
||||
_listPlaylistsMeta.text = "(${playlists.size + (if(watchLaterDownload != null) 1 else 0)} ${context.getString(R.string.playlists).lowercase()}, ${playlists.sumOf { it.playlist.videos.size } + watchLater.size} ${context.getString(R.string.videos).lowercase()})";
|
||||
|
||||
_listPlaylists.removeAllViews();
|
||||
for(view in playlists.map { PlaylistDownloadItem(context, it) }) {
|
||||
if(watchLaterDownload != null) {
|
||||
val pdView = PlaylistDownloadItem(context, "Watch Later", watchLater.firstOrNull()?.thumbnails?.getHQThumbnail(), "WATCHLATER");
|
||||
pdView.setOnClickListener {
|
||||
_frag.navigate<WatchLaterFragment>();
|
||||
}
|
||||
_listPlaylists.addView(pdView);
|
||||
}
|
||||
for(view in playlists.map { PlaylistDownloadItem(context, it.playlist.name, it.playlist.videos.firstOrNull()?.thumbnails?.getHQThumbnail(), it.playlist) }) {
|
||||
view.setOnClickListener {
|
||||
_frag.navigate<PlaylistFragment>(view.playlist.playlist);
|
||||
if(view.obj is Playlist) {
|
||||
_frag.navigate<PlaylistFragment>(view.obj);
|
||||
}
|
||||
};
|
||||
_listPlaylists.addView(view);
|
||||
}
|
||||
|
|
|
@ -201,14 +201,18 @@ class PlaylistFragment : MainFragment() {
|
|||
showConvertPlaylistButton();
|
||||
}
|
||||
|
||||
updateDownloadState();
|
||||
_playlist?.let {
|
||||
updateDownloadState(VideoDownload.GROUP_PLAYLIST, it.id, this::download);
|
||||
}
|
||||
}
|
||||
|
||||
fun onResume() {
|
||||
StateDownloads.instance.onDownloadsChanged.subscribe(this) {
|
||||
_fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||
try {
|
||||
updateDownloadState();
|
||||
_playlist?.let {
|
||||
updateDownloadState(VideoDownload.GROUP_PLAYLIST, it.id, this@PlaylistView::download);
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Logger.e(TAG, "Failed to update download state onDownloadedChanged.")
|
||||
}
|
||||
|
@ -217,7 +221,9 @@ class PlaylistFragment : MainFragment() {
|
|||
StateDownloads.instance.onDownloadedChanged.subscribe(this) {
|
||||
_fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||
try {
|
||||
updateDownloadState();
|
||||
_playlist?.let {
|
||||
updateDownloadState(VideoDownload.GROUP_PLAYLIST, it.id, this@PlaylistView::download);
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Logger.e(TAG, "Failed to update download state onDownloadedChanged.")
|
||||
}
|
||||
|
@ -225,6 +231,12 @@ class PlaylistFragment : MainFragment() {
|
|||
};
|
||||
}
|
||||
|
||||
private fun download() {
|
||||
_playlist?.let {
|
||||
UISlideOverlays.showDownloadPlaylistOverlay(it, overlayContainer);
|
||||
}
|
||||
}
|
||||
|
||||
fun onPause() {
|
||||
StateDownloads.instance.onDownloadsChanged.remove(this);
|
||||
StateDownloads.instance.onDownloadedChanged.remove(this);
|
||||
|
@ -268,43 +280,6 @@ class PlaylistFragment : MainFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateDownloadState() {
|
||||
val playlist = _playlist ?: return;
|
||||
val isDownloading = StateDownloads.instance.getDownloading().any { it.groupType == VideoDownload.GROUP_PLAYLIST && it.groupID == playlist.id };
|
||||
val isDownloaded = StateDownloads.instance.isPlaylistCached(playlist.id);
|
||||
|
||||
val dp10 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, resources.displayMetrics);
|
||||
|
||||
if(isDownloaded && !isDownloading)
|
||||
_buttonDownload.setBackgroundResource(R.drawable.background_button_round_green);
|
||||
else
|
||||
_buttonDownload.setBackgroundResource(R.drawable.background_button_round);
|
||||
|
||||
if(isDownloading) {
|
||||
_buttonDownload.setImageResource(R.drawable.ic_loader_animated);
|
||||
_buttonDownload.drawable.assume<Animatable, Unit> { it.start() };
|
||||
_buttonDownload.setOnClickListener {
|
||||
UIDialogs.showConfirmationDialog(context, context.getString(R.string.are_you_sure_you_want_to_delete_the_downloaded_videos), {
|
||||
StateDownloads.instance.deleteCachedPlaylist(playlist.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
else if(isDownloaded) {
|
||||
_buttonDownload.setImageResource(R.drawable.ic_download_off);
|
||||
_buttonDownload.setOnClickListener {
|
||||
UIDialogs.showConfirmationDialog(context, context.getString(R.string.are_you_sure_you_want_to_delete_the_downloaded_videos), {
|
||||
StateDownloads.instance.deleteCachedPlaylist(playlist.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
_buttonDownload.setImageResource(R.drawable.ic_download);
|
||||
_buttonDownload.setOnClickListener {
|
||||
UISlideOverlays.showDownloadPlaylistOverlay(playlist, overlayContainer);
|
||||
}
|
||||
}
|
||||
_buttonDownload.setPadding(dp10.toInt());
|
||||
}
|
||||
|
||||
override fun canEdit(): Boolean { return _playlist != null; }
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import android.view.View
|
|||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
import android.view.WindowManager
|
||||
import android.webkit.WebView
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
|
@ -124,6 +125,7 @@ import com.futo.platformplayer.views.overlays.LiveChatOverlay
|
|||
import com.futo.platformplayer.views.overlays.QueueEditorOverlay
|
||||
import com.futo.platformplayer.views.overlays.RepliesOverlay
|
||||
import com.futo.platformplayer.views.overlays.SupportOverlay
|
||||
import com.futo.platformplayer.views.overlays.WebviewOverlay
|
||||
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuButtonList
|
||||
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuGroup
|
||||
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem
|
||||
|
@ -244,6 +246,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
private val _container_content_replies: RepliesOverlay;
|
||||
private val _container_content_description: DescriptionOverlay;
|
||||
private val _container_content_liveChat: LiveChatOverlay;
|
||||
private val _container_content_browser: WebviewOverlay;
|
||||
private val _container_content_support: SupportOverlay;
|
||||
|
||||
private var _container_content_current: View;
|
||||
|
@ -349,7 +352,8 @@ class VideoDetailView : ConstraintLayout {
|
|||
_container_content_replies = findViewById(R.id.videodetail_container_replies);
|
||||
_container_content_description = findViewById(R.id.videodetail_container_description);
|
||||
_container_content_liveChat = findViewById(R.id.videodetail_container_livechat);
|
||||
_container_content_support = findViewById(R.id.videodetail_container_support)
|
||||
_container_content_support = findViewById(R.id.videodetail_container_support);
|
||||
_container_content_browser = findViewById(R.id.videodetail_container_webview)
|
||||
|
||||
_textComments = findViewById(R.id.text_comments);
|
||||
_addCommentView = findViewById(R.id.add_comment_view);
|
||||
|
@ -624,6 +628,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
_container_content_queue.onClose.subscribe { switchContentView(_container_content_main); };
|
||||
_container_content_replies.onClose.subscribe { switchContentView(_container_content_main); };
|
||||
_container_content_support.onClose.subscribe { switchContentView(_container_content_main); };
|
||||
_container_content_browser.onClose.subscribe { switchContentView(_container_content_main); };
|
||||
|
||||
_description_viewMore.setOnClickListener {
|
||||
switchContentView(_container_content_description);
|
||||
|
@ -644,6 +649,20 @@ class VideoDetailView : ConstraintLayout {
|
|||
|
||||
_container_content_current = _container_content_main;
|
||||
|
||||
_commentsList.onAuthorClick.subscribe { c ->
|
||||
if (c !is PolycentricPlatformComment) {
|
||||
return@subscribe;
|
||||
}
|
||||
|
||||
Logger.i(TAG, "onAuthorClick: " + c.author.id.value);
|
||||
if(c.author.id.value?.startsWith("polycentric://") ?: false) {
|
||||
val navUrl = "https://harbor.social/" + c.author.id.value?.substring("polycentric://".length);
|
||||
//fragment.navigate<BrowserFragment>(navUrl);
|
||||
//fragment.minimizeVideoDetail();
|
||||
_container_content_browser.goto(navUrl);
|
||||
switchContentView(_container_content_browser);
|
||||
}
|
||||
};
|
||||
_commentsList.onRepliesClick.subscribe { c ->
|
||||
val replyCount = c.replyCount ?: 0;
|
||||
var metadata = "";
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.futo.platformplayer.fragment.mainactivity.main
|
||||
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
|
@ -8,10 +9,17 @@ import android.widget.ImageButton
|
|||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.setPadding
|
||||
import com.bumptech.glide.Glide
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.UIDialogs
|
||||
import com.futo.platformplayer.UISlideOverlays
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||
import com.futo.platformplayer.assume
|
||||
import com.futo.platformplayer.downloads.VideoDownload
|
||||
import com.futo.platformplayer.images.GlideHelper.Companion.crossfade
|
||||
import com.futo.platformplayer.states.StateDownloads
|
||||
import com.futo.platformplayer.states.StatePlaylists
|
||||
import com.futo.platformplayer.views.lists.VideoListEditorView
|
||||
|
||||
abstract class VideoListEditorView : LinearLayout {
|
||||
|
@ -85,6 +93,44 @@ abstract class VideoListEditorView : LinearLayout {
|
|||
|
||||
}
|
||||
|
||||
protected fun updateDownloadState(groupType: String, playlistId: String, onDownload: ()->Unit) {
|
||||
//val playlist = _playlist ?: return;
|
||||
val isDownloading = StateDownloads.instance.getDownloading().any { it.groupType == groupType && it.groupID == playlistId };
|
||||
val isDownloaded = StateDownloads.instance.isPlaylistCached(playlistId);
|
||||
|
||||
val dp10 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, resources.displayMetrics);
|
||||
|
||||
if(isDownloaded && !isDownloading)
|
||||
_buttonDownload.setBackgroundResource(R.drawable.background_button_round_green);
|
||||
else
|
||||
_buttonDownload.setBackgroundResource(R.drawable.background_button_round);
|
||||
|
||||
if(isDownloading) {
|
||||
_buttonDownload.setImageResource(R.drawable.ic_loader_animated);
|
||||
_buttonDownload.drawable.assume<Animatable, Unit> { it.start() };
|
||||
_buttonDownload.setOnClickListener {
|
||||
UIDialogs.showConfirmationDialog(context, context.getString(R.string.are_you_sure_you_want_to_delete_the_downloaded_videos), {
|
||||
StateDownloads.instance.deleteCachedPlaylist(playlistId);
|
||||
});
|
||||
}
|
||||
}
|
||||
else if(isDownloaded) {
|
||||
_buttonDownload.setImageResource(R.drawable.ic_download_off);
|
||||
_buttonDownload.setOnClickListener {
|
||||
UIDialogs.showConfirmationDialog(context, context.getString(R.string.are_you_sure_you_want_to_delete_the_downloaded_videos), {
|
||||
StateDownloads.instance.deleteCachedPlaylist(playlistId);
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
_buttonDownload.setImageResource(R.drawable.ic_download);
|
||||
_buttonDownload.setOnClickListener {
|
||||
onDownload();
|
||||
//UISlideOverlays.showDownloadPlaylistOverlay(playlist, overlayContainer);
|
||||
}
|
||||
}
|
||||
_buttonDownload.setPadding(dp10.toInt());
|
||||
}
|
||||
|
||||
protected fun setName(name: String?) {
|
||||
_textName.text = name ?: "";
|
||||
|
|
|
@ -5,10 +5,17 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.futo.platformplayer.UISlideOverlays
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
|
||||
import com.futo.platformplayer.downloads.VideoDownload
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.states.StateDownloads
|
||||
import com.futo.platformplayer.states.StatePlayer
|
||||
import com.futo.platformplayer.states.StatePlaylists
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class WatchLaterFragment : MainFragment() {
|
||||
override val isMainView : Boolean = true;
|
||||
|
@ -28,6 +35,11 @@ class WatchLaterFragment : MainFragment() {
|
|||
return view;
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
_view?.onResume();
|
||||
}
|
||||
|
||||
override fun onDestroyMainView() {
|
||||
super.onDestroyMainView();
|
||||
_view = null;
|
||||
|
@ -45,6 +57,34 @@ class WatchLaterFragment : MainFragment() {
|
|||
fun onShown() {
|
||||
setName("Watch Later");
|
||||
setVideos(StatePlaylists.instance.getWatchLater(), true);
|
||||
|
||||
setButtonDownloadVisible(true);
|
||||
updateDownloadState(VideoDownload.GROUP_WATCHLATER, VideoDownload.GROUP_WATCHLATER, this@WatchLaterView::download);
|
||||
}
|
||||
|
||||
fun onResume(){
|
||||
StateDownloads.instance.onDownloadsChanged.subscribe(this) {
|
||||
_fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||
try {
|
||||
updateDownloadState(VideoDownload.GROUP_WATCHLATER, VideoDownload.GROUP_WATCHLATER, this@WatchLaterView::download);
|
||||
} catch (e: Throwable) {
|
||||
Logger.e(TAG, "Failed to update download state onDownloadedChanged.")
|
||||
}
|
||||
}
|
||||
};
|
||||
StateDownloads.instance.onDownloadedChanged.subscribe(this) {
|
||||
_fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||
try {
|
||||
updateDownloadState(VideoDownload.GROUP_WATCHLATER, VideoDownload.GROUP_WATCHLATER, this@WatchLaterView::download);
|
||||
} catch (e: Throwable) {
|
||||
Logger.e(TAG, "Failed to update download state onDownloadedChanged.")
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fun download(){
|
||||
UISlideOverlays.showDownloadWatchlaterOverlay(overlayContainer);
|
||||
}
|
||||
|
||||
override fun onPlayAllClick() {
|
||||
|
@ -76,6 +116,7 @@ class WatchLaterFragment : MainFragment() {
|
|||
}
|
||||
|
||||
companion object {
|
||||
val TAG = "WatchLaterFragment";
|
||||
fun newInstance() = WatchLaterFragment().apply {}
|
||||
}
|
||||
}
|
|
@ -270,7 +270,7 @@ class DownloadService : Service() {
|
|||
|
||||
fun closeDownloadSession() {
|
||||
Logger.i(TAG, "closeDownloadSession");
|
||||
stopForeground(STOP_FOREGROUND_DETACH);
|
||||
stopForeground(STOP_FOREGROUND_REMOVE);
|
||||
_notificationManager?.cancel(DOWNLOAD_NOTIF_ID);
|
||||
stopService();
|
||||
_started = false;
|
||||
|
|
|
@ -188,7 +188,7 @@ class ExportingService : Service() {
|
|||
|
||||
fun closeExportSession() {
|
||||
Logger.i(TAG, "closeExportSession");
|
||||
stopForeground(STOP_FOREGROUND_DETACH);
|
||||
stopForeground(STOP_FOREGROUND_REMOVE);
|
||||
_notificationManager?.cancel(EXPORT_NOTIF_ID);
|
||||
stopService();
|
||||
_started = false;
|
||||
|
|
|
@ -97,6 +97,9 @@ class StateDownloads {
|
|||
}
|
||||
}
|
||||
|
||||
fun getWatchLaterDescriptor(): PlaylistDownloadDescriptor? {
|
||||
return _downloadPlaylists.getItems().find { it.id == VideoDownload.GROUP_WATCHLATER };
|
||||
}
|
||||
fun getCachedPlaylists(): List<PlaylistDownloaded> {
|
||||
return _downloadPlaylists.getItems()
|
||||
.map { Pair(it, StatePlaylists.instance.getPlaylist(it.id)) }
|
||||
|
@ -124,10 +127,18 @@ class StateDownloads {
|
|||
val pdl = getPlaylistDownload(id);
|
||||
if(pdl != null)
|
||||
_downloadPlaylists.delete(pdl);
|
||||
getDownloading().filter { it.groupType == VideoDownload.GROUP_PLAYLIST && it.groupID == id }
|
||||
.forEach { removeDownload(it) };
|
||||
getDownloadedVideos().filter { it.groupType == VideoDownload.GROUP_PLAYLIST && it.groupID == id }
|
||||
.forEach { deleteCachedVideo(it.id) };
|
||||
if(id == VideoDownload.GROUP_WATCHLATER) {
|
||||
getDownloading().filter { it.groupType == VideoDownload.GROUP_WATCHLATER && it.groupID == id }
|
||||
.forEach { removeDownload(it) };
|
||||
getDownloadedVideos().filter { it.groupType == VideoDownload.GROUP_WATCHLATER && it.groupID == id }
|
||||
.forEach { deleteCachedVideo(it.id) };
|
||||
}
|
||||
else {
|
||||
getDownloading().filter { it.groupType == VideoDownload.GROUP_PLAYLIST && it.groupID == id }
|
||||
.forEach { removeDownload(it) };
|
||||
getDownloadedVideos().filter { it.groupType == VideoDownload.GROUP_PLAYLIST && it.groupID == id }
|
||||
.forEach { deleteCachedVideo(it.id) };
|
||||
}
|
||||
}
|
||||
|
||||
fun getDownloadedVideos(): List<VideoLocal> {
|
||||
|
@ -192,9 +203,59 @@ class StateDownloads {
|
|||
else
|
||||
Logger.v(TAG, "Offline playlist [${playlist.playlist.name}] is up to date");
|
||||
}
|
||||
val downloadWatchLater = getWatchLaterDescriptor();
|
||||
if(downloadWatchLater != null) {
|
||||
continueDownloadWatchLater(downloadWatchLater);
|
||||
}
|
||||
return hasChanged;
|
||||
}
|
||||
|
||||
fun continueDownloadWatchLater(playlistDownload: PlaylistDownloadDescriptor) {
|
||||
var hasNew = false;
|
||||
val watchLater = StatePlaylists.instance.getWatchLater();
|
||||
for(item in watchLater) {
|
||||
val existing = getCachedVideo(item.id);
|
||||
|
||||
if(!playlistDownload.shouldDownload(item)) {
|
||||
Logger.i(TAG, "Not downloading for watchlater [${playlistDownload.id}] Video [${item.name}]:${item.url}")
|
||||
continue;
|
||||
}
|
||||
if(existing == null) {
|
||||
val ongoingDownload = getDownloading().find { it.id.value == item.id.value && it.id.value != null };
|
||||
if(ongoingDownload != null) {
|
||||
Logger.i(TAG, "New watchlater video (already downloading) ${item.name}");
|
||||
ongoingDownload.groupID = VideoDownload.GROUP_WATCHLATER;
|
||||
ongoingDownload.groupType = VideoDownload.GROUP_WATCHLATER;
|
||||
}
|
||||
else {
|
||||
Logger.i(TAG, "New watchlater video ${item.name}");
|
||||
download(VideoDownload(item, playlistDownload.targetPxCount, playlistDownload.targetBitrate)
|
||||
.withGroup(VideoDownload.GROUP_PLAYLIST, VideoDownload.GROUP_WATCHLATER), false);
|
||||
hasNew = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Logger.i(TAG, "New watchlater video (already downloaded) ${item.name}");
|
||||
if(existing.groupID == null) {
|
||||
existing.groupID = VideoDownload.GROUP_WATCHLATER;
|
||||
existing.groupType = VideoDownload.GROUP_WATCHLATER;
|
||||
synchronized(_downloadedSet) {
|
||||
_downloadedSet.add(existing.id);
|
||||
}
|
||||
_downloaded.save(existing);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(watchLater.isNotEmpty() && Settings.instance.downloads.shouldDownload()) {
|
||||
if(hasNew) {
|
||||
UIDialogs.toast("Downloading [Watch Later]")
|
||||
StateApp.withContext {
|
||||
DownloadService.getOrCreateService(it);
|
||||
}
|
||||
}
|
||||
onDownloadsChanged.emit();
|
||||
}
|
||||
}
|
||||
fun continueDownload(playlistDownload: PlaylistDownloadDescriptor, playlist: Playlist) {
|
||||
var hasNew = false;
|
||||
for(item in playlist.videos) {
|
||||
|
@ -240,6 +301,11 @@ class StateDownloads {
|
|||
onDownloadsChanged.emit();
|
||||
}
|
||||
}
|
||||
fun downloadWatchLater(targetPixelCount: Long?, targetBitrate: Long?) {
|
||||
val playlistDownload = PlaylistDownloadDescriptor(VideoDownload.GROUP_WATCHLATER, targetPixelCount, targetBitrate);
|
||||
_downloadPlaylists.save(playlistDownload);
|
||||
continueDownloadWatchLater(playlistDownload);
|
||||
}
|
||||
fun download(playlist: Playlist, targetPixelcount: Long?, targetBitrate: Long?) {
|
||||
val playlistDownload = PlaylistDownloadDescriptor(playlist.id, targetPixelcount, targetBitrate);
|
||||
_downloadPlaylists.save(playlistDownload);
|
||||
|
|
|
@ -20,6 +20,7 @@ class ChannelViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifec
|
|||
val onChannelClicked = Event1<PlatformAuthorLink>();
|
||||
val onAddToClicked = Event1<IPlatformContent>();
|
||||
val onAddToQueueClicked = Event1<IPlatformContent>();
|
||||
val onAddToWatchLaterClicked = Event1<IPlatformContent>();
|
||||
val onLongPress = Event1<IPlatformContent>();
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
|
@ -56,6 +57,7 @@ class ChannelViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifec
|
|||
onChannelClicked.subscribe(this@ChannelViewPagerAdapter.onChannelClicked::emit);
|
||||
onAddToClicked.subscribe(this@ChannelViewPagerAdapter.onAddToClicked::emit);
|
||||
onAddToQueueClicked.subscribe(this@ChannelViewPagerAdapter.onAddToQueueClicked::emit);
|
||||
onAddToWatchLaterClicked.subscribe(this@ChannelViewPagerAdapter.onAddToWatchLaterClicked::emit);
|
||||
onLongPress.subscribe(this@ChannelViewPagerAdapter.onLongPress::emit);
|
||||
};
|
||||
1 -> ChannelListFragment.newInstance().apply { onClickChannel.subscribe(onChannelClicked::emit) };
|
||||
|
|
|
@ -48,6 +48,7 @@ class CommentViewHolder : ViewHolder {
|
|||
|
||||
var onRepliesClick = Event1<IPlatformComment>();
|
||||
var onDelete = Event1<IPlatformComment>();
|
||||
var onAuthorClick = Event1<IPlatformComment>();
|
||||
var comment: IPlatformComment? = null
|
||||
private set;
|
||||
|
||||
|
@ -95,6 +96,19 @@ class CommentViewHolder : ViewHolder {
|
|||
StatePolycentric.instance.updateLikeMap(c.reference, args.hasLiked, args.hasDisliked)
|
||||
};
|
||||
|
||||
_creatorThumbnail.onClick.subscribe {
|
||||
val c = comment ?: return@subscribe;
|
||||
onAuthorClick.emit(c);
|
||||
}
|
||||
|
||||
_creatorThumbnail.setOnClickListener {
|
||||
val c = comment ?: return@setOnClickListener;
|
||||
onAuthorClick.emit(c);
|
||||
}
|
||||
_textAuthor.setOnClickListener {
|
||||
val c = comment ?: return@setOnClickListener;
|
||||
onAuthorClick.emit(c);
|
||||
}
|
||||
_buttonReplies.onClick.subscribe {
|
||||
val c = comment ?: return@subscribe;
|
||||
onRepliesClick.emit(c);
|
||||
|
|
|
@ -53,9 +53,10 @@ class CommentWithReferenceViewHolder : ViewHolder {
|
|||
hideLikesDislikesReplies()
|
||||
}
|
||||
|
||||
var onRepliesClick = Event1<IPlatformComment>();
|
||||
var onDelete = Event1<IPlatformComment>();
|
||||
var onClick = Event1<IPlatformComment>();
|
||||
val onRepliesClick = Event1<IPlatformComment>();
|
||||
val onDelete = Event1<IPlatformComment>();
|
||||
val onClick = Event1<IPlatformComment>();
|
||||
val onAuthorClick = Event1<IPlatformComment>();
|
||||
var comment: IPlatformComment? = null
|
||||
private set;
|
||||
|
||||
|
@ -99,6 +100,14 @@ class CommentWithReferenceViewHolder : ViewHolder {
|
|||
StatePolycentric.instance.updateLikeMap(c.reference, args.hasLiked, args.hasDisliked)
|
||||
};
|
||||
|
||||
_creatorThumbnail.onClick.subscribe {
|
||||
val c = comment ?: return@subscribe;
|
||||
onAuthorClick.emit(c);
|
||||
}
|
||||
_textAuthor.setOnClickListener {
|
||||
val c = comment ?: return@setOnClickListener;
|
||||
onAuthorClick.emit(c);
|
||||
}
|
||||
_buttonReplies.onClick.subscribe {
|
||||
val c = comment ?: return@subscribe;
|
||||
onRepliesClick.emit(c);
|
||||
|
|
|
@ -39,6 +39,7 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader<ContentPreviewVi
|
|||
val onChannelClicked = Event1<PlatformAuthorLink>();
|
||||
val onAddToClicked = Event1<IPlatformContent>();
|
||||
val onAddToQueueClicked = Event1<IPlatformContent>();
|
||||
val onAddToWatchLaterClicked = Event1<IPlatformContent>();
|
||||
val onLongPress = Event1<IPlatformContent>();
|
||||
|
||||
private var _taskLoadContent = TaskHandler<Pair<ContentPreviewViewHolder, IPlatformContent>, Pair<ContentPreviewViewHolder, IPlatformContentDetails>>(
|
||||
|
@ -95,6 +96,7 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader<ContentPreviewVi
|
|||
this.onChannelClicked.subscribe(this@PreviewContentListAdapter.onChannelClicked::emit);
|
||||
this.onAddToClicked.subscribe(this@PreviewContentListAdapter.onAddToClicked::emit);
|
||||
this.onAddToQueueClicked.subscribe(this@PreviewContentListAdapter.onAddToQueueClicked::emit);
|
||||
this.onAddToWatchLaterClicked.subscribe(this@PreviewContentListAdapter.onAddToWatchLaterClicked::emit);
|
||||
};
|
||||
private fun createLockedViewHolder(viewGroup: ViewGroup): PreviewLockedViewHolder = PreviewLockedViewHolder(viewGroup, _feedStyle).apply {
|
||||
this.onLockedUrlClicked.subscribe(this@PreviewContentListAdapter.onUrlClicked::emit);
|
||||
|
@ -106,6 +108,7 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader<ContentPreviewVi
|
|||
this.onChannelClicked.subscribe(this@PreviewContentListAdapter.onChannelClicked::emit);
|
||||
this.onAddToClicked.subscribe(this@PreviewContentListAdapter.onAddToClicked::emit);
|
||||
this.onAddToQueueClicked.subscribe(this@PreviewContentListAdapter.onAddToQueueClicked::emit);
|
||||
this.onAddToWatchLaterClicked.subscribe(this@PreviewContentListAdapter.onAddToWatchLaterClicked::emit);
|
||||
this.onLongPress.subscribe(this@PreviewContentListAdapter.onLongPress::emit);
|
||||
};
|
||||
private fun createPlaylistViewHolder(viewGroup: ViewGroup): PreviewPlaylistViewHolder = PreviewPlaylistViewHolder(viewGroup, _feedStyle).apply {
|
||||
|
@ -161,6 +164,7 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader<ContentPreviewVi
|
|||
onChannelClicked.clear();
|
||||
onAddToClicked.clear();
|
||||
onAddToQueueClicked.clear();
|
||||
onAddToWatchLaterClicked.clear();
|
||||
}
|
||||
|
||||
private fun previewContentDetails(viewHolder: ContentPreviewViewHolder, videoDetails: IPlatformContentDetails?) {
|
||||
|
|
|
@ -19,6 +19,7 @@ class PreviewNestedVideoViewHolder : ContentPreviewViewHolder {
|
|||
val onChannelClicked = Event1<PlatformAuthorLink>();
|
||||
val onAddToClicked = Event1<IPlatformVideo>();
|
||||
val onAddToQueueClicked = Event1<IPlatformVideo>();
|
||||
val onAddToWatchLaterClicked = Event1<IPlatformVideo>();
|
||||
|
||||
override val content: IPlatformContent? get() = view.content;
|
||||
private val view: PreviewNestedVideoView get() = itemView as PreviewNestedVideoView;
|
||||
|
@ -31,6 +32,7 @@ class PreviewNestedVideoViewHolder : ContentPreviewViewHolder {
|
|||
view.onChannelClicked.subscribe(onChannelClicked::emit);
|
||||
view.onAddToClicked.subscribe(onAddToClicked::emit);
|
||||
view.onAddToQueueClicked.subscribe(onAddToQueueClicked::emit);
|
||||
view.onAddToWatchLaterClicked.subscribe(onAddToWatchLaterClicked::emit);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ open class PreviewVideoView : LinearLayout {
|
|||
protected val _layoutDownloaded: FrameLayout;
|
||||
|
||||
protected val _button_add_to_queue : View;
|
||||
protected val _button_add_to_watch_later : View;
|
||||
protected val _button_add_to : View;
|
||||
|
||||
protected val _exoPlayer: PlayerManager?;
|
||||
|
@ -80,6 +81,7 @@ open class PreviewVideoView : LinearLayout {
|
|||
val onChannelClicked = Event1<PlatformAuthorLink>();
|
||||
val onAddToClicked = Event1<IPlatformVideo>();
|
||||
val onAddToQueueClicked = Event1<IPlatformVideo>();
|
||||
val onAddToWatchLaterClicked = Event1<IPlatformVideo>();
|
||||
|
||||
var currentVideo: IPlatformVideo? = null
|
||||
private set
|
||||
|
@ -104,6 +106,7 @@ open class PreviewVideoView : LinearLayout {
|
|||
_containerDuration = findViewById(R.id.thumbnail_duration_container);
|
||||
_containerLive = findViewById(R.id.thumbnail_live_container);
|
||||
_button_add_to_queue = findViewById(R.id.button_add_to_queue);
|
||||
_button_add_to_watch_later = findViewById(R.id.button_add_to_watch_later);
|
||||
_button_add_to = findViewById(R.id.button_add_to);
|
||||
_imageNeopassChannel = findViewById(R.id.image_neopass_channel);
|
||||
_layoutDownloaded = findViewById(R.id.layout_downloaded);
|
||||
|
@ -124,7 +127,7 @@ open class PreviewVideoView : LinearLayout {
|
|||
_textVideoMetadata.setOnClickListener { currentVideo?.let { onChannelClicked.emit(it.author) } };
|
||||
_button_add_to.setOnClickListener { currentVideo?.let { onAddToClicked.emit(it) } };
|
||||
_button_add_to_queue.setOnClickListener { currentVideo?.let { onAddToQueueClicked.emit(it) } };
|
||||
|
||||
_button_add_to_watch_later.setOnClickListener { currentVideo?.let { onAddToWatchLaterClicked.emit(it); } }
|
||||
}
|
||||
|
||||
protected open fun inflate(feedStyle: FeedStyle) {
|
||||
|
|
|
@ -18,6 +18,7 @@ class PreviewVideoViewHolder : ContentPreviewViewHolder {
|
|||
val onChannelClicked = Event1<PlatformAuthorLink>();
|
||||
val onAddToClicked = Event1<IPlatformVideo>();
|
||||
val onAddToQueueClicked = Event1<IPlatformVideo>();
|
||||
val onAddToWatchLaterClicked = Event1<IPlatformVideo>();
|
||||
val onLongPress = Event1<IPlatformVideo>();
|
||||
|
||||
//val context: Context;
|
||||
|
@ -34,6 +35,7 @@ class PreviewVideoViewHolder : ContentPreviewViewHolder {
|
|||
view.onChannelClicked.subscribe(onChannelClicked::emit);
|
||||
view.onAddToClicked.subscribe(onAddToClicked::emit);
|
||||
view.onAddToQueueClicked.subscribe(onAddToQueueClicked::emit);
|
||||
view.onAddToWatchLaterClicked.subscribe(onAddToWatchLaterClicked::emit);
|
||||
view.onLongPress.subscribe(onLongPress::emit);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,16 +9,16 @@ import com.futo.platformplayer.R
|
|||
import com.futo.platformplayer.images.GlideHelper.Companion.crossfade
|
||||
import com.futo.platformplayer.models.PlaylistDownloaded
|
||||
|
||||
class PlaylistDownloadItem(context: Context, val playlist: PlaylistDownloaded): LinearLayout(context) {
|
||||
class PlaylistDownloadItem(context: Context, playlistName: String, playlistThumbnail: String?, val obj: Any): LinearLayout(context) {
|
||||
init { inflate(context, R.layout.list_downloaded_playlist, this) }
|
||||
|
||||
var imageView: ImageView = findViewById(R.id.downloaded_playlist_image);
|
||||
var imageText: TextView = findViewById(R.id.downloaded_playlist_name);
|
||||
|
||||
init {
|
||||
imageText.text = playlist.playlist.name;
|
||||
imageText.text = playlistName;
|
||||
Glide.with(imageView)
|
||||
.load(playlist.playlist.videos.firstOrNull()?.thumbnails?.getHQThumbnail())
|
||||
.load(playlistThumbnail)
|
||||
.crossfade()
|
||||
.into(imageView);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package com.futo.platformplayer.views.overlays
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.webkit.WebView
|
||||
import android.widget.LinearLayout
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.constructs.Event0
|
||||
import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.views.SupportView
|
||||
|
||||
class WebviewOverlay : LinearLayout {
|
||||
val onClose = Event0();
|
||||
|
||||
private val _topbar: OverlayTopbar;
|
||||
private val _webview: WebView;
|
||||
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
|
||||
inflate(context, R.layout.overlay_webview, this)
|
||||
_topbar = findViewById(R.id.topbar);
|
||||
_webview = findViewById(R.id.webview);
|
||||
_webview.settings.javaScriptEnabled = true;
|
||||
|
||||
_topbar.onClose.subscribe(this, onClose::emit);
|
||||
}
|
||||
|
||||
fun goto(url: String) {
|
||||
Logger.i("WebviewOverlay", "Loading [${url}]");
|
||||
_topbar.setInfo(url, "");
|
||||
_webview.loadUrl(url);
|
||||
}
|
||||
|
||||
fun cleanup() {
|
||||
_topbar.onClose.remove(this);
|
||||
}
|
||||
}
|
|
@ -88,6 +88,7 @@ class CommentsList : ConstraintLayout {
|
|||
private val _layoutScrollToTop: FrameLayout;
|
||||
|
||||
var onRepliesClick = Event1<IPlatformComment>();
|
||||
var onAuthorClick = Event1<IPlatformComment>();
|
||||
var onCommentsLoaded = Event1<Int>();
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
|
@ -120,6 +121,7 @@ class CommentsList : ConstraintLayout {
|
|||
childViewHolderFactory = { viewGroup, _ ->
|
||||
val holder = CommentViewHolder(viewGroup);
|
||||
holder.onRepliesClick.subscribe { c -> onRepliesClick.emit(c) };
|
||||
holder.onAuthorClick.subscribe { c -> onAuthorClick.emit(c) };
|
||||
holder.onDelete.subscribe(::onDelete);
|
||||
return@InsertedViewAdapterWithLoader holder;
|
||||
}
|
||||
|
|
10
app/src/main/res/drawable/ic_schedule_400.xml
Normal file
10
app/src/main/res/drawable/ic_schedule_400.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M612,668L668,612L520,464L520,280L440,280L440,496L612,668ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM480,800Q613,800 706.5,706.5Q800,613 800,480Q800,347 706.5,253.5Q613,160 480,160Q347,160 253.5,253.5Q160,347 160,480Q160,613 253.5,706.5Q347,800 480,800Z"/>
|
||||
</vector>
|
|
@ -26,7 +26,7 @@
|
|||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="28dp"
|
||||
android:textSize="22dp"
|
||||
android:layout_marginTop="-2dp"
|
||||
android:fontFamily="@font/inter_light"
|
||||
android:text="Grayjay"
|
||||
|
|
|
@ -542,6 +542,12 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<com.futo.platformplayer.views.overlays.WebviewOverlay
|
||||
android:id="@+id/videodetail_container_webview"
|
||||
android:visibility="gone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<com.futo.platformplayer.views.overlays.QueueEditorOverlay
|
||||
android:id="@+id/videodetail_container_queue"
|
||||
android:visibility="gone"
|
||||
|
|
|
@ -226,10 +226,18 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_add_to_watch_later"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/edit_text_background"
|
||||
app:srcCompat="@drawable/ic_clock_white" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_add_to_queue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:background="@drawable/edit_text_background"
|
||||
android:contentDescription="@string/add_to_queue"
|
||||
|
@ -242,20 +250,18 @@
|
|||
<LinearLayout
|
||||
android:id="@+id/button_add_to"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:background="@drawable/edit_text_background"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:padding="4dp">
|
||||
|
||||
<ImageButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:contentDescription="@string/options"
|
||||
app:srcCompat="@drawable/ic_add_white_8dp" />
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="16dp"
|
||||
android:paddingTop="1dp"
|
||||
android:src="@drawable/ic_settings" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
@ -262,45 +262,53 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:orientation="horizontal"
|
||||
android:paddingEnd="6dp">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_add_to_watch_later"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@drawable/edit_text_background"
|
||||
app:srcCompat="@drawable/ic_clock_white" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_add_to_queue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:paddingTop="7dp"
|
||||
android:background="@drawable/edit_text_background"
|
||||
android:contentDescription="@string/add_to_queue"
|
||||
android:paddingStart="6dp"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingEnd="5dp"
|
||||
android:paddingBottom="3dp"
|
||||
app:srcCompat="@drawable/ic_queue_16dp"
|
||||
android:background="@drawable/edit_text_background"
|
||||
android:contentDescription="@string/add_to_queue" />
|
||||
app:srcCompat="@drawable/ic_queue_16dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/button_add_to"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@drawable/edit_text_background"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:background="@drawable/edit_text_background"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:padding="4dp">
|
||||
<ImageButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
app:srcCompat="@drawable/ic_add_white_8dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:contentDescription="@string/options" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="16dp"
|
||||
android:paddingTop="1dp"
|
||||
android:src="@drawable/ic_settings" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/options"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:background="@color/transparent"
|
||||
android:textSize="12dp"
|
||||
android:textColor="@color/white"
|
||||
android:fontFamily="@font/inter_light"
|
||||
android:layout_marginEnd="4dp"/>
|
||||
android:text="@string/options"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="12dp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -150,14 +150,11 @@
|
|||
android:padding="5dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
<ImageButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
app:srcCompat="@drawable/ic_add_white_8dp"
|
||||
android:background="@color/transparent"
|
||||
android:layout_marginStart="4dp"
|
||||
android:contentDescription="@string/options" />
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="16dp"
|
||||
android:paddingTop="1dp"
|
||||
android:src="@drawable/ic_settings" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -180,13 +177,28 @@
|
|||
android:paddingBottom="2dp"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingRight="7dp"
|
||||
android:layout_marginLeft="7dp"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="@drawable/edit_text_background"
|
||||
android:contentDescription="@string/add_to_queue"
|
||||
app:layout_constraintLeft_toRightOf="@id/button_add_to"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_add_to_watch_later"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="27dp"
|
||||
android:src="@drawable/ic_clock_white"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingBottom="6dp"
|
||||
android:paddingLeft="9dp"
|
||||
android:paddingRight="9dp"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="@drawable/edit_text_background"
|
||||
app:layout_constraintLeft_toRightOf="@id/button_add_to_queue"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_video_name"
|
||||
android:layout_width="fill_parent"
|
||||
|
|
|
@ -206,6 +206,7 @@
|
|||
</LinearLayout>
|
||||
|
||||
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_add_to_queue"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -215,13 +216,28 @@
|
|||
android:paddingBottom="2dp"
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingRight="7dp"
|
||||
android:layout_marginLeft="7dp"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="@drawable/edit_text_background"
|
||||
android:contentDescription="@string/add_to_queue"
|
||||
app:layout_constraintLeft_toRightOf="@id/button_add_to"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_add_to_watch_later"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="27dp"
|
||||
android:src="@drawable/ic_clock_white"
|
||||
android:paddingTop="7dp"
|
||||
android:paddingBottom="6dp"
|
||||
android:paddingLeft="9dp"
|
||||
android:paddingRight="9dp"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="@drawable/edit_text_background"
|
||||
app:layout_constraintLeft_toRightOf="@id/button_add_to_queue"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_video_name"
|
||||
android:layout_width="fill_parent"
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
android:textColor="@color/white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:maxWidth="300dp"
|
||||
tools:text="Queue" />
|
||||
|
||||
<TextView
|
||||
|
@ -24,16 +27,21 @@
|
|||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toRightOf="@id/text_name"
|
||||
app:layout_constraintRight_toLeftOf="@id/button_container"
|
||||
android:fontFamily="@font/inter_regular"
|
||||
android:textColor="#ACACAC"
|
||||
android:textSize="13dp"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginBottom="7dp"
|
||||
android:layout_marginRight="45dp"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
tools:text="3 videos" />
|
||||
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/button_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
|
|
28
app/src/main/res/layout/overlay_webview.xml
Normal file
28
app/src/main/res/layout/overlay_webview.xml
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<com.futo.platformplayer.views.overlays.OverlayTopbar
|
||||
android:id="@+id/topbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
app:title="Web"
|
||||
app:metadata=""
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent" />
|
||||
|
||||
<WebView
|
||||
android:id="@+id/webview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/topbar"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
</WebView>
|
||||
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
Add table
Reference in a new issue