update some libraries & coroutines 1.5.0

* androidx.core 1.3.2 -> 1.5.0
* androidx.appcompat 1.2.0 -> 1.3.0
* gson 2.8.6 -> 2.8.7
* coroutines-android 1.4.3 -> 1.5.0
  * don't use GlobalScope, use lifecycleScope and vieModelScope instead. This fixes a few issues when fragments where destroied befor the coroutine finished.
* gradle wrapper 7.0 -> 7.9.2
This commit is contained in:
Jannik 2021-06-06 17:54:19 +02:00
parent 46e3d1f1b6
commit 5e48e724a7
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
15 changed files with 305 additions and 301 deletions

View File

@ -41,18 +41,20 @@ android {
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0'
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.core:core-ktx:1.5.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
implementation 'androidx.security:security-crypto:1.1.0-alpha03'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
implementation 'com.google.android.material:material:1.3.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.google.code.gson:gson:2.8.7'
implementation 'com.google.android.exoplayer:exoplayer-core:2.13.3'
implementation 'com.google.android.exoplayer:exoplayer-hls:2.13.3'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.13.3'

View File

@ -99,10 +99,12 @@ object AoDParser {
/**
* initially load all media and home screen data
*/
fun initialLoading() = listOf(
loadHome(),
listAnimes()
)
suspend fun initialLoading() {
coroutineScope {
launch { loadHome() }
launch { listAnimes() }
}
}
/**
* get a media by it's ID (int)
@ -121,9 +123,9 @@ object AoDParser {
/**
* get subscription info from aod website, remove "Anime-Abo" Prefix and trim
*/
fun getSubscriptionInfoAsync(): Deferred<String> {
return GlobalScope.async(Dispatchers.IO) {
// get the subscription page
suspend fun getSubscriptionInfoAsync(): Deferred<String> {
return coroutineScope {
async(Dispatchers.IO) {
val res = Jsoup.connect(baseUrl + subscriptionPath)
.cookies(sessionCookies)
.get()
@ -132,12 +134,13 @@ object AoDParser {
.removePrefix("Anime-Abo").trim()
}
}
}
fun getSubscriptionUrl(): String {
return baseUrl + subscriptionPath
}
fun markAsWatched(mediaId: Int, episodeId: Int) = GlobalScope.launch {
suspend fun markAsWatched(mediaId: Int, episodeId: Int) {
val episode = getMediaById(mediaId).getEpisodeById(episodeId)
episode.watched = true
sendCallback(episode.watchedCallback)
@ -146,7 +149,8 @@ object AoDParser {
}
// TODO don't use jsoup here
private fun sendCallback(callbackPath: String) = GlobalScope.launch(Dispatchers.IO) {
private suspend fun sendCallback(callbackPath: String) = coroutineScope {
launch(Dispatchers.IO) {
val headers = mutableMapOf(
Pair("Accept", "application/json, text/javascript, */*; q=0.01"),
Pair("Accept-Language", "de,en-US;q=0.7,en;q=0.3"),
@ -164,13 +168,15 @@ object AoDParser {
} catch (ex: IOException) {
Log.e(javaClass.name, "Callback for $callbackPath failed.", ex)
}
}
}
/**
* load all media from aod into itemMediaList and mediaList
* TODO private suspend fun listAnimes() = withContext(Dispatchers.IO) should also work, maybe a bug in android studio?
*/
private fun listAnimes() = GlobalScope.launch(Dispatchers.IO) {
private suspend fun listAnimes() = withContext(Dispatchers.IO) {
launch(Dispatchers.IO) {
val resAnimes = Jsoup.connect(baseUrl + libraryPath).get()
//println(resAnimes)
@ -198,11 +204,13 @@ object AoDParser {
Log.i(javaClass.name, "Total library size is: ${mediaList.size}")
}
}
/**
* load new episodes, titles and highlights
*/
private fun loadHome() = GlobalScope.launch(Dispatchers.IO) {
private suspend fun loadHome() = withContext(Dispatchers.IO) {
launch(Dispatchers.IO) {
val resHome = Jsoup.connect(baseUrl).get()
// get highlights from AoD
@ -278,6 +286,9 @@ object AoDParser {
highlightsList.add(ItemMedia(0,"", ""))
}
}
Log.i(javaClass.name, "loaded home")
}
}
/**
@ -286,7 +297,8 @@ object AoDParser {
* load streams for the media path, movies have one episode
* @param media is used as call ba reference
*/
private fun loadStreams(media: Media) = GlobalScope.launch(Dispatchers.IO) {
private suspend fun loadStreams(media: Media) = coroutineScope {
launch(Dispatchers.IO) {
if (sessionCookies.isEmpty()) login()
if (!loginSuccess) {
@ -393,6 +405,7 @@ object AoDParser {
}
Log.i(javaClass.name, "media loaded successfully")
}
}
/**
* don't use Gson().fromJson() as we don't have any control over the api and it may change
@ -402,7 +415,7 @@ object AoDParser {
return CompletableDeferred(AoDObject(listOf(), language))
}
return GlobalScope.async(Dispatchers.IO) {
return CoroutineScope(Dispatchers.IO).async(Dispatchers.IO) {
val headers = mutableMapOf(
Pair("Accept", "application/json, text/javascript, */*; q=0.01"),
Pair("Accept-Language", "de,en-US;q=0.7,en;q=0.3"),

View File

@ -32,20 +32,19 @@ import androidx.fragment.app.commit
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.callbacks.onDismiss
import com.google.android.material.bottomnavigation.BottomNavigationView
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.*
import org.mosad.teapod.R
import org.mosad.teapod.databinding.ActivityMainBinding
import org.mosad.teapod.parser.AoDParser
import org.mosad.teapod.ui.activity.player.PlayerActivity
import org.mosad.teapod.preferences.EncryptedPreferences
import org.mosad.teapod.preferences.Preferences
import org.mosad.teapod.ui.components.LoginDialog
import org.mosad.teapod.ui.activity.main.fragments.AccountFragment
import org.mosad.teapod.ui.activity.main.fragments.HomeFragment
import org.mosad.teapod.ui.activity.main.fragments.LibraryFragment
import org.mosad.teapod.ui.activity.main.fragments.SearchFragment
import org.mosad.teapod.ui.activity.onboarding.OnboardingActivity
import org.mosad.teapod.ui.activity.player.PlayerActivity
import org.mosad.teapod.ui.components.LoginDialog
import org.mosad.teapod.util.DataTypes
import org.mosad.teapod.util.StorageController
import org.mosad.teapod.util.exitAndRemoveTask
@ -138,7 +137,8 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
*/
private fun load() {
val time = measureTimeMillis {
val loadingJob = AoDParser.initialLoading() // start the initial loading
val loadingJob = CoroutineScope(Dispatchers.IO + CoroutineName("InitialLoadingScope"))
.async { AoDParser.initialLoading() } // start the initial loading
// load all saved stuff here
Preferences.load(this)
@ -165,7 +165,7 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
}
}
runBlocking { loadingJob.joinAll() } // wait for initial loading to finish
runBlocking { loadingJob.await() } // wait for initial loading to finish
}
Log.i(javaClass.name, "loading and login in $time ms")

View File

@ -12,9 +12,9 @@ import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsSingleChoice
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.mosad.teapod.BuildConfig
import org.mosad.teapod.R
@ -64,7 +64,7 @@ class AccountFragment : Fragment() {
// load subscription (async) info before anything else
binding.textAccountSubscription.text = getString(R.string.account_subscription, getString(R.string.loading))
GlobalScope.launch {
lifecycleScope.launch {
binding.textAccountSubscription.text = getString(
R.string.account_subscription,
AoDParser.getSubscriptionInfoAsync().await()

View File

@ -6,14 +6,13 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.mosad.teapod.R
import org.mosad.teapod.ui.activity.main.MainActivity
import org.mosad.teapod.databinding.FragmentHomeBinding
import org.mosad.teapod.parser.AoDParser
import org.mosad.teapod.ui.activity.main.MainActivity
import org.mosad.teapod.util.ItemMedia
import org.mosad.teapod.util.StorageController
import org.mosad.teapod.util.adapter.MediaItemAdapter
@ -40,7 +39,7 @@ class HomeFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
GlobalScope.launch(Dispatchers.Main) {
lifecycleScope.launch {
context?.let {
initHighlight()
initRecyclerViews()
@ -101,7 +100,7 @@ class HomeFragment : Fragment() {
private fun initActions() {
binding.buttonPlayHighlight.setOnClickListener {
// TODO get next episode
GlobalScope.launch {
lifecycleScope.launch {
val media = AoDParser.getMediaById(highlightMedia.id)
Log.d(javaClass.name, "Starting Player with mediaId: ${media.id}")

View File

@ -5,10 +5,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.mosad.teapod.databinding.FragmentLibraryBinding
import org.mosad.teapod.parser.AoDParser
import org.mosad.teapod.util.adapter.MediaItemAdapter
@ -29,9 +27,8 @@ class LibraryFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)
// init async
GlobalScope.launch {
lifecycleScope.launch {
// create and set the adapter, needs context
withContext(Dispatchers.Main) {
context?.let {
adapter = MediaItemAdapter(AoDParser.itemMediaList)
adapter.onItemClick = { mediaId, _ ->
@ -41,7 +38,6 @@ class LibraryFragment : Fragment() {
binding.recyclerMediaLibrary.adapter = adapter
binding.recyclerMediaLibrary.addItemDecoration(MediaItemDecoration(9))
}
}
}
}

View File

@ -10,20 +10,21 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.tabs.TabLayoutMediator
import jp.wasabeef.glide.transformations.BlurTransformation
import kotlinx.coroutines.*
import kotlinx.coroutines.launch
import org.mosad.teapod.R
import org.mosad.teapod.databinding.FragmentMediaBinding
import org.mosad.teapod.ui.activity.main.MainActivity
import org.mosad.teapod.ui.activity.main.viewmodel.MediaFragmentViewModel
import org.mosad.teapod.util.*
import org.mosad.teapod.util.DataTypes.MediaType
import org.mosad.teapod.util.Episode
import org.mosad.teapod.util.StorageController
/**
* The media detail fragment.
@ -61,15 +62,14 @@ class MediaFragment(private val mediaId: Int) : Fragment() {
}
}.attach()
GlobalScope.launch(Dispatchers.Main) {
lifecycleScope.launch {
model.load(mediaId) // load the streams and tmdb for the selected media
if (this@MediaFragment.isAdded) {
updateGUI()
initActions()
}
}
}
override fun onResume() {
super.onResume()

View File

@ -6,7 +6,8 @@ import android.view.View
import android.view.ViewGroup
import android.widget.SearchView
import androidx.fragment.app.Fragment
import kotlinx.coroutines.*
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import org.mosad.teapod.databinding.FragmentSearchBinding
import org.mosad.teapod.parser.AoDParser
import org.mosad.teapod.util.decoration.MediaItemDecoration
@ -26,9 +27,8 @@ class SearchFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
GlobalScope.launch {
lifecycleScope.launch {
// create and set the adapter, needs context
withContext(Dispatchers.Main) {
context?.let {
adapter = MediaItemAdapter(AoDParser.itemMediaList)
adapter!!.onItemClick = { mediaId, _ ->
@ -40,7 +40,6 @@ class SearchFragment : Fragment() {
binding.recyclerMediaSearch.addItemDecoration(MediaItemDecoration(9))
}
}
}
initActions()
}

View File

@ -5,6 +5,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.*
import org.mosad.teapod.R
import org.mosad.teapod.databinding.FragmentOnLoginBinding
@ -35,7 +36,7 @@ class OnLoginFragment: Fragment() {
EncryptedPreferences.saveCredentials(email, password, requireContext()) // save the credentials
binding.buttonLogin.isClickable = false
loginJob = GlobalScope.launch {
loginJob = lifecycleScope.launch {
if (AoDParser.login()) {
// if login was successful, switch to main
if (activity is OnboardingActivity) {

View File

@ -19,6 +19,7 @@ import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.GestureDetectorCompat
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.ui.StyledPlayerControlView
@ -26,7 +27,6 @@ import com.google.android.exoplayer2.util.Util
import kotlinx.android.synthetic.main.activity_player.*
import kotlinx.android.synthetic.main.player_controls.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.mosad.teapod.R
@ -255,7 +255,7 @@ class PlayerActivity : AppCompatActivity() {
}
timerUpdates = Timer().scheduleAtFixedRate(0, 500) {
GlobalScope.launch {
lifecycleScope.launch {
var btnNextEpIsVisible: Boolean
var controlsVisible: Boolean

View File

@ -4,6 +4,7 @@ import android.app.Application
import android.net.Uri
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.SimpleExoPlayer
@ -11,6 +12,7 @@ import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.source.hls.HlsMediaSource
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.mosad.teapod.R
import org.mosad.teapod.parser.AoDParser
@ -107,9 +109,11 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application)
// if episodes has not been watched, mark as watched
if (!episode.watched) {
viewModelScope.launch {
AoDParser.markAsWatched(media.id, episode.id)
}
}
}
fun playMedia(source: MediaSource, replace: Boolean = false, seekPosition: Long = 0) {
if (replace || player.contentDuration == C.TIME_UNSET) {

View File

@ -3,16 +3,12 @@ package org.mosad.teapod.util
import android.content.Context
import android.net.Uri
import android.util.Log
import android.widget.Toast
import com.google.gson.Gson
import com.google.gson.JsonParser
import kotlinx.coroutines.*
import org.mosad.teapod.R
import java.io.File
import java.io.FileReader
import java.io.FileWriter
import java.lang.Exception
import java.net.URI
/**
* This controller contains the logic for permanently saved data.
@ -45,7 +41,7 @@ object StorageController {
fun saveMyList(context: Context): Job {
val file = File(context.filesDir, fileNameMyList)
return GlobalScope.launch(Dispatchers.IO) {
return CoroutineScope(Dispatchers.IO).launch {
file.writeText(Gson().toJson(myList.distinct()))
}
}

View File

@ -24,8 +24,8 @@ class TMDBApiController {
val searchTerm = title.replace("(Sub)", "").trim()
return when (type) {
MediaType.MOVIE -> searchMovie(searchTerm).await()
MediaType.TVSHOW -> searchTVShow(searchTerm).await()
MediaType.MOVIE -> searchMovie(searchTerm)
MediaType.TVSHOW -> searchTVShow(searchTerm)
else -> {
Log.e(javaClass.name, "Wrong Type: $type")
TMDBResponse()
@ -34,14 +34,13 @@ class TMDBApiController {
}
fun searchTVShow(title: String): Deferred<TMDBResponse> {
@Suppress("BlockingMethodInNonBlockingContext")
private suspend fun searchTVShow(title: String): TMDBResponse = withContext(Dispatchers.IO) {
val url = URL("$searchTVUrl$preparedParameters&query=${URLEncoder.encode(title, "UTF-8")}")
return GlobalScope.async {
val response = JsonParser.parseString(url.readText()).asJsonObject
//println(response)
if (response.get("total_results").asInt > 0) {
return@withContext if (response.get("total_results").asInt > 0) {
response.get("results").asJsonArray.first().asJsonObject.let {
val id = getStringNotNull(it, "id").toInt()
val overview = getStringNotNull(it, "overview")
@ -54,16 +53,14 @@ class TMDBApiController {
TMDBResponse()
}
}
}
fun searchMovie(title: String): Deferred<TMDBResponse> {
@Suppress("BlockingMethodInNonBlockingContext")
private suspend fun searchMovie(title: String): TMDBResponse = withContext(Dispatchers.IO) {
val url = URL("$searchMovieUrl$preparedParameters&query=${URLEncoder.encode(title, "UTF-8")}")
return GlobalScope.async {
val response = JsonParser.parseString(url.readText()).asJsonObject
//println(response)
if (response.get("total_results").asInt > 0) {
return@withContext if (response.get("total_results").asInt > 0) {
response.get("results").asJsonArray.first().asJsonObject.let {
val id = getStringNotNull(it,"id").toInt()
val overview = getStringNotNull(it,"overview")
@ -77,19 +74,16 @@ class TMDBApiController {
TMDBResponse()
}
}
}
/**
* currently only used for runtime, need a rework
*/
fun getMovieRuntime(id: Int): Int = runBlocking {
@Suppress("BlockingMethodInNonBlockingContext")
suspend fun getMovieRuntime(id: Int): Int = withContext(Dispatchers.IO) {
val url = URL("$getMovieUrl/$id?api_key=$apiKey&language=$language")
GlobalScope.async {
val response = JsonParser.parseString(url.readText()).asJsonObject
return@async getStringNotNull(response,"runtime").toInt()
}.await()
return@withContext getStringNotNull(response,"runtime").toInt()
}
/**

View File

@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.5.0"
ext.kotlin_version = "1.5.10"
repositories {
google()
mavenCentral()

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists