This commit is contained in:
deniscerri 2025-03-26 22:22:02 +01:00
parent 444e188549
commit db55894fdc
No known key found for this signature in database
GPG key ID: 95C43D517D830350
11 changed files with 294 additions and 355 deletions

View file

@ -1298,33 +1298,28 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
updateToStatus(id, DownloadRepository.Status.Paused)
}
suspend fun pauseAllDownloads() {
fun pauseAllDownloads() = viewModelScope.launch {
pausedAllDownloads.value = PausedAllDownloadsState.PROCESSING
isPausingResuming = true
WorkManager.getInstance(application).cancelAllWorkByTag("download")
val activeDownloadsList = withContext(Dispatchers.IO){
getActiveDownloads()
}
activeDownloadsList.forEach {
cancelDownloadOnly(it.id)
}
withContext(Dispatchers.IO) {
delay(1000)
isPausingResuming = false
withContext(Dispatchers.IO){
repository.setDownloadStatusMultiple(activeDownloadsList.map { it.id }, DownloadRepository.Status.Paused)
}
delay(1500)
isPausingResuming = false
withContext(Dispatchers.Main) {
pausedAllDownloads.value = PausedAllDownloadsState.RESUME
}
pausedAllDownloads.value = PausedAllDownloadsState.RESUME
}
suspend fun resumeAllDownloads() {
fun resumeAllDownloads() = viewModelScope.launch {
pausedAllDownloads.value = PausedAllDownloadsState.PROCESSING
isPausingResuming = true
WorkManager.getInstance(application).cancelAllWorkByTag("download")
val paused = withContext(Dispatchers.IO) {
dao.getPausedDownloadsList()
}
@ -1333,11 +1328,9 @@ class DownloadViewModel(private val application: Application) : AndroidViewModel
dbManager.downloadDao.resetPausedToQueued()
repository.startDownloadWorker(paused, application)
}
delay(1500)
delay(1000)
isPausingResuming = false
withContext(Dispatchers.Main) {
pausedAllDownloads.value = PausedAllDownloadsState.PAUSE
}
pausedAllDownloads.value = PausedAllDownloadsState.PAUSE
}
fun deleteAll() = viewModelScope.launch {

View file

@ -1,8 +1,6 @@
package com.deniscerri.ytdl.ui.more.settings.advanced.generateyoutubepotokens
import android.app.Activity
import android.app.ActivityManager
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
@ -32,9 +30,6 @@ class GenerateYoutubePoTokensFragment : Fragment() {
private lateinit var preferences: SharedPreferences
private lateinit var configuration : MutableList<YoutubeGeneratePoTokenItem>
private lateinit var workManager : WorkManager
private lateinit var poTokenGenerator: PoTokenGenerator
private val sampleURL = "https://www.youtube.com/watch?v=aqz-KE-bpKQ" // Big Buck Bunny
override fun onCreateView(
inflater: LayoutInflater,
@ -44,7 +39,6 @@ class GenerateYoutubePoTokensFragment : Fragment() {
settingsActivity = activity as SettingsActivity
settingsActivity.changeTopAppbarTitle(getString(R.string.generate_potokens))
preferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
poTokenGenerator = PoTokenGenerator()
workManager = WorkManager.getInstance(requireContext())
return inflater.inflate(R.layout.fragment_generate_youtube_po_token, container, false)
}

View file

@ -1,13 +0,0 @@
package com.deniscerri.ytdl.ui.more.settings.advanced.generateyoutubepotokens.webview
class PoTokenException(message: String) : Exception(message)
// to be thrown if the WebView provided by the system is broken
class BadWebViewException(message: String) : Exception(message)
fun buildExceptionForJsError(error: String): Exception {
return if (error.contains("SyntaxError"))
BadWebViewException(error)
else
PoTokenException(error)
}

View file

@ -5,6 +5,7 @@ import android.content.Intent
import android.content.SharedPreferences
import android.os.Build
import android.os.Bundle
import android.view.MenuItem
import android.webkit.CookieManager
import android.webkit.WebView
import android.widget.Toast
@ -23,7 +24,8 @@ import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.CookieItem
import com.deniscerri.ytdl.database.viewmodel.CookieViewModel
import com.deniscerri.ytdl.ui.BaseActivity
import com.deniscerri.ytdl.ui.more.settings.advanced.generateyoutubepotokens.PoTokenGenerator
import com.deniscerri.ytdl.util.UiUtil
import com.deniscerri.ytdl.util.extractors.newpipe.potoken.NewPipePoTokenGenerator
import com.google.accompanist.web.AccompanistWebChromeClient
import com.google.accompanist.web.AccompanistWebViewClient
import com.google.accompanist.web.WebView
@ -57,7 +59,18 @@ class PoTokenWebViewLoginActivity : BaseActivity() {
lifecycleScope.launch {
val appbar = findViewById<AppBarLayout>(R.id.webview_appbarlayout)
toolbar = appbar.findViewById(R.id.webviewToolbar)
toolbar.menu.forEach { it.isVisible = false }
toolbar.setOnMenuItemClickListener { m : MenuItem ->
when(m.itemId) {
R.id.get_data_sync_id -> {
webView.evaluateJavascript("ytcfg.get('DATASYNC_ID')") { id ->
UiUtil.copyToClipboard(id.replace("\"", ""), this@PoTokenWebViewLoginActivity)
}
}
else -> {}
}
true
}
generateBtn = toolbar.findViewById(R.id.generate)
webViewCompose = findViewById(R.id.webview_compose)
@ -82,7 +95,7 @@ class PoTokenWebViewLoginActivity : BaseActivity() {
generateBtn.isEnabled = false
lifecycleScope.launch {
val generator = PoTokenGenerator()
val generator = NewPipePoTokenGenerator()
val res = withContext(Dispatchers.IO) {
generator.getWebClientPoToken(sampleVideoID)
}
@ -100,7 +113,7 @@ class PoTokenWebViewLoginActivity : BaseActivity() {
//update cookies
withContext(Dispatchers.IO) {
val url = "https://youtube.com"
val url = "Po Token Generated Cookies"
cookiesViewModel.getCookiesFromDB(url).getOrNull()?.let {
kotlin.runCatching {
cookiesViewModel.insert(

View file

@ -59,13 +59,18 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
}
ytdlVersion?.apply {
summary = preferences.getString("ytdl-version", "")
if (summary?.isBlank() == true) {
setYTDLPVersion()
}
setOnPreferenceClickListener {
initYTDLUpdate()
true
lifecycleScope.launch {
summary = getString(R.string.loading)
summary = withContext(Dispatchers.IO){
ytdlpViewModel.getVersion(preferences.getString("ytdlp_source", "stable")!!)
}
if (summary?.isBlank() == true) {
setYTDLPVersion()
}
setOnPreferenceClickListener {
initYTDLUpdate()
true
}
}
}

View file

@ -1,44 +1,18 @@
package com.deniscerri.ytdl.util
import android.annotation.SuppressLint
import android.app.Activity
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.text.method.LinkMovementMethod
import android.util.Log
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat.startActivity
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.work.WorkManager.UpdateResult
import com.deniscerri.ytdl.BuildConfig
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.GithubRelease
import com.deniscerri.ytdl.ui.adapter.ChangelogAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon
import io.noties.markwon.MarkwonConfiguration
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
@ -96,36 +70,6 @@ class UpdateUtil(var context: Context) {
}
}
fun showChangeLog(activity: Activity){
runCatching {
val releases = getGithubReleases()
val view = activity.layoutInflater.inflate(R.layout.generic_list, null)
val adapter = ChangelogAdapter(activity)
val recycler = view.findViewById<RecyclerView>(R.id.download_recyclerview)
recycler.layoutManager = LinearLayoutManager(activity)
recycler.adapter = adapter
adapter.submitList(releases)
recycler.enableFastScroll()
val changeLogDialog = MaterialAlertDialogBuilder(context)
.setTitle(activity.getString(R.string.changelog))
.setView(view)
.setIcon(R.drawable.ic_chapters)
.setNegativeButton(context.resources.getString(R.string.dismiss)) { _: DialogInterface?, _: Int -> }
Handler(Looper.getMainLooper()).post {
changeLogDialog.show()
}
}.onFailure {
if (it.message != null){
Handler(Looper.getMainLooper()).post {
UiUtil.showErrorDialog(context, it.message!!)
}
}
}
}
fun getGithubReleases(): List<GithubRelease> {
val url = "https://api.github.com/repos/deniscerri/ytdlnis/releases"
val conn: HttpURLConnection
@ -158,8 +102,7 @@ class UpdateUtil(var context: Context) {
suspend fun updateYoutubeDL(c: String? = null) : YTDLPUpdateResponse =
withContext(Dispatchers.IO){
val sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
if (updatingYTDL) {
YTDLPUpdateResponse(YTDLPUpdateStatus.PROCESSING)
}
@ -168,8 +111,6 @@ class UpdateUtil(var context: Context) {
val channel = if (c.isNullOrBlank()) sharedPreferences.getString("ytdlp_source", "stable") else c
when(channel) {
"stable", "nightly", "master" -> {
val res = YoutubeDL.updateYoutubeDL(context, channelMap[channel]!!)
@ -185,7 +126,7 @@ class UpdateUtil(var context: Context) {
request.addOption("--update-to", "${channel}@latest")
val res = YoutubeDL.getInstance().execute(request)
val out = res.out.split(System.getProperty("line.separator")).last { it.isNotBlank() }
val out = res.out.lines().last { it.isNotBlank() }
if (out.contains("ERROR")) YTDLPUpdateResponse(YTDLPUpdateStatus.ERROR, out)
if (out.contains("yt-dlp is up to date")) YTDLPUpdateResponse(YTDLPUpdateStatus.ALREADY_UP_TO_DATE, out)

View file

@ -10,7 +10,7 @@ import com.deniscerri.ytdl.database.models.ResultItem
import com.deniscerri.ytdl.database.viewmodel.ResultViewModel
import com.deniscerri.ytdl.util.Extensions.getIDFromYoutubeURL
import com.deniscerri.ytdl.util.Extensions.toStringDuration
import com.deniscerri.ytdl.ui.more.settings.advanced.generateyoutubepotokens.PoTokenGenerator
import com.deniscerri.ytdl.util.extractors.newpipe.potoken.NewPipePoTokenGenerator
import com.google.gson.Gson
import okhttp3.OkHttpClient
import org.json.JSONException
@ -38,7 +38,7 @@ class NewPipeUtil(context: Context) {
init {
NewPipe.init(NewPipeDownloaderImpl(OkHttpClient.Builder()), Localization(language, countryCode))
YoutubeStreamExtractor.setPoTokenProvider(PoTokenGenerator())
YoutubeStreamExtractor.setPoTokenProvider(NewPipePoTokenGenerator())
}
fun getVideoData(url : String) : Result<List<ResultItem>> {

View file

@ -1,4 +1,4 @@
package com.deniscerri.ytdl.ui.more.settings.advanced.generateyoutubepotokens.webview
package com.deniscerri.ytdl.util.extractors.newpipe.potoken
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonNull
@ -125,7 +125,7 @@ object JavascriptUtil {
.replace('_', '/')
.replace('.', '=')
return (base64Mod.decodeBase64() ?: throw PoTokenException("Cannot base64 decode"))
return (base64Mod.decodeBase64() ?: throw Exception("Cannot base64 decode"))
.toByteArray()
}
}

View file

@ -1,4 +1,4 @@
package com.deniscerri.ytdl.ui.more.settings.advanced.generateyoutubepotokens
package com.deniscerri.ytdl.util.extractors.newpipe.potoken
import android.os.Handler
import android.os.Looper
@ -6,7 +6,6 @@ import android.util.Log
import android.webkit.CookieManager
import com.deniscerri.ytdl.App
import com.deniscerri.ytdl.BuildConfig
import com.deniscerri.ytdl.ui.more.settings.advanced.generateyoutubepotokens.webview.PoTokenWebView
import kotlinx.coroutines.runBlocking
import org.schabi.newpipe.extractor.NewPipe
import org.schabi.newpipe.extractor.services.youtube.InnertubeClientRequestInfo
@ -14,9 +13,8 @@ import org.schabi.newpipe.extractor.services.youtube.PoTokenProvider
import org.schabi.newpipe.extractor.services.youtube.PoTokenResult
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper
//for newpipe
class PoTokenGenerator : PoTokenProvider {
val TAG = PoTokenGenerator::class.simpleName
class NewPipePoTokenGenerator : PoTokenProvider {
val TAG = NewPipePoTokenGenerator::class.simpleName
private val supportsWebView by lazy { runCatching { CookieManager.getInstance() }.isSuccess }
private object WebPoTokenGenLock
@ -39,7 +37,7 @@ class PoTokenGenerator : PoTokenProvider {
/**
* @param forceRecreate whether to force the recreation of [webPoTokenGenerator], to be used in
* case the current [webPoTokenGenerator] threw an error last time
* [PoTokenGenerator.generatePoToken] was called
* [NewPipePoTokenGenerator.generatePoToken] was called
*/
private fun getWebClientPoToken(videoId: String, forceRecreate: Boolean): PoTokenResult {
// just a helper class since Kotlin does not have builtin support for 4-tuples

View file

@ -1,4 +1,4 @@
package com.deniscerri.ytdl.ui.more.settings.advanced.generateyoutubepotokens.webview
package com.deniscerri.ytdl.util.extractors.newpipe.potoken
import android.content.Context
import android.os.Build
@ -17,10 +17,10 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import com.deniscerri.ytdl.BuildConfig
import com.deniscerri.ytdl.ui.more.settings.advanced.generateyoutubepotokens.webview.JavascriptUtil.parseChallengeData
import com.deniscerri.ytdl.ui.more.settings.advanced.generateyoutubepotokens.webview.JavascriptUtil.parseIntegrityTokenData
import com.deniscerri.ytdl.ui.more.settings.advanced.generateyoutubepotokens.webview.JavascriptUtil.stringToU8
import com.deniscerri.ytdl.ui.more.settings.advanced.generateyoutubepotokens.webview.JavascriptUtil.u8ToBase64
import com.deniscerri.ytdl.util.extractors.newpipe.potoken.JavascriptUtil.parseChallengeData
import com.deniscerri.ytdl.util.extractors.newpipe.potoken.JavascriptUtil.parseIntegrityTokenData
import com.deniscerri.ytdl.util.extractors.newpipe.potoken.JavascriptUtil.stringToU8
import com.deniscerri.ytdl.util.extractors.newpipe.potoken.JavascriptUtil.u8ToBase64
import okhttp3.Headers.Companion.toHeaders
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
@ -67,7 +67,7 @@ class PoTokenWebView private constructor(
// supports a really old version of JS.
val fmt = "\"${m.message()}\", source: ${m.sourceId()} (${m.lineNumber()})"
val exception = BadWebViewException(fmt)
val exception = Exception(fmt)
Log.e(TAG, "This WebView implementation is broken: $fmt")
onInitializationErrorCloseAndCancel(exception)
@ -136,7 +136,7 @@ class PoTokenWebView private constructor(
if (BuildConfig.DEBUG) {
Log.e(TAG, "Initialization error from JavaScript: $error")
}
onInitializationErrorCloseAndCancel(buildExceptionForJsError(error))
onInitializationErrorCloseAndCancel(Exception(error))
}
/**
@ -195,7 +195,7 @@ class PoTokenWebView private constructor(
if (BuildConfig.DEBUG) {
Log.e(TAG, "obtainPoToken error from JavaScript: $error")
}
popPoTokenContinuation(identifier)?.resumeWithException(buildExceptionForJsError(error))
popPoTokenContinuation(identifier)?.resumeWithException(Exception(error))
}
/**
@ -280,10 +280,10 @@ class PoTokenWebView private constructor(
}
val httpCode = response.code
if (httpCode != 200) {
onInitializationErrorCloseAndCancel(PoTokenException("Invalid response code: $httpCode"))
onInitializationErrorCloseAndCancel(Exception("Invalid response code: $httpCode"))
} else {
val body = withContext(Dispatchers.IO) {
response.body!!.string()
response.body.string()
}
handleResponseBody(body)
}

View file

@ -6,241 +6,249 @@
android:layout_height="match_parent"
tools:context="com.deniscerri.ytdl.ui.more.terminal.TerminalActivity">
<LinearLayout
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_height="wrap_content">
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/web_client_switch"
<LinearLayout
android:layout_width="match_parent"
android:background="?attr/colorSurfaceContainer"
android:paddingHorizontal="20dp"
android:layout_height="70dp"
android:checked="false"
android:textSize="16sp"
android:textStyle="bold"
android:text="Web Client Po Token"
tools:ignore="HardcodedText" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:checkable="true"
android:clickable="true"
android:focusable="true"
android:backgroundTint="@android:color/transparent"
app:checkedIcon="@null"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
app:strokeWidth="0dp"
app:cardPreventCornerOverlap="true"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="15dp">
<TextView
android:id="@+id/title_gvs"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textSize="15sp"
android:textStyle="bold"
android:text="PO Token (GVS)"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/content_gvs"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="15dp"
android:maxLines="3"
android:ellipsize="end"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_gvs" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:checkable="true"
android:clickable="true"
android:focusable="true"
android:backgroundTint="@android:color/transparent"
app:checkedIcon="@null"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
app:strokeWidth="0dp"
app:cardPreventCornerOverlap="true"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="15dp">
<TextView
android:id="@+id/title_player"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textSize="15sp"
android:textStyle="bold"
android:text="PO Token (Player)"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/content_player"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="15dp"
android:maxLines="3"
android:ellipsize="end"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_player" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:checkable="true"
android:clickable="true"
android:focusable="true"
android:backgroundTint="@android:color/transparent"
app:checkedIcon="@null"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
app:strokeWidth="0dp"
app:cardPreventCornerOverlap="true"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="15dp">
<TextView
android:id="@+id/title_visitordata"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textSize="15sp"
android:textStyle="bold"
android:text="Visitor Data"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/content_visitordata"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="15dp"
android:maxLines="3"
android:ellipsize="end"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_visitordata" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:checkable="true"
android:clickable="true"
android:focusable="true"
android:backgroundTint="@android:color/transparent"
app:checkedIcon="@null"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
app:strokeWidth="0dp"
app:cardPreventCornerOverlap="true"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/playerclient_div"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="15dp">
<TextView
android:id="@+id/title_playerclients"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textSize="15sp"
android:textStyle="bold"
android:text="@string/player_client"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/content_playerclient"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="15dp"
android:maxLines="3"
android:ellipsize="end"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_playerclients" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<Button
android:id="@+id/regenerate_webview_potokens"
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:text="@string/regenerate"
android:outlineProvider="none"
android:layout_gravity="end"
android:layout_margin="20dp"
app:icon="@drawable/baseline_stars_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:orientation="vertical">
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/use_visitor_data"
android:layout_width="match_parent"
android:paddingHorizontal="20dp"
android:layout_height="70dp"
android:checked="true"
android:textSize="16sp"
android:textStyle="bold"
android:text="Use Visitor Data"
tools:ignore="HardcodedText" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/web_client_switch"
android:layout_width="match_parent"
android:background="?attr/colorSurfaceContainer"
android:paddingHorizontal="20dp"
android:layout_height="70dp"
android:checked="false"
android:textSize="16sp"
android:textStyle="bold"
android:text="Web Client Po Token"
tools:ignore="HardcodedText" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:checkable="true"
android:clickable="true"
android:focusable="true"
android:backgroundTint="@android:color/transparent"
app:checkedIcon="@null"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
app:strokeWidth="0dp"
app:cardPreventCornerOverlap="true"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="15dp">
<TextView
android:id="@+id/title_gvs"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textSize="15sp"
android:textStyle="bold"
android:text="PO Token (GVS)"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/content_gvs"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="15dp"
android:maxLines="3"
android:ellipsize="end"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_gvs" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:checkable="true"
android:clickable="true"
android:focusable="true"
android:backgroundTint="@android:color/transparent"
app:checkedIcon="@null"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
app:strokeWidth="0dp"
app:cardPreventCornerOverlap="true"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="15dp">
<TextView
android:id="@+id/title_player"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textSize="15sp"
android:textStyle="bold"
android:text="PO Token (Player)"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/content_player"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="15dp"
android:maxLines="3"
android:ellipsize="end"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_player" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:checkable="true"
android:clickable="true"
android:focusable="true"
android:backgroundTint="@android:color/transparent"
app:checkedIcon="@null"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
app:strokeWidth="0dp"
app:cardPreventCornerOverlap="true"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="15dp">
<TextView
android:id="@+id/title_visitordata"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textSize="15sp"
android:textStyle="bold"
android:text="Visitor Data"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/content_visitordata"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="15dp"
android:maxLines="3"
android:ellipsize="end"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_visitordata" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:checkable="true"
android:clickable="true"
android:focusable="true"
android:backgroundTint="@android:color/transparent"
app:checkedIcon="@null"
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
app:strokeWidth="0dp"
app:cardPreventCornerOverlap="true"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/playerclient_div"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="15dp">
<TextView
android:id="@+id/title_playerclients"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textSize="15sp"
android:textStyle="bold"
android:text="@string/player_client"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/content_playerclient"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="15dp"
android:maxLines="3"
android:ellipsize="end"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title_playerclients" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<Button
android:id="@+id/regenerate_webview_potokens"
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:autoLink="all"
android:text="@string/regenerate"
android:outlineProvider="none"
android:layout_gravity="end"
android:layout_margin="20dp"
app:icon="@drawable/baseline_stars_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/use_visitor_data"
android:layout_width="match_parent"
android:paddingHorizontal="20dp"
android:layout_height="70dp"
android:checked="true"
android:textSize="16sp"
android:textStyle="bold"
android:text="Use Visitor Data"
tools:ignore="HardcodedText" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textStyle="bold"
android:text="@string/generate_potokens_warning"/>
</LinearLayout>
</ScrollView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:textStyle="bold"
android:text="@string/generate_potokens_warning"/>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>