mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2026-05-22 15:55:20 +02:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 76f112ac52 | |||
| 713d46c781 | |||
| 0429665173 | |||
| ac05edca77 | |||
| ad3dacf68f | |||
| 91a8996c11 | |||
| 40c4a51a2b | |||
| f8e0aaf4d2 | |||
| ad97b5a406 | |||
| b0e0c1b75f | |||
| b1fce443e9 | |||
| 66f8711055 | |||
| b7c123c281 | |||
| 9481bbf3f1 | |||
| 43ec7e821b | |||
| ca3454afbe |
+1
-1
@@ -163,7 +163,7 @@ dependencies {
|
||||
implementation 'androidx.core:core-ktx:1.12.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.11.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
|
||||
//Images
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
|
||||
|
||||
@@ -1022,15 +1022,35 @@
|
||||
return x.value
|
||||
});
|
||||
|
||||
|
||||
let settingsToUse = __DEV_SETTINGS ?? {};
|
||||
if (true) {
|
||||
for (let setting of this.Plugin?.currentPlugin?.settings) {
|
||||
if (typeof settingsToUse[setting.variable] == "undefined") {
|
||||
switch (setting?.type?.toLowerCase()) {
|
||||
case "boolean":
|
||||
settingsToUse[setting.variable] = setting.default === 'true';
|
||||
break;
|
||||
case "dropdown":
|
||||
let dropDownIndex = parseInt(setting.default);
|
||||
if (dropDownIndex) {
|
||||
settingsToUse[setting.variable] = setting.options[dropDownIndex];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(name == "enable") {
|
||||
if(parameterVals.length > 0)
|
||||
parameterVals[0] = this.Plugin.currentPlugin;
|
||||
else
|
||||
parameterVals.push(this.Plugin.currentPlugin);
|
||||
if(parameterVals.length > 1)
|
||||
parameterVals[1] = __DEV_SETTINGS;
|
||||
parameterVals[1] = settingsToUse;
|
||||
else
|
||||
parameterVals.push(__DEV_SETTINGS);
|
||||
parameterVals.push(settingsToUse);
|
||||
}
|
||||
|
||||
const func = source[name];
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.futo.platformplayer.states.StateCache
|
||||
import com.futo.platformplayer.states.StateMeta
|
||||
import com.futo.platformplayer.states.StatePayment
|
||||
import com.futo.platformplayer.states.StatePolycentric
|
||||
import com.futo.platformplayer.states.StateSync
|
||||
import com.futo.platformplayer.states.StateUpdate
|
||||
import com.futo.platformplayer.stores.FragmentedStorage
|
||||
import com.futo.platformplayer.stores.FragmentedStorageFileJson
|
||||
@@ -34,6 +35,7 @@ import com.futo.platformplayer.views.fields.DropdownFieldOptionsId
|
||||
import com.futo.platformplayer.views.fields.FieldForm
|
||||
import com.futo.platformplayer.views.fields.FormField
|
||||
import com.futo.platformplayer.views.fields.FormFieldButton
|
||||
import com.futo.platformplayer.views.fields.FormFieldWarning
|
||||
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -608,6 +610,11 @@ class Settings : FragmentedStorageFileJson() {
|
||||
@AdvancedField
|
||||
@FormField(R.string.shorts_pregenerate, FieldForm.TOGGLE, R.string.shorts_pregenerate_description, 28)
|
||||
var shortsPregenerate: Boolean = false;
|
||||
|
||||
@AdvancedField
|
||||
@FormField(R.string.shorts_fit_video, FieldForm.TOGGLE, R.string.shorts_fit_video_description, 29)
|
||||
@FormFieldWarning(R.string.shorts_fit_video_warning)
|
||||
var shortsFitVideo: Boolean = false;
|
||||
}
|
||||
|
||||
@FormField(R.string.comments, "group", R.string.comments_description, 6)
|
||||
@@ -1092,6 +1099,38 @@ class Settings : FragmentedStorageFileJson() {
|
||||
|
||||
@FormField(R.string.local_connections, FieldForm.TOGGLE, R.string.local_connections_description, 3)
|
||||
var localConnections: Boolean = true;
|
||||
|
||||
|
||||
|
||||
var syncServerUrl: String? = null;
|
||||
@FormField(R.string.relay_server, FieldForm.READONLYTEXT, -1, 6)
|
||||
val syncServer: String get() = if(syncServerUrl?.isBlank() == true) StateSync.RELAY_SERVER else syncServerUrl ?: StateSync.RELAY_SERVER;
|
||||
|
||||
@FormField(R.string.configure_sync_server, FieldForm.BUTTON, R.string.configure_sync_server_description, 7)
|
||||
fun configureSyncServer() {
|
||||
SettingsActivity.getActivity()?.let { context ->
|
||||
UIDialogs.showDialog(context, R.drawable.device_sync, false,
|
||||
"Enter the url to your relay server",
|
||||
"Using your own relay server requires a proper setup with portforwarding.\nUse at your own risk.",
|
||||
null,
|
||||
syncServerUrl ?: "",
|
||||
"YourRelayServerDomain.com", 0,
|
||||
UIDialogs.Action("Cancel", {}),
|
||||
UIDialogs.Action("Reset", {
|
||||
syncServerUrl = null;
|
||||
instance.save();
|
||||
context.reloadSettings();
|
||||
UIDialogs.toast("Sync server changes require a restart");
|
||||
}, UIDialogs.ActionStyle.ACCENT),
|
||||
UIDialogs.Action.withInput("Configure", {
|
||||
syncServerUrl = it?.text
|
||||
instance.save();
|
||||
context.reloadSettings();
|
||||
UIDialogs.toast("Sync server changes require a restart");
|
||||
}, UIDialogs.ActionStyle.PRIMARY),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FormField(R.string.info, FieldForm.GROUP, -1, 21)
|
||||
|
||||
@@ -113,8 +113,8 @@ class UIDialogs {
|
||||
currentDialog.code,
|
||||
currentDialog.defaultCloseAction,
|
||||
*currentDialog.actions.map {
|
||||
return@map Action(it.text, {
|
||||
it.action();
|
||||
return@map Action.withInput(it.text, { str ->
|
||||
it.invokeAction(str);
|
||||
multiShowDialog(context, dialogDescriptor.drop(1), finally);
|
||||
}, it.style);
|
||||
}.toTypedArray());
|
||||
@@ -203,7 +203,9 @@ class UIDialogs {
|
||||
fun showDialog(context: Context, icon: Int, text: String, textDetails: String? = null, code: String? = null, defaultCloseAction: Int, vararg actions: Action): AlertDialog {
|
||||
return showDialog(context, icon, false, text, textDetails, code, defaultCloseAction, *actions);
|
||||
}
|
||||
fun showDialog(context: Context, icon: Int, animated: Boolean, text: String, textDetails: String? = null, code: String? = null, defaultCloseAction: Int, vararg actions: Action): AlertDialog {
|
||||
fun showDialog(context: Context, icon: Int, animated: Boolean, text: String, textDetails: String? = null, code: String? = null, defaultCloseAction: Int, vararg actions: Action): AlertDialog
|
||||
= showDialog(context, icon, animated, text, textDetails, code, null, null, defaultCloseAction, *actions);
|
||||
fun showDialog(context: Context, icon: Int, animated: Boolean, text: String, textDetails: String? = null, code: String? = null, input: String?, placeholder: String?, defaultCloseAction: Int, vararg actions: Action): AlertDialog {
|
||||
val builder = AlertDialog.Builder(context);
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.dialog_multi_button, null);
|
||||
builder.setView(view);
|
||||
@@ -226,6 +228,16 @@ class UIDialogs {
|
||||
this.text = textDetails;
|
||||
}
|
||||
};
|
||||
var inputView = view.findViewById<TextView>(R.id.dialog_text_input);
|
||||
inputView.apply {
|
||||
if (input == null && placeholder == null) this.visibility = View.GONE;
|
||||
else {
|
||||
this.text = input ?: "";
|
||||
this.hint = placeholder ?: "";
|
||||
this.visibility = View.VISIBLE;
|
||||
this.textAlignment = View.TEXT_ALIGNMENT_VIEW_START
|
||||
}
|
||||
};
|
||||
view.findViewById<TextView>(R.id.dialog_text_code).apply {
|
||||
if (code == null) this.visibility = View.GONE;
|
||||
else {
|
||||
@@ -250,7 +262,7 @@ class UIDialogs {
|
||||
buttonView.textSize = 14f;
|
||||
buttonView.typeface = resources.getFont(R.font.inter_regular);
|
||||
buttonView.text = act.text;
|
||||
buttonView.setOnClickListener { act.action(); dialog.dismiss(); };
|
||||
buttonView.setOnClickListener { act.invokeAction(DialogResult(inputView?.text?.toString())); dialog.dismiss(); };
|
||||
when(act.style) {
|
||||
ActionStyle.PRIMARY -> buttonView.setBackgroundResource(R.drawable.background_button_primary);
|
||||
ActionStyle.ACCENT -> buttonView.setBackgroundResource(R.drawable.background_button_accent);
|
||||
@@ -275,7 +287,7 @@ class UIDialogs {
|
||||
};
|
||||
dialog.setOnCancelListener {
|
||||
if(defaultCloseAction >= 0 && defaultCloseAction < actions.size)
|
||||
actions[defaultCloseAction].action();
|
||||
actions[defaultCloseAction].invokeAction(DialogResult(inputView?.text?.toString()));
|
||||
}
|
||||
dialog.setOnDismissListener {
|
||||
registerDialogClosed(dialog);
|
||||
@@ -535,17 +547,36 @@ class UIDialogs {
|
||||
}
|
||||
class Action {
|
||||
val text: String;
|
||||
val action: ()->Unit;
|
||||
val action: ((DialogResult?)->Unit);
|
||||
val style: ActionStyle;
|
||||
var center: Boolean;
|
||||
|
||||
constructor(text: String, action: ()->Unit, style: ActionStyle = ActionStyle.NONE, center: Boolean = false) {
|
||||
this.text = text;
|
||||
this.action = { action() };
|
||||
this.style = style;
|
||||
this.center = center;
|
||||
}
|
||||
protected constructor(text: String, action: (DialogResult?)->Unit, style: ActionStyle = ActionStyle.NONE, center: Boolean = false) {
|
||||
this.text = text;
|
||||
this.action = action;
|
||||
this.style = style;
|
||||
this.center = center;
|
||||
}
|
||||
|
||||
fun invokeAction(input: DialogResult? = null) {
|
||||
this.action(input);
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun withInput(text: String, action: (DialogResult?)->Unit, style: ActionStyle = ActionStyle.NONE, center: Boolean = false): Action {
|
||||
return Action(text, action, style, center);
|
||||
}
|
||||
}
|
||||
}
|
||||
class DialogResult(
|
||||
val text: String?
|
||||
);
|
||||
enum class ActionStyle {
|
||||
NONE,
|
||||
PRIMARY,
|
||||
|
||||
@@ -76,9 +76,25 @@ class LoginActivity : AppCompatActivity() {
|
||||
};
|
||||
var isFirstLoad = true;
|
||||
val loginWarnings = authConfig.loginWarnings?.toMutableList() ?: mutableListOf<SourcePluginAuthConfig.Warning>();
|
||||
val uiMods = authConfig.uiMods?.toMutableList() ?: mutableListOf<SourcePluginAuthConfig.UIMod>();
|
||||
var currentScale = 100;
|
||||
var currentDesktop = false;
|
||||
webViewClient.onPageLoaded.subscribe { view, url ->
|
||||
_textUrl.setText(url ?: "");
|
||||
|
||||
if(loginWarnings.size > 0 && url != null) {
|
||||
synchronized(loginWarnings) {
|
||||
val warning = loginWarnings.find { url.matches(it.getRegex()) };
|
||||
if(warning != null) {
|
||||
if(warning.once == true)
|
||||
loginWarnings.remove(warning);
|
||||
UIDialogs.showDialog(this@LoginActivity, R.drawable.ic_warning_yellow, warning.text ?: "", warning.details ?: "", null, 0,
|
||||
UIDialogs.Action("Understood", {
|
||||
}, UIDialogs.ActionStyle.PRIMARY));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!isFirstLoad)
|
||||
return@subscribe;
|
||||
isFirstLoad = false;
|
||||
@@ -89,18 +105,34 @@ class LoginActivity : AppCompatActivity() {
|
||||
view?.evaluateJavascript("setTimeout(()=> document.querySelector(\"${authConfig.loginButton}\")?.click(), 1000)", {});
|
||||
}
|
||||
|
||||
if(loginWarnings.size > 0) {
|
||||
synchronized(loginWarnings) {
|
||||
val warning = loginWarnings.find { it.url.matches(it.getRegex()) };
|
||||
if(warning != null) {
|
||||
if(warning.once == true)
|
||||
loginWarnings.remove(warning);
|
||||
UIDialogs.showDialog(this@LoginActivity, R.drawable.ic_warning_yellow, warning.text ?: "", warning.details ?: "", null, 0,
|
||||
UIDialogs.Action("Understood", {
|
||||
}, UIDialogs.ActionStyle.PRIMARY));
|
||||
/*
|
||||
var specifiedScale = false;
|
||||
var specifiedDesktop = false;
|
||||
if(uiMods.size > 0 && url != null) {
|
||||
synchronized(uiMods) {
|
||||
val uimod = uiMods.find { url.matches(it.getRegex()) };
|
||||
if(uimod != null) {
|
||||
if(uimod.scale != null) {
|
||||
currentScale =(uimod.scale * 100).toInt();
|
||||
_webView.setInitialScale(currentScale);
|
||||
specifiedScale = true;
|
||||
}
|
||||
if(uimod.desktop != null && uimod.desktop) {
|
||||
_webView.settings.useWideViewPort = true;
|
||||
specifiedDesktop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!specifiedScale && currentScale != 100) {
|
||||
currentScale = (100).toInt();
|
||||
_webView.setInitialScale(currentScale);
|
||||
}
|
||||
if(!specifiedDesktop && currentDesktop) {
|
||||
_webView.settings.useWideViewPort = false;
|
||||
currentDesktop = false;
|
||||
}
|
||||
*/
|
||||
}
|
||||
_webView.settings.domStorageEnabled = true;
|
||||
|
||||
|
||||
@@ -365,6 +365,14 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
_fragVideoDetail.onMinimize.subscribe {
|
||||
updateSegmentPaddings();
|
||||
};
|
||||
_fragVideoDetail.onTransitioning.subscribe {
|
||||
if (it || _fragVideoDetail.state != VideoDetailFragment.State.MINIMIZED)
|
||||
_fragContainerOverlay.elevation =
|
||||
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15f, resources.displayMetrics);
|
||||
else
|
||||
_fragContainerOverlay.elevation =
|
||||
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics);
|
||||
}
|
||||
|
||||
_fragVideoDetail.onCloseEvent.subscribe {
|
||||
_fragMainHome.setPreviewsEnabled(true);
|
||||
@@ -1135,8 +1143,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
if (_fragContainerVideoDetail.visibility != View.VISIBLE)
|
||||
_fragContainerVideoDetail.visibility = View.VISIBLE;
|
||||
when (segment.state) {
|
||||
VideoDetailFragment.State.MINIMIZED -> segment.maximizeVideoDetail(false)
|
||||
VideoDetailFragment.State.CLOSED -> segment.maximizeVideoDetail(false)
|
||||
VideoDetailFragment.State.MINIMIZED -> segment.maximizeVideoDetail()
|
||||
VideoDetailFragment.State.CLOSED -> segment.maximizeVideoDetail()
|
||||
else -> {}
|
||||
}
|
||||
segment.onShown(parameter, isBack);
|
||||
@@ -1261,6 +1269,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun updateSegmentPaddings() {
|
||||
var paddingBottom = 0f;
|
||||
if (fragCurrent.hasBottomBar)
|
||||
@@ -1271,6 +1280,9 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
.toInt()
|
||||
);
|
||||
|
||||
if (_fragVideoDetail.state == VideoDetailFragment.State.MINIMIZED)
|
||||
paddingBottom += HEIGHT_VIDEO_MINIMIZED_DP;
|
||||
|
||||
_fragContainerMain.setPadding(
|
||||
0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom, resources.displayMetrics)
|
||||
.toInt()
|
||||
|
||||
+19
-1
@@ -16,7 +16,8 @@ class SourcePluginAuthConfig(
|
||||
val loginButton: String? = null,
|
||||
val domainHeadersToFind: Map<String, List<String>>? = null,
|
||||
val loginWarning: String? = null,
|
||||
val loginWarnings: List<Warning>? = null
|
||||
val loginWarnings: List<Warning>? = null,
|
||||
val uiMods: List<UIMod>? = null
|
||||
) {
|
||||
|
||||
@Serializable
|
||||
@@ -29,6 +30,23 @@ class SourcePluginAuthConfig(
|
||||
@Contextual
|
||||
private var _regex: Regex? = null;
|
||||
|
||||
fun getRegex(): Regex {
|
||||
return _regex ?: url.let {
|
||||
val reg = Regex(it);
|
||||
_regex = reg;
|
||||
return reg;
|
||||
}
|
||||
}
|
||||
}
|
||||
@Serializable
|
||||
class UIMod(
|
||||
val url: String,
|
||||
val scale: Float?,
|
||||
val desktop: Boolean?
|
||||
) {
|
||||
@Contextual
|
||||
private var _regex: Regex? = null;
|
||||
|
||||
fun getRegex(): Regex {
|
||||
return _regex ?: url.let {
|
||||
val reg = Regex(it);
|
||||
|
||||
+2
-2
@@ -199,7 +199,7 @@ class ChannelFragment : MainFragment() {
|
||||
when (v) {
|
||||
is IPlatformVideo -> {
|
||||
StatePlayer.instance.clearQueue()
|
||||
fragment.navigate<VideoDetailFragment>(v).maximizeVideoDetail(false)
|
||||
fragment.navigate<VideoDetailFragment>(v).maximizeVideoDetail()
|
||||
}
|
||||
|
||||
is IPlatformPlaylist -> {
|
||||
@@ -245,7 +245,7 @@ class ChannelFragment : MainFragment() {
|
||||
when (contentType) {
|
||||
ContentType.MEDIA -> {
|
||||
StatePlayer.instance.clearQueue()
|
||||
fragment.navigate<VideoDetailFragment>(url).maximizeVideoDetail(false)
|
||||
fragment.navigate<VideoDetailFragment>(url).maximizeVideoDetail()
|
||||
}
|
||||
|
||||
ContentType.URL -> fragment.navigate<BrowserFragment>(url)
|
||||
|
||||
+3
-3
@@ -197,9 +197,9 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
||||
StatePlayer.instance.insertToQueue(content, true);
|
||||
} else {
|
||||
if (Settings.instance.playback.shouldResumePreview(time))
|
||||
fragment.navigate<VideoDetailFragment>(content.withTimestamp(time)).maximizeVideoDetail(false);
|
||||
fragment.navigate<VideoDetailFragment>(content.withTimestamp(time)).maximizeVideoDetail();
|
||||
else
|
||||
fragment.navigate<VideoDetailFragment>(content).maximizeVideoDetail(false);
|
||||
fragment.navigate<VideoDetailFragment>(content).maximizeVideoDetail();
|
||||
}
|
||||
} else if (content is IPlatformPlaylist) {
|
||||
fragment.navigate<RemotePlaylistFragment>(content);
|
||||
@@ -218,7 +218,7 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
||||
when(contentType) {
|
||||
ContentType.MEDIA -> {
|
||||
StatePlayer.instance.clearQueue()
|
||||
fragment.navigate<VideoDetailFragment>(url).maximizeVideoDetail(false)
|
||||
fragment.navigate<VideoDetailFragment>(url).maximizeVideoDetail()
|
||||
}
|
||||
ContentType.PLAYLIST -> fragment.navigate<RemotePlaylistFragment>(url)
|
||||
ContentType.URL -> fragment.navigate<BrowserFragment>(url)
|
||||
|
||||
+1
-1
@@ -174,7 +174,7 @@ class DownloadsFragment : MainFragment() {
|
||||
.asAnyWithTop(findViewById(R.id.downloads_top)) {
|
||||
it.onClick.subscribe {
|
||||
StatePlayer.instance.clearQueue();
|
||||
_frag.navigate<VideoDetailFragment>(it).maximizeVideoDetail(false);
|
||||
_frag.navigate<VideoDetailFragment>(it).maximizeVideoDetail();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -247,7 +247,7 @@ class HistoryFragment : MainFragment() {
|
||||
val diff = v.video.duration - v.position;
|
||||
val vid: Any = if (diff > 5) { v.video.withTimestamp(v.position) } else { v.video };
|
||||
StatePlayer.instance.clearQueue();
|
||||
_fragment.navigate<VideoDetailFragment>(vid).maximizeVideoDetail(false);
|
||||
_fragment.navigate<VideoDetailFragment>(vid).maximizeVideoDetail();
|
||||
_editSearch.clearFocus();
|
||||
inputMethodManager.hideSoftInputFromWindow(_editSearch.windowToken, 0);
|
||||
|
||||
|
||||
@@ -279,6 +279,14 @@ class HomeFragment : MainFragment() {
|
||||
else {
|
||||
view.setToggle(!active);
|
||||
}
|
||||
}, { view, views, enabled ->
|
||||
val toDisable = views.filter { it != view && it.tag == "plugins" };
|
||||
if(!view.isActive)
|
||||
view.handleClick();
|
||||
for(tag in toDisable) {
|
||||
if(tag.isActive)
|
||||
tag.handleClick();
|
||||
}
|
||||
}).withTag("plugins")
|
||||
})
|
||||
else listOf())
|
||||
|
||||
+106
-50
@@ -34,12 +34,13 @@ import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.models.PlatformVideoWithTime
|
||||
import com.futo.platformplayer.models.UrlVideoWithTime
|
||||
import com.futo.platformplayer.states.StatePlayer
|
||||
import com.futo.platformplayer.views.containers.CustomMotionLayout
|
||||
import com.futo.platformplayer.views.containers.SingleViewTouchableMotionLayout
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
//region Fragment
|
||||
@UnstableApi
|
||||
class VideoDetailFragment() : MainFragment() {
|
||||
@@ -50,8 +51,8 @@ class VideoDetailFragment() : MainFragment() {
|
||||
|
||||
private var _isActive: Boolean = false;
|
||||
|
||||
private var _viewDetail : VideoDetailView? = null
|
||||
private var _motionLayout: CustomMotionLayout? = null
|
||||
private var _viewDetail : VideoDetailView? = null;
|
||||
private var _view : SingleViewTouchableMotionLayout? = null;
|
||||
|
||||
var isFullscreen : Boolean = false;
|
||||
/**
|
||||
@@ -60,6 +61,8 @@ class VideoDetailFragment() : MainFragment() {
|
||||
*/
|
||||
var isMinimizingFromFullScreen : Boolean = false;
|
||||
val onFullscreenChanged = Event1<Boolean>();
|
||||
var isTransitioning : Boolean = false
|
||||
private set;
|
||||
var isInPictureInPicture : Boolean = false
|
||||
private set;
|
||||
|
||||
@@ -75,8 +78,13 @@ class VideoDetailFragment() : MainFragment() {
|
||||
val currentUrl get() = _viewDetail?.currentUrl;
|
||||
|
||||
val onMinimize = Event0();
|
||||
val onTransitioning = Event1<Boolean>();
|
||||
val onMaximized = Event0();
|
||||
|
||||
private var _isInitialMaximize = true;
|
||||
|
||||
private val _maximizeProgress get() = _view?.progress ?: 0.0f;
|
||||
|
||||
private var _loadUrlOnCreate: UrlVideoWithTime? = null;
|
||||
private var _leavingPiP = false;
|
||||
|
||||
@@ -287,17 +295,22 @@ class VideoDetailFragment() : MainFragment() {
|
||||
|
||||
fun minimizeVideoDetail() {
|
||||
_viewDetail?.setFullscreen(false);
|
||||
_motionLayout?.transitionToState(R.id.collapsed)
|
||||
if(_view != null)
|
||||
_view!!.transitionToStart();
|
||||
}
|
||||
fun maximizeVideoDetail(instant: Boolean) {
|
||||
state = State.MAXIMIZED
|
||||
onMaximized.emit()
|
||||
if(instant) {
|
||||
_motionLayout?.setTransition(R.id.maximize)
|
||||
_motionLayout?.progress = 1f
|
||||
} else {
|
||||
_motionLayout?.transitionToState(R.id.expanded)
|
||||
fun maximizeVideoDetail(instant: Boolean = false) {
|
||||
if((_maximizeProgress > 0.9f || instant) && state != State.MAXIMIZED) {
|
||||
state = State.MAXIMIZED;
|
||||
onMaximized.emit();
|
||||
}
|
||||
_view?.let {
|
||||
if(!instant) {
|
||||
it.transitionToEnd();
|
||||
} else {
|
||||
it.progress = 1f;
|
||||
onTransitioning.emit(true);
|
||||
}
|
||||
};
|
||||
}
|
||||
fun closeVideoDetails() {
|
||||
Logger.i(TAG, "closeVideoDetails()")
|
||||
@@ -310,48 +323,83 @@ class VideoDetailFragment() : MainFragment() {
|
||||
}
|
||||
|
||||
override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
val viewDetail = VideoDetailView(this, inflater);
|
||||
|
||||
_motionLayout = viewDetail.findViewById(R.id.videodetail_root)
|
||||
viewDetail.applyFragment(this);
|
||||
viewDetail.onFullscreenChanged.subscribe(::onFullscreenChanged);
|
||||
viewDetail.onVideoChanged.subscribe(::onVideoChanged)
|
||||
viewDetail.onMinimize.subscribe {
|
||||
isMinimizingFromFullScreen = true
|
||||
_motionLayout?.transitionToState(R.id.collapsed)
|
||||
};
|
||||
viewDetail.onClose.subscribe {
|
||||
Logger.i(TAG, "onClose")
|
||||
closeVideoDetails();
|
||||
};
|
||||
viewDetail.onMaximize.subscribe { maximizeVideoDetail(it) };
|
||||
viewDetail.onEnterPictureInPicture.subscribe {
|
||||
Logger.i(TAG, "onEnterPictureInPicture")
|
||||
isInPictureInPicture = true;
|
||||
_viewDetail?.handleEnterPictureInPicture();
|
||||
_viewDetail?.invalidate();
|
||||
};
|
||||
_motionLayout!!.addTransitionListener(object : MotionLayout.TransitionListener {
|
||||
override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
|
||||
_view = inflater.inflate(R.layout.fragment_video_detail, container, false) as SingleViewTouchableMotionLayout;
|
||||
_viewDetail = _view!!.findViewById<VideoDetailView>(R.id.fragview_videodetail).also {
|
||||
it.applyFragment(this);
|
||||
it.onFullscreenChanged.subscribe(::onFullscreenChanged);
|
||||
it.onVideoChanged.subscribe(::onVideoChanged)
|
||||
it.onMinimize.subscribe {
|
||||
isMinimizingFromFullScreen = true
|
||||
_view!!.transitionToStart();
|
||||
};
|
||||
it.onClose.subscribe {
|
||||
Logger.i(TAG, "onClose")
|
||||
closeVideoDetails();
|
||||
};
|
||||
it.onMaximize.subscribe { maximizeVideoDetail(it) };
|
||||
it.onEnterPictureInPicture.subscribe {
|
||||
Logger.i(TAG, "onEnterPictureInPicture")
|
||||
isInPictureInPicture = true;
|
||||
_viewDetail?.handleEnterPictureInPicture();
|
||||
_viewDetail?.invalidate();
|
||||
};
|
||||
it.onTouchCancel.subscribe {
|
||||
val v = _view ?: return@subscribe;
|
||||
if (v.progress >= 0.5 && v.progress < 1) {
|
||||
maximizeVideoDetail();
|
||||
}
|
||||
if (v.progress < 0.5 && v.progress > 0) {
|
||||
minimizeVideoDetail();
|
||||
}
|
||||
};
|
||||
}
|
||||
_view!!.setTransitionListener(object : MotionLayout.TransitionListener {
|
||||
override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) {
|
||||
_viewDetail?.stopAllGestures()
|
||||
if (state != State.MINIMIZED && currentId == R.id.collapsed) {
|
||||
state = State.MINIMIZED
|
||||
|
||||
if (state != State.MINIMIZED && progress < 0.1) {
|
||||
state = State.MINIMIZED;
|
||||
isMinimizingFromFullScreen = false
|
||||
onMinimize.emit()
|
||||
onMinimize.emit();
|
||||
}
|
||||
else if (state != State.MAXIMIZED && progress > 0.9) {
|
||||
if (_isInitialMaximize) {
|
||||
state = State.CLOSED;
|
||||
_isInitialMaximize = false;
|
||||
}
|
||||
else {
|
||||
state = State.MAXIMIZED;
|
||||
onMaximized.emit();
|
||||
}
|
||||
}
|
||||
|
||||
if (state != State.MAXIMIZED && currentId == R.id.expanded) {
|
||||
state = State.MAXIMIZED
|
||||
onMaximized.emit()
|
||||
if (isTransitioning && (progress > 0.95 || progress < 0.05)) {
|
||||
isTransitioning = false;
|
||||
onTransitioning.emit(isTransitioning);
|
||||
|
||||
if(isInPictureInPicture) leavePictureInPictureMode(false); //Workaround to prevent getting stuck in p2p
|
||||
}
|
||||
else if (!isTransitioning && (progress < 0.95 && progress > 0.05)) {
|
||||
isTransitioning = true;
|
||||
onTransitioning.emit(isTransitioning);
|
||||
|
||||
if(isInPictureInPicture) leavePictureInPictureMode(false); //Workaround to prevent getting stuck in p2p
|
||||
}
|
||||
}
|
||||
override fun onTransitionStarted(motionLayout: MotionLayout?, startId: Int, endId: Int) {}
|
||||
override fun onTransitionTrigger(motionLayout: MotionLayout?, triggerId: Int, positive: Boolean, progress: Float) {}
|
||||
override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) {}
|
||||
})
|
||||
override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) { }
|
||||
override fun onTransitionStarted(p0: MotionLayout?, p1: Int, p2: Int) { }
|
||||
override fun onTransitionTrigger(p0: MotionLayout?, p1: Int, p2: Boolean, p3: Float) { }
|
||||
});
|
||||
|
||||
_view?.let {
|
||||
if (it.progress >= 0.5 && it.progress < 1.0)
|
||||
maximizeVideoDetail();
|
||||
if (it.progress < 0.5 && it.progress > 0.0)
|
||||
minimizeVideoDetail();
|
||||
}
|
||||
|
||||
_loadUrlOnCreate?.let { _viewDetail?.setVideo(it.url, it.timeSeconds, it.playWhenReady) };
|
||||
maximizeVideoDetail(false);
|
||||
maximizeVideoDetail();
|
||||
|
||||
SettingsActivity.settingsActivityClosed.subscribe(this) {
|
||||
updateOrientation()
|
||||
@@ -384,8 +432,7 @@ class VideoDetailFragment() : MainFragment() {
|
||||
}
|
||||
_autoRotateObserver?.startObserving()
|
||||
|
||||
_viewDetail = viewDetail
|
||||
return viewDetail
|
||||
return _view!!;
|
||||
}
|
||||
|
||||
fun onUserLeaveHint() {
|
||||
@@ -507,12 +554,21 @@ class VideoDetailFragment() : MainFragment() {
|
||||
_portraitOrientationListener?.disableListener()
|
||||
_autoRotateObserver?.stopObserving()
|
||||
|
||||
_viewDetail = null;
|
||||
_viewDetail?.let {
|
||||
_viewDetail = null;
|
||||
it.onDestroy();
|
||||
}
|
||||
_view = null;
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
_viewDetail?.let {
|
||||
_viewDetail = null;
|
||||
it.onDestroy();
|
||||
}
|
||||
|
||||
StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this);
|
||||
|
||||
Logger.i(TAG, "onDestroy");
|
||||
@@ -570,7 +626,7 @@ class VideoDetailFragment() : MainFragment() {
|
||||
}
|
||||
|
||||
updateOrientation();
|
||||
_motionLayout?.isInteractionEnabled = !fullscreen
|
||||
_view?.allowMotion = !fullscreen;
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
+137
-60
@@ -18,10 +18,11 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.support.v4.media.session.PlaybackStateCompat
|
||||
import android.text.Spanned
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.util.Rational
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
@@ -32,7 +33,6 @@ import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.C
|
||||
@@ -134,9 +134,9 @@ import com.futo.platformplayer.views.FeedStyle
|
||||
import com.futo.platformplayer.views.LoaderView
|
||||
import com.futo.platformplayer.views.MonetizationView
|
||||
import com.futo.platformplayer.views.adapters.feedtypes.PreviewVideoView
|
||||
import com.futo.platformplayer.views.behavior.TouchInterceptFrameLayout
|
||||
import com.futo.platformplayer.views.casting.CastView
|
||||
import com.futo.platformplayer.views.comments.AddCommentView
|
||||
import com.futo.platformplayer.views.containers.CustomMotionLayout
|
||||
import com.futo.platformplayer.views.others.CreatorThumbnail
|
||||
import com.futo.platformplayer.views.overlays.ChaptersOverlay
|
||||
import com.futo.platformplayer.views.overlays.DescriptionOverlay
|
||||
@@ -167,7 +167,6 @@ import com.futo.polycentric.core.PolycentricProfile
|
||||
import com.futo.polycentric.core.fullyBackfillServersAnnounceExceptions
|
||||
import com.futo.polycentric.core.toURLInfoSystemLinkUrl
|
||||
import com.google.protobuf.ByteString
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -183,7 +182,7 @@ import kotlin.math.abs
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
@UnstableApi
|
||||
class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) : FrameLayout(inflater.context) {
|
||||
class VideoDetailView : ConstraintLayout {
|
||||
private val TAG = "VideoDetailView"
|
||||
|
||||
lateinit var fragment: VideoDetailFragment;
|
||||
@@ -212,7 +211,7 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) :
|
||||
private val _timeBar: TimeBar;
|
||||
private var _upNext: UpNextView;
|
||||
|
||||
private val rootView: CustomMotionLayout;
|
||||
private val rootView: ConstraintLayout;
|
||||
|
||||
private val _title: TextView;
|
||||
private val _subTitle: TextView;
|
||||
@@ -241,6 +240,7 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) :
|
||||
|
||||
private val _commentsList: CommentsList;
|
||||
|
||||
private var _minimizeProgress: Float = 0f;
|
||||
private val _buttonSubscribe: SubscribeButton;
|
||||
|
||||
private val _buttonPins: RoundButtonGroup;
|
||||
@@ -262,7 +262,7 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) :
|
||||
private val _textResume: TextView;
|
||||
private val _layoutResume: LinearLayout;
|
||||
private var _jobHideResume: Job? = null;
|
||||
private val _layoutPlayerContainer: FrameLayout;
|
||||
private val _layoutPlayerContainer: TouchInterceptFrameLayout;
|
||||
private val _layoutChangeBottomSection: LinearLayout;
|
||||
|
||||
//Overlays
|
||||
@@ -340,6 +340,7 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) :
|
||||
|
||||
val onShouldEnterPictureInPictureChanged = Event0();
|
||||
|
||||
val onTouchCancel = Event0();
|
||||
private var _lastPositionSaveTime: Long = -1;
|
||||
|
||||
private val DP_5 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics);
|
||||
@@ -357,8 +358,9 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) :
|
||||
Pair(0, 10) //around live, try every 10 seconds
|
||||
);
|
||||
|
||||
init {
|
||||
inflater.inflate(R.layout.fragview_video_detail, this)
|
||||
@androidx.annotation.OptIn(UnstableApi::class)
|
||||
constructor(context: Context, attrs : AttributeSet? = null) : super(context, attrs) {
|
||||
inflate(context, R.layout.fragview_video_detail, this);
|
||||
|
||||
//Declare Views
|
||||
rootView = findViewById(R.id.videodetail_root);
|
||||
@@ -411,7 +413,8 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) :
|
||||
_textSkip = findViewById(R.id.text_skip);
|
||||
_layoutResume = findViewById(R.id.layout_resume);
|
||||
_textResume = findViewById(R.id.text_resume);
|
||||
_layoutPlayerContainer = findViewById(R.id.layout_player_container)
|
||||
_layoutPlayerContainer = findViewById(R.id.layout_player_container);
|
||||
_layoutPlayerContainer.onClick.subscribe { onMaximize.emit(false); };
|
||||
|
||||
_layoutRating = findViewById(R.id.layout_rating);
|
||||
_textDislikes = findViewById(R.id.text_dislikes);
|
||||
@@ -606,6 +609,10 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) :
|
||||
updatePlaybackTracking(position);
|
||||
};
|
||||
|
||||
_player.onVideoClicked.subscribe {
|
||||
if(_minimizeProgress < 0.5)
|
||||
onMaximize.emit(false);
|
||||
}
|
||||
_player.onSourceChanged.subscribe(::onSourceChanged);
|
||||
_player.onSourceEnded.subscribe {
|
||||
if (!fragment.isInPictureInPicture) {
|
||||
@@ -888,48 +895,6 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) :
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var currentState = R.id.expanded
|
||||
|
||||
rootView.addTransitionListener(object : MotionLayout.TransitionListener {
|
||||
override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
|
||||
when (currentId) {
|
||||
R.id.collapsed -> {
|
||||
_player.gestureControl.setOnClickListener {
|
||||
fragment.maximizeVideoDetail(false)
|
||||
}
|
||||
_player.gestureControl.controlsEnabled = false
|
||||
}
|
||||
|
||||
R.id.expanded -> {
|
||||
_layoutResume.alpha = 1f
|
||||
_cast.setButtonAlpha(1f)
|
||||
_player.lockControlsAlpha(false)
|
||||
_player.hideControls(false)
|
||||
|
||||
_player.gestureControl.controlsEnabled = true
|
||||
_player.gestureControl.setOnClickListener(null)
|
||||
}
|
||||
}
|
||||
currentState = currentId
|
||||
if(currentId == R.id.full_screen_gesture) {
|
||||
setFullscreen(true)
|
||||
motionLayout?.transitionToState(R.id.expanded)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTransitionStarted(motionLayout: MotionLayout?, startId: Int, endId: Int) {
|
||||
if (currentState == R.id.expanded) {
|
||||
_layoutResume.alpha = 0f
|
||||
_cast.setButtonAlpha(0f)
|
||||
_player.lockControlsAlpha(true)
|
||||
_player.hideControls(true);
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTransitionTrigger(motionLayout: MotionLayout?, triggerId: Int, positive: Boolean, progress: Float) { }
|
||||
override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) {}
|
||||
})
|
||||
}
|
||||
|
||||
val _trackingUpdateTimeLock = Object();
|
||||
@@ -1040,8 +1005,7 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) :
|
||||
}
|
||||
}
|
||||
_slideUpOverlay?.hide();
|
||||
} else null,
|
||||
if(video is JSVideoDetails && (video as JSVideoDetails).hasVODEvents())
|
||||
} else if(video is JSVideoDetails && (video as JSVideoDetails).hasVODEvents())
|
||||
RoundButton(context, R.drawable.ic_chat, context.getString(R.string.vod_chat), TAG_VODCHAT) {
|
||||
video?.let {
|
||||
try {
|
||||
@@ -1190,7 +1154,7 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) :
|
||||
//Recover cancelled loads
|
||||
if(video == null) {
|
||||
val t = (lastPositionMilliseconds / 1000.0f).roundToLong();
|
||||
if(_searchVideo != null)
|
||||
if(_searchVideo != null && !wasLoginCall)
|
||||
setVideoOverview(_searchVideo!!, true, t);
|
||||
else if(_url != null && !wasLoginCall)
|
||||
setVideo(_url!!, t, _playWhenReady);
|
||||
@@ -2237,6 +2201,16 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
|
||||
if (ev?.actionMasked == MotionEvent.ACTION_CANCEL ||
|
||||
ev?.actionMasked == MotionEvent.ACTION_POINTER_DOWN ||
|
||||
ev?.actionMasked == MotionEvent.ACTION_POINTER_UP) {
|
||||
onTouchCancel.emit();
|
||||
}
|
||||
|
||||
return super.onInterceptTouchEvent(ev);
|
||||
}
|
||||
|
||||
//Actions
|
||||
private fun showVideoSettings() {
|
||||
Logger.i(TAG, "showVideoSettings")
|
||||
@@ -2607,6 +2581,7 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) :
|
||||
}
|
||||
|
||||
isPlaying = playing;
|
||||
onShouldEnterPictureInPictureChanged.emit()
|
||||
updateTracker(lastPositionMilliseconds, playing, true);
|
||||
}
|
||||
|
||||
@@ -2730,7 +2705,10 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) :
|
||||
Logger.i(TAG, "handleFullScreen(fullscreen=$fullscreen)")
|
||||
|
||||
if(fullscreen) {
|
||||
val lp = _container_content.layoutParams as ConstraintLayout.LayoutParams;
|
||||
_container_content.visibility = GONE
|
||||
_layoutPlayerContainer.setPadding(0, 0, 0, 0);
|
||||
|
||||
val lp = _container_content.layoutParams as LayoutParams;
|
||||
lp.topMargin = 0;
|
||||
_container_content.layoutParams = lp;
|
||||
|
||||
@@ -2741,7 +2719,10 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) :
|
||||
setProgressBarOverlayed(null);
|
||||
}
|
||||
else {
|
||||
val lp = _container_content.layoutParams as ConstraintLayout.LayoutParams;
|
||||
_container_content.visibility = VISIBLE
|
||||
_layoutPlayerContainer.setPadding(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, Resources.getSystem().displayMetrics).toInt());
|
||||
|
||||
val lp = _container_content.layoutParams as LayoutParams;
|
||||
lp.topMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, -18.0f, Resources.getSystem().displayMetrics).toInt();
|
||||
_container_content.layoutParams = lp;
|
||||
|
||||
@@ -2970,7 +2951,7 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) :
|
||||
hideAddTo()
|
||||
|
||||
onVideoClicked.subscribe { video, _ ->
|
||||
fragment.navigate<VideoDetailFragment>(video).maximizeVideoDetail(false)
|
||||
fragment.navigate<VideoDetailFragment>(video).maximizeVideoDetail()
|
||||
}
|
||||
|
||||
onChannelClicked.subscribe {
|
||||
@@ -3017,12 +2998,17 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) :
|
||||
_container_content.visibility = GONE
|
||||
|
||||
_player.fillHeight(false)
|
||||
_layoutPlayerContainer.setPadding(0, 0, 0, 0);
|
||||
}
|
||||
fun handleLeavePictureInPicture() {
|
||||
Logger.i(TAG, "handleLeavePictureInPicture")
|
||||
|
||||
if(!_player.isFullScreen) {
|
||||
_container_content.visibility = VISIBLE
|
||||
_player.fitHeight();
|
||||
_layoutPlayerContainer.setPadding(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, Resources.getSystem().displayMetrics).toInt());
|
||||
} else {
|
||||
_layoutPlayerContainer.setPadding(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
fun getPictureInPictureParams() : PictureInPictureParams {
|
||||
@@ -3127,6 +3113,53 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) :
|
||||
}
|
||||
}
|
||||
|
||||
//Animation related setters
|
||||
fun setMinimizeProgress(progress : Float) {
|
||||
_minimizeProgress = progress;
|
||||
_player.lockControlsAlpha(progress < 0.9);
|
||||
_layoutPlayerContainer.shouldInterceptTouches = progress < 0.95;
|
||||
|
||||
if(progress > 0.9) {
|
||||
if(_minimize_controls.visibility != View.GONE)
|
||||
_minimize_controls.visibility = View.GONE;
|
||||
}
|
||||
else if(_minimize_controls.visibility != View.VISIBLE) {
|
||||
_minimize_controls.visibility = View.VISIBLE;
|
||||
}
|
||||
|
||||
//Switching video to fill
|
||||
if(progress > 0.25) {
|
||||
if(!_player.isFullScreen && _player.layoutParams.height != WRAP_CONTENT) {
|
||||
_player.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT);
|
||||
if(!fragment.isInPictureInPicture) {
|
||||
_player.fitHeight();
|
||||
_layoutPlayerContainer.setPadding(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, Resources.getSystem().displayMetrics).toInt());
|
||||
}
|
||||
else {
|
||||
_layoutPlayerContainer.setPadding(0, 0, 0, 0);
|
||||
}
|
||||
_cast.layoutParams = _cast.layoutParams.apply {
|
||||
(this as MarginLayoutParams).bottomMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, resources.displayMetrics).toInt();
|
||||
};
|
||||
setProgressBarOverlayed(false);
|
||||
_player.hideControls(false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(_player.layoutParams.height == WRAP_CONTENT) {
|
||||
_player.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
|
||||
_player.fillHeight(true)
|
||||
_cast.layoutParams = _cast.layoutParams.apply {
|
||||
(this as MarginLayoutParams).bottomMargin = 0;
|
||||
};
|
||||
setProgressBarOverlayed(true);
|
||||
_player.hideControls(false);
|
||||
|
||||
_layoutPlayerContainer.setPadding(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setPolycentricProfile(profile: PolycentricProfile?, animate: Boolean) {
|
||||
_polycentricProfile = profile
|
||||
|
||||
@@ -3150,8 +3183,42 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) :
|
||||
}
|
||||
|
||||
fun setProgressBarOverlayed(isOverlayed: Boolean?) {
|
||||
Logger.v(TAG, "setProgressBarOverlayed(isOverlayed: ${isOverlayed ?: "null"})")
|
||||
isOverlayed?.let { _cast.setProgressBarOverlayed(it) }
|
||||
Logger.v(TAG, "setProgressBarOverlayed(isOverlayed: ${isOverlayed ?: "null"})");
|
||||
isOverlayed?.let{ _cast.setProgressBarOverlayed(it) };
|
||||
|
||||
if(isOverlayed == null) {
|
||||
//For now this seems to be the best way to keep it updated?
|
||||
_playerProgress.layoutParams = _playerProgress.layoutParams.apply {
|
||||
(this as MarginLayoutParams).bottomMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, -12f, resources.displayMetrics).toInt();
|
||||
};
|
||||
_playerProgress.elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2f, resources.displayMetrics);
|
||||
}
|
||||
else if(isOverlayed) {
|
||||
_playerProgress.layoutParams = _playerProgress.layoutParams.apply {
|
||||
(this as MarginLayoutParams).bottomMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, -2f, resources.displayMetrics).toInt();
|
||||
};
|
||||
_playerProgress.elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics);
|
||||
}
|
||||
else {
|
||||
_playerProgress.layoutParams = _playerProgress.layoutParams.apply {
|
||||
(this as MarginLayoutParams).bottomMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6f, resources.displayMetrics).toInt();
|
||||
};
|
||||
_playerProgress.elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2f, resources.displayMetrics);
|
||||
}
|
||||
}
|
||||
fun setContentAlpha(alpha: Float) {
|
||||
_container_content.alpha = alpha;
|
||||
}
|
||||
fun setControllerAlpha(alpha: Float) {
|
||||
_layoutResume.alpha = alpha;
|
||||
_player.videoControls.alpha = alpha;
|
||||
_cast.setButtonAlpha(alpha);
|
||||
}
|
||||
fun setMinimizeControlsAlpha(alpha : Float) {
|
||||
_minimize_controls.alpha = alpha;
|
||||
val clickable = alpha > 0.9;
|
||||
if(_minimize_controls.isClickable != clickable)
|
||||
_minimize_controls.isClickable = clickable;
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration?) {
|
||||
@@ -3163,6 +3230,16 @@ class VideoDetailView(fragment: VideoDetailFragment, inflater: LayoutInflater) :
|
||||
}
|
||||
}
|
||||
|
||||
fun setVideoMinimize(value : Float) {
|
||||
val padRight = (resources.displayMetrics.widthPixels * 0.70 * value).toInt()
|
||||
_player.setPadding(0, _player.paddingTop, padRight, 0)
|
||||
_cast.setPadding(0, _cast.paddingTop, padRight, 0)
|
||||
}
|
||||
|
||||
fun setTopPadding(value: Float) {
|
||||
_player.setPadding(_player.paddingLeft, value.toInt(), _player.paddingRight, 0)
|
||||
}
|
||||
|
||||
//Tasks
|
||||
private val _taskLoadVideo = if(!isInEditMode) TaskHandler<String, IPlatformVideoDetails>(
|
||||
StateApp.instance.scopeGetter,
|
||||
|
||||
@@ -57,9 +57,12 @@ class StateSync {
|
||||
return
|
||||
}
|
||||
|
||||
var relayServerUrl = Settings.instance.synchronization.syncServer;
|
||||
Logger.i(TAG, "Relay used: ${relayServerUrl}");
|
||||
|
||||
syncService = SyncService(
|
||||
SERVICE_NAME,
|
||||
RELAY_SERVER,
|
||||
relayServerUrl,
|
||||
RELAY_PUBLIC_KEY,
|
||||
APP_ID,
|
||||
StoreBasedSyncDatabaseProvider(),
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import androidx.core.view.children
|
||||
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@@ -28,6 +29,8 @@ import kotlinx.coroutines.launch
|
||||
class ToggleBar : LinearLayout {
|
||||
private val _tagsContainer: LinearLayout;
|
||||
|
||||
private var allowLongPress: Boolean = false;
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
}
|
||||
@@ -48,12 +51,31 @@ class ToggleBar : LinearLayout {
|
||||
for(button in buttons) {
|
||||
_tagsContainer.addView(ToggleTagView(context).apply {
|
||||
if(button.icon > 0)
|
||||
this.setInfo(button.icon, button.name, button.isActive, button.isButton);
|
||||
this.setInfo(button.icon, button.name, button.isActive, button.isButton, button.tag);
|
||||
else if(button.iconVariable != null)
|
||||
this.setInfo(button.iconVariable, button.name, button.isActive, button.isButton);
|
||||
this.setInfo(button.iconVariable, button.name, button.isActive, button.isButton, button.tag);
|
||||
else
|
||||
this.setInfo(button.name, button.isActive, button.isButton);
|
||||
this.setInfo(button.name, button.isActive, button.isButton, button.tag);
|
||||
this.onClick.subscribe({ view, enabled -> button.action(view, enabled); });
|
||||
if(allowLongPress) {
|
||||
this.onLongClick.subscribe({ view, enabled ->
|
||||
for (tagView in _tagsContainer.children.filter { it is ToggleTagView }) {
|
||||
if (tagView != view && tagView is ToggleTagView && !tagView.isButton) {
|
||||
if (enabled && !tagView.isActive) {
|
||||
tagView.handleClick();
|
||||
} else if (!enabled && tagView.isActive) {
|
||||
tagView.handleClick();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
else if(button.actionLong != null) {
|
||||
this.onLongClick.subscribe({ view, enabled ->
|
||||
val tags = _tagsContainer.children.filter { it is ToggleTagView }.map { it as ToggleTagView }.toList();
|
||||
button.actionLong!!(view, tags, enabled);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -63,16 +85,18 @@ class ToggleBar : LinearLayout {
|
||||
val icon: Int;
|
||||
val iconVariable: ImageVariable?;
|
||||
val action: (ToggleTagView, Boolean)->Unit;
|
||||
val actionLong: ((ToggleTagView, List<ToggleTagView>, Boolean) -> Unit)?;
|
||||
val isActive: Boolean;
|
||||
var isButton: Boolean = false
|
||||
private set;
|
||||
var tag: String? = null;
|
||||
|
||||
constructor(name: String, icon: ImageVariable?, isActive: Boolean = false, action: (ToggleTagView, Boolean)->Unit) {
|
||||
constructor(name: String, icon: ImageVariable?, isActive: Boolean = false, action: (ToggleTagView, Boolean)->Unit, actionLong: ((ToggleTagView, List<ToggleTagView>, Boolean)->Unit)? = null) {
|
||||
this.name = name;
|
||||
this.icon = 0;
|
||||
this.iconVariable = icon;
|
||||
this.action = action;
|
||||
this.actionLong = actionLong;
|
||||
this.isActive = isActive;
|
||||
}
|
||||
constructor(name: String, icon: Int, isActive: Boolean = false, action: (ToggleTagView, Boolean)->Unit) {
|
||||
@@ -80,6 +104,7 @@ class ToggleBar : LinearLayout {
|
||||
this.icon = icon;
|
||||
this.iconVariable = null;
|
||||
this.action = action;
|
||||
this.actionLong = null;
|
||||
this.isActive = isActive;
|
||||
}
|
||||
constructor(name: String, isActive: Boolean = false, action: (ToggleTagView, Boolean)->Unit) {
|
||||
@@ -87,6 +112,7 @@ class ToggleBar : LinearLayout {
|
||||
this.icon = 0;
|
||||
this.iconVariable = null;
|
||||
this.action = action;
|
||||
this.actionLong = null;
|
||||
this.isActive = isActive;
|
||||
}
|
||||
|
||||
|
||||
@@ -119,8 +119,6 @@ class GestureControlView : LinearLayout {
|
||||
|
||||
var fullScreenGestureEnabled = true
|
||||
|
||||
var controlsEnabled = true
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
|
||||
LayoutInflater.from(context).inflate(R.layout.view_gesture_controls, this, true);
|
||||
|
||||
@@ -352,10 +350,6 @@ class GestureControlView : LinearLayout {
|
||||
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
||||
val ev = event ?: return super.onTouchEvent(event);
|
||||
|
||||
if(!controlsEnabled) {
|
||||
return super.onTouchEvent(ev)
|
||||
}
|
||||
|
||||
if (ev.action == MotionEvent.ACTION_UP && _speedHolding) {
|
||||
_speedHolding = false
|
||||
hideHoldSpeedControls()
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
package com.futo.platformplayer.views.containers
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewConfiguration
|
||||
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||
import com.futo.platformplayer.R
|
||||
import kotlin.math.abs
|
||||
|
||||
class CustomMotionLayout(context: Context, attributeSet: AttributeSet? = null) :
|
||||
MotionLayout(context, attributeSet) {
|
||||
|
||||
private val viewToDetectTouch by lazy {
|
||||
findViewById<View>(R.id.layout_player_container) //TODO move to Attributes
|
||||
}
|
||||
private val viewToDetectTouch2 by lazy {
|
||||
findViewById<View>(R.id.minimize_controls) //TODO move to Attributes
|
||||
}
|
||||
|
||||
private var savedActionDown: MotionEvent? = null
|
||||
private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
||||
|
||||
// intercepting touch events is necessary because something to do with PlayerControlView makes things not work
|
||||
override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
|
||||
val ev = event ?: return super.onInterceptTouchEvent(null)
|
||||
|
||||
// special touch interception logic is unnecessary if interaction is disabled
|
||||
if (!isInteractionEnabled) {
|
||||
return super.onInterceptTouchEvent(ev)
|
||||
}
|
||||
|
||||
when (ev.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
val viewRect = Rect()
|
||||
viewToDetectTouch.getHitRect(viewRect)
|
||||
val isInView = viewRect.contains(ev.x.toInt(), ev.y.toInt())
|
||||
viewToDetectTouch2.getHitRect(viewRect)
|
||||
val isInView2 = viewRect.contains(ev.x.toInt(), ev.y.toInt())
|
||||
|
||||
// Don't intercept touches if they are outside of the player or the mini player controls
|
||||
if (!isInView && !isInView2) {
|
||||
return false
|
||||
}
|
||||
|
||||
val thing = super.onInterceptTouchEvent(ev)
|
||||
// If the MotionLayout is already intercepting this touch then don't track it
|
||||
if (thing) {
|
||||
return true
|
||||
}
|
||||
|
||||
// MotionLayout didn't intercept the touch but the touch is over the player/mini controls views
|
||||
// in the future the class will
|
||||
// save the touch event for later
|
||||
// need to replay this initial touch to the MotionLayout if it ends up turning into a drag
|
||||
// return false because that matches the return from the super call above
|
||||
savedActionDown?.recycle() // Recycle the old event to prevent memory leaks (if for some reason it wasn't cleaned up in the other code paths)
|
||||
savedActionDown = MotionEvent.obtain(ev)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
val localSavedActionDown = savedActionDown
|
||||
|
||||
// only handle the move event if there is a saved action stored
|
||||
// then check to see if it has turned into a drag
|
||||
if (localSavedActionDown != null) {
|
||||
val dy = abs(ev.y - localSavedActionDown.y)
|
||||
if (dy > touchSlop) {
|
||||
// if it has turned into a drag then
|
||||
// replay the down action saved earlier
|
||||
// clean up our data
|
||||
// return true so that the MotionLayout's onTouchEvent will receive future events for this gesture
|
||||
//
|
||||
// it is necessary to replay the down action because otherwise MotionLayout will not always initialize the drag correctly
|
||||
super.onTouchEvent(localSavedActionDown)
|
||||
localSavedActionDown.recycle() // Clean up the saved event after replaying
|
||||
savedActionDown = null
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if it's an up or cancel action clean up our tracking
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||
savedActionDown?.recycle()
|
||||
savedActionDown = null
|
||||
}
|
||||
}
|
||||
|
||||
// since the function hasn't handled the even this far send it to the parent class
|
||||
return super.onInterceptTouchEvent(ev)
|
||||
}
|
||||
|
||||
// onTouchEvent is necessary to make sure that only touch and drag on the video triggers the animation (instead of everywhere on the screen)
|
||||
@SuppressLint("ClickableViewAccessibility") // pretty sure this issue doesn't apply
|
||||
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
||||
val ev = event ?: return super.onTouchEvent(null)
|
||||
|
||||
// special touch event handling logic is unnecessary if interaction is disabled
|
||||
if (!isInteractionEnabled) {
|
||||
return super.onTouchEvent(ev)
|
||||
}
|
||||
|
||||
val viewRect = Rect()
|
||||
viewToDetectTouch.getHitRect(viewRect)
|
||||
val isInView = viewRect.contains(ev.x.toInt(), ev.y.toInt())
|
||||
viewToDetectTouch2.getHitRect(viewRect)
|
||||
val isInView2 = viewRect.contains(ev.x.toInt(), ev.y.toInt())
|
||||
|
||||
// don't want to handle touches outside of the player/mini controls views
|
||||
if ((!isInView && !isInView2) && event.actionMasked == MotionEvent.ACTION_DOWN) {
|
||||
return false
|
||||
}
|
||||
return super.onTouchEvent(event)
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package com.futo.platformplayer.views.containers
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.util.AttributeSet
|
||||
import android.view.GestureDetector
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||
import com.futo.platformplayer.R
|
||||
|
||||
class SingleViewTouchableMotionLayout(context: Context, attributeSet: AttributeSet? = null) : MotionLayout(context, attributeSet) {
|
||||
|
||||
private val viewToDetectTouch by lazy {
|
||||
findViewById<View>(R.id.touchContainer) //TODO move to Attributes
|
||||
}
|
||||
private val viewRect = Rect()
|
||||
private var touchStarted = false
|
||||
private val transitionListenerList = mutableListOf<TransitionListener?>()
|
||||
|
||||
var allowMotion : Boolean = true;
|
||||
|
||||
init {
|
||||
addTransitionListener(object : TransitionListener {
|
||||
override fun onTransitionChange(p0: MotionLayout?, p1: Int, p2: Int, p3: Float) {
|
||||
}
|
||||
|
||||
override fun onTransitionCompleted(p0: MotionLayout?, p1: Int) {
|
||||
touchStarted = false
|
||||
}
|
||||
|
||||
override fun onTransitionStarted(p0: MotionLayout?, p1: Int, p2: Int) {
|
||||
}
|
||||
|
||||
override fun onTransitionTrigger(p0: MotionLayout?, p1: Int, p2: Boolean, p3: Float) {
|
||||
}
|
||||
})
|
||||
|
||||
super.setTransitionListener(object : TransitionListener {
|
||||
override fun onTransitionChange(p0: MotionLayout?, p1: Int, p2: Int, p3: Float) {
|
||||
transitionListenerList.filterNotNull()
|
||||
.forEach { it.onTransitionChange(p0, p1, p2, p3) }
|
||||
}
|
||||
|
||||
override fun onTransitionCompleted(p0: MotionLayout?, p1: Int) {
|
||||
transitionListenerList.filterNotNull()
|
||||
.forEach { it.onTransitionCompleted(p0, p1) }
|
||||
}
|
||||
|
||||
override fun onTransitionStarted(p0: MotionLayout?, p1: Int, p2: Int) {
|
||||
}
|
||||
|
||||
override fun onTransitionTrigger(p0: MotionLayout?, p1: Int, p2: Boolean, p3: Float) {
|
||||
}
|
||||
})
|
||||
|
||||
//isInteractionEnabled = false;
|
||||
}
|
||||
|
||||
override fun setTransitionListener(listener: TransitionListener?) {
|
||||
addTransitionListener(listener)
|
||||
}
|
||||
|
||||
override fun addTransitionListener(listener: TransitionListener?) {
|
||||
transitionListenerList += listener
|
||||
}
|
||||
|
||||
//This always triggers, workaround calling super.onTouchEvent
|
||||
//Blocks click events underneath
|
||||
override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
|
||||
if(!allowMotion)
|
||||
return false;
|
||||
if(event != null) {
|
||||
when (event.actionMasked) {
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||
touchStarted = false
|
||||
return super.onTouchEvent(event) && false;
|
||||
}
|
||||
}
|
||||
if (!touchStarted) {
|
||||
viewToDetectTouch.getHitRect(viewRect);
|
||||
val isInView = viewRect.contains(event.x.toInt(), event.y.toInt());
|
||||
touchStarted = isInView
|
||||
}
|
||||
}
|
||||
return touchStarted && super.onTouchEvent(event) && false;
|
||||
}
|
||||
|
||||
|
||||
//Not triggered on its own due to child views, intercept is used instead.
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,29 @@ class RadioGroupView : FlexboxLayout {
|
||||
radioView.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
|
||||
radioView.setInfo(option.first, initiallySelectedOptions.contains(option.second));
|
||||
radioView.setPadding(_padding_px, _padding_px, _padding_px, _padding_px);
|
||||
if(multiSelect)
|
||||
radioView.onLongClick.subscribe {
|
||||
val selected = !radioView.selected;
|
||||
if (selected) {
|
||||
selectedOptions.clear();
|
||||
for(v in radioViews)
|
||||
v.setIsSelected(true);
|
||||
selectedOptions.addAll(options.map { it.second });
|
||||
} else {
|
||||
if(atLeastOne) {
|
||||
for(v in radioViews)
|
||||
v.setIsSelected(false);
|
||||
selectedOptions.clear();
|
||||
selectedOptions.add(option.second);
|
||||
}
|
||||
else {
|
||||
for(v in radioViews)
|
||||
v.setIsSelected(false);
|
||||
selectedOptions.clear();
|
||||
}
|
||||
}
|
||||
onSelectedChange.emit(selectedOptions);
|
||||
}
|
||||
radioView.onClick.subscribe {
|
||||
val selected = !radioView.selected;
|
||||
if (selected) {
|
||||
|
||||
@@ -20,6 +20,7 @@ class RadioView : LinearLayout {
|
||||
|
||||
val selected get() = _selected;
|
||||
var onClick = Event0();
|
||||
var onLongClick = Event0();
|
||||
var onSelectedChange = Event1<Boolean>();
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
|
||||
@@ -32,6 +33,13 @@ class RadioView : LinearLayout {
|
||||
setIsSelected(!_selected)
|
||||
}
|
||||
};
|
||||
_root.setOnLongClickListener {
|
||||
onLongClick.emit();
|
||||
if (_handleClick) {
|
||||
setIsSelected(!_selected)
|
||||
}
|
||||
return@setOnLongClickListener true;
|
||||
}
|
||||
|
||||
_root.setBackgroundResource(R.drawable.background_radio_unselected);
|
||||
_textTag.setTextColor(ContextCompat.getColor(context, R.color.gray_67));
|
||||
|
||||
@@ -23,12 +23,16 @@ class ToggleTagView : LinearLayout {
|
||||
private var _text: String = "";
|
||||
private var _image: ImageView;
|
||||
|
||||
var tag: String? = null
|
||||
private set;
|
||||
|
||||
var isActive: Boolean = false
|
||||
private set;
|
||||
var isButton: Boolean = false
|
||||
private set;
|
||||
|
||||
var onClick = Event2<ToggleTagView, Boolean>();
|
||||
var onLongClick = Event2<ToggleTagView, Boolean>();
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
|
||||
LayoutInflater.from(context).inflate(R.layout.view_toggle_tag, this, true);
|
||||
@@ -36,10 +40,25 @@ class ToggleTagView : LinearLayout {
|
||||
_textTag = findViewById(R.id.text_tag);
|
||||
_image = findViewById(R.id.image_tag);
|
||||
_root.setOnClickListener {
|
||||
if(!isButton)
|
||||
setToggle(!isActive);
|
||||
onClick.emit(this, isActive);
|
||||
handleClick();
|
||||
}
|
||||
_root.setOnLongClickListener {
|
||||
if(onLongClick.hasListeners())
|
||||
onLongClick.emit(this, isActive);
|
||||
else {
|
||||
if(!isButton) {
|
||||
setToggle(!isActive);
|
||||
}
|
||||
onClick.emit(this, isActive);
|
||||
}
|
||||
return@setOnLongClickListener true;
|
||||
}
|
||||
}
|
||||
|
||||
fun handleClick() {
|
||||
if(!isButton)
|
||||
setToggle(!isActive);
|
||||
onClick.emit(this, isActive);
|
||||
}
|
||||
|
||||
fun setToggle(isActive: Boolean) {
|
||||
@@ -70,9 +89,10 @@ class ToggleTagView : LinearLayout {
|
||||
_image.visibility = View.VISIBLE;
|
||||
_textTag.visibility = if(!toggle.name.isNullOrEmpty()) View.VISIBLE else View.GONE;
|
||||
this.isButton = isButton;
|
||||
tag = toggle.tag;
|
||||
}
|
||||
|
||||
fun setInfo(imageResource: Int, text: String, isActive: Boolean, isButton: Boolean = false) {
|
||||
fun setInfo(imageResource: Int, text: String, isActive: Boolean, isButton: Boolean = false, tag: String? = null) {
|
||||
_text = text;
|
||||
_textTag.text = text;
|
||||
setToggle(isActive);
|
||||
@@ -80,8 +100,9 @@ class ToggleTagView : LinearLayout {
|
||||
_image.visibility = View.VISIBLE;
|
||||
_textTag.visibility = if(!text.isNullOrEmpty()) View.VISIBLE else View.GONE;
|
||||
this.isButton = isButton;
|
||||
this.tag = tag;
|
||||
}
|
||||
fun setInfo(image: ImageVariable, text: String, isActive: Boolean, isButton: Boolean = false) {
|
||||
fun setInfo(image: ImageVariable, text: String, isActive: Boolean, isButton: Boolean = false, tag: String? = null) {
|
||||
_text = text;
|
||||
_textTag.text = text;
|
||||
setToggle(isActive);
|
||||
@@ -89,13 +110,15 @@ class ToggleTagView : LinearLayout {
|
||||
_image.visibility = View.VISIBLE;
|
||||
_textTag.visibility = if(!text.isNullOrEmpty()) View.VISIBLE else View.GONE;
|
||||
this.isButton = isButton;
|
||||
this.tag = tag;
|
||||
}
|
||||
fun setInfo(text: String, isActive: Boolean, isButton: Boolean = false) {
|
||||
fun setInfo(text: String, isActive: Boolean, isButton: Boolean = false, tag: String? = null) {
|
||||
_image.visibility = View.GONE;
|
||||
_text = text;
|
||||
_textTag.text = text;
|
||||
_textTag.visibility = if(!text.isNullOrEmpty()) View.VISIBLE else View.GONE;
|
||||
setToggle(isActive);
|
||||
this.isButton = isButton;
|
||||
this.tag = tag;
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,12 @@ import androidx.annotation.OptIn
|
||||
import androidx.media3.common.PlaybackParameters
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.ui.AspectRatioFrameLayout
|
||||
import androidx.media3.ui.DefaultTimeBar
|
||||
import androidx.media3.ui.PlayerView
|
||||
import androidx.media3.ui.TimeBar
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.Settings
|
||||
import com.futo.platformplayer.constructs.Event1
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.states.StatePlayer
|
||||
@@ -66,6 +68,11 @@ class FutoShortPlayer(context: Context, attrs: AttributeSet? = null) :
|
||||
videoView = findViewById(R.id.short_player_view)
|
||||
progressBar = findViewById(R.id.short_player_progress_bar)
|
||||
|
||||
if(Settings.instance.playback.shortsFitVideo)
|
||||
videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;
|
||||
else
|
||||
videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
|
||||
|
||||
videoView.subtitleView?.setFixedTextSize(Dimension.SP, 18F);
|
||||
|
||||
if (!isInEditMode) {
|
||||
|
||||
@@ -125,6 +125,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||
private var _lastSourceFit: Float? = null;
|
||||
private var _lastWindowWidth: Int = resources.configuration.screenWidthDp
|
||||
private var _lastWindowHeight: Int = resources.configuration.screenHeightDp
|
||||
private var _originalBottomMargin: Int = 0;
|
||||
|
||||
private var _isControlsLocked: Boolean = false;
|
||||
|
||||
@@ -153,7 +154,10 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||
val onSourceEnded = Event0();
|
||||
val onPrevious = Event0();
|
||||
val onNext = Event0();
|
||||
|
||||
val onChapterChanged = Event2<IChapter?, Boolean>();
|
||||
|
||||
val onVideoClicked = Event0();
|
||||
val onTimeBarChanged = Event2<Long, Long>();
|
||||
|
||||
val onChapterClicked = Event1<IChapter>();
|
||||
@@ -646,10 +650,13 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||
}
|
||||
|
||||
if (fullScreen) {
|
||||
_videoView.setPadding(_videoView.paddingLeft, _videoView.paddingTop, _videoView.paddingRight, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0.0f, Resources.getSystem().displayMetrics).toInt())
|
||||
val lp = background.layoutParams as ConstraintLayout.LayoutParams;
|
||||
lp.bottomMargin = 0;
|
||||
background.layoutParams = lp;
|
||||
_videoView.setBackgroundColor(Color.parseColor("#FF000000"))
|
||||
|
||||
gestureControl.hideControls();
|
||||
//videoControlsBar.visibility = View.GONE;
|
||||
_videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;
|
||||
|
||||
_videoControls_fullscreen.show();
|
||||
@@ -657,10 +664,13 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||
videoControls.visibility = View.GONE;
|
||||
}
|
||||
else {
|
||||
_videoView.setPadding(_videoView.paddingLeft, _videoView.paddingTop, _videoView.paddingRight, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7.0f, Resources.getSystem().displayMetrics).toInt())
|
||||
val lp = background.layoutParams as ConstraintLayout.LayoutParams;
|
||||
lp.bottomMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, Resources.getSystem().displayMetrics).toInt();
|
||||
background.layoutParams = lp;
|
||||
_videoView.setBackgroundColor(Color.parseColor("#00000000"))
|
||||
|
||||
gestureControl.hideControls();
|
||||
//videoControlsBar.visibility = View.VISIBLE;
|
||||
_videoView.resizeMode = _desiredResizeModePortrait;
|
||||
|
||||
videoControls.show();
|
||||
@@ -691,6 +701,10 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||
_isControlsLocked = locked;
|
||||
}
|
||||
|
||||
override fun play() {
|
||||
super.play();
|
||||
}
|
||||
|
||||
override fun onVideoSizeChanged(videoSize: VideoSize) {
|
||||
gestureControl.resetZoomPan()
|
||||
_lastSourceFit = null;
|
||||
@@ -760,6 +774,11 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||
@OptIn(UnstableApi::class)
|
||||
fun fitHeight(videoSize: VideoSize? = null) {
|
||||
Logger.i(TAG, "Video Fit Height")
|
||||
if (_originalBottomMargin != 0) {
|
||||
val layoutParams = _videoView.layoutParams as ConstraintLayout.LayoutParams
|
||||
layoutParams.setMargins(0, 0, 0, _originalBottomMargin)
|
||||
_videoView.layoutParams = layoutParams
|
||||
}
|
||||
|
||||
var h = videoSize?.height ?: lastVideoSource?.height ?: exoPlayer?.player?.videoSize?.height
|
||||
?: 0
|
||||
@@ -800,13 +819,15 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||
}
|
||||
_videoView.resizeMode = _desiredResizeModePortrait
|
||||
|
||||
val marginBottom =
|
||||
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7f, resources.displayMetrics)
|
||||
val height = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
_lastSourceFit!!,
|
||||
resources.displayMetrics
|
||||
)
|
||||
_videoView.setPadding(_videoView.paddingLeft, _videoView.paddingTop, _videoView.paddingRight, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7.0f, Resources.getSystem().displayMetrics).toInt())
|
||||
val rootParams = LayoutParams(LayoutParams.MATCH_PARENT, (height).toInt())
|
||||
val rootParams = LayoutParams(LayoutParams.MATCH_PARENT, (height + marginBottom).toInt())
|
||||
rootParams.bottomMargin = marginBottom.toInt()
|
||||
_root.layoutParams = rootParams
|
||||
isFitMode = true
|
||||
}
|
||||
@@ -814,7 +835,9 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||
@OptIn(UnstableApi::class)
|
||||
fun fillHeight(isMiniPlayer: Boolean) {
|
||||
Logger.i(TAG, "Video Fill Height");
|
||||
var layoutParams = _videoView.layoutParams as ConstraintLayout.LayoutParams;
|
||||
val layoutParams = _videoView.layoutParams as ConstraintLayout.LayoutParams;
|
||||
_originalBottomMargin =
|
||||
if (layoutParams.bottomMargin > 0) layoutParams.bottomMargin else _originalBottomMargin;
|
||||
layoutParams.setMargins(0);
|
||||
_videoView.layoutParams = layoutParams;
|
||||
_videoView.invalidate();
|
||||
@@ -825,14 +848,16 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
||||
|
||||
if(isMiniPlayer){
|
||||
_videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM
|
||||
_videoView.setPadding(_videoView.paddingLeft, _videoView.paddingTop, _videoView.paddingRight, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7.0f, Resources.getSystem().displayMetrics).toInt())
|
||||
} else {
|
||||
_videoView.setPadding(_videoView.paddingLeft, _videoView.paddingTop, _videoView.paddingRight, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 0.0f, Resources.getSystem().displayMetrics).toInt())
|
||||
}
|
||||
|
||||
isFitMode = false;
|
||||
}
|
||||
|
||||
//Animated Calls
|
||||
fun setEndPadding(value: Float) {
|
||||
setPadding(0, 0, value.toInt(), 0)
|
||||
}
|
||||
|
||||
fun updateRotateLock() {
|
||||
_control_rotate_lock.visibility = View.VISIBLE;
|
||||
_control_rotate_lock_fullscreen.visibility = View.VISIBLE;
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:layout="@layout/fragview_video_detail"
|
||||
android:elevation="15dp"
|
||||
android:visibility="invisible" />
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
@@ -63,6 +63,16 @@
|
||||
android:layout_height="wrap_content"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/dialog_text_input"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/dialog_buttons"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -94,13 +94,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp" />
|
||||
|
||||
<!-- TODO: the padding for the recycler view needs to be the same as the minimized video player and perhaps should be dynamic based on whether the player is mini or closed-->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/list_results"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="80dp"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.futo.platformplayer.views.containers.SingleViewTouchableMotionLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:background="@color/transparent"
|
||||
app:layoutDescription="@xml/videodetail_scene"
|
||||
app:layout_collapseMode="parallax">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/touchContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="220dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:background="#222222" />
|
||||
|
||||
<com.futo.platformplayer.fragment.mainactivity.main.VideoDetailView
|
||||
android:id="@+id/fragview_videodetail"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/touchContainer"
|
||||
app:layout_constraintEnd_toEndOf="@+id/touchContainer"
|
||||
app:layout_constraintStart_toStartOf="@+id/touchContainer"
|
||||
app:layout_constraintTop_toTopOf="@+id/touchContainer"
|
||||
android:nestedScrollingEnabled="false" />
|
||||
|
||||
</com.futo.platformplayer.views.containers.SingleViewTouchableMotionLayout>
|
||||
@@ -1,26 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.futo.platformplayer.views.containers.CustomMotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:fitsSystemWindows="false"
|
||||
android:background="@color/transparent"
|
||||
android:background="@drawable/bottom_menu_border"
|
||||
android:id="@+id/videodetail_root"
|
||||
app:layoutDescription="@xml/videodetail_scene">
|
||||
android:clickable="true">
|
||||
|
||||
<FrameLayout
|
||||
<com.futo.platformplayer.views.behavior.TouchInterceptFrameLayout
|
||||
android:id="@+id/layout_player_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp">
|
||||
|
||||
<!--this acts as a background-->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:cardBackgroundColor="@color/black"
|
||||
android:translationY="-7dp"/>
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="6dp"
|
||||
android:elevation="2dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent">
|
||||
|
||||
<com.futo.platformplayer.views.video.FutoVideoPlayer
|
||||
android:id="@+id/videodetail_player"
|
||||
@@ -40,31 +38,39 @@
|
||||
android:visibility="gone"
|
||||
android:elevation="4dp"
|
||||
android:layout_marginBottom="6dp" />
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.media3.ui.PlayerControlView
|
||||
android:id="@+id/videodetail_progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:show_timeout="-1"
|
||||
android:elevation="1dp"
|
||||
android:background="@color/black"
|
||||
app:controller_layout_id="@layout/video_player_ui_bar" />
|
||||
<androidx.media3.ui.PlayerControlView
|
||||
android:id="@+id/videodetail_progress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="12dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginLeft="-6dp"
|
||||
android:layout_marginRight="-6dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
app:show_timeout="-1"
|
||||
android:elevation="2dp"
|
||||
app:controller_layout_id="@layout/video_player_ui_bar" />
|
||||
</com.futo.platformplayer.views.behavior.TouchInterceptFrameLayout>
|
||||
|
||||
<!--Minimized Controls-->
|
||||
<LinearLayout
|
||||
android:id="@+id/minimize_controls"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:paddingBottom="7dp"
|
||||
android:layout_height="60dp"
|
||||
android:gravity="center_vertical"
|
||||
android:background="@color/black"
|
||||
android:orientation="horizontal">
|
||||
android:paddingStart="10dp"
|
||||
android:clickable="false"
|
||||
android:elevation="5dp"
|
||||
android:alpha="1"
|
||||
app:layout_constraintTop_toTopOf="@id/layout_player_container"
|
||||
app:layout_constraintBottom_toBottomOf="@id/layout_player_container"
|
||||
app:layout_constraintEnd_toEndOf="@id/layout_player_container"
|
||||
app:layout_constraintWidth_percent="0.7">
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:orientation="vertical">
|
||||
<!--Video Title-->
|
||||
@@ -174,8 +180,16 @@
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/contentContainer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp">
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="-18dp"
|
||||
android:elevation="1dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_player_container"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/videodetail_container_main"
|
||||
android:layout_width="match_parent"
|
||||
@@ -380,23 +394,19 @@
|
||||
android:id="@+id/videodetail_rating"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/buttons_pins"
|
||||
android:layout_marginTop="7dp"
|
||||
android:layout_marginStart="15dp"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"/>
|
||||
android:layout_marginStart="15dp" />
|
||||
|
||||
<com.futo.platformplayer.views.pills.RoundButtonGroup
|
||||
android:id="@+id/buttons_pins"
|
||||
app:layout_constraintStart_toEndOf="@id/videodetail_rating"
|
||||
app:layout_constraintLeft_toRightOf="@id/videodetail_rating"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginStart="10dp"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintWidth_max="500dp"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"/>
|
||||
android:layout_height="wrap_content" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
@@ -601,13 +611,15 @@
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/videodetail_quality_overview"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:visibility="gone"/>
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:elevation="100dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/overlay_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:elevation="100dp"
|
||||
android:visibility="gone" />
|
||||
</com.futo.platformplayer.views.containers.CustomMotionLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -7,7 +7,8 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="bottom"
|
||||
android:layoutDirection="ltr"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
tools:targetApi="28">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_minimize"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="bottom"
|
||||
android:layoutDirection="ltr"
|
||||
android:orientation="vertical">
|
||||
@@ -10,7 +10,7 @@
|
||||
<com.futo.platformplayer.views.behavior.TouchInterceptFrameLayout
|
||||
android:id="@+id/layout_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="12dp"
|
||||
app:shouldInterceptTouches="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
android:background="@color/transparent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<androidx.media3.ui.PlayerView
|
||||
android:paddingBottom="7dp"
|
||||
android:id="@+id/video_player"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:default_artwork="@drawable/placeholder_video_thumbnail"
|
||||
app:use_artwork="true"
|
||||
app:use_controller="false"
|
||||
app:show_buffering="always"/>
|
||||
app:show_buffering="always"
|
||||
android:layout_marginBottom="6dp" />
|
||||
<!--
|
||||
<androidx.media3.ui.PlayerControlView
|
||||
android:id="@+id/video_player_bar"
|
||||
@@ -28,7 +28,8 @@
|
||||
android:id="@+id/layout_controls_background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#55000000">
|
||||
android:background="#55000000"
|
||||
android:layout_marginBottom="6dp">
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
@@ -70,4 +71,4 @@
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -9,7 +9,7 @@
|
||||
android:layout_above="@+id/short_player_progress_bar"
|
||||
android:background="@color/black"
|
||||
app:default_artwork="@drawable/placeholder_video_thumbnail"
|
||||
app:resize_mode="fill"
|
||||
app:resize_mode="zoom"
|
||||
app:show_buffering="when_playing"
|
||||
app:use_artwork="true"
|
||||
app:use_controller="false" />
|
||||
|
||||
@@ -338,6 +338,8 @@
|
||||
<string name="test_background_worker">Test Background Worker</string>
|
||||
<string name="test_background_worker_description"></string>
|
||||
<string name="clear_payment">Clear Payment</string>
|
||||
<string name="configure_sync_server">Configure Sync Server</string>
|
||||
<string name="configure_sync_server_description">Allows you to change the Sync Server to a self-hosted one.</string>
|
||||
<string name="clears_cookies_when_you_log_out">Clears cookies when you log out</string>
|
||||
<string name="clears_in_app_browser_cookies">Clears in-app browser cookies</string>
|
||||
<string name="configure_browsing_behavior">Configure browsing behavior</string>
|
||||
@@ -438,6 +440,9 @@
|
||||
<string name="delete_watchlist_on_finish_description">After you leave a video that you mostly watched, it will be removed from watch later.</string>
|
||||
<string name="shorts_pregenerate">Pre-generate shorts sources</string>
|
||||
<string name="shorts_pregenerate_description">Generates short sources (when applicable) one video ahead</string>
|
||||
<string name="shorts_fit_video">Fit Shorts Video</string>
|
||||
<string name="shorts_fit_video_description">Will scale the video to fit the view, instead of filling the view properly.</string>
|
||||
<string name="shorts_fit_video_warning">This setting will require you to reboot Grayjay.</string>
|
||||
<string name="seek_offset">Seek duration</string>
|
||||
<string name="min_playback_speed">Minimum Playback Speed</string>
|
||||
<string name="min_playback_speed_description">Minimum Available Speed</string>
|
||||
@@ -473,6 +478,7 @@
|
||||
<string name="number_of_concurrent_threads_to_multiply_download_speeds_from_throttled_sources">Number of concurrent threads to multiply download speeds from throttled sources</string>
|
||||
<string name="payment">Payment</string>
|
||||
<string name="payment_status">Payment Status</string>
|
||||
<string name="relay_server">Sync Relay Server</string>
|
||||
<string name="bypass_rotation_prevention">Bypass Rotation Prevention</string>
|
||||
<string name="playlist_delete_confirmation">Playlist Delete Confirmation</string>
|
||||
<string name="playlist_delete_confirmation_description">Show confirmation dialog when deleting media from a playlist</string>
|
||||
|
||||
@@ -3,180 +3,208 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<Transition
|
||||
android:id="@+id/maximize"
|
||||
app:constraintSetEnd="@id/expanded"
|
||||
app:constraintSetStart="@id/collapsed"
|
||||
app:duration="300">
|
||||
app:duration="300"
|
||||
app:motionInterpolator="easeInOut">
|
||||
|
||||
<OnSwipe
|
||||
app:dragDirection="dragUp"
|
||||
app:maxAcceleration="200"
|
||||
app:touchAnchorId="@+id/touchContainer"
|
||||
app:nestedScrollFlags="disableScroll"
|
||||
app:touchAnchorId="@id/layout_player_container"
|
||||
app:touchAnchorSide="top" />
|
||||
|
||||
<!--pretty sure this isn't doing anything right now-->
|
||||
<OnClick
|
||||
app:clickAction="transitionToEnd"
|
||||
app:targetId="@id/layout_player_container" />
|
||||
<KeyFrameSet>
|
||||
<!--
|
||||
<KeyAttribute
|
||||
android:alpha="0"
|
||||
app:framePosition="0"
|
||||
app:motionTarget="@id/contentContainer" />
|
||||
|
||||
<KeyAttribute
|
||||
android:alpha="1"
|
||||
app:framePosition="100"
|
||||
app:motionTarget="@id/contentContainer" /> -->
|
||||
|
||||
<KeyAttribute
|
||||
app:framePosition="3"
|
||||
app:motionTarget="@id/touchContainer">
|
||||
<CustomAttribute
|
||||
app:attributeName="cardElevation"
|
||||
app:customDimension="0dp" />
|
||||
</KeyAttribute>
|
||||
|
||||
<!--Minimize Progress-->
|
||||
<KeyAttribute
|
||||
app:framePosition="0"
|
||||
app:motionTarget="@id/fragview_videodetail">
|
||||
<CustomAttribute
|
||||
app:attributeName="MinimizeProgress"
|
||||
app:customFloatValue="0" />
|
||||
</KeyAttribute>
|
||||
<KeyAttribute
|
||||
app:framePosition="100"
|
||||
app:motionTarget="@id/fragview_videodetail">
|
||||
<CustomAttribute
|
||||
app:attributeName="MinimizeProgress"
|
||||
app:customFloatValue="1" />
|
||||
</KeyAttribute>
|
||||
|
||||
<!--Controller Alpha-->
|
||||
<KeyAttribute
|
||||
app:framePosition="0"
|
||||
app:motionTarget="@id/fragview_videodetail">
|
||||
<CustomAttribute
|
||||
app:attributeName="ControllerAlpha"
|
||||
app:customFloatValue="0" />
|
||||
</KeyAttribute>
|
||||
<KeyAttribute
|
||||
app:framePosition="100"
|
||||
app:motionTarget="@id/fragview_videodetail">
|
||||
<CustomAttribute
|
||||
app:attributeName="ControllerAlpha"
|
||||
app:customFloatValue="1" />
|
||||
</KeyAttribute>
|
||||
|
||||
<!--Content Alpha-->
|
||||
<KeyAttribute
|
||||
app:framePosition="0"
|
||||
app:motionTarget="@id/fragview_videodetail">
|
||||
<CustomAttribute
|
||||
app:attributeName="ContentAlpha"
|
||||
app:customFloatValue="0" />
|
||||
</KeyAttribute>
|
||||
<KeyAttribute
|
||||
app:framePosition="30"
|
||||
app:motionTarget="@id/fragview_videodetail">
|
||||
<CustomAttribute
|
||||
app:attributeName="ContentAlpha"
|
||||
app:customFloatValue="0" />
|
||||
</KeyAttribute>
|
||||
<KeyAttribute
|
||||
app:framePosition="100"
|
||||
app:motionTarget="@id/fragview_videodetail">
|
||||
<CustomAttribute
|
||||
app:attributeName="ContentAlpha"
|
||||
app:customFloatValue="1" />
|
||||
</KeyAttribute>
|
||||
|
||||
<!--MinimizeControlsAlpha Alpha -->
|
||||
<KeyAttribute
|
||||
app:framePosition="0"
|
||||
app:motionTarget="@id/fragview_videodetail">
|
||||
<CustomAttribute
|
||||
app:attributeName="MinimizeControlsAlpha"
|
||||
app:customFloatValue="1" />
|
||||
</KeyAttribute>
|
||||
<KeyAttribute
|
||||
app:framePosition="20"
|
||||
app:motionTarget="@id/fragview_videodetail">
|
||||
<CustomAttribute
|
||||
app:attributeName="MinimizeControlsAlpha"
|
||||
app:customFloatValue="0" />
|
||||
</KeyAttribute>
|
||||
<KeyAttribute
|
||||
app:framePosition="100"
|
||||
app:motionTarget="@id/fragview_videodetail">
|
||||
<CustomAttribute
|
||||
app:attributeName="MinimizeControlsAlpha"
|
||||
app:customFloatValue="0" />
|
||||
</KeyAttribute>
|
||||
|
||||
|
||||
|
||||
<!--Padding Right-->
|
||||
<KeyAttribute
|
||||
app:framePosition="0"
|
||||
app:motionTarget="@id/fragview_videodetail">
|
||||
<CustomAttribute
|
||||
app:attributeName="VideoMinimize"
|
||||
app:customFloatValue="1" />
|
||||
</KeyAttribute>
|
||||
<KeyAttribute
|
||||
app:framePosition="20"
|
||||
app:motionTarget="@id/fragview_videodetail">
|
||||
<CustomAttribute
|
||||
app:attributeName="VideoMinimize"
|
||||
app:customFloatValue="0" />
|
||||
</KeyAttribute>
|
||||
<KeyAttribute
|
||||
app:framePosition="100"
|
||||
app:motionTarget="@id/fragview_videodetail">
|
||||
<CustomAttribute
|
||||
app:attributeName="VideoMinimize"
|
||||
app:customFloatValue="0" />
|
||||
</KeyAttribute>
|
||||
|
||||
<!--Padding Top-->
|
||||
<KeyAttribute
|
||||
app:framePosition="0"
|
||||
app:motionTarget="@id/fragview_videodetail">
|
||||
<CustomAttribute
|
||||
app:attributeName="TopPadding"
|
||||
app:customDimension="1dp" />
|
||||
</KeyAttribute>
|
||||
<KeyAttribute
|
||||
app:framePosition="100"
|
||||
app:motionTarget="@id/fragview_videodetail">
|
||||
<CustomAttribute
|
||||
app:attributeName="TopPadding"
|
||||
app:customDimension="0dp" />
|
||||
</KeyAttribute>
|
||||
|
||||
</KeyFrameSet>
|
||||
</Transition>
|
||||
|
||||
<Transition
|
||||
android:id="@+id/full_screen"
|
||||
app:constraintSetEnd="@id/full_screen_gesture"
|
||||
app:constraintSetStart="@id/expanded"
|
||||
app:duration="300">
|
||||
|
||||
<OnSwipe
|
||||
app:dragDirection="dragUp"
|
||||
app:maxAcceleration="200"
|
||||
app:nestedScrollFlags="disableScroll"
|
||||
app:onTouchUp="autoCompleteToStart"
|
||||
app:touchAnchorId="@id/layout_player_container"
|
||||
app:touchAnchorSide="bottom" />
|
||||
</Transition>
|
||||
|
||||
<ConstraintSet android:id="@+id/collapsed">
|
||||
<Constraint
|
||||
android:id="@id/layout_player_container"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginBottom="47dp"
|
||||
android:elevation="3dp"
|
||||
android:paddingBottom="6dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/minimize_controls"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintHorizontal_weight="150"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintWidth_max="150dp" />
|
||||
<Constraint
|
||||
android:id="@id/contentContainer"
|
||||
android:elevation="1dp"
|
||||
android:orientation="vertical"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_player_container" />
|
||||
<Constraint
|
||||
android:id="@id/minimize_controls"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="60dp"
|
||||
android:elevation="1dp"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintHorizontal_weight="350"
|
||||
app:layout_constraintStart_toEndOf="@id/layout_player_container"
|
||||
app:layout_constraintTop_toTopOf="@id/layout_player_container"
|
||||
app:layout_constraintWidth_max="350dp" />
|
||||
<Constraint
|
||||
android:id="@id/videodetail_progress"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="-12dp"
|
||||
android:background="@color/black"
|
||||
android:elevation="2dp"
|
||||
app:layout_constraintEnd_toEndOf="@id/minimize_controls"
|
||||
app:layout_constraintStart_toStartOf="@id/layout_player_container"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_player_container" />
|
||||
<Constraint
|
||||
android:id="@id/videodetail_quality_overview"
|
||||
app:visibilityMode="ignore"/>
|
||||
<Constraint
|
||||
android:id="@id/overlay_container"
|
||||
app:visibilityMode="ignore"/>
|
||||
</ConstraintSet>
|
||||
|
||||
<ConstraintSet android:id="@+id/expanded">
|
||||
|
||||
<Constraint
|
||||
android:id="@id/layout_player_container"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@id/touchContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="220dp"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:elevation="2dp"
|
||||
android:paddingBottom="6dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Constraint
|
||||
android:id="@id/contentContainer"
|
||||
android:elevation="2dp"
|
||||
android:orientation="vertical"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:id="@id/fragview_videodetail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_player_container" />
|
||||
<Constraint
|
||||
android:id="@id/minimize_controls"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="60dp"
|
||||
android:elevation="1dp"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/layout_player_container"
|
||||
app:layout_constraintTop_toTopOf="@id/layout_player_container" />
|
||||
<Constraint
|
||||
android:id="@id/videodetail_progress"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="-12dp"
|
||||
android:background="@color/transparent"
|
||||
android:elevation="1dp"
|
||||
app:layout_constraintEnd_toEndOf="@id/minimize_controls"
|
||||
app:layout_constraintStart_toStartOf="@id/layout_player_container"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_player_container" />
|
||||
<Constraint
|
||||
android:id="@id/videodetail_quality_overview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:elevation="100dp"
|
||||
android:visibility="gone"
|
||||
app:visibilityMode="ignore"/>
|
||||
<Constraint
|
||||
android:id="@id/overlay_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:elevation="100dp"
|
||||
android:visibility="gone"
|
||||
app:visibilityMode="ignore"/>
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</ConstraintSet>
|
||||
|
||||
<ConstraintSet android:id="@+id/full_screen_gesture">
|
||||
<ConstraintSet android:id="@+id/collapsed">
|
||||
|
||||
<Constraint
|
||||
android:id="@id/layout_player_container"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="-130dp"
|
||||
android:layout_marginBottom="0dp"
|
||||
android:elevation="2dp"
|
||||
android:paddingBottom="6dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
<Constraint
|
||||
android:id="@id/contentContainer"
|
||||
android:elevation="1dp"
|
||||
android:orientation="vertical"
|
||||
android:visibility="visible"
|
||||
android:id="@id/touchContainer"
|
||||
android:layout_height="60dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_marginBottom="48dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_player_container" />
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<Constraint
|
||||
android:id="@id/minimize_controls"
|
||||
android:layout_width="0dp"
|
||||
android:id="@id/fragview_videodetail"
|
||||
android:layout_height="60dp"
|
||||
android:elevation="1dp"
|
||||
android:visibility="invisible"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_marginBottom="48dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/layout_player_container"
|
||||
app:layout_constraintTop_toTopOf="@id/layout_player_container" />
|
||||
<Constraint
|
||||
android:id="@id/videodetail_progress"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="-12dp"
|
||||
android:background="@color/transparent"
|
||||
android:elevation="1dp"
|
||||
app:layout_constraintEnd_toEndOf="@id/minimize_controls"
|
||||
app:layout_constraintStart_toStartOf="@id/layout_player_container"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_player_container" />
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
</ConstraintSet>
|
||||
</MotionScene>
|
||||
|
||||
</MotionScene>
|
||||
Submodule app/src/stable/assets/sources/bilibili updated: 1222638042...0fc4e8fbbf
Submodule app/src/stable/assets/sources/spotify updated: 207738f599...0b50c2e61b
Submodule app/src/stable/assets/sources/youtube updated: 95c60c2dc6...f370a88604
Submodule app/src/unstable/assets/sources/bilibili updated: 1222638042...0fc4e8fbbf
Submodule app/src/unstable/assets/sources/spotify updated: 207738f599...0b50c2e61b
Submodule app/src/unstable/assets/sources/youtube updated: 95c60c2dc6...f370a88604
Reference in New Issue
Block a user