mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-08-03 14:50:49 +00:00
Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay
This commit is contained in:
commit
1393c489c1
19 changed files with 271 additions and 104 deletions
|
@ -5,6 +5,8 @@ import android.graphics.Color
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import android.widget.ImageButton
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||||
|
@ -369,6 +371,33 @@ class UISlideOverlays {
|
||||||
return overlay;
|
return overlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showCreatePlaylistOverlay(container: ViewGroup, onCreate: (String) -> Unit): SlideUpMenuOverlay {
|
||||||
|
val nameInput = SlideUpMenuTextInput(container.context, container.context.getString(R.string.name));
|
||||||
|
val addPlaylistOverlay = SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.create_new_playlist), container.context.getString(R.string.ok), false, nameInput);
|
||||||
|
|
||||||
|
addPlaylistOverlay.onOK.subscribe {
|
||||||
|
val text = nameInput.text;
|
||||||
|
if (text.isBlank()) {
|
||||||
|
return@subscribe;
|
||||||
|
}
|
||||||
|
|
||||||
|
addPlaylistOverlay.hide();
|
||||||
|
nameInput.deactivate();
|
||||||
|
nameInput.clear();
|
||||||
|
onCreate(text)
|
||||||
|
};
|
||||||
|
|
||||||
|
addPlaylistOverlay.onCancel.subscribe {
|
||||||
|
nameInput.deactivate();
|
||||||
|
nameInput.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
addPlaylistOverlay.show();
|
||||||
|
nameInput.activate();
|
||||||
|
|
||||||
|
return addPlaylistOverlay
|
||||||
|
}
|
||||||
|
|
||||||
fun showVideoOptionsOverlay(video: IPlatformVideo, container: ViewGroup, vararg actions: SlideUpMenuItem): SlideUpMenuOverlay {
|
fun showVideoOptionsOverlay(video: IPlatformVideo, container: ViewGroup, vararg actions: SlideUpMenuItem): SlideUpMenuOverlay {
|
||||||
val items = arrayListOf<View>();
|
val items = arrayListOf<View>();
|
||||||
val lastUpdated = StatePlaylists.instance.getLastUpdatedPlaylist();
|
val lastUpdated = StatePlaylists.instance.getLastUpdatedPlaylist();
|
||||||
|
@ -407,6 +436,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, "${container.context.getString(R.string.add_to)} " + playlist.name + "", "${playlist.videos.size} " + container.context.getString(R.string.videos), "",
|
playlistItems.add(SlideUpMenuItem(container.context, R.drawable.ic_playlist_add, "${container.context.getString(R.string.add_to)} " + playlist.name + "", "${playlist.videos.size} " + container.context.getString(R.string.videos), "",
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,7 +7,6 @@ class HttpConstantHandler(method: String, path: String, val content: String, val
|
||||||
val headers = this.headers.clone();
|
val headers = this.headers.clone();
|
||||||
if(contentType != null)
|
if(contentType != null)
|
||||||
headers["Content-Type"] = contentType;
|
headers["Content-Type"] = contentType;
|
||||||
headers["Content-Length"] = content.length.toString();
|
|
||||||
|
|
||||||
httpContext.respondCode(200, headers, content);
|
httpContext.respondCode(200, headers, content);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
package com.futo.platformplayer.api.http.server.handlers
|
package com.futo.platformplayer.api.http.server.handlers
|
||||||
|
|
||||||
import com.futo.platformplayer.api.http.server.HttpContext
|
import com.futo.platformplayer.api.http.server.HttpContext
|
||||||
|
import com.futo.platformplayer.api.http.server.HttpHeaders
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.zip.GZIPOutputStream
|
import java.util.zip.GZIPOutputStream
|
||||||
|
|
||||||
class HttpFileHandler(method: String, path: String, private val contentType: String, private val filePath: String, private val closeAfterRequest: Boolean = false): HttpHandler(method, path) {
|
class HttpFileHandler(method: String, path: String, private val contentType: String, private val filePath: String): HttpHandler(method, path) {
|
||||||
override fun handle(httpContext: HttpContext) {
|
override fun handle(httpContext: HttpContext) {
|
||||||
val requestHeaders = httpContext.headers;
|
val requestHeaders = httpContext.headers;
|
||||||
val responseHeaders = this.headers.clone();
|
val responseHeaders = this.headers.clone();
|
||||||
|
@ -30,19 +32,13 @@ class HttpFileHandler(method: String, path: String, private val contentType: Str
|
||||||
|
|
||||||
responseHeaders["Content-Disposition"] = "attachment; filename=\"${file.name.replace("\"", "\\\"")}\""
|
responseHeaders["Content-Disposition"] = "attachment; filename=\"${file.name.replace("\"", "\\\"")}\""
|
||||||
|
|
||||||
val acceptEncoding = requestHeaders["Accept-Encoding"]
|
|
||||||
val shouldGzip = acceptEncoding != null && acceptEncoding.split(',').any { it.trim().equals("gzip", ignoreCase = true) || it == "*" }
|
|
||||||
if (shouldGzip) {
|
|
||||||
responseHeaders["Content-Encoding"] = "gzip"
|
|
||||||
}
|
|
||||||
|
|
||||||
val range = requestHeaders["Range"]
|
val range = requestHeaders["Range"]
|
||||||
var start: Long
|
val start: Long
|
||||||
val end: Long
|
val end: Long
|
||||||
if (range != null && range.startsWith("bytes=")) {
|
if (range != null && range.startsWith("bytes=")) {
|
||||||
val parts = range.substring(6).split("-")
|
val parts = range.substring(6).split("-")
|
||||||
start = parts[0].toLong()
|
start = parts[0].toLong()
|
||||||
end = parts.getOrNull(1)?.toLong() ?: (file.length() - 1)
|
end = parts.getOrNull(1)?.toLongOrNull() ?: (file.length() - 1)
|
||||||
responseHeaders["Content-Range"] = "bytes $start-$end/${file.length()}"
|
responseHeaders["Content-Range"] = "bytes $start-$end/${file.length()}"
|
||||||
} else {
|
} else {
|
||||||
start = 0
|
start = 0
|
||||||
|
@ -51,18 +47,19 @@ class HttpFileHandler(method: String, path: String, private val contentType: Str
|
||||||
|
|
||||||
var totalBytesSent = 0
|
var totalBytesSent = 0
|
||||||
val contentLength = end - start + 1
|
val contentLength = end - start + 1
|
||||||
Logger.i(TAG, "Sending $contentLength bytes (start: $start, end: $end, shouldGzip: $shouldGzip)")
|
|
||||||
responseHeaders["Content-Length"] = contentLength.toString()
|
responseHeaders["Content-Length"] = contentLength.toString()
|
||||||
|
Logger.i(TAG, "Sending $contentLength bytes (start: $start, end: $end)")
|
||||||
|
|
||||||
file.inputStream().use { inputStream ->
|
file.inputStream().use { inputStream ->
|
||||||
httpContext.respond(if (range == null) 200 else 206, responseHeaders) { responseStream ->
|
httpContext.respond(if (range != null) 206 else 200, responseHeaders) { responseStream ->
|
||||||
try {
|
try {
|
||||||
val buffer = ByteArray(8192)
|
val buffer = ByteArray(8192)
|
||||||
inputStream.skip(start)
|
inputStream.skip(start)
|
||||||
|
var current = start
|
||||||
|
|
||||||
val outputStream = if (shouldGzip) GZIPOutputStream(responseStream) else responseStream
|
val outputStream = responseStream
|
||||||
while (true) {
|
while (true) {
|
||||||
val expectedBytesRead = (end - start + 1).coerceAtMost(buffer.size.toLong());
|
val expectedBytesRead = (end - current + 1).coerceAtMost(buffer.size.toLong());
|
||||||
val bytesRead = inputStream.read(buffer);
|
val bytesRead = inputStream.read(buffer);
|
||||||
if (bytesRead < 0) {
|
if (bytesRead < 0) {
|
||||||
Logger.i(TAG, "End of file reached")
|
Logger.i(TAG, "End of file reached")
|
||||||
|
@ -73,27 +70,21 @@ class HttpFileHandler(method: String, path: String, private val contentType: Str
|
||||||
outputStream.write(buffer, 0, bytesToSend)
|
outputStream.write(buffer, 0, bytesToSend)
|
||||||
|
|
||||||
totalBytesSent += bytesToSend
|
totalBytesSent += bytesToSend
|
||||||
Logger.v(TAG, "Sent bytes $start-${start + bytesToSend}, totalBytesSent=$totalBytesSent")
|
Logger.v(TAG, "Sent bytes $current-${current + bytesToSend}, totalBytesSent=$totalBytesSent")
|
||||||
|
|
||||||
start += bytesToSend.toLong()
|
current += bytesToSend.toLong()
|
||||||
if (start >= end) {
|
if (current >= end) {
|
||||||
Logger.i(TAG, "Expected amount of bytes sent")
|
Logger.i(TAG, "Expected amount of bytes sent")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.i(TAG, "Finished sending file (segment)")
|
Logger.i(TAG, "Finished sending file (segment)")
|
||||||
|
|
||||||
if (shouldGzip) (outputStream as GZIPOutputStream).finish()
|
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
httpContext.respondCode(500, headers)
|
httpContext.respondCode(500, headers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (closeAfterRequest) {
|
|
||||||
httpContext.keepAlive = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.net.Uri
|
||||||
import com.futo.platformplayer.api.http.server.HttpContext
|
import com.futo.platformplayer.api.http.server.HttpContext
|
||||||
import com.futo.platformplayer.api.http.server.HttpHeaders
|
import com.futo.platformplayer.api.http.server.HttpHeaders
|
||||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||||
|
import com.futo.platformplayer.logging.Logger
|
||||||
|
|
||||||
class HttpProxyHandler(method: String, path: String, val targetUrl: String): HttpHandler(method, path) {
|
class HttpProxyHandler(method: String, path: String, val targetUrl: String): HttpHandler(method, path) {
|
||||||
var content: String? = null;
|
var content: String? = null;
|
||||||
|
@ -34,8 +35,8 @@ class HttpProxyHandler(method: String, path: String, val targetUrl: String): Htt
|
||||||
proxyHeaders.put("Referer", targetUrl);
|
proxyHeaders.put("Referer", targetUrl);
|
||||||
|
|
||||||
val useMethod = if (method == "inherit") context.method else method;
|
val useMethod = if (method == "inherit") context.method else method;
|
||||||
//Logger.i(TAG, "Proxied Request ${useMethod}: ${targetUrl}");
|
Logger.i(TAG, "Proxied Request ${useMethod}: ${targetUrl}");
|
||||||
//Logger.i(TAG, "Headers:" + proxyHeaders.map { "${it.key}: ${it.value}" }.joinToString("\n"));
|
Logger.i(TAG, "Headers:" + proxyHeaders.map { "${it.key}: ${it.value}" }.joinToString("\n"));
|
||||||
|
|
||||||
val resp = when (useMethod) {
|
val resp = when (useMethod) {
|
||||||
"GET" -> _client.get(targetUrl, proxyHeaders);
|
"GET" -> _client.get(targetUrl, proxyHeaders);
|
||||||
|
@ -44,7 +45,7 @@ class HttpProxyHandler(method: String, path: String, val targetUrl: String): Htt
|
||||||
else -> _client.requestMethod(useMethod, targetUrl, proxyHeaders);
|
else -> _client.requestMethod(useMethod, targetUrl, proxyHeaders);
|
||||||
};
|
};
|
||||||
|
|
||||||
//Logger.i(TAG, "Proxied Response [${resp.code}]");
|
Logger.i(TAG, "Proxied Response [${resp.code}]");
|
||||||
val headersFiltered = HttpHeaders(resp.getHeadersFlat().filter { !_ignoreRequestHeaders.contains(it.key.lowercase()) });
|
val headersFiltered = HttpHeaders(resp.getHeadersFlat().filter { !_ignoreRequestHeaders.contains(it.key.lowercase()) });
|
||||||
for(newHeader in headers)
|
for(newHeader in headers)
|
||||||
headersFiltered.put(newHeader.key, newHeader.value);
|
headersFiltered.put(newHeader.key, newHeader.value);
|
||||||
|
@ -92,4 +93,8 @@ class HttpProxyHandler(method: String, path: String, val targetUrl: String): Htt
|
||||||
_ignoreRequestHeaders.add("referer");
|
_ignoreRequestHeaders.add("referer");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "HttpProxyHandler"
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -486,7 +486,7 @@ class StateCasting {
|
||||||
}
|
}
|
||||||
if (subtitleSource != null) {
|
if (subtitleSource != null) {
|
||||||
_castServer.addHandler(
|
_castServer.addHandler(
|
||||||
HttpFileHandler("GET", subtitlePath, subtitleSource.format ?: "text/vtt", subtitleSource.filePath, true)
|
HttpFileHandler("GET", subtitlePath, subtitleSource.format ?: "text/vtt", subtitleSource.filePath)
|
||||||
.withHeader("Access-Control-Allow-Origin", "*"), true
|
.withHeader("Access-Control-Allow-Origin", "*"), true
|
||||||
).withTag("cast");
|
).withTag("cast");
|
||||||
_castServer.addHandler(
|
_castServer.addHandler(
|
||||||
|
|
|
@ -27,6 +27,7 @@ import com.futo.platformplayer.views.adapters.InsertedViewHolder
|
||||||
import com.futo.platformplayer.views.adapters.PreviewNestedVideoViewHolder
|
import com.futo.platformplayer.views.adapters.PreviewNestedVideoViewHolder
|
||||||
import com.futo.platformplayer.views.adapters.PreviewVideoViewHolder
|
import com.futo.platformplayer.views.adapters.PreviewVideoViewHolder
|
||||||
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem
|
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem
|
||||||
|
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
|
||||||
abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent, IPlatformContent, IPager<IPlatformContent>, ContentPreviewViewHolder> where TFragment : MainFragment {
|
abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent, IPlatformContent, IPager<IPlatformContent>, ContentPreviewViewHolder> where TFragment : MainFragment {
|
||||||
|
@ -37,6 +38,7 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
||||||
private var _previewsEnabled: Boolean = true;
|
private var _previewsEnabled: Boolean = true;
|
||||||
override val visibleThreshold: Int get() = if (feedStyle == FeedStyle.PREVIEW) { 5 } else { 10 };
|
override val visibleThreshold: Int get() = if (feedStyle == FeedStyle.PREVIEW) { 5 } else { 10 };
|
||||||
protected lateinit var headerView: LinearLayout;
|
protected lateinit var headerView: LinearLayout;
|
||||||
|
private var _videoOptionsOverlay: SlideUpMenuOverlay? = null;
|
||||||
|
|
||||||
constructor(fragment: TFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) {
|
constructor(fragment: TFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) {
|
||||||
|
|
||||||
|
@ -70,26 +72,8 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
||||||
adapter.onChannelClicked.subscribe(this) { fragment.navigate<ChannelFragment>(it) };
|
adapter.onChannelClicked.subscribe(this) { fragment.navigate<ChannelFragment>(it) };
|
||||||
adapter.onAddToClicked.subscribe(this) { content ->
|
adapter.onAddToClicked.subscribe(this) { content ->
|
||||||
//TODO: Reconstruct search video from detail if search is null
|
//TODO: Reconstruct search video from detail if search is null
|
||||||
_overlayContainer.let {
|
if(content is IPlatformVideo) {
|
||||||
if(content is IPlatformVideo)
|
showVideoOptionsOverlay(content)
|
||||||
UISlideOverlays.showVideoOptionsOverlay(content, it, SlideUpMenuItem(context, R.drawable.ic_visibility_off, context.getString(R.string.hide), context.getString(R.string.hide_from_home), "hide",
|
|
||||||
{ StateMeta.instance.addHiddenVideo(content.url);
|
|
||||||
if (fragment is HomeFragment) {
|
|
||||||
val removeIndex = recyclerData.results.indexOf(content);
|
|
||||||
if (removeIndex >= 0) {
|
|
||||||
recyclerData.results.removeAt(removeIndex);
|
|
||||||
recyclerData.adapter.notifyItemRemoved(recyclerData.adapter.childToParentPosition(removeIndex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
SlideUpMenuItem(context, R.drawable.ic_playlist, context.getString(R.string.play_feed_as_queue), context.getString(R.string.play_entire_feed), "playFeed",
|
|
||||||
{
|
|
||||||
val newQueue = listOf(content) + recyclerData.results
|
|
||||||
.filterIsInstance<IPlatformVideo>()
|
|
||||||
.filter { it != content };
|
|
||||||
StatePlayer.instance.setQueue(newQueue, StatePlayer.TYPE_QUEUE, "Feed Queue", true, false);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
adapter.onAddToQueueClicked.subscribe(this) {
|
adapter.onAddToQueueClicked.subscribe(this) {
|
||||||
|
@ -99,6 +83,50 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
||||||
UIDialogs.toast(context, context.getString(R.string.queued) + " [$name]", false);
|
UIDialogs.toast(context, context.getString(R.string.queued) + " [$name]", false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
adapter.onLongPress.subscribe(this) {
|
||||||
|
if (it is IPlatformVideo) {
|
||||||
|
showVideoOptionsOverlay(it)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onBackPressed(): Boolean {
|
||||||
|
val videoOptionsOverlay = _videoOptionsOverlay
|
||||||
|
if (videoOptionsOverlay != null) {
|
||||||
|
if (videoOptionsOverlay.isVisible) {
|
||||||
|
videoOptionsOverlay.hide();
|
||||||
|
_videoOptionsOverlay = null
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_videoOptionsOverlay = null
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showVideoOptionsOverlay(content: IPlatformVideo) {
|
||||||
|
_overlayContainer.let {
|
||||||
|
_videoOptionsOverlay = UISlideOverlays.showVideoOptionsOverlay(content, it, SlideUpMenuItem(context, R.drawable.ic_visibility_off, context.getString(R.string.hide), context.getString(R.string.hide_from_home), "hide",
|
||||||
|
{ StateMeta.instance.addHiddenVideo(content.url);
|
||||||
|
if (fragment is HomeFragment) {
|
||||||
|
val removeIndex = recyclerData.results.indexOf(content);
|
||||||
|
if (removeIndex >= 0) {
|
||||||
|
recyclerData.results.removeAt(removeIndex);
|
||||||
|
recyclerData.adapter.notifyItemRemoved(recyclerData.adapter.childToParentPosition(removeIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
SlideUpMenuItem(context, R.drawable.ic_playlist, context.getString(R.string.play_feed_as_queue), context.getString(R.string.play_entire_feed), "playFeed",
|
||||||
|
{
|
||||||
|
val newQueue = listOf(content) + recyclerData.results
|
||||||
|
.filterIsInstance<IPlatformVideo>()
|
||||||
|
.filter { it != content };
|
||||||
|
StatePlayer.instance.setQueue(newQueue, StatePlayer.TYPE_QUEUE, "Feed Queue", true, false);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun detachAdapterEvents() {
|
private fun detachAdapterEvents() {
|
||||||
|
@ -108,6 +136,7 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
||||||
adapter.onChannelClicked.remove(this);
|
adapter.onChannelClicked.remove(this);
|
||||||
adapter.onAddToClicked.remove(this);
|
adapter.onAddToClicked.remove(this);
|
||||||
adapter.onAddToQueueClicked.remove(this);
|
adapter.onAddToQueueClicked.remove(this);
|
||||||
|
adapter.onLongPress.remove(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>) {
|
override fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>) {
|
||||||
|
@ -137,11 +166,14 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
||||||
|
|
||||||
protected open fun onContentClicked(content: IPlatformContent, time: Long) {
|
protected open fun onContentClicked(content: IPlatformContent, time: Long) {
|
||||||
if(content is IPlatformVideo) {
|
if(content is IPlatformVideo) {
|
||||||
StatePlayer.instance.clearQueue();
|
if (StatePlayer.instance.hasQueue) {
|
||||||
if (Settings.instance.playback.shouldResumePreview(time))
|
StatePlayer.instance.addToQueue(content)
|
||||||
fragment.navigate<VideoDetailFragment>(content.withTimestamp(time)).maximizeVideoDetail();
|
} else {
|
||||||
else
|
if (Settings.instance.playback.shouldResumePreview(time))
|
||||||
fragment.navigate<VideoDetailFragment>(content).maximizeVideoDetail();
|
fragment.navigate<VideoDetailFragment>(content.withTimestamp(time)).maximizeVideoDetail();
|
||||||
|
else
|
||||||
|
fragment.navigate<VideoDetailFragment>(content).maximizeVideoDetail();
|
||||||
|
}
|
||||||
} else if (content is IPlatformPlaylist) {
|
} else if (content is IPlatformPlaylist) {
|
||||||
fragment.navigate<PlaylistFragment>(content);
|
fragment.navigate<PlaylistFragment>(content);
|
||||||
} else if (content is IPlatformPost) {
|
} else if (content is IPlatformPost) {
|
||||||
|
|
|
@ -62,6 +62,13 @@ class ContentSearchResultsFragment : MainFragment() {
|
||||||
_view = null;
|
_view = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed(): Boolean {
|
||||||
|
if (_view?.onBackPressed() == true)
|
||||||
|
return true
|
||||||
|
|
||||||
|
return super.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
fun setPreviewsEnabled(previewsEnabled: Boolean) {
|
fun setPreviewsEnabled(previewsEnabled: Boolean) {
|
||||||
_view?.setPreviewsEnabled(previewsEnabled && Settings.instance.search.previewFeedItems);
|
_view?.setPreviewsEnabled(previewsEnabled && Settings.instance.search.previewFeedItems);
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,13 @@ class HomeFragment : MainFragment() {
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed(): Boolean {
|
||||||
|
if (_view?.onBackPressed() == true)
|
||||||
|
return true
|
||||||
|
|
||||||
|
return super.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyMainView() {
|
override fun onDestroyMainView() {
|
||||||
super.onDestroyMainView();
|
super.onDestroyMainView();
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,13 @@ class PlaylistSearchResultsFragment : MainFragment() {
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed(): Boolean {
|
||||||
|
if (_view?.onBackPressed() == true)
|
||||||
|
return true
|
||||||
|
|
||||||
|
return super.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyMainView() {
|
override fun onDestroyMainView() {
|
||||||
super.onDestroyMainView();
|
super.onDestroyMainView();
|
||||||
_view?.cleanup();
|
_view?.cleanup();
|
||||||
|
|
|
@ -17,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.futo.platformplayer.states.StatePlayer
|
import com.futo.platformplayer.states.StatePlayer
|
||||||
import com.futo.platformplayer.states.StatePlaylists
|
import com.futo.platformplayer.states.StatePlaylists
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
|
import com.futo.platformplayer.UISlideOverlays
|
||||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||||
import com.futo.platformplayer.assume
|
import com.futo.platformplayer.assume
|
||||||
import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFragment
|
import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFragment
|
||||||
|
@ -54,6 +55,14 @@ class PlaylistsFragment : MainFragment() {
|
||||||
_view?.onShown(parameter, isBack);
|
_view?.onShown(parameter, isBack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed(): Boolean {
|
||||||
|
if (_view?.onBackPressed() == true) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("ViewConstructor")
|
@SuppressLint("ViewConstructor")
|
||||||
class PlaylistsView : LinearLayout {
|
class PlaylistsView : LinearLayout {
|
||||||
private val _fragment: PlaylistsFragment;
|
private val _fragment: PlaylistsFragment;
|
||||||
|
@ -64,6 +73,7 @@ class PlaylistsFragment : MainFragment() {
|
||||||
private var _adapterWatchLater: VideoListHorizontalAdapter;
|
private var _adapterWatchLater: VideoListHorizontalAdapter;
|
||||||
private var _adapterPlaylist: PlaylistsAdapter;
|
private var _adapterPlaylist: PlaylistsAdapter;
|
||||||
private var _layoutWatchlist: ConstraintLayout;
|
private var _layoutWatchlist: ConstraintLayout;
|
||||||
|
private var _slideUpOverlay: SlideUpMenuOverlay? = null;
|
||||||
|
|
||||||
constructor(fragment: PlaylistsFragment, inflater: LayoutInflater) : super(inflater.context) {
|
constructor(fragment: PlaylistsFragment, inflater: LayoutInflater) : super(inflater.context) {
|
||||||
_fragment = fragment;
|
_fragment = fragment;
|
||||||
|
@ -92,41 +102,24 @@ class PlaylistsFragment : MainFragment() {
|
||||||
recyclerPlaylists.adapter = _adapterPlaylist;
|
recyclerPlaylists.adapter = _adapterPlaylist;
|
||||||
recyclerPlaylists.layoutManager = LinearLayoutManager(context);
|
recyclerPlaylists.layoutManager = LinearLayoutManager(context);
|
||||||
|
|
||||||
val nameInput = SlideUpMenuTextInput(context, context.getString(R.string.name));
|
|
||||||
val addPlaylistOverlay = SlideUpMenuOverlay(context, findViewById<FrameLayout>(R.id.overlay_create_playlist), context.getString(R.string.create_new_playlist), context.getString(R.string.ok), false, nameInput);
|
|
||||||
|
val buttonCreatePlaylist = findViewById<ImageButton>(R.id.button_create_playlist);
|
||||||
|
buttonCreatePlaylist.setOnClickListener {
|
||||||
|
_slideUpOverlay = UISlideOverlays.showCreatePlaylistOverlay(findViewById<FrameLayout>(R.id.overlay_create_playlist)) {
|
||||||
|
val playlist = Playlist(it, arrayListOf());
|
||||||
|
playlists.add(0, playlist);
|
||||||
|
StatePlaylists.instance.createOrUpdatePlaylist(playlist);
|
||||||
|
|
||||||
|
_adapterPlaylist.notifyItemInserted(0);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
_adapterPlaylist.onClick.subscribe { p -> _fragment.navigate<PlaylistFragment>(p); };
|
_adapterPlaylist.onClick.subscribe { p -> _fragment.navigate<PlaylistFragment>(p); };
|
||||||
_adapterPlaylist.onPlay.subscribe { p ->
|
_adapterPlaylist.onPlay.subscribe { p ->
|
||||||
StatePlayer.instance.setPlaylist(p, 0, true);
|
StatePlayer.instance.setPlaylist(p, 0, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
addPlaylistOverlay.onOK.subscribe {
|
|
||||||
val text = nameInput.text;
|
|
||||||
if (text.isBlank()) {
|
|
||||||
return@subscribe;
|
|
||||||
}
|
|
||||||
|
|
||||||
val playlist = Playlist(text, arrayListOf());
|
|
||||||
playlists.add(0, playlist);
|
|
||||||
StatePlaylists.instance.createOrUpdatePlaylist(playlist);
|
|
||||||
|
|
||||||
_adapterPlaylist.notifyItemInserted(0);
|
|
||||||
addPlaylistOverlay.hide();
|
|
||||||
nameInput.deactivate();
|
|
||||||
nameInput.clear();
|
|
||||||
};
|
|
||||||
|
|
||||||
addPlaylistOverlay.onCancel.subscribe {
|
|
||||||
nameInput.deactivate();
|
|
||||||
nameInput.clear();
|
|
||||||
};
|
|
||||||
|
|
||||||
val buttonCreatePlaylist = findViewById<ImageButton>(R.id.button_create_playlist);
|
|
||||||
buttonCreatePlaylist.setOnClickListener {
|
|
||||||
addPlaylistOverlay.show();
|
|
||||||
nameInput.activate();
|
|
||||||
};
|
|
||||||
|
|
||||||
_appBar = findViewById(R.id.app_bar);
|
_appBar = findViewById(R.id.app_bar);
|
||||||
_layoutWatchlist = findViewById(R.id.layout_watchlist);
|
_layoutWatchlist = findViewById(R.id.layout_watchlist);
|
||||||
|
|
||||||
|
@ -142,12 +135,28 @@ class PlaylistsFragment : MainFragment() {
|
||||||
|
|
||||||
fun onShown(parameter: Any?, isBack: Boolean) {
|
fun onShown(parameter: Any?, isBack: Boolean) {
|
||||||
playlists.clear()
|
playlists.clear()
|
||||||
playlists.addAll(StatePlaylists.instance.getPlaylists().sortedByDescending { maxOf(it.datePlayed, it.dateUpdate, it.dateCreation) });
|
playlists.addAll(
|
||||||
|
StatePlaylists.instance.getPlaylists()
|
||||||
|
.sortedByDescending { maxOf(it.datePlayed, it.dateUpdate, it.dateCreation) });
|
||||||
_adapterPlaylist.notifyDataSetChanged();
|
_adapterPlaylist.notifyDataSetChanged();
|
||||||
|
|
||||||
updateWatchLater();
|
updateWatchLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onBackPressed(): Boolean {
|
||||||
|
val slideUpOverlay = _slideUpOverlay;
|
||||||
|
if (slideUpOverlay != null) {
|
||||||
|
if (slideUpOverlay.isVisible) {
|
||||||
|
slideUpOverlay.hide();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateWatchLater() {
|
private fun updateWatchLater() {
|
||||||
val watchList = StatePlaylists.instance.getWatchLater();
|
val watchList = StatePlaylists.instance.getWatchLater();
|
||||||
if (watchList.isNotEmpty()) {
|
if (watchList.isNotEmpty()) {
|
||||||
|
|
|
@ -80,6 +80,13 @@ class SubscriptionsFeedFragment : MainFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed(): Boolean {
|
||||||
|
if (_view?.onBackPressed() == true)
|
||||||
|
return true
|
||||||
|
|
||||||
|
return super.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
fun setPreviewsEnabled(previewsEnabled: Boolean) {
|
fun setPreviewsEnabled(previewsEnabled: Boolean) {
|
||||||
_view?.setPreviewsEnabled(previewsEnabled && Settings.instance.subscriptions.previewFeedItems);
|
_view?.setPreviewsEnabled(previewsEnabled && Settings.instance.subscriptions.previewFeedItems);
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,12 @@ class StatePlayer {
|
||||||
var queueShuffle: Boolean = false
|
var queueShuffle: Boolean = false
|
||||||
private set;
|
private set;
|
||||||
|
|
||||||
|
val hasQueue: Boolean get() {
|
||||||
|
synchronized(_queue) {
|
||||||
|
return _queue.isNotEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val queueName: String get() = _queueName ?: _queueType;
|
val queueName: String get() = _queueName ?: _queueType;
|
||||||
|
|
||||||
//Events
|
//Events
|
||||||
|
|
|
@ -32,6 +32,7 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader<ContentPreviewVi
|
||||||
val onChannelClicked = Event1<PlatformAuthorLink>();
|
val onChannelClicked = Event1<PlatformAuthorLink>();
|
||||||
val onAddToClicked = Event1<IPlatformContent>();
|
val onAddToClicked = Event1<IPlatformContent>();
|
||||||
val onAddToQueueClicked = Event1<IPlatformContent>();
|
val onAddToQueueClicked = Event1<IPlatformContent>();
|
||||||
|
val onLongPress = Event1<IPlatformContent>();
|
||||||
|
|
||||||
private var _taskLoadContent = TaskHandler<Pair<ContentPreviewViewHolder, IPlatformContent>, Pair<ContentPreviewViewHolder, IPlatformContentDetails>>(
|
private var _taskLoadContent = TaskHandler<Pair<ContentPreviewViewHolder, IPlatformContent>, Pair<ContentPreviewViewHolder, IPlatformContentDetails>>(
|
||||||
StateApp.instance.scopeGetter, { (viewHolder, video) ->
|
StateApp.instance.scopeGetter, { (viewHolder, video) ->
|
||||||
|
@ -93,6 +94,7 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader<ContentPreviewVi
|
||||||
this.onChannelClicked.subscribe(this@PreviewContentListAdapter.onChannelClicked::emit);
|
this.onChannelClicked.subscribe(this@PreviewContentListAdapter.onChannelClicked::emit);
|
||||||
this.onAddToClicked.subscribe(this@PreviewContentListAdapter.onAddToClicked::emit);
|
this.onAddToClicked.subscribe(this@PreviewContentListAdapter.onAddToClicked::emit);
|
||||||
this.onAddToQueueClicked.subscribe(this@PreviewContentListAdapter.onAddToQueueClicked::emit);
|
this.onAddToQueueClicked.subscribe(this@PreviewContentListAdapter.onAddToQueueClicked::emit);
|
||||||
|
this.onLongPress.subscribe(this@PreviewContentListAdapter.onLongPress::emit);
|
||||||
};
|
};
|
||||||
private fun createPlaylistViewHolder(viewGroup: ViewGroup): PreviewPlaylistViewHolder = PreviewPlaylistViewHolder(viewGroup, _feedStyle).apply {
|
private fun createPlaylistViewHolder(viewGroup: ViewGroup): PreviewPlaylistViewHolder = PreviewPlaylistViewHolder(viewGroup, _feedStyle).apply {
|
||||||
this.onPlaylistClicked.subscribe { this@PreviewContentListAdapter.onContentClicked.emit(it, 0L) };
|
this.onPlaylistClicked.subscribe { this@PreviewContentListAdapter.onContentClicked.emit(it, 0L) };
|
||||||
|
|
|
@ -68,6 +68,7 @@ open class PreviewVideoView : LinearLayout {
|
||||||
};
|
};
|
||||||
|
|
||||||
val onVideoClicked = Event2<IPlatformVideo, Long>();
|
val onVideoClicked = Event2<IPlatformVideo, Long>();
|
||||||
|
val onLongPress = Event1<IPlatformVideo>();
|
||||||
val onChannelClicked = Event1<PlatformAuthorLink>();
|
val onChannelClicked = Event1<PlatformAuthorLink>();
|
||||||
val onAddToClicked = Event1<IPlatformVideo>();
|
val onAddToClicked = Event1<IPlatformVideo>();
|
||||||
val onAddToQueueClicked = Event1<IPlatformVideo>();
|
val onAddToQueueClicked = Event1<IPlatformVideo>();
|
||||||
|
@ -119,7 +120,13 @@ open class PreviewVideoView : LinearLayout {
|
||||||
|
|
||||||
this._exoPlayer = exoPlayer
|
this._exoPlayer = exoPlayer
|
||||||
|
|
||||||
setOnClickListener { onOpenClicked() };
|
setOnLongClickListener {
|
||||||
|
onLongPress()
|
||||||
|
true
|
||||||
|
};
|
||||||
|
setOnClickListener {
|
||||||
|
onOpenClicked()
|
||||||
|
};
|
||||||
_imageChannel.setOnClickListener { currentVideo?.let { onChannelClicked.emit(it.author) } };
|
_imageChannel.setOnClickListener { currentVideo?.let { onChannelClicked.emit(it.author) } };
|
||||||
_textChannelName.setOnClickListener { currentVideo?.let { onChannelClicked.emit(it.author) } };
|
_textChannelName.setOnClickListener { currentVideo?.let { onChannelClicked.emit(it.author) } };
|
||||||
_textVideoMetadata.setOnClickListener { currentVideo?.let { onChannelClicked.emit(it.author) } };
|
_textVideoMetadata.setOnClickListener { currentVideo?.let { onChannelClicked.emit(it.author) } };
|
||||||
|
@ -145,6 +152,12 @@ open class PreviewVideoView : LinearLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected open fun onLongPress() {
|
||||||
|
currentVideo?.let {
|
||||||
|
onLongPress.emit(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
open fun bind(content: IPlatformContent) {
|
open fun bind(content: IPlatformContent) {
|
||||||
_taskLoadProfile.cancel();
|
_taskLoadProfile.cancel();
|
||||||
|
|
|
@ -17,6 +17,7 @@ class PreviewVideoViewHolder : ContentPreviewViewHolder {
|
||||||
val onChannelClicked = Event1<PlatformAuthorLink>();
|
val onChannelClicked = Event1<PlatformAuthorLink>();
|
||||||
val onAddToClicked = Event1<IPlatformVideo>();
|
val onAddToClicked = Event1<IPlatformVideo>();
|
||||||
val onAddToQueueClicked = Event1<IPlatformVideo>();
|
val onAddToQueueClicked = Event1<IPlatformVideo>();
|
||||||
|
val onLongPress = Event1<IPlatformVideo>();
|
||||||
|
|
||||||
//val context: Context;
|
//val context: Context;
|
||||||
val currentVideo: IPlatformVideo? get() = view.currentVideo;
|
val currentVideo: IPlatformVideo? get() = view.currentVideo;
|
||||||
|
@ -30,6 +31,7 @@ class PreviewVideoViewHolder : ContentPreviewViewHolder {
|
||||||
view.onChannelClicked.subscribe(onChannelClicked::emit);
|
view.onChannelClicked.subscribe(onChannelClicked::emit);
|
||||||
view.onAddToClicked.subscribe(onAddToClicked::emit);
|
view.onAddToClicked.subscribe(onAddToClicked::emit);
|
||||||
view.onAddToQueueClicked.subscribe(onAddToQueueClicked::emit);
|
view.onAddToQueueClicked.subscribe(onAddToQueueClicked::emit);
|
||||||
|
view.onLongPress.subscribe(onLongPress::emit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.animation.ObjectAnimator
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Animatable
|
import android.graphics.drawable.Animatable
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
import android.view.GestureDetector
|
import android.view.GestureDetector
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
|
@ -57,8 +58,10 @@ class GestureControlView : LinearLayout {
|
||||||
private var _isFullScreen = false;
|
private var _isFullScreen = false;
|
||||||
private var _animatorBrightness: ObjectAnimator? = null;
|
private var _animatorBrightness: ObjectAnimator? = null;
|
||||||
private val _layoutControlsFullscreen: FrameLayout;
|
private val _layoutControlsFullscreen: FrameLayout;
|
||||||
private var _adjustingFullscreen: Boolean = false;
|
private var _adjustingFullscreenUp: Boolean = false;
|
||||||
private var _fullScreenFactor = 1.0f;
|
private var _adjustingFullscreenDown: Boolean = false;
|
||||||
|
private var _fullScreenFactorUp = 1.0f;
|
||||||
|
private var _fullScreenFactorDown = 1.0f;
|
||||||
|
|
||||||
val onSeek = Event1<Long>();
|
val onSeek = Event1<Long>();
|
||||||
val onBrightnessAdjusted = Event1<Float>();
|
val onBrightnessAdjusted = Event1<Float>();
|
||||||
|
@ -100,10 +103,14 @@ class GestureControlView : LinearLayout {
|
||||||
_soundFactor = (_soundFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
|
_soundFactor = (_soundFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
|
||||||
_progressSound.progress = _soundFactor;
|
_progressSound.progress = _soundFactor;
|
||||||
onSoundAdjusted.emit(_soundFactor);
|
onSoundAdjusted.emit(_soundFactor);
|
||||||
} else if (_adjustingFullscreen) {
|
} else if (_adjustingFullscreenUp) {
|
||||||
val adjustAmount = (distanceY * 2) / height;
|
val adjustAmount = (distanceY * 2) / height;
|
||||||
_fullScreenFactor = (_fullScreenFactor + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
|
_fullScreenFactorUp = (_fullScreenFactorUp + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
|
||||||
_layoutControlsFullscreen.transitionAlpha = _fullScreenFactor;
|
_layoutControlsFullscreen.alpha = _fullScreenFactorUp;
|
||||||
|
} else if (_adjustingFullscreenDown) {
|
||||||
|
val adjustAmount = (-distanceY * 2) / height;
|
||||||
|
_fullScreenFactorDown = (_fullScreenFactorDown + adjustAmount).coerceAtLeast(0.0f).coerceAtMost(1.0f);
|
||||||
|
_layoutControlsFullscreen.alpha = _fullScreenFactorDown;
|
||||||
} else {
|
} else {
|
||||||
val rx = p0.x / width;
|
val rx = p0.x / width;
|
||||||
val ry = p0.y / height;
|
val ry = p0.y / height;
|
||||||
|
@ -114,7 +121,11 @@ class GestureControlView : LinearLayout {
|
||||||
} else if (_isFullScreen && rx > 0.6) {
|
} else if (_isFullScreen && rx > 0.6) {
|
||||||
startAdjustingSound();
|
startAdjustingSound();
|
||||||
} else if (rx >= 0.4 && rx <= 0.6) {
|
} else if (rx >= 0.4 && rx <= 0.6) {
|
||||||
startAdjustingFullscreen();
|
if (_isFullScreen) {
|
||||||
|
startAdjustingFullscreenDown();
|
||||||
|
} else {
|
||||||
|
startAdjustingFullscreenUp();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -180,11 +191,18 @@ class GestureControlView : LinearLayout {
|
||||||
stopAdjustingBrightness();
|
stopAdjustingBrightness();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_adjustingFullscreen && ev.action == MotionEvent.ACTION_UP) {
|
if (_adjustingFullscreenUp && ev.action == MotionEvent.ACTION_UP) {
|
||||||
if (_fullScreenFactor > 0.5) {
|
if (_fullScreenFactorUp > 0.5) {
|
||||||
onToggleFullscreen.emit();
|
onToggleFullscreen.emit();
|
||||||
}
|
}
|
||||||
stopAdjustingFullscreen();
|
stopAdjustingFullscreenUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_adjustingFullscreenDown && ev.action == MotionEvent.ACTION_UP) {
|
||||||
|
if (_fullScreenFactorDown > 0.5) {
|
||||||
|
onToggleFullscreen.emit();
|
||||||
|
}
|
||||||
|
stopAdjustingFullscreenDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
startHideJobIfNecessary();
|
startHideJobIfNecessary();
|
||||||
|
@ -469,15 +487,27 @@ class GestureControlView : LinearLayout {
|
||||||
_animatorSound?.start();
|
_animatorSound?.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startAdjustingFullscreen() {
|
private fun startAdjustingFullscreenUp() {
|
||||||
_adjustingFullscreen = true;
|
_adjustingFullscreenUp = true;
|
||||||
_fullScreenFactor = 0f;
|
_fullScreenFactorUp = 0f;
|
||||||
_layoutControlsFullscreen.transitionAlpha = 0f;
|
_layoutControlsFullscreen.alpha = 0f;
|
||||||
_layoutControlsFullscreen.visibility = View.VISIBLE;
|
_layoutControlsFullscreen.visibility = View.VISIBLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopAdjustingFullscreen() {
|
private fun stopAdjustingFullscreenUp() {
|
||||||
_adjustingFullscreen = false;
|
_adjustingFullscreenUp = false;
|
||||||
|
_layoutControlsFullscreen.visibility = View.GONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startAdjustingFullscreenDown() {
|
||||||
|
_adjustingFullscreenDown = true;
|
||||||
|
_fullScreenFactorDown = 0f;
|
||||||
|
_layoutControlsFullscreen.alpha = 0f;
|
||||||
|
_layoutControlsFullscreen.visibility = View.VISIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopAdjustingFullscreenDown() {
|
||||||
|
_adjustingFullscreenDown = false;
|
||||||
_layoutControlsFullscreen.visibility = View.GONE;
|
_layoutControlsFullscreen.visibility = View.GONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -511,7 +541,7 @@ class GestureControlView : LinearLayout {
|
||||||
//onSoundAdjusted.emit(1.0f);
|
//onSoundAdjusted.emit(1.0f);
|
||||||
stopAdjustingBrightness();
|
stopAdjustingBrightness();
|
||||||
stopAdjustingSound();
|
stopAdjustingSound();
|
||||||
stopAdjustingFullscreen();
|
stopAdjustingFullscreenUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
_isFullScreen = isFullScreen;
|
_isFullScreen = isFullScreen;
|
||||||
|
|
|
@ -134,6 +134,10 @@ class SlideUpMenuOverlay : RelativeLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun show(){
|
fun show(){
|
||||||
|
if (isVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
isVisible = true;
|
isVisible = true;
|
||||||
_container?.post {
|
_container?.post {
|
||||||
_container?.visibility = View.VISIBLE;
|
_container?.visibility = View.VISIBLE;
|
||||||
|
@ -146,8 +150,8 @@ class SlideUpMenuOverlay : RelativeLayout {
|
||||||
_viewBackground.alpha = 0f;
|
_viewBackground.alpha = 0f;
|
||||||
|
|
||||||
val animations = arrayListOf<Animator>();
|
val animations = arrayListOf<Animator>();
|
||||||
animations.add(ObjectAnimator.ofFloat(_viewBackground, "alpha", 0.0f, 1.0f).setDuration(500));
|
animations.add(ObjectAnimator.ofFloat(_viewBackground, "alpha", 0.0f, 1.0f).setDuration(ANIMATION_DURATION_MS));
|
||||||
animations.add(ObjectAnimator.ofFloat(_viewOverlayContainer, "translationY", _viewOverlayContainer.measuredHeight.toFloat(), 0.0f).setDuration(500));
|
animations.add(ObjectAnimator.ofFloat(_viewOverlayContainer, "translationY", _viewOverlayContainer.measuredHeight.toFloat(), 0.0f).setDuration(ANIMATION_DURATION_MS));
|
||||||
|
|
||||||
val animatorSet = AnimatorSet();
|
val animatorSet = AnimatorSet();
|
||||||
animatorSet.playTogether(animations);
|
animatorSet.playTogether(animations);
|
||||||
|
@ -159,11 +163,15 @@ class SlideUpMenuOverlay : RelativeLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hide(animate: Boolean = true){
|
fun hide(animate: Boolean = true){
|
||||||
|
if (!isVisible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
isVisible = false;
|
isVisible = false;
|
||||||
if (_animated && animate) {
|
if (_animated && animate) {
|
||||||
val animations = arrayListOf<Animator>();
|
val animations = arrayListOf<Animator>();
|
||||||
animations.add(ObjectAnimator.ofFloat(_viewBackground, "alpha", 1.0f, 0.0f).setDuration(500));
|
animations.add(ObjectAnimator.ofFloat(_viewBackground, "alpha", 1.0f, 0.0f).setDuration(ANIMATION_DURATION_MS));
|
||||||
animations.add(ObjectAnimator.ofFloat(_viewOverlayContainer, "translationY", 0.0f, _viewOverlayContainer.measuredHeight.toFloat()).setDuration(500));
|
animations.add(ObjectAnimator.ofFloat(_viewOverlayContainer, "translationY", 0.0f, _viewOverlayContainer.measuredHeight.toFloat()).setDuration(ANIMATION_DURATION_MS));
|
||||||
|
|
||||||
val animatorSet = AnimatorSet();
|
val animatorSet = AnimatorSet();
|
||||||
animatorSet.doOnEnd {
|
animatorSet.doOnEnd {
|
||||||
|
@ -180,4 +188,8 @@ class SlideUpMenuOverlay : RelativeLayout {
|
||||||
_container?.visibility = View.GONE;
|
_container?.visibility = View.GONE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ANIMATION_DURATION_MS = 350L
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -659,6 +659,8 @@
|
||||||
<string name="stopped_after_requestcount_to_avoid_rate_limit_click_load_more_to_load_more">Stopped after {requestCount} to avoid rate limit, click load more to load more.</string>
|
<string name="stopped_after_requestcount_to_avoid_rate_limit_click_load_more_to_load_more">Stopped after {requestCount} to avoid rate limit, click load more to load more.</string>
|
||||||
<string name="this_creator_has_not_setup_any_monetization_features">This creator has not setup any monetization features</string>
|
<string name="this_creator_has_not_setup_any_monetization_features">This creator has not setup any monetization features</string>
|
||||||
<string name="plus_tax">" + Tax"</string>
|
<string name="plus_tax">" + Tax"</string>
|
||||||
|
<string name="new_playlist">New playlist</string>
|
||||||
|
<string name="add_to_new_playlist">Add to new playlist</string>
|
||||||
<string-array name="home_screen_array">
|
<string-array name="home_screen_array">
|
||||||
<item>Recommendations</item>
|
<item>Recommendations</item>
|
||||||
<item>Subscriptions</item>
|
<item>Subscriptions</item>
|
||||||
|
|
|
@ -25,7 +25,7 @@ cp ./app/build/outputs/apk/stable/release/app-stable-arm64-v8a-release.apk $DOCU
|
||||||
VERSION=$(git describe --tags)
|
VERSION=$(git describe --tags)
|
||||||
echo $VERSION > $DOCUMENT_ROOT/version.txt
|
echo $VERSION > $DOCUMENT_ROOT/version.txt
|
||||||
mkdir -p $DOCUMENT_ROOT/changelogs
|
mkdir -p $DOCUMENT_ROOT/changelogs
|
||||||
git tag -l $VERSION -n1000 | awk '{$1=""; print $0}' | sed -e 's/^[ \t]*//' > $DOCUMENT_ROOT/changelogs/$VERSION
|
git tag -l --format='%(contents)' $VERSION > $DOCUMENT_ROOT/changelogs/$VERSION
|
||||||
|
|
||||||
# Notify Cloudflare to wipe the CDN cache
|
# Notify Cloudflare to wipe the CDN cache
|
||||||
echo "Purging Cloudflare cache for zone $CLOUDFLARE_ZONE_ID..."
|
echo "Purging Cloudflare cache for zone $CLOUDFLARE_ZONE_ID..."
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue