add voting system

This commit is contained in:
Cloudburst 2022-09-05 18:45:13 +02:00
parent ae137f4a34
commit a3eef399a9
11 changed files with 560 additions and 2 deletions

View file

@ -0,0 +1,86 @@
package com.lagradost.cloudstream3.plugins
import android.util.Log
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import java.security.MessageDigest
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
object VotingApi { // please do not cheat the votes lol
private const val LOGKEY = "VotingApi"
enum class VoteType(val value: Int) {
UPVOTE(1),
DOWNVOTE(-1),
NONE(0)
}
private val apiDomain = "https://api.countapi.xyz"
private fun transformUrl(url: String): String = // dont touch or all votes get reset
MessageDigest
.getInstance("SHA-1")
.digest("${url}#funny-salt".toByteArray())
.fold("") { str, it -> str + "%02x".format(it) }
suspend fun SitePlugin.getVotes(): Int {
if (repositoryUrl == null) return 0
return getVotes(repositoryUrl, url)
}
suspend fun SitePlugin.vote(requestType: VoteType): Int {
if (repositoryUrl == null) return 0
return vote(repositoryUrl, url, requestType)
}
fun SitePlugin.getVoteType(): VoteType {
if (repositoryUrl == null) return VoteType.NONE
return getVoteType(repositoryUrl, url)
}
suspend fun getVotes(repositoryUrl: String, pluginUrl: String): Int {
val url = "${apiDomain}/get/cs3-votes-${transformUrl(repositoryUrl)}/${transformUrl(pluginUrl)}"
Log.d(LOGKEY, "Requesting: $url")
return app.get(url).parsedSafe<Result>()?.value ?: (0.also {
ioSafe {
createBucket(repositoryUrl, pluginUrl)
}
})
}
fun getVoteType(repositoryUrl: String, pluginUrl: String): VoteType {
return getKey("cs3-votes-${transformUrl(repositoryUrl)}/${transformUrl(pluginUrl)}") ?: VoteType.NONE
}
private suspend fun createBucket(repositoryUrl: String, pluginUrl: String) {
val url = "${apiDomain}/create?namespace=cs3-votes-${transformUrl(repositoryUrl)}&key=${transformUrl(pluginUrl)}&value=0&update_lowerbound=-2&update_upperbound=2&enable_reset=0"
Log.d(LOGKEY, "Requesting: $url")
app.get(url)
}
suspend fun vote(repositoryUrl: String, pluginUrl: String, requestType: VoteType): Int {
val savedType: VoteType = getKey("cs3-votes-${transformUrl(repositoryUrl)}/${transformUrl(pluginUrl)}") ?: VoteType.NONE
var newType: VoteType = requestType
var changeValue = 0
if (requestType == savedType) {
newType = VoteType.NONE
changeValue = -requestType.value
} else if (savedType == VoteType.NONE) {
changeValue = requestType.value
} else if (savedType != requestType) {
changeValue = -savedType.value + requestType.value
}
val url = "${apiDomain}/update/cs3-votes-${transformUrl(repositoryUrl)}/${transformUrl(pluginUrl)}?amount=${changeValue}"
Log.d(LOGKEY, "Requesting: $url")
val res = app.get(url).parsedSafe<Result>()?.value
if (res != null) {
setKey("cs3-votes-${transformUrl(repositoryUrl)}/${transformUrl(pluginUrl)}", newType)
}
return res ?: 0
}
private data class Result(
val value: Int?
)
}

View file

@ -4,10 +4,8 @@ import android.annotation.SuppressLint
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.annotation.LayoutRes
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.ContentLoadingProgressBar import androidx.core.widget.ContentLoadingProgressBar

View file

@ -5,17 +5,22 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity
import com.lagradost.cloudstream3.PROVIDER_STATUS_DOWN import com.lagradost.cloudstream3.PROVIDER_STATUS_DOWN
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.plugins.VotingApi.getVotes
import com.lagradost.cloudstream3.ui.result.setText import com.lagradost.cloudstream3.ui.result.setText
import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.GlideApp import com.lagradost.cloudstream3.utils.GlideApp
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.setImage
@ -23,6 +28,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.toPx
import kotlinx.android.synthetic.main.repository_item.view.* import kotlinx.android.synthetic.main.repository_item.view.*
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
import java.text.DecimalFormat
data class PluginViewData( data class PluginViewData(
@ -101,6 +107,23 @@ class PluginAdapter(
private val iconSize by lazy { private val iconSize by lazy {
findClosestBase2(iconSizeExact, 16, 512) findClosestBase2(iconSizeExact, 16, 512)
} }
fun prettyCount(number: Number): String? {
val suffix = charArrayOf(' ', 'k', 'M', 'B', 'T', 'P', 'E')
val numValue = number.toLong()
val value = Math.floor(Math.log10(numValue.toDouble())).toInt()
val base = value / 3
return if (value >= 3 && base < suffix.size) {
DecimalFormat("#0.00").format(
numValue / Math.pow(
10.0,
(base * 3).toDouble()
)
) + suffix[base]
} else {
DecimalFormat().format(numValue)
}
}
} }
inner class PluginViewHolder(itemView: View) : inner class PluginViewHolder(itemView: View) :
@ -112,6 +135,7 @@ class PluginAdapter(
val metadata = data.plugin.second val metadata = data.plugin.second
val disabled = metadata.status == PROVIDER_STATUS_DOWN val disabled = metadata.status == PROVIDER_STATUS_DOWN
val alpha = if (disabled) 0.6f else 1f val alpha = if (disabled) 0.6f else 1f
val isLocal = data.plugin.second.repositoryUrl == null
itemView.main_text?.alpha = alpha itemView.main_text?.alpha = alpha
itemView.sub_text?.alpha = alpha itemView.sub_text?.alpha = alpha
@ -125,6 +149,13 @@ class PluginAdapter(
itemView.action_button?.setOnClickListener { itemView.action_button?.setOnClickListener {
iconClickCallback.invoke(data.plugin) iconClickCallback.invoke(data.plugin)
} }
itemView.setOnClickListener {
if (isLocal) return@setOnClickListener
val sheet = PluginDetailsFragment(data)
val activity = itemView.context.getActivity() as AppCompatActivity
sheet.show(activity.supportFragmentManager, "PluginDetails")
}
//if (itemView.context?.isTrueTvSettings() == false) { //if (itemView.context?.isTrueTvSettings() == false) {
// val siteUrl = metadata.repositoryUrl // val siteUrl = metadata.repositoryUrl
// if (siteUrl != null && siteUrl.isNotBlank() && siteUrl != "NONE") { // if (siteUrl != null && siteUrl.isNotBlank() && siteUrl != "NONE") {
@ -185,6 +216,11 @@ class PluginAdapter(
itemView.lang_icon.text = fromTwoLettersToLanguage(metadata.language) itemView.lang_icon.text = fromTwoLettersToLanguage(metadata.language)
} }
ioSafe {
metadata.getVotes().main {
itemView.ext_votes?.setText(txt(R.string.votes_format, prettyCount(it)))
}
}
if (metadata.fileSize != null) { if (metadata.fileSize != null) {
itemView.ext_filesize?.isVisible = true itemView.ext_filesize?.isVisible = true

View file

@ -0,0 +1,112 @@
package com.lagradost.cloudstream3.ui.settings.extensions
import android.content.res.ColorStateList
import android.os.Bundle
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.UIHelper.toPx
import kotlinx.android.synthetic.main.fragment_plugin_details.*
import android.text.format.Formatter.formatFileSize
import com.lagradost.cloudstream3.plugins.VotingApi
import com.lagradost.cloudstream3.plugins.VotingApi.getVoteType
import com.lagradost.cloudstream3.plugins.VotingApi.getVotes
import com.lagradost.cloudstream3.plugins.VotingApi.vote
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
class PluginDetailsFragment(val data: PluginViewData) : BottomSheetDialogFragment() {
companion object {
private tailrec fun findClosestBase2(target: Int, current: Int = 16, max: Int = 512): Int {
if (current >= max) return max
if (current >= target) return current
return findClosestBase2(target, current * 2, max)
}
private val iconSizeExact = 50.toPx
private val iconSize by lazy {
findClosestBase2(iconSizeExact, 16, 512)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_plugin_details, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val metadata = data.plugin.second
if (plugin_icon?.setImage(//plugin_icon?.height ?:
metadata.iconUrl?.replace(
"%size%",
"$iconSize"
)?.replace(
"%exact_size%",
"$iconSizeExact"
),
null,
errorImageDrawable = R.drawable.ic_baseline_extension_24
) != true
) {
plugin_icon?.setImageResource(R.drawable.ic_baseline_extension_24)
}
plugin_name?.text = metadata.name
plugin_version?.text = metadata.version.toString()
plugin_description?.text = metadata.description ?: getString(R.string.no_data)
plugin_size?.text = if (metadata.fileSize == null) getString(R.string.no_data) else formatFileSize(context, metadata.fileSize)
plugin_author?.text = if (metadata.authors.isEmpty()) getString(R.string.no_data) else metadata.authors.joinToString(", ")
plugin_status?.text = resources.getStringArray(R.array.extension_statuses)[metadata.status]
plugin_types?.text = if ((metadata.tvTypes == null) || metadata.tvTypes.isEmpty()) getString(R.string.no_data) else metadata.tvTypes.joinToString(", ")
upvote.setOnClickListener {
ioSafe {
metadata.vote(VotingApi.VoteType.UPVOTE).main {
updateVoting(it)
}
}
}
downvote.setOnClickListener {
ioSafe {
metadata.vote(VotingApi.VoteType.DOWNVOTE).main {
updateVoting(it)
}
}
}
ioSafe {
metadata.getVotes().main {
updateVoting(it)
}
}
}
private fun updateVoting(value: Int) {
val metadata = data.plugin.second
plugin_votes.text = value.toString()
when (metadata.getVoteType()) {
VotingApi.VoteType.UPVOTE -> {
upvote.imageTintList = ColorStateList.valueOf(context?.colorFromAttribute(R.attr.colorPrimary) ?: R.color.colorPrimary)
downvote.imageTintList = ColorStateList.valueOf(context?.colorFromAttribute(R.attr.white) ?: R.color.white)
}
VotingApi.VoteType.DOWNVOTE -> {
downvote.imageTintList = ColorStateList.valueOf(context?.colorFromAttribute(R.attr.colorPrimary) ?: R.color.colorPrimary)
upvote.imageTintList = ColorStateList.valueOf(context?.colorFromAttribute(R.attr.white) ?: R.color.white)
}
VotingApi.VoteType.NONE -> {
upvote.imageTintList = ColorStateList.valueOf(context?.colorFromAttribute(R.attr.white) ?: R.color.white)
downvote.imageTintList = ColorStateList.valueOf(context?.colorFromAttribute(R.attr.white) ?: R.color.white)
}
}
}
}

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="?attr/white"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M15,3L6,3c-0.83,0 -1.54,0.5 -1.84,1.22l-3.02,7.05c-0.09,0.23 -0.14,0.47 -0.14,0.73v2c0,1.1 0.9,2 2,2h6.31l-0.95,4.57 -0.03,0.32c0,0.41 0.17,0.79 0.44,1.06L9.83,23l6.59,-6.59c0.36,-0.36 0.58,-0.86 0.58,-1.41L17,5c0,-1.1 -0.9,-2 -2,-2zM19,3v12h4L23,3h-4z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="?attr/white"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M1,21h4L5,9L1,9v12zM23,10c0,-1.1 -0.9,-2 -2,-2h-6.31l0.95,-4.57 0.03,-0.32c0,-0.41 -0.17,-0.79 -0.44,-1.06L14.17,1 7.59,7.59C7.22,7.95 7,8.45 7,9v10c0,1.1 0.9,2 2,2h9c0.83,0 1.54,-0.5 1.84,-1.22l3.02,-7.05c0.09,-0.23 0.14,-0.47 0.14,-0.73v-2z"/>
</vector>

View file

@ -0,0 +1,275 @@
<?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="wrap_content"
android:backgroundTint="?attr/primaryGrayBackground"
android:clipToPadding="false"
android:orientation="vertical"
tools:context=".ui.settings.extensions.PluginDetailsFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="20dp"
android:visibility="visible">
<androidx.cardview.widget.CardView
android:layout_width="50dp"
android:layout_height="50dp"
app:cardCornerRadius="25dp">
<ImageView
android:id="@+id/plugin_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="ContentDescription" />
</androidx.cardview.widget.CardView>
<TextView
android:id="@+id/plugin_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:textColor="?attr/textColor"
android:textSize="20sp"
android:textStyle="normal"
tools:text="Hello world" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:padding="8dp">
<!--marquee_forever-->
<com.google.android.material.button.MaterialButton
style="@style/SmallBlackButton"
android:layout_gravity="center"
android:layout_marginStart="10dp"
android:text="@string/extension_description" />
<TextView
android:id="@+id/plugin_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:ellipsize="none"
android:gravity="center_vertical"
android:singleLine="false"
android:textColor="?attr/textColor"
tools:text="Lolem ipsum kek Lolem ipsum kek Lolem ipsum kek Lolem ipsum kek " />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:padding="8dp">
<!--marquee_forever-->
<com.google.android.material.button.MaterialButton
style="@style/SmallBlackButton"
android:layout_gravity="center"
android:layout_marginStart="10dp"
android:text="@string/extension_authors" />
<TextView
android:id="@+id/plugin_author"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:ellipsize="marquee"
android:gravity="center_vertical"
android:singleLine="false"
android:textColor="?attr/textColor"
tools:text="Lolem ipsum kek" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:padding="8dp">
<!--marquee_forever-->
<com.google.android.material.button.MaterialButton
style="@style/SmallBlackButton"
android:layout_gravity="center"
android:layout_marginStart="10dp"
android:text="@string/extension_version" />
<TextView
android:id="@+id/plugin_version"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:ellipsize="marquee"
android:gravity="center_vertical"
android:singleLine="true"
android:textColor="?attr/textColor"
tools:text="Lolem ipsum kek" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:padding="8dp">
<!--marquee_forever-->
<com.google.android.material.button.MaterialButton
style="@style/SmallBlackButton"
android:layout_gravity="center"
android:layout_marginStart="10dp"
android:text="@string/extension_status" />
<TextView
android:id="@+id/plugin_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:ellipsize="marquee"
android:gravity="center_vertical"
android:singleLine="true"
android:textColor="?attr/textColor"
tools:text="Lolem ipsum kek" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:padding="8dp">
<!--marquee_forever-->
<com.google.android.material.button.MaterialButton
style="@style/SmallBlackButton"
android:layout_gravity="center"
android:layout_marginStart="10dp"
android:text="@string/extension_size" />
<TextView
android:id="@+id/plugin_size"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:ellipsize="marquee"
android:gravity="center_vertical"
android:singleLine="true"
android:textColor="?attr/textColor"
tools:text="Lolem ipsum kek" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:padding="8dp">
<!--marquee_forever-->
<com.google.android.material.button.MaterialButton
style="@style/SmallBlackButton"
android:layout_gravity="center"
android:layout_marginStart="10dp"
android:text="@string/extension_types" />
<TextView
android:id="@+id/plugin_types"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:ellipsize="marquee"
android:gravity="center_vertical"
android:singleLine="false"
android:textColor="?attr/textColor"
tools:text="Lolem ipsum kek" />
</LinearLayout>
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_horizontal|center_vertical">
<ImageView
android:id="@+id/downvote"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_thumb_down_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/plugin_votes"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/plugin_votes"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="32dp"
android:gravity="center_horizontal|center_vertical"
android:text="0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/upvote"
app:layout_constraintStart_toEndOf="@+id/downvote"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/upvote"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_thumb_up_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/plugin_votes"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View file

@ -73,6 +73,17 @@
android:visibility="gone" android:visibility="gone"
tools:visibility="visible" /> tools:visibility="visible" />
<TextView
android:id="@+id/ext_votes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="5dp"
android:text="10K"
android:textColor="?attr/grayTextColor"
android:visibility="gone"
tools:visibility="visible" />
<TextView <TextView
android:id="@+id/nsfw_marker" android:id="@+id/nsfw_marker"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -88,6 +99,7 @@
android:id="@+id/sub_text" android:id="@+id/sub_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="?attr/grayTextColor" android:textColor="?attr/grayTextColor"
android:textSize="12sp" android:textSize="12sp"
tools:text="https://github.com/..." /> tools:text="https://github.com/..." />

View file

@ -73,6 +73,17 @@
android:visibility="gone" android:visibility="gone"
tools:visibility="visible" /> tools:visibility="visible" />
<TextView
android:id="@+id/ext_votes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="5dp"
android:text="Votes: 0"
android:textColor="?attr/grayTextColor"
android:visibility="gone"
tools:visibility="visible" />
<TextView <TextView
android:id="@+id/nsfw_marker" android:id="@+id/nsfw_marker"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -88,9 +99,11 @@
android:id="@+id/sub_text" android:id="@+id/sub_text"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="?attr/grayTextColor" android:textColor="?attr/grayTextColor"
android:textSize="12sp" android:textSize="12sp"
tools:text="https://github.com/..." /> tools:text="https://github.com/..." />
</LinearLayout> </LinearLayout>
<ImageView <ImageView

View file

@ -257,6 +257,14 @@
<item>Light</item> <item>Light</item>
</string-array> </string-array>
<string-array name="extension_statuses">
<item>Down</item>
<!-- "Ok" is usually capitalized as "OK". Ok android studio 🤓-->
<item>Ok</item>
<item>Slow</item>
<item>Beta</item>
</string-array>
<!--https://github.com/videolan/vlc-android/blob/72ccfb93db027b49855760001d1a930fa657c5a8/application/resources/src/main/res/values/arrays.xml#L266--> <!--https://github.com/videolan/vlc-android/blob/72ccfb93db027b49855760001d1a930fa657c5a8/application/resources/src/main/res/values/arrays.xml#L266-->
<string-array name="subtitles_encoding_list" tools:ignore="TypographyDashes"> <string-array name="subtitles_encoding_list" tools:ignore="TypographyDashes">
<item>@string/automatic</item> <item>@string/automatic</item>

View file

@ -615,4 +615,12 @@
<string name="safe_mode_title">Safe Mode enabled</string> <string name="safe_mode_title">Safe Mode enabled</string>
<string name="safe_mode_description">An unrecoverable crash occurred and we\'ve automatically disabled all extensions, so you can find and remove the extension which is causing trouble.</string> <string name="safe_mode_description">An unrecoverable crash occurred and we\'ve automatically disabled all extensions, so you can find and remove the extension which is causing trouble.</string>
<string name="safe_mode_crash_info">View crash info</string> <string name="safe_mode_crash_info">View crash info</string>
<string name="votes_format" formatted="true">Votes: %s</string>
<string name="extension_description">Description</string>
<string name="extension_version">Version</string>
<string name="extension_status">Status</string>
<string name="extension_size">Size</string>
<string name="extension_authors">Authors</string>
<string name="extension_types">Supported</string>
</resources> </resources>