Compare commits

...

36 Commits

Author SHA1 Message Date
Kai DeLorenzo 231d2461b3 Merge branch 'incorrect-number-of-columns-bug' into 'master'
fix the calculation that incorrectly sets the number of columns to display

See merge request videostreaming/grayjay!54
2024-12-11 17:48:17 +00:00
Kai DeLorenzo 3b457f87c4 Merge branch 'fix-fullscreen-from-pip' into 'master'
prevent going into full screen when entering pip mode

See merge request videostreaming/grayjay!55
2024-12-11 17:48:00 +00:00
Koen J de3ced4d3c Intent class should be MediaButtonReceiver. 2024-12-11 10:41:36 +01:00
Koen J 891777e89e Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay 2024-12-11 10:36:13 +01:00
Koen J 287239dd1c Added media button receiver. 2024-12-11 10:36:02 +01:00
Kelvin 7cdded8fd7 Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay 2024-12-10 22:35:46 +01:00
Kelvin 8c9d045e1d Download playlist fix for videos without audio file 2024-12-10 22:35:38 +01:00
Kai 620f5a0459 prevent going into full screen when entering pip mode 2024-12-10 11:30:48 -06:00
Koen 178d874ba0 Merge branch 'spinner-block-player' into 'master'
Spinner block player go full screen

See merge request videostreaming/grayjay!53
2024-12-10 16:18:55 +00:00
Koen d44f30c8a6 Merge branch 'creator-filter-clear' into 'master'
enable creator filter clear

See merge request videostreaming/grayjay!52
2024-12-10 15:56:42 +00:00
Koen J ce66937429 Offline playback toast now doesn't show more than once every 5 seconds. 2024-12-10 14:01:34 +01:00
Koen J 9823337375 Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay 2024-12-10 13:54:29 +01:00
Koen J 11f5f0dfe1 Fixed comment character counter. 2024-12-10 13:54:02 +01:00
Koen J e1882f19e8 Comment close now requires confirmation. Fixed comment character counter. 2024-12-10 13:52:24 +01:00
Koen J 6a8b9f06c2 Comment close now requires confirmation. 2024-12-10 13:52:02 +01:00
Koen J 752fc8787d Fixed link scrolling behaviour. 2024-12-10 13:29:09 +01:00
Koen J 90a1cd8280 Placing reply comments works again. 2024-12-10 13:07:03 +01:00
Koen J aa570ac29d Updated submodules. 2024-12-10 12:47:18 +01:00
Kai fb7b6363f9 added multi column support for channels 2024-12-09 18:00:24 -06:00
Kai 23afe7994c fix the calculation that incorrectly sets the number of columns to display 2024-12-08 16:47:13 -06:00
Kai 7557e6f6ba prevent going full screen before the video has loaded 2024-12-07 11:32:28 -06:00
Kai 86b6938911 added video detail check 2024-12-07 11:09:48 -06:00
Kai 8f30a45fa8 enable creator filter clear 2024-12-06 11:13:29 -06:00
Koen J 7c9e9d5f52 Should not crash app when StateSync fails to bind. 2024-12-06 17:49:18 +01:00
Koen J 4066ce73a8 Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay 2024-12-06 14:55:10 +01:00
Koen J b5722dba1a MainActivity should be singleInstance. 2024-12-06 14:54:57 +01:00
Koen 81765ecafc Update deploy-playstore.sh 2024-12-06 10:54:36 +00:00
Kelvin 84b42e9d19 refs 2024-12-05 17:08:30 +01:00
Koen J ed319a0e5f Fixed invalid padding. 2024-12-05 17:03:45 +01:00
Koen J dd55d10194 Crashfix. 2024-12-05 17:00:23 +01:00
Koen J 2084b46090 Possible fix for sub bar distance. 2024-12-05 16:58:35 +01:00
Koen J 53443a6cf2 Only show announcements on subscriptions if home is not enabled. Add margin top to subscriptions. 2024-12-05 16:51:56 +01:00
Koen J 92715b5642 Fixed crash due to AnnouncementView. 2024-12-05 16:40:05 +01:00
Kelvin 6166392515 Polycentric disk caching 2024-12-04 18:20:40 +01:00
Kelvin 49d0dead7d Fix playlist wrong id for local conversion, refs 2024-12-04 01:31:53 +01:00
Kelvin 6f004830ff Fix directly opening playlists urls in wrong ui, causing wrong thread 2024-12-02 16:23:30 +01:00
46 changed files with 529 additions and 282 deletions
+7 -1
View File
@@ -36,6 +36,12 @@
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
</provider>
<receiver android:name=".receivers.MediaButtonReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
<service android:name=".services.MediaPlaybackService"
android:enabled="true"
android:foregroundServiceType="mediaPlayback" />
@@ -52,7 +58,7 @@
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="true"
android:theme="@style/Theme.FutoVideo.NoActionBar"
android:launchMode="singleTask"
android:launchMode="singleInstance"
android:resizeableActivity="true"
android:supportsPictureInPicture="true">
@@ -866,6 +866,9 @@ class Settings : FragmentedStorageFileJson() {
@FormField(R.string.enable_polycentric, FieldForm.TOGGLE, R.string.can_be_disabled_when_you_are_experiencing_issues, 3)
var polycentricEnabled: Boolean = true;
@FormField(R.string.polycentric_local_cache, FieldForm.TOGGLE, R.string.polycentric_local_cache_description, 4)
var polycentricLocalCache: Boolean = true;
}
@FormField(R.string.gesture_controls, FieldForm.GROUP, -1, 19)
@@ -1,11 +1,13 @@
package com.futo.platformplayer.activities
import android.annotation.SuppressLint
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.media.AudioManager
import android.net.Uri
import android.os.Bundle
import android.os.StrictMode
@@ -72,6 +74,7 @@ import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.ImportCache
import com.futo.platformplayer.models.UrlVideoWithTime
import com.futo.platformplayer.receivers.MediaButtonReceiver
import com.futo.platformplayer.setNavigationBarColorAndIcons
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StateBackup
@@ -107,6 +110,7 @@ import java.util.LinkedList
import java.util.Queue
import java.util.concurrent.ConcurrentLinkedQueue
class MainActivity : AppCompatActivity, IWithResultLauncher {
//TODO: Move to dimensions
@@ -834,7 +838,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
} else if (StatePlatform.instance.hasEnabledPlaylistClient(url)) {
Logger.i(TAG, "handleUrl(url=$url) found playlist client");
lifecycleScope.launch(Dispatchers.Main) {
navigate(_fragMainPlaylist, url);
navigate(_fragMainRemotePlaylist, url);
delay(100);
_fragVideoDetail.minimizeVideoDetail();
};
@@ -10,6 +10,7 @@ import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.api.media.structures.ReusablePager
import com.futo.platformplayer.getOrThrow
import com.futo.platformplayer.models.Playlist
import java.util.UUID
class JSPlaylistDetails: JSPlaylist, IPlatformPlaylistDetails {
override val contents: IPager<IPlatformVideo>;
@@ -37,6 +38,6 @@ class JSPlaylistDetails: JSPlaylist, IPlatformPlaylistDetails {
onProgress?.invoke(videos.size);
}
return Playlist(id.toString(), name, videos.map { SerializedPlatformVideo.fromVideo(it)});
return Playlist(UUID.randomUUID().toString(), name, videos.map { SerializedPlatformVideo.fromVideo(it)});
}
}
@@ -6,6 +6,7 @@ import android.graphics.Color
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
@@ -57,11 +58,21 @@ class CommentDialog(context: Context?, val contextUrl: String, val ref: Protocol
_editComment = findViewById(R.id.edit_comment);
_textCharacterCount = findViewById(R.id.character_count);
_textCharacterCountMax = findViewById(R.id.character_count_max);
setCanceledOnTouchOutside(false)
setOnKeyListener { _, keyCode, event ->
if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) {
handleCloseAttempt()
true
} else {
false
}
}
_editComment.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) = Unit
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, c: Int) {
val count = s?.length ?: 0;
_textCharacterCount.text = count.toString();
if (count > PolycentricPlatformComment.MAX_COMMENT_SIZE) {
@@ -79,10 +90,13 @@ class CommentDialog(context: Context?, val contextUrl: String, val ref: Protocol
_inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager;
_buttonCancel.setOnClickListener {
clearFocus();
dismiss();
handleCloseAttempt()
};
setOnCancelListener {
handleCloseAttempt()
}
_buttonCreate.setOnClickListener {
clearFocus();
@@ -134,6 +148,22 @@ class CommentDialog(context: Context?, val contextUrl: String, val ref: Protocol
focus();
}
private fun handleCloseAttempt() {
if (_editComment.text.isEmpty()) {
clearFocus()
dismiss()
} else {
UIDialogs.showConfirmationDialog(
context,
context.resources.getString(R.string.not_empty_close),
action = {
clearFocus()
dismiss()
}
)
}
}
private fun focus() {
_editComment.requestFocus();
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
@@ -100,6 +100,7 @@ class VideoDownload {
var requireVideoSource: Boolean = false;
var requireAudioSource: Boolean = false;
var requiredCheck: Boolean = false;
@Contextual
@Transient
@@ -164,7 +165,7 @@ class VideoDownload {
onStateChanged.emit(newState);
}
constructor(video: IPlatformVideo, targetPixelCount: Long? = null, targetBitrate: Long? = null) {
constructor(video: IPlatformVideo, targetPixelCount: Long? = null, targetBitrate: Long? = null, optionalSources: Boolean = false) {
this.video = SerializedPlatformVideo.fromVideo(video);
this.videoSource = null;
this.audioSource = null;
@@ -175,8 +176,9 @@ class VideoDownload {
this.requiresLiveVideoSource = false;
this.requiresLiveAudioSource = false;
this.targetVideoName = videoSource?.name;
this.requireVideoSource = targetPixelCount != null
this.requireVideoSource = targetPixelCount != null;
this.requireAudioSource = targetBitrate != null; //TODO: May not be a valid check.. can only be determined after live fetch?
this.requiredCheck = optionalSources;
}
constructor(video: IPlatformVideoDetails, videoSource: IVideoSource?, audioSource: IAudioSource?, subtitleSource: SubtitleRawSource?) {
this.video = SerializedPlatformVideo.fromVideo(video);
@@ -250,6 +252,30 @@ class VideoDownload {
if(original !is IPlatformVideoDetails)
throw IllegalStateException("Original content is not media?");
if(requiredCheck) {
if(original.video is VideoUnMuxedSourceDescriptor) {
if(requireVideoSource) {
if((original.video as VideoUnMuxedSourceDescriptor).audioSources.any() && !original.video.videoSources.any()) {
requireVideoSource = false;
targetPixelCount = null;
}
}
if(requireAudioSource) {
if(!(original.video as VideoUnMuxedSourceDescriptor).audioSources.any() && original.video.videoSources.any()) {
requireAudioSource = false;
targetBitrate = null;
}
}
}
else {
if(requireAudioSource) {
requireAudioSource = false;
targetBitrate = null;
}
}
requiredCheck = false;
}
if(original.video.hasAnySource() && !original.isDownloadable()) {
Logger.i(TAG, "Attempted to download unsupported video [${original.name}]:${original.url}");
throw DownloadException("Unsupported video for downloading", false);
@@ -1,12 +1,13 @@
package com.futo.platformplayer.fragment.channel.tab
import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.R
import com.futo.platformplayer.Settings
@@ -15,7 +16,6 @@ import com.futo.platformplayer.api.media.models.PlatformAuthorLink
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
import com.futo.platformplayer.api.media.models.contents.ContentType
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.api.media.platforms.js.models.JSPager
import com.futo.platformplayer.api.media.structures.IAsyncPager
import com.futo.platformplayer.api.media.structures.IPager
@@ -41,10 +41,11 @@ import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
import com.futo.platformplayer.views.adapters.feedtypes.PreviewContentListAdapter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.math.max
class ChannelContentsFragment : Fragment(), IChannelTabFragment {
private var _recyclerResults: RecyclerView? = null;
private var _llmVideo: LinearLayoutManager? = null;
private var _glmVideo: GridLayoutManager? = null;
private var _loading = false;
private var _pager_parent: IPager<IPlatformContent>? = null;
private var _pager: IPager<IPlatformContent>? = null;
@@ -118,7 +119,7 @@ class ChannelContentsFragment : Fragment(), IChannelTabFragment {
super.onScrolled(recyclerView, dx, dy);
val recyclerResults = _recyclerResults ?: return;
val llmVideo = _llmVideo ?: return;
val llmVideo = _glmVideo ?: return;
val visibleItemCount = recyclerResults.childCount;
val firstVisibleItem = llmVideo.findFirstVisibleItemPosition();
@@ -163,9 +164,10 @@ class ChannelContentsFragment : Fragment(), IChannelTabFragment {
this.onLongPress.subscribe(this@ChannelContentsFragment.onLongPress::emit);
}
_llmVideo = LinearLayoutManager(view.context);
val numColumns = max((resources.configuration.screenWidthDp.toDouble() / resources.getInteger(R.integer.column_width_dp)).toInt(), 1)
_glmVideo = GridLayoutManager(view.context, numColumns);
_recyclerResults?.adapter = _adapterResults;
_recyclerResults?.layoutManager = _llmVideo;
_recyclerResults?.layoutManager = _glmVideo;
_recyclerResults?.addOnScrollListener(_scrollListener);
return view;
@@ -181,6 +183,13 @@ class ChannelContentsFragment : Fragment(), IChannelTabFragment {
_nextPageHandler.cancel();
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
_glmVideo?.spanCount =
max((resources.configuration.screenWidthDp.toDouble() / resources.getInteger(R.integer.column_width_dp)).toInt(), 1)
}
/*
private fun setPager(pager: IPager<IPlatformContent>, cache: FeedFragment.ItemCache<IPlatformContent>? = null) {
if (_pager_parent != null && _pager_parent is IRefreshPager<*>) {
@@ -1,12 +1,13 @@
package com.futo.platformplayer.fragment.channel.tab
import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.R
import com.futo.platformplayer.Settings
@@ -36,10 +37,11 @@ import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
import com.futo.platformplayer.views.adapters.feedtypes.PreviewContentListAdapter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.math.max
class ChannelPlaylistsFragment : Fragment(), IChannelTabFragment {
private var _recyclerResults: RecyclerView? = null
private var _llmPlaylist: LinearLayoutManager? = null
private var _glmPlaylist: GridLayoutManager? = null
private var _loading = false
private var _pagerParent: IPager<IPlatformPlaylist>? = null
private var _pager: IPager<IPlatformPlaylist>? = null
@@ -109,7 +111,7 @@ class ChannelPlaylistsFragment : Fragment(), IChannelTabFragment {
super.onScrolled(recyclerView, dx, dy)
val recyclerResults = _recyclerResults ?: return
val llmPlaylist = _llmPlaylist ?: return
val llmPlaylist = _glmPlaylist ?: return
val visibleItemCount = recyclerResults.childCount
val firstVisibleItem = llmPlaylist.findFirstVisibleItemPosition()
@@ -158,9 +160,10 @@ class ChannelPlaylistsFragment : Fragment(), IChannelTabFragment {
this.onLongPress.subscribe(this@ChannelPlaylistsFragment.onLongPress::emit)
}
_llmPlaylist = LinearLayoutManager(view.context)
val numColumns = max((resources.configuration.screenWidthDp.toDouble() / resources.getInteger(R.integer.column_width_dp)).toInt(), 1)
_glmPlaylist = GridLayoutManager(view.context, numColumns)
_recyclerResults?.adapter = _adapterResults
_recyclerResults?.layoutManager = _llmPlaylist
_recyclerResults?.layoutManager = _glmPlaylist
_recyclerResults?.addOnScrollListener(_scrollListener)
return view
@@ -176,6 +179,13 @@ class ChannelPlaylistsFragment : Fragment(), IChannelTabFragment {
_nextPageHandler.cancel()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
_glmPlaylist?.spanCount =
max((resources.configuration.screenWidthDp.toDouble() / resources.getInteger(R.integer.column_width_dp)).toInt(), 1)
}
private fun setPager(
pager: IPager<IPlatformPlaylist>
) {
@@ -33,6 +33,7 @@ import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay
import com.futo.platformplayer.withTimestamp
import kotlin.math.floor
import kotlin.math.max
abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent, IPlatformContent, IPager<IPlatformContent>, ContentPreviewViewHolder> where TFragment : MainFragment {
private var _exoPlayer: PlayerManager? = null;
@@ -168,7 +169,7 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
val glmResults =
GridLayoutManager(
context,
(resources.configuration.screenWidthDp / resources.getDimension(R.dimen.landscape_threshold)).toInt() + 1
max((resources.configuration.screenWidthDp.toDouble() / resources.getInteger(R.integer.column_width_dp)).toInt(), 1)
);
return glmResults
}
@@ -8,6 +8,7 @@ import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.Spinner
import androidx.core.widget.addTextChangedListener
import androidx.recyclerview.widget.LinearLayoutManager
@@ -25,11 +26,20 @@ class CreatorsFragment : MainFragment() {
private var _overlayContainer: FrameLayout? = null;
private var _containerSearch: FrameLayout? = null;
private var _editSearch: EditText? = null;
private var _buttonClearSearch: ImageButton? = null
override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val view = inflater.inflate(R.layout.fragment_creators, container, false);
_containerSearch = view.findViewById(R.id.container_search);
_editSearch = view.findViewById(R.id.edit_search);
val editSearch: EditText = view.findViewById(R.id.edit_search);
val buttonClearSearch: ImageButton = view.findViewById(R.id.button_clear_search)
_editSearch = editSearch
_buttonClearSearch = buttonClearSearch
buttonClearSearch.setOnClickListener {
editSearch.text.clear()
editSearch.requestFocus()
_buttonClearSearch?.visibility = View.INVISIBLE;
}
val adapter = SubscriptionAdapter(inflater, getString(R.string.confirm_delete_subscription));
adapter.onClick.subscribe { platformUser -> navigate<ChannelFragment>(platformUser) };
@@ -51,7 +61,12 @@ class CreatorsFragment : MainFragment() {
_spinnerSortBy = spinnerSortBy;
_editSearch?.addTextChangedListener {
adapter.query = it.toString();
adapter.query = it.toString()
if (it?.isEmpty() == true) {
_buttonClearSearch?.visibility = View.INVISIBLE
} else {
_buttonClearSearch?.visibility = View.VISIBLE
}
}
val recyclerView = view.findViewById<RecyclerView>(R.id.recycler_subscriptions);
@@ -25,10 +25,12 @@ import com.futo.platformplayer.views.others.ProgressBar
import com.futo.platformplayer.views.others.TagsView
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
import com.futo.platformplayer.views.adapters.InsertedViewHolder
import com.futo.platformplayer.views.announcements.AnnouncementView
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.time.OffsetDateTime
import kotlin.math.max
abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : LinearLayout where TPager : IPager<TResult>, TViewHolder : RecyclerView.ViewHolder, TFragment : MainFragment {
protected val _recyclerResults: RecyclerView;
@@ -37,6 +39,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
private val _progressBar: ProgressBar;
private val _spinnerSortBy: Spinner;
private val _containerSortBy: LinearLayout;
private val _announcementView: AnnouncementView;
private val _tagsView: TagsView;
private val _textCentered: TextView;
private val _emptyPagerContainer: FrameLayout;
@@ -73,6 +76,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
_textCentered = findViewById(R.id.text_centered);
_emptyPagerContainer = findViewById(R.id.empty_pager_container);
_progressBar = findViewById(R.id.progress_bar);
_announcementView = findViewById(R.id.announcement_view)
_progressBar.inactiveColor = Color.TRANSPARENT;
_swipeRefresh = findViewById(R.id.swipe_refresh);
@@ -172,6 +176,10 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
_recyclerResults.addOnScrollListener(_scrollListener);
}
protected fun showAnnouncementView() {
_announcementView.visibility = View.VISIBLE
}
private fun ensureEnoughContentVisible(filteredResults: List<TConverted>) {
val canScroll = if (recyclerData.results.isEmpty()) false else {
val layoutManager = recyclerData.layoutManager
@@ -227,7 +235,8 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
}
open fun updateSpanCount() {
recyclerData.layoutManager.spanCount = (resources.configuration.screenWidthDp / resources.getDimension(R.dimen.landscape_threshold)).toInt() + 1
recyclerData.layoutManager.spanCount =
max((resources.configuration.screenWidthDp.toDouble() / resources.getInteger(R.integer.column_width_dp)).toInt(), 1)
}
override fun onConfigurationChanged(newConfig: Configuration?) {
@@ -94,20 +94,10 @@ class HomeFragment : MainFragment() {
class HomeView : ContentFeedView<HomeFragment> {
override val feedStyle: FeedStyle get() = Settings.instance.home.getHomeFeedStyle();
private var _announcementsView: AnnouncementView = AnnouncementView(context, null).apply {
if(!this.isClosed()) {
recyclerData.adapter.viewsToPrepend.add(this)
this.onClose.subscribe {
recyclerData.adapter.viewsToPrepend.remove(this)
}
}
};
private val _taskGetPager: TaskHandler<Boolean, IPager<IPlatformContent>>;
override val shouldShowTimeBar: Boolean get() = Settings.instance.home.progressBar
constructor(fragment: HomeFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, GridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) {
_taskGetPager = TaskHandler<Boolean, IPager<IPlatformContent>>({ fragment.lifecycleScope }, {
StatePlatform.instance.getHomeRefresh(fragment.lifecycleScope)
})
@@ -138,6 +128,7 @@ class HomeFragment : MainFragment() {
};
setPreviewsEnabled(Settings.instance.home.previewFeedItems);
showAnnouncementView()
}
fun onShown() {
@@ -70,7 +70,7 @@ class PlaylistFragment : MainFragment() {
private var _editPlaylistOverlay: SlideUpMenuOverlay? = null;
private var _url: String? = null;
private val _taskLoadPlaylist: TaskHandler<String, IPlatformPlaylistDetails>;
private val _taskLoadPlaylist: TaskHandler<String, Playlist>;
constructor(fragment: PlaylistFragment, inflater: LayoutInflater) : super(inflater) {
_fragment = fragment;
@@ -137,16 +137,16 @@ class PlaylistFragment : MainFragment() {
);
};
_taskLoadPlaylist = TaskHandler<String, IPlatformPlaylistDetails>(
_taskLoadPlaylist = TaskHandler<String, Playlist>(
StateApp.instance.scopeGetter,
{
return@TaskHandler StatePlatform.instance.getPlaylist(it);
return@TaskHandler StatePlatform.instance.getPlaylist(it).toPlaylist();
})
.success {
setName(it.name);
//TODO: Implement support for pagination
setVideos(it.toPlaylist().videos, false);
setVideoCount(it.videoCount);
setVideos(it.videos, false);
setVideoCount(it.videos.size);
setLoading(false);
}
.exception<Throwable> {
@@ -35,7 +35,6 @@ import com.futo.platformplayer.views.ToastView
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
import com.futo.platformplayer.views.adapters.InsertedViewHolder
import com.futo.platformplayer.views.announcements.AnnouncementView
import com.futo.platformplayer.views.buttons.BigButton
import com.futo.platformplayer.views.subscriptions.SubscriptionBar
import kotlinx.coroutines.CancellationException
@@ -125,6 +124,9 @@ class SubscriptionsFeedFragment : MainFragment() {
initializeToolbarContent();
setPreviewsEnabled(Settings.instance.subscriptions.previewFeedItems);
if (Settings.instance.tabs.find { it.id == 0 }?.enabled != true) {
showAnnouncementView()
}
}
fun onShown() {
@@ -145,26 +147,6 @@ class SubscriptionsFeedFragment : MainFragment() {
}
}
val announcementsView = _announcementsView;
val homeTab = Settings.instance.tabs.find { it.id == 0 };
val isHomeEnabled = homeTab?.enabled == true;
if (announcementsView != null && isHomeEnabled) {
recyclerData.adapter.viewsToPrepend.remove(announcementsView)
_announcementsView = null
}
if (announcementsView == null && !isHomeEnabled) {
val c = context;
if (c != null) {
_announcementsView = AnnouncementView(c, null).apply {
recyclerData.adapter.viewsToPrepend.add(this)
this.onClose.subscribe {
recyclerData.adapter.viewsToPrepend.remove(this)
}
}
}
}
if (!StateSubscriptions.instance.global.isGlobalUpdating) {
finishRefreshLayoutLoader();
}
@@ -192,8 +174,6 @@ class SubscriptionsFeedFragment : MainFragment() {
private var _subscriptionBar: SubscriptionBar? = null;
private var _announcementsView: AnnouncementView? = null;
@Serializable
class FeedFilterSettings: FragmentedStorageFileJson() {
val allowContentTypes: MutableList<ContentType> = mutableListOf(ContentType.MEDIA, ContentType.POST);
@@ -91,7 +91,7 @@ class VideoDetailFragment : MainFragment {
return min(
resources.configuration.screenWidthDp,
resources.configuration.screenHeightDp
) < resources.getDimension(R.dimen.landscape_threshold)
) < resources.getInteger(R.integer.column_width_dp) * 2
}
override fun onConfigurationChanged(newConfig: Configuration) {
@@ -1799,8 +1799,13 @@ class VideoDetailView : ConstraintLayout {
private fun onSourceChanged(videoSource: IVideoSource?, audioSource: IAudioSource?, resume: Boolean){
Logger.i(TAG, "onSourceChanged(videoSource=$videoSource, audioSource=$audioSource, resume=$resume)")
if((videoSource == null || videoSource is LocalVideoSource) && (audioSource == null || audioSource is LocalAudioSource))
UIDialogs.toast(context, context.getString(R.string.offline_playback), false);
if((videoSource == null || videoSource is LocalVideoSource) && (audioSource == null || audioSource is LocalAudioSource)) {
Logger.i(TAG, "Time since last offline playback toast: " + (System.currentTimeMillis() - _lastOfflinePlaybackToastTime).toString())
if (System.currentTimeMillis() - _lastOfflinePlaybackToastTime > 5000) {
UIDialogs.toast(context, context.getString(R.string.offline_playback), false);
_lastOfflinePlaybackToastTime = System.currentTimeMillis()
}
}
//If LiveStream, set to end
if(videoSource is IDashManifestSource || videoSource is IHLSManifestSource) {
if (video?.isLive == true) {
@@ -2379,8 +2384,8 @@ class VideoDetailView : ConstraintLayout {
}
fun isLandscapeVideo(): Boolean? {
var videoSourceWidth = _player.exoPlayer?.player?.videoSize?.width
var videoSourceHeight = _player.exoPlayer?.player?.videoSize?.height
val videoSourceWidth = _player.exoPlayer?.player?.videoSize?.width
val videoSourceHeight = _player.exoPlayer?.player?.videoSize?.height
return if (videoSourceWidth == null || videoSourceHeight == null || videoSourceWidth == 0 || videoSourceHeight == 0){
null
@@ -2582,7 +2587,6 @@ class VideoDetailView : ConstraintLayout {
_overlayContainer.removeAllViews();
_overlay_quality_selector?.hide();
_player.setFullScreen(true)
_player.fillHeight(false)
_layoutPlayerContainer.setPadding(0, 0, 0, 0);
}
@@ -2797,7 +2801,7 @@ class VideoDetailView : ConstraintLayout {
super.onConfigurationChanged(newConfig)
if (fragment.state == VideoDetailFragment.State.MINIMIZED) {
_player.fillHeight(true)
} else if (!fragment.isFullscreen) {
} else if (!fragment.isFullscreen && !fragment.isInPictureInPicture) {
_player.fitHeight()
}
}
@@ -3030,8 +3034,6 @@ class VideoDetailView : ConstraintLayout {
const val TAG_MORE = "MORE";
private val _buttonPinStore = FragmentedStorage.get<StringArrayStorage>("videoPinnedButtons");
private var _lastOfflinePlaybackToastTime: Long = 0
}
}
@@ -12,70 +12,109 @@ import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.receivers.MediaControlReceiver
import com.futo.platformplayer.timestampRegex
import com.futo.platformplayer.views.behavior.NonScrollingTextView
import com.futo.platformplayer.views.behavior.NonScrollingTextView.Companion
import kotlinx.coroutines.runBlocking
class PlatformLinkMovementMethod : LinkMovementMethod {
private val _context: Context;
class PlatformLinkMovementMethod(private val _context: Context) : LinkMovementMethod() {
constructor(context: Context) : super() {
_context = context;
}
private var pressedLinks: Array<URLSpan>? = null
private var linkPressed = false
private var downX = 0f
private var downY = 0f
private val touchSlop = 20
override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean {
val action = event.action;
Logger.i(TAG, "onTouchEvent (action = $action)")
if (action == MotionEvent.ACTION_UP) {
val x = event.x.toInt() - widget.totalPaddingLeft + widget.scrollX;
val y = event.y.toInt() - widget.totalPaddingTop + widget.scrollY;
val action = event.actionMasked
val layout = widget.layout;
val line = layout.getLineForVertical(y);
val off = layout.getOffsetForHorizontal(line, x.toFloat());
val links = buffer.getSpans(off, off, URLSpan::class.java);
when (action) {
MotionEvent.ACTION_DOWN -> {
val links = findLinksAtTouchPosition(widget, buffer, event)
if (links.isNotEmpty()) {
pressedLinks = links
linkPressed = true
downX = event.x
downY = event.y
widget.parent?.requestDisallowInterceptTouchEvent(true)
return true
} else {
linkPressed = false
pressedLinks = null
}
}
if (links.isNotEmpty()) {
runBlocking {
for (link in links) {
Logger.i(TAG) { "Link clicked '${link.url}'." };
MotionEvent.ACTION_MOVE -> {
if (linkPressed) {
val dx = event.x - downX
val dy = event.y - downY
if (Math.abs(dx) > touchSlop || Math.abs(dy) > touchSlop) {
linkPressed = false
pressedLinks = null
widget.parent?.requestDisallowInterceptTouchEvent(false)
return false
}
return true
}
}
if (_context is MainActivity) {
if (_context.handleUrl(link.url)) {
continue;
}
MotionEvent.ACTION_UP -> {
if (linkPressed && pressedLinks != null) {
val dx = event.x - downX
val dy = event.y - downY
if (Math.abs(dx) <= touchSlop && Math.abs(dy) <= touchSlop) {
runBlocking {
for (link in pressedLinks!!) {
Logger.i(TAG) { "Link clicked '${link.url}'." }
if (timestampRegex.matches(link.url)) {
val tokens = link.url.split(':');
if (_context is MainActivity) {
if (_context.handleUrl(link.url)) continue
if (timestampRegex.matches(link.url)) {
val tokens = link.url.split(':')
var time_s = -1L
when (tokens.size) {
2 -> time_s = tokens[0].toLong() * 60 + tokens[1].toLong()
3 -> time_s = tokens[0].toLong() * 3600 +
tokens[1].toLong() * 60 +
tokens[2].toLong()
}
var time_s = -1L;
if (tokens.size == 2) {
time_s = tokens[0].toLong() * 60 + tokens[1].toLong();
} else if (tokens.size == 3) {
time_s =
tokens[0].toLong() * 60 * 60 + tokens[1].toLong() * 60 + tokens[2].toLong();
}
if (time_s != -1L) {
MediaControlReceiver.onSeekToReceived.emit(time_s * 1000);
continue;
if (time_s != -1L) {
MediaControlReceiver.onSeekToReceived.emit(time_s * 1000)
continue
}
}
}
_context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url)))
}
}
_context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url)));
pressedLinks = null
linkPressed = false
return true
} else {
pressedLinks = null
linkPressed = false
}
}
}
return true;
MotionEvent.ACTION_CANCEL -> {
linkPressed = false
pressedLinks = null
}
}
return super.onTouchEvent(widget, buffer, event);
return super.onTouchEvent(widget, buffer, event)
}
private fun findLinksAtTouchPosition(widget: TextView, buffer: Spannable, event: MotionEvent): Array<URLSpan> {
val x = (event.x - widget.totalPaddingLeft + widget.scrollX).toInt()
val y = (event.y - widget.totalPaddingTop + widget.scrollY).toInt()
val layout = widget.layout ?: return emptyArray()
val line = layout.getLineForVertical(y)
val off = layout.getOffsetForHorizontal(line, x.toFloat())
return buffer.getSpans(off, off, URLSpan::class.java)
}
companion object {
val TAG = "PlatformLinkMovementMethod";
const val TAG = "PlatformLinkMovementMethod"
}
}
}
@@ -0,0 +1,35 @@
package com.futo.platformplayer.receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.view.KeyEvent
import com.futo.platformplayer.logging.Logger
class MediaButtonReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val keyEvent: KeyEvent? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent?.getParcelableExtra(Intent.EXTRA_KEY_EVENT, KeyEvent::class.java)
} else {
@Suppress("DEPRECATION")
(intent?.getParcelableExtra(Intent.EXTRA_KEY_EVENT))
}
Logger.i(TAG, "Received media button intent, keyCode: " + keyEvent?.keyCode)
if (keyEvent != null && keyEvent.action == KeyEvent.ACTION_DOWN) {
when (keyEvent.keyCode) {
KeyEvent.KEYCODE_MEDIA_PLAY -> MediaControlReceiver.onPlayReceived.emit()
KeyEvent.KEYCODE_MEDIA_PAUSE -> MediaControlReceiver.onPauseReceived.emit()
KeyEvent.KEYCODE_MEDIA_NEXT -> MediaControlReceiver.onNextReceived.emit()
KeyEvent.KEYCODE_MEDIA_PREVIOUS -> MediaControlReceiver.onPreviousReceived.emit()
KeyEvent.KEYCODE_MEDIA_STOP -> MediaControlReceiver.onCloseReceived.emit()
}
}
}
companion object {
private val TAG = "MediaButtonReceiver"
}
}
@@ -23,6 +23,7 @@ import android.support.v4.media.MediaMetadataCompat
import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
import android.util.Log
import android.view.KeyEvent
import androidx.core.app.NotificationCompat
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
@@ -32,6 +33,7 @@ import com.futo.platformplayer.Settings
import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.receivers.MediaButtonReceiver
import com.futo.platformplayer.receivers.MediaControlReceiver
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePlayer
@@ -91,6 +93,7 @@ class MediaPlaybackService : Service() {
return START_STICKY;
}
fun setupNotificationRequirements() {
_audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager;
_notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager;
@@ -101,6 +104,7 @@ class MediaPlaybackService : Service() {
_notificationManager!!.createNotificationChannel(_notificationChannel!!);
_mediaSession = MediaSessionCompat(this, "PlayerState");
_mediaSession?.isActive = true
_mediaSession?.setPlaybackState(PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_PLAYING, 0, 1f)
.build());
@@ -143,6 +147,12 @@ class MediaPlaybackService : Service() {
MediaControlReceiver.onNextReceived.emit();
}
});
_mediaSession?.setMediaButtonReceiver(PendingIntent.getBroadcast(
this@MediaPlaybackService,
0,
Intent(Intent.ACTION_MEDIA_BUTTON).setClass(this@MediaPlaybackService, MediaButtonReceiver::class.java),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
))
}
override fun onCreate() {
@@ -47,6 +47,7 @@ import com.futo.platformplayer.services.DownloadService
import com.futo.platformplayer.stores.FragmentedStorage
import com.futo.platformplayer.stores.v2.ManagedStore
import com.futo.platformplayer.views.ToastView
import com.futo.polycentric.core.ApiMethods
import kotlinx.coroutines.*
import java.io.File
import java.util.*
@@ -156,6 +157,7 @@ class StateApp {
//Files
private var _tempDirectory: File? = null;
private var _cacheDirectory: File? = null;
private var _persistentDirectory: File? = null;
@@ -324,6 +326,9 @@ class StateApp {
_tempDirectory?.deleteRecursively();
}
_tempDirectory?.mkdirs();
_cacheDirectory = File(context.filesDir, "cache");
if(_cacheDirectory?.exists() == false)
_cacheDirectory?.mkdirs();
_persistentDirectory = File(context.filesDir, "persist");
if(_persistentDirectory?.exists() == false) {
_persistentDirectory?.mkdirs();
@@ -383,6 +388,11 @@ class StateApp {
Logger.i(TAG, "MainApp Starting");
initializeFiles(true);
if(Settings.instance.other.polycentricLocalCache) {
Logger.i(TAG, "Initialize Polycentric Disk Cache")
_cacheDirectory?.let { ApiMethods.initCache(it) };
}
val logFile = File(context.filesDir, "log.txt");
if (Settings.instance.logging.logLevel > LogLevel.NONE.value) {
val fileLogConsumer = FileLogConsumer(logFile, LogLevel.fromInt(Settings.instance.logging.logLevel), false);
@@ -251,7 +251,7 @@ class StateDownloads {
}
else {
Logger.i(TAG, "New watchlater video ${item.name}");
download(VideoDownload(item, playlistDownload.targetPxCount, playlistDownload.targetBitrate)
download(VideoDownload(item, playlistDownload.targetPxCount, playlistDownload.targetBitrate, true)
.withGroup(VideoDownload.GROUP_WATCHLATER, VideoDownload.GROUP_WATCHLATER), false);
hasNew = true;
}
@@ -296,7 +296,7 @@ class StateDownloads {
}
else {
Logger.i(TAG, "New playlist video ${item.name}");
download(VideoDownload(item, playlistDownload.targetPxCount, playlistDownload.targetBitrate)
download(VideoDownload(item, playlistDownload.targetPxCount, playlistDownload.targetBitrate, true)
.withGroup(VideoDownload.GROUP_PLAYLIST, playlist.id), false);
hasNew = true;
}
@@ -112,18 +112,23 @@ class StateSync {
Logger.i(TAG, "Sync key pair initialized (public key = ${publicKey})")
_thread = Thread {
val serverSocket = ServerSocket(PORT)
_serverSocket = serverSocket
try {
val serverSocket = ServerSocket(PORT)
_serverSocket = serverSocket
Log.i(TAG, "Running on port ${PORT} (TCP)")
Log.i(TAG, "Running on port ${PORT} (TCP)")
while (_started) {
val socket = serverSocket.accept()
val session = createSocketSession(socket, true) { session, socketSession ->
while (_started) {
val socket = serverSocket.accept()
val session = createSocketSession(socket, true) { session, socketSession ->
}
session.startAsResponder()
}
session.startAsResponder()
} catch (e: Throwable) {
Logger.e(TAG, "Failed to bind server socket to port ${PORT}", e)
UIDialogs.toast("Failed to start sync, port in use")
}
}.apply { start() }
@@ -24,7 +24,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class AnnouncementView : LinearLayout {
private val _root: ConstraintLayout;
private val _root: FrameLayout;
private val _textTitle: TextView;
private val _textCounter: TextView;
private val _textBody: TextView;
@@ -45,9 +45,6 @@ class AnnouncementView : LinearLayout {
_scope = findViewTreeLifecycleOwner()?.lifecycleScope ?: StateApp.instance.scopeOrNull; //TODO: Fetch correct scope
val dp10 = 10.dp(resources);
setPadding(dp10, dp10, dp10, dp10);
_root = findViewById(R.id.root);
_textTitle = findViewById(R.id.text_title);
_textCounter = findViewById(R.id.text_counter);
@@ -115,12 +112,12 @@ class AnnouncementView : LinearLayout {
_currentAnnouncement = announcement;
if (announcement == null) {
visibility = View.GONE
_root.visibility = View.GONE
onClose.emit()
return;
}
visibility = View.VISIBLE
_root.visibility = View.VISIBLE
_textTitle.text = announcement.title;
_textBody.text = announcement.msg;
@@ -16,73 +16,113 @@ import com.futo.platformplayer.timestampRegex
import kotlinx.coroutines.runBlocking
class NonScrollingTextView : androidx.appcompat.widget.AppCompatTextView {
private var _lastTouchedLinks: Array<URLSpan>? = null
private var downX = 0f
private var downY = 0f
private var linkPressed = false
private val touchSlop = 20
constructor(context: Context) : super(context) {}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}
override fun scrollTo(x: Int, y: Int) {
//do nothing
// do nothing
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
val action = event?.action
Logger.i(TAG, "onTouchEvent (action = $action)");
val action = event?.actionMasked
if (event == null) return super.onTouchEvent(event)
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
val x = event.x.toInt()
val y = event.y.toInt()
when (action) {
MotionEvent.ACTION_DOWN -> {
val x = event.x.toInt()
val y = event.y.toInt()
val layout: Layout? = this.layout
if (layout != null) {
val line = layout.getLineForVertical(y)
val offset = layout.getOffsetForHorizontal(line, x.toFloat())
val text = this.text
if (text is Spannable) {
val layout: Layout? = this.layout
if (layout != null && this.text is Spannable) {
val offset = layout.getOffsetForHorizontal(layout.getLineForVertical(y), x.toFloat())
val text = this.text as Spannable
val links = text.getSpans(offset, offset, URLSpan::class.java)
if (links.isNotEmpty()) {
runBlocking {
for (link in links) {
Logger.i(PlatformLinkMovementMethod.TAG) { "Link clicked '${link.url}'." };
val c = context;
if (c is MainActivity) {
if (c.handleUrl(link.url)) {
continue;
}
if (timestampRegex.matches(link.url)) {
val tokens = link.url.split(':');
var time_s = -1L;
if (tokens.size == 2) {
time_s = tokens[0].toLong() * 60 + tokens[1].toLong();
} else if (tokens.size == 3) {
time_s = tokens[0].toLong() * 60 * 60 + tokens[1].toLong() * 60 + tokens[2].toLong();
}
if (time_s != -1L) {
MediaControlReceiver.onSeekToReceived.emit(time_s * 1000);
continue;
}
}
c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url)));
}
}
}
parent?.requestDisallowInterceptTouchEvent(true)
_lastTouchedLinks = links
downX = event.x
downY = event.y
linkPressed = true
return true
} else {
linkPressed = false
_lastTouchedLinks = null
}
}
}
MotionEvent.ACTION_MOVE -> {
if (linkPressed) {
val dx = event.x - downX
val dy = event.y - downY
if (Math.abs(dx) > touchSlop || Math.abs(dy) > touchSlop) {
linkPressed = false
_lastTouchedLinks = null
parent?.requestDisallowInterceptTouchEvent(false)
return false
}
return true
}
}
MotionEvent.ACTION_UP -> {
if (linkPressed && _lastTouchedLinks != null) {
val dx = event.x - downX
val dy = event.y - downY
if (Math.abs(dx) <= touchSlop && Math.abs(dy) <= touchSlop) {
runBlocking {
for (link in _lastTouchedLinks!!) {
Logger.i(PlatformLinkMovementMethod.TAG) { "Link clicked '${link.url}'." }
val c = context
if (c is MainActivity) {
if (c.handleUrl(link.url)) continue
if (timestampRegex.matches(link.url)) {
val tokens = link.url.split(':')
var time_s = -1L
when (tokens.size) {
2 -> time_s = tokens[0].toLong() * 60 + tokens[1].toLong()
3 -> time_s = tokens[0].toLong() * 3600 +
tokens[1].toLong() * 60 +
tokens[2].toLong()
}
if (time_s != -1L) {
MediaControlReceiver.onSeekToReceived.emit(time_s * 1000)
continue
}
}
c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url)))
} else {
c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url)))
}
}
}
_lastTouchedLinks = null
linkPressed = false
return true
} else {
linkPressed = false
_lastTouchedLinks = null
}
}
}
MotionEvent.ACTION_CANCEL -> {
linkPressed = false
_lastTouchedLinks = null
}
}
super.onTouchEvent(event)
return false
return super.onTouchEvent(event)
}
companion object {
private const val TAG = "NonScrollingTextView"
}
}
}
@@ -14,6 +14,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.api.media.models.comments.IPlatformComment
import com.futo.platformplayer.api.media.models.comments.LazyComment
import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
import com.futo.platformplayer.api.media.structures.IAsyncPager
@@ -267,9 +268,13 @@ class CommentsList : ConstraintLayout {
}
fun replaceComment(c: PolycentricPlatformComment, newComment: PolycentricPlatformComment) {
val index = _comments.indexOf(c);
_comments[index] = newComment;
_adapterComments.notifyItemChanged(_adapterComments.childToParentPosition(index));
val index = _comments.indexOfFirst { it == c || (it is LazyComment && it.getUnderlyingComment() == c) };
if (index >= 0) {
_comments[index] = newComment;
_adapterComments.notifyItemChanged(_adapterComments.childToParentPosition(index));
} else {
Logger.w(TAG, "Parent comment not found")
}
}
companion object {
@@ -592,6 +592,11 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
@OptIn(UnstableApi::class)
fun setFullScreen(fullScreen: Boolean) {
// prevent fullscreen before the video has loaded to make sure we know whether it's a vertical or horizontal video
if(exoPlayer?.player?.videoSize?.height ?: 0 == 0 && fullScreen){
return
}
updateRotateLock()
if (isFullScreen == fullScreen) {
+8 -1
View File
@@ -35,6 +35,12 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<com.futo.platformplayer.views.announcements.AnnouncementView
android:id="@+id/announcement_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone" />
<LinearLayout
android:id="@+id/container_sort_by"
android:layout_width="match_parent"
@@ -110,7 +116,8 @@
android:visibility="gone"
android:id="@+id/empty_pager_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="wrap_content"
android:layout_marginTop="30dp" />
</FrameLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
+87 -84
View File
@@ -1,119 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:id="@+id/root"
android:background="@drawable/background_16_round_4dp"
android:paddingLeft="10dp"
android:paddingTop="10dp"
android:paddingRight="10dp">
android:id="@+id/root">
<TextView android:id="@+id/text_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
tools:text="Do you know?"
android:fontFamily="@font/inter_semibold"
android:textSize="15sp"
android:textColor="@color/white"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toLeftOf="@id/text_counter" />
<TextView android:id="@+id/text_counter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="1/4"
android:fontFamily="@font/inter_regular"
android:textSize="12dp"
android:textColor="#585656"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<TextView android:id="@+id/text_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Our app now supports dark mode for a better viewing experience. Check it out in your settings. Enjoy the new look!"
android:fontFamily="@font/inter_light"
android:textSize="14sp"
android:textColor="#9D9D9D"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_title"/>
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/text_body"
app:layout_constraintRight_toRightOf="parent"
android:paddingTop="4dp"
android:paddingBottom="10dp">
android:background="@drawable/background_16_round_4dp"
android:paddingLeft="10dp"
android:paddingTop="10dp"
android:paddingRight="10dp"
android:layout_margin="10dp">
<TextView android:id="@+id/text_time"
<TextView android:id="@+id/text_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
tools:text="Do you know?"
android:fontFamily="@font/inter_semibold"
android:textSize="15sp"
android:textColor="@color/white"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toLeftOf="@id/text_counter" />
<TextView android:id="@+id/text_counter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="2022-03-01"
tools:text="1/4"
android:fontFamily="@font/inter_regular"
android:textSize="12dp"
android:textColor="#585656"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<Space android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView android:id="@+id/text_never"
<TextView android:id="@+id/text_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/never"
android:fontFamily="@font/inter_regular"
tools:text="Our app now supports dark mode for a better viewing experience. Check it out in your settings. Enjoy the new look!"
android:fontFamily="@font/inter_light"
android:textSize="14sp"
android:textColor="@color/colorPrimary"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
app:layout_constraintTop_toBottomOf="@id/text_body"
app:layout_constraintRight_toLeftOf="@id/text_close"/>
android:textColor="#9D9D9D"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_title"/>
<TextView android:id="@+id/text_close"
android:layout_width="wrap_content"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/dismiss"
android:fontFamily="@font/inter_regular"
android:textSize="14sp"
android:textColor="@color/colorPrimary"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
app:layout_constraintTop_toBottomOf="@id/text_body"
app:layout_constraintRight_toLeftOf="@id/button_action"/>
app:layout_constraintRight_toRightOf="parent"
android:paddingTop="4dp"
android:paddingBottom="10dp">
<FrameLayout android:id="@+id/button_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/background_button_primary_round_4dp"
app:layout_constraintTop_toBottomOf="@id/text_body"
app:layout_constraintRight_toRightOf="parent">
<TextView android:id="@+id/text_action"
<TextView android:id="@+id/text_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="What's New"
tools:text="2022-03-01"
android:fontFamily="@font/inter_regular"
android:textSize="12dp"
android:textColor="#585656"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<Space android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView android:id="@+id/text_never"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/never"
android:fontFamily="@font/inter_regular"
android:textSize="14sp"
android:textColor="@color/white"
android:textColor="@color/colorPrimary"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
app:layout_constraintTop_toBottomOf="@id/text_body"
app:layout_constraintRight_toLeftOf="@id/text_close"/>
</FrameLayout>
</LinearLayout>
<TextView android:id="@+id/text_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dismiss"
android:fontFamily="@font/inter_regular"
android:textSize="14sp"
android:textColor="@color/colorPrimary"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
app:layout_constraintTop_toBottomOf="@id/text_body"
app:layout_constraintRight_toLeftOf="@id/button_action"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout android:id="@+id/button_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/background_button_primary_round_4dp"
app:layout_constraintTop_toBottomOf="@id/text_body"
app:layout_constraintRight_toRightOf="parent">
<TextView android:id="@+id/text_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="What's New"
android:fontFamily="@font/inter_regular"
android:textSize="14sp"
android:textColor="@color/white"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
app:layout_constraintTop_toBottomOf="@id/text_body"
app:layout_constraintRight_toLeftOf="@id/text_close"/>
</FrameLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
+1 -1
View File
@@ -2,5 +2,5 @@
<resources>
<dimen name="minimized_player_max_width">500dp</dimen>
<dimen name="app_bar_height">200dp</dimen>
<dimen name="landscape_threshold">300dp</dimen>
<integer name="column_width_dp">400</integer>
</resources>
+3
View File
@@ -199,6 +199,7 @@
<string name="previous">Previous</string>
<string name="next">Next</string>
<string name="comment">Comment</string>
<string name="not_empty_close">Comment is not empty, close anyway?</string>
<string name="str_import">Import</string>
<string name="my_playlist_name">My Playlist Name</string>
<string name="do_you_want_to_import_this_store">Do you want to import this store?</string>
@@ -424,6 +425,8 @@
<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>
<string name="enable_polycentric">Enable Polycentric</string>
<string name="polycentric_local_cache">Enable Polycentric Local Caching</string>
<string name="polycentric_local_cache_description">Caches polycentric results on-device to reduce load times, changing requires app reboot</string>
<string name="can_be_disabled_when_you_are_experiencing_issues">Can be disabled when you are experiencing issues</string>
<string name="bypass_rotation_prevention_description">Allows for rotation on non-video views.\nWARNING: Not designed for it</string>
<string name="bypass_rotation_prevention_warning">This may cause unexpected behavior, and is mostly untested.</string>
+1
View File
@@ -15,6 +15,7 @@ touch $DOCUMENT_ROOT/maintenance.file
# Swap over the content
echo "Deploying content..."
cp ./app/build/outputs/bundle/playstoreRelease/app-playstore-release.aab $DOCUMENT_ROOT/app-playstore-release.aab
aws s3 cp ./app/build/outputs/bundle/playstoreRelease/app-playstore-release.aab s3://artifacts-grayjay-app/app-playstore-release.aab
# Notify Cloudflare to wipe the CDN cache