Merge branch 'monetization' into 'master'

Monetization

See merge request videostreaming/grayjay!1
This commit is contained in:
Koen 2023-11-07 12:10:40 +00:00
commit 8f7b4b8257
37 changed files with 1045 additions and 205 deletions

View file

@ -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
}
}
}

View file

@ -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 {

View file

@ -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);
@ -2096,12 +2099,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?) {

View file

@ -1,5 +1,7 @@
package com.futo.platformplayer.images;
import android.util.Log;
import androidx.annotation.NonNull;
import com.bumptech.glide.Priority;

View file

@ -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) };

View file

@ -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";
}
}

View file

@ -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";
}
}

View file

@ -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";
}
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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>

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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

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>

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"/>

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"/>

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="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"/>

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>

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>

View file

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

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>

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>

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>

View file

@ -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>

View file

@ -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"

View file

@ -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>

View file

@ -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>

View file

@ -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>

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>

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>
@ -625,6 +630,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>

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>

@ -1 +1 @@
Subproject commit d02bf3d8e38b3dda1672866b894aea6e35643b20
Subproject commit 3fe81ffb3c730f0dc9f645149dc20c31284cd790