diff --git a/.github/downloads.jpg b/.github/downloads.jpg index 0b671edc..ca14a664 100644 Binary files a/.github/downloads.jpg and b/.github/downloads.jpg differ diff --git a/.github/home.jpg b/.github/home.jpg index 2ccfaff4..72370d3c 100644 Binary files a/.github/home.jpg and b/.github/home.jpg differ diff --git a/.github/player.jpg b/.github/player.jpg index 0580fb03..f6959cf3 100644 Binary files a/.github/player.jpg and b/.github/player.jpg differ diff --git a/.github/results.jpg b/.github/results.jpg index 5e63169f..4dbc9b8d 100644 Binary files a/.github/results.jpg and b/.github/results.jpg differ diff --git a/.github/search.jpg b/.github/search.jpg index 998b7753..784bec89 100644 Binary files a/.github/search.jpg and b/.github/search.jpg differ diff --git a/app/src/debug/ic_launcher-playstore.png b/app/src/debug/ic_launcher-playstore.png index 3c4e788c..8c374dd9 100644 Binary files a/app/src/debug/ic_launcher-playstore.png and b/app/src/debug/ic_launcher-playstore.png differ diff --git a/app/src/debug/res/mipmap-hdpi/ic_launcher.png b/app/src/debug/res/mipmap-hdpi/ic_launcher.png index bf8e595f..c947f526 100644 Binary files a/app/src/debug/res/mipmap-hdpi/ic_launcher.png and b/app/src/debug/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png index bf8e595f..c947f526 100644 Binary files a/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png and b/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/debug/res/mipmap-mdpi/ic_launcher.png b/app/src/debug/res/mipmap-mdpi/ic_launcher.png index 935b7108..e841896f 100644 Binary files a/app/src/debug/res/mipmap-mdpi/ic_launcher.png and b/app/src/debug/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png index 935b7108..e841896f 100644 Binary files a/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png and b/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/debug/res/mipmap-xhdpi/ic_banner.png b/app/src/debug/res/mipmap-xhdpi/ic_banner.png index 16c4fdd1..6e23cfcf 100644 Binary files a/app/src/debug/res/mipmap-xhdpi/ic_banner.png and b/app/src/debug/res/mipmap-xhdpi/ic_banner.png differ diff --git a/app/src/debug/res/mipmap-xhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xhdpi/ic_launcher.png index d62f3f79..c80f9a10 100644 Binary files a/app/src/debug/res/mipmap-xhdpi/ic_launcher.png and b/app/src/debug/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png index d62f3f79..c80f9a10 100644 Binary files a/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png and b/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png index 38d6ede0..f0b781bb 100644 Binary files a/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png index 38d6ede0..f0b781bb 100644 Binary files a/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png and b/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png index 81c5621b..d5fa9d70 100644 Binary files a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png index 81c5621b..d5fa9d70 100644 Binary files a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png and b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 7686a84c..cd320060 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -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() diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index b5441b94..c4d1f66f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -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() + 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 + ): 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 + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt index fbc055a8..19e24f74 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt @@ -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) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt index 19f2b25b..1ddd752f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt @@ -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() { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt index 3abd827e..6fc81473 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -60,7 +60,7 @@ class EpisodeAdapter( private val clickCallback: (EpisodeClickEvent) -> Unit, private val downloadClickCallback: (DownloadClickEvent) -> Unit, ) : RecyclerView.Adapter() { - private var cardList: MutableList = mutableListOf() + var cardList: MutableList = mutableListOf() private val mBoundViewHolders: HashSet = HashSet() private fun getAllBoundViewHolders(): Set? { @@ -239,7 +239,6 @@ class EpisodeAdapter( itemView.setOnLongClickListener { clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_OPTIONS, card)) - return@setOnLongClickListener true } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index 5fc61146..1ec2dd39 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -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 + ) } } } diff --git a/app/src/main/res/drawable/example_poster.jpg b/app/src/main/res/drawable/example_poster.jpg index 6d6e04ee..f5d7345b 100644 Binary files a/app/src/main/res/drawable/example_poster.jpg and b/app/src/main/res/drawable/example_poster.jpg differ diff --git a/app/src/main/res/drawable/subtitles_preview_background.jpg b/app/src/main/res/drawable/subtitles_preview_background.jpg index c7cd5f2e..c140e9b3 100644 Binary files a/app/src/main/res/drawable/subtitles_preview_background.jpg and b/app/src/main/res/drawable/subtitles_preview_background.jpg differ diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index 7c5b4edd..a428b80f 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -420,14 +420,14 @@ + android:minWidth="250dp" + android:nextFocusRight="@id/result_bookmark_button"> @@ -753,6 +753,16 @@ android:orientation="horizontal" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" tools:listitem="@layout/result_episode" /> + + diff --git a/app/src/main/res/mipmap-xhdpi/ic_banner.png b/app/src/main/res/mipmap-xhdpi/ic_banner.png index 7a3b1b33..d1b9bdfb 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_banner.png and b/app/src/main/res/mipmap-xhdpi/ic_banner.png differ diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index ea6eb140..fdc89692 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -455,4 +455,5 @@ Wspierane Język Najpierw zainstaluj rozszerzenie + Zaaktualizowano %d rozszerzeń diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 34315a28..9f45c2b2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -601,6 +601,7 @@ Downloaded: %d Disabled: %d Not downloaded: %d + Updated %d plugins Add a repository to install site extensions View community repositories Public list diff --git a/app/src/prerelease/res/mipmap-xhdpi/ic_banner.png b/app/src/prerelease/res/mipmap-xhdpi/ic_banner.png index bbf98919..f518ca52 100644 Binary files a/app/src/prerelease/res/mipmap-xhdpi/ic_banner.png and b/app/src/prerelease/res/mipmap-xhdpi/ic_banner.png differ diff --git a/app/src/release/res/mipmap-xhdpi/ic_banner.png b/app/src/release/res/mipmap-xhdpi/ic_banner.png index 7a3b1b33..d1b9bdfb 100644 Binary files a/app/src/release/res/mipmap-xhdpi/ic_banner.png and b/app/src/release/res/mipmap-xhdpi/ic_banner.png differ