Merge branch 'master' of https://github.com/recloudstream/cloudstream into recloudstream-master
# Conflicts: # app/src/main/ic_launcher-playstore.png # app/src/main/res/mipmap-hdpi/ic_launcher.png # app/src/main/res/mipmap-hdpi/ic_launcher_round.png # app/src/main/res/mipmap-mdpi/ic_launcher.png # app/src/main/res/mipmap-mdpi/ic_launcher_round.png # app/src/main/res/mipmap-xhdpi/ic_launcher.png # app/src/main/res/mipmap-xhdpi/ic_launcher_round.png # app/src/main/res/mipmap-xxhdpi/ic_launcher.png # app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png # app/src/main/res/mipmap-xxxhdpi/ic_launcher.png # app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png # app/src/prerelease/ic_launcher-playstore.png # app/src/prerelease/res/mipmap-hdpi/ic_launcher.png # app/src/prerelease/res/mipmap-hdpi/ic_launcher_round.png # app/src/prerelease/res/mipmap-mdpi/ic_launcher.png # app/src/prerelease/res/mipmap-mdpi/ic_launcher_round.png # app/src/prerelease/res/mipmap-xhdpi/ic_launcher.png # app/src/prerelease/res/mipmap-xhdpi/ic_launcher_round.png # app/src/prerelease/res/mipmap-xxhdpi/ic_launcher.png # app/src/prerelease/res/mipmap-xxhdpi/ic_launcher_round.png # app/src/prerelease/res/mipmap-xxxhdpi/ic_launcher.png # app/src/prerelease/res/mipmap-xxxhdpi/ic_launcher_round.png
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 136 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 149 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 8.2 KiB |
|
@ -150,7 +150,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
* @return true if the str has launched an app task (be it successful or not)
|
||||
* @param isWebview does not handle providers and opening download page if true. Can still add repos and login.
|
||||
* */
|
||||
fun handleAppIntentUrl(activity: FragmentActivity?, str: String?, isWebview: Boolean): Boolean =
|
||||
fun handleAppIntentUrl(
|
||||
activity: FragmentActivity?,
|
||||
str: String?,
|
||||
isWebview: Boolean
|
||||
): Boolean =
|
||||
with(activity) {
|
||||
if (str != null && this != null) {
|
||||
if (str.startsWith("https://cs.repo")) {
|
||||
|
@ -191,7 +195,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
val url = str.replaceFirst(appStringRepo, "https")
|
||||
loadRepository(url)
|
||||
return true
|
||||
} else if (!isWebview){
|
||||
} else if (!isWebview) {
|
||||
if (str.startsWith(DOWNLOAD_NAVIGATE_TO)) {
|
||||
this.navigate(R.id.navigation_downloads)
|
||||
return true
|
||||
|
@ -565,9 +569,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
for (api in accountManagers) {
|
||||
api.init()
|
||||
}
|
||||
}
|
||||
|
||||
ioSafe {
|
||||
inAppAuths.apmap { api ->
|
||||
try {
|
||||
api.initialize()
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
package com.lagradost.cloudstream3.plugins
|
||||
|
||||
import android.app.*
|
||||
import dalvik.system.PathClassLoader
|
||||
import com.google.gson.Gson
|
||||
import android.content.res.AssetManager
|
||||
import android.content.res.Resources
|
||||
import android.os.Environment
|
||||
import android.widget.Toast
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.net.toUri
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
|
@ -25,7 +31,9 @@ import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
|
|||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
||||
import com.lagradost.cloudstream3.utils.extractorApis
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
@ -38,6 +46,9 @@ import java.util.*
|
|||
const val PLUGINS_KEY = "PLUGINS_KEY"
|
||||
const val PLUGINS_KEY_LOCAL = "PLUGINS_KEY_LOCAL"
|
||||
|
||||
const val EXTENSIONS_CHANNEL_ID = "cloudstream3.extensions"
|
||||
const val EXTENSIONS_CHANNEL_NAME = "Extensions"
|
||||
const val EXTENSIONS_CHANNEL_DESCRIPT = "Extension notification channel"
|
||||
|
||||
// Data class for internal storage
|
||||
data class PluginData(
|
||||
|
@ -78,6 +89,8 @@ object PluginManager {
|
|||
|
||||
const val TAG = "PluginManager"
|
||||
|
||||
private var hasCreatedNotChanel = false
|
||||
|
||||
/**
|
||||
* Store data about the plugin for fetching later
|
||||
* */
|
||||
|
@ -220,8 +233,11 @@ object PluginManager {
|
|||
"Outdated plugins: ${outdatedPlugins.filter { it.isOutdated }}"
|
||||
}
|
||||
|
||||
val updatedPlugins = mutableListOf<String>()
|
||||
|
||||
outdatedPlugins.apmap { pluginData ->
|
||||
if (pluginData.isDisabled) {
|
||||
//updatedPlugins.add(activity.getString(R.string.single_plugin_disabled, pluginData.onlineData.second.name))
|
||||
unloadPlugin(pluginData.savedData.filePath)
|
||||
} else if (pluginData.isOutdated) {
|
||||
downloadAndLoadPlugin(
|
||||
|
@ -229,10 +245,17 @@ object PluginManager {
|
|||
pluginData.onlineData.second.url,
|
||||
pluginData.savedData.internalName,
|
||||
pluginData.onlineData.first
|
||||
)
|
||||
).let { success ->
|
||||
if (success)
|
||||
updatedPlugins.add(pluginData.onlineData.second.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
createNotification(activity, updatedPlugins)
|
||||
}
|
||||
|
||||
ioSafe {
|
||||
afterPluginsLoadedEvent.invoke(true)
|
||||
}
|
||||
|
@ -438,4 +461,59 @@ object PluginManager {
|
|||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun Context.createNotificationChannel() {
|
||||
hasCreatedNotChanel = true
|
||||
// Create the NotificationChannel, but only on API 26+ because
|
||||
// the NotificationChannel class is new and not in the support library
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val name = EXTENSIONS_CHANNEL_NAME //getString(R.string.channel_name)
|
||||
val descriptionText = EXTENSIONS_CHANNEL_DESCRIPT//getString(R.string.channel_description)
|
||||
val importance = NotificationManager.IMPORTANCE_LOW
|
||||
val channel = NotificationChannel(EXTENSIONS_CHANNEL_ID, name, importance).apply {
|
||||
description = descriptionText
|
||||
}
|
||||
// Register the channel with the system
|
||||
val notificationManager: NotificationManager =
|
||||
this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
private fun createNotification(
|
||||
context: Context,
|
||||
extensionNames: List<String>
|
||||
): Notification? {
|
||||
try {
|
||||
if (extensionNames.isEmpty()) return null
|
||||
|
||||
val content = extensionNames.joinToString(", ")
|
||||
// main { // DON'T WANT TO SLOW IT DOWN
|
||||
val builder = NotificationCompat.Builder(context, EXTENSIONS_CHANNEL_ID)
|
||||
.setAutoCancel(false)
|
||||
.setColorized(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setSilent(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setColor(context.colorFromAttribute(R.attr.colorPrimary))
|
||||
.setContentTitle(context.getString(R.string.plugins_updated, extensionNames.size))
|
||||
.setSmallIcon(R.drawable.ic_baseline_extension_24)
|
||||
.setStyle(NotificationCompat.BigTextStyle()
|
||||
.bigText(content))
|
||||
.setContentText(content)
|
||||
|
||||
if (!hasCreatedNotChanel) {
|
||||
context.createNotificationChannel()
|
||||
}
|
||||
|
||||
val notification = builder.build()
|
||||
with(NotificationManagerCompat.from(context)) {
|
||||
// notificationId is a unique int for each notification that you must define
|
||||
notify((System.currentTimeMillis()/1000).toInt(), notification)
|
||||
}
|
||||
return notification
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -40,13 +40,15 @@ class WebviewFragment : Fragment() {
|
|||
return super.shouldOverrideUrlLoading(view, request)
|
||||
}
|
||||
}
|
||||
|
||||
WebViewResolver.webViewUserAgent = web_view.settings.userAgentString
|
||||
|
||||
web_view.addJavascriptInterface(RepoApi(activity), "RepoApi")
|
||||
web_view.settings.javaScriptEnabled = true
|
||||
web_view.settings.userAgentString = USER_AGENT
|
||||
web_view.settings.domStorageEnabled = true
|
||||
// WebView.setWebContentsDebuggingEnabled(true)
|
||||
|
||||
WebViewResolver.webViewUserAgent = web_view.settings.userAgentString
|
||||
// web_view.settings.userAgentString = USER_AGENT
|
||||
web_view.loadUrl(url)
|
||||
}
|
||||
|
||||
|
|
|
@ -611,6 +611,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
player_lock?.isGone = !isShowing
|
||||
//player_media_route_button?.isClickable = !isGone
|
||||
player_go_back_holder?.isGone = isGone
|
||||
player_sources_btt?.isGone = isGone
|
||||
}
|
||||
|
||||
private fun updateLockUI() {
|
||||
|
|
|
@ -60,7 +60,7 @@ class EpisodeAdapter(
|
|||
private val clickCallback: (EpisodeClickEvent) -> Unit,
|
||||
private val downloadClickCallback: (DownloadClickEvent) -> Unit,
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
private var cardList: MutableList<ResultEpisode> = mutableListOf()
|
||||
var cardList: MutableList<ResultEpisode> = mutableListOf()
|
||||
|
||||
private val mBoundViewHolders: HashSet<DownloadButtonViewHolder> = HashSet()
|
||||
private fun getAllBoundViewHolders(): Set<DownloadButtonViewHolder?>? {
|
||||
|
@ -239,7 +239,6 @@ class EpisodeAdapter(
|
|||
|
||||
itemView.setOnLongClickListener {
|
||||
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_OPTIONS, card))
|
||||
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
|
||||
|
|
|
@ -95,6 +95,7 @@ import kotlinx.android.synthetic.main.fragment_result.result_vpn
|
|||
import kotlinx.android.synthetic.main.fragment_result_swipe.*
|
||||
import kotlinx.android.synthetic.main.fragment_result_tv.*
|
||||
import kotlinx.android.synthetic.main.result_sync.*
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
|
||||
|
@ -293,7 +294,7 @@ open class ResultFragment : ResultTrailerPlayer() {
|
|||
result_reload_connection_open_in_browser?.isVisible = true
|
||||
}
|
||||
2 -> {
|
||||
result_bookmark_fab?.isGone = isTvSettings()
|
||||
result_bookmark_fab?.isGone = isTrueTvSettings()
|
||||
result_bookmark_fab?.extend()
|
||||
//if (result_bookmark_button?.context?.isTrueTvSettings() == true) {
|
||||
// when {
|
||||
|
@ -412,7 +413,39 @@ open class ResultFragment : ResultTrailerPlayer() {
|
|||
is ResourceSome.Success -> {
|
||||
result_episodes?.isVisible = true
|
||||
result_episode_loading?.isVisible = false
|
||||
|
||||
/*
|
||||
* Okay so what is this fuckery?
|
||||
* Basically Android TV will crash if you request a new focus while
|
||||
* the adapter gets updated.
|
||||
*
|
||||
* This means that if you load thumbnails and request a next focus at the same time
|
||||
* the app will crash without any way to catch it!
|
||||
*
|
||||
* How to bypass this?
|
||||
* This code basically steals the focus for 500ms and puts it in an inescapable view
|
||||
* then lets out the focus by requesting focus to result_episodes
|
||||
*/
|
||||
|
||||
// Do not use this.isTv, that is the player
|
||||
val isTv = isTvSettings()
|
||||
val hasEpisodes =
|
||||
!(result_episodes?.adapter as? EpisodeAdapter?)?.cardList.isNullOrEmpty()
|
||||
|
||||
if (isTv && hasEpisodes) {
|
||||
// Make it impossible to focus anywhere else!
|
||||
temporary_no_focus?.isFocusable = true
|
||||
temporary_no_focus?.requestFocus()
|
||||
}
|
||||
|
||||
(result_episodes?.adapter as? EpisodeAdapter?)?.updateList(episodes.value)
|
||||
|
||||
if (isTv && hasEpisodes) main {
|
||||
delay(500)
|
||||
temporary_no_focus?.isFocusable = false
|
||||
// This might make some people sad as it changes the focus when leaving an episode :(
|
||||
result_episodes?.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -458,7 +491,14 @@ open class ResultFragment : ResultTrailerPlayer() {
|
|||
val storedData = getStoredData(activity ?: context ?: return) ?: return
|
||||
|
||||
//viewModel.clear()
|
||||
viewModel.load(activity, storedData.url ?: return, storedData.apiName, storedData.showFillers, storedData.dubStatus, storedData.start)
|
||||
viewModel.load(
|
||||
activity,
|
||||
storedData.url ?: return,
|
||||
storedData.apiName,
|
||||
storedData.showFillers,
|
||||
storedData.dubStatus,
|
||||
storedData.start
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -916,7 +956,14 @@ open class ResultFragment : ResultTrailerPlayer() {
|
|||
|
||||
if (storedData?.url != null) {
|
||||
result_reload_connectionerror.setOnClickListener {
|
||||
viewModel.load(activity, storedData.url, storedData.apiName, storedData.showFillers, storedData.dubStatus, storedData.start)
|
||||
viewModel.load(
|
||||
activity,
|
||||
storedData.url,
|
||||
storedData.apiName,
|
||||
storedData.showFillers,
|
||||
storedData.dubStatus,
|
||||
storedData.start
|
||||
)
|
||||
}
|
||||
|
||||
result_reload_connection_open_in_browser?.setOnClickListener {
|
||||
|
@ -952,7 +999,14 @@ open class ResultFragment : ResultTrailerPlayer() {
|
|||
|
||||
if (restart || !viewModel.hasLoaded()) {
|
||||
//viewModel.clear()
|
||||
viewModel.load(activity, storedData.url, storedData.apiName, storedData.showFillers, storedData.dubStatus, storedData.start)
|
||||
viewModel.load(
|
||||
activity,
|
||||
storedData.url,
|
||||
storedData.apiName,
|
||||
storedData.showFillers,
|
||||
storedData.dubStatus,
|
||||
storedData.start
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.6 MiB |
|
@ -420,14 +420,14 @@
|
|||
|
||||
|
||||
<FrameLayout
|
||||
android:nextFocusRight="@id/result_bookmark_button"
|
||||
android:id="@+id/result_movie_progress_downloaded_holder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_weight="1"
|
||||
android:minWidth="250dp">
|
||||
android:minWidth="250dp"
|
||||
android:nextFocusRight="@id/result_bookmark_button">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_download_movie"
|
||||
|
@ -510,17 +510,17 @@
|
|||
</FrameLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:nextFocusLeft="@id/result_movie_progress_downloaded_holder"
|
||||
android:nextFocusDown="@id/result_resume_series_button_play"
|
||||
|
||||
android:id="@+id/result_bookmark_button"
|
||||
style="@style/BlackButton"
|
||||
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_weight="1"
|
||||
android:minWidth="250dp"
|
||||
android:nextFocusLeft="@id/result_movie_progress_downloaded_holder"
|
||||
android:nextFocusDown="@id/result_resume_series_button_play"
|
||||
android:text="@string/type_none"
|
||||
android:visibility="visible" />
|
||||
</LinearLayout>
|
||||
|
@ -753,6 +753,16 @@
|
|||
android:orientation="horizontal"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/result_episode" />
|
||||
|
||||
<View
|
||||
android:id="@+id/temporary_no_focus"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="1dp"
|
||||
android:focusable="false"
|
||||
android:nextFocusLeft="@id/temporary_no_focus"
|
||||
android:nextFocusRight="@id/temporary_no_focus"
|
||||
android:nextFocusUp="@id/temporary_no_focus"
|
||||
android:nextFocusDown="@id/temporary_no_focus" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.0 KiB |
|
@ -455,4 +455,5 @@
|
|||
<string name="extension_types">Wspierane</string>
|
||||
<string name="extension_language">Język</string>
|
||||
<string name="extension_install_first">Najpierw zainstaluj rozszerzenie</string>
|
||||
<string name="plugins_updated">Zaaktualizowano %d rozszerzeń</string>
|
||||
</resources>
|
||||
|
|
|
@ -601,6 +601,7 @@
|
|||
<string name="plugins_downloaded" formatted="true">Downloaded: %d</string>
|
||||
<string name="plugins_disabled" formatted="true">Disabled: %d</string>
|
||||
<string name="plugins_not_downloaded" formatted="true">Not downloaded: %d</string>
|
||||
<string name="plugins_updated" formatted="true">Updated %d plugins</string>
|
||||
<string name="blank_repo_message">Add a repository to install site extensions</string>
|
||||
<string name="view_public_repositories_button">View community repositories</string>
|
||||
<string name="view_public_repositories_button_short">Public list</string>
|
||||
|
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.0 KiB |