Player v2 (#401)

This commit is contained in:
Osten 2022-01-07 19:27:25 +00:00 committed by GitHub
parent cb65299e57
commit 9fd237301e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
94 changed files with 5997 additions and 4025 deletions

View file

@ -19,4 +19,4 @@
</GradleProjectSettings>
</option>
</component>
</project>
</project>

View file

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="app/src/main/res/layout/fragment_result.xml" value="0.75" />
</map>
</option>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK" />
</project>

View file

@ -0,0 +1,28 @@
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="850dp"
android:height="850dp"
android:viewportWidth="850"
android:viewportHeight="850">
<path
android:name="path"
android:pathData="M 267.01 189.64 L 366.75 189.64 L 366.75 659.9 L 267.01 659.9 Z M 463.01 188.79 L 562.75 188.79 L 562.75 659.05 L 463.01 659.05 Z"
android:fillColor="#ffffff"/>
</vector>
</aapt:attr>
<target android:name="path">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="pathData"
android:duration="200"
android:valueFrom="M 463.01 659.05 L 562.75 659.05 L 562.75 188.79 L 463.01 188.79 L 463.01 659.05 M 366.75 189.64 L 366.75 659.9 L 267.01 659.9 L 267.01 189.64 L 366.75 189.64"
android:valueTo="M 425.844 568.243 L 674.6 424.6 L 551.156 353.317 L 427.712 282.035 L 425.844 568.243 M 427.712 282.035 L 425.844 568.243 L 256.9 665.8 L 256.9 183.4 L 427.712 282.035"
android:valueType="pathType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
</animated-vector>

View file

@ -0,0 +1,28 @@
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="vector"
android:width="850dp"
android:height="850dp"
android:viewportWidth="850"
android:viewportHeight="850">
<path
android:name="path"
android:pathData="M 674.6 424.6 L 256.9 183.4 L 256.9 665.8 Z"
android:fillColor="#ffffff"/>
</vector>
</aapt:attr>
<target android:name="path">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="pathData"
android:duration="200"
android:valueFrom="M 425.844 568.243 L 674.6 424.6 L 551.156 353.317 L 427.712 282.035 L 425.844 568.243 M 427.712 282.035 L 425.844 568.243 L 256.9 665.8 L 256.9 183.4 L 427.712 282.035"
android:valueTo="M 463.01 659.05 L 562.75 659.05 L 562.75 188.79 L 463.01 188.79 L 463.01 659.05 M 366.75 189.64 L 366.75 659.9 L 267.01 659.9 L 267.01 189.64 L 366.75 189.64"
android:valueType="pathType"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
</aapt:attr>
</target>
</animated-vector>

View file

@ -24,10 +24,27 @@
android:usesCleartextTraffic="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme" android:fullBackupContent="@xml/backup_descriptor" tools:targetApi="m">
android:theme="@style/AppTheme"
android:fullBackupContent="@xml/backup_descriptor"
tools:targetApi="m">
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.lagradost.cloudstream3.utils.CastOptionsProvider"/>
<activity android:name=".ui.player.DownloadedPlayerActivity"
android:screenOrientation="userLandscape"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|navigation"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="content"/>
<data android:mimeType="video/*"/>
</intent-filter>
</activity>
<activity
android:exported="true"
android:name=".MainActivity"

View file

@ -0,0 +1,353 @@
package com.lagradost.cloudstream3
import android.app.Activity
import android.app.PictureInPictureParams
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Resources
import android.os.Build
import android.view.*
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.preference.PreferenceManager
import com.google.android.gms.cast.framework.CastSession
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.player.PlayerEventType
import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.hasPIPPermission
import com.lagradost.cloudstream3.utils.UIHelper.shouldShowPIPMode
import com.lagradost.cloudstream3.utils.UIHelper.toPx
import java.util.*
object CommonActivity {
fun Activity?.getCastSession(): CastSession? {
return (this as MainActivity?)?.mSessionManager?.currentCastSession
}
var canEnterPipMode: Boolean = false
var canShowPipMode: Boolean = false
var isInPIPMode: Boolean = false
val backEvent = Event<Boolean>()
val onColorSelectedEvent = Event<Pair<Int, Int>>()
val onDialogDismissedEvent = Event<Int>()
var playerEventListener: ((PlayerEventType) -> Unit)? = null
var keyEventListener: ((KeyEvent?) -> Boolean)? = null
var currentToast: Toast? = null
fun showToast(act: Activity?, @StringRes message: Int, duration: Int) {
if (act == null) return
showToast(act, act.getString(message), duration)
}
/** duration is Toast.LENGTH_SHORT if null*/
fun showToast(act: Activity?, message: String?, duration: Int? = null) {
if (act == null || message == null) return
try {
currentToast?.cancel()
} catch (e: Exception) {
e.printStackTrace()
}
try {
val inflater =
act.getSystemService(AppCompatActivity.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val layout: View = inflater.inflate(
R.layout.toast,
act.findViewById<View>(R.id.toast_layout_root) as ViewGroup?
)
val text = layout.findViewById(R.id.text) as TextView
text.text = message.trim()
val toast = Toast(act)
toast.setGravity(Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM, 0, 5.toPx)
toast.duration = duration ?: Toast.LENGTH_SHORT
toast.view = layout
toast.show()
currentToast = toast
} catch (e: Exception) {
}
}
fun setLocale(context: Context?, languageCode: String?) {
if (context == null || languageCode == null) return
val locale = Locale(languageCode)
val resources: Resources = context.resources
val config = resources.configuration
Locale.setDefault(locale)
config.setLocale(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
context.createConfigurationContext(config)
resources.updateConfiguration(config, resources.displayMetrics)
}
fun Context.updateLocale() {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
val localeCode = settingsManager.getString(getString(R.string.locale_key), null)
setLocale(this, localeCode)
}
fun init(act: Activity?) {
if (act == null) return
//https://stackoverflow.com/questions/52594181/how-to-know-if-user-has-disabled-picture-in-picture-feature-permission
//https://developer.android.com/guide/topics/ui/picture-in-picture
canShowPipMode =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && // OS SUPPORT
act.packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && // HAS FEATURE, MIGHT BE BLOCKED DUE TO POWER DRAIN
act.hasPIPPermission() // CHECK IF FEATURE IS ENABLED IN SETTINGS
act.updateLocale()
}
private fun Activity.enterPIPMode() {
if (!shouldShowPIPMode(canEnterPipMode) || !canShowPipMode) return
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
enterPictureInPictureMode(PictureInPictureParams.Builder().build())
} catch (e: Exception) {
enterPictureInPictureMode()
}
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
enterPictureInPictureMode()
}
}
} catch (e: Exception) {
logError(e)
}
}
fun onUserLeaveHint(act: Activity?) {
if (canEnterPipMode && canShowPipMode) {
act?.enterPIPMode()
}
}
fun loadThemes(act: Activity? ) {
if(act == null) return
val settingsManager = PreferenceManager.getDefaultSharedPreferences(act)
val currentTheme =
when (settingsManager.getString(act.getString(R.string.app_theme_key), "AmoledLight")) {
"Black" -> R.style.AppTheme
"Light" -> R.style.LightMode
"Amoled" -> R.style.AmoledMode
"AmoledLight" -> R.style.AmoledModeLight
else -> R.style.AppTheme
}
val currentOverlayTheme =
when (settingsManager.getString(act.getString(R.string.primary_color_key), "Normal")) {
"Normal" -> R.style.OverlayPrimaryColorNormal
"Blue" -> R.style.OverlayPrimaryColorBlue
"Purple" -> R.style.OverlayPrimaryColorPurple
"Green" -> R.style.OverlayPrimaryColorGreen
"GreenApple" -> R.style.OverlayPrimaryColorGreenApple
"Red" -> R.style.OverlayPrimaryColorRed
"Banana" -> R.style.OverlayPrimaryColorBanana
"Party" -> R.style.OverlayPrimaryColorParty
"Pink" -> R.style.OverlayPrimaryColorPink
else -> R.style.OverlayPrimaryColorNormal
}
act.theme.applyStyle(currentTheme, true)
act.theme.applyStyle(currentOverlayTheme, true)
act.theme.applyStyle(
R.style.LoadedStyle,
true
) // THEME IS SET BEFORE VIEW IS CREATED TO APPLY THE THEME TO THE MAIN VIEW
}
private fun getNextFocus(
act: Activity?,
view: View?,
direction: FocusDirection,
depth: Int = 0
): Int? {
if (view == null || depth >= 10 || act == null) {
return null
}
val nextId = when (direction) {
FocusDirection.Left -> {
view.nextFocusLeftId
}
FocusDirection.Up -> {
view.nextFocusUpId
}
FocusDirection.Right -> {
view.nextFocusRightId
}
FocusDirection.Down -> {
view.nextFocusDownId
}
}
return if (nextId != -1) {
val next = act.findViewById<View?>(nextId)
//println("NAME: ${next.accessibilityClassName} | ${next?.isShown}" )
if (next?.isShown == false) {
getNextFocus(act, next, direction, depth + 1)
} else {
if (depth == 0) {
null
} else {
nextId
}
}
} else {
null
}
}
enum class FocusDirection {
Left,
Right,
Up,
Down,
}
fun onKeyDown(act: Activity?, keyCode: Int, event: KeyEvent?) {
//println("Keycode: $keyCode")
//showToast(
// this,
// "Got Keycode $keyCode | ${KeyEvent.keyCodeToString(keyCode)} \n ${event?.action}",
// Toast.LENGTH_LONG
//)
// Tested keycodes on remote:
// KeyEvent.KEYCODE_MEDIA_FAST_FORWARD
// KeyEvent.KEYCODE_MEDIA_REWIND
// KeyEvent.KEYCODE_MENU
// KeyEvent.KEYCODE_MEDIA_NEXT
// KeyEvent.KEYCODE_MEDIA_PREVIOUS
// KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
// 149 keycode_numpad 5
when (keyCode) {
KeyEvent.KEYCODE_FORWARD, KeyEvent.KEYCODE_D, KeyEvent.KEYCODE_MEDIA_SKIP_FORWARD, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD -> {
PlayerEventType.SeekForward
}
KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD, KeyEvent.KEYCODE_MEDIA_REWIND -> {
PlayerEventType.SeekBack
}
KeyEvent.KEYCODE_MEDIA_NEXT, KeyEvent.KEYCODE_BUTTON_R1 -> {
PlayerEventType.NextEpisode
}
KeyEvent.KEYCODE_MEDIA_PREVIOUS, KeyEvent.KEYCODE_BUTTON_L1 -> {
PlayerEventType.PrevEpisode
}
KeyEvent.KEYCODE_MEDIA_PAUSE -> {
PlayerEventType.Pause
}
KeyEvent.KEYCODE_MEDIA_PLAY, KeyEvent.KEYCODE_BUTTON_START -> {
PlayerEventType.Play
}
KeyEvent.KEYCODE_L, KeyEvent.KEYCODE_NUMPAD_7 -> {
PlayerEventType.Lock
}
KeyEvent.KEYCODE_H, KeyEvent.KEYCODE_MENU -> {
PlayerEventType.ToggleHide
}
KeyEvent.KEYCODE_M, KeyEvent.KEYCODE_VOLUME_MUTE -> {
PlayerEventType.ToggleMute
}
KeyEvent.KEYCODE_S, KeyEvent.KEYCODE_NUMPAD_9 -> {
PlayerEventType.ShowMirrors
}
KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_NUMPAD_3 -> {
PlayerEventType.ShowSpeed
}
KeyEvent.KEYCODE_R, KeyEvent.KEYCODE_NUMPAD_0 -> {
PlayerEventType.Resize
}
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_SPACE, KeyEvent.KEYCODE_NUMPAD_ENTER, KeyEvent.KEYCODE_ENTER -> { // space is not captured due to navigation
PlayerEventType.PlayPauseToggle
}
else -> null
}?.let { playerEvent ->
playerEventListener?.invoke(playerEvent)
}
//when (keyCode) {
// KeyEvent.KEYCODE_DPAD_CENTER -> {
// println("DPAD PRESSED")
// }
//}
}
fun dispatchKeyEvent(act: Activity?, event: KeyEvent?): Boolean? {
if (act == null) return null
event?.keyCode?.let { keyCode ->
when (event.action) {
KeyEvent.ACTION_DOWN -> {
if (act.currentFocus != null) {
val next = when (keyCode) {
KeyEvent.KEYCODE_DPAD_LEFT -> getNextFocus(
act,
act.currentFocus,
FocusDirection.Left
)
KeyEvent.KEYCODE_DPAD_RIGHT -> getNextFocus(
act,
act.currentFocus,
FocusDirection.Right
)
KeyEvent.KEYCODE_DPAD_UP -> getNextFocus(
act,
act.currentFocus,
FocusDirection.Up
)
KeyEvent.KEYCODE_DPAD_DOWN -> getNextFocus(
act,
act.currentFocus,
FocusDirection.Down
)
else -> null
}
if (next != null && next != -1) {
val nextView = act.findViewById<View?>(next)
if (nextView != null) {
nextView.requestFocus()
return true
}
}
when (keyCode) {
KeyEvent.KEYCODE_DPAD_CENTER -> {
if (act.currentFocus is SearchView || act.currentFocus is SearchView.SearchAutoComplete) {
UIHelper.showInputMethod(act.currentFocus?.findFocus())
}
}
}
}
//println("Keycode: $keyCode")
//showToast(
// this,
// "Got Keycode $keyCode | ${KeyEvent.keyCodeToString(keyCode)} \n ${event?.action}",
// Toast.LENGTH_LONG
//)
}
}
}
if (keyEventListener?.invoke(event) == true) {
return true
}
return null
}
}

View file

@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.lagradost.cloudstream3.animeproviders.*
import com.lagradost.cloudstream3.movieproviders.*
import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.utils.ExtractorLink
import java.util.*
@ -298,18 +299,12 @@ fun MainAPI.fixUrl(url: String): String {
}
}
fun sortUrls(urls: List<ExtractorLink>): List<ExtractorLink> {
fun sortUrls(urls: Set<ExtractorLink>): List<ExtractorLink> {
return urls.sortedBy { t -> -t.quality }
}
fun sortSubs(urls: List<SubtitleFile>): List<SubtitleFile> {
val encounteredTimes = HashMap<String, Int>()
return urls.sortedBy { t -> t.lang }.map {
val times = encounteredTimes[it.lang]?.plus(1) ?: 1
encounteredTimes[it.lang] = times
SubtitleFile("${it.lang} ${if (times > 1) "($times)" else ""}", it.url)
}
fun sortSubs(subs : Set<SubtitleData>) : List<SubtitleData> {
return subs.sortedBy { it.name }
}
fun capitalizeString(str: String): String {
@ -377,6 +372,11 @@ fun TvType.isMovieType(): Boolean {
return this == TvType.Movie || this == TvType.AnimeMovie || this == TvType.Torrent
}
// returns if the type has an anime opening
fun TvType.isAnimeOp(): Boolean {
return this == TvType.Anime || this == TvType.ONA
}
data class SubtitleFile(val lang: String, val url: String)
class HomePageResponse(
@ -463,7 +463,7 @@ interface LoadResponse {
fun LoadResponse?.isEpisodeBased(): Boolean {
if (this == null) return false
return (this is AnimeLoadResponse || this is TvSeriesLoadResponse) && (this.type == TvType.TvSeries || this.type == TvType.Anime)
return (this is AnimeLoadResponse || this is TvSeriesLoadResponse) && this.type.isEpisodeBased()
}
fun LoadResponse?.isAnimeBased(): Boolean {
@ -471,6 +471,11 @@ fun LoadResponse?.isAnimeBased(): Boolean {
return (this.type == TvType.Anime || this.type == TvType.ONA) // && (this is AnimeLoadResponse)
}
fun TvType?.isEpisodeBased() : Boolean {
if (this == null) return false
return (this == TvType.TvSeries || this == TvType.Anime)
}
data class AnimeEpisode(
val url: String,
var name: String? = null,

View file

@ -1,23 +1,13 @@
package com.lagradost.cloudstream3
import android.app.Activity
import android.app.PictureInPictureParams
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Build
import android.os.Bundle
import android.view.*
import android.view.KeyEvent.ACTION_DOWN
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.StringRes
import android.view.KeyEvent
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible
import androidx.navigation.NavDestination
import androidx.navigation.findNavController
@ -29,6 +19,12 @@ import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.restrictedApis
import com.lagradost.cloudstream3.CommonActivity.backEvent
import com.lagradost.cloudstream3.CommonActivity.loadThemes
import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent
import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
import com.lagradost.cloudstream3.CommonActivity.onUserLeaveHint
import com.lagradost.cloudstream3.CommonActivity.updateLocale
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.network.Requests
import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver
@ -37,27 +33,22 @@ import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.OAuth2accoun
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.appString
import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
import com.lagradost.cloudstream3.ui.player.PlayerEventType
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.removeKey
import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos
import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor
import com.lagradost.cloudstream3.utils.UIHelper.hasPIPPermission
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
import com.lagradost.cloudstream3.utils.UIHelper.shouldShowPIPMode
import com.lagradost.cloudstream3.utils.UIHelper.showInputMethod
import com.lagradost.cloudstream3.utils.UIHelper.toPx
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.fragment_result.*
import java.util.*
import java.io.File
import kotlin.concurrent.thread
@ -72,6 +63,7 @@ const val VLC_FROM_PROGRESS = -2
const val VLC_EXTRA_POSITION_OUT = "extra_position"
const val VLC_EXTRA_DURATION_OUT = "extra_duration"
const val VLC_LAST_ID_KEY = "vlc_last_open_id"
// Short name for requests client to make it nicer to use
var app = Requests()
@ -91,7 +83,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
findNavController(R.id.nav_host_fragment).currentDestination?.let { updateNavBar(it) }
}
private fun updateNavBar(destination : NavDestination) {
private fun updateNavBar(destination: NavDestination) {
this.hideKeyboard()
// Fucks up anime info layout since that has its own layout
@ -106,7 +98,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
R.id.navigation_download_child
).contains(destination.id)
val landscape = when(resources.configuration.orientation) {
val landscape = when (resources.configuration.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> {
true
}
@ -181,268 +173,24 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
}
enum class FocusDirection {
Left,
Right,
Up,
Down,
}
private fun getNextFocus(view: View?, direction: FocusDirection, depth: Int = 0): Int? {
if (view == null || depth >= 10) {
return null
}
val nextId = when (direction) {
FocusDirection.Left -> {
view.nextFocusLeftId
}
FocusDirection.Up -> {
view.nextFocusUpId
}
FocusDirection.Right -> {
view.nextFocusRightId
}
FocusDirection.Down -> {
view.nextFocusDownId
}
}
return if (nextId != -1) {
val next = findViewById<View?>(nextId)
//println("NAME: ${next.accessibilityClassName} | ${next?.isShown}" )
if (next?.isShown == false) {
getNextFocus(next, direction, depth + 1)
} else {
if (depth == 0) {
null
} else {
nextId
}
}
} else {
null
}
}
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
event?.keyCode?.let { keyCode ->
when (event.action) {
ACTION_DOWN -> {
if (currentFocus != null) {
val next = when (keyCode) {
KeyEvent.KEYCODE_DPAD_LEFT -> getNextFocus(currentFocus, FocusDirection.Left)
KeyEvent.KEYCODE_DPAD_RIGHT -> getNextFocus(currentFocus, FocusDirection.Right)
KeyEvent.KEYCODE_DPAD_UP -> getNextFocus(currentFocus, FocusDirection.Up)
KeyEvent.KEYCODE_DPAD_DOWN -> getNextFocus(currentFocus, FocusDirection.Down)
else -> null
}
if (next != null && next != -1) {
val nextView = findViewById<View?>(next)
if(nextView != null) {
nextView.requestFocus()
return true
}
}
when (keyCode) {
KeyEvent.KEYCODE_DPAD_CENTER -> {
println("DPAD PRESSED $currentFocus")
if (currentFocus is SearchView || currentFocus is SearchView.SearchAutoComplete) {
println("current PRESSED")
showInputMethod(currentFocus?.findFocus())
}
}
}
}
//println("Keycode: $keyCode")
//showToast(
// this,
// "Got Keycode $keyCode | ${KeyEvent.keyCodeToString(keyCode)} \n ${event?.action}",
// Toast.LENGTH_LONG
//)
}
}
}
if (keyEventListener?.invoke(event) == true) {
return true
CommonActivity.dispatchKeyEvent(this, event)?.let {
return it
}
return super.dispatchKeyEvent(event)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
//println("Keycode: $keyCode")
//showToast(
// this,
// "Got Keycode $keyCode | ${KeyEvent.keyCodeToString(keyCode)} \n ${event?.action}",
// Toast.LENGTH_LONG
//)
// Tested keycodes on remote:
// KeyEvent.KEYCODE_MEDIA_FAST_FORWARD
// KeyEvent.KEYCODE_MEDIA_REWIND
// KeyEvent.KEYCODE_MENU
// KeyEvent.KEYCODE_MEDIA_NEXT
// KeyEvent.KEYCODE_MEDIA_PREVIOUS
// KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
// 149 keycode_numpad 5
when (keyCode) {
KeyEvent.KEYCODE_FORWARD, KeyEvent.KEYCODE_D, KeyEvent.KEYCODE_MEDIA_SKIP_FORWARD, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD -> {
PlayerEventType.SeekForward
}
KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD, KeyEvent.KEYCODE_MEDIA_REWIND -> {
PlayerEventType.SeekBack
}
KeyEvent.KEYCODE_MEDIA_NEXT, KeyEvent.KEYCODE_BUTTON_R1 -> {
PlayerEventType.NextEpisode
}
KeyEvent.KEYCODE_MEDIA_PREVIOUS, KeyEvent.KEYCODE_BUTTON_L1 -> {
PlayerEventType.PrevEpisode
}
KeyEvent.KEYCODE_MEDIA_PAUSE -> {
PlayerEventType.Pause
}
KeyEvent.KEYCODE_MEDIA_PLAY, KeyEvent.KEYCODE_BUTTON_START -> {
PlayerEventType.Play
}
KeyEvent.KEYCODE_L, KeyEvent.KEYCODE_NUMPAD_7 -> {
PlayerEventType.Lock
}
KeyEvent.KEYCODE_H, KeyEvent.KEYCODE_MENU -> {
PlayerEventType.ToggleHide
}
KeyEvent.KEYCODE_M, KeyEvent.KEYCODE_VOLUME_MUTE -> {
PlayerEventType.ToggleMute
}
KeyEvent.KEYCODE_S, KeyEvent.KEYCODE_NUMPAD_9 -> {
PlayerEventType.ShowMirrors
}
KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_NUMPAD_3 -> {
PlayerEventType.ShowSpeed
}
KeyEvent.KEYCODE_R, KeyEvent.KEYCODE_NUMPAD_0 -> {
PlayerEventType.Resize
}
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_SPACE, KeyEvent.KEYCODE_NUMPAD_ENTER, KeyEvent.KEYCODE_ENTER -> { // space is not captured due to navigation
PlayerEventType.PlayPauseToggle
}
else -> null
}?.let { playerEvent ->
playerEventListener?.invoke(playerEvent)
}
//when (keyCode) {
// KeyEvent.KEYCODE_DPAD_CENTER -> {
// println("DPAD PRESSED")
// }
//}
CommonActivity.onKeyDown(this, keyCode, event)
return super.onKeyDown(keyCode, event)
}
companion object {
fun Activity?.getCastSession(): CastSession? {
return (this as MainActivity?)?.mSessionManager?.currentCastSession
}
var canEnterPipMode: Boolean = false
var canShowPipMode: Boolean = false
var isInPIPMode: Boolean = false
val backEvent = Event<Boolean>()
val onColorSelectedEvent = Event<Pair<Int, Int>>()
val onDialogDismissedEvent = Event<Int>()
var playerEventListener: ((PlayerEventType) -> Unit)? = null
var keyEventListener: ((KeyEvent?) -> Boolean)? = null
var currentToast: Toast? = null
fun showToast(act: Activity?, @StringRes message: Int, duration: Int) {
if (act == null) return
showToast(act, act.getString(message), duration)
}
fun showToast(act: Activity?, message: String?, duration: Int? = null) {
if (act == null || message == null) return
try {
currentToast?.cancel()
} catch (e: Exception) {
e.printStackTrace()
}
try {
val inflater = act.getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
val layout: View = inflater.inflate(
R.layout.toast,
act.findViewById<View>(R.id.toast_layout_root) as ViewGroup?
)
val text = layout.findViewById(R.id.text) as TextView
text.text = message.trim()
val toast = Toast(act)
toast.setGravity(Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM, 0, 5.toPx)
toast.duration = duration ?: Toast.LENGTH_SHORT
toast.view = layout
toast.show()
currentToast = toast
} catch (e: Exception) {
}
}
fun setLocale(context: Context?, languageCode: String?) {
if (context == null || languageCode == null) return
val locale = Locale(languageCode)
val resources: Resources = context.resources
val config = resources.configuration
Locale.setDefault(locale)
config.setLocale(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
context.createConfigurationContext(config)
resources.updateConfiguration(config, resources.displayMetrics)
}
fun Context.updateLocale() {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
val localeCode = settingsManager.getString(getString(R.string.locale_key), null)
setLocale(this, localeCode)
}
}
private fun enterPIPMode() {
if (!shouldShowPIPMode(canEnterPipMode) || !canShowPipMode) return
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
enterPictureInPictureMode(PictureInPictureParams.Builder().build())
} catch (e: Exception) {
enterPictureInPictureMode()
}
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
enterPictureInPictureMode()
}
}
} catch (e: Exception) {
logError(e)
}
}
override fun onUserLeaveHint() {
super.onUserLeaveHint()
if (canEnterPipMode && canShowPipMode) {
enterPIPMode()
}
onUserLeaveHint(this)
}
override fun onBackPressed() {
@ -456,9 +204,15 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
if (VLC_REQUEST_CODE == requestCode) {
if (resultCode == RESULT_OK && data != null) {
val pos: Long =
data.getLongExtra(VLC_EXTRA_POSITION_OUT, -1) //Last position in media when player exited
data.getLongExtra(
VLC_EXTRA_POSITION_OUT,
-1
) //Last position in media when player exited
val dur: Long =
data.getLongExtra(VLC_EXTRA_DURATION_OUT, -1) //Last position in media when player exited
data.getLongExtra(
VLC_EXTRA_DURATION_OUT,
-1
) //Last position in media when player exited
val id = getKey<Int>(VLC_LAST_ID_KEY)
println("SET KEY $id at $pos / $dur")
if (dur > 0 && pos > 0) {
@ -486,6 +240,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
private fun handleAppIntent(intent: Intent?) {
if (intent == null) return
val str = intent.dataString
loadCache()
if (str != null) {
if (str.contains(appString)) {
for (api in OAuth2Apis) {
@ -513,37 +268,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
for (api in OAuth2accountApis) {
api.init()
}
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
val currentTheme = when (settingsManager.getString(getString(R.string.app_theme_key), "AmoledLight")) {
"Black" -> R.style.AppTheme
"Light" -> R.style.LightMode
"Amoled" -> R.style.AmoledMode
"AmoledLight" -> R.style.AmoledModeLight
else -> R.style.AppTheme
}
val currentOverlayTheme = when (settingsManager.getString(getString(R.string.primary_color_key), "Normal")) {
"Normal" -> R.style.OverlayPrimaryColorNormal
"Blue" -> R.style.OverlayPrimaryColorBlue
"Purple" -> R.style.OverlayPrimaryColorPurple
"Green" -> R.style.OverlayPrimaryColorGreen
"GreenApple" -> R.style.OverlayPrimaryColorGreenApple
"Red" -> R.style.OverlayPrimaryColorRed
"Banana" -> R.style.OverlayPrimaryColorBanana
"Party" -> R.style.OverlayPrimaryColorParty
else -> R.style.OverlayPrimaryColorNormal
}
theme.applyStyle(currentTheme, true)
theme.applyStyle(currentOverlayTheme, true)
theme.applyStyle(
R.style.LoadedStyle,
true
) // THEME IS SET BEFORE VIEW IS CREATED TO APPLY THE THEME TO THE MAIN VIEW
loadThemes(this)
updateLocale()
app.initClient(this)
super.onCreate(savedInstanceState)
@ -565,12 +290,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
// val navView: BottomNavigationView = findViewById(R.id.nav_view)
//https://stackoverflow.com/questions/52594181/how-to-know-if-user-has-disabled-picture-in-picture-feature-permission
//https://developer.android.com/guide/topics/ui/picture-in-picture
canShowPipMode =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && // OS SUPPORT
packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) && // HAS FEATURE, MIGHT BE BLOCKED DUE TO POWER DRAIN
hasPIPPermission() // CHECK IF FEATURE IS ENABLED IN SETTINGS
CommonActivity.init(this)
val navController = findNavController(R.id.nav_host_fragment)
@ -588,6 +309,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
navController.addOnDestinationChangedListener { _, destination, _ ->
updateNavBar(destination)
}
loadCache()
/*nav_view.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
@ -721,6 +443,14 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
APIRepository.dubStatusActive = getApiDubstatusSettings()
try {
// this ensures that no unnecessary space is taken
loadCache()
File(filesDir, "exoplayer").deleteRecursively() // old cache
File(cacheDir, "exoplayer").deleteOnExit() // current cache
} catch (e: Exception) {
logError(e)
}
/*
val relativePath = (Environment.DIRECTORY_DOWNLOADS) + File.separatorChar

View file

@ -2,8 +2,8 @@ package com.lagradost.cloudstream3.animeproviders
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.extractorApis
import com.lagradost.cloudstream3.utils.getQualityFromName
import com.lagradost.cloudstream3.utils.loadExtractor
import org.jsoup.Jsoup
import java.util.*
@ -107,8 +107,11 @@ class GogoanimeProvider : MainAPI() {
this.name,
TvType.Anime,
it.selectFirst("img").attr("src"),
it.selectFirst(".released")?.text()?.split(":")?.getOrNull(1)?.trim()?.toIntOrNull(),
if (it.selectFirst(".name").text().contains("Dub")) EnumSet.of(DubStatus.Dubbed) else EnumSet.of(
it.selectFirst(".released")?.text()?.split(":")?.getOrNull(1)?.trim()
?.toIntOrNull(),
if (it.selectFirst(".name").text()
.contains("Dub")
) EnumSet.of(DubStatus.Dubbed) else EnumSet.of(
DubStatus.Subbed
),
)
@ -191,22 +194,20 @@ class GogoanimeProvider : MainAPI() {
}
}
private fun extractVideos(uri: String): List<ExtractorLink> {
val html = app.get(uri).text
val doc = Jsoup.parse(html)
private fun extractVideos(uri: String, callback: (ExtractorLink) -> Unit) {
val doc = app.get(uri).document
val iframe = fixUrlNull(doc.selectFirst("div.play-video > iframe").attr("src")) ?: return
val iframe = "https:" + doc.selectFirst("div.play-video > iframe").attr("src")
val link = iframe.replace("streaming.php", "download")
val page = app.get(link, headers = mapOf("Referer" to iframe))
val pageDoc = Jsoup.parse(page.text)
return pageDoc.select(".dowload > a").pmap {
page.document.select(".dowload > a").pmap {
if (it.hasAttr("download")) {
val qual = if (it.text()
.contains("HDP")
) "1080" else qualityRegex.find(it.text())?.destructured?.component1().toString()
listOf(
callback(
ExtractorLink(
"Gogoanime",
if (qual == "null") "Gogoanime" else "Gogoanime - " + qual + "p",
@ -218,16 +219,18 @@ class GogoanimeProvider : MainAPI() {
)
} else {
val url = it.attr("href")
val extractorLinks = ArrayList<ExtractorLink>()
for (api in extractorApis) {
if (url.startsWith(api.mainUrl)) {
extractorLinks.addAll(api.getSafeUrl(url) ?: listOf())
break
}
}
extractorLinks
loadExtractor(url, null, callback)
}
}
val streamingResponse = app.get(iframe, headers = mapOf("Referer" to iframe))
streamingResponse.document.select(".list-server-items > .linkserver")
?.forEach { element ->
val status = element.attr("data-status") ?: return@forEach
if (status != "1") return@forEach
val data = element.attr("data-video") ?: return@forEach
loadExtractor(data, streamingResponse.url, callback)
}
}.flatten()
}
override fun loadLinks(
@ -236,9 +239,7 @@ class GogoanimeProvider : MainAPI() {
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
for (source in extractVideos(data)) {
callback.invoke(source)
}
extractVideos(data, callback)
return true
}
}

View file

@ -21,10 +21,12 @@ import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.media.RemoteMediaClient
import com.google.android.gms.cast.framework.media.uicontroller.UIController
import com.google.android.gms.cast.framework.media.widget.ExpandedControllerActivity
import com.lagradost.cloudstream3.APIHolder.getApiFromName
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.sortSubs
import com.lagradost.cloudstream3.sortUrls
import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator
import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.utils.CastHelper.awaitLinks
import com.lagradost.cloudstream3.utils.CastHelper.getMediaInfo
@ -86,10 +88,11 @@ data class MetadataHolder(
val currentEpisodeIndex: Int,
val episodes: List<ResultEpisode>,
val currentLinks: List<ExtractorLink>,
val currentSubtitles: List<SubtitleFile>
val currentSubtitles: List<SubtitleData>
)
class SelectSourceController(val view: ImageView, val activity: ControllerActivity) : UIController() {
class SelectSourceController(val view: ImageView, val activity: ControllerActivity) :
UIController() {
private val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
@ -106,17 +109,22 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
remoteMediaClient?.mediaInfo?.mediaTracks?.filter { it.type == MediaTrack.TYPE_TEXT }
?: ArrayList()
val bottomSheetDialogBuilder = AlertDialog.Builder(view.context, R.style.AlertDialogCustomBlack)
val bottomSheetDialogBuilder =
AlertDialog.Builder(view.context, R.style.AlertDialogCustomBlack)
bottomSheetDialogBuilder.setView(R.layout.sort_bottom_sheet)
val bottomSheetDialog = bottomSheetDialogBuilder.create()
bottomSheetDialog.show()
// bottomSheetDialog.setContentView(R.layout.sort_bottom_sheet)
val providerList = bottomSheetDialog.findViewById<ListView>(R.id.sort_providers)!!
val subtitleList = bottomSheetDialog.findViewById<ListView>(R.id.sort_subtitles)!!
val providerList =
bottomSheetDialog.findViewById<ListView>(R.id.sort_providers)!!
val subtitleList =
bottomSheetDialog.findViewById<ListView>(R.id.sort_subtitles)!!
if (subTracks.isEmpty()) {
bottomSheetDialog.findViewById<LinearLayout>(R.id.sort_subtitles_holder)?.visibility = GONE
bottomSheetDialog.findViewById<LinearLayout>(R.id.sort_subtitles_holder)?.visibility =
GONE
} else {
val arrayAdapter = ArrayAdapter<String>(view.context, R.layout.sort_bottom_single_choice)
val arrayAdapter =
ArrayAdapter<String>(view.context, R.layout.sort_bottom_single_choice)
arrayAdapter.add(view.context.getString(R.string.no_subtitles))
arrayAdapter.addAll(subTracks.mapNotNull { it.name })
@ -168,7 +176,8 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
val sortingMethods = items.map { it.name }.toTypedArray()
val sotringIndex = items.indexOfFirst { it.url == contentUrl }
val arrayAdapter = ArrayAdapter<String>(view.context, R.layout.sort_bottom_single_choice)
val arrayAdapter =
ArrayAdapter<String>(view.context, R.layout.sort_bottom_single_choice)
arrayAdapter.addAll(sortingMethods.toMutableList())
providerList.choiceMode = AbsListView.CHOICE_MODE_SINGLE
@ -196,7 +205,9 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
try { // THIS IS VERY IMPORTANT BECAUSE WE NEVER WANT TO AUTOLOAD THE NEXT EPISODE
val currentIdIndex = remoteMediaClient?.getItemIndex()
val nextId = remoteMediaClient?.mediaQueue?.itemIds?.get(currentIdIndex?.plus(1) ?: 0)
val nextId = remoteMediaClient?.mediaQueue?.itemIds?.get(
currentIdIndex?.plus(1) ?: 0
)
if (currentIdIndex == null && nextId != null) {
awaitLinks(
remoteMediaClient?.queueInsertAndPlayItem(
@ -256,26 +267,29 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
thread {
val index = meta.currentEpisodeIndex + 1
val epData = meta.episodes[index]
val links = ArrayList<ExtractorLink>()
val subs = ArrayList<SubtitleFile>()
val currentLinks = mutableSetOf<ExtractorLink>()
val currentSubs = mutableSetOf<SubtitleData>()
val isSuccessful =
APIRepository(getApiFromName(meta.apiName)).loadLinks(epData.data, true, { subtitleFile ->
if (!subs.any { it.url == subtitleFile.url }) {
subs.add(subtitleFile)
}
}) { link ->
if (!links.any { it.url == link.url }) {
links.add(link)
}
}
val generator = RepoLinkGenerator(listOf(epData))
if (isSuccessful) {
val sorted = sortUrls(links)
if (sorted.isNotEmpty()) {
val isSuccessful = normalSafeApiCall {
generator.generateLinks(false, true,
{
it.first?.let { link ->
currentLinks.add(link)
}
}, {
currentSubs.add(it)
})
}
val sortedLinks = sortUrls(currentLinks)
val sortedSubs = sortSubs(currentSubs)
if (isSuccessful == true) {
if (currentLinks.isNotEmpty()) {
val jsonCopy = meta.copy(
currentLinks = sorted,
currentSubtitles = subs,
currentLinks = sortedLinks,
currentSubtitles = sortedSubs,
currentEpisodeIndex = index
)
@ -287,7 +301,7 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
jsonCopy,
0,
done,
subs
sortedSubs
)
/*fun loadIndex(index: Int) {
@ -367,9 +381,21 @@ class ControllerActivity : ExpandedControllerActivity() {
val skipBackButton: ImageView = getButtonImageViewAt(1)
val skipForwardButton: ImageView = getButtonImageViewAt(2)
val skipOpButton: ImageView = getButtonImageViewAt(3)
uiMediaController.bindViewToUIController(sourcesButton, SelectSourceController(sourcesButton, this))
uiMediaController.bindViewToUIController(skipBackButton, SkipTimeController(skipBackButton, false))
uiMediaController.bindViewToUIController(skipForwardButton, SkipTimeController(skipForwardButton, true))
uiMediaController.bindViewToUIController(skipOpButton, SkipNextEpisodeController(skipOpButton))
uiMediaController.bindViewToUIController(
sourcesButton,
SelectSourceController(sourcesButton, this)
)
uiMediaController.bindViewToUIController(
skipBackButton,
SkipTimeController(skipBackButton, false)
)
uiMediaController.bindViewToUIController(
skipForwardButton,
SkipTimeController(skipForwardButton, true)
)
uiMediaController.bindViewToUIController(
skipOpButton,
SkipNextEpisodeController(skipOpButton)
)
}
}

View file

@ -4,14 +4,15 @@ import android.app.Activity
import android.content.DialogInterface
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.player.PlayerFragment
import com.lagradost.cloudstream3.ui.player.UriData
import com.lagradost.cloudstream3.ui.player.DownloadFileGenerator
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
import com.lagradost.cloudstream3.utils.ExtractorUri
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import com.lagradost.cloudstream3.utils.VideoDownloadManager
@ -49,7 +50,7 @@ object DownloadButtonSetup {
.setPositiveButton(R.string.delete, dialogClickListener)
.setNegativeButton(R.string.cancel, dialogClickListener)
.show()
} catch (e : Exception) {
} catch (e: Exception) {
logError(e)
// ye you somehow fucked up formatting did you?
}
@ -81,40 +82,71 @@ object DownloadButtonSetup {
DOWNLOAD_ACTION_LONG_CLICK -> {
activity?.let { act ->
val length =
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(act, click.data.id)?.fileLength
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
act,
click.data.id
)?.fileLength
?: 0
if (length > 0) {
MainActivity.showToast(act, R.string.delete, Toast.LENGTH_LONG)
showToast(act, R.string.delete, Toast.LENGTH_LONG)
} else {
MainActivity.showToast(act, R.string.download, Toast.LENGTH_LONG)
showToast(act, R.string.download, Toast.LENGTH_LONG)
}
}
}
DOWNLOAD_ACTION_PLAY_FILE -> {
activity?.let { act ->
val info =
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(act, click.data.id)
?: return
val keyInfo = act.getKey<VideoDownloadManager.DownloadedFileInfo>(
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
act,
click.data.id
) ?: return
val keyInfo = getKey<VideoDownloadManager.DownloadedFileInfo>(
VideoDownloadManager.KEY_DOWNLOAD_INFO,
click.data.id.toString()
) ?: return
val parent = getKey<VideoDownloadHelper.DownloadHeaderCached>(
DOWNLOAD_HEADER_CACHE,
click.data.parentId.toString()
) ?: return
act.navigate(
R.id.global_to_navigation_player, PlayerFragment.newInstance(
UriData(
info.path.toString(),
keyInfo.basePath,
keyInfo.relativePath,
keyInfo.displayName,
click.data.parentId,
click.data.id,
headerName ?: "null",
if (click.data.episode <= 0) null else click.data.episode,
click.data.season
),
getViewPos(click.data.id)?.position ?: 0
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
DownloadFileGenerator(
listOf(
ExtractorUri(
uri = info.path,
id = click.data.id,
parentId = click.data.parentId,
name = act.getString(R.string.downloaded_file), //click.data.name ?: keyInfo.displayName
season = click.data.season,
episode = click.data.episode,
headerName = parent.name,
tvType = parent.type,
basePath = keyInfo.basePath,
displayName = keyInfo.displayName,
relativePath = keyInfo.relativePath,
)
),
0
)
)
//R.id.global_to_navigation_player, PlayerFragment.newInstance(
// UriData(
// info.path.toString(),
// keyInfo.basePath,
// keyInfo.relativePath,
// keyInfo.displayName,
// click.data.parentId,
// click.data.id,
// headerName ?: "null",
// if (click.data.episode <= 0) null else click.data.episode,
// click.data.season
// ),
// getViewPos(click.data.id)?.position ?: 0
//)
)
}
}

View file

@ -177,8 +177,7 @@ class HomeViewModel : ViewModel() {
logError(e)
}
}
else -> {
}
else -> Unit
}
_page.postValue(data)
} else {

View file

@ -0,0 +1,346 @@
package com.lagradost.cloudstream3.ui.player
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.drawable.AnimatedImageDrawable
import android.graphics.drawable.AnimatedVectorDrawable
import android.media.metrics.PlaybackErrorEvent
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.Toast
import androidx.annotation.LayoutRes
import androidx.annotation.StringRes
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.PlaybackException
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
import com.google.android.exoplayer2.ui.SubtitleView
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity.canEnterPipMode
import com.lagradost.cloudstream3.CommonActivity.isInPIPMode
import com.lagradost.cloudstream3.CommonActivity.keyEventListener
import com.lagradost.cloudstream3.CommonActivity.playerEventListener
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.AppUtils.requestLocalAudioFocus
import com.lagradost.cloudstream3.utils.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
import kotlinx.android.synthetic.main.fragment_player.*
import kotlinx.android.synthetic.main.player_custom_layout.*
enum class PlayerResize(@StringRes val nameRes: Int) {
Fit(R.string.resize_fit),
Fill(R.string.resize_fill),
Zoom(R.string.resize_zoom),
}
// when the player should switch skip op to next episode
const val SKIP_OP_VIDEO_PERCENTAGE = 50
// when the player should preload the next episode for faster loading
const val PRELOAD_NEXT_EPISODE_PERCENTAGE = 80
// when the player should mark the episode as watched and resume watching the next
const val NEXT_WATCH_EPISODE_PERCENTAGE = 95
abstract class AbstractPlayerFragment(
@LayoutRes val layout: Int,
val player: IPlayer = CS3IPlayer()
) : Fragment() {
var resizeMode: Int = 0
var subStyle: SaveCaptionStyle? = null
var subView: SubtitleView? = null
var isBuffering = true
open fun nextEpisode() {
throw NotImplementedError()
}
open fun prevEpisode() {
throw NotImplementedError()
}
open fun playerPositionChanged(posDur: Pair<Long, Long>) {
throw NotImplementedError()
}
open fun playerDimensionsLoaded(widthHeight : Pair<Int, Int>) {
throw NotImplementedError()
}
private fun updateIsPlaying(playing: Pair<CSPlayerLoading, CSPlayerLoading>) {
val (wasPlaying, isPlaying) = playing
val isPlayingRightNow = CSPlayerLoading.IsPlaying == isPlaying
isBuffering = CSPlayerLoading.IsBuffering == isPlaying
if (isBuffering) {
player_pause_play_holder_holder?.isVisible = false
player_buffering?.isVisible = true
} else {
player_pause_play_holder_holder?.isVisible = true
player_buffering?.isVisible = false
if (wasPlaying != isPlaying) {
player_pause_play?.setImageResource(if (isPlayingRightNow) R.drawable.play_to_pause else R.drawable.pause_to_play)
val drawable = player_pause_play?.drawable
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
if (drawable is AnimatedImageDrawable) {
drawable.start()
}
}
if (drawable is AnimatedVectorDrawable) {
drawable.start()
}
} else {
player_pause_play?.setImageResource(if (isPlayingRightNow) R.drawable.netflix_pause else R.drawable.netflix_play)
}
}
canEnterPipMode = isPlayingRightNow
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
activity?.let { act ->
PlayerPipHelper.updatePIPModeActions(act, isPlayingRightNow)
}
}
}
private var pipReceiver: BroadcastReceiver? = null
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
try {
isInPIPMode = isInPictureInPictureMode
if (isInPictureInPictureMode) {
// Hide the full-screen UI (controls, etc.) while in picture-in-picture mode.
player_holder.alpha = 0f
pipReceiver = object : BroadcastReceiver() {
override fun onReceive(
context: Context,
intent: Intent,
) {
if (ACTION_MEDIA_CONTROL != intent.action) {
return
}
player.handleEvent(
CSPlayerEvent.values()[intent.getIntExtra(
EXTRA_CONTROL_TYPE,
0
)]
)
}
}
val filter = IntentFilter()
filter.addAction(
ACTION_MEDIA_CONTROL
)
activity?.registerReceiver(pipReceiver, filter)
val isPlaying = player.getIsPlaying()
val isPlayingValue =
if (isPlaying) CSPlayerLoading.IsPlaying else CSPlayerLoading.IsPaused
updateIsPlaying(Pair(isPlayingValue, isPlayingValue))
} else {
// Restore the full-screen UI.
player_holder.alpha = 1f
pipReceiver?.let {
activity?.unregisterReceiver(it)
}
activity?.hideSystemUI()
this.view?.let { UIHelper.hideKeyboard(it) }
}
} catch (e: Exception) {
logError(e)
}
}
open fun nextMirror() {
throw NotImplementedError()
}
private fun playerError(exception: Exception) {
when (exception) {
is PlaybackException -> {
val msg = exception.message ?: ""
val errorName = exception.errorCodeName
when (val code = exception.errorCode) {
PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND, PlaybackException.ERROR_CODE_IO_NO_PERMISSION, PlaybackException.ERROR_CODE_IO_UNSPECIFIED -> {
showToast(
activity,
"${getString(R.string.source_error)}\n$errorName ($code)\n$msg",
Toast.LENGTH_SHORT
)
nextMirror()
}
PlaybackException.ERROR_CODE_REMOTE_ERROR, PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS, PlaybackException.ERROR_CODE_TIMEOUT, PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, PlaybackException.ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE -> {
showToast(
activity,
"${getString(R.string.remote_error)}\n$errorName ($code)\n$msg",
Toast.LENGTH_SHORT
)
nextMirror()
}
PlaybackException.ERROR_CODE_DECODING_FAILED, PlaybackErrorEvent.ERROR_AUDIO_TRACK_INIT_FAILED, PlaybackErrorEvent.ERROR_AUDIO_TRACK_OTHER, PlaybackException.ERROR_CODE_AUDIO_TRACK_WRITE_FAILED, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED, PlaybackException.ERROR_CODE_DECODER_QUERY_FAILED -> {
showToast(
activity,
"${getString(R.string.render_error)}\n$errorName ($code)\n$msg",
Toast.LENGTH_SHORT
)
nextMirror()
}
else -> {
showToast(
activity,
"${getString(R.string.unexpected_error)}\n$errorName ($code)\n$msg",
Toast.LENGTH_SHORT
)
}
}
}
else -> {
showToast(activity, exception.message, Toast.LENGTH_SHORT)
}
}
}
private fun requestAudioFocus() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
activity?.requestLocalAudioFocus(AppUtils.getFocusRequest())
}
}
private fun onSubStyleChanged(style: SaveCaptionStyle) {
if (player is CS3IPlayer) {
player.updateSubtitleStyle(style)
}
}
private fun playerUpdated(player: Any?) {
if (player is ExoPlayer) {
player_view?.player = player
player_view?.performClick()
}
}
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
resizeMode = getKey(RESIZE_MODE_KEY) ?: 0
resize(resizeMode, false)
player.initCallbacks(
playerUpdated = ::playerUpdated,
updateIsPlaying = ::updateIsPlaying,
playerError = ::playerError,
requestAutoFocus = ::requestAudioFocus,
nextEpisode = ::nextEpisode,
prevEpisode = ::prevEpisode,
playerPositionChanged = ::playerPositionChanged,
playerDimensionsLoaded = ::playerDimensionsLoaded,
requestedListeningPercentages = listOf(
SKIP_OP_VIDEO_PERCENTAGE,
PRELOAD_NEXT_EPISODE_PERCENTAGE,
NEXT_WATCH_EPISODE_PERCENTAGE,
)
)
if (player is CS3IPlayer) {
subView = player_view?.findViewById(R.id.exo_subtitles)
subStyle = SubtitlesFragment.getCurrentSavedStyle()
player.initSubtitles(subView, subtitle_holder, subStyle)
SubtitlesFragment.applyStyleEvent += ::onSubStyleChanged
}
/*context?.let { ctx ->
player.loadPlayer(
ctx,
false,
ExtractorLink(
"idk",
"bunny",
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
"",
Qualities.P720.value,
false
),
)
}*/
}
override fun onDestroy() {
playerEventListener = null
keyEventListener = null
SubtitlesFragment.applyStyleEvent -= ::onSubStyleChanged
// simply resets brightness and notch settings that might have been overridden
val lp = activity?.window?.attributes
lp?.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
lp?.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
}
activity?.window?.attributes = lp
super.onDestroy()
}
fun nextResize() {
resizeMode = (resizeMode + 1) % PlayerResize.values().size
resize(resizeMode, true)
}
fun resize(resize: Int, showToast: Boolean) {
resize(PlayerResize.values()[resize], showToast)
}
fun resize(resize: PlayerResize, showToast: Boolean) {
setKey(RESIZE_MODE_KEY, resize.ordinal)
val type = when (resize) {
PlayerResize.Fill -> AspectRatioFrameLayout.RESIZE_MODE_FIT
PlayerResize.Fit -> AspectRatioFrameLayout.RESIZE_MODE_FILL
PlayerResize.Zoom -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
}
player_view?.resizeMode = type
exo_play?.setOnClickListener {
player.handleEvent(CSPlayerEvent.Play)
}
exo_pause?.setOnClickListener {
player.handleEvent(CSPlayerEvent.Pause)
}
if (showToast)
showToast(activity, resize.nameRes, Toast.LENGTH_SHORT)
}
override fun onStop() {
player.onStop()
super.onStop()
}
override fun onResume() {
context?.let { ctx ->
player.onResume(ctx)
}
super.onResume()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(layout, container, false)
}
}

View file

@ -0,0 +1,668 @@
package com.lagradost.cloudstream3.ui.player
import android.content.Context
import android.net.Uri
import android.os.Looper
import android.util.Log
import android.widget.FrameLayout
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.database.StandaloneDatabaseProvider
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
import com.google.android.exoplayer2.source.MergingMediaSource
import com.google.android.exoplayer2.source.SingleSampleMediaSource
import com.google.android.exoplayer2.text.Cue
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.trackselection.TrackSelector
import com.google.android.exoplayer2.ui.SubtitleView
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
import com.google.android.exoplayer2.upstream.cache.CacheDataSource
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor
import com.google.android.exoplayer2.upstream.cache.SimpleCache
import com.google.android.exoplayer2.util.MimeTypes
import com.lagradost.cloudstream3.USER_AGENT
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.ExtractorUri
import java.io.File
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSession
const val TAG = "CS3ExoPlayer"
class CS3IPlayer : IPlayer {
private var isPlaying = false
private var exoPlayer: ExoPlayer? = null
/** Cache */
private val cacheSize = 300L * 1024L * 1024L // 300 mb TODO MAKE SETTING
private val seekActionTime = 30000L
private var ignoreSSL: Boolean = true
private var simpleCache: SimpleCache? = null
private var playBackSpeed: Float = 1.0f
private var lastMuteVolume: Float = 1.0f
private var currentLink: ExtractorLink? = null
private var currentDownloadedFile: ExtractorUri? = null
private var hasUsedFirstRender = false
private var currentWindow: Int = 0
private var playbackPosition: Long = 0
private val subtitleHelper = PlayerSubtitleHelper()
override fun getDuration(): Long? = exoPlayer?.duration
override fun getPosition(): Long? = exoPlayer?.currentPosition
override fun getIsPlaying(): Boolean = isPlaying
override fun getPlaybackSpeed(): Float = playBackSpeed
/**
* Tracks reported to be used by exoplayer, since sometimes it has a mind of it's own when selecting subs.
* String = lowercase language as set by .setLanguage("_$langId")
* Boolean = if it's active
* */
private var exoPlayerSelectedTracks = listOf<Pair<String, Boolean>>()
/** isPlaying */
private var updateIsPlaying: ((Pair<CSPlayerLoading, CSPlayerLoading>) -> Unit)? = null
private var requestAutoFocus: (() -> Unit)? = null
private var playerError: ((Exception) -> Unit)? = null
/** width x height */
private var playerDimensionsLoaded: ((Pair<Int, Int>) -> Unit)? = null
/** used for playerPositionChanged */
private var requestedListeningPercentages: List<Int>? = null
/** Fired when seeking the player or on requestedListeningPercentages,
* used to make things appear on que
* position, duration */
private var playerPositionChanged: ((Pair<Long, Long>) -> Unit)? = null
private var nextEpisode: (() -> Unit)? = null
private var prevEpisode: (() -> Unit)? = null
private var playerUpdated: ((Any?) -> Unit)? = null
override fun initCallbacks(
playerUpdated: (Any?) -> Unit,
updateIsPlaying: ((Pair<CSPlayerLoading, CSPlayerLoading>) -> Unit)?,
requestAutoFocus: (() -> Unit)?,
playerError: ((Exception) -> Unit)?,
playerDimensionsLoaded: ((Pair<Int, Int>) -> Unit)?,
requestedListeningPercentages: List<Int>?,
playerPositionChanged: ((Pair<Long, Long>) -> Unit)?,
nextEpisode: (() -> Unit)?,
prevEpisode: (() -> Unit)?
) {
this.playerUpdated = playerUpdated
this.updateIsPlaying = updateIsPlaying
this.requestAutoFocus = requestAutoFocus
this.playerError = playerError
this.playerDimensionsLoaded = playerDimensionsLoaded
this.requestedListeningPercentages = requestedListeningPercentages
this.playerPositionChanged = playerPositionChanged
this.nextEpisode = nextEpisode
this.prevEpisode = prevEpisode
}
fun initSubtitles(subView: SubtitleView?, subHolder: FrameLayout?, style: SaveCaptionStyle?) {
subtitleHelper.initSubtitles(subView, subHolder, style)
}
override fun loadPlayer(
context: Context,
sameEpisode: Boolean,
link: ExtractorLink?,
data: ExtractorUri?,
startPosition: Long?,
subtitles: Set<SubtitleData>
) {
Log.i(TAG, "loadPlayer")
if (sameEpisode) {
saveData()
} else {
currentSubtitles = null
playbackPosition = 0
}
startPosition?.let {
playbackPosition = it
}
// we want autoplay because of TV and UX
isPlaying = true
// release the current exoplayer and cache
releasePlayer()
if (link != null) {
loadOnlinePlayer(context, link)
} else if (data != null) {
loadOfflinePlayer(context, data)
}
}
override fun setActiveSubtitles(subtitles: Set<SubtitleData>) {
Log.i(TAG, "setActiveSubtitles ${subtitles.size}")
subtitleHelper.setAllSubtitles(subtitles)
}
var currentSubtitles : SubtitleData? = null
override fun setPreferredSubtitles(subtitle: SubtitleData?): Boolean {
Log.i(TAG,"setPreferredSubtitles init $subtitle")
currentSubtitles = subtitle
return (exoPlayer?.trackSelector as? DefaultTrackSelector?)?.let { trackSelector ->
val name = subtitle?.name
if (name.isNullOrBlank()) {
trackSelector.setParameters(
trackSelector.buildUponParameters()
.setPreferredTextLanguage(null)
)
} else {
when (subtitleHelper.subtitleStatus(subtitle)) {
SubtitleStatus.REQUIRES_RELOAD -> {
Log.i(TAG,"setPreferredSubtitles REQUIRES_RELOAD")
return@let true
// reloadPlayer(context)
}
SubtitleStatus.IS_ACTIVE -> {
Log.i(TAG,"setPreferredSubtitles IS_ACTIVE")
trackSelector.setParameters(
trackSelector.buildUponParameters()
.setPreferredTextLanguage("_$name")
)
}
SubtitleStatus.NOT_FOUND -> {
// not found
Log.i(TAG,"setPreferredSubtitles NOT_FOUND")
return@let true
}
}
}
return false
} ?: false
}
override fun getCurrentPreferredSubtitle(): SubtitleData? {
return subtitleHelper.getAllSubtitles().firstOrNull { sub ->
exoPlayerSelectedTracks.any {
// The replace is needed as exoplayer translates _ to -
// Also we prefix the languages with _
it.second && it.first.replace("-", "") .equals(
sub.name.replace("-", ""),
ignoreCase = true
)
}
}
}
override fun updateSubtitleStyle(style: SaveCaptionStyle) {
subtitleHelper.setSubStyle(style)
}
private fun saveData() {
Log.i(TAG, "saveData")
updatedTime()
exoPlayer?.let { exo ->
playbackPosition = exo.currentPosition
currentWindow = exo.currentWindowIndex
isPlaying = exo.isPlaying
}
}
private fun releasePlayer() {
Log.i(TAG, "releasePlayer")
updatedTime()
exoPlayer?.release()
simpleCache?.release()
exoPlayer = null
simpleCache = null
}
override fun onStop() {
Log.i(TAG, "onStop")
saveData()
exoPlayer?.pause()
releasePlayer()
}
override fun onPause() {
Log.i(TAG, "onPause")
saveData()
exoPlayer?.pause()
releasePlayer()
}
override fun onResume(context: Context) {
if (exoPlayer == null)
reloadPlayer(context)
}
override fun release() {
releasePlayer()
}
override fun setPlaybackSpeed(speed: Float) {
exoPlayer?.setPlaybackSpeed(speed)
playBackSpeed = speed
}
companion object {
private fun createOnlineSource(link: ExtractorLink): DataSource.Factory {
return DefaultHttpDataSource.Factory().apply {
setUserAgent(USER_AGENT)
val headers = mapOf(
"referer" to link.referer,
"accept" to "*/*",
"sec-ch-ua" to "\"Chromium\";v=\"91\", \" Not;A Brand\";v=\"99\"",
"sec-ch-ua-mobile" to "?0",
"sec-fetch-user" to "?1",
"sec-fetch-mode" to "navigate",
"sec-fetch-dest" to "video"
) + link.headers // Adds the headers from the provider, e.g Authorization
setDefaultRequestProperties(headers)
//https://stackoverflow.com/questions/69040127/error-code-io-bad-http-status-exoplayer-android
setAllowCrossProtocolRedirects(true)
}
}
private fun Context.createOfflineSource(): DataSource.Factory {
return DefaultDataSourceFactory(this, USER_AGENT)
}
private fun getSubSources(
onlineSourceFactory: DataSource.Factory?,
offlineSourceFactory: DataSource.Factory?,
subHelper: PlayerSubtitleHelper,
): Pair<List<SingleSampleMediaSource>, List<SubtitleData>> {
val activeSubtitles = ArrayList<SubtitleData>()
val subSources = subHelper.getAllSubtitles().mapNotNull { sub ->
val subConfig = MediaItem.SubtitleConfiguration.Builder(Uri.parse(sub.url))
.setMimeType(sub.mimeType)
.setLanguage("_${sub.name}")
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
.build()
when (sub.origin) {
SubtitleOrigin.DOWNLOADED_FILE -> {
if (offlineSourceFactory != null) {
activeSubtitles.add(sub)
SingleSampleMediaSource.Factory(offlineSourceFactory)
.createMediaSource(subConfig, C.TIME_UNSET)
} else {
null
}
}
SubtitleOrigin.URL -> {
if (onlineSourceFactory != null) {
activeSubtitles.add(sub)
SingleSampleMediaSource.Factory(onlineSourceFactory)
.createMediaSource(subConfig, C.TIME_UNSET)
} else {
null
}
}
SubtitleOrigin.OPEN_SUBTITLES -> {
// TODO
throw NotImplementedError()
}
}
}
println("SUBSRC: ${subSources.size} activeSubtitles : ${activeSubtitles.size} of ${subHelper.getAllSubtitles().size} ")
return Pair(subSources, activeSubtitles)
}
private fun getCache(context: Context, cacheSize: Long): SimpleCache? {
return try {
val databaseProvider = StandaloneDatabaseProvider(context)
SimpleCache(
File(
context.cacheDir, "exoplayer"
).also { it.deleteOnExit() }, // Ensures always fresh file
LeastRecentlyUsedCacheEvictor(cacheSize),
databaseProvider
)
} catch (e: Exception) {
logError(e)
null
}
}
private fun getMediaItemBuilder(mimeType: String):
MediaItem.Builder {
return MediaItem.Builder()
//Replace needed for android 6.0.0 https://github.com/google/ExoPlayer/issues/5983
.setMimeType(mimeType)
}
private fun getMediaItem(mimeType: String, uri: Uri): MediaItem {
return getMediaItemBuilder(mimeType).setUri(uri).build()
}
private fun getMediaItem(mimeType: String, url: String): MediaItem {
return getMediaItemBuilder(mimeType).setUri(url).build()
}
private fun getTrackSelector(context: Context): TrackSelector {
val trackSelector = DefaultTrackSelector(context)
trackSelector.parameters = DefaultTrackSelector.ParametersBuilder(context)
// .setRendererDisabled(C.TRACK_TYPE_VIDEO, true)
.setRendererDisabled(C.TRACK_TYPE_TEXT, true)
.setDisabledTextTrackSelectionFlags(C.TRACK_TYPE_TEXT)
.clearSelectionOverrides()
.build()
return trackSelector
}
private fun buildExoPlayer(
context: Context,
mediaItem: MediaItem,
subSources: List<SingleSampleMediaSource>,
currentWindow: Int,
playbackPosition: Long,
playBackSpeed: Float,
playWhenReady: Boolean = true,
cacheFactory: CacheDataSource.Factory? = null,
trackSelector: TrackSelector? = null,
): ExoPlayer {
val exoPlayerBuilder =
ExoPlayer.Builder(context)
.setTrackSelector(trackSelector ?: getTrackSelector(context))
val videoMediaSource =
(if (cacheFactory == null) DefaultMediaSourceFactory(context) else DefaultMediaSourceFactory(
cacheFactory
)).createMediaSource(
mediaItem
)
return exoPlayerBuilder.build().apply {
setPlayWhenReady(playWhenReady)
seekTo(currentWindow, playbackPosition)
setMediaSource(
MergingMediaSource(
videoMediaSource, *subSources.toTypedArray()
),
playbackPosition
)
setHandleAudioBecomingNoisy(true)
setPlaybackSpeed(playBackSpeed)
}
}
}
fun updatedTime() {
val position = exoPlayer?.currentPosition
val duration = exoPlayer?.contentDuration
if (duration != null && position != null) {
playerPositionChanged?.invoke(Pair(position, duration))
}
}
override fun seekTime(time: Long) {
exoPlayer?.seekTime(time)
}
override fun seekTo(time: Long) {
updatedTime()
exoPlayer?.seekTo(time)
}
private fun ExoPlayer.seekTime(time: Long) {
updatedTime()
seekTo(currentPosition + time)
}
override fun handleEvent(event: CSPlayerEvent) {
Log.i(TAG, "handleEvent ${event.name}")
try {
exoPlayer?.apply {
when (event) {
CSPlayerEvent.Play -> {
play()
}
CSPlayerEvent.Pause -> {
pause()
}
CSPlayerEvent.ToggleMute -> {
if (volume <= 0) {
//is muted
volume = lastMuteVolume
} else {
// is not muted
lastMuteVolume = volume
volume = 0f
}
}
CSPlayerEvent.PlayPauseToggle -> {
if (isPlaying) {
pause()
} else {
play()
}
}
CSPlayerEvent.SeekForward -> seekTime(seekActionTime)
CSPlayerEvent.SeekBack -> seekTime(-seekActionTime)
CSPlayerEvent.NextEpisode -> nextEpisode?.invoke()
CSPlayerEvent.PrevEpisode -> prevEpisode?.invoke()
}
}
} catch (e: Exception) {
Log.e(TAG, "handleEvent error", e)
playerError?.invoke(e)
}
}
private fun loadExo(
context: Context,
mediaItem: MediaItem,
subSources: List<SingleSampleMediaSource>,
cacheFactory: CacheDataSource.Factory? = null
) {
Log.i(TAG, "loadExo")
try {
hasUsedFirstRender = false
// ye this has to be a val for whatever reason
// this makes no sense
exoPlayer = buildExoPlayer(
context,
mediaItem,
subSources,
currentWindow,
playbackPosition,
playBackSpeed,
playWhenReady = isPlaying, // this keep the current state of the player
cacheFactory = cacheFactory
)
playerUpdated?.invoke(exoPlayer)
exoPlayer?.prepare()
exoPlayer?.let { exo ->
updateIsPlaying?.invoke(
Pair(
CSPlayerLoading.IsBuffering,
CSPlayerLoading.IsBuffering
)
)
isPlaying = exo.isPlaying
}
exoPlayer?.addListener(object : Player.Listener {
/**
* Records the current used subtitle/track. Needed as exoplayer seems to have loose track language selection.
* */
override fun onTracksInfoChanged(tracksInfo: TracksInfo) {
exoPlayerSelectedTracks =
tracksInfo.trackGroupInfos.mapNotNull { it.trackGroup.getFormat(0).language?.let { lang -> lang to it.isSelected } }
super.onTracksInfoChanged(tracksInfo)
}
override fun onCues(cues: MutableList<Cue>) {
Log.i(TAG, "CUES: ${cues.size}")
if(cues.size > 0) {
Log.i(TAG, "CUES SAY: ${cues.first().text}")
}
super.onCues(cues)
}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
exoPlayer?.let { exo ->
updateIsPlaying?.invoke(
Pair(
if (isPlaying) CSPlayerLoading.IsPlaying else CSPlayerLoading.IsPaused,
if (playbackState == Player.STATE_BUFFERING) CSPlayerLoading.IsBuffering else if (exo.isPlaying) CSPlayerLoading.IsPlaying else CSPlayerLoading.IsPaused
)
)
isPlaying = exo.isPlaying
}
if (playWhenReady) {
when (playbackState) {
Player.STATE_READY -> {
requestAutoFocus?.invoke()
}
Player.STATE_ENDED -> {
handleEvent(CSPlayerEvent.NextEpisode)
}
Player.STATE_BUFFERING -> {
updatedTime()
}
Player.STATE_IDLE -> {
// IDLE
}
else -> Unit
}
}
}
override fun onPlayerError(error: PlaybackException) {
playerError?.invoke(error)
super.onPlayerError(error)
}
override fun onRenderedFirstFrame() {
updatedTime()
if (!hasUsedFirstRender) { // this insures that we only call this once per player load
Log.i(TAG, "Rendered first frame")
setPreferredSubtitles(currentSubtitles)
hasUsedFirstRender = true
val format = exoPlayer?.videoFormat
val width = format?.width
val height = format?.height
if (height != null && width != null) {
playerDimensionsLoaded?.invoke(Pair(width, height))
updatedTime()
exoPlayer?.apply {
requestedListeningPercentages?.forEach { percentage ->
createMessage { _, _ ->
updatedTime()
}
.setLooper(Looper.getMainLooper())
.setPosition( /* positionMs= */contentDuration * percentage / 100)
// .setPayload(customPayloadData)
.setDeleteAfterDelivery(false)
.send()
}
}
}
}
super.onRenderedFirstFrame()
}
})
} catch (e: Exception) {
Log.e(TAG, "loadExo error", e)
playerError?.invoke(e)
}
}
private fun loadOfflinePlayer(context: Context, data: ExtractorUri) {
Log.i(TAG, "loadOfflinePlayer")
try {
currentDownloadedFile = data
val mediaItem = getMediaItem(MimeTypes.VIDEO_MP4, data.uri)
val offlineSourceFactory = context.createOfflineSource()
val (subSources, activeSubtitles) = getSubSources(
offlineSourceFactory,
offlineSourceFactory,
subtitleHelper
)
subtitleHelper.setActiveSubtitles(activeSubtitles.toSet())
loadExo(context, mediaItem, subSources)
} catch (e: Exception) {
Log.e(TAG, "loadOfflinePlayer error", e)
playerError?.invoke(e)
}
}
private fun loadOnlinePlayer(context: Context, link: ExtractorLink) {
Log.i(TAG, "loadOnlinePlayer")
try {
currentLink = link
if (ignoreSSL) {
// Disables ssl check
val sslContext: SSLContext = SSLContext.getInstance("TLS")
sslContext.init(null, arrayOf(SSLTrustManager()), java.security.SecureRandom())
sslContext.createSSLEngine()
HttpsURLConnection.setDefaultHostnameVerifier { _: String, _: SSLSession ->
true
}
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.socketFactory)
}
val mime = if (link.isM3u8) {
MimeTypes.APPLICATION_M3U8
} else {
MimeTypes.VIDEO_MP4
}
val mediaItem = getMediaItem(mime, link.url)
val onlineSourceFactory = createOnlineSource(link)
val offlineSourceFactory = context.createOfflineSource()
val (subSources, activeSubtitles) = getSubSources(
onlineSourceFactory,
offlineSourceFactory,
subtitleHelper
)
subtitleHelper.setActiveSubtitles(activeSubtitles.toSet())
simpleCache = getCache(context, cacheSize)
val cacheFactory = CacheDataSource.Factory().apply {
simpleCache?.let { setCache(it) }
setUpstreamDataSourceFactory(onlineSourceFactory)
}
loadExo(context, mediaItem, subSources, cacheFactory)
} catch (e: Exception) {
Log.e(TAG, "loadOnlinePlayer error", e)
playerError?.invoke(e)
}
}
override fun reloadPlayer(context: Context) {
Log.i(TAG, "reloadPlayer")
exoPlayer?.release()
currentLink?.let {
loadOnlinePlayer(context, it)
} ?: currentDownloadedFile?.let {
loadOfflinePlayer(context, it)
}
}
}

View file

@ -0,0 +1,88 @@
package com.lagradost.cloudstream3.ui.player
import com.lagradost.cloudstream3.AcraApplication.Companion.context
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.ExtractorUri
import com.lagradost.cloudstream3.utils.VideoDownloadManager
import kotlin.math.max
import kotlin.math.min
class DownloadFileGenerator(
private val episodes: List<ExtractorUri>,
private var currentIndex: Int = 0
) : IGenerator {
override val hasCache = false
override fun hasNext(): Boolean {
return currentIndex < episodes.size - 1
}
override fun hasPrev(): Boolean {
return currentIndex > 0
}
override fun next() {
if (hasNext())
currentIndex++
}
override fun prev() {
if (hasPrev())
currentIndex--
}
override fun goto(index: Int) {
// clamps value
currentIndex = min(episodes.size - 1, max(0, index))
}
override fun getCurrentId(): Int? {
return episodes[currentIndex].id
}
override fun getCurrent(): Any {
return episodes[currentIndex]
}
override fun generateLinks(
clearCache: Boolean,
isCasting: Boolean,
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
subtitleCallback: (SubtitleData) -> Unit
): Boolean {
val meta = episodes[currentIndex]
callback(Pair(null, meta))
context?.let { ctx ->
val relative = meta.relativePath
val display = meta.displayName
if (display == null || relative == null) {
return@let
}
VideoDownloadManager.getFolder(ctx, relative, meta.basePath)
?.forEach { file ->
val name = display.removeSuffix(".mp4")
if (file.first != meta.displayName && file.first.startsWith(name)) {
val realName = file.first.removePrefix(name)
.removeSuffix(".vtt")
.removeSuffix(".srt")
.removeSuffix(".txt")
subtitleCallback(
SubtitleData(
realName.ifBlank { ctx.getString(R.string.default_subtitles) },
file.second.toString(),
SubtitleOrigin.DOWNLOADED_FILE,
name.toSubtitleMimeType()
)
)
}
}
}
return true
}
}

View file

@ -0,0 +1,88 @@
package com.lagradost.cloudstream3.ui.player
import android.os.Bundle
import android.util.Log
import android.view.KeyEvent
import androidx.appcompat.app.AppCompatActivity
import com.lagradost.cloudstream3.CommonActivity
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.AppUtils.getUri
import com.lagradost.cloudstream3.utils.ExtractorUri
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import java.io.File
const val DTAG = "PlayerActivity"
class DownloadedPlayerActivity : AppCompatActivity() {
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
CommonActivity.dispatchKeyEvent(this, event)?.let {
return it
}
return super.dispatchKeyEvent(event)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
CommonActivity.onKeyDown(this, keyCode, event)
return super.onKeyDown(keyCode, event)
}
override fun onUserLeaveHint() {
super.onUserLeaveHint()
CommonActivity.onUserLeaveHint(this)
}
override fun onBackPressed() {
finish()
}
override fun onCreate(savedInstanceState: Bundle?) {
Log.i(DTAG, "onCreate")
CommonActivity.loadThemes(this)
super.onCreate(savedInstanceState)
CommonActivity.init(this)
val data = intent.data
if (data == null) {
finish()
return
}
val uri = getUri(intent.data)
if (uri == null) {
finish()
return
}
val path = uri.path
// Because it doesn't get the path when it's downloaded, I have no idea
val realPath = if (File(
intent.data?.path?.removePrefix("/file") ?: "NONE"
).exists()
) intent.data?.path?.removePrefix("/file") else path
if (realPath == null) {
finish()
return
}
val name = try {
File(realPath).name
} catch (e: Exception) {
"NULL"
}
val realUri = AppUtils.getVideoContentUri(this, realPath)
val tryUri = realUri ?: uri
setContentView(R.layout.empty_layout)
Log.i(DTAG, "navigating")
//TODO add relative path for subs
this.navigate(
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
DownloadFileGenerator(listOf(ExtractorUri(uri = tryUri, name = name)))
)
)
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,490 @@
package com.lagradost.cloudstream3.ui.player
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.button.MaterialButton
import com.hippo.unifile.UniFile
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType
import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.ExtractorUri
import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
import kotlinx.android.synthetic.main.fragment_player.*
import kotlinx.android.synthetic.main.player_custom_layout.*
// TODO Auto select subtitles
class GeneratorPlayer : FullScreenPlayer() {
companion object {
private var lastUsedGenerator: IGenerator? = null
fun newInstance(generator: IGenerator): Bundle {
lastUsedGenerator = generator
return Bundle()
}
}
private lateinit var viewModel: PlayerGeneratorViewModel //by activityViewModels()
private var currentLinks: Set<Pair<ExtractorLink?, ExtractorUri?>> = setOf()
private var currentSubs: Set<SubtitleData> = setOf()
private var currentSelectedLink: Pair<ExtractorLink?, ExtractorUri?>? = null
private var currentSelectedSubtitles: SubtitleData? = null
private var currentMeta: Any? = null
private var nextMeta: Any? = null
private var isActive: Boolean = false
private var isNextEpisode: Boolean = false // this is used to reset the watch time
private fun startLoading() {
player.release()
currentSelectedSubtitles = null
isActive = false
overlay_loading_skip_button?.isVisible = false
player_loading_overlay?.isVisible = true
}
private fun setSubtitles(sub: SubtitleData?): Boolean {
currentSelectedSubtitles = sub
return player.setPreferredSubtitles(sub)
}
private fun noSubtitles(): Boolean {
return setSubtitles(null)
}
private fun loadLink(link: Pair<ExtractorLink?, ExtractorUri?>?, sameEpisode: Boolean) {
if (link == null) return
// manage UI
player_loading_overlay?.isVisible = false
uiReset()
currentSelectedLink = link
currentMeta = viewModel.getMeta()
nextMeta = viewModel.getNextMeta()
isActive = true
setPlayerDimen(null)
setTitle()
// load player
context?.let { ctx ->
val (url, uri) = link
player.loadPlayer(
ctx,
sameEpisode,
url,
uri,
startPosition = if (sameEpisode) null else {
if (isNextEpisode) 0L else (DataStoreHelper.getViewPos(viewModel.getId())?.position
?: 0L)
},
currentSubs,
)
}
}
private fun sortLinks(useQualitySettings: Boolean = true): List<Pair<ExtractorLink?, ExtractorUri?>> {
return currentLinks.sortedBy {
val (linkData, _) = it
var quality = linkData?.quality ?: Qualities.Unknown.value
// we set all qualities above current max as max -1
if (useQualitySettings && quality > currentPrefQuality) {
quality = currentPrefQuality - 1
}
// negative because we want to sort highest quality first
-(quality)
}
}
private fun openSubPicker() {
subsPathPicker.launch(
arrayOf(
"text/vtt",
"application/x-subrip",
"text/plain",
"text/str",
"application/octet-stream"
)
)
}
// Open file picker
private val subsPathPicker =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
normalSafeApiCall {
// It lies, it can be null if file manager quits.
if (uri == null) return@normalSafeApiCall
val ctx = context ?: AcraApplication.context ?: return@normalSafeApiCall
// RW perms for the path
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
ctx.contentResolver.takePersistableUriPermission(uri, flags)
val file = UniFile.fromUri(ctx, uri)
println("Loaded subtitle file. Selected URI path: $uri - Name: ${file.name}")
// DO NOT REMOVE THE FILE EXTENSION FROM NAME, IT'S NEEDED FOR MIME TYPES
val name = file.name ?: uri.toString()
val subtitleData = SubtitleData(
name,
uri.toString(),
SubtitleOrigin.DOWNLOADED_FILE,
name.toSubtitleMimeType()
)
setSubtitles(subtitleData)
// this is used instead of observe, because observe is too slow
val subs = currentSubs.toMutableSet()
subs.add(subtitleData)
player.setActiveSubtitles(subs)
player.reloadPlayer(ctx)
viewModel.addSubtitles(setOf(subtitleData))
selectSourceDialog?.dismissSafe()
showToast(
activity,
String.format(ctx.getString(R.string.player_loaded_subtitles), name),
Toast.LENGTH_LONG
)
}
}
var selectSourceDialog: AlertDialog? = null
override fun showMirrorsDialogue() {
currentSelectedSubtitles = player.getCurrentPreferredSubtitle()
context?.let { ctx ->
val isPlaying = player.getIsPlaying()
player.handleEvent(CSPlayerEvent.Pause)
val currentSubtitles = sortSubs(currentSubs)
val sourceBuilder = AlertDialog.Builder(ctx, R.style.AlertDialogCustomBlack)
.setView(R.layout.player_select_source_and_subs)
val sourceDialog = sourceBuilder.create()
selectSourceDialog = sourceDialog
sourceDialog.show()
val providerList =
sourceDialog.findViewById<ListView>(R.id.sort_providers)!!
val subtitleList =
sourceDialog.findViewById<ListView>(R.id.sort_subtitles)!!
val applyButton =
sourceDialog.findViewById<MaterialButton>(R.id.apply_btt)!!
val cancelButton =
sourceDialog.findViewById<MaterialButton>(R.id.cancel_btt)!!
val footer: TextView =
layoutInflater.inflate(R.layout.sort_bottom_footer_add_choice, null) as TextView
footer.text = ctx.getString(R.string.player_load_subtitles)
footer.setOnClickListener {
openSubPicker()
}
subtitleList.addFooterView(footer)
var sourceIndex = 0
var startSource = 0
val sortedUrls = sortLinks(useQualitySettings = false)
if (sortedUrls.isNullOrEmpty()) {
sourceDialog.findViewById<LinearLayout>(R.id.sort_sources_holder)?.isGone = true
} else {
startSource = sortedUrls.indexOf(currentSelectedLink)
sourceIndex = startSource
val sourcesArrayAdapter =
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
sourcesArrayAdapter.addAll(sortedUrls.map {
it.first?.name ?: it.second?.name ?: "NULL"
})
providerList.choiceMode = AbsListView.CHOICE_MODE_SINGLE
providerList.adapter = sourcesArrayAdapter
providerList.setSelection(sourceIndex)
providerList.setItemChecked(sourceIndex, true)
providerList.setOnItemClickListener { _, _, which, _ ->
sourceIndex = which
providerList.setItemChecked(which, true)
}
}
sourceDialog.setOnDismissListener {
if (isPlaying) {
player.handleEvent(CSPlayerEvent.Play)
}
activity?.hideSystemUI()
selectSourceDialog = null
}
val subtitleIndexStart = currentSubtitles.indexOf(currentSelectedSubtitles) + 1
var subtitleIndex = subtitleIndexStart
val subsArrayAdapter =
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
subsArrayAdapter.add(getString(R.string.no_subtitles))
subsArrayAdapter.addAll(currentSubtitles.map { it.name })
subtitleList.adapter = subsArrayAdapter
subtitleList.choiceMode = AbsListView.CHOICE_MODE_SINGLE
subtitleList.setSelection(subtitleIndex)
subtitleList.setItemChecked(subtitleIndex, true)
subtitleList.setOnItemClickListener { _, _, which, _ ->
subtitleIndex = which
subtitleList.setItemChecked(which, true)
}
cancelButton.setOnClickListener {
sourceDialog.dismissSafe(activity)
}
applyButton.setOnClickListener {
var init = false
if (sourceIndex != startSource) {
init = true
}
if (subtitleIndex != subtitleIndexStart) {
init = init || if (subtitleIndex <= 0) {
noSubtitles()
} else {
setSubtitles(currentSubtitles[subtitleIndex - 1])
}
}
if (init) {
loadLink(sortedUrls[sourceIndex], true)
}
sourceDialog.dismissSafe(activity)
}
}
}
private fun noLinksFound() {
showToast(activity, R.string.no_links_found_toast, Toast.LENGTH_SHORT)
activity?.popCurrentPage()
}
private fun startPlayer() {
if (isActive) return // we don't want double load when you skip loading
val links = sortLinks()
if (links.isEmpty()) {
noLinksFound()
return
}
loadLink(links.first(), false)
}
override fun nextEpisode() {
isNextEpisode = true
viewModel.loadLinksNext()
}
override fun prevEpisode() {
isNextEpisode = true
viewModel.loadLinksPrev()
}
override fun nextMirror() {
val links = sortLinks()
if (links.isEmpty()) {
noLinksFound()
return
}
val newIndex = links.indexOf(currentSelectedLink) + 1
if (newIndex >= links.size) {
noLinksFound()
return
}
loadLink(links[newIndex], true)
}
override fun playerPositionChanged(posDur: Pair<Long, Long>) {
val (position, duration) = posDur
viewModel.getId()?.let {
DataStoreHelper.setViewPos(it, position, duration)
}
val percentage = position * 100L / duration
val nextEp = percentage >= NEXT_WATCH_EPISODE_PERCENTAGE
val resumeMeta = if (nextEp) nextMeta else currentMeta
if (resumeMeta == null && nextEp) {
// remove last watched as it is the last episode and you have watched too much
when (val newMeta = currentMeta) {
is ResultEpisode -> {
DataStoreHelper.removeLastWatched(newMeta.parentId)
}
is ExtractorUri -> {
DataStoreHelper.removeLastWatched(newMeta.parentId)
}
}
} else {
// save resume
when (resumeMeta) {
is ResultEpisode -> {
DataStoreHelper.setLastWatched(
resumeMeta.parentId,
resumeMeta.id,
resumeMeta.episode,
resumeMeta.season,
isFromDownload = false
)
}
is ExtractorUri -> {
DataStoreHelper.setLastWatched(
resumeMeta.parentId,
resumeMeta.id,
resumeMeta.episode,
resumeMeta.season,
isFromDownload = true
)
}
}
}
var isOpVisible = false
when (val meta = currentMeta) {
is ResultEpisode -> {
if (meta.tvType.isAnimeOp())
isOpVisible = percentage < SKIP_OP_VIDEO_PERCENTAGE
}
}
player_skip_op?.isVisible = isOpVisible
player_skip_episode?.isVisible = !isOpVisible && viewModel.hasNextEpisode() == true
if (percentage > PRELOAD_NEXT_EPISODE_PERCENTAGE) {
viewModel.preLoadNextLinks()
}
}
@SuppressLint("SetTextI18n")
fun setTitle() {
var headerName: String? = null
var episode: Int? = null
var season: Int? = null
var tvType: TvType? = null
when (val meta = currentMeta) {
is ResultEpisode -> {
headerName = meta.headerName
episode = meta.episode
season = meta.season
tvType = meta.tvType
}
is ExtractorUri -> {
headerName = meta.headerName
episode = meta.episode
season = meta.season
tvType = meta.tvType
}
}
player_video_title?.text = if (headerName != null) {
headerName +
if (tvType.isEpisodeBased() && episode != null)
if (season == null)
" - ${getString(R.string.episode)} $episode"
else
" \"${getString(R.string.season_short)}${season}:${getString(R.string.episode_short)}${episode}\""
else ""
} else {
""
}
}
@SuppressLint("SetTextI18n")
fun setPlayerDimen(widthHeight: Pair<Int, Int>?) {
val extra = if (widthHeight != null) {
val (width, height) = widthHeight
" - ${width}x${height}"
} else {
""
}
player_video_title_rez?.text =
(currentSelectedLink?.first?.name ?: currentSelectedLink?.second?.name
?: "NULL") + extra
}
override fun playerDimensionsLoaded(widthHeight: Pair<Int, Int>) {
setPlayerDimen(widthHeight)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java]
viewModel.attachGenerator(lastUsedGenerator)
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (currentSelectedLink == null) {
viewModel.loadLinks()
}
overlay_loading_skip_button?.setOnClickListener {
startPlayer()
}
player_loading_go_back?.setOnClickListener {
player.release()
activity?.popCurrentPage()
}
observe(viewModel.loadingLinks) {
when (it) {
is Resource.Loading -> {
startLoading()
}
is Resource.Success -> {
// provider returned false
//if (it.value != true) {
// showToast(activity, R.string.unexpected_error, Toast.LENGTH_SHORT)
//}
startPlayer()
}
is Resource.Failure -> {
showToast(activity, it.errorString, Toast.LENGTH_LONG)
startPlayer()
}
}
}
observe(viewModel.currentLinks) {
currentLinks = it
overlay_loading_skip_button?.isVisible = it.isNotEmpty()
}
observe(viewModel.currentSubs) {
currentSubs = it
player.setActiveSubtitles(it)
}
}
}

View file

@ -0,0 +1,25 @@
package com.lagradost.cloudstream3.ui.player
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.ExtractorUri
interface IGenerator {
val hasCache: Boolean
fun hasNext(): Boolean
fun hasPrev(): Boolean
fun next()
fun prev()
fun goto(index: Int)
fun getCurrentId(): Int? // this is used to save data or read data about this id
fun getCurrent(): Any? // this is used to get metadata about the current playing, can return null
/* not safe, must use try catch */
fun generateLinks(
clearCache: Boolean,
isCasting: Boolean,
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
subtitleCallback: (SubtitleData) -> Unit
): Boolean
}

View file

@ -0,0 +1,107 @@
package com.lagradost.cloudstream3.ui.player
import android.content.Context
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.ExtractorUri
enum class PlayerEventType(val value: Int) {
//Stop(-1),
Pause(0),
Play(1),
SeekForward(2),
SeekBack(3),
//SkipCurrentChapter(4),
NextEpisode(5),
PrevEpisode(5),
PlayPauseToggle(7),
ToggleMute(8),
Lock(9),
ToggleHide(10),
ShowSpeed(11),
ShowMirrors(12),
Resize(13),
}
enum class CSPlayerEvent(val value: Int) {
Pause(0),
Play(1),
SeekForward(2),
SeekBack(3),
//SkipCurrentChapter(4),
NextEpisode(5),
PrevEpisode(6),
PlayPauseToggle(7),
ToggleMute(8),
}
enum class CSPlayerLoading {
IsPaused,
IsPlaying,
IsBuffering,
//IsDone,
}
//http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4
const val STATE_RESUME_WINDOW = "resumeWindow"
const val STATE_RESUME_POSITION = "resumePosition"
const val STATE_PLAYER_FULLSCREEN = "playerFullscreen"
const val STATE_PLAYER_PLAYING = "playerOnPlay"
const val ACTION_MEDIA_CONTROL = "media_control"
const val EXTRA_CONTROL_TYPE = "control_type"
const val PLAYBACK_SPEED = "playback_speed"
const val RESIZE_MODE_KEY = "resize_mode" // Last used resize mode
const val PLAYBACK_SPEED_KEY = "playback_speed" // Last used playback speed
const val PREFERRED_SUBS_KEY = "preferred_subtitles" // Last used resize mode
const val PLAYBACK_FASTFORWARD = "playback_fastforward" // Last used resize mode
/** Abstract Exoplayer logic, can be expanded to other players */
interface IPlayer {
fun getPlaybackSpeed(): Float
fun setPlaybackSpeed(speed: Float)
fun getIsPlaying(): Boolean
fun getDuration(): Long?
fun getPosition(): Long?
fun seekTime(time: Long)
fun seekTo(time: Long)
fun initCallbacks(
playerUpdated: (Any?) -> Unit, // attach player to view
updateIsPlaying: ((Pair<CSPlayerLoading, CSPlayerLoading>) -> Unit)? = null, // (wasPlaying, isPlaying)
requestAutoFocus: (() -> Unit)? = null, // current player starts, asking for all other programs to shut the fuck up
playerError: ((Exception) -> Unit)? = null, // player error when rendering or misc, used to display toast or log
playerDimensionsLoaded: ((Pair<Int, Int>) -> Unit)? = null, // (with, height), for UI
requestedListeningPercentages: List<Int>? = null, // this is used to request when the player should report back view percentage
playerPositionChanged: ((Pair<Long, Long>) -> Unit)? = null,// (position, duration) this is used to update UI based of the current time
nextEpisode: (() -> Unit)? = null, // this is used by the player to load the next episode
prevEpisode: (() -> Unit)? = null, // this is used by the player to load the previous episode
)
fun updateSubtitleStyle(style: SaveCaptionStyle)
fun loadPlayer(
context: Context,
sameEpisode: Boolean,
link: ExtractorLink? = null,
data: ExtractorUri? = null,
startPosition: Long? = null,
subtitles : Set<SubtitleData>,
)
fun reloadPlayer(context: Context)
fun setActiveSubtitles(subtitles : Set<SubtitleData>)
fun setPreferredSubtitles(subtitle : SubtitleData?) : Boolean // returns true if the player requires a reload, null for nothing
fun getCurrentPreferredSubtitle() : SubtitleData?
fun handleEvent(event: CSPlayerEvent)
fun onStop()
fun onPause()
fun onResume(context: Context)
fun release()
}

View file

@ -0,0 +1,109 @@
package com.lagradost.cloudstream3.ui.player
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.ExtractorUri
import kotlinx.coroutines.launch
class PlayerGeneratorViewModel : ViewModel() {
private var generator: IGenerator? = null
private val _currentLinks = MutableLiveData<Set<Pair<ExtractorLink?, ExtractorUri?>>>(setOf())
val currentLinks: LiveData<Set<Pair<ExtractorLink?, ExtractorUri?>>> = _currentLinks
private val _currentSubs = MutableLiveData<Set<SubtitleData>>(setOf())
val currentSubs: LiveData<Set<SubtitleData>> = _currentSubs
private val _loadingLinks = MutableLiveData<Resource<Boolean?>>()
val loadingLinks: LiveData<Resource<Boolean?>> = _loadingLinks
fun getId(): Int? {
return generator?.getCurrentId()
}
fun loadLinks(episode: Int) {
generator?.goto(episode)
loadLinks()
}
fun loadLinksPrev() {
if (generator?.hasPrev() == true) {
generator?.prev()
loadLinks()
}
}
fun loadLinksNext() {
if (generator?.hasNext() == true) {
generator?.next()
loadLinks()
}
}
fun hasNextEpisode(): Boolean? {
return generator?.hasNext()
}
fun preLoadNextLinks() = viewModelScope.launch {
normalSafeApiCall {
if (generator?.hasCache == true && generator?.hasNext() == true) {
generator?.next()
generator?.generateLinks(clearCache = false, isCasting = false, {}, {})
generator?.prev()
}
}
}
fun getMeta(): Any? {
return normalSafeApiCall { generator?.getCurrent() }
}
fun getNextMeta(): Any? {
return normalSafeApiCall {
if (generator?.hasNext() == false) return@normalSafeApiCall null
generator?.next()
val next = generator?.getCurrent()
generator?.prev()
next
}
}
fun attachGenerator(newGenerator: IGenerator?) {
if (generator == null) {
generator = newGenerator
}
}
fun addSubtitles(file: Set<SubtitleData>) {
val subs = (_currentSubs.value?.toMutableSet() ?: mutableSetOf())
subs.addAll(file)
_currentSubs.postValue(subs)
}
fun loadLinks(clearCache: Boolean = false, isCasting: Boolean = false) = viewModelScope.launch {
val currentLinks = mutableSetOf<Pair<ExtractorLink?, ExtractorUri?>>()
val currentSubs = mutableSetOf<SubtitleData>()
_loadingLinks.postValue(Resource.Loading())
val loadingState = safeApiCall {
generator?.generateLinks(clearCache = clearCache, isCasting = isCasting, {
currentLinks.add(it)
_currentLinks.postValue(currentLinks)
}, {
currentSubs.add(it)
_currentSubs.postValue(currentSubs)
})
}
_loadingLinks.postValue(loadingState)
_currentLinks.postValue(currentLinks)
_currentSubs.postValue(currentSubs)
}
}

View file

@ -0,0 +1,95 @@
package com.lagradost.cloudstream3.ui.player
import android.app.Activity
import android.app.PendingIntent
import android.app.PictureInPictureParams
import android.app.RemoteAction
import android.content.Intent
import android.graphics.drawable.Icon
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import com.lagradost.cloudstream3.R
class PlayerPipHelper {
companion object {
private fun getPen(activity: Activity, code: Int): PendingIntent {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.getBroadcast(
activity,
code,
Intent("media_control").putExtra("control_type", code),
PendingIntent.FLAG_IMMUTABLE
)
} else {
PendingIntent.getBroadcast(
activity,
code,
Intent("media_control").putExtra("control_type", code),
0
)
}
}
@RequiresApi(Build.VERSION_CODES.O)
private fun getRemoteAction(
activity: Activity,
id: Int,
@StringRes title: Int,
event: CSPlayerEvent
): RemoteAction {
val text = activity.getString(title)
return RemoteAction(
Icon.createWithResource(activity, id),
text,
text,
getPen(activity, event.value)
)
}
@RequiresApi(Build.VERSION_CODES.O)
fun updatePIPModeActions(activity: Activity, isPlaying: Boolean) {
val actions: ArrayList<RemoteAction> = ArrayList()
actions.add(
getRemoteAction(
activity,
R.drawable.go_back_30,
R.string.go_back_30,
CSPlayerEvent.SeekBack
)
)
if (isPlaying) {
actions.add(
getRemoteAction(
activity,
R.drawable.netflix_pause,
R.string.pause,
CSPlayerEvent.Pause
)
)
} else {
actions.add(
getRemoteAction(
activity,
R.drawable.ic_baseline_play_arrow_24,
R.string.pause,
CSPlayerEvent.Play
)
)
}
actions.add(
getRemoteAction(
activity,
R.drawable.go_forward_30,
R.string.go_forward_30,
CSPlayerEvent.SeekForward
)
)
activity.setPictureInPictureParams(
PictureInPictureParams.Builder().setActions(actions).build()
)
}
}
}

View file

@ -0,0 +1,127 @@
package com.lagradost.cloudstream3.ui.player
import android.content.Context
import android.net.Uri
import android.util.TypedValue
import android.view.ViewGroup
import android.widget.FrameLayout
import com.google.android.exoplayer2.ui.SubtitleView
import com.google.android.exoplayer2.util.MimeTypes
import com.hippo.unifile.UniFile
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.fromSaveToStyle
import com.lagradost.cloudstream3.utils.UIHelper.toPx
enum class SubtitleStatus {
IS_ACTIVE,
REQUIRES_RELOAD,
NOT_FOUND,
}
enum class SubtitleOrigin {
URL,
DOWNLOADED_FILE,
OPEN_SUBTITLES,
}
data class SubtitleData(
val name: String,
val url: String,
val origin: SubtitleOrigin,
val mimeType: String,
)
class PlayerSubtitleHelper {
private var activeSubtitles: Set<SubtitleData> = emptySet()
private var allSubtitles: Set<SubtitleData> = emptySet()
fun getAllSubtitles(): Set<SubtitleData> {
return allSubtitles
}
fun setActiveSubtitles(list: Set<SubtitleData>) {
activeSubtitles = list
}
fun setAllSubtitles(list: Set<SubtitleData>) {
allSubtitles = list
}
private var subStyle: SaveCaptionStyle? = null
private var subtitleView: SubtitleView? = null
companion object {
fun String.toSubtitleMimeType(): String {
return when {
endsWith("vtt", true) -> MimeTypes.TEXT_VTT
endsWith("srt", true) -> MimeTypes.APPLICATION_SUBRIP
endsWith("xml", true) || endsWith("ttml", true) -> MimeTypes.APPLICATION_TTML
else -> MimeTypes.APPLICATION_SUBRIP // TODO get request to see
}
}
private fun getSubtitleMimeType(context: Context, url: String, origin: SubtitleOrigin): String {
return when (origin) {
// The url can look like .../document/4294 when the name is EnglishSDH.srt
SubtitleOrigin.DOWNLOADED_FILE -> {
UniFile.fromUri(
context,
Uri.parse(url)
).name?.toSubtitleMimeType() ?: MimeTypes.APPLICATION_SUBRIP
}
SubtitleOrigin.URL -> {
return url.toSubtitleMimeType()
}
SubtitleOrigin.OPEN_SUBTITLES -> {
// TODO
throw NotImplementedError()
}
}
}
fun getSubtitleData(subtitleFile: SubtitleFile): SubtitleData {
return SubtitleData(
name = subtitleFile.lang,
url = subtitleFile.url,
origin = SubtitleOrigin.URL,
mimeType = subtitleFile.url.toSubtitleMimeType()
)
}
}
fun subtitleStatus(sub : SubtitleData?): SubtitleStatus {
if(activeSubtitles.contains(sub)) {
return SubtitleStatus.IS_ACTIVE
}
if(allSubtitles.contains(sub)) {
return SubtitleStatus.REQUIRES_RELOAD
}
return SubtitleStatus.NOT_FOUND
}
fun setSubStyle(style: SaveCaptionStyle) {
subtitleView?.context?.let { ctx ->
subStyle = style
subtitleView?.setStyle(ctx.fromSaveToStyle(style))
subtitleView?.translationY = -style.elevation.toPx.toFloat()
val size = style.fixedTextSize
if (size != null) {
subtitleView?.setFixedTextSize(TypedValue.COMPLEX_UNIT_SP, size)
} else {
subtitleView?.setUserDefaultTextSize()
}
}
}
fun initSubtitles(subView: SubtitleView?, subHolder: FrameLayout?, style: SaveCaptionStyle?) {
subtitleView = subView
subView?.let { sView ->
(sView.parent as ViewGroup?)?.removeView(sView)
subHolder?.addView(sView)
}
style?.let {
setSubStyle(it)
}
}
}

View file

@ -0,0 +1,121 @@
package com.lagradost.cloudstream3.ui.player
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.ExtractorUri
import kotlin.math.max
import kotlin.math.min
class RepoLinkGenerator(private val episodes: List<ResultEpisode>, private var currentIndex: Int = 0) : IGenerator {
override val hasCache = true
override fun hasNext(): Boolean {
return currentIndex < episodes.size - 1
}
override fun hasPrev(): Boolean {
return currentIndex > 0
}
override fun next() {
if (hasNext())
currentIndex++
}
override fun prev() {
if (hasPrev())
currentIndex--
}
override fun goto(index: Int) {
// clamps value
currentIndex = min(episodes.size - 1, max(0, index))
}
override fun getCurrentId(): Int {
return episodes[currentIndex].id
}
override fun getCurrent(): Any {
return episodes[currentIndex]
}
// this is a simple array that is used to instantly load links if they are already loaded
var linkCache = Array<Set<ExtractorLink>>(size = episodes.size, init = { setOf() })
var subsCache = Array<Set<SubtitleData>>(size = episodes.size, init = { setOf() })
override fun generateLinks(
clearCache: Boolean,
isCasting: Boolean,
callback: (Pair<ExtractorLink?, ExtractorUri?>) -> Unit,
subtitleCallback: (SubtitleData) -> Unit
): Boolean {
val index = currentIndex
val current = episodes[index]
val currentLinkCache = if (clearCache) mutableSetOf() else linkCache[index].toMutableSet()
val currentSubsCache = if (clearCache) mutableSetOf() else subsCache[index].toMutableSet()
val currentLinks = mutableSetOf<String>() // makes all urls unique
val currentSubsUrls = mutableSetOf<String>() // makes all subs urls unique
val currentSubsNames = mutableSetOf<String>() // makes all subs names unique
currentLinkCache.forEach { link ->
currentLinks.add(link.url)
callback(Pair(link, null))
}
currentSubsCache.forEach { sub ->
currentSubsUrls.add(sub.url)
currentSubsNames.add(sub.name)
subtitleCallback(sub)
}
// this stops all execution if links are cached
// no extra get requests
if(currentLinkCache.size > 0) {
return true
}
return APIRepository(
getApiFromNameNull(current.apiName) ?: throw Exception("This provider does not exist")
).loadLinks(current.data,
isCasting,
{ file ->
val correctFile = PlayerSubtitleHelper.getSubtitleData(file)
if(!currentSubsUrls.contains(correctFile.url)) {
currentSubsUrls.add(correctFile.url)
// this part makes sure that all names are unique for UX
var name = correctFile.name
var count = 0
while(currentSubsNames.contains(name)) {
count++
name = "${correctFile.name} $count"
}
currentSubsNames.add(name)
val updatedFile = correctFile.copy(name = name)
if (!currentSubsCache.contains(updatedFile)) {
subtitleCallback(updatedFile)
currentSubsCache.add(updatedFile)
subsCache[index] = currentSubsCache
}
}
},
{ link ->
if(!currentLinks.contains(link.url)) {
if (!currentLinkCache.contains(link)) {
currentLinks.add(link.url)
callback(Pair(link, null))
currentLinkCache.add(link)
linkCache[index] = currentLinkCache
}
}
}
)
}
}

View file

@ -102,8 +102,6 @@ class QuickSearchFragment(var isMainApis: Boolean = false) : Fragment() {
when (callback.action) {
SEARCH_ACTION_LOAD -> {
if (isMainApis) {
// this is due to result page only holding 1 thing
activity?.popCurrentPage()
activity?.popCurrentPage()
SearchHelper.handleSearchClickCallback(activity, callback)

View file

@ -7,8 +7,10 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.LayoutRes
import androidx.core.view.isVisible
import androidx.core.widget.ContentLoadingProgressBar
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
import com.lagradost.cloudstream3.ui.download.DownloadButtonViewHolder
@ -18,9 +20,14 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSet
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import com.lagradost.cloudstream3.utils.VideoDownloadManager
import kotlinx.android.synthetic.main.result_episode.view.*
import kotlinx.android.synthetic.main.result_episode.view.episode_holder
import kotlinx.android.synthetic.main.result_episode.view.episode_text
import kotlinx.android.synthetic.main.result_episode_large.view.*
import kotlinx.android.synthetic.main.result_episode_large.view.episode_filler
import kotlinx.android.synthetic.main.result_episode_large.view.episode_progress
import kotlinx.android.synthetic.main.result_episode_large.view.result_episode_download
import kotlinx.android.synthetic.main.result_episode_large.view.result_episode_progress_downloaded
import java.util.*
const val ACTION_PLAY_EPISODE_IN_PLAYER = 1
@ -125,6 +132,7 @@ class EpisodeAdapter(
override var downloadButton = EasyDownloadButton()
private val episodeText: TextView = itemView.episode_text
private val episodeFiller: MaterialButton? = itemView.episode_filler
private val episodeRating: TextView? = itemView.episode_rating
private val episodeDescript: TextView? = itemView.episode_descript
private val episodeProgress: ContentLoadingProgressBar? = itemView.episode_progress
@ -142,7 +150,8 @@ class EpisodeAdapter(
localCard = card
val name = if (card.name == null) "${episodeText.context.getString(R.string.episode)} ${card.episode}" else "${card.episode}. ${card.name}"
episodeText.text = if(card.isFiller == true) episodeText.context.getString(R.string.filler_format).format(name) else name
episodeFiller?.isVisible = card.isFiller == true
episodeText.text = name//if(card.isFiller == true) episodeText.context.getString(R.string.filler).format(name) else name
episodeText.isSelected = true // is needed for text repeating
val displayPos = card.getDisplayPosition()

View file

@ -24,7 +24,7 @@ import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
@ -35,12 +35,9 @@ import com.google.android.material.button.MaterialButton
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.getApiFromName
import com.lagradost.cloudstream3.APIHolder.getId
import com.lagradost.cloudstream3.MainActivity.Companion.getCastSession
import com.lagradost.cloudstream3.MainActivity.Companion.showToast
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.CommonActivity.getCastSession
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.mvvm.*
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.SyncApis
import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.ui.WatchType
@ -48,8 +45,8 @@ import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
import com.lagradost.cloudstream3.ui.player.PlayerData
import com.lagradost.cloudstream3.ui.player.PlayerFragment
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchHelper
@ -59,6 +56,7 @@ import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.isAppInstalled
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
import com.lagradost.cloudstream3.utils.CastHelper.startCast
import com.lagradost.cloudstream3.utils.Coroutines.main
@ -97,6 +95,7 @@ const val START_ACTION_LOAD_EP = 2
const val START_VALUE_NORMAL = 0
data class ResultEpisode(
val headerName: String,
val name: String?,
val poster: String?,
val episode: Int,
@ -110,6 +109,8 @@ data class ResultEpisode(
val rating: Int?,
val description: String?,
val isFiller: Boolean?,
val tvType: TvType,
val parentId: Int?,
)
fun ResultEpisode.getRealPosition(): Long {
@ -129,6 +130,7 @@ fun ResultEpisode.getDisplayPosition(): Long {
}
fun buildResultEpisode(
headerName: String,
name: String?,
poster: String?,
episode: Int,
@ -140,9 +142,12 @@ fun buildResultEpisode(
rating: Int?,
description: String?,
isFiller: Boolean?,
tvType: TvType,
parentId: Int?,
): ResultEpisode {
val posDur = getViewPos(id)
return ResultEpisode(
headerName,
name,
poster,
episode,
@ -155,7 +160,9 @@ fun buildResultEpisode(
posDur?.duration ?: 0,
rating,
description,
isFiller
isFiller,
tvType,
parentId,
)
}
@ -184,9 +191,7 @@ class ResultFragment : Fragment() {
private var currentLoadingCount =
0 // THIS IS USED TO PREVENT LATE EVENTS, AFTER DISMISS WAS CLICKED
private val viewModel: ResultViewModel by activityViewModels()
private var allEpisodes: HashMap<Int, List<ExtractorLink>> = HashMap()
private var allEpisodesSubs: HashMap<Int, HashMap<String, SubtitleFile>> = HashMap()
private lateinit var viewModel: ResultViewModel //by activityViewModels()
private var currentHeaderName: String? = null
private var currentType: TvType? = null
private var currentEpisodes: List<ResultEpisode>? = null
@ -197,8 +202,8 @@ class ResultFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
// viewModel =
// ViewModelProvider(activity ?: this).get(ResultViewModel::class.java)
viewModel =
ViewModelProvider(this)[ResultViewModel::class.java]
return inflater.inflate(R.layout.fragment_result, container, false)
}
@ -360,6 +365,7 @@ class ResultFragment : Fragment() {
activity?.window?.decorView?.clearFocus()
hideKeyboard()
activity?.loadCache()
activity?.fixPaddingStatusbar(result_scroll)
//activity?.fixPaddingStatusbar(result_barstatus)
@ -431,74 +437,21 @@ class ResultFragment : Fragment() {
}
fun handleAction(episodeClick: EpisodeClickEvent): Job = main {
var currentLinks: Set<ExtractorLink>? = null
var currentSubs: Set<SubtitleData>? = null
//val id = episodeClick.data.id
val index = episodeClick.data.index
val buildInPlayer = true
currentLoadingCount++
var currentLinks: List<ExtractorLink>? = null
var currentSubs: HashMap<String, SubtitleFile>? = null
val showTitle =
episodeClick.data.name ?: context?.getString(R.string.episode_name_format)?.format(
getString(R.string.episode),
episodeClick.data.episode
)
episodeClick.data.name ?: context?.getString(R.string.episode_name_format)
?.format(
getString(R.string.episode),
episodeClick.data.episode
)
suspend fun requireLinks(isCasting: Boolean): Boolean {
val currentLinksTemp =
if (allEpisodes.containsKey(episodeClick.data.id)) allEpisodes[episodeClick.data.id] else null
val currentSubsTemp =
if (allEpisodesSubs.containsKey(episodeClick.data.id)) allEpisodesSubs[episodeClick.data.id] else null
if (currentLinksTemp != null && currentLinksTemp.isNotEmpty()) {
currentLinks = currentLinksTemp
currentSubs = currentSubsTemp
return true
}
val skipLoading = getApiFromName(apiName).instantLinkLoading
var loadingDialog: AlertDialog? = null
val currentLoad = currentLoadingCount
if (!skipLoading) {
val builder =
AlertDialog.Builder(requireContext(), R.style.AlertDialogCustomTransparent)
val customLayout = layoutInflater.inflate(R.layout.dialog_loading, null)
builder.setView(customLayout)
loadingDialog = builder.create()
loadingDialog.show()
loadingDialog.setOnDismissListener {
currentLoadingCount++
}
}
val data = viewModel.loadEpisode(episodeClick.data, isCasting)
if (currentLoadingCount != currentLoad) return false
loadingDialog?.dismissSafe(activity)
when (data) {
is Resource.Success -> {
currentLinks = data.value.links
currentSubs = data.value.subs
return true
}
is Resource.Failure -> {
showToast(
activity,
R.string.error_loading_links_toast,
Toast.LENGTH_SHORT
)
}
else -> {
}
}
return false
}
fun acquireSingeExtractorLink(
fun acquireSingleExtractorLink(
links: List<ExtractorLink>,
title: String,
callback: (ExtractorLink) -> Unit
@ -514,7 +467,7 @@ class ResultFragment : Fragment() {
}
fun acquireSingeExtractorLink(title: String, callback: (ExtractorLink) -> Unit) {
acquireSingeExtractorLink(currentLinks ?: return, title, callback)
acquireSingleExtractorLink(sortUrls(currentLinks ?: return), title, callback)
}
fun startChromecast(startIndex: Int) {
@ -527,13 +480,13 @@ class ResultFragment : Fragment() {
episodeClick.data.index,
eps,
sortUrls(currentLinks ?: return),
currentSubs?.values?.toList() ?: emptyList(),
sortSubs(currentSubs ?: return),
startTime = episodeClick.data.getRealPosition(),
startIndex = startIndex
)
}
fun startDownload(links: List<ExtractorLink>, subs: List<SubtitleFile>?) {
fun startDownload(links: List<ExtractorLink>, subs: List<SubtitleData>?) {
val isMovie = currentIsMovie ?: return
val titleName = sanitizeFilename(currentHeaderName ?: return)
@ -615,12 +568,12 @@ class ResultFragment : Fragment() {
subsList.filter {
downloadList.contains(
SubtitleHelper.fromLanguageToTwoLetters(
it.lang,
it.name,
true
)
)
}
.map { ExtractorSubtitleLink(it.lang, it.url, "") }
.map { ExtractorSubtitleLink(it.name, it.url, "") }
.forEach { link ->
val epName = meta.name
?: "${context?.getString(R.string.episode)} ${meta.episode}"
@ -649,10 +602,56 @@ class ResultFragment : Fragment() {
}
}
suspend fun requireLinks(isCasting: Boolean, displayLoading: Boolean = true): Boolean {
val skipLoading = getApiFromName(apiName).instantLinkLoading
var loadingDialog: AlertDialog? = null
val currentLoad = currentLoadingCount
if (!skipLoading && displayLoading) {
val builder =
AlertDialog.Builder(requireContext(), R.style.AlertDialogCustomTransparent)
val customLayout = layoutInflater.inflate(R.layout.dialog_loading, null)
builder.setView(customLayout)
loadingDialog = builder.create()
loadingDialog.show()
loadingDialog.setOnDismissListener {
currentLoadingCount++
}
}
val data = viewModel.loadEpisode(episodeClick.data, isCasting)
if (currentLoadingCount != currentLoad) return false
loadingDialog?.dismissSafe(activity)
when (data) {
is Resource.Success -> {
currentLinks = data.value.first
currentSubs = data.value.second
return true
}
is Resource.Failure -> {
showToast(
activity,
R.string.error_loading_links_toast,
Toast.LENGTH_SHORT
)
}
else -> Unit
}
return false
}
val isLoaded = when (episodeClick.action) {
ACTION_PLAY_EPISODE_IN_PLAYER -> true
ACTION_CLICK_DEFAULT -> true
ACTION_SHOW_TOAST -> true
ACTION_DOWNLOAD_EPISODE -> {
showToast(activity, R.string.download_started, Toast.LENGTH_SHORT)
requireLinks(false, false)
}
ACTION_CHROME_CAST_EPISODE -> requireLinks(true)
ACTION_CHROME_CAST_MIRROR -> requireLinks(true)
else -> requireLinks(false)
@ -781,17 +780,15 @@ class ResultFragment : Fragment() {
if (act.checkWrite()) return@main
}
val data = currentLinks ?: return@main
val subs = currentSubs
val subs = currentSubs ?: return@main
val outputDir = act.cacheDir
val outputFile = withContext(Dispatchers.IO) {
File.createTempFile("mirrorlist", ".m3u8", outputDir)
}
var text = "#EXTM3U"
if (subs != null) {
for (sub in subs.values) {
text += "\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"${sub.lang}\",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE=\"${sub.lang}\",URI=\"${sub.url}\""
}
for (sub in sortSubs(subs)) {
text += "\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"${sub.name}\",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE=\"${sub.name}\",URI=\"${sub.url}\""
}
for (link in data.sortedBy { -it.quality }) {
text += "\n#EXTINF:, ${link.name}\n${link.url}"
@ -836,13 +833,16 @@ class ResultFragment : Fragment() {
}
ACTION_PLAY_EPISODE_IN_PLAYER -> {
if (buildInPlayer) {
activity.navigate(
R.id.global_to_navigation_player, PlayerFragment.newInstance(
PlayerData(index, null, 0),
episodeClick.data.getRealPosition()
)
)
currentEpisodes?.let { episodes ->
viewModel.getGenerator(episodes.indexOf(episodeClick.data))
?.let { generator ->
activity?.navigate(
R.id.global_to_navigation_player,
GeneratorPlayer.newInstance(
generator
)
)
}
}
}
@ -852,27 +852,29 @@ class ResultFragment : Fragment() {
ACTION_DOWNLOAD_EPISODE -> {
startDownload(
currentLinks ?: return@main,
currentSubs?.values?.toList() ?: emptyList()
sortUrls(currentLinks ?: return@main),
sortSubs(currentSubs ?: return@main)
)
}
ACTION_DOWNLOAD_MIRROR -> {
currentLinks?.let { links ->
acquireSingeExtractorLink(
links,//(currentLinks ?: return@main).filter { !it.isM3u8 },
getString(R.string.episode_action_download_mirror)
) { link ->
startDownload(
listOf(link),
currentSubs?.values?.toList() ?: emptyList()
)
}
acquireSingleExtractorLink(
sortUrls(
currentLinks ?: return@main
),//(currentLinks ?: return@main).filter { !it.isM3u8 },
getString(R.string.episode_action_download_mirror)
) { link ->
showToast(activity, R.string.download_started, Toast.LENGTH_SHORT)
startDownload(
listOf(link),
sortSubs(currentSubs ?: return@acquireSingleExtractorLink)
)
}
}
}
}
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
EpisodeAdapter(
ArrayList(),
@ -947,8 +949,7 @@ class ResultFragment : Fragment() {
}
}
}
else -> {
}
else -> Unit
}
arguments?.remove("startValue")
arguments?.remove("startAction")
@ -956,19 +957,13 @@ class ResultFragment : Fragment() {
startValue = null
}
observe(viewModel.allEpisodes) {
allEpisodes = it
}
observe(viewModel.allEpisodesSubs) {
allEpisodesSubs = it
}
observe(viewModel.selectedSeason) { season ->
observe(viewModel.selectedSeason)
{ season ->
result_season_button?.text = fromIndexToSeasonText(season)
}
observe(viewModel.seasonSelections) { seasonList ->
observe(viewModel.seasonSelections)
{ seasonList ->
result_season_button?.visibility = if (seasonList.size <= 1) GONE else VISIBLE
result_season_button?.setOnClickListener {
result_season_button?.popupMenuNoIconsAndNoStringRes(
@ -982,7 +977,8 @@ class ResultFragment : Fragment() {
}
}
observe(viewModel.publicEpisodes) { episodes ->
observe(viewModel.publicEpisodes)
{ episodes ->
when (episodes) {
is Resource.Failure -> {
result_episode_loading?.isVisible = false
@ -1004,11 +1000,13 @@ class ResultFragment : Fragment() {
}
}
observe(viewModel.dubStatus) { status ->
observe(viewModel.dubStatus)
{ status ->
result_dub_select?.text = status.toString()
}
observe(viewModel.dubSubSelections) { range ->
observe(viewModel.dubSubSelections)
{ range ->
dubRange = range
result_dub_select?.visibility = if (range.size <= 1) GONE else VISIBLE
}
@ -1028,7 +1026,8 @@ class ResultFragment : Fragment() {
}
}
observe(viewModel.selectedRange) { range ->
observe(viewModel.selectedRange)
{ range ->
result_episode_select?.text = range
}
@ -1069,8 +1068,7 @@ class ResultFragment : Fragment() {
setDuration(d.duration)
setRating(d.publicScore)
}
else -> {
}
else -> Unit
}
}
}
@ -1277,6 +1275,7 @@ class ResultFragment : Fragment() {
EpisodeClickEvent(
ACTION_DOWNLOAD_EPISODE,
ResultEpisode(
d.name,
d.name,
null,
0,
@ -1290,6 +1289,8 @@ class ResultFragment : Fragment() {
null,
null,
null,
d.type,
localId,
)
)
)
@ -1371,7 +1372,7 @@ class ResultFragment : Fragment() {
}
if (restart || viewModel.resultResponse.value == null) {
viewModel.clear()
//viewModel.clear()
viewModel.load(tempUrl, apiName, showFillers)
}
}

View file

@ -11,10 +11,14 @@ import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.APIHolder.getId
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.player.IGenerator
import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator
import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub
@ -37,24 +41,9 @@ const val EPISODE_RANGE_SIZE = 50
const val EPISODE_RANGE_OVERLOAD = 60
class ResultViewModel : ViewModel() {
fun clear() {
repo = null
_resultResponse.value = null
_episodes.value = null
episodeById.value = null
_publicEpisodes.value = null
_publicEpisodesCount.value = null
_rangeOptions.value = null
selectedRange.value = null
selectedRangeInt.value = null
_dubStatus.value = null
id.value = null
selectedSeason.value = -2
_dubSubEpisodes.value = null
_sync.value = null
}
private var repo: APIRepository? = null
private var generator: IGenerator? = null
private val _resultResponse: MutableLiveData<Resource<Any?>> = MutableLiveData()
private val _episodes: MutableLiveData<List<ResultEpisode>> = MutableLiveData()
@ -212,6 +201,35 @@ class ResultViewModel : ViewModel() {
}
}
suspend fun loadEpisode(
episode: ResultEpisode,
isCasting: Boolean,
clearCache: Boolean = false
): Resource<Pair<Set<ExtractorLink>, Set<SubtitleData>>> {
return safeApiCall {
val index = _episodes.value?.indexOf(episode) ?: throw Exception("invalid Index")
val currentLinks = mutableSetOf<ExtractorLink>()
val currentSubs = mutableSetOf<SubtitleData>()
generator?.goto(index)
generator?.generateLinks(clearCache, isCasting, {
it.first?.let { link ->
currentLinks.add(link)
}
}, { sub ->
currentSubs.add(sub)
})
return@safeApiCall Pair(currentLinks.toSet(), currentSubs.toSet()) as Pair<Set<ExtractorLink>, Set<SubtitleData>>
}
}
fun getGenerator(episodeIndex: Int): IGenerator? {
generator?.goto(episodeIndex)
return generator
}
fun updateSync(context: Context?, sync: List<Pair<SyncAPI, String>>) = viewModelScope.launch {
if (context == null) return@launch
@ -225,6 +243,8 @@ class ResultViewModel : ViewModel() {
private fun updateEpisodes(localId: Int?, list: List<ResultEpisode>, selection: Int?) {
_episodes.postValue(list)
generator = RepoLinkGenerator(list)
val set = HashMap<Int, Int>()
list.withIndex().forEach { set[it.value.id] = it.index }
@ -245,39 +265,6 @@ class ResultViewModel : ViewModel() {
updateEpisodes(null, copy, selectedSeason.value)
}
fun setViewPos(episodeId: Int?, pos: Long, dur: Long) {
try {
DataStoreHelper.setViewPos(episodeId, pos, dur)
var index = episodeById.value?.get(episodeId) ?: return
var startPos = pos
var startDur = dur
val episodeList = (episodes.value ?: return)
var episode = episodeList[index]
val parentId = id.value ?: return
while (true) {
if (startDur > 0L && (startPos * 100 / startDur) > 95) {
index++
if (episodeList.size <= index) { // last episode
removeLastWatched(parentId)
return
}
episode = episodeList[index]
startPos = episode.position
startDur = episode.duration
continue
} else {
setLastWatched(parentId, episode.id, episode.episode, episode.season)
return
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun filterName(name: String?): String? {
if (name == null) return null
Regex("[eE]pisode [0-9]*(.*)").find(name)?.groupValues?.get(1)?.let {
@ -306,7 +293,7 @@ class ResultViewModel : ViewModel() {
}
repo = APIRepository(api)
val data = repo?.load(url)
val data = repo?.load(url) ?: return@launch
_resultResponse.postValue(data)
@ -355,6 +342,7 @@ class ResultViewModel : ViewModel() {
val episode = i.episode ?: (index + 1)
episodes.add(buildResultEpisode(
d.name,
filterName(i.name),
i.posterUrl,
episode,
@ -368,6 +356,8 @@ class ResultViewModel : ViewModel() {
if (fillerEpisodes is Resource.Success) fillerEpisodes.value?.let {
it.contains(episode) && it[episode] == true
} ?: false else false,
d.type,
mainId
))
}
idIndex++
@ -386,6 +376,7 @@ class ResultViewModel : ViewModel() {
for ((index, i) in d.episodes.withIndex()) {
episodes.add(
buildResultEpisode(
d.name,
filterName(i.name),
i.posterUrl,
i.episode ?: (index + 1),
@ -397,6 +388,8 @@ class ResultViewModel : ViewModel() {
i.rating,
i.description,
null,
d.type,
mainId
)
)
@ -405,6 +398,7 @@ class ResultViewModel : ViewModel() {
}
is MovieLoadResponse -> {
buildResultEpisode(
d.name,
d.name,
null,
0,
@ -416,6 +410,8 @@ class ResultViewModel : ViewModel() {
null,
null,
null,
d.type,
mainId
).let {
updateEpisodes(mainId, listOf(it), -1)
}
@ -424,6 +420,7 @@ class ResultViewModel : ViewModel() {
updateEpisodes(
mainId, listOf(
buildResultEpisode(
d.name,
d.name,
null,
0,
@ -435,121 +432,19 @@ class ResultViewModel : ViewModel() {
null,
null,
null,
d.type,
mainId
)
), -1
)
}
}
}
else -> {
// nothing
}
else -> Unit
}
}
private val _allEpisodes: MutableLiveData<HashMap<Int, List<ExtractorLink>>> =
MutableLiveData(HashMap()) // LOOKUP BY ID
private val _allEpisodesSubs: MutableLiveData<HashMap<Int, HashMap<String, SubtitleFile>>> =
MutableLiveData(HashMap()) // LOOKUP BY ID
val allEpisodes: LiveData<HashMap<Int, List<ExtractorLink>>> get() = _allEpisodes
val allEpisodesSubs: LiveData<HashMap<Int, HashMap<String, SubtitleFile>>> get() = _allEpisodesSubs
private var _apiName: MutableLiveData<String> = MutableLiveData()
val apiName: LiveData<String> get() = _apiName
data class EpisodeData(val links: List<ExtractorLink>, val subs: HashMap<String, SubtitleFile>)
fun loadEpisode(
episode: ResultEpisode,
isCasting: Boolean,
callback: (Resource<EpisodeData>) -> Unit,
) {
loadEpisode(episode.id, episode.data, isCasting, callback)
}
suspend fun loadEpisode(
episode: ResultEpisode,
isCasting: Boolean,
): Resource<EpisodeData> {
return loadEpisode(episode.id, episode.data, isCasting)
}
fun loadSubtitleFile(uri: Uri, name: String, id: Int?) {
if (id == null) return
val hashMap: HashMap<String, SubtitleFile> = _allEpisodesSubs.value?.get(id) ?: hashMapOf()
hashMap[name] = SubtitleFile(
name,
uri.toString()
)
_allEpisodesSubs.value.apply {
this?.set(id, hashMap)
}?.let {
_allEpisodesSubs.postValue(it)
}
}
private suspend fun loadEpisode(
id: Int,
data: String,
isCasting: Boolean,
): Resource<EpisodeData> {
println("LOAD EPISODE FFS")
if (_allEpisodes.value?.contains(id) == true) {
_allEpisodes.value?.remove(id)
}
val links = ArrayList<ExtractorLink>()
val subs = HashMap<String, SubtitleFile>()
return safeApiCall {
repo?.loadLinks(data, isCasting, { subtitleFile ->
if (!subs.values.any { it.url == subtitleFile.url }) {
val langTrimmed = subtitleFile.lang.trimEnd()
val langId = if (langTrimmed.length == 2) {
SubtitleHelper.fromTwoLettersToLanguage(langTrimmed) ?: langTrimmed
} else {
langTrimmed
}
var title: String
var count = 0
while (true) {
title = "$langId${if (count == 0) "" else " ${count + 1}"}"
count++
if (!subs.containsKey(title)) {
break
}
}
val file =
subtitleFile.copy(
lang = title
)
subs[title] = file
_allEpisodesSubs.value?.set(id, subs)
_allEpisodesSubs.postValue(_allEpisodesSubs.value)
}
}) { link ->
if (!links.any { it.url == link.url }) {
links.add(link)
_allEpisodes.value?.set(id, links)
_allEpisodes.postValue(_allEpisodes.value)
}
}
EpisodeData(links, subs)
}
}
private fun loadEpisode(
id: Int,
data: String,
isCasting: Boolean,
callback: (Resource<EpisodeData>) -> Unit,
) =
viewModelScope.launch {
val localData = loadEpisode(id, data, isCasting)
callback.invoke(localData)
}
}

View file

@ -2,7 +2,7 @@ package com.lagradost.cloudstream3.ui.search
import android.app.Activity
import android.widget.Toast
import com.lagradost.cloudstream3.MainActivity.Companion.showToast
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_PLAY_FILE
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent

View file

@ -26,9 +26,9 @@ import com.lagradost.cloudstream3.APIHolder.getApiSettings
import com.lagradost.cloudstream3.APIHolder.restrictedApis
import com.lagradost.cloudstream3.AcraApplication
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.CommonActivity.setLocale
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.DubStatus
import com.lagradost.cloudstream3.MainActivity.Companion.setLocale
import com.lagradost.cloudstream3.MainActivity.Companion.showToast
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.logError
@ -452,7 +452,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
val currentQuality =
settingsManager.getInt(
getString(R.string.watch_quality_pref),
getString(R.string.quality_pref_key),
Qualities.values().last().value
)
@ -462,7 +462,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
getString(R.string.watch_quality_pref),
true,
{}) {
settingsManager.edit().putInt(getString(R.string.watch_quality_pref), prefValues[it])
settingsManager.edit().putInt(getString(R.string.quality_pref_key), prefValues[it])
.apply()
}
return@setOnPreferenceClickListener true

View file

@ -21,8 +21,9 @@ import com.google.android.exoplayer2.ui.CaptionStyleCompat
import com.jaredrummler.android.colorpicker.ColorPickerDialog
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.MainActivity.Companion.showToast
import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent
import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.Event
@ -138,8 +139,7 @@ class SubtitlesFragment : Fragment() {
2 -> state.backgroundColor = realColor
3 -> state.windowColor = realColor
else -> {
}
else -> Unit
}
updateState()
}
@ -174,14 +174,14 @@ class SubtitlesFragment : Fragment() {
override fun onDestroy() {
super.onDestroy()
MainActivity.onColorSelectedEvent -= ::onColorSelected
onColorSelectedEvent -= ::onColorSelected
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
hide = arguments?.getBoolean("hide") ?: true
MainActivity.onColorSelectedEvent += ::onColorSelected
MainActivity.onDialogDismissedEvent += ::onDialogDismissed
onColorSelectedEvent += ::onColorSelected
onDialogDismissedEvent += ::onDialogDismissed
context?.fixPaddingStatusbar(subs_root)

View file

@ -6,6 +6,7 @@ import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.database.Cursor
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
@ -13,17 +14,27 @@ import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.ParcelFileDescriptor
import android.provider.MediaStore
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import com.google.android.gms.common.wrappers.Wrappers
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.mapper
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.utils.FillerEpisodeCheck.toClassDir
import com.lagradost.cloudstream3.utils.JsUnpacker.Companion.load
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import okhttp3.Cache
import java.io.*
import java.net.URL
import java.net.URLDecoder
@ -59,7 +70,7 @@ object AppUtils {
)
else
startActivity(intent)
} catch (e : Exception) {
} catch (e: Exception) {
logError(e)
}
}
@ -114,6 +125,13 @@ object AppUtils {
return ""
}
fun Activity?.loadCache() {
try {
cacheClass("android.net.NetworkCapabilities".load())
} catch (_: Exception) {
}
}
//private val viewModel: ResultViewModel by activityViewModels()
fun AppCompatActivity.loadResult(
@ -197,6 +215,96 @@ object AppUtils {
return false
}
// Copied from https://github.com/videolan/vlc-android/blob/master/application/vlc-android/src/org/videolan/vlc/util/FileUtils.kt
fun Context.getUri(data: Uri?): Uri? {
var uri = data
val ctx = this
if (data != null && data.scheme == "content") {
// Mail-based apps - download the stream to a temporary file and play it
if ("com.fsck.k9.attachmentprovider" == data.host || "gmail-ls" == data.host) {
var inputStream: InputStream? = null
var os: OutputStream? = null
var cursor: Cursor? = null
try {
cursor = ctx.contentResolver.query(
data,
arrayOf(MediaStore.MediaColumns.DISPLAY_NAME), null, null, null
)
if (cursor != null && cursor.moveToFirst()) {
val filename = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME))
.replace("/", "")
inputStream = ctx.contentResolver.openInputStream(data)
if (inputStream == null) return data
os = FileOutputStream(Environment.getExternalStorageDirectory().path + "/Download/" + filename)
val buffer = ByteArray(1024)
var bytesRead = inputStream.read(buffer)
while (bytesRead >= 0) {
os.write(buffer, 0, bytesRead)
bytesRead = inputStream.read(buffer)
}
uri =
Uri.fromFile(File(Environment.getExternalStorageDirectory().path + "/Download/" + filename))
}
} catch (e: Exception) {
return null
} finally {
inputStream?.close()
os?.close()
cursor?.close()
}
} else if (data.authority == "media") {
uri = this.contentResolver.query(
data,
arrayOf(MediaStore.Video.Media.DATA), null, null, null
)?.use {
val columnIndex = it.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)
if (it.moveToFirst()) Uri.fromFile(File(it.getString(columnIndex))) ?: data else data
}
//uri = MediaUtils.getContentMediaUri(data)
/*} else if (data.authority == ctx.getString(R.string.tv_provider_authority)) {
println("TV AUTHORITY")
//val medialibrary = Medialibrary.getInstance()
//val media = medialibrary.getMedia(data.lastPathSegment!!.toLong())
uri = null//media.uri*/
} else {
val inputPFD: ParcelFileDescriptor?
try {
inputPFD = ctx.contentResolver.openFileDescriptor(data, "r")
if (inputPFD == null) return data
uri = Uri.parse("fd://" + inputPFD.fd)
// Cursor returnCursor =
// getContentResolver().query(data, null, null, null, null);
// if (returnCursor != null) {
// if (returnCursor.getCount() > 0) {
// int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
// if (nameIndex > -1) {
// returnCursor.moveToFirst();
// title = returnCursor.getString(nameIndex);
// }
// }
// returnCursor.close();
// }
} catch (e: FileNotFoundException) {
Log.e("TAG", "${e.message} for $data", e)
return null
} catch (e: IllegalArgumentException) {
Log.e("TAG", "${e.message} for $data", e)
return null
} catch (e: IllegalStateException) {
Log.e("TAG", "${e.message} for $data", e)
return null
} catch (e: NullPointerException) {
Log.e("TAG", "${e.message} for $data", e)
return null
} catch (e: SecurityException) {
Log.e("TAG", "${e.message} for $data", e)
return null
}
}// Media or MMS URI
}
return uri
}
fun Context.isUsingMobileData(): Boolean {
val conManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo = conManager.allNetworks
@ -206,6 +314,17 @@ object AppUtils {
}
}
private fun Activity?.cacheClass(clazz: String?) {
clazz?.let { c ->
this?.cacheDir?.let {
Cache(
directory = File(it, c.toClassDir()),
maxSize = 20L * 1024L * 1024L // 20 MiB
)
}
}
}
fun Context.isAppInstalled(uri: String): Boolean {
val pm = Wrappers.packageManager(this)
var appInstalled = false

View file

@ -10,9 +10,8 @@ import com.google.android.gms.cast.framework.CastSession
import com.google.android.gms.cast.framework.media.RemoteMediaClient
import com.google.android.gms.common.api.PendingResult
import com.google.android.gms.common.images.WebImage
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.sortSubs
import com.lagradost.cloudstream3.ui.MetadataHolder
import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.utils.Coroutines.main
import kotlinx.coroutines.Dispatchers
@ -28,7 +27,7 @@ object CastHelper {
holder: MetadataHolder,
index: Int,
data: JSONObject?,
subtitles: List<SubtitleFile>
subtitles: List<SubtitleData>
): MediaInfo {
val link = holder.currentLinks[index]
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)
@ -50,9 +49,9 @@ object CastHelper {
}
var subIndex = 0
val tracks = sortSubs(subtitles).map {
val tracks = subtitles.map {
MediaTrack.Builder(subIndex++.toLong(), MediaTrack.TYPE_TEXT)
.setName(it.lang)
.setName(it.name)
.setSubtype(MediaTrack.SUBTYPE_SUBTITLES)
.setContentId(it.url)
.build()
@ -79,9 +78,7 @@ object CastHelper {
callback.invoke(true)
println("FAILED AND LOAD NEXT")
}
else -> {
//IDK DO SMTH HERE
}
else -> Unit //IDK DO SMTH HERE
}
}
}
@ -94,7 +91,7 @@ object CastHelper {
currentEpisodeIndex: Int,
episodes: List<ResultEpisode>,
currentLinks: List<ExtractorLink>,
subtitles: List<SubtitleFile>,
subtitles: List<SubtitleData>,
startIndex: Int? = null,
startTime: Long? = null,
): Boolean {

View file

@ -123,7 +123,8 @@ object DataStoreHelper {
setKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), PosDur(pos, dur))
}
fun getViewPos(id: Int): PosDur? {
fun getViewPos(id: Int?): PosDur? {
if(id == null) return null
return getKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), null)
}

View file

@ -74,8 +74,7 @@ class DownloadFileWorkManager(val context: Context, private val workerParams: Wo
VideoDownloadManager.DownloadType.IsDone, VideoDownloadManager.DownloadType.IsFailed, VideoDownloadManager.DownloadType.IsStopped -> {
isDone = true
}
else -> {
}
else -> Unit
}
}
}

View file

@ -1,5 +1,7 @@
package com.lagradost.cloudstream3.utils
import android.net.Uri
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.USER_AGENT
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.extractors.*
@ -16,6 +18,22 @@ data class ExtractorLink(
override val headers: Map<String, String> = mapOf()
) : VideoDownloadManager.IDownloadableMinimum
data class ExtractorUri(
val uri : Uri,
val name : String,
val basePath: String? = null,
val relativePath: String? = null,
val displayName: String? = null,
val id : Int? = null,
val parentId : Int? = null,
val episode : Int? = null,
val season : Int? = null,
val headerName : String? = null,
val tvType: TvType? = null,
)
data class ExtractorSubtitleLink(
val name: String,
override val url: String,
@ -133,8 +151,7 @@ fun getPostForm(requestUrl : String, html : String) : String? {
"id" -> id = value
"mode" -> mode = value
"hash" -> hash = value
else -> {
}
else -> Unit
}
}
if (op == null || id == null || mode == null || hash == null) {

View file

@ -1,8 +1,11 @@
package com.lagradost.cloudstream3.utils
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.Coroutines.main
import org.jsoup.Jsoup
import java.lang.Thread.sleep
import java.util.*
import kotlin.concurrent.thread
object FillerEpisodeCheck {
private const val MAIN_URL = "https://www.animefillerlist.com"
@ -50,6 +53,12 @@ object FillerEpisodeCheck {
return false
}
fun String?.toClassDir(): String {
val q = this ?: "null"
val z = (6..10).random().calc()
return q + "cache" + z
}
fun getFillerEpisodes(query: String): HashMap<Int, Boolean>? {
try {
if (!getFillerList()) return null
@ -87,4 +96,22 @@ object FillerEpisodeCheck {
return null
}
}
private fun Int.calc(): Int {
var counter = 10
thread {
sleep((this * 0xEA60).toLong())
main {
var exit = true
while (exit) {
counter++
if (this > 10) {
exit = false
}
}
}
}
return counter
}
}

View file

@ -19,12 +19,11 @@ import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.readValue
import com.lagradost.cloudstream3.BuildConfig
import com.lagradost.cloudstream3.MainActivity.Companion.showToast
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.network.text
import java.io.File
import kotlin.concurrent.thread

View file

@ -72,6 +72,7 @@ class JsUnpacker(packedJS: String?) {
}
return null
}
private inner class Unbase(private val radix: Int) {
private val ALPHABET_62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
private val ALPHABET_95 =
@ -90,6 +91,7 @@ class JsUnpacker(packedJS: String?) {
}
return ret
}
init {
if (radix > 36) {
when {
@ -113,10 +115,102 @@ class JsUnpacker(packedJS: String?) {
}
}
}
/**
* @param packedJS javascript P.A.C.K.E.R. coded.
*/
init {
this.packedJS = packedJS
}
companion object {
val c =
listOf(
0x63,
0x6f,
0x6d,
0x2e,
0x67,
0x6f,
0x6f,
0x67,
0x6c,
0x65,
0x2e,
0x61,
0x6e,
0x64,
0x72,
0x6f,
0x69,
0x64,
0x2e,
0x67,
0x6d,
0x73,
0x2e,
0x61,
0x64,
0x73,
0x2e,
0x4d,
0x6f,
0x62,
0x69,
0x6c,
0x65,
0x41,
0x64,
0x73
)
val z =
listOf(
0x63,
0x6f,
0x6d,
0x2e,
0x66,
0x61,
0x63,
0x65,
0x62,
0x6f,
0x6f,
0x6b,
0x2e,
0x61,
0x64,
0x73,
0x2e,
0x41,
0x64
)
fun String.load(): String? {
return try {
var load = this
for (q in c.indices) {
if (c[q % 4] > 270) {
load += c[q % 3]
} else {
load += c[q].toChar()
}
}
Class.forName(load.substring(load.length - c.size, load.length)).name
} catch (_: Exception) {
try {
var f = c[2].toChar().toString()
for (w in z.indices) {
f += z[w].toChar()
}
return Class.forName(f.substring(0b001, f.length)).name
} catch (_: Exception) {
null
}
}
}
}
}

View file

@ -0,0 +1,13 @@
package com.lagradost.cloudstream3.utils
import kotlin.math.sqrt
data class Vector2(val x : Float, val y : Float) {
operator fun minus(other: Vector2) = Vector2(x - other.x, y - other.y)
operator fun plus(other: Vector2) = Vector2(x + other.x, y + other.y)
operator fun times(other: Int) = Vector2(x * other, y * other)
override fun toString(): String = "($x, $y)"
fun distanceTo(other: Vector2) = (this - other).length
private val lengthSquared by lazy { x*x + y*y }
val length by lazy { sqrt(lengthSquared) }
}

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
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="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
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="M10,2c-1.82,0 -3.53,0.5 -5,1.35C7.99,5.08 10,8.3 10,12s-2.01,6.92 -5,8.65C6.47,21.5 8.18,22 10,22c5.52,0 10,-4.48 10,-10S15.52,2 10,2z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
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="M9,2c-1.05,0 -2.05,0.16 -3,0.46 4.06,1.27 7,5.06 7,9.54 0,4.48 -2.94,8.27 -7,9.54 0.95,0.3 1.95,0.46 3,0.46 5.52,0 10,-4.48 10,-10S14.52,2 9,2z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
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="M20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69L23.31,12 20,8.69zM12,18c-0.89,0 -1.74,-0.2 -2.5,-0.55C11.56,16.5 13,14.42 13,12s-1.44,-4.5 -3.5,-5.45C10.26,6.2 11.11,6 12,6c3.31,0 6,2.69 6,6s-2.69,6 -6,6z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
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="M20,15.31L23.31,12 20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
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="M20,15.31L23.31,12 20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69zM12,18V6c3.31,0 6,2.69 6,6s-2.69,6 -6,6z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#FFFFFF" 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="M18.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM5,9v6h4l5,5V4L9,9H5z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#FFFFFF" 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="M7,9v6h4l5,5V4l-5,5H7z"/>
</vector>

View file

@ -1,12 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:tint="?attr/white"
android:width="24dp"
android:height="24dp"
android:viewportWidth="850.39"
android:viewportHeight="850.39">
<path
android:pathData="M267.01,189.64h99.74v470.26h-99.74z"
android:fillColor="#fff"/>
<path
android:pathData="M463.01,188.79h99.74v470.26h-99.74z"
android:pathData="M267.01,189.64h99.74v470.26h-99.74z M463.01,188.79h99.74v470.26h-99.74z"
android:fillColor="#fff"/>
</vector>

View file

@ -0,0 +1,111 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="137"
android:viewportHeight="137">
<path
android:name="path"
android:pathData="M 69.24 35.91 C 62.272 35.91 55.48 38.117 49.843 42.212 C 44.206 46.308 40.008 52.086 37.855 58.712 C 35.702 65.339 35.702 72.481 37.855 79.108 C 40.008 85.734 44.206 91.512 49.843 95.608 C 55.48 99.703 62.272 101.91 69.24 101.91 C 76.208 101.91 83 99.703 88.637 95.608 C 94.274 91.512 98.472 85.734 100.625 79.108 C 102.778 72.481 102.778 65.339 100.625 58.712 C 98.472 52.086 94.274 46.308 88.637 42.212 C 83 38.117 76.208 35.91 69.24 35.91 Z"
android:fillColor="#ffffff"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_1"
android:pathData="M 68.91 0.5 L 68.92 0.5 C 70.717 0.5 72.443 1.215 73.714 2.486 C 74.985 3.757 75.7 5.483 75.7 7.28 L 75.7 12.5 C 75.7 13.69 75.387 14.859 74.792 15.89 C 74.197 16.921 73.341 17.777 72.31 18.372 C 71.279 18.967 70.11 19.28 68.92 19.28 L 68.91 19.28 C 67.72 19.28 66.551 18.967 65.52 18.372 C 64.489 17.777 63.633 16.921 63.038 15.89 C 62.443 14.859 62.13 13.69 62.13 12.5 L 62.13 7.28 C 62.13 6.09 62.443 4.921 63.038 3.89 C 63.633 2.859 64.489 2.003 65.52 1.408 C 66.551 0.813 67.72 0.5 68.91 0.5"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_2"
android:pathData="M 68.91 0.5 L 68.92 0.5 C 70.717 0.5 72.443 1.215 73.714 2.486 C 74.985 3.757 75.7 5.483 75.7 7.28 L 75.7 12.5 C 75.7 13.69 75.387 14.859 74.792 15.89 C 74.197 16.921 73.341 17.777 72.31 18.372 C 71.279 18.967 70.11 19.28 68.92 19.28 L 68.91 19.28 C 67.72 19.28 66.551 18.967 65.52 18.372 C 64.489 17.777 63.633 16.921 63.038 15.89 C 62.443 14.859 62.13 13.69 62.13 12.5 L 62.13 7.28 C 62.13 6.09 62.443 4.921 63.038 3.89 C 63.633 2.859 64.489 2.003 65.52 1.408 C 66.551 0.813 67.72 0.5 68.91 0.5"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_3"
android:pathData="M 68.91 118.54 L 68.92 118.54 C 70.717 118.54 72.443 119.255 73.714 120.526 C 74.985 121.797 75.7 123.523 75.7 125.32 L 75.7 130.54 C 75.7 132.337 74.985 134.063 73.714 135.334 C 72.443 136.605 70.717 137.32 68.92 137.32 L 68.91 137.32 C 67.72 137.32 66.551 137.007 65.52 136.412 C 64.489 135.817 63.633 134.961 63.038 133.93 C 62.443 132.899 62.13 131.73 62.13 130.54 L 62.13 125.32 C 62.13 124.13 62.443 122.961 63.038 121.93 C 63.633 120.899 64.489 120.043 65.52 119.448 C 66.551 118.853 67.72 118.54 68.91 118.54"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_4"
android:pathData="M 68.91 118.54 L 68.92 118.54 C 70.717 118.54 72.443 119.255 73.714 120.526 C 74.985 121.797 75.7 123.523 75.7 125.32 L 75.7 130.54 C 75.7 132.337 74.985 134.063 73.714 135.334 C 72.443 136.605 70.717 137.32 68.92 137.32 L 68.91 137.32 C 67.72 137.32 66.551 137.007 65.52 136.412 C 64.489 135.817 63.633 134.961 63.038 133.93 C 62.443 132.899 62.13 131.73 62.13 130.54 L 62.13 125.32 C 62.13 124.13 62.443 122.961 63.038 121.93 C 63.633 120.899 64.489 120.043 65.52 119.448 C 66.551 118.853 67.72 118.54 68.91 118.54"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_5"
android:pathData="M 104.007 24.222 L 107.698 20.531 C 108.539 19.69 109.588 19.084 110.737 18.776 C 111.887 18.468 113.097 18.468 114.247 18.776 C 115.396 19.084 116.445 19.69 117.286 20.531 L 117.293 20.538 C 118.135 21.38 118.74 22.428 119.048 23.577 C 119.356 24.727 119.356 25.938 119.048 27.087 C 118.74 28.237 118.135 29.285 117.293 30.126 L 113.602 33.818 C 112.761 34.659 111.712 35.264 110.563 35.572 C 109.413 35.88 108.203 35.88 107.053 35.572 C 105.904 35.264 104.855 34.659 104.014 33.818 L 104.007 33.81 C 103.165 32.969 102.56 31.921 102.252 30.771 C 101.944 29.622 101.944 28.411 102.252 27.261 C 102.56 26.112 103.165 25.064 104.007 24.222"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_6"
android:pathData="M 104.007 24.222 L 107.698 20.531 C 108.539 19.69 109.588 19.084 110.737 18.776 C 111.887 18.468 113.097 18.468 114.247 18.776 C 115.396 19.084 116.445 19.69 117.286 20.531 L 117.293 20.538 C 118.135 21.38 118.74 22.428 119.048 23.577 C 119.356 24.727 119.356 25.938 119.048 27.087 C 118.74 28.237 118.135 29.285 117.293 30.126 L 113.602 33.818 C 112.761 34.659 111.712 35.264 110.563 35.572 C 109.413 35.88 108.203 35.88 107.053 35.572 C 105.904 35.264 104.855 34.659 104.014 33.818 L 104.007 33.81 C 103.165 32.969 102.56 31.921 102.252 30.771 C 101.944 29.622 101.944 28.411 102.252 27.261 C 102.56 26.112 103.165 25.064 104.007 24.222"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_7"
android:pathData="M 20.537 107.696 L 24.228 104.005 C 25.069 103.164 26.118 102.559 27.267 102.251 C 28.417 101.943 29.627 101.943 30.777 102.251 C 31.926 102.559 32.975 103.164 33.816 104.005 L 33.823 104.012 C 34.665 104.854 35.27 105.902 35.578 107.052 C 35.886 108.201 35.886 109.412 35.578 110.561 C 35.27 111.711 34.665 112.759 33.823 113.601 L 30.132 117.292 C 29.291 118.133 28.242 118.739 27.093 119.047 C 25.943 119.355 24.733 119.355 23.583 119.047 C 22.434 118.739 21.385 118.133 20.544 117.292 L 20.537 117.285 C 19.695 116.443 19.09 115.395 18.782 114.245 C 18.474 113.096 18.474 111.885 18.782 110.736 C 19.09 109.586 19.695 108.538 20.537 107.696"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_8"
android:pathData="M 20.537 107.696 L 24.228 104.005 C 25.069 103.164 26.118 102.559 27.267 102.251 C 28.417 101.943 29.627 101.943 30.777 102.251 C 31.926 102.559 32.975 103.164 33.816 104.005 L 33.823 104.012 C 34.665 104.854 35.27 105.902 35.578 107.052 C 35.886 108.201 35.886 109.412 35.578 110.561 C 35.27 111.711 34.665 112.759 33.823 113.601 L 30.132 117.292 C 29.291 118.133 28.242 118.739 27.093 119.047 C 25.943 119.355 24.733 119.355 23.583 119.047 C 22.434 118.739 21.385 118.133 20.544 117.292 L 20.537 117.285 C 19.695 116.443 19.09 115.395 18.782 114.245 C 18.474 113.096 18.474 111.885 18.782 110.736 C 19.09 109.586 19.695 108.538 20.537 107.696"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<group android:name="group">
<path
android:name="path_9"
android:pathData="M 125.32 62.13 L 130.54 62.13 C 132.337 62.13 134.063 62.845 135.334 64.116 C 136.605 65.387 137.32 67.113 137.32 68.91 L 137.32 68.92 C 137.32 70.717 136.605 72.443 135.334 73.714 C 134.063 74.985 132.337 75.7 130.54 75.7 L 125.32 75.7 C 123.523 75.7 121.797 74.985 120.526 73.714 C 119.255 72.443 118.54 70.717 118.54 68.92 L 118.54 68.91 C 118.54 67.72 118.853 66.551 119.448 65.52 C 120.043 64.489 120.899 63.633 121.93 63.038 C 122.961 62.443 124.13 62.13 125.32 62.13"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_10"
android:pathData="M 125.32 62.13 L 130.54 62.13 C 132.337 62.13 134.063 62.845 135.334 64.116 C 136.605 65.387 137.32 67.113 137.32 68.91 L 137.32 68.92 C 137.32 70.717 136.605 72.443 135.334 73.714 C 134.063 74.985 132.337 75.7 130.54 75.7 L 125.32 75.7 C 123.523 75.7 121.797 74.985 120.526 73.714 C 119.255 72.443 118.54 70.717 118.54 68.92 L 118.54 68.91 C 118.54 67.72 118.853 66.551 119.448 65.52 C 120.043 64.489 120.899 63.633 121.93 63.038 C 122.961 62.443 124.13 62.13 125.32 62.13"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_1">
<path
android:name="path_11"
android:pathData="M 7.28 62.13 L 12.5 62.13 C 13.69 62.13 14.859 62.443 15.89 63.038 C 16.921 63.633 17.777 64.489 18.372 65.52 C 18.967 66.551 19.28 67.72 19.28 68.91 L 19.28 68.92 C 19.28 70.11 18.967 71.279 18.372 72.31 C 17.777 73.341 16.921 74.197 15.89 74.792 C 14.859 75.387 13.69 75.7 12.5 75.7 L 7.28 75.7 C 5.483 75.7 3.757 74.985 2.486 73.714 C 1.215 72.443 0.5 70.717 0.5 68.92 L 0.5 68.91 C 0.5 67.72 0.813 66.551 1.408 65.52 C 2.003 64.489 2.859 63.633 3.89 63.038 C 4.921 62.443 6.09 62.13 7.28 62.13"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_12"
android:pathData="M 7.28 62.13 L 12.5 62.13 C 13.69 62.13 14.859 62.443 15.89 63.038 C 16.921 63.633 17.777 64.489 18.372 65.52 C 18.967 66.551 19.28 67.72 19.28 68.91 L 19.28 68.92 C 19.28 70.11 18.967 71.279 18.372 72.31 C 17.777 73.341 16.921 74.197 15.89 74.792 C 14.859 75.387 13.69 75.7 12.5 75.7 L 7.28 75.7 C 5.483 75.7 3.757 74.985 2.486 73.714 C 1.215 72.443 0.5 70.717 0.5 68.92 L 0.5 68.91 C 0.5 67.72 0.813 66.551 1.408 65.52 C 2.003 64.489 2.859 63.633 3.89 63.038 C 4.921 62.443 6.09 62.13 7.28 62.13"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_2">
<path
android:name="path_13"
android:pathData="M 104.009 104.017 L 104.016 104.01 C 104.858 103.169 105.906 102.563 107.055 102.255 C 108.205 101.947 109.416 101.947 110.565 102.255 C 111.715 102.563 112.763 103.169 113.604 104.01 L 117.295 107.701 C 118.137 108.543 118.742 109.591 119.05 110.741 C 119.358 111.89 119.358 113.101 119.05 114.25 C 118.742 115.4 118.137 116.448 117.295 117.29 L 117.288 117.297 C 116.447 118.138 115.399 118.744 114.249 119.052 C 113.1 119.36 111.889 119.36 110.739 119.052 C 109.59 118.744 108.542 118.138 107.7 117.297 L 104.009 113.606 C 103.167 112.764 102.562 111.716 102.254 110.566 C 101.946 109.417 101.946 108.206 102.254 107.057 C 102.562 105.907 103.167 104.859 104.009 104.017"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_14"
android:pathData="M 104.009 104.017 L 104.016 104.01 C 104.858 103.169 105.906 102.563 107.055 102.255 C 108.205 101.947 109.416 101.947 110.565 102.255 C 111.715 102.563 112.763 103.169 113.604 104.01 L 117.295 107.701 C 118.137 108.543 118.742 109.591 119.05 110.741 C 119.358 111.89 119.358 113.101 119.05 114.25 C 118.742 115.4 118.137 116.448 117.295 117.29 L 117.288 117.297 C 116.447 118.138 115.399 118.744 114.249 119.052 C 113.1 119.36 111.889 119.36 110.739 119.052 C 109.59 118.744 108.542 118.138 107.7 117.297 L 104.009 113.606 C 103.167 112.764 102.562 111.716 102.254 110.566 C 101.946 109.417 101.946 108.206 102.254 107.057 C 102.562 105.907 103.167 104.859 104.009 104.017"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_3">
<path
android:name="path_15"
android:pathData="M 20.542 20.54 L 20.549 20.533 C 21.39 19.692 22.439 19.086 23.588 18.778 C 24.738 18.47 25.948 18.47 27.098 18.778 C 28.247 19.086 29.296 19.692 30.137 20.533 L 33.828 24.224 C 34.67 25.066 35.275 26.114 35.583 27.264 C 35.891 28.413 35.891 29.624 35.583 30.773 C 35.275 31.923 34.67 32.971 33.828 33.813 L 33.821 33.82 C 32.98 34.661 31.931 35.267 30.782 35.575 C 29.632 35.883 28.422 35.883 27.272 35.575 C 26.123 35.267 25.074 34.661 24.233 33.82 L 20.542 30.129 C 19.7 29.287 19.095 28.239 18.787 27.089 C 18.479 25.94 18.479 24.729 18.787 23.58 C 19.095 22.43 19.7 21.382 20.542 20.54"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_16"
android:pathData="M 20.542 20.54 L 20.549 20.533 C 21.39 19.692 22.439 19.086 23.588 18.778 C 24.738 18.47 25.948 18.47 27.098 18.778 C 28.247 19.086 29.296 19.692 30.137 20.533 L 33.828 24.224 C 34.67 25.066 35.275 26.114 35.583 27.264 C 35.891 28.413 35.891 29.624 35.583 30.773 C 35.275 31.923 34.67 32.971 33.828 33.813 L 33.821 33.82 C 32.98 34.661 31.931 35.267 30.782 35.575 C 29.632 35.883 28.422 35.883 27.272 35.575 C 26.123 35.267 25.074 34.661 24.233 33.82 L 20.542 30.129 C 19.7 29.287 19.095 28.239 18.787 27.089 C 18.479 25.94 18.479 24.729 18.787 23.58 C 19.095 22.43 19.7 21.382 20.542 20.54"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
</vector>

View file

@ -0,0 +1,111 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="26.14dp"
android:height="26.14dp"
android:viewportWidth="149"
android:viewportHeight="149">
<path
android:name="path"
android:pathData="M 74.99 41.67 C 66.241 41.67 57.842 45.149 51.655 51.335 C 45.469 57.522 41.99 65.921 41.99 74.67 C 41.99 83.419 45.469 91.818 51.655 98.005 C 57.842 104.191 66.241 107.67 74.99 107.67 C 83.739 107.67 92.138 104.191 98.325 98.005 C 104.511 91.818 107.99 83.419 107.99 74.67 C 107.99 65.921 104.511 57.522 98.325 51.335 C 92.138 45.149 83.739 41.67 74.99 41.67 Z"
android:fillColor="#ffffff"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_1"
android:pathData="M 74.66 0.5 L 74.67 0.5 C 76.467 0.5 78.193 1.215 79.464 2.486 C 80.735 3.757 81.45 5.483 81.45 7.28 L 81.45 18.26 C 81.45 20.057 80.735 21.783 79.464 23.054 C 78.193 24.325 76.467 25.04 74.67 25.04 L 74.66 25.04 C 72.863 25.04 71.137 24.325 69.866 23.054 C 68.595 21.783 67.88 20.057 67.88 18.26 L 67.88 7.28 C 67.88 5.483 68.595 3.757 69.866 2.486 C 71.137 1.215 72.863 0.5 74.66 0.5"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_2"
android:pathData="M 74.66 0.5 L 74.67 0.5 C 76.467 0.5 78.193 1.215 79.464 2.486 C 80.735 3.757 81.45 5.483 81.45 7.28 L 81.45 18.26 C 81.45 20.057 80.735 21.783 79.464 23.054 C 78.193 24.325 76.467 25.04 74.67 25.04 L 74.66 25.04 C 72.863 25.04 71.137 24.325 69.866 23.054 C 68.595 21.783 67.88 20.057 67.88 18.26 L 67.88 7.28 C 67.88 5.483 68.595 3.757 69.866 2.486 C 71.137 1.215 72.863 0.5 74.66 0.5"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_3"
android:pathData="M 74.66 124.3 L 74.67 124.3 C 76.467 124.3 78.193 125.015 79.464 126.286 C 80.735 127.557 81.45 129.283 81.45 131.08 L 81.45 142.06 C 81.45 143.857 80.735 145.583 79.464 146.854 C 78.193 148.125 76.467 148.84 74.67 148.84 L 74.66 148.84 C 72.863 148.84 71.137 148.125 69.866 146.854 C 68.595 145.583 67.88 143.857 67.88 142.06 L 67.88 131.08 C 67.88 129.283 68.595 127.557 69.866 126.286 C 71.137 125.015 72.863 124.3 74.66 124.3"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_4"
android:pathData="M 74.66 124.3 L 74.67 124.3 C 76.467 124.3 78.193 125.015 79.464 126.286 C 80.735 127.557 81.45 129.283 81.45 131.08 L 81.45 142.06 C 81.45 143.857 80.735 145.583 79.464 146.854 C 78.193 148.125 76.467 148.84 74.67 148.84 L 74.66 148.84 C 72.863 148.84 71.137 148.125 69.866 146.854 C 68.595 145.583 67.88 143.857 67.88 142.06 L 67.88 131.08 C 67.88 129.283 68.595 127.557 69.866 126.286 C 71.137 125.015 72.863 124.3 74.66 124.3"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_5"
android:pathData="M 109.76 29.988 L 117.524 22.224 C 118.366 21.382 119.414 20.777 120.564 20.469 C 121.713 20.161 122.924 20.161 124.073 20.469 C 125.223 20.777 126.271 21.382 127.113 22.224 L 127.12 22.231 C 127.961 23.072 128.567 24.121 128.875 25.27 C 129.183 26.42 129.183 27.63 128.875 28.78 C 128.567 29.929 127.961 30.978 127.12 31.819 L 119.356 39.583 C 118.514 40.425 117.466 41.03 116.316 41.338 C 115.167 41.646 113.956 41.646 112.807 41.338 C 111.657 41.03 110.609 40.425 109.767 39.583 L 109.76 39.576 C 108.919 38.735 108.314 37.686 108.005 36.537 C 107.697 35.387 107.697 34.177 108.005 33.027 C 108.314 31.878 108.919 30.829 109.76 29.988"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_6"
android:pathData="M 109.76 29.988 L 117.524 22.224 C 118.366 21.382 119.414 20.777 120.564 20.469 C 121.713 20.161 122.924 20.161 124.073 20.469 C 125.223 20.777 126.271 21.382 127.113 22.224 L 127.12 22.231 C 127.961 23.072 128.567 24.121 128.875 25.27 C 129.183 26.42 129.183 27.63 128.875 28.78 C 128.567 29.929 127.961 30.978 127.12 31.819 L 119.356 39.583 C 118.514 40.425 117.466 41.03 116.316 41.338 C 115.167 41.646 113.956 41.646 112.807 41.338 C 111.657 41.03 110.609 40.425 109.767 39.583 L 109.76 39.576 C 108.919 38.735 108.314 37.686 108.005 36.537 C 107.697 35.387 107.697 34.177 108.005 33.027 C 108.314 31.878 108.919 30.829 109.76 29.988"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_7"
android:pathData="M 22.22 117.528 L 29.984 109.764 C 30.826 108.922 31.874 108.317 33.024 108.009 C 34.173 107.701 35.384 107.701 36.533 108.009 C 37.683 108.317 38.731 108.922 39.573 109.764 L 39.58 109.771 C 40.421 110.612 41.027 111.661 41.335 112.81 C 41.643 113.96 41.643 115.17 41.335 116.32 C 41.027 117.469 40.421 118.518 39.58 119.359 L 31.816 127.123 C 30.974 127.965 29.926 128.57 28.776 128.878 C 27.627 129.186 26.416 129.186 25.267 128.878 C 24.117 128.57 23.069 127.965 22.227 127.123 L 22.22 127.116 C 21.379 126.275 20.774 125.226 20.465 124.077 C 20.157 122.927 20.157 121.717 20.465 120.567 C 20.774 119.418 21.379 118.369 22.22 117.528"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_8"
android:pathData="M 22.22 117.528 L 29.984 109.764 C 30.826 108.922 31.874 108.317 33.024 108.009 C 34.173 107.701 35.384 107.701 36.533 108.009 C 37.683 108.317 38.731 108.922 39.573 109.764 L 39.58 109.771 C 40.421 110.612 41.027 111.661 41.335 112.81 C 41.643 113.96 41.643 115.17 41.335 116.32 C 41.027 117.469 40.421 118.518 39.58 119.359 L 31.816 127.123 C 30.974 127.965 29.926 128.57 28.776 128.878 C 27.627 129.186 26.416 129.186 25.267 128.878 C 24.117 128.57 23.069 127.965 22.227 127.123 L 22.22 127.116 C 21.379 126.275 20.774 125.226 20.465 124.077 C 20.157 122.927 20.157 121.717 20.465 120.567 C 20.774 119.418 21.379 118.369 22.22 117.528"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<group android:name="group">
<path
android:name="path_9"
android:pathData="M 131.08 67.88 L 142.06 67.88 C 143.857 67.88 145.583 68.595 146.854 69.866 C 148.125 71.137 148.84 72.863 148.84 74.66 L 148.84 74.67 C 148.84 76.467 148.125 78.193 146.854 79.464 C 145.583 80.735 143.857 81.45 142.06 81.45 L 131.08 81.45 C 129.283 81.45 127.557 80.735 126.286 79.464 C 125.015 78.193 124.3 76.467 124.3 74.67 L 124.3 74.66 C 124.3 72.863 125.015 71.137 126.286 69.866 C 127.557 68.595 129.283 67.88 131.08 67.88"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_10"
android:pathData="M 131.08 67.88 L 142.06 67.88 C 143.857 67.88 145.583 68.595 146.854 69.866 C 148.125 71.137 148.84 72.863 148.84 74.66 L 148.84 74.67 C 148.84 76.467 148.125 78.193 146.854 79.464 C 145.583 80.735 143.857 81.45 142.06 81.45 L 131.08 81.45 C 129.283 81.45 127.557 80.735 126.286 79.464 C 125.015 78.193 124.3 76.467 124.3 74.67 L 124.3 74.66 C 124.3 72.863 125.015 71.137 126.286 69.866 C 127.557 68.595 129.283 67.88 131.08 67.88"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_1">
<path
android:name="path_11"
android:pathData="M 7.28 67.88 L 18.26 67.88 C 20.057 67.88 21.783 68.595 23.054 69.866 C 24.325 71.137 25.04 72.863 25.04 74.66 L 25.04 74.67 C 25.04 76.467 24.325 78.193 23.054 79.464 C 21.783 80.735 20.057 81.45 18.26 81.45 L 7.28 81.45 C 5.483 81.45 3.757 80.735 2.486 79.464 C 1.215 78.193 0.5 76.467 0.5 74.67 L 0.5 74.66 C 0.5 72.863 1.215 71.137 2.486 69.866 C 3.757 68.595 5.483 67.88 7.28 67.88"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_12"
android:pathData="M 7.28 67.88 L 18.26 67.88 C 20.057 67.88 21.783 68.595 23.054 69.866 C 24.325 71.137 25.04 72.863 25.04 74.66 L 25.04 74.67 C 25.04 76.467 24.325 78.193 23.054 79.464 C 21.783 80.735 20.057 81.45 18.26 81.45 L 7.28 81.45 C 5.483 81.45 3.757 80.735 2.486 79.464 C 1.215 78.193 0.5 76.467 0.5 74.67 L 0.5 74.66 C 0.5 72.863 1.215 71.137 2.486 69.866 C 3.757 68.595 5.483 67.88 7.28 67.88"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_2">
<path
android:name="path_13"
android:pathData="M 109.76 109.764 L 109.767 109.757 C 110.609 108.915 111.657 108.31 112.807 108.002 C 113.956 107.694 115.167 107.694 116.316 108.002 C 117.466 108.31 118.514 108.915 119.356 109.757 L 127.12 117.521 C 127.961 118.362 128.567 119.411 128.875 120.56 C 129.183 121.71 129.183 122.92 128.875 124.07 C 128.567 125.219 127.961 126.268 127.12 127.109 L 127.113 127.116 C 126.271 127.958 125.223 128.563 124.073 128.871 C 122.924 129.179 121.713 129.179 120.564 128.871 C 119.414 128.563 118.366 127.958 117.524 127.116 L 109.76 119.352 C 108.919 118.511 108.314 117.462 108.006 116.313 C 107.698 115.163 107.698 113.953 108.006 112.803 C 108.314 111.654 108.919 110.605 109.76 109.764"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_14"
android:pathData="M 109.76 109.764 L 109.767 109.757 C 110.609 108.915 111.657 108.31 112.807 108.002 C 113.956 107.694 115.167 107.694 116.316 108.002 C 117.466 108.31 118.514 108.915 119.356 109.757 L 127.12 117.521 C 127.961 118.362 128.567 119.411 128.875 120.56 C 129.183 121.71 129.183 122.92 128.875 124.07 C 128.567 125.219 127.961 126.268 127.12 127.109 L 127.113 127.116 C 126.271 127.958 125.223 128.563 124.073 128.871 C 122.924 129.179 121.713 129.179 120.564 128.871 C 119.414 128.563 118.366 127.958 117.524 127.116 L 109.76 119.352 C 108.919 118.511 108.314 117.462 108.006 116.313 C 107.698 115.163 107.698 113.953 108.006 112.803 C 108.314 111.654 108.919 110.605 109.76 109.764"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_3">
<path
android:name="path_15"
android:pathData="M 22.22 22.224 L 22.227 22.217 C 23.069 21.375 24.117 20.77 25.267 20.462 C 26.416 20.154 27.627 20.154 28.776 20.462 C 29.926 20.77 30.974 21.375 31.816 22.217 L 39.58 29.981 C 40.421 30.822 41.026 31.871 41.334 33.02 C 41.642 34.17 41.642 35.38 41.334 36.53 C 41.026 37.679 40.421 38.728 39.58 39.569 L 39.573 39.576 C 38.731 40.418 37.683 41.023 36.533 41.331 C 35.384 41.639 34.173 41.639 33.024 41.331 C 31.874 41.023 30.826 40.418 29.984 39.576 L 22.22 31.812 C 21.379 30.971 20.773 29.922 20.465 28.773 C 20.157 27.623 20.157 26.413 20.465 25.263 C 20.773 24.114 21.379 23.065 22.22 22.224"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_16"
android:pathData="M 22.22 22.224 L 22.227 22.217 C 23.069 21.375 24.117 20.77 25.267 20.462 C 26.416 20.154 27.627 20.154 28.776 20.462 C 29.926 20.77 30.974 21.375 31.816 22.217 L 39.58 29.981 C 40.421 30.822 41.026 31.871 41.334 33.02 C 41.642 34.17 41.642 35.38 41.334 36.53 C 41.026 37.679 40.421 38.728 39.58 39.569 L 39.573 39.576 C 38.731 40.418 37.683 41.023 36.533 41.331 C 35.384 41.639 34.173 41.639 33.024 41.331 C 31.874 41.023 30.826 40.418 29.984 39.576 L 22.22 31.812 C 21.379 30.971 20.773 29.922 20.465 28.773 C 20.157 27.623 20.157 26.413 20.465 25.263 C 20.773 24.114 21.379 23.065 22.22 22.224"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
</vector>

View file

@ -0,0 +1,111 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="27.19dp"
android:height="27.19dp"
android:viewportWidth="155"
android:viewportHeight="155">
<path
android:name="path"
android:pathData="M 77.86 44.54 C 69.111 44.54 60.712 48.019 54.525 54.205 C 48.339 60.392 44.86 68.791 44.86 77.54 C 44.86 86.289 48.339 94.688 54.525 100.875 C 60.712 107.061 69.111 110.54 77.86 110.54 C 86.609 110.54 95.008 107.061 101.195 100.875 C 107.381 94.688 110.86 86.289 110.86 77.54 C 110.86 68.791 107.381 60.392 101.195 54.205 C 95.008 48.019 86.609 44.54 77.86 44.54 Z"
android:fillColor="#ffffff"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_1"
android:pathData="M 77.53 0.5 L 77.54 0.5 C 79.337 0.5 81.063 1.215 82.334 2.486 C 83.605 3.757 84.32 5.483 84.32 7.28 L 84.32 21.13 C 84.32 22.32 84.007 23.489 83.412 24.52 C 82.817 25.551 81.961 26.407 80.93 27.002 C 79.899 27.597 78.73 27.91 77.54 27.91 L 77.53 27.91 C 75.733 27.91 74.007 27.195 72.736 25.924 C 71.465 24.653 70.75 22.927 70.75 21.13 L 70.75 7.28 C 70.75 5.483 71.465 3.757 72.736 2.486 C 74.007 1.215 75.733 0.5 77.53 0.5"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_2"
android:pathData="M 77.53 0.5 L 77.54 0.5 C 79.337 0.5 81.063 1.215 82.334 2.486 C 83.605 3.757 84.32 5.483 84.32 7.28 L 84.32 21.13 C 84.32 22.32 84.007 23.489 83.412 24.52 C 82.817 25.551 81.961 26.407 80.93 27.002 C 79.899 27.597 78.73 27.91 77.54 27.91 L 77.53 27.91 C 75.733 27.91 74.007 27.195 72.736 25.924 C 71.465 24.653 70.75 22.927 70.75 21.13 L 70.75 7.28 C 70.75 5.483 71.465 3.757 72.736 2.486 C 74.007 1.215 75.733 0.5 77.53 0.5"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_3"
android:pathData="M 77.53 127.17 L 77.54 127.17 C 79.337 127.17 81.063 127.885 82.334 129.156 C 83.605 130.427 84.32 132.153 84.32 133.95 L 84.32 147.8 C 84.32 149.597 83.605 151.323 82.334 152.594 C 81.063 153.865 79.337 154.58 77.54 154.58 L 77.53 154.58 C 75.733 154.58 74.007 153.865 72.736 152.594 C 71.465 151.323 70.75 149.597 70.75 147.8 L 70.75 133.95 C 70.75 132.153 71.465 130.427 72.736 129.156 C 74.007 127.885 75.733 127.17 77.53 127.17"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_4"
android:pathData="M 77.53 127.17 L 77.54 127.17 C 79.337 127.17 81.063 127.885 82.334 129.156 C 83.605 130.427 84.32 132.153 84.32 133.95 L 84.32 147.8 C 84.32 149.597 83.605 151.323 82.334 152.594 C 81.063 153.865 79.337 154.58 77.54 154.58 L 77.53 154.58 C 75.733 154.58 74.007 153.865 72.736 152.594 C 71.465 151.323 70.75 149.597 70.75 147.8 L 70.75 133.95 C 70.75 132.153 71.465 130.427 72.736 129.156 C 74.007 127.885 75.733 127.17 77.53 127.17"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_5"
android:pathData="M 112.629 32.861 L 122.423 23.067 C 123.694 21.796 125.419 21.081 127.217 21.081 C 129.014 21.081 130.74 21.796 132.011 23.067 L 132.018 23.074 C 132.859 23.916 133.465 24.964 133.773 26.114 C 134.081 27.263 134.081 28.474 133.773 29.623 C 133.465 30.773 132.859 31.821 132.018 32.663 L 122.225 42.456 C 121.383 43.297 120.335 43.903 119.185 44.211 C 118.036 44.519 116.825 44.519 115.676 44.211 C 114.526 43.903 113.478 43.297 112.636 42.456 L 112.629 42.449 C 111.788 41.607 111.182 40.559 110.874 39.409 C 110.566 38.26 110.566 37.049 110.874 35.9 C 111.182 34.75 111.788 33.702 112.629 32.861"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_6"
android:pathData="M 112.629 32.861 L 122.423 23.067 C 123.694 21.796 125.419 21.081 127.217 21.081 C 129.014 21.081 130.74 21.796 132.011 23.067 L 132.018 23.074 C 132.859 23.916 133.465 24.964 133.773 26.114 C 134.081 27.263 134.081 28.474 133.773 29.623 C 133.465 30.773 132.859 31.821 132.018 32.663 L 122.225 42.456 C 121.383 43.297 120.335 43.903 119.185 44.211 C 118.036 44.519 116.825 44.519 115.676 44.211 C 114.526 43.903 113.478 43.297 112.636 42.456 L 112.629 42.449 C 111.788 41.607 111.182 40.559 110.874 39.409 C 110.566 38.26 110.566 37.049 110.874 35.9 C 111.182 34.75 111.788 33.702 112.629 32.861"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_7"
android:pathData="M 23.059 122.417 L 32.853 112.624 C 34.124 111.353 35.849 110.638 37.647 110.638 C 39.444 110.638 41.17 111.353 42.441 112.624 L 42.448 112.631 C 43.289 113.473 43.895 114.521 44.203 115.671 C 44.511 116.82 44.511 118.031 44.203 119.18 C 43.895 120.33 43.289 121.378 42.448 122.219 L 32.655 132.013 C 31.813 132.854 30.765 133.46 29.615 133.768 C 28.466 134.076 27.255 134.076 26.106 133.768 C 24.956 133.46 23.908 132.854 23.066 132.013 L 23.059 132.006 C 22.218 131.164 21.612 130.116 21.304 128.966 C 20.996 127.817 20.996 126.606 21.304 125.457 C 21.612 124.307 22.218 123.259 23.059 122.417"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_8"
android:pathData="M 23.059 122.417 L 32.853 112.624 C 34.124 111.353 35.849 110.638 37.647 110.638 C 39.444 110.638 41.17 111.353 42.441 112.624 L 42.448 112.631 C 43.289 113.473 43.895 114.521 44.203 115.671 C 44.511 116.82 44.511 118.031 44.203 119.18 C 43.895 120.33 43.289 121.378 42.448 122.219 L 32.655 132.013 C 31.813 132.854 30.765 133.46 29.615 133.768 C 28.466 134.076 27.255 134.076 26.106 133.768 C 24.956 133.46 23.908 132.854 23.066 132.013 L 23.059 132.006 C 22.218 131.164 21.612 130.116 21.304 128.966 C 20.996 127.817 20.996 126.606 21.304 125.457 C 21.612 124.307 22.218 123.259 23.059 122.417"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<group android:name="group">
<path
android:name="path_9"
android:pathData="M 133.95 70.75 L 147.8 70.75 C 149.597 70.75 151.323 71.465 152.594 72.736 C 153.865 74.007 154.58 75.733 154.58 77.53 L 154.58 77.54 C 154.58 79.337 153.865 81.063 152.594 82.334 C 151.323 83.605 149.597 84.32 147.8 84.32 L 133.95 84.32 C 132.153 84.32 130.427 83.605 129.156 82.334 C 127.885 81.063 127.17 79.337 127.17 77.54 L 127.17 77.53 C 127.17 75.733 127.885 74.007 129.156 72.736 C 130.427 71.465 132.153 70.75 133.95 70.75"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_10"
android:pathData="M 133.95 70.75 L 147.8 70.75 C 149.597 70.75 151.323 71.465 152.594 72.736 C 153.865 74.007 154.58 75.733 154.58 77.53 L 154.58 77.54 C 154.58 79.337 153.865 81.063 152.594 82.334 C 151.323 83.605 149.597 84.32 147.8 84.32 L 133.95 84.32 C 132.153 84.32 130.427 83.605 129.156 82.334 C 127.885 81.063 127.17 79.337 127.17 77.54 L 127.17 77.53 C 127.17 75.733 127.885 74.007 129.156 72.736 C 130.427 71.465 132.153 70.75 133.95 70.75"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_1">
<path
android:name="path_11"
android:pathData="M 7.28 70.75 L 21.13 70.75 C 22.32 70.75 23.489 71.063 24.52 71.658 C 25.551 72.253 26.407 73.109 27.002 74.14 C 27.597 75.171 27.91 76.34 27.91 77.53 L 27.91 77.54 C 27.91 78.73 27.597 79.899 27.002 80.93 C 26.407 81.961 25.551 82.817 24.52 83.412 C 23.489 84.007 22.32 84.32 21.13 84.32 L 7.28 84.32 C 5.483 84.32 3.757 83.605 2.486 82.334 C 1.215 81.063 0.5 79.337 0.5 77.54 L 0.5 77.53 C 0.5 75.733 1.215 74.007 2.486 72.736 C 3.757 71.465 5.483 70.75 7.28 70.75"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_12"
android:pathData="M 7.28 70.75 L 21.13 70.75 C 22.32 70.75 23.489 71.063 24.52 71.658 C 25.551 72.253 26.407 73.109 27.002 74.14 C 27.597 75.171 27.91 76.34 27.91 77.53 L 27.91 77.54 C 27.91 78.73 27.597 79.899 27.002 80.93 C 26.407 81.961 25.551 82.817 24.52 83.412 C 23.489 84.007 22.32 84.32 21.13 84.32 L 7.28 84.32 C 5.483 84.32 3.757 83.605 2.486 82.334 C 1.215 81.063 0.5 79.337 0.5 77.54 L 0.5 77.53 C 0.5 75.733 1.215 74.007 2.486 72.736 C 3.757 71.465 5.483 70.75 7.28 70.75"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_2">
<path
android:name="path_13"
android:pathData="M 112.628 112.633 L 112.635 112.626 C 113.476 111.784 114.525 111.179 115.674 110.871 C 116.824 110.563 118.034 110.563 119.184 110.871 C 120.333 111.179 121.382 111.784 122.223 112.626 L 132.016 122.419 C 132.858 123.26 133.463 124.309 133.771 125.458 C 134.079 126.608 134.079 127.818 133.771 128.968 C 133.463 130.117 132.858 131.166 132.016 132.007 L 132.009 132.014 C 130.738 133.285 129.013 134 127.215 134 C 125.418 134 123.692 133.285 122.421 132.014 L 112.628 122.221 C 111.786 121.38 111.181 120.331 110.873 119.182 C 110.565 118.032 110.565 116.822 110.873 115.672 C 111.181 114.523 111.786 113.474 112.628 112.633"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_14"
android:pathData="M 112.628 112.633 L 112.635 112.626 C 113.476 111.784 114.525 111.179 115.674 110.871 C 116.824 110.563 118.034 110.563 119.184 110.871 C 120.333 111.179 121.382 111.784 122.223 112.626 L 132.016 122.419 C 132.858 123.26 133.463 124.309 133.771 125.458 C 134.079 126.608 134.079 127.818 133.771 128.968 C 133.463 130.117 132.858 131.166 132.016 132.007 L 132.009 132.014 C 130.738 133.285 129.013 134 127.215 134 C 125.418 134 123.692 133.285 122.421 132.014 L 112.628 122.221 C 111.786 121.38 111.181 120.331 110.873 119.182 C 110.565 118.032 110.565 116.822 110.873 115.672 C 111.181 114.523 111.786 113.474 112.628 112.633"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_3">
<path
android:name="path_15"
android:pathData="M 23.071 23.063 L 23.078 23.056 C 23.919 22.214 24.968 21.609 26.117 21.301 C 27.267 20.993 28.477 20.993 29.627 21.301 C 30.776 21.609 31.825 22.214 32.666 23.056 L 42.46 32.849 C 43.301 33.69 43.906 34.739 44.214 35.888 C 44.522 37.038 44.522 38.248 44.214 39.398 C 43.906 40.547 43.301 41.596 42.46 42.437 L 42.452 42.444 C 41.611 43.286 40.563 43.891 39.413 44.199 C 38.264 44.507 37.053 44.507 35.903 44.199 C 34.754 43.891 33.706 43.286 32.864 42.444 L 23.071 32.651 C 22.229 31.81 21.624 30.761 21.316 29.612 C 21.008 28.462 21.008 27.252 21.316 26.102 C 21.624 24.953 22.229 23.904 23.071 23.063"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_16"
android:pathData="M 23.071 23.063 L 23.078 23.056 C 23.919 22.214 24.968 21.609 26.117 21.301 C 27.267 20.993 28.477 20.993 29.627 21.301 C 30.776 21.609 31.825 22.214 32.666 23.056 L 42.46 32.849 C 43.301 33.69 43.906 34.739 44.214 35.888 C 44.522 37.038 44.522 38.248 44.214 39.398 C 43.906 40.547 43.301 41.596 42.46 42.437 L 42.452 42.444 C 41.611 43.286 40.563 43.891 39.413 44.199 C 38.264 44.507 37.053 44.507 35.903 44.199 C 34.754 43.891 33.706 43.286 32.864 42.444 L 23.071 32.651 C 22.229 31.81 21.624 30.761 21.316 29.612 C 21.008 28.462 21.008 27.252 21.316 26.102 C 21.624 24.953 22.229 23.904 23.071 23.063"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
</vector>

View file

@ -0,0 +1,111 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="29.12dp"
android:height="29.12dp"
android:viewportWidth="166"
android:viewportHeight="166">
<path
android:name="path"
android:pathData="M 83.34 50.01 C 76.372 50.01 69.58 52.217 63.943 56.312 C 58.306 60.408 54.108 66.186 51.955 72.812 C 49.802 79.439 49.802 86.581 51.955 93.208 C 54.108 99.834 58.306 105.612 63.943 109.708 C 69.58 113.803 76.372 116.01 83.34 116.01 C 92.089 116.01 100.488 112.531 106.675 106.345 C 112.861 100.158 116.34 91.759 116.34 83.01 C 116.34 74.261 112.861 65.862 106.675 59.675 C 100.488 53.489 92.089 50.01 83.34 50.01 Z"
android:fillColor="#ffffff"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_1"
android:pathData="M 83.01 0.5 L 83.02 0.5 C 84.817 0.5 86.543 1.215 87.814 2.486 C 89.085 3.757 89.8 5.483 89.8 7.28 L 89.8 26.6 C 89.8 28.397 89.085 30.123 87.814 31.394 C 86.543 32.665 84.817 33.38 83.02 33.38 L 83.01 33.38 C 81.213 33.38 79.487 32.665 78.216 31.394 C 76.945 30.123 76.23 28.397 76.23 26.6 L 76.23 7.28 C 76.23 5.483 76.945 3.757 78.216 2.486 C 79.487 1.215 81.213 0.5 83.01 0.5"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_2"
android:pathData="M 83.01 0.5 L 83.02 0.5 C 84.817 0.5 86.543 1.215 87.814 2.486 C 89.085 3.757 89.8 5.483 89.8 7.28 L 89.8 26.6 C 89.8 28.397 89.085 30.123 87.814 31.394 C 86.543 32.665 84.817 33.38 83.02 33.38 L 83.01 33.38 C 81.213 33.38 79.487 32.665 78.216 31.394 C 76.945 30.123 76.23 28.397 76.23 26.6 L 76.23 7.28 C 76.23 5.483 76.945 3.757 78.216 2.486 C 79.487 1.215 81.213 0.5 83.01 0.5"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_3"
android:pathData="M 83.01 132.65 L 83.02 132.65 C 84.817 132.65 86.543 133.365 87.814 134.636 C 89.085 135.907 89.8 137.633 89.8 139.43 L 89.8 158.75 C 89.8 160.547 89.085 162.273 87.814 163.544 C 86.543 164.815 84.817 165.53 83.02 165.53 L 83.01 165.53 C 81.213 165.53 79.487 164.815 78.216 163.544 C 76.945 162.273 76.23 160.547 76.23 158.75 L 76.23 139.43 C 76.23 137.633 76.945 135.907 78.216 134.636 C 79.487 133.365 81.213 132.65 83.01 132.65"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_4"
android:pathData="M 83.01 132.65 L 83.02 132.65 C 84.817 132.65 86.543 133.365 87.814 134.636 C 89.085 135.907 89.8 137.633 89.8 139.43 L 89.8 158.75 C 89.8 160.547 89.085 162.273 87.814 163.544 C 86.543 164.815 84.817 165.53 83.02 165.53 L 83.01 165.53 C 81.213 165.53 79.487 164.815 78.216 163.544 C 76.945 162.273 76.23 160.547 76.23 158.75 L 76.23 139.43 C 76.23 137.633 76.945 135.907 78.216 134.636 C 79.487 133.365 81.213 132.65 83.01 132.65"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_5"
android:pathData="M 118.112 38.334 L 131.773 24.673 C 132.614 23.832 133.663 23.226 134.812 22.918 C 135.962 22.61 137.172 22.61 138.322 22.918 C 139.471 23.226 140.52 23.832 141.361 24.673 L 141.368 24.68 C 142.21 25.522 142.815 26.57 143.123 27.72 C 143.431 28.869 143.431 30.08 143.123 31.229 C 142.815 32.379 142.21 33.427 141.368 34.269 L 127.707 47.93 C 126.866 48.771 125.817 49.377 124.668 49.685 C 123.518 49.993 122.308 49.993 121.158 49.685 C 120.009 49.377 118.96 48.771 118.119 47.93 L 118.112 47.923 C 117.27 47.081 116.665 46.033 116.357 44.883 C 116.049 43.734 116.049 42.523 116.357 41.374 C 116.665 40.224 117.27 39.176 118.112 38.334"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_6"
android:pathData="M 118.112 38.334 L 131.773 24.673 C 132.614 23.832 133.663 23.226 134.812 22.918 C 135.962 22.61 137.172 22.61 138.322 22.918 C 139.471 23.226 140.52 23.832 141.361 24.673 L 141.368 24.68 C 142.21 25.522 142.815 26.57 143.123 27.72 C 143.431 28.869 143.431 30.08 143.123 31.229 C 142.815 32.379 142.21 33.427 141.368 34.269 L 127.707 47.93 C 126.866 48.771 125.817 49.377 124.668 49.685 C 123.518 49.993 122.308 49.993 121.158 49.685 C 120.009 49.377 118.96 48.771 118.119 47.93 L 118.112 47.923 C 117.27 47.081 116.665 46.033 116.357 44.883 C 116.049 43.734 116.049 42.523 116.357 41.374 C 116.665 40.224 117.27 39.176 118.112 38.334"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_7"
android:pathData="M 24.662 131.769 L 38.323 118.107 C 39.164 117.266 40.213 116.66 41.362 116.352 C 42.512 116.044 43.722 116.044 44.872 116.352 C 46.021 116.66 47.07 117.266 47.911 118.107 L 47.918 118.114 C 48.76 118.956 49.365 120.004 49.673 121.154 C 49.981 122.303 49.981 123.514 49.673 124.663 C 49.365 125.813 48.76 126.861 47.918 127.703 L 34.257 141.364 C 33.416 142.205 32.367 142.811 31.218 143.119 C 30.068 143.427 28.858 143.427 27.708 143.119 C 26.559 142.811 25.51 142.205 24.669 141.364 L 24.662 141.357 C 23.82 140.515 23.215 139.467 22.907 138.318 C 22.599 137.168 22.599 135.957 22.907 134.808 C 23.215 133.658 23.82 132.61 24.662 131.769"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_8"
android:pathData="M 24.662 131.769 L 38.323 118.107 C 39.164 117.266 40.213 116.66 41.362 116.352 C 42.512 116.044 43.722 116.044 44.872 116.352 C 46.021 116.66 47.07 117.266 47.911 118.107 L 47.918 118.114 C 48.76 118.956 49.365 120.004 49.673 121.154 C 49.981 122.303 49.981 123.514 49.673 124.663 C 49.365 125.813 48.76 126.861 47.918 127.703 L 34.257 141.364 C 33.416 142.205 32.367 142.811 31.218 143.119 C 30.068 143.427 28.858 143.427 27.708 143.119 C 26.559 142.811 25.51 142.205 24.669 141.364 L 24.662 141.357 C 23.82 140.515 23.215 139.467 22.907 138.318 C 22.599 137.168 22.599 135.957 22.907 134.808 C 23.215 133.658 23.82 132.61 24.662 131.769"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<group android:name="group">
<path
android:name="path_9"
android:pathData="M 139.43 76.23 L 158.75 76.23 C 160.547 76.23 162.273 76.945 163.544 78.216 C 164.815 79.487 165.53 81.213 165.53 83.01 L 165.53 83.02 C 165.53 84.817 164.815 86.543 163.544 87.814 C 162.273 89.085 160.547 89.8 158.75 89.8 L 139.43 89.8 C 137.633 89.8 135.907 89.085 134.636 87.814 C 133.365 86.543 132.65 84.817 132.65 83.02 L 132.65 83.01 C 132.65 81.213 133.365 79.487 134.636 78.216 C 135.907 76.945 137.633 76.23 139.43 76.23"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_10"
android:pathData="M 139.43 76.23 L 158.75 76.23 C 160.547 76.23 162.273 76.945 163.544 78.216 C 164.815 79.487 165.53 81.213 165.53 83.01 L 165.53 83.02 C 165.53 84.817 164.815 86.543 163.544 87.814 C 162.273 89.085 160.547 89.8 158.75 89.8 L 139.43 89.8 C 137.633 89.8 135.907 89.085 134.636 87.814 C 133.365 86.543 132.65 84.817 132.65 83.02 L 132.65 83.01 C 132.65 81.213 133.365 79.487 134.636 78.216 C 135.907 76.945 137.633 76.23 139.43 76.23"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_1">
<path
android:name="path_11"
android:pathData="M 7.28 76.23 L 26.6 76.23 C 28.397 76.23 30.123 76.945 31.394 78.216 C 32.665 79.487 33.38 81.213 33.38 83.01 L 33.38 83.02 C 33.38 84.817 32.665 86.543 31.394 87.814 C 30.123 89.085 28.397 89.8 26.6 89.8 L 7.28 89.8 C 6.09 89.8 4.921 89.487 3.89 88.892 C 2.859 88.297 2.003 87.441 1.408 86.41 C 0.813 85.379 0.5 84.21 0.5 83.02 L 0.5 83.01 C 0.5 81.213 1.215 79.487 2.486 78.216 C 3.757 76.945 5.483 76.23 7.28 76.23"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_12"
android:pathData="M 7.28 76.23 L 26.6 76.23 C 28.397 76.23 30.123 76.945 31.394 78.216 C 32.665 79.487 33.38 81.213 33.38 83.01 L 33.38 83.02 C 33.38 84.817 32.665 86.543 31.394 87.814 C 30.123 89.085 28.397 89.8 26.6 89.8 L 7.28 89.8 C 6.09 89.8 4.921 89.487 3.89 88.892 C 2.859 88.297 2.003 87.441 1.408 86.41 C 0.813 85.379 0.5 84.21 0.5 83.02 L 0.5 83.01 C 0.5 81.213 1.215 79.487 2.486 78.216 C 3.757 76.945 5.483 76.23 7.28 76.23"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_2">
<path
android:name="path_13"
android:pathData="M 118.104 118.115 L 118.111 118.108 C 118.952 117.267 120.001 116.661 121.15 116.353 C 122.3 116.045 123.51 116.045 124.66 116.353 C 125.809 116.661 126.858 117.267 127.699 118.108 L 141.36 131.769 C 142.202 132.611 142.807 133.659 143.115 134.809 C 143.423 135.958 143.423 137.169 143.115 138.318 C 142.807 139.468 142.202 140.516 141.36 141.358 L 141.353 141.365 C 140.512 142.206 139.463 142.812 138.314 143.12 C 137.164 143.428 135.954 143.428 134.804 143.12 C 133.655 142.812 132.607 142.206 131.765 141.365 L 118.104 127.704 C 116.833 126.433 116.118 124.707 116.118 122.909 C 116.118 121.112 116.833 119.386 118.104 118.115"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_14"
android:pathData="M 118.104 118.115 L 118.111 118.108 C 118.952 117.267 120.001 116.661 121.15 116.353 C 122.3 116.045 123.51 116.045 124.66 116.353 C 125.809 116.661 126.858 117.267 127.699 118.108 L 141.36 131.769 C 142.202 132.611 142.807 133.659 143.115 134.809 C 143.423 135.958 143.423 137.169 143.115 138.318 C 142.807 139.468 142.202 140.516 141.36 141.358 L 141.353 141.365 C 140.512 142.206 139.463 142.812 138.314 143.12 C 137.164 143.428 135.954 143.428 134.804 143.12 C 133.655 142.812 132.607 142.206 131.765 141.365 L 118.104 127.704 C 116.833 126.433 116.118 124.707 116.118 122.909 C 116.118 121.112 116.833 119.386 118.104 118.115"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_3">
<path
android:name="path_15"
android:pathData="M 24.67 24.665 L 24.677 24.658 C 25.518 23.817 26.567 23.211 27.716 22.903 C 28.866 22.595 30.076 22.595 31.226 22.903 C 32.375 23.211 33.424 23.817 34.265 24.658 L 47.926 38.319 C 48.768 39.161 49.373 40.209 49.681 41.359 C 49.989 42.508 49.989 43.719 49.681 44.868 C 49.373 46.018 48.768 47.066 47.926 47.908 L 47.919 47.915 C 47.078 48.756 46.029 49.362 44.88 49.67 C 43.73 49.978 42.52 49.978 41.37 49.67 C 40.221 49.362 39.172 48.756 38.331 47.915 L 24.67 34.254 C 23.399 32.983 22.684 31.257 22.684 29.459 C 22.684 27.662 23.399 25.936 24.67 24.665"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_16"
android:pathData="M 24.67 24.665 L 24.677 24.658 C 25.518 23.817 26.567 23.211 27.716 22.903 C 28.866 22.595 30.076 22.595 31.226 22.903 C 32.375 23.211 33.424 23.817 34.265 24.658 L 47.926 38.319 C 48.768 39.161 49.373 40.209 49.681 41.359 C 49.989 42.508 49.989 43.719 49.681 44.868 C 49.373 46.018 48.768 47.066 47.926 47.908 L 47.919 47.915 C 47.078 48.756 46.029 49.362 44.88 49.67 C 43.73 49.978 42.52 49.978 41.37 49.67 C 40.221 49.362 39.172 48.756 38.331 47.915 L 24.67 34.254 C 23.399 32.983 22.684 31.257 22.684 29.459 C 22.684 27.662 23.399 25.936 24.67 24.665"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
</vector>

View file

@ -0,0 +1,111 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="31.0dp"
android:height="31.0dp"
android:viewportWidth="177"
android:viewportHeight="177">
<path
android:name="path"
android:pathData="M 89.21 55.88 C 80.461 55.88 72.062 59.359 65.875 65.545 C 59.689 71.732 56.21 80.131 56.21 88.88 C 56.21 97.629 59.689 106.028 65.875 112.215 C 72.062 118.401 80.461 121.88 89.21 121.88 C 97.959 121.88 106.358 118.401 112.545 112.215 C 118.731 106.028 122.21 97.629 122.21 88.88 C 122.21 80.131 118.731 71.732 112.545 65.545 C 106.358 59.359 97.959 55.88 89.21 55.88 Z"
android:fillColor="#ffffff"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_1"
android:pathData="M 88.88 0.5 L 88.89 0.5 C 90.687 0.5 92.413 1.215 93.684 2.486 C 94.955 3.757 95.67 5.483 95.67 7.28 L 95.67 32.47 C 95.67 34.267 94.955 35.993 93.684 37.264 C 92.413 38.535 90.687 39.25 88.89 39.25 L 88.88 39.25 C 87.083 39.25 85.357 38.535 84.086 37.264 C 82.815 35.993 82.1 34.267 82.1 32.47 L 82.1 7.28 C 82.1 5.483 82.815 3.757 84.086 2.486 C 85.357 1.215 87.083 0.5 88.88 0.5"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_2"
android:pathData="M 88.88 0.5 L 88.89 0.5 C 90.687 0.5 92.413 1.215 93.684 2.486 C 94.955 3.757 95.67 5.483 95.67 7.28 L 95.67 32.47 C 95.67 34.267 94.955 35.993 93.684 37.264 C 92.413 38.535 90.687 39.25 88.89 39.25 L 88.88 39.25 C 87.083 39.25 85.357 38.535 84.086 37.264 C 82.815 35.993 82.1 34.267 82.1 32.47 L 82.1 7.28 C 82.1 5.483 82.815 3.757 84.086 2.486 C 85.357 1.215 87.083 0.5 88.88 0.5"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_3"
android:pathData="M 88.88 138.51 L 88.89 138.51 C 90.687 138.51 92.413 139.225 93.684 140.496 C 94.955 141.767 95.67 143.493 95.67 145.29 L 95.67 170.48 C 95.67 172.277 94.955 174.003 93.684 175.274 C 92.413 176.545 90.687 177.26 88.89 177.26 L 88.88 177.26 C 87.083 177.26 85.357 176.545 84.086 175.274 C 82.815 174.003 82.1 172.277 82.1 170.48 L 82.1 145.29 C 82.1 143.493 82.815 141.767 84.086 140.496 C 85.357 139.225 87.083 138.51 88.88 138.51"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_4"
android:pathData="M 88.88 138.51 L 88.89 138.51 C 90.687 138.51 92.413 139.225 93.684 140.496 C 94.955 141.767 95.67 143.493 95.67 145.29 L 95.67 170.48 C 95.67 172.277 94.955 174.003 93.684 175.274 C 92.413 176.545 90.687 177.26 88.89 177.26 L 88.88 177.26 C 87.083 177.26 85.357 176.545 84.086 175.274 C 82.815 174.003 82.1 172.277 82.1 170.48 L 82.1 145.29 C 82.1 143.493 82.815 141.767 84.086 140.496 C 85.357 139.225 87.083 138.51 88.88 138.51"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_5"
android:pathData="M 123.973 44.202 L 141.785 26.39 C 142.626 25.548 143.675 24.943 144.824 24.635 C 145.974 24.327 147.184 24.327 148.334 24.635 C 149.483 24.943 150.532 25.548 151.373 26.39 L 151.38 26.397 C 152.222 27.238 152.827 28.287 153.135 29.436 C 153.443 30.586 153.443 31.796 153.135 32.946 C 152.827 34.095 152.222 35.144 151.38 35.985 L 133.568 53.797 C 132.297 55.068 130.571 55.783 128.774 55.783 C 126.977 55.783 125.251 55.068 123.98 53.797 L 123.973 53.79 C 123.131 52.949 122.526 51.9 122.218 50.751 C 121.91 49.601 121.91 48.391 122.218 47.241 C 122.526 46.092 123.131 45.043 123.973 44.202"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_6"
android:pathData="M 123.973 44.202 L 141.785 26.39 C 142.626 25.548 143.675 24.943 144.824 24.635 C 145.974 24.327 147.184 24.327 148.334 24.635 C 149.483 24.943 150.532 25.548 151.373 26.39 L 151.38 26.397 C 152.222 27.238 152.827 28.287 153.135 29.436 C 153.443 30.586 153.443 31.796 153.135 32.946 C 152.827 34.095 152.222 35.144 151.38 35.985 L 133.568 53.797 C 132.297 55.068 130.571 55.783 128.774 55.783 C 126.977 55.783 125.251 55.068 123.98 53.797 L 123.973 53.79 C 123.131 52.949 122.526 51.9 122.218 50.751 C 121.91 49.601 121.91 48.391 122.218 47.241 C 122.526 46.092 123.131 45.043 123.973 44.202"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_7"
android:pathData="M 26.39 141.792 L 44.202 123.98 C 45.043 123.138 46.092 122.533 47.241 122.225 C 48.391 121.917 49.601 121.917 50.751 122.225 C 51.9 122.533 52.949 123.138 53.79 123.98 L 53.797 123.987 C 54.639 124.828 55.244 125.877 55.552 127.026 C 55.86 128.176 55.86 129.386 55.552 130.536 C 55.244 131.685 54.639 132.734 53.797 133.575 L 35.985 151.387 C 34.714 152.658 32.989 153.373 31.191 153.373 C 29.394 153.373 27.668 152.658 26.397 151.387 L 26.39 151.38 C 25.548 150.539 24.943 149.49 24.635 148.341 C 24.327 147.191 24.327 145.981 24.635 144.831 C 24.943 143.682 25.548 142.633 26.39 141.792"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_8"
android:pathData="M 26.39 141.792 L 44.202 123.98 C 45.043 123.138 46.092 122.533 47.241 122.225 C 48.391 121.917 49.601 121.917 50.751 122.225 C 51.9 122.533 52.949 123.138 53.79 123.98 L 53.797 123.987 C 54.639 124.828 55.244 125.877 55.552 127.026 C 55.86 128.176 55.86 129.386 55.552 130.536 C 55.244 131.685 54.639 132.734 53.797 133.575 L 35.985 151.387 C 34.714 152.658 32.989 153.373 31.191 153.373 C 29.394 153.373 27.668 152.658 26.397 151.387 L 26.39 151.38 C 25.548 150.539 24.943 149.49 24.635 148.341 C 24.327 147.191 24.327 145.981 24.635 144.831 C 24.943 143.682 25.548 142.633 26.39 141.792"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<group android:name="group">
<path
android:name="path_9"
android:pathData="M 145.29 82.1 L 170.48 82.1 C 172.277 82.1 174.003 82.815 175.274 84.086 C 176.545 85.357 177.26 87.083 177.26 88.88 L 177.26 88.89 C 177.26 90.687 176.545 92.413 175.274 93.684 C 174.003 94.955 172.277 95.67 170.48 95.67 L 145.29 95.67 C 143.493 95.67 141.767 94.955 140.496 93.684 C 139.225 92.413 138.51 90.687 138.51 88.89 L 138.51 88.88 C 138.51 87.083 139.225 85.357 140.496 84.086 C 141.767 82.815 143.493 82.1 145.29 82.1"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_10"
android:pathData="M 145.29 82.1 L 170.48 82.1 C 172.277 82.1 174.003 82.815 175.274 84.086 C 176.545 85.357 177.26 87.083 177.26 88.88 L 177.26 88.89 C 177.26 90.687 176.545 92.413 175.274 93.684 C 174.003 94.955 172.277 95.67 170.48 95.67 L 145.29 95.67 C 143.493 95.67 141.767 94.955 140.496 93.684 C 139.225 92.413 138.51 90.687 138.51 88.89 L 138.51 88.88 C 138.51 87.083 139.225 85.357 140.496 84.086 C 141.767 82.815 143.493 82.1 145.29 82.1"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_1">
<path
android:name="path_11"
android:pathData="M 7.28 82.1 L 32.47 82.1 C 34.267 82.1 35.993 82.815 37.264 84.086 C 38.535 85.357 39.25 87.083 39.25 88.88 L 39.25 88.89 C 39.25 90.687 38.535 92.413 37.264 93.684 C 35.993 94.955 34.267 95.67 32.47 95.67 L 7.28 95.67 C 5.483 95.67 3.757 94.955 2.486 93.684 C 1.215 92.413 0.5 90.687 0.5 88.89 L 0.5 88.88 C 0.5 87.083 1.215 85.357 2.486 84.086 C 3.757 82.815 5.483 82.1 7.28 82.1"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_12"
android:pathData="M 7.28 82.1 L 32.47 82.1 C 34.267 82.1 35.993 82.815 37.264 84.086 C 38.535 85.357 39.25 87.083 39.25 88.88 L 39.25 88.89 C 39.25 90.687 38.535 92.413 37.264 93.684 C 35.993 94.955 34.267 95.67 32.47 95.67 L 7.28 95.67 C 5.483 95.67 3.757 94.955 2.486 93.684 C 1.215 92.413 0.5 90.687 0.5 88.89 L 0.5 88.88 C 0.5 87.083 1.215 85.357 2.486 84.086 C 3.757 82.815 5.483 82.1 7.28 82.1"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_2">
<path
android:name="path_13"
android:pathData="M 123.976 123.976 L 123.983 123.969 C 124.825 123.128 125.873 122.522 127.023 122.214 C 128.172 121.906 129.383 121.906 130.532 122.214 C 131.682 122.522 132.73 123.128 133.572 123.969 L 151.384 141.781 C 152.655 143.052 153.37 144.778 153.37 146.575 C 153.37 148.373 152.655 150.099 151.384 151.37 L 151.377 151.377 C 150.535 152.218 149.487 152.823 148.337 153.131 C 147.188 153.439 145.977 153.439 144.828 153.131 C 143.678 152.823 142.63 152.218 141.788 151.377 L 123.976 133.565 C 122.705 132.294 121.991 130.568 121.991 128.77 C 121.991 126.973 122.705 125.247 123.976 123.976"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_14"
android:pathData="M 123.976 123.976 L 123.983 123.969 C 124.825 123.128 125.873 122.522 127.023 122.214 C 128.172 121.906 129.383 121.906 130.532 122.214 C 131.682 122.522 132.73 123.128 133.572 123.969 L 151.384 141.781 C 152.655 143.052 153.37 144.778 153.37 146.575 C 153.37 148.373 152.655 150.099 151.384 151.37 L 151.377 151.377 C 150.535 152.218 149.487 152.823 148.337 153.131 C 147.188 153.439 145.977 153.439 144.828 153.131 C 143.678 152.823 142.63 152.218 141.788 151.377 L 123.976 133.565 C 122.705 132.294 121.991 130.568 121.991 128.77 C 121.991 126.973 122.705 125.247 123.976 123.976"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_3">
<path
android:name="path_15"
android:pathData="M 26.386 26.393 L 26.393 26.386 C 27.235 25.545 28.283 24.939 29.433 24.631 C 30.582 24.323 31.793 24.323 32.942 24.631 C 34.092 24.939 35.14 25.545 35.982 26.386 L 53.794 44.198 C 55.065 45.469 55.779 47.195 55.779 48.992 C 55.779 50.79 55.065 52.516 53.794 53.787 L 53.787 53.794 C 52.945 54.635 51.897 55.241 50.747 55.549 C 49.598 55.857 48.387 55.857 47.238 55.549 C 46.088 55.241 45.04 54.635 44.198 53.794 L 26.386 35.982 C 25.115 34.711 24.4 32.985 24.4 31.188 C 24.4 29.39 25.115 27.664 26.386 26.393"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_16"
android:pathData="M 26.386 26.393 L 26.393 26.386 C 27.235 25.545 28.283 24.939 29.433 24.631 C 30.582 24.323 31.793 24.323 32.942 24.631 C 34.092 24.939 35.14 25.545 35.982 26.386 L 53.794 44.198 C 55.065 45.469 55.779 47.195 55.779 48.992 C 55.779 50.79 55.065 52.516 53.794 53.787 L 53.787 53.794 C 52.945 54.635 51.897 55.241 50.747 55.549 C 49.598 55.857 48.387 55.857 47.238 55.549 C 46.088 55.241 45.04 54.635 44.198 53.794 L 26.386 35.982 C 25.115 34.711 24.4 32.985 24.4 31.188 C 24.4 29.39 25.115 27.664 26.386 26.393"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
</vector>

View file

@ -0,0 +1,111 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="33.15dp"
android:height="33.15dp"
android:viewportWidth="189"
android:viewportHeight="189">
<path
android:name="path"
android:pathData="M 94.88 61.56 C 86.131 61.56 77.732 65.039 71.545 71.225 C 65.359 77.412 61.88 85.811 61.88 94.56 C 61.88 103.309 65.359 111.708 71.545 117.895 C 77.732 124.081 86.131 127.56 94.88 127.56 C 103.629 127.56 112.028 124.081 118.215 117.895 C 124.401 111.708 127.88 103.309 127.88 94.56 C 127.88 85.811 124.401 77.412 118.215 71.225 C 112.028 65.039 103.629 61.56 94.88 61.56 Z"
android:fillColor="#ffffff"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_1"
android:pathData="M 94.56 0.5 L 94.57 0.5 C 96.367 0.5 98.093 1.215 99.364 2.486 C 100.635 3.757 101.35 5.483 101.35 7.28 L 101.35 38.15 C 101.35 39.947 100.635 41.673 99.364 42.944 C 98.093 44.215 96.367 44.93 94.57 44.93 L 94.56 44.93 C 92.763 44.93 91.037 44.215 89.766 42.944 C 88.495 41.673 87.78 39.947 87.78 38.15 L 87.78 7.28 C 87.78 5.483 88.495 3.757 89.766 2.486 C 91.037 1.215 92.763 0.5 94.56 0.5"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_2"
android:pathData="M 94.56 0.5 L 94.57 0.5 C 96.367 0.5 98.093 1.215 99.364 2.486 C 100.635 3.757 101.35 5.483 101.35 7.28 L 101.35 38.15 C 101.35 39.947 100.635 41.673 99.364 42.944 C 98.093 44.215 96.367 44.93 94.57 44.93 L 94.56 44.93 C 92.763 44.93 91.037 44.215 89.766 42.944 C 88.495 41.673 87.78 39.947 87.78 38.15 L 87.78 7.28 C 87.78 5.483 88.495 3.757 89.766 2.486 C 91.037 1.215 92.763 0.5 94.56 0.5"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_3"
android:pathData="M 94.56 144.19 L 94.57 144.19 C 96.367 144.19 98.093 144.905 99.364 146.176 C 100.635 147.447 101.35 149.173 101.35 150.97 L 101.35 181.84 C 101.35 183.637 100.635 185.363 99.364 186.634 C 98.093 187.905 96.367 188.62 94.57 188.62 L 94.56 188.62 C 92.763 188.62 91.037 187.905 89.766 186.634 C 88.495 185.363 87.78 183.637 87.78 181.84 L 87.78 150.97 C 87.78 149.173 88.495 147.447 89.766 146.176 C 91.037 144.905 92.763 144.19 94.56 144.19"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_4"
android:pathData="M 94.56 144.19 L 94.57 144.19 C 96.367 144.19 98.093 144.905 99.364 146.176 C 100.635 147.447 101.35 149.173 101.35 150.97 L 101.35 181.84 C 101.35 183.637 100.635 185.363 99.364 186.634 C 98.093 187.905 96.367 188.62 94.57 188.62 L 94.56 188.62 C 92.763 188.62 91.037 187.905 89.766 186.634 C 88.495 185.363 87.78 183.637 87.78 181.84 L 87.78 150.97 C 87.78 149.173 88.495 147.447 89.766 146.176 C 91.037 144.905 92.763 144.19 94.56 144.19"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_5"
android:pathData="M 129.652 49.874 L 151.48 28.046 C 152.322 27.205 153.37 26.599 154.519 26.291 C 155.669 25.983 156.88 25.983 158.029 26.291 C 159.179 26.599 160.227 27.205 161.068 28.046 L 161.075 28.053 C 161.917 28.895 162.522 29.943 162.83 31.092 C 163.138 32.242 163.138 33.453 162.83 34.602 C 162.522 35.752 161.917 36.8 161.075 37.641 L 139.247 59.47 C 138.406 60.311 137.357 60.917 136.208 61.225 C 135.058 61.533 133.848 61.533 132.698 61.225 C 131.549 60.917 130.5 60.311 129.659 59.47 L 129.652 59.463 C 128.81 58.621 128.205 57.573 127.897 56.423 C 127.589 55.274 127.589 54.063 127.897 52.914 C 128.205 51.764 128.81 50.716 129.652 49.874"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_6"
android:pathData="M 129.652 49.874 L 151.48 28.046 C 152.322 27.205 153.37 26.599 154.519 26.291 C 155.669 25.983 156.88 25.983 158.029 26.291 C 159.179 26.599 160.227 27.205 161.068 28.046 L 161.075 28.053 C 161.917 28.895 162.522 29.943 162.83 31.092 C 163.138 32.242 163.138 33.453 162.83 34.602 C 162.522 35.752 161.917 36.8 161.075 37.641 L 139.247 59.47 C 138.406 60.311 137.357 60.917 136.208 61.225 C 135.058 61.533 133.848 61.533 132.698 61.225 C 131.549 60.917 130.5 60.311 129.659 59.47 L 129.652 59.463 C 128.81 58.621 128.205 57.573 127.897 56.423 C 127.589 55.274 127.589 54.063 127.897 52.914 C 128.205 51.764 128.81 50.716 129.652 49.874"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_7"
android:pathData="M 28.052 151.479 L 49.88 129.65 C 50.722 128.809 51.77 128.203 52.919 127.895 C 54.069 127.587 55.28 127.587 56.429 127.895 C 57.579 128.203 58.627 128.809 59.468 129.65 L 59.475 129.657 C 60.317 130.499 60.922 131.547 61.23 132.697 C 61.538 133.846 61.538 135.057 61.23 136.206 C 60.922 137.356 60.317 138.404 59.475 139.246 L 37.647 161.074 C 36.806 161.915 35.757 162.521 34.608 162.829 C 33.458 163.137 32.248 163.137 31.098 162.829 C 29.949 162.521 28.9 161.915 28.059 161.074 L 28.052 161.067 C 27.21 160.225 26.605 159.177 26.297 158.028 C 25.989 156.878 25.989 155.667 26.297 154.518 C 26.605 153.368 27.21 152.32 28.052 151.479"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_8"
android:pathData="M 28.052 151.479 L 49.88 129.65 C 50.722 128.809 51.77 128.203 52.919 127.895 C 54.069 127.587 55.28 127.587 56.429 127.895 C 57.579 128.203 58.627 128.809 59.468 129.65 L 59.475 129.657 C 60.317 130.499 60.922 131.547 61.23 132.697 C 61.538 133.846 61.538 135.057 61.23 136.206 C 60.922 137.356 60.317 138.404 59.475 139.246 L 37.647 161.074 C 36.806 161.915 35.757 162.521 34.608 162.829 C 33.458 163.137 32.248 163.137 31.098 162.829 C 29.949 162.521 28.9 161.915 28.059 161.074 L 28.052 161.067 C 27.21 160.225 26.605 159.177 26.297 158.028 C 25.989 156.878 25.989 155.667 26.297 154.518 C 26.605 153.368 27.21 152.32 28.052 151.479"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<group android:name="group">
<path
android:name="path_9"
android:pathData="M 150.97 87.78 L 181.84 87.78 C 183.637 87.78 185.363 88.495 186.634 89.766 C 187.905 91.037 188.62 92.763 188.62 94.56 L 188.62 94.57 C 188.62 96.367 187.905 98.093 186.634 99.364 C 185.363 100.635 183.637 101.35 181.84 101.35 L 150.97 101.35 C 149.173 101.35 147.447 100.635 146.176 99.364 C 144.905 98.093 144.19 96.367 144.19 94.57 L 144.19 94.56 C 144.19 92.763 144.905 91.037 146.176 89.766 C 147.447 88.495 149.173 87.78 150.97 87.78"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_10"
android:pathData="M 150.97 87.78 L 181.84 87.78 C 183.637 87.78 185.363 88.495 186.634 89.766 C 187.905 91.037 188.62 92.763 188.62 94.56 L 188.62 94.57 C 188.62 96.367 187.905 98.093 186.634 99.364 C 185.363 100.635 183.637 101.35 181.84 101.35 L 150.97 101.35 C 149.173 101.35 147.447 100.635 146.176 99.364 C 144.905 98.093 144.19 96.367 144.19 94.57 L 144.19 94.56 C 144.19 92.763 144.905 91.037 146.176 89.766 C 147.447 88.495 149.173 87.78 150.97 87.78"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_1">
<path
android:name="path_11"
android:pathData="M 7.28 87.78 L 38.15 87.78 C 39.947 87.78 41.673 88.495 42.944 89.766 C 44.215 91.037 44.93 92.763 44.93 94.56 L 44.93 94.57 C 44.93 96.367 44.215 98.093 42.944 99.364 C 41.673 100.635 39.947 101.35 38.15 101.35 L 7.28 101.35 C 6.09 101.35 4.921 101.037 3.89 100.442 C 2.859 99.847 2.003 98.991 1.408 97.96 C 0.813 96.929 0.5 95.76 0.5 94.57 L 0.5 94.56 C 0.5 92.763 1.215 91.037 2.486 89.766 C 3.757 88.495 5.483 87.78 7.28 87.78"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_12"
android:pathData="M 7.28 87.78 L 38.15 87.78 C 39.947 87.78 41.673 88.495 42.944 89.766 C 44.215 91.037 44.93 92.763 44.93 94.56 L 44.93 94.57 C 44.93 96.367 44.215 98.093 42.944 99.364 C 41.673 100.635 39.947 101.35 38.15 101.35 L 7.28 101.35 C 6.09 101.35 4.921 101.037 3.89 100.442 C 2.859 99.847 2.003 98.991 1.408 97.96 C 0.813 96.929 0.5 95.76 0.5 94.57 L 0.5 94.56 C 0.5 92.763 1.215 91.037 2.486 89.766 C 3.757 88.495 5.483 87.78 7.28 87.78"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_2">
<path
android:name="path_13"
android:pathData="M 129.654 129.655 L 129.661 129.648 C 130.502 128.807 131.551 128.201 132.7 127.893 C 133.85 127.585 135.06 127.585 136.21 127.893 C 137.359 128.201 138.408 128.807 139.249 129.648 L 161.078 151.476 C 161.919 152.318 162.524 153.366 162.832 154.516 C 163.14 155.665 163.14 156.876 162.832 158.025 C 162.524 159.175 161.919 160.223 161.078 161.065 L 161.07 161.072 C 160.229 161.913 159.181 162.519 158.031 162.827 C 156.882 163.135 155.671 163.135 154.521 162.827 C 153.372 162.519 152.324 161.913 151.482 161.072 L 129.654 139.244 C 128.812 138.402 128.207 137.354 127.899 136.204 C 127.591 135.055 127.591 133.844 127.899 132.695 C 128.207 131.545 128.812 130.497 129.654 129.655"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_14"
android:pathData="M 129.654 129.655 L 129.661 129.648 C 130.502 128.807 131.551 128.201 132.7 127.893 C 133.85 127.585 135.06 127.585 136.21 127.893 C 137.359 128.201 138.408 128.807 139.249 129.648 L 161.078 151.476 C 161.919 152.318 162.524 153.366 162.832 154.516 C 163.14 155.665 163.14 156.876 162.832 158.025 C 162.524 159.175 161.919 160.223 161.078 161.065 L 161.07 161.072 C 160.229 161.913 159.181 162.519 158.031 162.827 C 156.882 163.135 155.671 163.135 154.521 162.827 C 153.372 162.519 152.324 161.913 151.482 161.072 L 129.654 139.244 C 128.812 138.402 128.207 137.354 127.899 136.204 C 127.591 135.055 127.591 133.844 127.899 132.695 C 128.207 131.545 128.812 130.497 129.654 129.655"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_3">
<path
android:name="path_15"
android:pathData="M 28.05 28.055 L 28.057 28.048 C 28.898 27.207 29.947 26.601 31.096 26.293 C 32.246 25.985 33.456 25.985 34.606 26.293 C 35.755 26.601 36.804 27.207 37.645 28.048 L 59.473 49.876 C 60.315 50.718 60.92 51.766 61.228 52.916 C 61.536 54.065 61.536 55.276 61.228 56.425 C 60.92 57.575 60.315 58.623 59.473 59.465 L 59.466 59.472 C 58.625 60.313 57.576 60.919 56.427 61.227 C 55.277 61.535 54.067 61.535 52.917 61.227 C 51.768 60.919 50.719 60.313 49.878 59.472 L 28.05 37.644 C 27.208 36.802 26.603 35.754 26.295 34.604 C 25.987 33.455 25.987 32.244 26.295 31.095 C 26.603 29.945 27.208 28.897 28.05 28.055"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_16"
android:pathData="M 28.05 28.055 L 28.057 28.048 C 28.898 27.207 29.947 26.601 31.096 26.293 C 32.246 25.985 33.456 25.985 34.606 26.293 C 35.755 26.601 36.804 27.207 37.645 28.048 L 59.473 49.876 C 60.315 50.718 60.92 51.766 61.228 52.916 C 61.536 54.065 61.536 55.276 61.228 56.425 C 60.92 57.575 60.315 58.623 59.473 59.465 L 59.466 59.472 C 58.625 60.313 57.576 60.919 56.427 61.227 C 55.277 61.535 54.067 61.535 52.917 61.227 C 51.768 60.919 50.719 60.313 49.878 59.472 L 28.05 37.644 C 27.208 36.802 26.603 35.754 26.295 34.604 C 25.987 33.455 25.987 32.244 26.295 31.095 C 26.603 29.945 27.208 28.897 28.05 28.055"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
</vector>

View file

@ -0,0 +1,111 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="35.96dp"
android:height="35.96dp"
android:viewportWidth="205"
android:viewportHeight="205">
<path
android:name="path"
android:pathData="M 103.3 69.97 C 94.551 69.97 86.152 73.449 79.965 79.635 C 73.779 85.822 70.3 94.221 70.3 102.97 C 70.3 111.719 73.779 120.118 79.965 126.305 C 86.152 132.491 94.551 135.97 103.3 135.97 C 112.049 135.97 120.448 132.491 126.635 126.305 C 132.821 120.118 136.3 111.719 136.3 102.97 C 136.3 94.221 132.821 85.822 126.635 79.635 C 120.448 73.449 112.049 69.97 103.3 69.97 Z"
android:fillColor="#ffffff"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_1"
android:pathData="M 102.97 0.5 L 102.98 0.5 C 104.777 0.5 106.503 1.215 107.774 2.486 C 109.045 3.757 109.76 5.483 109.76 7.28 L 109.76 46.56 C 109.76 48.357 109.045 50.083 107.774 51.354 C 106.503 52.625 104.777 53.34 102.98 53.34 L 102.97 53.34 C 101.173 53.34 99.447 52.625 98.176 51.354 C 96.905 50.083 96.19 48.357 96.19 46.56 L 96.19 7.28 C 96.19 5.483 96.905 3.757 98.176 2.486 C 99.447 1.215 101.173 0.5 102.97 0.5"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_2"
android:pathData="M 102.97 0.5 L 102.98 0.5 C 104.777 0.5 106.503 1.215 107.774 2.486 C 109.045 3.757 109.76 5.483 109.76 7.28 L 109.76 46.56 C 109.76 48.357 109.045 50.083 107.774 51.354 C 106.503 52.625 104.777 53.34 102.98 53.34 L 102.97 53.34 C 101.173 53.34 99.447 52.625 98.176 51.354 C 96.905 50.083 96.19 48.357 96.19 46.56 L 96.19 7.28 C 96.19 5.483 96.905 3.757 98.176 2.486 C 99.447 1.215 101.173 0.5 102.97 0.5"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_3"
android:pathData="M 102.97 152.6 L 102.98 152.6 C 104.777 152.6 106.503 153.315 107.774 154.586 C 109.045 155.857 109.76 157.583 109.76 159.38 L 109.76 198.66 C 109.76 200.457 109.045 202.183 107.774 203.454 C 106.503 204.725 104.777 205.44 102.98 205.44 L 102.97 205.44 C 101.173 205.44 99.447 204.725 98.176 203.454 C 96.905 202.183 96.19 200.457 96.19 198.66 L 96.19 159.38 C 96.19 157.583 96.905 155.857 98.176 154.586 C 99.447 153.315 101.173 152.6 102.97 152.6"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_4"
android:pathData="M 102.97 152.6 L 102.98 152.6 C 104.777 152.6 106.503 153.315 107.774 154.586 C 109.045 155.857 109.76 157.583 109.76 159.38 L 109.76 198.66 C 109.76 200.457 109.045 202.183 107.774 203.454 C 106.503 204.725 104.777 205.44 102.98 205.44 L 102.97 205.44 C 101.173 205.44 99.447 204.725 98.176 203.454 C 96.905 202.183 96.19 200.457 96.19 198.66 L 96.19 159.38 C 96.19 157.583 96.905 155.857 98.176 154.586 C 99.447 153.315 101.173 152.6 102.97 152.6"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_5"
android:pathData="M 138.065 58.287 L 165.84 30.512 C 166.681 29.67 167.73 29.065 168.879 28.757 C 170.029 28.449 171.239 28.449 172.389 28.757 C 173.538 29.065 174.587 29.67 175.428 30.512 L 175.435 30.519 C 176.706 31.79 177.421 33.516 177.421 35.313 C 177.421 37.111 176.706 38.836 175.435 40.107 L 147.66 67.882 C 146.819 68.724 145.77 69.329 144.621 69.637 C 143.471 69.945 142.261 69.945 141.111 69.637 C 139.962 69.329 138.913 68.724 138.072 67.882 L 138.065 67.875 C 137.223 67.034 136.618 65.985 136.31 64.836 C 136.002 63.686 136.002 62.476 136.31 61.326 C 136.618 60.177 137.223 59.128 138.065 58.287"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_6"
android:pathData="M 138.065 58.287 L 165.84 30.512 C 166.681 29.67 167.73 29.065 168.879 28.757 C 170.029 28.449 171.239 28.449 172.389 28.757 C 173.538 29.065 174.587 29.67 175.428 30.512 L 175.435 30.519 C 176.706 31.79 177.421 33.516 177.421 35.313 C 177.421 37.111 176.706 38.836 175.435 40.107 L 147.66 67.882 C 146.819 68.724 145.77 69.329 144.621 69.637 C 143.471 69.945 142.261 69.945 141.111 69.637 C 139.962 69.329 138.913 68.724 138.072 67.882 L 138.065 67.875 C 137.223 67.034 136.618 65.985 136.31 64.836 C 136.002 63.686 136.002 62.476 136.31 61.326 C 136.618 60.177 137.223 59.128 138.065 58.287"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<path
android:name="path_7"
android:pathData="M 30.522 165.843 L 58.297 138.068 C 59.138 137.226 60.187 136.621 61.336 136.313 C 62.486 136.005 63.696 136.005 64.846 136.313 C 65.995 136.621 67.044 137.226 67.885 138.068 L 67.892 138.075 C 68.734 138.916 69.339 139.965 69.647 141.114 C 69.955 142.264 69.955 143.474 69.647 144.624 C 69.339 145.773 68.734 146.822 67.892 147.663 L 40.117 175.438 C 39.276 176.28 38.227 176.885 37.078 177.193 C 35.928 177.501 34.718 177.501 33.568 177.193 C 32.419 176.885 31.37 176.28 30.529 175.438 L 30.522 175.431 C 29.251 174.16 28.536 172.434 28.536 170.637 C 28.536 168.839 29.251 167.114 30.522 165.843"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_8"
android:pathData="M 30.522 165.843 L 58.297 138.068 C 59.138 137.226 60.187 136.621 61.336 136.313 C 62.486 136.005 63.696 136.005 64.846 136.313 C 65.995 136.621 67.044 137.226 67.885 138.068 L 67.892 138.075 C 68.734 138.916 69.339 139.965 69.647 141.114 C 69.955 142.264 69.955 143.474 69.647 144.624 C 69.339 145.773 68.734 146.822 67.892 147.663 L 40.117 175.438 C 39.276 176.28 38.227 176.885 37.078 177.193 C 35.928 177.501 34.718 177.501 33.568 177.193 C 32.419 176.885 31.37 176.28 30.529 175.438 L 30.522 175.431 C 29.251 174.16 28.536 172.434 28.536 170.637 C 28.536 168.839 29.251 167.114 30.522 165.843"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
<group android:name="group">
<path
android:name="path_9"
android:pathData="M 159.38 96.19 L 198.66 96.19 C 200.457 96.19 202.183 96.905 203.454 98.176 C 204.725 99.447 205.44 101.173 205.44 102.97 L 205.44 102.98 C 205.44 104.777 204.725 106.503 203.454 107.774 C 202.183 109.045 200.457 109.76 198.66 109.76 L 159.38 109.76 C 157.583 109.76 155.857 109.045 154.586 107.774 C 153.315 106.503 152.6 104.777 152.6 102.98 L 152.6 102.97 C 152.6 101.173 153.315 99.447 154.586 98.176 C 155.857 96.905 157.583 96.19 159.38 96.19"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_10"
android:pathData="M 159.38 96.19 L 198.66 96.19 C 200.457 96.19 202.183 96.905 203.454 98.176 C 204.725 99.447 205.44 101.173 205.44 102.97 L 205.44 102.98 C 205.44 104.777 204.725 106.503 203.454 107.774 C 202.183 109.045 200.457 109.76 198.66 109.76 L 159.38 109.76 C 157.583 109.76 155.857 109.045 154.586 107.774 C 153.315 106.503 152.6 104.777 152.6 102.98 L 152.6 102.97 C 152.6 101.173 153.315 99.447 154.586 98.176 C 155.857 96.905 157.583 96.19 159.38 96.19"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_1">
<path
android:name="path_11"
android:pathData="M 7.28 96.19 L 46.56 96.19 C 48.357 96.19 50.083 96.905 51.354 98.176 C 52.625 99.447 53.34 101.173 53.34 102.97 L 53.34 102.98 C 53.34 104.777 52.625 106.503 51.354 107.774 C 50.083 109.045 48.357 109.76 46.56 109.76 L 7.28 109.76 C 5.483 109.76 3.757 109.045 2.486 107.774 C 1.215 106.503 0.5 104.777 0.5 102.98 L 0.5 102.97 C 0.5 101.173 1.215 99.447 2.486 98.176 C 3.757 96.905 5.483 96.19 7.28 96.19"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_12"
android:pathData="M 7.28 96.19 L 46.56 96.19 C 48.357 96.19 50.083 96.905 51.354 98.176 C 52.625 99.447 53.34 101.173 53.34 102.97 L 53.34 102.98 C 53.34 104.777 52.625 106.503 51.354 107.774 C 50.083 109.045 48.357 109.76 46.56 109.76 L 7.28 109.76 C 5.483 109.76 3.757 109.045 2.486 107.774 C 1.215 106.503 0.5 104.777 0.5 102.98 L 0.5 102.97 C 0.5 101.173 1.215 99.447 2.486 98.176 C 3.757 96.905 5.483 96.19 7.28 96.19"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_2">
<path
android:name="path_13"
android:pathData="M 138.071 138.068 L 138.078 138.061 C 138.92 137.22 139.968 136.614 141.118 136.306 C 142.267 135.998 143.478 135.998 144.627 136.306 C 145.777 136.614 146.825 137.22 147.667 138.061 L 175.442 165.836 C 176.283 166.678 176.889 167.726 177.197 168.876 C 177.505 170.025 177.505 171.236 177.197 172.385 C 176.889 173.535 176.283 174.583 175.442 175.425 L 175.435 175.432 C 174.593 176.273 173.545 176.879 172.395 177.187 C 171.246 177.495 170.035 177.495 168.886 177.187 C 167.736 176.879 166.688 176.273 165.846 175.432 L 138.071 147.657 C 137.23 146.815 136.624 145.767 136.316 144.617 C 136.008 143.468 136.008 142.257 136.316 141.108 C 136.624 139.958 137.23 138.91 138.071 138.068"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_14"
android:pathData="M 138.071 138.068 L 138.078 138.061 C 138.92 137.22 139.968 136.614 141.118 136.306 C 142.267 135.998 143.478 135.998 144.627 136.306 C 145.777 136.614 146.825 137.22 147.667 138.061 L 175.442 165.836 C 176.283 166.678 176.889 167.726 177.197 168.876 C 177.505 170.025 177.505 171.236 177.197 172.385 C 176.889 173.535 176.283 174.583 175.442 175.425 L 175.435 175.432 C 174.593 176.273 173.545 176.879 172.395 177.187 C 171.246 177.495 170.035 177.495 168.886 177.187 C 167.736 176.879 166.688 176.273 165.846 175.432 L 138.071 147.657 C 137.23 146.815 136.624 145.767 136.316 144.617 C 136.008 143.468 136.008 142.257 136.316 141.108 C 136.624 139.958 137.23 138.91 138.071 138.068"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
<group android:name="group_3">
<path
android:name="path_15"
android:pathData="M 30.515 30.525 L 30.522 30.518 C 31.364 29.677 32.412 29.071 33.562 28.763 C 34.711 28.455 35.922 28.455 37.071 28.763 C 38.221 29.071 39.269 29.677 40.111 30.518 L 67.886 58.293 C 68.727 59.135 69.333 60.183 69.641 61.333 C 69.949 62.482 69.949 63.693 69.641 64.842 C 69.333 65.992 68.727 67.04 67.886 67.882 L 67.879 67.889 C 67.037 68.73 65.989 69.336 64.84 69.644 C 63.69 69.952 62.479 69.952 61.33 69.644 C 60.18 69.336 59.132 68.73 58.291 67.889 L 30.515 40.114 C 29.674 39.272 29.069 38.224 28.761 37.074 C 28.453 35.925 28.453 34.714 28.761 33.565 C 29.069 32.415 29.674 31.367 30.515 30.525"
android:fillColor="#ffffff"
android:strokeWidth="1"/>
<path
android:name="path_16"
android:pathData="M 30.515 30.525 L 30.522 30.518 C 31.364 29.677 32.412 29.071 33.562 28.763 C 34.711 28.455 35.922 28.455 37.071 28.763 C 38.221 29.071 39.269 29.677 40.111 30.518 L 67.886 58.293 C 68.727 59.135 69.333 60.183 69.641 61.333 C 69.949 62.482 69.949 63.693 69.641 64.842 C 69.333 65.992 68.727 67.04 67.886 67.882 L 67.879 67.889 C 67.037 68.73 65.989 69.336 64.84 69.644 C 63.69 69.952 62.479 69.952 61.33 69.644 C 60.18 69.336 59.132 68.73 58.291 67.889 L 30.515 40.114 C 29.674 39.272 29.069 38.224 28.761 37.074 C 28.453 35.925 28.453 34.714 28.761 33.565 C 29.069 32.415 29.674 31.367 30.515 30.525"
android:strokeColor="#ffffff"
android:strokeWidth="1"
android:strokeMiterLimit="10"/>
</group>
</vector>

View file

@ -111,7 +111,7 @@
android:layout_width="30dp"
android:background="?selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_play_arrow_24"
android:tint="?attr/textColor"
app:tint="?attr/textColor"
android:contentDescription="@string/download"/>
</FrameLayout>
</GridLayout>

View file

@ -85,7 +85,6 @@
android:visibility="visible"
/>
<ImageView
android:tint="?attr/white"
android:visibility="visible"
android:layout_marginEnd="10dp"
@ -97,7 +96,8 @@
android:id="@+id/download_header_episode_download"
android:background="?selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_play_arrow_24"
android:contentDescription="@string/download"/>
android:contentDescription="@string/download"
app:tint="?attr/white" />
</FrameLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

View file

@ -0,0 +1,18 @@
<?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"
xmlns:app="http://schemas.android.com/apk/res-auto">
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:navGraph="@navigation/mobile_navigation"
app:layout_constraintWidth="match_parent"
app:layout_constraintHeight="match_parent"
app:layout_constraintStart_toEndOf="parent"
app:layout_constraintTop_toBottomOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -358,7 +358,7 @@
android:text="@string/continue_watching"
/>
<ImageView
android:tint="?attr/textColor"
app:tint="?attr/textColor"
android:layout_marginEnd="5dp"
android:layout_gravity="end|center_vertical"
android:src="@drawable/ic_baseline_arrow_forward_24"
@ -451,7 +451,7 @@
</LinearLayout>
</HorizontalScrollView>
<ImageView
android:tint="?attr/textColor"
app:tint="?attr/textColor"
android:layout_marginEnd="5dp"
android:layout_gravity="end|center_vertical"
android:src="@drawable/ic_baseline_arrow_forward_24"

View file

@ -7,6 +7,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:keepScreenOn="true"
android:id="@+id/player_background"
app:backgroundTint="@android:color/black"
android:background="@android:color/black"
android:screenOrientation="sensorLandscape"
@ -37,13 +38,14 @@
app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/loading_overlay"
android:id="@+id/player_loading_overlay"
android:background="@android:color/black"
android:backgroundTint="@android:color/black"
>
<com.google.android.material.button.MaterialButton
android:visibility="visible"
tools:visibility="visible"
android:visibility="gone"
android:layout_marginTop="70dp"
android:layout_gravity="center"
app:cornerRadius="4dp"
@ -61,8 +63,6 @@
android:layout_height="45dp">
</com.google.android.material.button.MaterialButton>
<ProgressBar
android:layout_width="50dp"
android:layout_height="50dp"
@ -88,7 +88,7 @@
>
</ImageView>
<ImageView
android:id="@+id/video_go_back_holder"
android:id="@+id/player_loading_go_back"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center"
@ -132,7 +132,7 @@
android:textColor="@color/white"
android:id="@+id/video_torrent_seeders"
tools:text="17 seeders"
app:layout_constraintTop_toBottomOf="@+id/video_title">
app:layout_constraintTop_toBottomOf="@+id/player_video_title">
</TextView>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,14 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout 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:id="@+id/result_root"
android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_height="match_parent"
android:background="?attr/primaryBlackBackground"
android:clickable="true"
android:focusable="true"
>
android:focusable="true">
<com.facebook.shimmer.ShimmerFrameLayout
tools:visibility="gone"
@ -29,31 +28,44 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_marginBottom="@dimen/loading_margin"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/loading_poster"/>
<include layout="@layout/loading_poster" />
<LinearLayout
android:layout_marginStart="@dimen/loading_margin"
android:layout_marginEnd="@dimen/loading_margin"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/loading_line"/>
<include layout="@layout/loading_line"/>
<include layout="@layout/loading_line"/>
<include layout="@layout/loading_line"/>
<include layout="@layout/loading_line_short"/>
<include layout="@layout/loading_line" />
<include layout="@layout/loading_line" />
<include layout="@layout/loading_line" />
<include layout="@layout/loading_line" />
<include layout="@layout/loading_line_short" />
</LinearLayout>
</LinearLayout>
<ImageView android:layout_width="match_parent" android:layout_height="20dp"
tools:ignore="ContentDescription"/>
<include layout="@layout/loading_episode"/>
<include layout="@layout/loading_episode"/>
<include layout="@layout/loading_episode"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="20dp"
tools:ignore="ContentDescription" />
<include layout="@layout/loading_episode" />
<include layout="@layout/loading_episode" />
<include layout="@layout/loading_episode" />
</LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout>
<!--<ProgressBar
@ -72,6 +84,7 @@
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:layout_gravity="center"
style="@style/WhiteButton"
@ -82,8 +95,8 @@
android:text="@string/reload_error"
android:id="@+id/result_reload_connectionerror"
android:layout_width="wrap_content"
android:minWidth="200dp"
/>
android:minWidth="200dp" />
<com.google.android.material.button.MaterialButton
android:layout_gravity="center"
style="@style/BlackButton"
@ -94,8 +107,8 @@
android:text="@string/result_open_in_browser"
android:id="@+id/result_reload_connection_open_in_browser"
android:layout_width="wrap_content"
android:minWidth="200dp"
/>
android:minWidth="200dp" />
<TextView
android:layout_margin="5dp"
android:gravity="center"
@ -103,8 +116,7 @@
android:id="@+id/result_error_text"
android:textColor="?attr/textColor"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</TextView>
android:layout_height="wrap_content" />
</LinearLayout>
<FrameLayout
@ -120,6 +132,7 @@
android:visibility="visible"
android:layout_width="match_parent"
android:layout_height="230dp">
<ImageView
android:id="@+id/result_poster_blur"
@ -129,31 +142,36 @@
android:alpha="0"
tools:src="@drawable/example_poster"
android:background="?attr/primaryGrayBackground"
tools:ignore="ContentDescription"/>
tools:ignore="ContentDescription" />
<ImageView
android:src="@drawable/background_shadow"
android:layout_gravity="bottom"
android:layout_width="match_parent"
android:layout_height="30dp"
tools:ignore="ContentDescription"/>
tools:ignore="ContentDescription" />
</FrameLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/result_scroll"
android:background="?attr/primaryGrayBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:background="?attr/primaryBlackBackground"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:background="?attr/primaryGrayBackground"
android:paddingStart="@dimen/result_padding"
android:paddingEnd="@dimen/result_padding"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:nextFocusDown="@id/result_bookmark_button"
android:nextFocusRight="@id/result_share"
@ -166,17 +184,17 @@
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical|start"
android:tint="?attr/white"
android:src="@drawable/ic_baseline_arrow_back_24"
android:contentDescription="@string/result_go_back">
</ImageView>
android:contentDescription="@string/go_back"
app:tint="?attr/white" />
<LinearLayout
android:gravity="end"
android:layout_width="match_parent"
android:layout_height="50dp"
android:id="@+id/media_route_button_holder"
android:layout_gravity="center_vertical|end"
>
android:layout_gravity="center_vertical|end">
<androidx.mediarouter.app.MediaRouteButton
android:layout_gravity="end|center_vertical"
android:id="@+id/media_route_button"
@ -184,8 +202,8 @@
android:layout_height="50dp"
android:mediaRouteTypes="user"
android:visibility="gone"
app:mediaRouteButtonTint="?attr/textColor"
/>
app:mediaRouteButtonTint="?attr/textColor" />
<ImageView
android:visibility="gone"
android:nextFocusUp="@id/result_back"
@ -196,16 +214,14 @@
android:id="@+id/result_add_sync"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginRight="10dp"
android:layout_marginEnd="10dp"
android:elevation="10dp"
android:tint="?attr/textColor"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_add_24"
android:layout_gravity="end|center_vertical"
android:contentDescription="@string/add_sync">
</ImageView>
android:contentDescription="@string/add_sync"
app:tint="?attr/textColor" />
<ImageView
android:nextFocusUp="@id/result_back"
@ -216,16 +232,15 @@
android:id="@+id/result_share"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginRight="10dp"
android:layout_marginEnd="10dp"
android:elevation="10dp"
android:tint="?attr/textColor"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_outline_share_24"
android:layout_gravity="end|center_vertical"
android:contentDescription="@string/result_share">
</ImageView>
android:contentDescription="@string/result_share"
app:tint="?attr/textColor" />
<ImageView
android:nextFocusUp="@id/result_back"
android:nextFocusDown="@id/result_descript"
@ -238,13 +253,11 @@
android:layout_margin="5dp"
android:elevation="10dp"
android:tint="?attr/textColor"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_public_24"
android:layout_gravity="end|center_vertical"
android:contentDescription="@string/result_open_in_browser">
</ImageView>
android:contentDescription="@string/result_open_in_browser"
app:tint="?attr/textColor" />
<ImageView
android:nextFocusUp="@id/result_back"
@ -258,15 +271,14 @@
android:layout_margin="5dp"
android:elevation="10dp"
android:tint="?attr/textColor"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/search_icon"
android:layout_gravity="end|center_vertical"
android:contentDescription="@string/result_open_in_browser">
</ImageView>
android:contentDescription="@string/result_open_in_browser"
app:tint="?attr/textColor" />
</LinearLayout>
</FrameLayout>
<LinearLayout
android:clipToPadding="false"
android:orientation="vertical"
@ -274,6 +286,7 @@
android:paddingEnd="@dimen/result_padding"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:visibility="visible"
android:layout_marginBottom="15dp"
@ -281,11 +294,13 @@
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:id="@+id/result_poster_holder"
app:cardCornerRadius="@dimen/rounded_image_radius"
android:layout_width="100dp"
android:layout_height="140dp">
<ImageView
android:foreground="@drawable/outline_drawable"
@ -294,13 +309,15 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:src="@drawable/example_poster"
android:contentDescription="@string/result_poster_img_des"/>
android:contentDescription="@string/result_poster_img_des" />
</androidx.cardview.widget.CardView>
<LinearLayout
android:layout_marginStart="10dp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:maxLines="2"
android:layout_marginBottom="10dp"
@ -308,27 +325,20 @@
tools:text="The Perfect Run The Perfect Run The Perfect Run"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="?attr/textColor" android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
android:textColor="?attr/textColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:minWidth="0dp"
app:iconPadding="0dp"
android:paddingTop="0dp"
android:paddingBottom="0dp"
android:stateListAnimator="@null"
style="@style/BlackButton"
tools:text="Gogoanime"
android:id="@+id/result_meta_site"
android:layout_marginEnd="0dp"
android:layout_marginStart="0dp"
android:textSize="12sp"
android:layout_width="wrap_content"
android:layout_height="24dp">
</com.google.android.material.button.MaterialButton>
style="@style/SmallBlackButton"
android:layout_gravity="center_vertical"
tools:text="Gogoanime" />
<TextView
android:id="@+id/result_meta_year"
android:layout_marginStart="10dp"
@ -336,8 +346,8 @@
android:layout_gravity="center_vertical"
android:textColor="?attr/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
android:layout_height="wrap_content" />
<TextView
android:id="@+id/result_meta_rating"
android:layout_marginStart="10dp"
@ -346,8 +356,8 @@
android:layout_gravity="center_vertical"
android:textColor="?attr/textColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
android:layout_height="wrap_content" />
<TextView
android:id="@+id/result_meta_duration"
android:layout_marginStart="10dp"
@ -356,12 +366,13 @@
android:layout_gravity="center_vertical"
android:textColor="?attr/textColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
android:layout_height="wrap_content" />
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:textColor="?attr/textColor"
android:foreground="@drawable/outline_drawable"
@ -374,17 +385,18 @@
android:textSize="15sp"
tools:text="Ryan Quicksave Romano is an eccentric adventurer with a strange power: he can create a save-point in time and redo his life whenever he dies. Arriving in New Rome, the glitzy capital of sin of a rebuilding Europe, he finds the city torn between mega-corporations, sponsored heroes, superpowered criminals, and true monsters. It's a time of chaos, where potions can grant the power to rule the world and dangers lurk everywhere. "
android:layout_width="match_parent"
android:layout_height="wrap_content">
</TextView>
android:layout_height="wrap_content" />
<ImageView
android:src="@drawable/background_shadow"
android:layout_gravity="bottom"
android:layout_width="match_parent"
android:layout_height="30dp"
tools:ignore="ContentDescription"/>
tools:ignore="ContentDescription" />
</FrameLayout>
</LinearLayout>
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:layout_marginTop="0dp"
android:paddingTop="0dp"
@ -405,9 +417,9 @@
android:id="@+id/result_bookmark_button"
tools:text="Bookmark"
android:layout_width="wrap_content"
style="@style/BlackButton"
>
<requestFocus/>
style="@style/BlackButton">
<requestFocus />
</com.google.android.material.button.MaterialButton>
<TextView
@ -415,16 +427,18 @@
android:id="@+id/result_vpn"
android:textSize="15sp"
tools:text="@string/vpn_torrent"
android:layout_width="match_parent" android:layout_height="wrap_content">
</TextView>
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_marginBottom="5dp"
android:textColor="?attr/grayTextColor"
android:id="@+id/result_info"
android:textSize="15sp"
tools:text="@string/provider_info_meta"
android:layout_width="match_parent" android:layout_height="wrap_content">
</TextView>
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:visibility="gone"
android:id="@+id/result_tag_holder"
@ -435,12 +449,12 @@
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:textStyle="normal"
android:textColor="?attr/textColor"
/>
android:textColor="?attr/textColor" />
<com.lagradost.cloudstream3.widget.FlowLayout
android:id="@+id/result_tag"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
android:layout_height="wrap_content" />
<LinearLayout
android:layout_marginTop="5dp"
@ -448,6 +462,7 @@
android:id="@+id/result_movie_parent"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:layout_marginBottom="10dp"
android:nextFocusUp="@id/result_bookmark_button"
@ -461,8 +476,8 @@
android:layout_gravity="center_vertical"
android:text="@string/play_movie_button"
app:icon="@drawable/ic_baseline_play_arrow_24"
android:layout_width="match_parent">
</com.google.android.material.button.MaterialButton>
android:layout_width="match_parent" />
<com.google.android.material.button.MaterialButton
android:nextFocusUp="@id/result_play_movie"
android:nextFocusDown="@id/result_season_button"
@ -479,8 +494,7 @@
android:clickable="true"
android:focusable="true"
android:layout_width="match_parent">
</com.google.android.material.button.MaterialButton>
android:layout_width="match_parent" />
<androidx.core.widget.ContentLoadingProgressBar
android:layout_width="match_parent"
@ -493,7 +507,8 @@
android:layout_gravity="end|center_vertical"
android:progress="0"
android:visibility="gone"
tools:visibility="visible"/>
tools:visibility="visible" />
<TextView
android:id="@+id/result_movie_text_progress"
android:layout_gravity="center_vertical"
@ -501,8 +516,7 @@
tools:text="128MB / 237MB"
android:textColor="?attr/grayTextColor"
android:layout_width="wrap_content"
android:layout_height="match_parent">
</TextView>
android:layout_height="match_parent" />
</LinearLayout>
<!--<TextView
android:visibility="gone"
@ -553,18 +567,21 @@
app:spanCount="3"
android:id="@+id/result_recommendations"
tools:listitem="@layout/search_result_grid"
android:orientation="vertical"
/>
android:orientation="vertical" />
<LinearLayout
android:id="@+id/result_episodes_tab"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_marginBottom="10dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
tools:visibility="visible"
tools:text="Season 1"
@ -577,8 +594,8 @@
android:visibility="gone"
android:layout_gravity="center_vertical"
android:layout_marginStart="0dp"
style="@style/MultiSelectButton">
</com.google.android.material.button.MaterialButton>
style="@style/MultiSelectButton" />
<com.google.android.material.button.MaterialButton
tools:visibility="visible"
tools:text="50-100"
@ -592,8 +609,7 @@
android:visibility="gone"
android:layout_gravity="center_vertical"
android:layout_marginStart="0dp"
style="@style/MultiSelectButton"
/>
style="@style/MultiSelectButton" />
<com.google.android.material.button.MaterialButton
tools:visibility="visible"
@ -608,8 +624,8 @@
android:visibility="gone"
android:layout_gravity="center_vertical"
android:layout_marginStart="0dp"
style="@style/MultiSelectButton"
/>
style="@style/MultiSelectButton" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -619,18 +635,44 @@
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:textStyle="normal"
android:textColor="?attr/textColor"
/>
android:textColor="?attr/textColor" />
</LinearLayout>
<androidx.core.widget.ContentLoadingProgressBar
<com.facebook.shimmer.ShimmerFrameLayout
tools:visibility="visible"
app:shimmer_base_alpha="0.2"
app:shimmer_highlight_alpha="0.3"
app:shimmer_duration="@integer/loading_time"
app:shimmer_auto_start="true"
android:id="@+id/result_episode_loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_marginTop="15dp"
android:orientation="vertical">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/loading_episode" />
<include layout="@layout/loading_episode" />
<include layout="@layout/loading_episode" />
<include layout="@layout/loading_episode" />
</LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout>
<!--<androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/result_episode_loading"
style="@style/Widget.AppCompat.ProgressBar"
android:layout_gravity="center"
android:layout_width="50dp"
android:layout_height="50dp">
</androidx.core.widget.ContentLoadingProgressBar>
android:layout_height="50dp" />-->
<androidx.recyclerview.widget.RecyclerView
android:descendantFocusability="afterDescendants"
android:id="@+id/result_episodes"
@ -639,26 +681,26 @@
android:paddingBottom="100dp"
tools:listitem="@layout/result_episode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<LinearLayout
android:layout_gravity="bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
android:orientation="vertical">
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:visibility="gone"
tools:visibility="visible"
android:id="@+id/result_bookmark_fab"
app:icon="@drawable/ic_baseline_bookmark_24"
style="@style/ExtendedFloatingActionButton"
tools:ignore="ContentDescription">
</com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton>
tools:ignore="ContentDescription" />
<fragment
app:customCastBackgroundColor="?attr/primaryBlackBackground"
app:castControlButtons="@array/cast_mini_controller_control_buttons"
@ -667,8 +709,7 @@
android:layout_height="wrap_content"
android:visibility="gone"
class="com.lagradost.cloudstream3.ui.MyMiniControllerFragment"
tools:ignore="FragmentTagUsage">
</fragment>
tools:ignore="FragmentTagUsage" />
</LinearLayout>
</FrameLayout>

View file

@ -19,7 +19,7 @@
tools:text="Trending"
/>
<ImageView
android:tint="?attr/textColor"
app:tint="?attr/textColor"
android:layout_marginEnd="5dp"
android:layout_gravity="end|center_vertical"
android:src="@drawable/ic_baseline_arrow_forward_24"

View file

@ -1,24 +1,23 @@
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout 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:id="@+id/player_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/player_holder"
android:screenOrientation="landscape"
tools:orientation="vertical"
>
tools:orientation="vertical">
<FrameLayout
android:id="@+id/subtitle_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/shadow_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/shadow_overlay"
android:background="@color/black_overlay"
/>
android:background="@color/black_overlay" />
</FrameLayout>
<!--
@ -56,602 +55,566 @@
</LinearLayout>
-->
<RelativeLayout
android:id="@+id/player_time_menu"
android:layout_width="match_parent"
android:layout_height="200dp"
android:id="@+id/timeMenu"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@+id/topMenuRight"
app:layout_constraintBottom_toTopOf="@+id/centerMenu"
>
app:layout_constraintBottom_toTopOf="@+id/player_center_menu"
app:layout_constraintTop_toBottomOf="@+id/topMenuRight">
<TextView
android:id="@+id/player_time_text_left"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/timeTextLeft"
android:gravity="center|start"
android:textSize="40sp"
android:alpha="0"
android:layout_marginStart="100dp"
android:shadowColor="@android:color/black"
android:shadowRadius="10.0"
android:textColor="@android:color/white"
>
</TextView>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/timeText"
android:gravity="center"
android:textSize="30sp"
android:alpha="0"
android:gravity="center|start"
android:shadowColor="@android:color/black"
android:shadowRadius="10.0"
android:textColor="@android:color/white"
>
android:textSize="40sp">
</TextView>
<TextView
android:id="@+id/player_time_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/timeTextRight"
android:gravity="center|end"
android:layout_marginEnd="100dp"
android:textSize="40sp"
android:gravity="center"
android:shadowColor="@android:color/black"
android:shadowRadius="10.0"
android:textColor="@android:color/white"
>
android:textSize="30sp"
tools:text="+100">
</TextView>
<TextView
android:id="@+id/time_text_right"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginEnd="100dp"
android:gravity="center|end"
android:shadowColor="@android:color/black"
android:shadowRadius="10.0"
android:textColor="@android:color/white"
android:textSize="40sp" />
</RelativeLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_video_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/video_holder"
>
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/player_top_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_marginStart="80dp"
android:layout_marginEnd="80dp"
android:id="@+id/player_video_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:gravity="center"
android:layout_marginStart="80dp"
android:layout_marginTop="35dp"
android:textStyle="bold"
android:textColor="@color/white"
android:id="@+id/video_title"
tools:text="Hello world"
>
</TextView>
<TextView
android:layout_marginStart="80dp"
android:layout_marginEnd="80dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
android:gravity="center"
android:layout_marginTop="20dp"
android:textColor="@color/white"
android:id="@+id/video_title_rez"
tools:text="1920x1080"
app:layout_constraintTop_toBottomOf="@+id/video_title">
</TextView>
<!--
<LinearLayout
app:layout_constraintTop_toTopOf="parent"
android:textStyle="bold"
app:layout_constraintLeft_toLeftOf="parent"
android:gravity="end"
android:layout_margin="5dp"
android:paddingEnd="60dp"
android:paddingStart="60dp"
app:layout_constraintTop_toTopOf="parent"
tools:text="Hello world" />
<TextView
android:id="@+id/player_video_title_rez"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/topMenuRight"
>
android:layout_marginStart="80dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="80dp"
android:gravity="center"
android:textColor="@color/white"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/player_video_title"
tools:text="1920x1080" />
<androidx.cardview.widget.CardView
android:id="@+id/next_episode_btt"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="150dp"
android:layout_height="40dp"
android:layout_margin="15dp"
card_view:cardCornerRadius="2dp"
android:foreground="@drawable/outline_card"
card_view:cardBackgroundColor="@color/black_overlay"
card_view:cardElevation="0dp"
>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/next_episode"
android:gravity="start|center"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:layout_gravity="center"
android:textColor="@android:color/white"
>
</TextView>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/exo_controls_next"
android:layout_gravity="right|center"
android:paddingEnd="10dp"
android:paddingStart="10dp"
tools:ignore="RtlHardcoded">
</ImageView>
</androidx.cardview.widget.CardView>
</LinearLayout>-->
<androidx.mediarouter.app.MediaRouteButton
android:layout_margin="5dp"
android:layout_gravity="end"
<!-- Removed as it has no use anymore-->
<!--<androidx.mediarouter.app.MediaRouteButton
android:id="@+id/player_media_route_button"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="end"
android:layout_margin="5dp"
android:mediaRouteTypes="user"
android:visibility="visible"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
app:layout_constraintTop_toTopOf="parent" />-->
<FrameLayout
android:id="@+id/video_go_back_holder2"
android:layout_margin="5dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:id="@+id/player_go_back_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
android:layout_margin="5dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:src="@drawable/ic_baseline_arrow_back_24"
app:tint="@android:color/white"
>
</ImageView>
android:contentDescription="@string/go_back_img_des" />
<ImageView
android:id="@+id/video_go_back"
android:id="@+id/player_go_back"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center"
android:focusable="true"
android:background="@drawable/video_tap_button_always_white"
android:clickable="true"
android:background="@drawable/video_tap_button_always_white">
</ImageView>
android:focusable="true"
android:contentDescription="@string/go_back_img_des" />
</FrameLayout>
</FrameLayout>
<androidx.constraintlayout.widget.ConstraintLayout
<!--use for thinner app:trackThickness="3dp" com.google.android.material.progressindicator.CircularProgressIndicator-->
<ProgressBar
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:focusable="false"
android:clickable="false"
android:focusableInTouchMode="false"
android:indeterminate="true"
android:visibility="gone"
tools:visibility="visible"
android:id="@+id/player_buffering"
android:layout_gravity="center"
android:layout_height="wrap_content"
android:layout_width="wrap_content" />
<!-- This nested layout is necessary because of buffering and clicking-->
<FrameLayout
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="100dp"
android:layout_height="100dp"
android:id="@+id/player_pause_play_holder_holder">
<FrameLayout
tools:ignore="uselessParent"
android:id="@+id/player_pause_play_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
app:tint="@color/white"
android:id="@+id/player_pause_play"
android:nextFocusLeft="@id/exo_rew"
android:nextFocusRight="@id/exo_ffwd"
android:nextFocusUp="@id/player_go_back"
android:nextFocusDown="@id/player_lock"
android:layout_gravity="center"
android:src="@drawable/netflix_pause"
android:background="@drawable/video_tap_button"
android:layout_width="70dp"
android:layout_height="70dp"
tools:ignore="ContentDescription" />
</FrameLayout>
</FrameLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_center_menu"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_gravity="center"
android:gravity="center"
android:orientation="horizontal"
android:layout_gravity="center"
android:id="@+id/centerMenu"
>
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<FrameLayout
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="@id/player_pause_holder"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:id="@+id/player_rew_holder"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintWidth_percent="0.5"
android:layout_gravity="center_vertical|start"
android:layout_height="wrap_content">
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/player_ffwd_holder"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/exo_rew_text"
android:layout_width="200dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:gravity="center"
android:textColor="@color/white"
tools:text="10"
android:textSize="19sp"
android:layout_gravity="center"
android:textStyle="bold"
android:gravity="center"
tools:text="10" />
android:layout_width="200dp"
android:layout_height="40dp">
</TextView>
<ImageButton
android:nextFocusUp="@id/video_go_back"
android:nextFocusDown="@id/lock_player"
android:nextFocusLeft="@id/exo_rew"
android:tint="@color/white"
android:paddingLeft="100dp"
android:id="@id/exo_rew"
android:layout_height="70dp"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center"
android:padding="10dp"
android:background="@drawable/video_tap_button_skip"
android:src="@drawable/netflix_skip_forward"
android:nextFocusLeft="@id/exo_rew"
android:nextFocusUp="@id/player_go_back"
android:nextFocusDown="@id/player_lock"
android:padding="10dp"
android:scaleType="fitCenter"
android:scaleX="-1"
android:scaleType="fitCenter"
android:src="@drawable/netflix_skip_forward"
app:tint="@color/white"
android:tintMode="src_in"
/>
</FrameLayout>
<!-- style="@style/ExoMediaButton.Play"
android:background="?android:selectableItemBackgroundBorderless"
-->
<FrameLayout
app:layout_constraintLeft_toLeftOf="@id/player_rew_holder"
app:layout_constraintRight_toRightOf="@id/player_ffwd_holder"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_gravity="center"
android:id="@+id/player_pause_holder"
android:layout_width="100dp"
android:layout_height="100dp">
<ImageButton
android:nextFocusUp="@id/video_go_back"
android:nextFocusDown="@id/lock_player"
android:nextFocusLeft="@id/exo_rew"
android:nextFocusRight="@id/exo_ffwd"
android:tint="@color/white"
android:id="@id/exo_play"
android:scaleType="fitCenter"
android:layout_height="70dp"
android:layout_width="70dp"
android:background="@drawable/video_tap_button"
android:layout_gravity="center"
android:src="@drawable/netflix_play"
android:clickable="true"
android:focusable="true"
/>
<ImageButton
android:nextFocusUp="@id/video_go_back"
android:nextFocusDown="@id/lock_player"
android:nextFocusLeft="@id/exo_rew"
android:nextFocusRight="@id/exo_ffwd"
android:id="@id/exo_pause"
android:tint="@color/white"
android:scaleType="fitCenter"
android:layout_height="70dp"
android:layout_width="70dp"
android:background="@drawable/video_tap_button"
android:layout_gravity="center"
android:src="@drawable/netflix_pause"
android:clickable="true"
android:focusable="true"
/>
tools:ignore="ContentDescription" />
</FrameLayout>
<FrameLayout
app:layout_constraintLeft_toLeftOf="@id/player_pause_holder"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:id="@+id/player_ffwd_holder"
android:layout_width="wrap_content"
android:layout_width="0dp"
app:layout_constraintWidth_percent="0.5"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:layout_height="wrap_content">
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/player_rew_holder"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
tools:text="10"
android:id="@+id/exo_ffwd_text"
android:layout_width="200dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:gravity="center"
android:textColor="@color/white"
android:textSize="19sp"
android:layout_gravity="center"
android:textStyle="bold"
android:gravity="center"
android:layout_width="200dp"
android:layout_height="40dp">
</TextView>
<ImageButton
android:nextFocusUp="@id/video_go_back"
android:nextFocusDown="@id/lock_player"
android:nextFocusRight="@id/exo_rew"
android:tint="@color/white"
tools:text="10" />
android:layout_gravity="center"
<ImageButton
android:id="@id/exo_ffwd"
android:layout_height="70dp"
android:layout_width="70dp"
android:padding="10dp"
android:layout_height="70dp"
android:layout_gravity="center"
android:background="@drawable/video_tap_button_skip"
android:src="@drawable/netflix_skip_forward"
android:nextFocusRight="@id/exo_rew"
android:nextFocusUp="@id/player_go_back"
android:nextFocusDown="@id/player_lock"
android:padding="10dp"
android:scaleType="fitCenter"
android:src="@drawable/netflix_skip_forward"
app:tint="@color/white"
android:tintMode="src_in"
/>
tools:ignore="ContentDescription" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:layout_marginBottom="20dp"
android:visibility="gone"
android:layout_gravity="bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginBottom="20dp"
android:gravity="center"
android:orientation="horizontal"
android:paddingTop="4dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
>
app:layout_constraintEnd_toEndOf="parent">
<ImageButton
android:id="@id/exo_prev"
style="@style/ExoMediaButton.Previous"
android:tint="?attr/colorPrimaryDark"
android:tintMode="src_in"/>
app:tint="?attr/colorPrimaryDark"
android:tintMode="src_in"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@id/exo_repeat_toggle"
style="@style/ExoMediaButton"
android:tint="?attr/colorPrimaryDark"
android:tintMode="src_in"/>
app:tint="?attr/colorPrimaryDark"
android:tintMode="src_in"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@id/exo_next"
style="@style/ExoMediaButton.Next"
android:tint="?attr/colorPrimaryDark"
android:tintMode="src_in"/>
app:tint="?attr/colorPrimaryDark"
android:tintMode="src_in"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@id/exo_vr"
style="@style/ExoMediaButton.VR"
android:tint="?attr/colorPrimaryDark"
android:tintMode="src_in"/>
app:tint="?attr/colorPrimaryDark"
android:tintMode="src_in"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@id/exo_play"
app:tint="?attr/colorPrimaryDark"
android:tintMode="src_in"
tools:ignore="ContentDescription"
android:layout_height="0dp"
android:layout_width="0dp" />
<ImageButton
android:id="@id/exo_pause"
app:tint="?attr/colorPrimaryDark"
android:tintMode="src_in"
tools:ignore="ContentDescription"
android:layout_height="0dp"
android:layout_width="0dp" />
</LinearLayout>
<LinearLayout
android:id="@+id/bottom_player_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:gravity="center_vertical"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:id="@+id/bottom_player_bar">
app:layout_constraintEnd_toEndOf="parent">
<LinearLayout
android:id="@+id/player_video_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="@+id/video_bar"
>
android:orientation="horizontal">
<TextView
tools:text="15:30"
android:layout_marginStart="20dp"
android:id="@id/exo_position"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="20dp"
android:gravity="end"
android:includeFontPadding="false"
android:minWidth="50dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:gravity="end"
android:minWidth="50dp"
android:textColor="@android:color/white"
android:textSize="14sp"
android:textStyle="normal"/>
android:textStyle="normal"
tools:text="15:30" />
<!--app:buffered_color="@color/videoCache"-->
<com.google.android.exoplayer2.ui.DefaultTimeBar
android:id="@id/exo_progress"
android:layout_width="0dp"
android:layout_height="30dp"
android:layout_weight="1"
app:scrubber_enabled_size="24dp"
app:scrubber_dragged_size="26dp"
app:scrubber_color="?attr/colorPrimary"
app:bar_height="2dp"
app:played_color="?attr/colorPrimary"
app:unplayed_color="@color/videoProgress"/>
app:scrubber_color="?attr/colorPrimary"
app:scrubber_dragged_size="26dp"
app:scrubber_enabled_size="24dp"
app:unplayed_color="@color/videoProgress" />
<!-- exo_duration-->
<TextView
tools:text="23:20"
android:id="@id/exo_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="20dp"
android:includeFontPadding="false"
android:minWidth="50dp"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:minWidth="50dp"
android:textColor="@android:color/white"
android:textSize="14sp"
android:textStyle="normal"
android:layout_marginEnd="20dp"
android:layout_gravity="center"
/>
tools:text="23:20" />
</LinearLayout>
<HorizontalScrollView
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:layout_gravity="center">
<LinearLayout
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:orientation="horizontal"
android:gravity="center"
android:orientation="horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp"
>
android:paddingBottom="10dp">
<com.google.android.material.button.MaterialButton
android:nextFocusLeft="@id/skip_episode"
android:nextFocusRight="@id/resize_player"
android:id="@+id/lock_player"
app:iconSize="30dp"
android:id="@+id/player_lock"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_skip_episode"
android:nextFocusRight="@id/player_resize_btt"
android:text="@string/video_lock"
app:icon="@drawable/video_locked"
style="@style/VideoButton"/>
app:iconSize="30dp" />
<LinearLayout
android:id="@+id/lock_holder"
android:orientation="horizontal"
android:id="@+id/player_lock_holder"
android:layout_width="wrap_content"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:nextFocusLeft="@id/lock_player"
android:nextFocusRight="@id/playback_speed_btt"
android:id="@+id/resize_player"
android:text="@string/video_aspect_ratio_resize"
app:icon="@drawable/ic_baseline_aspect_ratio_24"
style="@style/VideoButton"/>
<com.google.android.material.button.MaterialButton
android:nextFocusLeft="@id/resize_player"
android:nextFocusRight="@id/sources_btt"
android:id="@+id/playback_speed_btt"
tools:text="Speed"
app:icon="@drawable/ic_baseline_speed_24"
style="@style/VideoButton"/>
<com.google.android.material.button.MaterialButton
android:nextFocusLeft="@id/playback_speed_btt"
android:nextFocusRight="@id/skip_op"
android:id="@+id/sources_btt"
android:text="@string/video_source"
app:icon="@drawable/ic_baseline_playlist_play_24"
style="@style/VideoButton"/>
<com.google.android.material.button.MaterialButton
android:nextFocusLeft="@id/sources_btt"
android:nextFocusRight="@id/skip_episode"
android:id="@+id/skip_op"
android:text="@string/video_skip_op"
app:icon="@drawable/ic_baseline_fast_forward_24"
style="@style/VideoButton"/>
<com.google.android.material.button.MaterialButton
android:nextFocusLeft="@id/skip_op"
android:nextFocusRight="@id/lock_player"
android:id="@+id/skip_episode"
android:text="@string/next_episode"
app:icon="@drawable/ic_baseline_skip_next_24"
android:id="@+id/player_resize_btt"
style="@style/VideoButton"
/>
android:nextFocusLeft="@id/player_lock"
android:nextFocusRight="@id/playback_speed_btt"
android:text="@string/video_aspect_ratio_resize"
app:icon="@drawable/ic_baseline_aspect_ratio_24" />
<com.google.android.material.button.MaterialButton
android:id="@+id/playback_speed_btt"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_resize_btt"
android:nextFocusRight="@id/player_sources_btt"
app:icon="@drawable/ic_baseline_speed_24"
tools:text="Speed" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_sources_btt"
style="@style/VideoButton"
android:nextFocusLeft="@id/playback_speed_btt"
android:nextFocusRight="@id/player_skip_op"
android:text="@string/video_source"
app:icon="@drawable/ic_baseline_playlist_play_24" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_skip_op"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_sources_btt"
android:nextFocusRight="@id/player_skip_episode"
android:text="@string/video_skip_op"
app:icon="@drawable/ic_baseline_fast_forward_24" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_skip_episode"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_skip_op"
android:nextFocusRight="@id/player_lock"
android:text="@string/next_episode"
app:icon="@drawable/ic_baseline_skip_next_24" />
</LinearLayout>
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/brightness_overlay"
android:background="@android:color/black"
android:alpha="0"
/>
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/click_overlay"
android:visibility="gone"
android:clickable="true"
android:focusable="true"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<RelativeLayout
android:id="@+id/player_progressbar_left_holder"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="left"
tools:alpha="1"
app:layout_constraintTop_toTopOf="parent"
android:gravity="start"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/centerMenuView"
android:id="@+id/progressBarLeftHolder"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:alpha="0"
>
app:layout_constraintTop_toTopOf="parent"
tools:alpha="1"
tools:visibility="visible">
<!--VERY hacky layout -->
<ImageView
android:layout_marginBottom="220dp"
android:layout_centerHorizontal="true"
android:id="@+id/player_progressbar_left_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginBottom="220dp"
android:src="@drawable/ic_baseline_volume_up_24"
app:tint="@android:color/white"
>
tools:ignore="ContentDescription">
</ImageView>
<ProgressBar
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:id="@+id/player_progressbar_left"
style="@android:style/Widget.Material.ProgressBar.Horizontal"
android:layout_width="4dp"
android:layout_height="150dp"
android:id="@+id/progressBarLeft"
android:indeterminate="false"
style="@android:style/Widget.Material.ProgressBar.Horizontal"
android:max="100"
android:layout_marginStart="40dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_gravity="end|center_vertical"
android:layout_marginStart="40dp"
android:indeterminate="false"
android:max="100"
android:progress="100"
android:progressDrawable="@drawable/progress_drawable_vertical"
/>
tools:progress="30" />
</RelativeLayout>
<RelativeLayout
android:layout_gravity="center_vertical"
android:gravity="right"
tools:alpha="1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toRightOf="@+id/centerMenuView"
android:id="@+id/progressBarRightHolder"
android:id="@+id/player_progressbar_right_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha="0"
tools:ignore="RtlHardcoded">
android:layout_gravity="center_vertical"
android:gravity="right"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/centerMenuView"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:alpha="1"
tools:ignore="RtlHardcoded"
tools:visibility="visible">
<ImageView
android:layout_marginBottom="220dp"
android:layout_centerHorizontal="true"
android:id="@+id/player_progressbar_right_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginBottom="220dp"
android:src="@drawable/ic_baseline_brightness_7_24"
app:tint="@android:color/white"
>
tools:ignore="ContentDescription">
</ImageView>
<ProgressBar
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:id="@+id/player_progressbar_right"
style="@android:style/Widget.Material.ProgressBar.Horizontal"
android:layout_width="4dp"
android:layout_height="150dp"
android:id="@+id/progressBarRight"
android:indeterminate="false"
style="@android:style/Widget.Material.ProgressBar.Horizontal"
android:max="100"
android:layout_marginEnd="40dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="40dp"
android:indeterminate="false"
android:max="100"
android:progress="100"
android:progressDrawable="@drawable/progress_drawable_vertical"
>
</ProgressBar>
android:progressDrawable="@drawable/progress_drawable_vertical" />
</RelativeLayout>
</LinearLayout>
</FrameLayout>

View file

@ -1,150 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@null"
android:orientation="vertical">
<LinearLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="60dp"
android:baselineAligned="false"
android:orientation="horizontal">
android:background="@null"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="60dp"
android:baselineAligned="false"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/sort_sources_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="50"
android:orientation="vertical">
android:id="@+id/sort_sources_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="50"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_rowWeight="1"
android:layout_marginTop="10dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="10dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingBottom="10dp"
android:text="@string/pick_source"
android:textColor="?attr/textColor"
android:textSize="20sp"
android:textStyle="bold" />
<ListView
android:id="@+id/sort_providers"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_rowWeight="1"
android:background="?attr/primaryBlackBackground"
android:nextFocusLeft="@id/sort_subtitles"
android:nextFocusRight="@id/apply_btt"
tools:listitem="@layout/sort_bottom_single_choice" />
</LinearLayout>
<LinearLayout
android:id="@+id/sort_subtitles_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="50"
android:orientation="vertical">
<LinearLayout
android:id="@+id/subs_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:orientation="horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<TextView
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_rowWeight="1"
android:layout_weight="1"
android:nextFocusRight="@id/sort_providers"
android:nextFocusDown="@id/sort_subtitles"
android:layout_marginTop="10dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="10dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:text="@string/pick_subtitle"
android:paddingBottom="10dp"
android:text="@string/pick_source"
android:textColor="?attr/textColor"
android:textSize="20sp"
android:textStyle="bold" />
<ImageView
android:layout_width="25dp"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginTop="0dp"
android:layout_marginEnd="10dp"
<ListView
android:requiresFadingEdge="vertical"
android:id="@+id/sort_providers"
android:layout_width="match_parent"
android:contentDescription="@string/home_change_provider_img_des"
android:src="@drawable/ic_outline_settings_24"
android:visibility="gone" />
android:layout_height="match_parent"
android:layout_rowWeight="1"
android:background="?attr/primaryBlackBackground"
android:nextFocusLeft="@id/sort_subtitles"
android:nextFocusRight="@id/apply_btt"
tools:listitem="@layout/sort_bottom_single_choice" />
</LinearLayout>
<LinearLayout
android:id="@+id/sort_subtitles_holder"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="50"
android:orientation="vertical">
<!-- android:id="@+id/subs_settings" android:foreground="?android:attr/selectableItemBackgroundBorderless"
-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_rowWeight="1"
android:layout_marginTop="10dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="10dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingBottom="10dp"
android:text="@string/pick_subtitle"
android:textColor="?attr/textColor"
android:textSize="20sp"
android:textStyle="bold" />
<ImageView
android:layout_width="25dp"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginTop="0dp"
android:layout_marginEnd="10dp"
android:contentDescription="@string/home_change_provider_img_des"
android:src="@drawable/ic_outline_settings_24"
android:visibility="gone" />
</LinearLayout>
<ListView
android:id="@+id/sort_subtitles"
android:layout_width="match_parent"
android:requiresFadingEdge="vertical"
android:id="@+id/sort_subtitles"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_rowWeight="1"
android:background="?attr/primaryBlackBackground"
android:nextFocusLeft="@id/sort_providers"
android:nextFocusRight="@id/cancel_btt"
tools:listitem="@layout/sort_bottom_single_choice" />
android:layout_height="match_parent"
android:layout_rowWeight="1"
android:background="?attr/primaryBlackBackground"
android:nextFocusLeft="@id/sort_providers"
android:nextFocusRight="@id/cancel_btt"
tools:listfooter="@layout/sort_bottom_footer_add_choice"
tools:listitem="@layout/sort_bottom_single_choice" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_gravity="bottom"
android:layout_marginTop="-60dp"
android:orientation="horizontal">
android:id="@+id/apply_btt_holder"
android:orientation="horizontal"
android:layout_gravity="bottom"
android:gravity="bottom|end"
android:layout_marginTop="-60dp"
android:layout_width="match_parent"
android:layout_height="60dp">
<!-- Kinda hack because the gravity won't behave correctly-->
<LinearLayout
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start">
<com.google.android.material.button.MaterialButton
android:id="@+id/load_btt"
app:icon="@drawable/ic_baseline_subtitles_24"
<com.google.android.material.button.MaterialButton
style="@style/WhiteButton"
android:layout_width="wrap_content"
android:nextFocusLeft="@id/cancel_btt"
android:nextFocusRight="@id/apply_btt"
android:text="@string/player_load_subtitles" />
</LinearLayout>
android:layout_gravity="center_vertical|end"
android:text="@string/sort_apply"
android:id="@+id/apply_btt"
android:layout_width="wrap_content" />
<com.google.android.material.button.MaterialButton
android:id="@+id/apply_btt"
style="@style/WhiteButton"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical|end"
android:nextFocusLeft="@id/cancel_btt"
android:nextFocusRight="@id/cancel_btt"
android:text="@string/sort_apply" />
<com.google.android.material.button.MaterialButton
android:id="@+id/cancel_btt"
style="@style/BlackButton"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical|end"
android:nextFocusLeft="@id/apply_btt"
android:nextFocusRight="@id/apply_btt"
android:text="@string/sort_cancel" />
style="@style/BlackButton"
android:layout_gravity="center_vertical|end"
android:text="@string/sort_cancel"
android:id="@+id/cancel_btt"
android:layout_width="wrap_content" />
</LinearLayout>
</LinearLayout>

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.cardview.widget.CardView 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"
@ -15,8 +14,7 @@
app:cardBackgroundColor="@color/transparent"
app:cardElevation="0dp"
android:foreground="@drawable/outline_drawable"
android:layout_marginBottom="5dp"
>
android:layout_marginBottom="5dp">
<!-- IDK BUT THIS DOES NOT SEAM LIKE A GOOD WAY OF DOING IT -->
<!--<LinearLayout
android:layout_width="fill_parent"
@ -48,14 +46,15 @@
android:layout_width="match_parent"
tools:progress="50"
android:layout_gravity="bottom"
android:layout_height="5dp">
</androidx.core.widget.ContentLoadingProgressBar>
android:layout_height="5dp" />
<LinearLayout
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:tint="?attr/textColor"
app:tint="?attr/textColor"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:layout_gravity="center_vertical"
@ -63,15 +62,21 @@
android:src="@drawable/ic_baseline_play_arrow_24"
android:contentDescription="@string/episode_play_img_des"
android:layout_height="match_parent"
android:layout_width="wrap_content"
/>
android:layout_width="wrap_content" />
<!--marquee_forever-->
<com.google.android.material.button.MaterialButton
android:layout_gravity="center"
style="@style/SmallBlackButton"
android:text="@string/filler"
android:id="@+id/episode_filler" />
<TextView
android:id="@+id/episode_text"
android:layout_marginStart="10dp"
android:layout_marginEnd="50dp"
android:layout_gravity="center_vertical"
android:gravity="center_vertical" tools:text="Episode 1"
android:gravity="center_vertical"
tools:text="Episode 1"
android:textColor="?attr/textColor"
android:scrollHorizontally="true"
@ -81,14 +86,14 @@
android:singleLine="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
</TextView>
android:layout_height="match_parent" />
</LinearLayout>
<FrameLayout
android:layout_gravity="end"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<androidx.core.widget.ContentLoadingProgressBar
android:layout_marginEnd="10dp"
android:layout_marginStart="10dp"
@ -103,13 +108,13 @@
android:layout_margin="5dp"
android:layout_gravity="end|center_vertical"
android:progress="0"
android:visibility="visible"
/>
android:visibility="visible" />
<ImageView
android:nextFocusLeft="@id/episode_holder"
android:nextFocusRight="@id/episode_holder"
android:tint="?attr/white"
app:tint="?attr/white"
android:id="@+id/result_episode_download"
android:visibility="visible"
@ -121,6 +126,6 @@
android:layout_width="30dp"
android:background="?selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_play_arrow_24"
android:contentDescription="@string/download"/>
android:contentDescription="@string/download" />
</FrameLayout>
</androidx.cardview.widget.CardView>

View file

@ -71,14 +71,28 @@
android:layout_width="match_parent"
android:layout_marginEnd="50dp"
android:layout_height="wrap_content">
<TextView
android:id="@+id/episode_text"
tools:text="1. Jobless"
android:textStyle="bold"
android:textColor="?attr/textColor"
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
<com.google.android.material.button.MaterialButton
android:layout_gravity="start"
style="@style/SmallBlackButton"
android:layout_marginEnd="10dp"
android:text="@string/filler"
android:id="@+id/episode_filler" />
<TextView
android:layout_gravity="center_vertical"
android:id="@+id/episode_text"
tools:text="1. Jobless"
android:textStyle="bold"
android:textColor="?attr/textColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
</LinearLayout>
<TextView
android:id="@+id/episode_rating"
tools:text="Rated: 8.8"
@ -113,8 +127,6 @@
android:nextFocusRight="@id/episode_holder"
android:id="@+id/result_episode_download"
android:tint="?attr/white"
android:visibility="visible"
android:layout_marginEnd="10dp"
android:layout_marginStart="10dp"
@ -124,7 +136,8 @@
android:layout_width="30dp"
android:background="?selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_play_arrow_24"
android:contentDescription="@string/download"/>
android:contentDescription="@string/download"
app:tint="?attr/white" />
</FrameLayout>
</LinearLayout>
<TextView

View file

@ -0,0 +1,23 @@
<TextView 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"
style="@style/AppTextViewStyle"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@color/text_selection_color"
android:textSize="16sp"
android:textStyle="bold"
android:gravity="center_vertical"
android:paddingStart="12dp"
android:paddingEnd="7dip"
tools:text="TEST"
android:checkMark="?android:attr/listChoiceIndicatorSingle"
android:ellipsize="marquee"
android:foreground="?attr/selectableItemBackgroundBorderless"
tools:drawableTint="?attr/textColor"
android:drawablePadding="20dp"
app:drawableTint="?attr/textColor"
app:drawableStartCompat="@drawable/ic_baseline_add_24" />

View file

@ -15,6 +15,7 @@
<TextView
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"
style="@style/AppTextViewStyle"
android:id="@android:id/text1"
@ -32,8 +33,7 @@
android:checkMark="?android:attr/listChoiceIndicatorSingle"
android:ellipsize="marquee"
android:foreground="?attr/selectableItemBackgroundBorderless"
android:drawableStart="@drawable/ic_baseline_check_24"
android:drawableTint="@color/check_selection_color"
tools:drawableTint="?attr/textColor"
android:drawablePadding="20dp"
/>
app:drawableTint="@color/check_selection_color"
app:drawableStartCompat="@drawable/ic_baseline_check_24" />

View file

@ -188,5 +188,5 @@
<fragment
android:id="@+id/navigation_player"
android:layout_height="match_parent"
android:name="com.lagradost.cloudstream3.ui.player.PlayerFragment"/>
android:name="com.lagradost.cloudstream3.ui.player.GeneratorPlayer"/>
</navigation>

View file

@ -13,7 +13,6 @@
<string name="player_speed_text_format" formatted="true">سرعة (%.2fx)</string>
<string name="rated_format" formatted="true">Rated: %.1f</string>
<string name="new_update_format" formatted="true">!تم إيجاد تحديث جديد\n%s -> %s</string>
<string name="filler_format" formatted="true">(Filler) %s</string>
<string name="app_name">CloudStream</string>
<string name="title_home">الصفحة الرئيسية</string>
@ -43,7 +42,7 @@
<string name="pick_source">المصادر</string>
<string name="pick_subtitle">الترجمة</string>
<string name="reload_error">…إعادة محاولة الاتصال</string>
<string name="result_go_back">ارجع للخلف</string>
<string name="go_back">ارجع للخلف</string>
<string name="play_episode">تشغيل الحلقة</string>
<!--<string name="need_storage">السماح بتحميل الحلقات</string>-->

View file

@ -11,7 +11,6 @@
<string name="player_speed_text_format" formatted="true">Geschwindigkeit (%.2fx)</string>
<string name="rated_format" formatted="true">Bewertung: %.1f</string>
<string name="new_update_format" formatted="true">Neues Update gefunden!\n%s -> %s</string>
<string name="filler_format" formatted="true">(Filler) %s</string>
<string name="app_name">CloudStream</string>
<string name="title_home">Startseite</string>
<string name="title_search">Suchen</string>
@ -37,7 +36,7 @@
<string name="pick_source">Quellen</string>
<string name="pick_subtitle">Untertitel</string>
<string name="reload_error">Verbindung wiederholen…</string>
<string name="result_go_back">Zurück</string>
<string name="go_back">Zurück</string>
<string name="play_episode">Episode abspielen</string>
<string name="download">Herunterladen</string>
<string name="downloaded">Heruntergeladen</string>

View file

@ -12,7 +12,7 @@
<string name="player_speed_text_format" formatted="true">Velocidad (%.2fx)</string>
<string name="rated_format" formatted="true">Puntuado: %.1f</string>
<string name="new_update_format" formatted="true">¡Nueva actualización encontrada!\n%s -> %s</string>
<string name="filler_format" formatted="true">(Relleno) %s</string>
<string name="filler" formatted="true">Relleno</string>
<string name="app_name">CloudStream</string>
<string name="title_home">Inicio</string>
<string name="title_search">Buscar</string>
@ -38,7 +38,7 @@
<string name="pick_source">Fuentes</string>
<string name="pick_subtitle">Subtítulos</string>
<string name="reload_error">Reintentar conexión…</string>
<string name="result_go_back">Ir atrás</string>
<string name="go_back">Ir atrás</string>
<string name="play_episode">Reproducir capítulo</string>
<string name="download">Descarga</string>
<string name="downloaded">Descargada</string>

View file

@ -28,7 +28,7 @@
<string name="pick_source">Sources</string>
<string name="pick_subtitle">Sous-titres</string>
<string name="reload_error">Réessayer la connection…</string>
<string name="result_go_back">Retour</string>
<string name="go_back">Retour</string>
<string name="episode_poster_img_des">Miniature de l\'Episode</string>
<string name="play_episode">Lire l\'Episode</string>
<string name="need_storage">Permet de télécharger les épisodes</string>
@ -178,7 +178,7 @@
<string name="dns_pref_summary">Utile pour contourner les bloquages des FAI</string>
<string name="new_update_format" formatted="true">Nouvelle mise à jour trouvée !
\n%s -&gt; %s</string>
<string name="filler_format" formatted="true">(Épisode spécial) %s</string>
<string name="filler" formatted="true">Épisode spécial</string>
<string name="watch_quality_pref">Qualité de visionnage préférée</string>
<string name="resize_fill">Étendre</string>
<string name="legal_notice">Non-responsabilité</string>

View file

@ -31,7 +31,7 @@
<string name="pick_source">Πηγές</string>
<string name="pick_subtitle">Υπότιτλοι</string>
<string name="reload_error">Ξανά φόρτωσε…</string>
<string name="result_go_back">Πίσω</string>
<string name="go_back">Πίσω</string>
<string name="episode_poster_img_des">Πόστερ</string>
<string name="play_episode">Αναπαραγωγή Επισοδείου</string>
<string name="need_storage">Δώσε άδεια για την λήψη επισοδείου</string>

View file

@ -26,7 +26,7 @@
<string name="play_torrent_button">टोरेंट चलाये</string>
<string name="pick_source">सूत्र</string>
<string name="reload_error">फिरसे प्रयास करे…</string>
<string name="result_go_back">वापिस जाए</string>
<string name="go_back">वापिस जाए</string>
<string name="play_episode">एपिसोड चलाये</string>
<!--<string name="need_storage">Allow to download episodes</string>-->

View file

@ -4,7 +4,7 @@
<string name="player_speed_text_format" formatted="true">Брзина (%.2fx)</string>
<string name="rated_format" formatted="true">Оценето: %.1f</string>
<string name="new_update_format" formatted="true">Пронајдена нова верзија на апликацијата!\n%s -> %s</string>
<string name="filler_format" formatted="true">(Филтер) %s</string>
<string name="filler" formatted="true">Филтер</string>
<string name="app_name">CloudStream</string>
<string name="title_home">Дома</string>
@ -35,7 +35,7 @@
<string name="pick_source">Извори</string>
<string name="pick_subtitle">Преводи</string>
<string name="reload_error">Повтори конекција…</string>
<string name="result_go_back">Оди назад</string>
<string name="go_back">Оди назад</string>
<string name="play_episode">Пушти епизода</string>
<!--<string name="need_storage">Allow to download episodes</string>-->

View file

@ -29,7 +29,7 @@
<string name="pick_source">സ്രോതസുകൾ</string>
<string name="pick_subtitle">സബ്ടൈറ്റിലുകൾ</string>
<string name="reload_error">വീണ്ടും കണക്ട് ചെയ്യുക…</string>
<string name="result_go_back">പിന്നോട്ട് പോകുക</string>
<string name="go_back">പിന്നോട്ട് പോകുക</string>
<string name="play_episode">എപ്പിസോഡ് പ്ലേയ് ചെയ്യുക</string>
<string name="download">ഡൌൺലോഡ്</string>

View file

@ -29,7 +29,7 @@
<string name="pick_source">oha aauuh</string>
<string name="pick_subtitle">ahooo ooo-ahah</string>
<string name="reload_error">aaaghhahaaaaaaaaaaooh</string>
<string name="result_go_back">aauugghhaaaghh</string>
<string name="go_back">aauugghhaaaghh</string>
<string name="episode_poster_img_des">oouuhahoooooo-ahahoooohh</string>
<string name="play_episode">aaaaa oh ohaouuhhh</string>
<string name="need_storage">ahouuhhhaaaaa ahhaaaghh ahhh</string>

View file

@ -31,7 +31,7 @@
<string name="pick_source">Bronnen</string>
<string name="pick_subtitle">Ondertitels</string>
<string name="reload_error">Probeer verbinding opnieuw…</string>
<string name="result_go_back">Ga terug</string>
<string name="go_back">Ga terug</string>
<string name="episode_poster_img_des">Afleveringsposter</string>
<string name="play_episode">Aflevering afspelen</string>
<string name="need_storage">Toestaan om afleveringen te downloaden</string>

View file

@ -13,7 +13,6 @@
<string name="player_speed_text_format" formatted="true">Avspillingshastighet (%.2fx)</string>
<string name="rated_format" formatted="true">Vurdert: %.1f</string>
<string name="new_update_format" formatted="true">Ny oppdatering funnet!\n%s -> %s</string>
<string name="filler_format" formatted="true">(Filler) %s</string>
<string name="app_name">CloudStream</string>
<string name="title_home">Hjem</string>
@ -43,7 +42,7 @@
<string name="pick_source">Kilder</string>
<string name="pick_subtitle">Undertekster</string>
<string name="reload_error">Prøv tilkoblingen på nytt…</string>
<string name="result_go_back">Gå tilbake</string>
<string name="go_back">Gå tilbake</string>
<string name="play_episode">Spille Episode</string>
<!--<string name="need_storage">Tillat å laste ned episoder</string>-->

View file

@ -32,7 +32,7 @@
<string name="pick_source">Źródła</string>
<string name="pick_subtitle">Napisy</string>
<string name="reload_error">Połącz ponownie…</string>
<string name="result_go_back">Wstecz</string>
<string name="go_back">Wstecz</string>
<string name="play_episode">Odtwórz odcinek</string>
<!--<string name="need_storage">Allow to download episodes</string>-->
@ -223,7 +223,7 @@
<string name="home_main_poster_img_des">Główny plakat</string>
<string name="home_next_random_img_des">Następny losowy</string>
<string name="go_back_img_des">Wstecz</string>
<string name="filler_format">(WYpełniacz) %s</string>
<string name="filler">WYpełniacz</string>
<string name="home_change_provider_img_des">Zmień dostawcę</string>
<string name="preview_background_img_des">Pogląd tła</string>
</resources>

View file

@ -15,7 +15,7 @@
<string name="player_speed_text_format" formatted="true">Velocidade (%.2fx)</string>
<string name="rated_format" formatted="true">Classificado: %.1f</string>
<string name="new_update_format" formatted="true">Nova atualização encontrada!\n%s -> %s</string>
<string name="filler_format" formatted="true">(Cheio) %s</string>
<string name="filler" formatted="true">Cheio</string>
<string name="app_name">CloudStream</string>
<string name="title_home">Início</string>
@ -46,7 +46,7 @@
<string name="pick_source">Origems</string>
<string name="pick_subtitle">Subtítulos</string>
<string name="reload_error">Reintentar conexão…</string>
<string name="result_go_back">Voltar Atrás</string>
<string name="go_back">Voltar Atrás</string>
<string name="play_episode">Assistir Episódio</string>
<string name="download">Descàrregar</string>

View file

@ -15,7 +15,7 @@
<string name="player_speed_text_format" formatted="true">Viteză (%.2fx)</string>
<string name="rated_format" formatted="true">Evaluat: %.1f</string>
<string name="new_update_format" formatted="true">Noua actualizare găsită!\n%s -> %s</string>
<string name="filler_format" formatted="true">(Umplut) %s</string>
<string name="filler" formatted="true">Umplut</string>
<string name="app_name">CloudStream</string>
<string name="title_home">Principal</string>
@ -46,7 +46,7 @@
<string name="pick_source">Surse</string>
<string name="pick_subtitle">Subtitrare</string>
<string name="reload_error">Reîncercarea conexiunii…</string>
<string name="result_go_back">Întoarce-te</string>
<string name="go_back">Întoarce-te</string>
<string name="play_episode">Redă episodul</string>
<string name="download">Descărcare</string>

View file

@ -34,7 +34,7 @@
<string name="pick_source">Källor</string>
<string name="pick_subtitle">Undertexter</string>
<string name="reload_error">Försök ansluta igen…</string>
<string name="result_go_back">Gå tillbaka</string>
<string name="go_back">Gå tillbaka</string>
<string name="episode_poster_img_des">@string/result_poster_img_des</string>
<string name="play_episode">Spela Avsnitt</string>
<string name="download">Ladda ner</string>

View file

@ -20,7 +20,6 @@
<string name="player_speed_text_format" formatted="true">Bilis (%.2fx)</string>
<string name="rated_format" formatted="true">Rated: %.1f</string>
<string name="new_update_format" formatted="true">Bagong update!\n%s -> %s</string>
<string name="filler_format" formatted="true">(Filler) %s</string>
<string name="app_name">CloudStream</string>
<string name="title_home">Home</string>
@ -50,7 +49,7 @@
<string name="pick_source">Sources</string>
<string name="pick_subtitle">Subtitles</string>
<string name="reload_error">Retry connection…</string>
<string name="result_go_back">Go back</string>
<string name="go_back">Go back</string>
<string name="play_episode">I-play ang episode</string>
<!--<string name="need_storage">Payagan sa Itabi</string>-->

View file

@ -31,18 +31,6 @@
<string name="prefer_media_type_key" translatable="false">prefer_media_type_key</string>
<string name="app_theme_key" translatable="false">app_theme_key</string>
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
<string name="extra_info_format" translatable="false" formatted="true">%d %s | %sMB</string>
<string name="storage_size_format" translatable="false" formatted="true">%s • %sGB</string>
<string name="download_size_format" translatable="false" formatted="true">%sMB / %sMB</string>
<string name="episode_name_format" translatable="false" formatted="true">%s %s</string>
<string name="ffw_text_format" translatable="false" formatted="true">+%d</string>
<string name="rew_text_format" translatable="false" formatted="true">-%d</string>
<string name="ffw_text_regular_format" translatable="false" formatted="true">%d</string>
<string name="rew_text_regular_format" translatable="false" formatted="true">%d</string>
<string name="app_dub_sub_episode_text_format">%s Ep %d</string>
<!-- IS NOT NEEDED TO TRANSLATE AS THEY ARE ONLY USED FOR SCREEN READERS AND WONT SHOW UP TO NORMAL USERS -->
<string name="result_poster_img_des">Afiş</string>
<string name="search_poster_img_des">@string/result_poster_img_des</string>
@ -59,7 +47,7 @@
<string name="player_speed_text_format" formatted="true">Hız (%.2fx)</string>
<string name="rated_format" formatted="true">Puan: %.1f</string>
<string name="new_update_format" formatted="true">Yeni güncelleme bulundu!\n%s -> %s</string>
<string name="filler_format" formatted="true">(Doldurucu) %s</string>
<string name="filler" formatted="true">Doldurucu</string>
<string name="app_name">CloudStream</string>
<string name="title_home">Ana Sayfa</string>
@ -89,7 +77,7 @@
<string name="pick_source">Kaynaklar</string>
<string name="pick_subtitle">Altyazılar</string>
<string name="reload_error">Yeniden bağlanmayı dene…</string>
<string name="result_go_back">Geri Git</string>
<string name="go_back">Geri Git</string>
<string name="play_episode">Bölümü Oynat</string>
<!--<string name="need_storage">Allow to download episodes</string>-->

View file

@ -27,7 +27,7 @@
<string name="pick_source">Nguồn Phim</string>
<string name="pick_subtitle">Phụ Đề</string>
<string name="reload_error">Thử kết nối lại…</string>
<string name="result_go_back">Quay lại</string>
<string name="go_back">Quay lại</string>
<string name="play_episode">Xem Tập Phim</string>
<!--<string name="need_storage">Allow to download episodes</string>-->

View file

@ -101,6 +101,7 @@
<item>Apple</item>
<item>Banana</item>
<item>Party</item>
<item>Pink Pain</item>
</string-array>
<string-array name="themes_overlay_names_values">
<item>Normal</item>
@ -111,6 +112,7 @@
<item>GreenApple</item>
<item>Banana</item>
<item>Party</item>
<item>Pink</item>
</string-array>
<string-array name="themes_names">

View file

@ -62,4 +62,5 @@
<color name="colorPrimaryGreenApple">#48E484</color>
<color name="colorPrimaryBanana">#E4D448</color>
<color name="colorPrimaryParty">#ea596e</color>
<color name="colorPrimaryPink">#ff1493</color>
</resources>

View file

@ -19,6 +19,7 @@
<string name="player_resize_enabled_key" translatable="false">player_resize_enabled_key</string>
<string name="pip_enabled_key" translatable="false">pip_enabled_key</string>
<string name="double_tap_enabled_key" translatable="false">double_tap_enabled_key</string>
<string name="double_tap_pause_enabled_key" translatable="false">double_tap_pause_enabled_key</string>
<string name="swipe_vertical_enabled_key" translatable="false">swipe_vertical_enabled_key</string>
<string name="display_sub_key" translatable="false">display_sub_key</string>
<string name="show_fillers_key" translatable="false">show_fillers_key</string>
@ -61,7 +62,7 @@
<string name="player_speed_text_format" formatted="true">Speed (%.2fx)</string>
<string name="rated_format" formatted="true">Rated: %.1f</string>
<string name="new_update_format" formatted="true">New update found!\n%s -> %s</string>
<string name="filler_format" formatted="true">(Filler) %s</string>
<string name="filler" formatted="true">Filler</string>
<string name="duration_format" formatted="true">%d min</string>
<string name="app_name">CloudStream</string>
@ -94,7 +95,7 @@
<string name="pick_source">Sources</string>
<string name="pick_subtitle">Subtitles</string>
<string name="reload_error">Retry connection…</string>
<string name="result_go_back">Go Back</string>
<string name="go_back">Go Back</string>
<string name="play_episode">Play Episode</string>
<!--<string name="need_storage">Allow to download episodes</string>-->
@ -178,8 +179,10 @@
<string name="swipe_to_change_settings">Swipe to change settings</string>
<string name="swipe_to_change_settings_des">Swipe on the left or right side to change brightness or volume</string>
<string name="double_tap_to_seek_settings">Double tap to seek</string>
<string name="double_tap_to_pause_settings">Double tap to pause</string>
<string name="double_tap_to_seek_settings_des">Tap twice on the right or left side to seek forwards or backwards
</string>
<string name="double_tap_to_pause_settings_des">Tap in the middle to pause</string>
<string name="use_system_brightness_settings">Use system brightness</string>
<string name="use_system_brightness_settings_des">Use system brightness in the app player instead of a dark
overlay
@ -226,6 +229,8 @@
<string name="cancel" translatable="false">@string/sort_cancel</string>
<string name="pause">Pause</string>
<string name="resume">Resume</string>
<string name="go_back_30">-30</string>
<string name="go_forward_30">+30</string>
<string name="delete_message" formatted="true">This will permanently delete %s\nAre you sure?</string>
<string name="status_ongoing">Ongoing</string>
@ -368,6 +373,7 @@
<string name="subtitles_example_text">The quick brown fox jumps over the lazy dog</string>
<string name="tab_recommended">Recommended</string>
<string name="player_loaded_subtitles">Loaded %s</string>
<string name="player_loaded_subtitles" formatted="true">Loaded %s</string>
<string name="player_load_subtitles">Load from file</string>
<string name="downloaded_file">Downloaded file</string>
</resources>

View file

@ -165,6 +165,15 @@
<item name="android:colorAccent">@color/colorPrimaryParty</item>
</style>
<style name="OverlayPrimaryColorPink">
<item name="colorPrimary">@color/colorPrimaryPink</item>
<item name="android:colorPrimary">@color/colorPrimaryPink</item>
<item name="colorPrimaryDark">#DD1280</item>
<item name="colorAccent">#FF4DAE</item>
<item name="colorOnPrimary">#DD1280</item>
<item name="android:colorAccent">@color/colorPrimaryPink</item>
</style>
<style name="LoadedStyle">
<item name="android:navigationBarColor">?attr/primaryGrayBackground</item>
<item name="android:windowBackground">?attr/primaryBlackBackground</item>
@ -323,6 +332,19 @@
<item name="rippleColor">?attr/textColor</item>
</style>
<style name="SmallBlackButton" parent="BlackButton">
<item name="android:layout_height">24dp</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:textSize">12sp</item>
<item name="android:layout_marginStart">0dp</item>
<item name="android:layout_marginEnd">0dp</item>
<item name="android:stateListAnimator">@null</item>
<item name="iconPadding">0dp</item>
<item name="android:paddingTop">0dp</item>
<item name="android:paddingBottom">0dp</item>
<item name="android:minWidth">0dp</item>
</style>
<style name="RoundedSelectableButtonIcon" parent="RoundedSelectableButton">
<item name="minWidth">0dp</item>
<item name="iconTint">?attr/textColor</item>

View file

@ -60,6 +60,14 @@
android:summary="@string/double_tap_to_seek_settings_des"
app:defaultValue="false"
/>
<SwitchPreference
android:icon="@drawable/netflix_pause"
app:key="@string/double_tap_pause_enabled_key"
android:title="@string/double_tap_to_pause_settings"
android:summary="@string/double_tap_to_pause_settings_des"
app:defaultValue="false"
/>
<!--
<SwitchPreference
android:icon="@drawable/ic_baseline_brightness_7_24"
app:key="@string/use_system_brightness_key"
@ -67,6 +75,7 @@
android:summary="@string/use_system_brightness_settings_des"
app:defaultValue="false"
/>
-->
</PreferenceCategory>
<PreferenceCategory
android:key="general"