Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay

This commit is contained in:
Kelvin
2023-11-07 15:43:21 +01:00
49 changed files with 1122 additions and 216 deletions
@@ -0,0 +1,20 @@
package com.futo.platformplayer
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.RecyclerView
class HorizontalSpaceItemDecoration(private val startSpace: Int, private val betweenSpace: Int, private val endSpace: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
outRect.left = betweenSpace
val position = parent.getChildAdapterPosition(view)
if (position == 0) {
outRect.left = startSpace
}
else if (position == state.itemCount - 1) {
outRect.right = endSpace
}
}
}
@@ -11,11 +11,12 @@ import com.futo.platformplayer.R
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.views.SupportView
import com.futo.platformplayer.views.buttons.BigButton
class ChannelMonetizationFragment : Fragment, IChannelTabFragment {
private var _buttonStore: BigButton? = null;
private var _supportView: SupportView? = null
private var _lastChannel: IPlatformChannel? = null;
private var _lastPolycentricProfile: PolycentricProfile? = null;
@@ -24,20 +25,7 @@ class ChannelMonetizationFragment : Fragment, IChannelTabFragment {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_channel_monetization, container, false);
_buttonStore = view.findViewById(R.id.button_store);
_buttonStore?.onClick?.subscribe {
_lastPolycentricProfile?.systemState?.store?.let {
try {
val uri = Uri.parse(it);
val intent = Intent(Intent.ACTION_VIEW)
intent.data = uri
startActivity(intent)
} catch (e: Throwable) {
Logger.e(TAG, "Failed to open URI: '${it}'.", e);
}
}
};
_supportView = view.findViewById(R.id.support);
_lastChannel?.also {
setChannel(it);
@@ -52,24 +40,16 @@ class ChannelMonetizationFragment : Fragment, IChannelTabFragment {
override fun onDestroyView() {
super.onDestroyView();
_buttonStore = null;
_supportView = null;
}
override fun setChannel(channel: IPlatformChannel) {
_lastChannel = channel;
_buttonStore?.visibility = View.GONE;
}
fun setPolycentricProfile(polycentricProfile: PolycentricProfile?, animate: Boolean) {
_lastPolycentricProfile = polycentricProfile;
if (polycentricProfile == null) {
return;
}
if (polycentricProfile.systemState.store.isNotEmpty()) {
_buttonStore?.visibility = View.VISIBLE;
}
_lastPolycentricProfile = polycentricProfile
_supportView?.setPolycentricProfile(polycentricProfile, animate)
}
companion object {
@@ -70,6 +70,7 @@ import com.futo.platformplayer.receivers.MediaControlReceiver
import com.futo.platformplayer.states.*
import com.futo.platformplayer.stores.FragmentedStorage
import com.futo.platformplayer.stores.StringArrayStorage
import com.futo.platformplayer.views.MonetizationView
import com.futo.platformplayer.views.behavior.TouchInterceptFrameLayout
import com.futo.platformplayer.views.casting.CastView
import com.futo.platformplayer.views.comments.AddCommentView
@@ -79,6 +80,7 @@ import com.futo.platformplayer.views.overlays.DescriptionOverlay
import com.futo.platformplayer.views.overlays.LiveChatOverlay
import com.futo.platformplayer.views.overlays.QueueEditorOverlay
import com.futo.platformplayer.views.overlays.RepliesOverlay
import com.futo.platformplayer.views.overlays.SupportOverlay
import com.futo.platformplayer.views.overlays.slideup.*
import com.futo.platformplayer.views.pills.PillRatingLikesDislikes
import com.futo.platformplayer.views.pills.RoundButton
@@ -191,6 +193,7 @@ class VideoDetailView : ConstraintLayout {
private val _container_content_replies: RepliesOverlay;
private val _container_content_description: DescriptionOverlay;
private val _container_content_liveChat: LiveChatOverlay;
private val _container_content_support: SupportOverlay;
private var _container_content_current: View;
@@ -200,9 +203,7 @@ class VideoDetailView : ConstraintLayout {
private val _imageDislikeIcon: ImageView;
private val _imageLikeIcon: ImageView;
private val _buttonSupport: LinearLayout;
private val _buttonStore: LinearLayout;
private val _layoutMonetization: LinearLayout;
private val _monetization: MonetizationView;
private val _buttonMore: RoundButton;
@@ -292,6 +293,7 @@ class VideoDetailView : ConstraintLayout {
_container_content_replies = findViewById(R.id.videodetail_container_replies);
_container_content_description = findViewById(R.id.videodetail_container_description);
_container_content_liveChat = findViewById(R.id.videodetail_container_livechat);
_container_content_support = findViewById(R.id.videodetail_container_support)
_textComments = findViewById(R.id.text_comments);
_addCommentView = findViewById(R.id.add_comment_view);
@@ -310,11 +312,7 @@ class VideoDetailView : ConstraintLayout {
_imageLikeIcon = findViewById(R.id.image_like_icon);
_imageDislikeIcon = findViewById(R.id.image_dislike_icon);
_buttonSupport = findViewById(R.id.button_support);
_buttonStore = findViewById(R.id.button_store);
_layoutMonetization = findViewById(R.id.layout_monetization);
_layoutMonetization.visibility = View.GONE;
_monetization = findViewById(R.id.monetization);
_player.attachPlayer();
@@ -327,16 +325,12 @@ class VideoDetailView : ConstraintLayout {
fragment.navigate<VideoDetailFragment>(it.targetUrl);
};
_buttonSupport.setOnClickListener {
val author = video?.author ?: _searchVideo?.author;
author?.let { fragment.navigate<ChannelFragment>(it).selectTab(2); };
fragment.lifecycleScope.launch {
delay(100);
fragment.minimizeVideoDetail();
};
_monetization.onSupportTap.subscribe {
_container_content_support.setPolycentricProfile(_polycentricProfile?.profile, false);
switchContentView(_container_content_support);
};
_buttonStore.setOnClickListener {
_monetization.onStoreTap.subscribe {
_polycentricProfile?.profile?.systemState?.store?.let {
try {
val uri = Uri.parse(it);
@@ -349,6 +343,13 @@ class VideoDetailView : ConstraintLayout {
}
};
_player.attachPlayer();
_container_content_liveChat.onRaidNow.subscribe {
StatePlayer.instance.clearQueue();
fragment.navigate<VideoDetailFragment>(it.targetUrl);
};
StateApp.instance.preventPictureInPicture.subscribe(this) {
Logger.i(TAG, "StateApp.instance.preventPictureInPicture.subscribe preventPictureInPicture = true");
preventPictureInPicture = true;
@@ -545,6 +546,7 @@ class VideoDetailView : ConstraintLayout {
_container_content_liveChat.onClose.subscribe { switchContentView(_container_content_main); };
_container_content_queue.onClose.subscribe { switchContentView(_container_content_main); };
_container_content_replies.onClose.subscribe { switchContentView(_container_content_main); };
_container_content_support.onClose.subscribe { switchContentView(_container_content_main); };
_description_viewMore.setOnClickListener {
switchContentView(_container_content_description);
@@ -847,6 +849,7 @@ class VideoDetailView : ConstraintLayout {
_container_content_replies.cleanup();
_container_content_queue.cleanup();
_container_content_description.cleanup();
_container_content_support.cleanup();
StateCasting.instance.onActiveDevicePlayChanged.remove(this);
StateCasting.instance.onActiveDeviceTimeChanged.remove(this);
StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this);
@@ -1809,6 +1812,7 @@ class VideoDetailView : ConstraintLayout {
_isCasting = isCasting;
if(isCasting) {
setFullscreen(false);
_player.stop();
_player.hideControls(false);
_cast.visibility = View.VISIBLE;
@@ -2096,12 +2100,7 @@ class VideoDetailView : ConstraintLayout {
_creatorThumbnail.setHarborAvailable(profile != null, animate);
}
if (profile != null) {
_channelName.text = cachedPolycentricProfile.profile.systemState.username;
_layoutMonetization.visibility = View.VISIBLE;
} else {
_layoutMonetization.visibility = View.GONE;
}
_monetization.setPolycentricProfile(cachedPolycentricProfile, animate);
}
fun setProgressBarOverlayed(isOverlayed: Boolean?) {
@@ -1,5 +1,7 @@
package com.futo.platformplayer.images;
import android.util.Log;
import androidx.annotation.NonNull;
import com.bumptech.glide.Priority;
@@ -39,7 +39,12 @@ class PolycentricCache {
ContentType.USERNAME.value,
ContentType.DESCRIPTION.value,
ContentType.STORE.value,
ContentType.SERVER.value
ContentType.SERVER.value,
ContentType.STORE_DATA.value,
ContentType.PROMOTION_BANNER.value,
ContentType.PROMOTION.value,
ContentType.MEMBERSHIP_URLS.value,
ContentType.DONATION_DESTINATIONS.value
)
).eventsList.map { e -> SignedEvent.fromProto(e) };
@@ -10,6 +10,7 @@ import android.media.AudioFocusRequest
import android.media.AudioManager
import android.media.AudioManager.OnAudioFocusChangeListener
import android.media.MediaMetadata
import android.os.Build
import android.os.IBinder
import android.os.SystemClock
import android.support.v4.media.MediaMetadataCompat
@@ -278,7 +279,13 @@ class MediaPlaybackService : Service() {
Logger.i(TAG, "Updating notification bitmap=${if (bitmap != null) "yes" else "no."} channelId=${channel.id} icon=${icon} video=${video?.name ?: ""} playWhenReady=${playWhenReady} session.sessionToken=${session.sessionToken}");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// For API 29 and above
startForeground(MEDIA_NOTIF_ID, notif, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
} else {
// For API 28 and below
startForeground(MEDIA_NOTIF_ID, notif);
}
_notif_last_bitmap = bitmap;
}
@@ -0,0 +1,145 @@
package com.futo.platformplayer.views
import android.content.Context
import android.net.Uri
import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.R
import com.futo.platformplayer.HorizontalSpaceItemDecoration
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.polycentric.PolycentricCache
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny
import com.futo.platformplayer.views.adapters.viewholders.StoreItemViewHolder
import com.futo.platformplayer.views.platform.PlatformIndicator
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
@Serializable
data class StoreItem(
val url: String,
val name: String,
val image: String
);
class MonetizationView : LinearLayout {
private val _buttonSupport: LinearLayout;
private val _buttonStore: LinearLayout;
private val _buttonMembership: LinearLayout;
private val _platformIndicator: PlatformIndicator;
private val _textMerchandise: TextView;
private val _recyclerMerchandise: RecyclerView;
private val _loaderMerchandise: Loader;
private val _layoutMerchandise: FrameLayout;
private var _merchandiseAdapterView: AnyAdapterView<StoreItem, StoreItemViewHolder>? = null;
private val _root: LinearLayout;
private val _taskLoadMerchandise = TaskHandler<String, List<StoreItem>>(StateApp.instance.scopeGetter, { url ->
val client = ManagedHttpClient();
val result = client.get("https://storecache.grayjay.app/StoreData?url=$url")
if (!result.isOk) {
throw Exception("Failed to retrieve store data.");
}
return@TaskHandler result.body?.let { Json.decodeFromString<List<StoreItem>>(it.string()); } ?: listOf();
})
.success { setMerchandise(it) }
.exception<Throwable> {
Logger.w(TAG, "Failed to load merchandise profile.", it);
};
val onSupportTap = Event0();
val onStoreTap = Event0();
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
inflate(context, R.layout.view_monetization, this);
_buttonSupport = findViewById(R.id.button_support);
_buttonStore = findViewById(R.id.button_store);
_buttonMembership = findViewById(R.id.button_membership);
_platformIndicator = findViewById(R.id.platform_indicator);
_textMerchandise = findViewById(R.id.text_merchandise);
_recyclerMerchandise = findViewById(R.id.recycler_merchandise);
_loaderMerchandise = findViewById(R.id.loader_merchandise);
_layoutMerchandise = findViewById(R.id.layout_merchandise);
_root = findViewById(R.id.root);
_recyclerMerchandise.addItemDecoration(HorizontalSpaceItemDecoration(30, 16, 30))
_merchandiseAdapterView = _recyclerMerchandise.asAny(orientation = RecyclerView.HORIZONTAL);
_buttonSupport.setOnClickListener { onSupportTap.emit(); }
_buttonStore.setOnClickListener { onStoreTap.emit(); }
_buttonMembership.visibility = View.GONE;
setMerchandise(null);
}
fun setPlatformMembership() {
//TODO:
}
private fun setMerchandise(items: List<StoreItem>?) {
_loaderMerchandise.stop();
if (items == null) {
_textMerchandise.visibility = View.GONE;
_recyclerMerchandise.visibility = View.GONE;
_layoutMerchandise.visibility = View.GONE;
} else {
_textMerchandise.visibility = View.VISIBLE;
_recyclerMerchandise.visibility = View.VISIBLE;
_layoutMerchandise.visibility = View.VISIBLE;
_merchandiseAdapterView?.adapter?.setData(items.shuffled());
}
}
fun setPolycentricProfile(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean) {
val profile = cachedPolycentricProfile?.profile;
if (profile != null) {
if (profile.systemState.store.isNotEmpty()) {
_buttonStore.visibility = View.VISIBLE;
} else {
_buttonStore.visibility = View.GONE;
}
_root.visibility = View.VISIBLE;
} else {
_root.visibility = View.GONE;
}
setMerchandise(null);
val storeData = profile?.systemState?.storeData;
if (storeData != null) {
try {
val storeItems = Json.decodeFromString<List<StoreItem>>(storeData);
setMerchandise(storeItems);
} catch (_: Throwable) {
try {
val uri = Uri.parse(storeData);
if (uri.isAbsolute) {
_taskLoadMerchandise.run(storeData);
_loaderMerchandise.start();
} else {
Logger.i(TAG, "Merchandise not loaded, not URL nor JSON")
}
} catch (_: Throwable) {
}
}
}
}
companion object {
const val TAG = "MonetizationView";
}
}
@@ -0,0 +1,243 @@
package com.futo.platformplayer.views
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Drawable
import android.net.Uri
import android.util.AttributeSet
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.futo.platformplayer.R
import com.futo.platformplayer.dp
import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile
import com.futo.platformplayer.images.GlideHelper.Companion.crossfade
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.polycentric.PolycentricCache
import com.futo.platformplayer.views.buttons.BigButton
import com.futo.polycentric.core.toURLInfoSystemLinkUrl
import com.google.android.material.imageview.ShapeableImageView
import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.ShapeAppearanceModel
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import userpackage.Protocol.ImageManifest
class SupportView : LinearLayout {
private val _layoutStore: LinearLayout
private val _buttonPromotion: BigButton
private val _layoutMemberships: LinearLayout
private val _layoutMembershipEntries: LinearLayout
private val _layoutPromotions: LinearLayout
private val _layoutPromotionEntries: LinearLayout
private val _layoutDonation: LinearLayout
private val _layoutDonationEntries: LinearLayout
private val _buttonStore: BigButton
private val _imagePromotion: ShapeableImageView
private var _textNoSupportOptionsSet: TextView
private var _polycentricProfile: PolycentricProfile? = null
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
inflate(context, R.layout.view_support, this);
_layoutStore = findViewById(R.id.layout_store)
_buttonStore = findViewById(R.id.button_store)
_layoutMemberships = findViewById(R.id.layout_memberships)
_layoutMembershipEntries = findViewById(R.id.layout_membership_entries)
_layoutPromotions = findViewById(R.id.layout_promotions)
_layoutPromotionEntries = findViewById(R.id.layout_promotion_entries)
_layoutDonation = findViewById(R.id.layout_donation)
_layoutDonationEntries = findViewById(R.id.layout_donation_entries)
_buttonPromotion = findViewById(R.id.button_promotion)
_imagePromotion = findViewById(R.id.image_promotion)
_textNoSupportOptionsSet = findViewById(R.id.text_no_support_options_set)
_buttonPromotion.onClick.subscribe { openPromotion() }
_imagePromotion.setOnClickListener { openPromotion() }
_buttonStore.onClick.subscribe {
val storeUrl = _polycentricProfile?.systemState?.store ?: return@subscribe
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(storeUrl))
context.startActivity(browserIntent)
}
}
private fun openPromotion() {
val promotionUrl = _polycentricProfile?.systemState?.promotion ?: return
val uri = Uri.parse(promotionUrl)
if (!uri.isAbsolute && (uri.scheme == "https" || uri.scheme == "http")) {
return
}
val browserIntent = Intent(Intent.ACTION_VIEW, uri)
context.startActivity(browserIntent)
}
private fun setMemberships(urls: List<String>) {
_layoutMembershipEntries.removeAllViews()
for (url in urls) {
val button = createMembershipButton(url)
_layoutMembershipEntries.addView(button)
}
_layoutMemberships.visibility = if (urls.isEmpty()) View.GONE else View.VISIBLE
}
private fun createMembershipButton(url: String): BigButton {
val uri = Uri.parse(url)
val name: String
val iconDrawableId: Int
if (uri.host?.contains("patreon.com") == true) {
name = "Patreon"
iconDrawableId = R.drawable.patreon
} else {
name = uri.host.toString()
iconDrawableId = R.drawable.ic_web_white
}
return BigButton(context, name, "Become a member on $name", iconDrawableId) {
val intent = Intent(Intent.ACTION_VIEW);
intent.data = uri;
context.startActivity(intent);
}.apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
};
}
private fun setDonations(destinations: List<String>) {
_layoutDonationEntries.removeAllViews()
for (destination in destinations) {
val button = createDonationButton(destination)
_layoutDonationEntries.addView(button)
}
_layoutDonation.visibility = if (destinations.isEmpty()) View.GONE else View.VISIBLE
}
private enum class CryptoType {
BITCOIN, ETHEREUM, LITECOIN, RIPPLE, UNKNOWN
}
private fun getCryptoType(address: String): CryptoType {
val btcRegex = Regex("^(1|3)[1-9A-HJ-NP-Za-km-z]{25,34}$|^(bc1)[0-9a-zA-HJ-NP-Z]{39,59}$")
val ethRegex = Regex("^(0x)[0-9a-fA-F]{40}$")
val ltcRegex = Regex("^(L|M)[1-9A-HJ-NP-Za-km-z]{26,33}$|^(ltc1)[0-9a-zA-HJ-NP-Z]{39,59}$")
val xrpRegex = Regex("^r[1-9A-HJ-NP-Za-km-z]{24,34}$")
return when {
ltcRegex.matches(address) -> CryptoType.LITECOIN
btcRegex.matches(address) -> CryptoType.BITCOIN
ethRegex.matches(address) -> CryptoType.ETHEREUM
xrpRegex.matches(address) -> CryptoType.RIPPLE
else -> CryptoType.UNKNOWN
}
}
private fun createDonationButton(destination: String): BigButton {
val uri = Uri.parse(destination)
var action: (() -> Unit)? = null
val (name, iconDrawableId, cryptoType) = if (uri.scheme == "http" || uri.scheme == "https") {
val hostName = uri.host ?: ""
action = {
val intent = Intent(Intent.ACTION_VIEW);
intent.data = uri;
context.startActivity(intent);
}
if (hostName.contains("paypal.com")) {
Triple("Paypal", R.drawable.paypal, null) // Replace with your actual PayPal drawable resource
} else {
Triple(hostName, R.drawable.ic_web_white, null) // Replace with your generic web drawable resource
}
} else {
action = {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Donation Address", destination)
clipboard.setPrimaryClip(clip)
Toast.makeText(context, "Copied to clipboard", Toast.LENGTH_SHORT).show()
}
when (getCryptoType(destination)) {
CryptoType.BITCOIN -> Triple("Bitcoin", R.drawable.bitcoin, CryptoType.BITCOIN)
CryptoType.ETHEREUM -> Triple("Ethereum", R.drawable.ethereum, CryptoType.ETHEREUM)
CryptoType.LITECOIN -> Triple("Litecoin", R.drawable.litecoin, CryptoType.LITECOIN)
CryptoType.RIPPLE -> Triple("Ripple", R.drawable.ripple, CryptoType.RIPPLE)
CryptoType.UNKNOWN -> Triple("Unknown", R.drawable.ic_paid, CryptoType.UNKNOWN)
}
}
return BigButton(context, name, destination.takeIf { cryptoType != null } ?: "Donate on $name", iconDrawableId, action).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)
};
}
private fun setPromotions(url: String?, imageUrl: String?) {
Logger.i(TAG, "setPromotions($url, $imageUrl)")
if (url != null) {
_layoutPromotions.visibility = View.VISIBLE
if (imageUrl != null) {
_buttonPromotion.visibility = View.GONE
_imagePromotion.visibility = View.VISIBLE
Glide.with(_imagePromotion)
.load(imageUrl)
.crossfade()
.into(_imagePromotion)
} else {
_buttonPromotion.setSecondaryText(url)
_buttonPromotion.visibility = View.VISIBLE
_imagePromotion.visibility = View.GONE
}
} else {
_layoutPromotions.visibility = View.GONE
}
}
fun setPolycentricProfile(profile: PolycentricProfile?, animate: Boolean) {
if (_polycentricProfile == profile) {
return
}
if (profile != null) {
setDonations(profile.systemState.donationDestinations);
setMemberships(profile.systemState.membershipUrls);
val imageManifest = profile.systemState.promotionBanner?.imageManifestsList?.firstOrNull()
if (imageManifest != null) {
val imageUrl = imageManifest.toURLInfoSystemLinkUrl(profile.system.toProto(), imageManifest.process, profile.systemState.servers.toList());
setPromotions(profile.systemState.promotion, imageUrl);
} else {
setPromotions(null, null);
}
if (profile.systemState.store.isNotEmpty()) {
_layoutStore.visibility = View.VISIBLE
} else {
_layoutStore.visibility = View.GONE
}
_textNoSupportOptionsSet.visibility = View.GONE
} else {
setDonations(listOf());
setMemberships(listOf());
setPromotions(null, null);
_layoutStore.visibility = View.GONE
_textNoSupportOptionsSet.visibility = View.VISIBLE
}
_polycentricProfile = profile
}
companion object {
const val TAG = "SupportView";
}
}
@@ -0,0 +1,55 @@
package com.futo.platformplayer.views.adapters.viewholders
import android.content.Intent
import android.net.Uri
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import com.bumptech.glide.Glide
import com.futo.platformplayer.R
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.images.GlideHelper.Companion.crossfade
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.views.StoreItem
import com.futo.platformplayer.views.adapters.AnyAdapter
import com.google.android.material.imageview.ShapeableImageView
class StoreItemViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder<StoreItem>(
LayoutInflater.from(_viewGroup.context).inflate(R.layout.view_store_item, _viewGroup, false)) {
private val _image: ShapeableImageView;
private val _name: TextView;
private var _storeItem: StoreItem? = null;
init {
_image = _view.findViewById(R.id.image_item);
_name = _view.findViewById(R.id.text_item);
_view.findViewById<LinearLayout>(R.id.root).setOnClickListener {
val s = _storeItem ?: return@setOnClickListener;
try {
val uri = Uri.parse(s.url);
val intent = Intent(Intent.ACTION_VIEW);
intent.data = uri;
_view.context.startActivity(intent);
} catch (e: Throwable) {
Logger.e(TAG, "Failed to open URI: '${it}'.", e);
}
}
}
override fun bind(storeItem: StoreItem) {
Glide.with(_image)
.load(storeItem.image)
.crossfade()
.into(_image);
_name.text = storeItem.name;
_storeItem = storeItem;
}
companion object {
private const val TAG = "StoreItemViewHolder";
}
}
@@ -69,6 +69,10 @@ open class BigButton : LinearLayout {
_textSecondary.text = attrTextSecondary;
}
fun setSecondaryText(text: String?) {
_textSecondary.text = text
}
fun withPrimaryText(text: String): BigButton {
_textPrimary.text = text;
return this;
@@ -0,0 +1,33 @@
package com.futo.platformplayer.views.overlays
import android.content.Context
import android.util.AttributeSet
import android.widget.LinearLayout
import com.futo.platformplayer.R
import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile
import com.futo.platformplayer.views.SupportView
class SupportOverlay : LinearLayout {
val onClose = Event0();
private val _topbar: OverlayTopbar;
private val _support: SupportView;
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
inflate(context, R.layout.overlay_support, this)
_topbar = findViewById(R.id.topbar);
_support = findViewById(R.id.support);
_topbar.onClose.subscribe(this, onClose::emit);
}
fun setPolycentricProfile(profile: PolycentricProfile?, animate: Boolean) {
_support.setPolycentricProfile(profile, animate)
}
fun cleanup() {
_topbar.onClose.remove(this);
}
}
@@ -71,6 +71,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
private val _control_videosettings_fullscreen: ImageButton;
private val _control_minimize_fullscreen: ImageButton;
private val _control_rotate_lock_fullscreen: ImageButton;
private val _control_cast_fullscreen: ImageButton;
private val _control_play_fullscreen: ImageButton;
private val _time_bar_fullscreen: TimeBar;
private val _overlay_brightness: FrameLayout;
@@ -127,6 +128,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
_control_minimize_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_minimize);
_control_videosettings_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_settings);
_control_rotate_lock_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_rotate_lock);
_control_cast_fullscreen = _videoControls_fullscreen.findViewById(R.id.exo_cast);
_control_play_fullscreen = videoControls.findViewById(com.google.android.exoplayer2.ui.R.id.exo_play);
_control_chapter_fullscreen = _videoControls_fullscreen.findViewById(R.id.text_chapter_current);
_time_bar_fullscreen = _videoControls_fullscreen.findViewById(com.google.android.exoplayer2.ui.R.id.exo_progress);
@@ -213,7 +215,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
updateRotateLock();
};
_control_cast.setOnClickListener {
UIDialogs.showCastingDialog(context);
};
_control_minimize_fullscreen.setOnClickListener {
@@ -229,6 +231,9 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
StatePlayer.instance.rotationLock = !StatePlayer.instance.rotationLock;
updateRotateLock();
};
_control_cast_fullscreen.setOnClickListener {
UIDialogs.showCastingDialog(context);
};
var lastPos = 0L;
videoControls.setProgressUpdateListener { position, bufferedPosition ->
@@ -270,7 +275,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
if (drawable != null) {
_videoView.defaultArtwork = drawable;
_videoView.useArtwork = true;
fitHeight();
fitOrFill(isFullScreen);
} else {
_videoView.defaultArtwork = null;
_videoView.useArtwork = false;
@@ -311,7 +316,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
gestureControl.hideControls();
//videoControlsBar.visibility = View.GONE;
_videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;
fillHeight();
_videoControls_fullscreen.show();
videoControls.hide();
}
@@ -323,16 +328,25 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
gestureControl.hideControls();
//videoControlsBar.visibility = View.VISIBLE;
_videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
fitHeight();
videoControls.show();
_videoControls_fullscreen.hide();
}
fitOrFill(fullScreen);
gestureControl.setFullscreen(fullScreen);
onToggleFullScreen.emit(fullScreen);
isFullScreen = fullScreen;
}
private fun fitOrFill(fullScreen: Boolean) {
if (fullScreen) {
fillHeight();
} else {
fitHeight();
}
}
fun lockControlsAlpha(locked : Boolean) {
if(locked && _isControlsLocked != locked) {
_isControlsLocked = locked;
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#232323" />
<corners android:radius="5dp" />
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#3A1448" />
<corners android:radius="14dp" />
<corners android:radius="5dp" />
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#144826" />
<corners android:radius="14dp" />
<corners android:radius="5dp" />
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>
+9
View File
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="273.6dp"
android:height="360dp"
android:viewportWidth="273.6"
android:viewportHeight="360">
<path
android:pathData="M217.02,167.04c18.63,-9.48 30.29,-26.18 27.57,-54.01c-3.67,-38.02 -36.53,-50.77 -78.01,-54.4l-0.01,-52.74h-32.14l-0.01,51.35c-8.46,0 -17.08,0.17 -25.66,0.34L108.76,5.9l-32.11,-0l-0.01,52.73c-6.96,0.14 -13.79,0.28 -20.47,0.28v-0.16l-44.33,-0.02l0.01,34.28c0,0 23.73,-0.45 23.34,-0.01c13.01,0.01 17.26,7.56 18.48,14.08l0.01,60.08v84.4c-0.57,4.09 -2.98,10.63 -12.08,10.64c0.41,0.36 -23.38,-0 -23.38,-0l-6.38,38.33h41.82c7.79,0.01 15.45,0.13 22.96,0.19l0.03,53.34l32.1,0.01l-0.01,-52.78c8.83,0.18 17.36,0.26 25.68,0.25l-0.01,52.53h32.14l0.02,-53.25c54.02,-3.1 91.84,-16.7 96.54,-67.39C266.92,192.61 247.69,174.4 217.02,167.04zM109.54,95.32c18.13,0 75.13,-5.77 75.14,32.06c-0.01,36.27 -57,32.03 -75.14,32.03V95.32zM109.52,262.45l0.01,-70.67c21.78,-0.01 90.08,-6.26 90.09,35.32C199.64,266.97 131.31,262.43 109.52,262.45z"
android:fillColor="#ffffff"/>
</vector>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size android:height="0dp"
android:width="4dp"/>
</shape>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size android:height="0dp"
android:width="8dp"/>
</shape>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size android:height="20dp"
android:width="0dp"/>
</shape>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size android:height="8dp"
android:width="0dp"/>
</shape>
+9
View File
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="32"
android:viewportHeight="32">
<path
android:pathData="M15.927,23.959l-9.823,-5.797 9.817,13.839 9.828,-13.839 -9.828,5.797zM16.073,0l-9.819,16.297 9.819,5.807 9.823,-5.801z"
android:fillColor="#ffffff"/>
</vector>
+1 -2
View File
@@ -2,8 +2,7 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M480,760Q546,760 593,713Q640,666 640,600L640,440Q640,374 593,327Q546,280 480,280Q414,280 367,327Q320,374 320,440L320,600Q320,666 367,713Q414,760 480,760ZM400,640L560,640L560,560L400,560L400,640ZM400,480L560,480L560,400L400,400L400,480ZM480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520ZM480,840Q415,840 359.5,808Q304,776 272,720L160,720L160,640L244,640Q241,620 240.5,600Q240,580 240,560L160,560L160,480L240,480Q240,460 240.5,440Q241,420 244,400L160,400L160,320L272,320Q286,297 303.5,277Q321,257 344,242L280,176L336,120L422,206Q450,197 479,197Q508,197 536,206L624,120L680,176L614,242Q637,257 655.5,276.5Q674,296 688,320L800,320L800,400L716,400Q719,420 719.5,440Q720,460 720,480L800,480L800,560L720,560Q720,580 719.5,600Q719,620 716,640L800,640L800,720L688,720Q656,776 600.5,808Q545,840 480,840Z"/>
+1 -2
View File
@@ -2,8 +2,7 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M380,620L660,440L380,260L380,620ZM320,840L320,760L160,760Q127,760 103.5,736.5Q80,713 80,680L80,200Q80,167 103.5,143.5Q127,120 160,120L800,120Q833,120 856.5,143.5Q880,167 880,200L880,680Q880,713 856.5,736.5Q833,760 800,760L640,760L640,840L320,840ZM160,680L800,680Q800,680 800,680Q800,680 800,680L800,200Q800,200 800,200Q800,200 800,200L160,200Q160,200 160,200Q160,200 160,200L160,680Q160,680 160,680Q160,680 160,680ZM160,680Q160,680 160,680Q160,680 160,680L160,200Q160,200 160,200Q160,200 160,200L160,200Q160,200 160,200Q160,200 160,200L160,680Q160,680 160,680Q160,680 160,680L160,680Z"/>
@@ -2,8 +2,7 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M160,760L160,680L240,680L240,400Q240,317 290,252.5Q340,188 420,168L420,140Q420,115 437.5,97.5Q455,80 480,80Q505,80 522.5,97.5Q540,115 540,140L540,168Q620,188 670,252.5Q720,317 720,400L720,680L800,680L800,760L160,760ZM480,460L480,460L480,460L480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460Q480,460 480,460ZM480,880Q447,880 423.5,856.5Q400,833 400,800L560,800Q560,833 536.5,856.5Q513,880 480,880ZM320,680L640,680L640,400Q640,334 593,287Q546,240 480,240Q414,240 367,287Q320,334 320,400L320,680Z"/>
+9
View File
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M293,796.92L342.61,584.39L177.69,441.54L394.92,422.69L480,222.31L565.08,422.69L782.31,441.54L617.39,584.39L667,796.92L480,684.08L293,796.92Z"/>
</vector>
+9
View File
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="82.6dp"
android:height="82.6dp"
android:viewportWidth="82.6"
android:viewportHeight="82.6">
<path
android:pathData="M41.3,0A41.3,41.3 0,1 0,82.6 41.3h0A41.18,41.18 0,0 0,41.54 0ZM42,42.7 L37.7,57.2h23a1.16,1.16 0,0 1,1.2 1.12v0.38l-2,6.9a1.49,1.49 0,0 1,-1.5 1.1H23.2l5.9,-20.1 -6.6,2L24,44l6.6,-2 8.3,-28.2a1.51,1.51 0,0 1,1.5 -1.1h8.9a1.16,1.16 0,0 1,1.2 1.12v0.38L43.5,38l6.6,-2 -1.4,4.8Z"
android:fillColor="#ffffff"/>
</vector>

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

+12
View File
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="180dp"
android:height="180dp"
android:viewportWidth="180"
android:viewportHeight="180">
<path
android:pathData="M108.81,26.07c-26.47,0 -48,21.53 -48,48 0,26.39 21.53,47.85 48,47.85 26.39,0 47.85,-21.47 47.85,-47.85 0,-26.47 -21.47,-48 -47.85,-48"
android:fillColor="#ffffff"/>
<path
android:pathData="M23.33,153.93V26.07h23.47v127.87z"
android:fillColor="#ffffff"/>
</vector>
+15
View File
@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="32"
android:viewportHeight="32">
<path
android:fillColor="#FF000000"
android:pathData="M12,26H7c-0.8,0 -1.6,-0.4 -2.1,-1c-0.5,-0.6 -0.7,-1.4 -0.5,-2.2L8.7,4l0,0c0.3,-1.2 1.3,-2 2.6,-2h8.6c2.3,0 4.4,1 5.9,2.8c1.4,1.8 2,4.1 1.5,6.4c-0.8,4 -4.4,6.8 -8.5,6.8h-3.2c-0.5,0 -1,0.4 -1.1,0.9L13,25.2C12.9,25.7 12.5,26 12,26z"/>
<path
android:pathData="M12,26H7c-0.8,0 -1.6,-0.4 -2.1,-1c-0.5,-0.6 -0.7,-1.4 -0.5,-2.2L8.7,4l0,0c0.3,-1.2 1.3,-2 2.6,-2h8.6c2.3,0 4.4,1 5.9,2.8c1.4,1.8 2,4.1 1.5,6.4c-0.8,4 -4.4,6.8 -8.5,6.8h-3.2c-0.5,0 -1,0.4 -1.1,0.9L13,25.2C12.9,25.7 12.5,26 12,26z"
android:fillColor="#ffffff"/>
<path
android:pathData="M29.3,11.3c0,0.1 0,0.2 0,0.3c-1,4.9 -5.4,8.4 -10.4,8.4h-2.5l-1.4,5.7C14.6,27.1 13.4,28 12,28h-2c0.1,0.4 0.2,0.7 0.5,1c0.5,0.6 1.2,0.9 2,0.9H17c0.5,0 0.9,-0.3 1,-0.7l1.4,-5.5c0.1,-0.4 0.5,-0.6 0.9,-0.6h2.9c3.7,0 7,-2.5 7.7,-6C31.3,15 30.7,12.8 29.3,11.3z"
android:fillColor="#ffffff"/>
</vector>
+9
View File
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="32"
android:viewportHeight="32">
<path
android:pathData="M27.401,19.531c-1.131,-0.645 -2.407,-0.837 -3.672,-0.885 -1.052,-0.031 -2.631,-0.724 -2.631,-2.645 0,-1.432 1.156,-2.588 2.647,-2.645 1.265,-0.048 2.541,-0.24 3.671,-0.891 3.193,-1.844 4.292,-5.928 2.448,-9.125 -1.859,-3.199 -5.952,-4.287 -9.156,-2.437 -2.072,1.187 -3.348,3.401 -3.339,5.787 0,1.296 0.464,2.484 1.052,3.599 0.496,0.927 0.735,2.661 -0.948,3.635 -1.265,0.724 -2.843,0.272 -3.624,-0.989 -0.661,-1.068 -1.459,-2.063 -2.589,-2.708 -3.197,-1.849 -7.291,-0.751 -9.124,2.437 -1.839,3.199 -0.745,7.281 2.452,9.125 2.068,1.187 4.609,1.187 6.677,0 1.125,-0.647 1.923,-1.641 2.584,-2.708 0.541,-0.871 1.911,-1.985 3.624,-0.991 1.267,0.719 1.657,2.319 0.948,3.641 -0.583,1.093 -1.052,2.297 -1.052,3.593 0,3.688 2.991,6.672 6.677,6.677 3.688,0 6.672,-2.989 6.677,-6.677 0.011,-2.385 -1.255,-4.599 -3.323,-5.792z"
android:fillColor="#ffffff"/>
</vector>
@@ -1,92 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout 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:orientation="vertical"
android:layout_margin="18dp">
android:layout_height="match_parent">
<com.futo.platformplayer.views.buttons.BigButton
android:id="@+id/button_store"
<com.futo.platformplayer.views.SupportView
android:id="@+id/support"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:buttonIcon="@drawable/ic_store"
app:buttonText="@string/store"
app:buttonSubText="@string/visit_my_store" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="20dp"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_regular"
android:text="@string/memberships" />
<com.google.android.flexbox.FlexboxLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:flexWrap="wrap"
android:layout_marginTop="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14dp"
android:textColor="#909090"
android:fontFamily="@font/inter_light"
android:text="@string/a_monthly_recurring_payment_with_often" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14dp"
android:textColor="#909090"
android:fontFamily="@font/inter_bold"
android:text="@string/additional_perks" />
</com.google.android.flexbox.FlexboxLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp">
<com.futo.platformplayer.views.buttons.BigButton
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_regular"
android:text="@string/donation"
android:layout_marginTop="20dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14dp"
android:textColor="#909090"
android:fontFamily="@font/inter_light"
android:text="@string/a_one_time_payment_to_support_the_creator" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp">
<com.futo.platformplayer.views.buttons.BigButton
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
android:layout_height="match_parent" />
</FrameLayout>
@@ -445,65 +445,10 @@
android:text="@string/click_to_read_more"/>
</LinearLayout>
<LinearLayout
android:id="@+id/layout_monetization"
<com.futo.platformplayer.views.MonetizationView
android:id="@+id/monetization"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:layout_marginEnd="14dp"
android:layout_marginTop="14dp">
<LinearLayout
android:id="@+id/button_support"
android:layout_width="0dp"
android:layout_height="30dp"
android:layout_weight="1"
android:gravity="center"
android:background="@drawable/background_support">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@drawable/ic_paid" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@color/white"
android:fontFamily="@font/inter_light"
android:text="@string/support"
android:textSize="14dp"
android:includeFontPadding="false"
android:layout_marginStart="6dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/button_store"
android:layout_width="0dp"
android:layout_height="30dp"
android:layout_weight="1"
android:gravity="center"
android:layout_marginStart="8dp"
android:background="@drawable/background_store">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@drawable/ic_store" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@color/white"
android:fontFamily="@font/inter_light"
tools:text="Store"
android:textSize="14dp"
android:includeFontPadding="false"
android:layout_marginStart="6dp" />
</LinearLayout>
</LinearLayout>
android:layout_height="wrap_content" />
<com.futo.platformplayer.views.videometa.UpNextView
android:id="@+id/up_next"
@@ -602,6 +547,12 @@
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.futo.platformplayer.views.overlays.SupportOverlay
android:id="@+id/videodetail_container_support"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<FrameLayout
android:id="@+id/videodetail_loading_overlay"
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.futo.platformplayer.views.overlays.OverlayTopbar
android:id="@+id/topbar"
android:layout_width="match_parent"
android:layout_height="40dp"
app:title="@string/support"
app:metadata=""
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<com.futo.platformplayer.views.SupportView
android:id="@+id/support"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/topbar"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -57,6 +57,14 @@
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent">
<ImageButton
android:id="@+id/exo_cast"
android:layout_width="50dp"
android:layout_height="50dp"
android:scaleType="fitCenter"
android:clickable="true"
android:padding="12dp"
app:srcCompat="@drawable/ic_cast" />
<ImageButton
android:id="@+id/exo_rotate_lock"
android:layout_width="50dp"
@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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:layout_gravity="bottom"
android:orientation="vertical"
android:id="@+id/root">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:layout_marginEnd="14dp"
android:layout_marginTop="14dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:showDividers="middle"
android:divider="@drawable/divider_transparent_4dp">
<LinearLayout
android:id="@+id/button_membership"
android:layout_width="0dp"
android:layout_height="30dp"
android:layout_weight="1"
android:gravity="center"
android:background="@drawable/background_membership">
<com.futo.platformplayer.views.platform.PlatformIndicator
android:id="@+id/platform_indicator"
android:layout_width="18dp"
android:layout_height="18dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@color/white"
android:fontFamily="@font/inter_light"
android:text="@string/membership"
android:textSize="14dp"
android:includeFontPadding="false"
android:layout_marginStart="6dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/button_support"
android:layout_width="0dp"
android:layout_height="30dp"
android:layout_weight="1"
android:gravity="center"
android:background="@drawable/background_support">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@drawable/ic_paid" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@color/white"
android:fontFamily="@font/inter_light"
android:text="@string/support"
android:textSize="14dp"
android:includeFontPadding="false"
android:layout_marginStart="6dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/button_store"
android:layout_width="0dp"
android:layout_height="30dp"
android:layout_weight="1"
android:gravity="center"
android:background="@drawable/background_store">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@drawable/ic_store" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@color/white"
android:fontFamily="@font/inter_light"
android:text="@string/store"
android:textSize="14dp"
android:includeFontPadding="false"
android:layout_marginStart="6dp" />
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/text_merchandise"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:layout_marginTop="8dp"
android:fontFamily="@font/inter_medium"
android:textColor="@color/white"
android:textSize="17dp"
android:text="@string/merchandise" />
<FrameLayout
android:id="@+id/layout_merchandise"
android:layout_width="match_parent"
android:layout_height="140dp"
android:layout_marginTop="8dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_merchandise"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_gravity="center" />
<com.futo.platformplayer.views.Loader
android:id="@+id/loader_merchandise"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_gravity="center"/>
</FrameLayout>
</LinearLayout>
@@ -0,0 +1,32 @@
<LinearLayout 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="100dp"
android:layout_height="match_parent"
android:orientation="vertical"
android:clickable="true"
android:id="@+id/root"
android:gravity="center_vertical">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image_item"
android:layout_width="100dp"
android:layout_height="100dp"
android:scaleType="centerCrop"
app:shapeAppearanceOverlay="@style/roundedCorners_10dp"
android:background="#E9E9E9"/>
<TextView
android:id="@+id/text_item"
android:layout_width="wrap_content"
android:layout_height="30dp"
tools:text="BEAST ORIGINALS HOODIE - BLACK"
android:fontFamily="@font/inter_regular"
android:textColor="#888888"
android:textSize="10dp"
android:maxLines="2"
android:layout_marginTop="8dp"
android:ellipsize="end"
android:gravity="center_horizontal"
android:layout_gravity="center_horizontal"/>
</LinearLayout>
+191
View File
@@ -0,0 +1,191 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView 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="match_parent">
<LinearLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_margin="18dp"
android:showDividers="middle"
android:divider="@drawable/divider_transparent_vertical_20dp">
<TextView
android:id="@+id/text_no_support_options_set"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="13dp"
android:textColor="#909090"
android:fontFamily="@font/inter_light"
android:gravity="center_horizontal"
android:text="@string/this_creator_has_not_set_any_support_options_on_harbor_polycentric" />
<LinearLayout
android:id="@+id/layout_store"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_regular"
android:text="@string/store"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="13dp"
android:textColor="#909090"
android:fontFamily="@font/inter_light"
android:text="@string/a_store_by_the_creator" />
<com.futo.platformplayer.views.buttons.BigButton
android:id="@+id/button_store"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:buttonIcon="@drawable/ic_store"
app:buttonText="@string/store"
app:buttonSubText="@string/visit_my_store"
android:layout_marginTop="8dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/layout_memberships"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_regular"
android:text="@string/memberships" />
<com.google.android.flexbox.FlexboxLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:flexWrap="wrap"
android:layout_marginTop="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="13dp"
android:textColor="#909090"
android:fontFamily="@font/inter_light"
android:text="@string/a_monthly_recurring_payment_with_often" />
<Space android:layout_width="4dp"
android:layout_height="match_parent"></Space>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="13dp"
android:textColor="#909090"
android:fontFamily="@font/inter_bold"
android:text="@string/additional_perks" />
</com.google.android.flexbox.FlexboxLayout>
<LinearLayout
android:id="@+id/layout_membership_entries"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="vertical"
android:showDividers="middle"
android:divider="@drawable/divider_transparent_vertical_8dp">
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/layout_promotions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_regular"
android:text="@string/promotions" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="13dp"
android:textColor="#909090"
android:fontFamily="@font/inter_light"
android:text="@string/current_promotions_by_this_creator" />
<LinearLayout
android:id="@+id/layout_promotion_entries"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="vertical"
android:gravity="center_horizontal">
<com.futo.platformplayer.views.buttons.BigButton
android:id="@+id/button_promotion"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:buttonIcon="@drawable/ic_star"
app:buttonText="Promotion"
app:buttonSubText="URL"
android:layout_marginTop="8dp" />
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image_promotion"
android:layout_width="300dp"
android:layout_height="100dp"
android:scaleType="centerCrop"
android:contentDescription="@string/thumbnail"
app:shapeAppearanceOverlay="@style/roundedCorners_10dp"
app:srcCompat="@drawable/placeholder_video_thumbnail" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/layout_donation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_regular"
android:text="@string/donation" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="13dp"
android:textColor="#909090"
android:fontFamily="@font/inter_light"
android:text="@string/a_one_time_payment_to_support_the_creator" />
<LinearLayout
android:id="@+id/layout_donation_entries"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="vertical"
android:showDividers="middle"
android:divider="@drawable/divider_transparent_vertical_8dp">
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
+4
View File
@@ -682,6 +682,10 @@
<string-array name="subscriptions_sortby_array">
<item>الاسم تصاعدياً</item>
<item>الاسم تنازلياً</item>
<item>المشاهدات تصاعدياً</item>
<item>المشاهدات تنازلياً</item>
<item>زمن المشاهدة تصاعدياً</item>
<item>زمن المشاهدة تنازلياً</item>
</string-array>
<string-array name="feed_style">
<item>معاينة</item>
+4
View File
@@ -682,6 +682,10 @@
<string-array name="subscriptions_sortby_array">
<item>Name aufsteigend</item>
<item>Name absteigend</item>
<item>Aufrufe aufsteigend</item>
<item>Aufrufe absteigend</item>
<item>Wiedergabezeit aufsteigend</item>
<item>Wiedergabezeit absteigend</item>
</string-array>
<string-array name="feed_style">
<item>Vorschau</item>
+4
View File
@@ -698,6 +698,10 @@
<string-array name="subscriptions_sortby_array">
<item>Nombre Ascendente</item>
<item>Nombre Descendente</item>
<item>Visitas Ascendente</item>
<item>Visitas Descendente</item>
<item>Tiempo de Visualización Ascendente</item>
<item>Tiempo de Visualización Descendente</item>
</string-array>
<string-array name="feed_style">
<item>Vista Previa</item>
+6 -2
View File
@@ -680,8 +680,12 @@
<item>Activé</item>
</string-array>
<string-array name="subscriptions_sortby_array">
<item>Nom croissant</item>
<item>Nom décroissant</item>
<item>Nom Ascendant</item>
<item>Nom Descendant</item>
<item>Vues Ascendantes</item>
<item>Vues Descendantes</item>
<item>Temps de visionnage Ascendant</item>
<item>Temps de visionnage Descendant</item>
</string-array>
<string-array name="feed_style">
<item>Aperçu</item>
+6 -2
View File
@@ -680,8 +680,12 @@
<item>有効</item>
</string-array>
<string-array name="subscriptions_sortby_array">
<item>名前昇順</item>
<item>名前降順</item>
<item>名前昇順</item>
<item>名前降順</item>
<item>視聴回数の昇順</item>
<item>視聴回数の降順</item>
<item>視聴時間の昇順</item>
<item>視聴時間の降順</item>
</string-array>
<string-array name="feed_style">
<item>プレビュー</item>
+4
View File
@@ -682,6 +682,10 @@
<string-array name="subscriptions_sortby_array">
<item>이름 오름차순</item>
<item>이름 내림차순</item>
<item>조회수 오름차순</item>
<item>조회수 내림차순</item>
<item>시청시간 오름차순</item>
<item>시청시간 내림차순</item>
</string-array>
<string-array name="feed_style">
<item>미리보기</item>
+4
View File
@@ -682,6 +682,10 @@
<string-array name="subscriptions_sortby_array">
<item>Nome Ascendente</item>
<item>Nome Descendente</item>
<item>Visualizações Ascendente</item>
<item>Visualizações Descendente</item>
<item>Tempo de Assistência Ascendente</item>
<item>Tempo de Assistência Descendente</item>
</string-array>
<string-array name="feed_style">
<item>Pré-visualização</item>
+6 -2
View File
@@ -680,8 +680,12 @@
<item>Включено</item>
</string-array>
<string-array name="subscriptions_sortby_array">
<item>Имя по возрастанию</item>
<item>Имя по убыванию</item>
<item>По имени по возрастанию</item>
<item>По имени по убыванию</item>
<item>По количеству просмотров по возрастанию</item>
<item>По количеству просмотров по убыванию</item>
<item>По времени просмотра по возрастанию</item>
<item>По времени просмотра по убыванию</item>
</string-array>
<string-array name="feed_style">
<item>Предпросмотр</item>
+4
View File
@@ -682,6 +682,10 @@
<string-array name="subscriptions_sortby_array">
<item>名称升序</item>
<item>名称降序</item>
<item>观看次数升序</item>
<item>观看次数降序</item>
<item>观看时间升序</item>
<item>观看时间降序</item>
</string-array>
<string-array name="feed_style">
<item>预览</item>
+7 -1
View File
@@ -79,6 +79,7 @@
<string name="developer">Developer</string>
<string name="remove_historical_suggestion">Remove historical suggestion</string>
<string name="comments">Comments</string>
<string name="merchandise">Merchandise</string>
<string name="reached_the_end_of_the_playlist">Reached the end of the playlist</string>
<string name="the_playlist_will_restart_after_the_video_is_finished">The playlist will restart after the video is finished</string>
<string name="restart_now">Restart Now</string>
@@ -192,15 +193,19 @@
<string name="i_already_paid">I Already Paid</string>
<string name="memberships">Memberships</string>
<string name="a_monthly_recurring_payment_with_often">A monthly recurring payment with often</string>
<string name="additional_perks">additional perks.</string>
<string name="additional_perks">additional perks</string>
<string name="a_one_time_payment_to_support_the_creator">A one-time payment to support the creator</string>
<string name="a_store_by_the_creator">A store by the creator</string>
<string name="donation">Donation</string>
<string name="promotions">Promotions</string>
<string name="current_promotions_by_this_creator">Current promotions by this creator</string>
<string name="downloading">Downloading</string>
<string name="videos">Videos</string>
<string name="clear_history">Clear history</string>
<string name="nothing_to_import">Nothing to import</string>
<string name="enabling_lots_of_sources_can_reduce_the_loading_speed_of_your_application">Enabling lots of sources can reduce the loading speed of your application.</string>
<string name="support">Support</string>
<string name="membership">Membership</string>
<string name="store">Store</string>
<string name="live_chat">Live Chat</string>
<string name="remove">Remove</string>
@@ -631,6 +636,7 @@
<string name="select_your_pins_in_order">Select your pins in order</string>
<string name="more_options">More Options</string>
<string name="save">Save</string>
<string name="this_creator_has_not_set_any_support_options_on_harbor_polycentric">This creator has not set any support options on Harbor (Polycentric)</string>
<string-array name="home_screen_array">
<item>Recommendations</item>
<item>Subscriptions</item>
+4
View File
@@ -11,6 +11,10 @@
<item name="cornerFamily">rounded</item>
<item name="cornerSize">4dp</item>
</style>
<style name="roundedCorners_10dp" parent="">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">10dp</item>
</style>
<style name="roundedCorners_16dp" parent="">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">16dp</item>