forked from recloudstream/cloudstream
inapp updater and intent and dubbedanime
This commit is contained in:
parent
d12a8894ae
commit
1886fcd80e
13 changed files with 438 additions and 20 deletions
|
@ -30,11 +30,24 @@
|
||||||
android:supportsPictureInPicture="true">
|
android:supportsPictureInPicture="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
|
||||||
|
<data android:scheme="https" android:host="bestdubbedanime.com" android:pathPrefix="/"/>
|
||||||
|
<data android:scheme="https" android:host="tenshi.moe" android:pathPrefix="/"/>
|
||||||
|
<data android:scheme="https" android:host="www.wcostream.com" android:pathPrefix="/"/>
|
||||||
|
<data android:scheme="https" android:host="wcostream.cc" android:pathPrefix="/"/>
|
||||||
|
<data android:scheme="https" android:host="hdm.to" android:pathPrefix="/"/>
|
||||||
|
<data android:scheme="https" android:host="lookmovie.io" android:pathPrefix="/"/>
|
||||||
|
<data android:scheme="https" android:host="trailers.to" android:pathPrefix="/"/>
|
||||||
|
<data android:scheme="https" android:host="www.vmovee.watch" android:pathPrefix="/"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".receivers.VideoDownloadRestartReceiver"
|
android:name=".receivers.VideoDownloadRestartReceiver"
|
||||||
|
|
|
@ -5,10 +5,7 @@ import androidx.preference.PreferenceManager
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||||
import com.fasterxml.jackson.databind.json.JsonMapper
|
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||||
import com.lagradost.cloudstream3.animeproviders.DubbedAnimeProvider
|
import com.lagradost.cloudstream3.animeproviders.*
|
||||||
import com.lagradost.cloudstream3.animeproviders.TenshiProvider
|
|
||||||
import com.lagradost.cloudstream3.animeproviders.WatchCartoonOnlineProvider
|
|
||||||
import com.lagradost.cloudstream3.animeproviders.WcoProvider
|
|
||||||
import com.lagradost.cloudstream3.movieproviders.HDMProvider
|
import com.lagradost.cloudstream3.movieproviders.HDMProvider
|
||||||
import com.lagradost.cloudstream3.movieproviders.TrailersToProvider
|
import com.lagradost.cloudstream3.movieproviders.TrailersToProvider
|
||||||
import com.lagradost.cloudstream3.movieproviders.VMoveeProvider
|
import com.lagradost.cloudstream3.movieproviders.VMoveeProvider
|
||||||
|
@ -25,6 +22,8 @@ val mapper = JsonMapper.builder().addModule(KotlinModule())
|
||||||
object APIHolder {
|
object APIHolder {
|
||||||
val unixTime: Long
|
val unixTime: Long
|
||||||
get() = System.currentTimeMillis() / 1000L
|
get() = System.currentTimeMillis() / 1000L
|
||||||
|
val unixTimeMS: Long
|
||||||
|
get() = System.currentTimeMillis()
|
||||||
|
|
||||||
private const val defProvider = 0
|
private const val defProvider = 0
|
||||||
|
|
||||||
|
@ -117,11 +116,11 @@ abstract class MainAPI {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun search(query: String): ArrayList<SearchResponse>? {
|
open fun search(query: String): List<SearchResponse>? {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun quickSearch(query: String): ArrayList<SearchResponse>? {
|
open fun quickSearch(query: String): List<SearchResponse>? {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,8 +32,10 @@ import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.removeKey
|
import com.lagradost.cloudstream3.utils.DataStore.removeKey
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos
|
||||||
import com.lagradost.cloudstream3.utils.Event
|
import com.lagradost.cloudstream3.utils.Event
|
||||||
|
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
|
||||||
import kotlinx.android.synthetic.main.activity_main.*
|
import kotlinx.android.synthetic.main.activity_main.*
|
||||||
import kotlinx.android.synthetic.main.fragment_result.*
|
import kotlinx.android.synthetic.main.fragment_result.*
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
const val VLC_PACKAGE = "org.videolan.vlc"
|
const val VLC_PACKAGE = "org.videolan.vlc"
|
||||||
const val VLC_INTENT_ACTION_RESULT = "org.videolan.vlc.player.result"
|
const val VLC_INTENT_ACTION_RESULT = "org.videolan.vlc.player.result"
|
||||||
|
@ -295,6 +297,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
createISO()
|
createISO()
|
||||||
}*/
|
}*/
|
||||||
handleAppIntent(intent)
|
handleAppIntent(intent)
|
||||||
|
|
||||||
|
thread {
|
||||||
|
runAutoUpdate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.APIHolder.unixTime
|
import com.lagradost.cloudstream3.APIHolder.unixTime
|
||||||
|
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
|
@ -17,6 +18,8 @@ class DubbedAnimeProvider : MainAPI() {
|
||||||
get() = "DubbedAnime"
|
get() = "DubbedAnime"
|
||||||
override val hasQuickSearch: Boolean
|
override val hasQuickSearch: Boolean
|
||||||
get() = true
|
get() = true
|
||||||
|
override val hasMainPage: Boolean
|
||||||
|
get() = true
|
||||||
|
|
||||||
override val supportedTypes: Set<TvType>
|
override val supportedTypes: Set<TvType>
|
||||||
get() = setOf(
|
get() = setOf(
|
||||||
|
@ -57,6 +60,67 @@ class DubbedAnimeProvider : MainAPI() {
|
||||||
@JsonProperty("tags") val tags: String,*/
|
@JsonProperty("tags") val tags: String,*/
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun parseDocumentTrending(url: String): List<SearchResponse> {
|
||||||
|
val response = khttp.get(url)
|
||||||
|
val document = Jsoup.parse(response.text)
|
||||||
|
return document.select("li > a").map {
|
||||||
|
val href = fixUrl(it.attr("href"))
|
||||||
|
val title = it.selectFirst("> div > div.cittx").text()
|
||||||
|
val poster = fixUrl(it.selectFirst("> div > div.imghddde > img").attr("src"))
|
||||||
|
AnimeSearchResponse(
|
||||||
|
title,
|
||||||
|
href,
|
||||||
|
this.name,
|
||||||
|
TvType.Anime,
|
||||||
|
poster,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
EnumSet.of(DubStatus.Dubbed),
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseDocument(url: String, trimEpisode: Boolean = false): List<SearchResponse> {
|
||||||
|
val response = khttp.get(url)
|
||||||
|
val document = Jsoup.parse(response.text)
|
||||||
|
return document.select("a.grid__link").map {
|
||||||
|
val href = fixUrl(it.attr("href"))
|
||||||
|
val title = it.selectFirst("> div.gridtitlek").text()
|
||||||
|
val poster = fixUrl(it.selectFirst("> img.grid__img").attr("src"))
|
||||||
|
AnimeSearchResponse(
|
||||||
|
title,
|
||||||
|
if (trimEpisode) href.removeRange(href.lastIndexOf('/'), href.length) else href,
|
||||||
|
this.name,
|
||||||
|
TvType.Anime,
|
||||||
|
poster,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
EnumSet.of(DubStatus.Dubbed),
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMainPage(): HomePageResponse {
|
||||||
|
val trendingUrl = "$mainUrl/xz/trending.php?_=$unixTimeMS"
|
||||||
|
val lastEpisodeUrl = "$mainUrl/xz/epgrid.php?p=1&_=$unixTimeMS"
|
||||||
|
val recentlyAddedUrl = "$mainUrl/xz/gridgrabrecent.php?p=1&_=$unixTimeMS"
|
||||||
|
//val allUrl = "$mainUrl/xz/gridgrab.php?p=1&limit=12&_=$unixTimeMS"
|
||||||
|
|
||||||
|
val listItems = listOf(
|
||||||
|
HomePageList("Trending", parseDocumentTrending(trendingUrl)),
|
||||||
|
HomePageList("Recently Added", parseDocument(recentlyAddedUrl)),
|
||||||
|
HomePageList("Recent Releases", parseDocument(lastEpisodeUrl, true)),
|
||||||
|
// HomePageList("All", parseDocument(allUrl))
|
||||||
|
)
|
||||||
|
|
||||||
|
return HomePageResponse(listItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun getAnimeEpisode(slug: String, isMovie: Boolean): EpisodeInfo {
|
private fun getAnimeEpisode(slug: String, isMovie: Boolean): EpisodeInfo {
|
||||||
val url =
|
val url =
|
||||||
mainUrl + (if (isMovie) "/movies/jsonMovie" else "/xz/v3/jsonEpi") + ".php?slug=$slug&_=$unixTime"
|
mainUrl + (if (isMovie) "/movies/jsonMovie" else "/xz/v3/jsonEpi") + ".php?slug=$slug&_=$unixTime"
|
||||||
|
@ -76,8 +140,8 @@ class DubbedAnimeProvider : MainAPI() {
|
||||||
return href.replace("$mainUrl/", "")
|
return href.replace("$mainUrl/", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun quickSearch(query: String): ArrayList<SearchResponse> {
|
override fun quickSearch(query: String): List<SearchResponse> {
|
||||||
val url = "$mainUrl/xz/searchgrid.php?p=1&limit=12&s=$query&_=${unixTime}"
|
val url = "$mainUrl/xz/searchgrid.php?p=1&limit=12&s=$query&_=$unixTime"
|
||||||
val response = khttp.get(url)
|
val response = khttp.get(url)
|
||||||
val document = Jsoup.parse(response.text)
|
val document = Jsoup.parse(response.text)
|
||||||
val items = document.select("div.grid__item > a")
|
val items = document.select("div.grid__item > a")
|
||||||
|
@ -111,7 +175,7 @@ class DubbedAnimeProvider : MainAPI() {
|
||||||
return returnValue
|
return returnValue
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun search(query: String): ArrayList<SearchResponse> {
|
override fun search(query: String): List<SearchResponse> {
|
||||||
val url = "$mainUrl/search/$query"
|
val url = "$mainUrl/search/$query"
|
||||||
val response = khttp.get(url)
|
val response = khttp.get(url)
|
||||||
val document = Jsoup.parse(response.text)
|
val document = Jsoup.parse(response.text)
|
||||||
|
@ -188,9 +252,8 @@ class DubbedAnimeProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun load(url: String): LoadResponse {
|
override fun load(url: String): LoadResponse {
|
||||||
val slug = url.replace("$mainUrl/","")
|
if (getIsMovie(url)) {
|
||||||
if (getIsMovie(slug)) {
|
val realSlug = url.replace("movies/", "")
|
||||||
val realSlug = slug.replace("movies/", "")
|
|
||||||
val episode = getAnimeEpisode(realSlug, true)
|
val episode = getAnimeEpisode(realSlug, true)
|
||||||
val poster = episode.previewImg ?: episode.wideImg
|
val poster = episode.previewImg ?: episode.wideImg
|
||||||
return MovieLoadResponse(
|
return MovieLoadResponse(
|
||||||
|
@ -205,7 +268,7 @@ class DubbedAnimeProvider : MainAPI() {
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val response = khttp.get("$mainUrl/$slug")
|
val response = khttp.get(url)
|
||||||
val document = Jsoup.parse(response.text)
|
val document = Jsoup.parse(response.text)
|
||||||
val title = document.selectFirst("h4").text()
|
val title = document.selectFirst("h4").text()
|
||||||
val descriptHeader = document.selectFirst("div.animeDescript")
|
val descriptHeader = document.selectFirst("div.animeDescript")
|
||||||
|
@ -220,7 +283,18 @@ class DubbedAnimeProvider : MainAPI() {
|
||||||
|
|
||||||
val img = fixUrl(document.select("div.fkimgs > img").attr("src"))
|
val img = fixUrl(document.select("div.fkimgs > img").attr("src"))
|
||||||
return AnimeLoadResponse(
|
return AnimeLoadResponse(
|
||||||
null, null, title, "$mainUrl/$slug", this.name, TvType.Anime, img, year, ArrayList(episodes), null, null, descript,
|
null,
|
||||||
|
null,
|
||||||
|
title,
|
||||||
|
url,
|
||||||
|
this.name,
|
||||||
|
TvType.Anime,
|
||||||
|
img,
|
||||||
|
year,
|
||||||
|
ArrayList(episodes),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
descript,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,7 @@ class APIRepository(val api: MainAPI) {
|
||||||
|
|
||||||
suspend fun load(url: String): Resource<LoadResponse> {
|
suspend fun load(url: String): Resource<LoadResponse> {
|
||||||
return safeApiCall {
|
return safeApiCall {
|
||||||
// remove suffix for some slugs to handle correctly
|
api.load(api.fixUrl(url)) ?: throw ErrorLoadingException()
|
||||||
api.load(url.removeSuffix("/")) ?: throw ErrorLoadingException()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +29,7 @@ class APIRepository(val api: MainAPI) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun quickSearch(query: String): Resource<ArrayList<SearchResponse>> {
|
suspend fun quickSearch(query: String): Resource<List<SearchResponse>> {
|
||||||
return safeApiCall {
|
return safeApiCall {
|
||||||
api.quickSearch(query) ?: throw ErrorLoadingException()
|
api.quickSearch(query) ?: throw ErrorLoadingException()
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,7 +122,12 @@ class HomeFragment : Fragment() {
|
||||||
val MAX_BREAK_COUNT = 10
|
val MAX_BREAK_COUNT = 10
|
||||||
|
|
||||||
while (random?.posterUrl == null) {
|
while (random?.posterUrl == null) {
|
||||||
random = home.items.random().list.random()
|
try {
|
||||||
|
random = home.items.random().list.random()
|
||||||
|
} catch (e : Exception) {
|
||||||
|
// probs Collection is empty.
|
||||||
|
}
|
||||||
|
|
||||||
breakCount++
|
breakCount++
|
||||||
if (breakCount > MAX_BREAK_COUNT) {
|
if (breakCount > MAX_BREAK_COUNT) {
|
||||||
break
|
break
|
||||||
|
|
|
@ -1,16 +1,30 @@
|
||||||
package com.lagradost.cloudstream3.ui.settings
|
package com.lagradost.cloudstream3.ui.settings
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
|
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
|
||||||
|
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
class SettingsFragment : PreferenceFragmentCompat() {
|
class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
setPreferencesFromResource(R.xml.settings, rootKey)
|
setPreferencesFromResource(R.xml.settings, rootKey)
|
||||||
|
val updatePrefrence = findPreference<Preference>(getString(R.string.manual_check_update_key))!!
|
||||||
|
updatePrefrence.setOnPreferenceClickListener {
|
||||||
|
thread {
|
||||||
|
if (!requireActivity().runAutoUpdate(false)) {
|
||||||
|
activity?.runOnUiThread {
|
||||||
|
Toast.makeText(this.context, "No Update Found", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@setOnPreferenceClickListener true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
|
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
|
||||||
|
|
|
@ -0,0 +1,283 @@
|
||||||
|
package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||||
|
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.R
|
||||||
|
import java.io.*
|
||||||
|
import java.net.URL
|
||||||
|
import java.net.URLConnection
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
const val UPDATE_TIME = 1000
|
||||||
|
|
||||||
|
class InAppUpdater {
|
||||||
|
companion object {
|
||||||
|
// === IN APP UPDATER ===
|
||||||
|
data class GithubAsset(
|
||||||
|
@JsonProperty("name") val name: String,
|
||||||
|
@JsonProperty("size") val size: Int, // Size bytes
|
||||||
|
@JsonProperty("browser_download_url") val browser_download_url: String, // download link
|
||||||
|
@JsonProperty("content_type") val content_type: String, // application/vnd.android.package-archive
|
||||||
|
)
|
||||||
|
|
||||||
|
data class GithubRelease(
|
||||||
|
@JsonProperty("tag_name") val tag_name: String, // Version code
|
||||||
|
@JsonProperty("body") val body: String, // Desc
|
||||||
|
@JsonProperty("assets") val assets: List<GithubAsset>,
|
||||||
|
@JsonProperty("target_commitish") val target_commitish: String, // branch
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Update(
|
||||||
|
@JsonProperty("shouldUpdate") val shouldUpdate: Boolean,
|
||||||
|
@JsonProperty("updateURL") val updateURL: String?,
|
||||||
|
@JsonProperty("updateVersion") val updateVersion: String?,
|
||||||
|
@JsonProperty("changelog") val changelog: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val mapper = JsonMapper.builder().addModule(KotlinModule())
|
||||||
|
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
|
||||||
|
|
||||||
|
private fun Activity.getAppUpdate(): Update {
|
||||||
|
try {
|
||||||
|
val url = "https://api.github.com/repos/LagradOst/CloudStream-3/releases"
|
||||||
|
val headers = mapOf("Accept" to "application/vnd.github.v3+json")
|
||||||
|
val response =
|
||||||
|
mapper.readValue<List<GithubRelease>>(khttp.get(url, headers = headers).text)
|
||||||
|
|
||||||
|
val versionRegex = Regex("""(.*?((\d)\.(\d)\.(\d)).*\.apk)""")
|
||||||
|
|
||||||
|
/*
|
||||||
|
val releases = response.map { it.assets }.flatten()
|
||||||
|
.filter { it.content_type == "application/vnd.android.package-archive" }
|
||||||
|
val found =
|
||||||
|
releases.sortedWith(compareBy {
|
||||||
|
versionRegex.find(it.name)?.groupValues?.get(2)
|
||||||
|
}).toList().lastOrNull()*/
|
||||||
|
val found =
|
||||||
|
response.sortedWith(compareBy { release ->
|
||||||
|
release.assets.filter { it.content_type == "application/vnd.android.package-archive" }
|
||||||
|
.getOrNull(0)?.name?.let { it1 ->
|
||||||
|
versionRegex.find(
|
||||||
|
it1
|
||||||
|
)?.groupValues?.get(2)
|
||||||
|
}
|
||||||
|
}).toList().lastOrNull()
|
||||||
|
val foundAsset = found?.assets?.getOrNull(0)
|
||||||
|
val currentVersion = packageName?.let {
|
||||||
|
packageManager.getPackageInfo(it,
|
||||||
|
0)
|
||||||
|
}
|
||||||
|
|
||||||
|
val foundVersion = foundAsset?.name?.let { versionRegex.find(it) }
|
||||||
|
val shouldUpdate = if (found != null && foundAsset?.browser_download_url != "" && foundVersion != null) currentVersion?.versionName?.compareTo(
|
||||||
|
foundVersion.groupValues[2]
|
||||||
|
)!! < 0 else false
|
||||||
|
return if (foundVersion != null) {
|
||||||
|
Update(shouldUpdate, foundAsset.browser_download_url, foundVersion.groupValues[2], found.body)
|
||||||
|
} else {
|
||||||
|
Update(false, null, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println(e)
|
||||||
|
return Update(false, null, null, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Activity.downloadUpdate(url: String): Boolean {
|
||||||
|
println("DOWNLOAD UPDATE $url")
|
||||||
|
var fullResume = false // IF FULL RESUME
|
||||||
|
try {
|
||||||
|
// =================== DOWNLOAD POSTERS AND SETUP PATH ===================
|
||||||
|
val path = filesDir.toString() +
|
||||||
|
"/Download/apk/update.apk"
|
||||||
|
|
||||||
|
// =================== MAKE DIRS ===================
|
||||||
|
val rFile = File(path)
|
||||||
|
try {
|
||||||
|
rFile.parentFile?.mkdirs()
|
||||||
|
} catch (_ex: Exception) {
|
||||||
|
println("FAILED:::$_ex")
|
||||||
|
}
|
||||||
|
val url = url.replace(" ", "%20")
|
||||||
|
|
||||||
|
val _url = URL(url)
|
||||||
|
|
||||||
|
val connection: URLConnection = _url.openConnection()
|
||||||
|
|
||||||
|
var bytesRead = 0L
|
||||||
|
|
||||||
|
// =================== STORAGE ===================
|
||||||
|
try {
|
||||||
|
if (!rFile.exists()) {
|
||||||
|
rFile.createNewFile()
|
||||||
|
} else {
|
||||||
|
rFile.delete()
|
||||||
|
rFile.createNewFile()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println(e)
|
||||||
|
runOnUiThread {
|
||||||
|
Toast.makeText(this, "Permission error", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================== CONNECTION ===================
|
||||||
|
connection.setRequestProperty("Accept-Encoding", "identity")
|
||||||
|
connection.connectTimeout = 10000
|
||||||
|
var clen = 0
|
||||||
|
try {
|
||||||
|
connection.connect()
|
||||||
|
clen = connection.contentLength
|
||||||
|
println("CONTENTN LENGTH: $clen")
|
||||||
|
} catch (_ex: Exception) {
|
||||||
|
println("CONNECT:::$_ex")
|
||||||
|
_ex.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================== VALIDATE ===================
|
||||||
|
if (clen < 5000000) { // min of 5 MB
|
||||||
|
clen = 0
|
||||||
|
}
|
||||||
|
if (clen <= 0) { // TO SMALL OR INVALID
|
||||||
|
//showNot(0, 0, 0, DownloadType.IsFailed, info)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================== SETUP VARIABLES ===================
|
||||||
|
//val bytesTotal: Long = (clen + bytesRead.toInt()).toLong()
|
||||||
|
val input: InputStream = BufferedInputStream(connection.inputStream)
|
||||||
|
val output: OutputStream = FileOutputStream(rFile, false)
|
||||||
|
var bytesPerSec = 0L
|
||||||
|
val buffer = ByteArray(1024)
|
||||||
|
var count: Int
|
||||||
|
//var lastUpdate = System.currentTimeMillis()
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
count = input.read(buffer)
|
||||||
|
if (count < 0) break
|
||||||
|
|
||||||
|
bytesRead += count
|
||||||
|
bytesPerSec += count
|
||||||
|
output.write(buffer, 0, count)
|
||||||
|
} catch (_ex: Exception) {
|
||||||
|
println("CONNECT TRUE:::$_ex")
|
||||||
|
_ex.printStackTrace()
|
||||||
|
fullResume = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fullResume) { // IF FULL RESUME DELETE CURRENT AND DONT SHOW DONE
|
||||||
|
with(NotificationManagerCompat.from(this)) {
|
||||||
|
cancel(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output.flush()
|
||||||
|
output.close()
|
||||||
|
input.close()
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
val contentUri = FileProvider.getUriForFile(
|
||||||
|
this,
|
||||||
|
BuildConfig.APPLICATION_ID + ".provider",
|
||||||
|
rFile
|
||||||
|
)
|
||||||
|
val install = Intent(Intent.ACTION_VIEW)
|
||||||
|
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
install.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
|
install.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
|
||||||
|
install.data = contentUri
|
||||||
|
startActivity(install)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
val apkUri = Uri.fromFile(rFile)
|
||||||
|
val install = Intent(Intent.ACTION_VIEW)
|
||||||
|
install.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||||
|
install.setDataAndType(
|
||||||
|
apkUri,
|
||||||
|
"application/vnd.android.package-archive"
|
||||||
|
)
|
||||||
|
startActivity(install)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (_ex: Exception) {
|
||||||
|
println("FATAL EX DOWNLOADING:::$_ex")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Activity.runAutoUpdate(checkAutoUpdate: Boolean = true): Boolean {
|
||||||
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
|
if (!checkAutoUpdate || settingsManager.getBoolean(getString(R.string.auto_update_key), true)
|
||||||
|
) {
|
||||||
|
val update = getAppUpdate()
|
||||||
|
if (update.shouldUpdate && update.updateURL != null) {
|
||||||
|
runOnUiThread {
|
||||||
|
val currentVersion = packageName?.let {
|
||||||
|
packageManager.getPackageInfo(it,
|
||||||
|
0)
|
||||||
|
}
|
||||||
|
|
||||||
|
val builder: AlertDialog.Builder = AlertDialog.Builder(this)
|
||||||
|
builder.setTitle("New update found!\n${currentVersion?.versionName} -> ${update.updateVersion}")
|
||||||
|
builder.setMessage("${update.changelog}")
|
||||||
|
|
||||||
|
val context = this
|
||||||
|
builder.apply {
|
||||||
|
setPositiveButton("Update") { _, _ ->
|
||||||
|
Toast.makeText(context, "Download started", Toast.LENGTH_LONG).show()
|
||||||
|
thread {
|
||||||
|
val downloadStatus = context.downloadUpdate(update.updateURL)
|
||||||
|
if (!downloadStatus) {
|
||||||
|
runOnUiThread {
|
||||||
|
Toast.makeText(context,
|
||||||
|
"Download Failed",
|
||||||
|
Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
} /*else {
|
||||||
|
activity.runOnUiThread {
|
||||||
|
Toast.makeText(localContext,
|
||||||
|
"Downloaded APK",
|
||||||
|
Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setNegativeButton("Cancel") { _, _ -> }
|
||||||
|
|
||||||
|
if(checkAutoUpdate) {
|
||||||
|
setNeutralButton("Don't show again") { _, _ ->
|
||||||
|
settingsManager.edit().putBoolean("auto_update", false).apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.show()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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="M7.58,4.08L6.15,2.65C3.75,4.48 2.17,7.3 2.03,10.5h2c0.15,-2.65 1.51,-4.97 3.55,-6.42zM19.97,10.5h2c-0.15,-3.2 -1.73,-6.02 -4.12,-7.85l-1.42,1.43c2.02,1.45 3.39,3.77 3.54,6.42zM18,11c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2v-5zM12,22c0.14,0 0.27,-0.01 0.4,-0.04 0.65,-0.14 1.18,-0.58 1.44,-1.18 0.1,-0.24 0.15,-0.5 0.15,-0.78h-4c0.01,1.1 0.9,2 2.01,2z"/>
|
||||||
|
</vector>
|
|
@ -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="M17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19L7,19L7,5h10v14zM16,13h-3L13,8h-2v5L8,13l4,4 4,-4z"/>
|
||||||
|
</vector>
|
|
@ -87,6 +87,7 @@
|
||||||
android:id="@+id/home_main_poster"
|
android:id="@+id/home_main_poster"
|
||||||
tools:src="@drawable/example_poster"
|
tools:src="@drawable/example_poster"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
android:layout_width="150dp"
|
android:layout_width="150dp"
|
||||||
android:layout_height="212dp"
|
android:layout_height="212dp"
|
||||||
android:contentDescription="@string/home_main_poster">
|
android:contentDescription="@string/home_main_poster">
|
||||||
|
|
|
@ -83,4 +83,6 @@
|
||||||
<string name="subs_font">Font</string>
|
<string name="subs_font">Font</string>
|
||||||
<string name="search_provider_text_providers">Search using providers</string>
|
<string name="search_provider_text_providers">Search using providers</string>
|
||||||
<string name="search_provider_text_types">Search using types</string>
|
<string name="search_provider_text_types">Search using types</string>
|
||||||
|
<string name="auto_update_key">auto_update</string>
|
||||||
|
<string name="manual_check_update_key">manual_check_update</string>
|
||||||
</resources>
|
</resources>
|
|
@ -84,6 +84,18 @@
|
||||||
android:summaryOff="Only sends data on crashes"
|
android:summaryOff="Only sends data on crashes"
|
||||||
android:summaryOn="Sends no data"
|
android:summaryOn="Sends no data"
|
||||||
android:defaultValue="false"/>
|
android:defaultValue="false"/>
|
||||||
|
<SwitchPreference
|
||||||
|
app:key="@string/auto_update_key"
|
||||||
|
android:title="Show app updates"
|
||||||
|
android:summary="Automatically search for new updates on start"
|
||||||
|
app:defaultValue="true"
|
||||||
|
android:icon="@drawable/ic_baseline_notifications_active_24"
|
||||||
|
/>
|
||||||
|
<Preference
|
||||||
|
android:title="Check for Update"
|
||||||
|
app:key="@string/manual_check_update_key"
|
||||||
|
app:icon="@drawable/ic_baseline_system_update_24"
|
||||||
|
/>
|
||||||
<Preference
|
<Preference
|
||||||
android:title="Github"
|
android:title="Github"
|
||||||
android:icon="@drawable/ic_github_logo"
|
android:icon="@drawable/ic_github_logo"
|
||||||
|
|
Loading…
Reference in a new issue