improved MediaFragment UI
* fix searchview not losing focus when media is selected
This commit is contained in:
@ -9,6 +9,8 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.commit
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.mosad.teapod.parser.AoDParser
|
||||
import org.mosad.teapod.preferences.EncryptedPreferences
|
||||
import org.mosad.teapod.ui.MediaFragment
|
||||
@ -84,7 +86,10 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
|
||||
}
|
||||
}
|
||||
|
||||
fun showDetailFragment(media: Media) {
|
||||
/**
|
||||
* TODO show loading fragment
|
||||
*/
|
||||
fun showDetailFragment(media: Media) = GlobalScope.launch {
|
||||
media.episodes = AoDParser().loadStreams(media) // load the streams for the selected media
|
||||
|
||||
val tmdb = TMDBApiController().search(media.title, media.type)
|
||||
|
@ -36,10 +36,10 @@ class AoDParser {
|
||||
.execute()
|
||||
|
||||
val authenticityToken = resAuth.parse().select("meta[name=csrf-token]").attr("content")
|
||||
println("Authenticity token is: $authenticityToken")
|
||||
val authCookies = resAuth.cookies()
|
||||
|
||||
val cookies = resAuth.cookies()
|
||||
println("cookies: $cookies")
|
||||
Log.i(javaClass.name, "Received authenticity token: $authenticityToken")
|
||||
Log.i(javaClass.name, "Received authenticity cookies: $authCookies")
|
||||
|
||||
val data = mapOf(
|
||||
Pair("user[login]", EncryptedPreferences.login),
|
||||
@ -53,7 +53,7 @@ class AoDParser {
|
||||
.method(Connection.Method.POST)
|
||||
.data(data)
|
||||
.postDataCharset("UTF-8")
|
||||
.cookies(cookies)
|
||||
.cookies(authCookies)
|
||||
.execute()
|
||||
|
||||
//println(resLogin.body())
|
||||
@ -122,6 +122,15 @@ class AoDParser {
|
||||
|
||||
//println(res)
|
||||
|
||||
// parse additional info from the media page
|
||||
res.select("table.vertical-table").select("tr").forEach {
|
||||
when (it.select("th").text().toLowerCase(Locale.ROOT)) {
|
||||
"produktionsjahr" -> media.info.year = it.select("td").text().toInt()
|
||||
"fsk" -> media.info.age = it.select("td").text().toInt()
|
||||
"episodenanzahl" -> media.info.episodesCount = it.select("td").text().toInt()
|
||||
}
|
||||
}
|
||||
|
||||
val playlists = res.select("input.streamstarter_html5").eachAttr("data-playlist")
|
||||
val csrfToken = res.select("meta[name=csrf-token]").attr("content")
|
||||
|
||||
@ -176,10 +185,16 @@ class AoDParser {
|
||||
.first().asJsonObject
|
||||
.get("file").asString
|
||||
val episodeTitle = it.asJsonObject.get("title").asString
|
||||
val episodePoster = it.asJsonObject.get("image").asString
|
||||
val episodeDescription = it.asJsonObject.get("description").asString
|
||||
val episodeNumber = episodeTitle.substringAfter(", Ep. ").toInt()
|
||||
|
||||
Episode(
|
||||
episodeTitle,
|
||||
episodeStream
|
||||
episodeStream,
|
||||
episodePoster,
|
||||
episodeDescription,
|
||||
episodeNumber
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -55,20 +55,26 @@ class MediaFragment(private val media: Media, private val tmdb: TMDBResponse) :
|
||||
.into(image_poster)
|
||||
|
||||
text_title.text = media.title
|
||||
// TODO add year, fsk
|
||||
text_overview.text = if (tmdb.overview.isNotEmpty()) tmdb.overview else media.shortDesc
|
||||
text_year.text = media.info.year.toString()
|
||||
text_age.text = media.info.age.toString()
|
||||
text_overview.text = media.shortDesc //if (tmdb.overview.isNotEmpty()) tmdb.overview else media.shortDesc
|
||||
|
||||
// specific gui
|
||||
if (media.type == MediaType.TVSHOW) {
|
||||
val episodeTitles = media.episodes.map { it.title }
|
||||
|
||||
adapterRecEpisodes = EpisodesAdapter(episodeTitles)
|
||||
adapterRecEpisodes = EpisodesAdapter(media.episodes, requireContext())
|
||||
viewManager = LinearLayoutManager(context)
|
||||
recycler_episodes.layoutManager = viewManager
|
||||
recycler_episodes.adapter = adapterRecEpisodes
|
||||
|
||||
text_episodes_or_runtime.text = getString(R.string.text_episodes_count, media.info.episodesCount)
|
||||
} else if (media.type == MediaType.MOVIE) {
|
||||
recycler_episodes.visibility = View.GONE
|
||||
|
||||
if (tmdb.runtime > 0) {
|
||||
text_episodes_or_runtime.text = getString(R.string.text_runtime, tmdb.runtime)
|
||||
} else {
|
||||
text_episodes_or_runtime.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,6 @@ import org.mosad.teapod.util.Media
|
||||
|
||||
class SearchFragment : Fragment() {
|
||||
|
||||
private val instance = this
|
||||
private lateinit var adapter : CustomAdapter
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
@ -46,6 +45,8 @@ class SearchFragment : Fragment() {
|
||||
private fun initActions() {
|
||||
search_text.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
adapter.filter.filter(query)
|
||||
adapter.notifyDataSetChanged()
|
||||
return false
|
||||
}
|
||||
|
||||
@ -57,12 +58,14 @@ class SearchFragment : Fragment() {
|
||||
})
|
||||
|
||||
list_search.setOnItemClickListener { _, _, position, _ ->
|
||||
val media = adapter.getItem(position) as Media
|
||||
search_text.clearFocus() // remove focus from the SearchView
|
||||
|
||||
println("selected item is: ${media.title}")
|
||||
runBlocking {
|
||||
val media = adapter.getItem(position) as Media
|
||||
println("selected item is: ${media.title}")
|
||||
|
||||
val mainActivity = activity as MainActivity
|
||||
mainActivity.showDetailFragment(media)
|
||||
(activity as MainActivity).showDetailFragment(media).join()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -8,12 +8,14 @@ class DataTypes {
|
||||
}
|
||||
}
|
||||
|
||||
data class Media(val title: String, val link: String, val type: DataTypes.MediaType, val posterLink: String, val shortDesc : String, var episodes: List<Episode> = listOf()) {
|
||||
data class Media(val title: String, val link: String, val type: DataTypes.MediaType, val posterLink: String, val shortDesc : String, var episodes: List<Episode> = listOf(), val info : Info = Info()) {
|
||||
override fun toString(): String {
|
||||
return title
|
||||
}
|
||||
}
|
||||
|
||||
data class Episode(val title: String = "", val streamUrl: String = "", val posterLink: String = "", var watched: Boolean = false)
|
||||
data class Info(var description: String = "", var year: Int = 0, var age: Int = 0, var episodesCount: Int = 0)
|
||||
|
||||
data class TMDBResponse(val title: String = "", val overview: String = "", val posterUrl: String = "", val backdropUrl: String = "")
|
||||
data class Episode(val title: String = "", val streamUrl: String = "", val posterLink: String = "", var description: String = "", var number: Int = 0, var watched: Boolean = false)
|
||||
|
||||
data class TMDBResponse(val id: Int = 0, val title: String = "", val overview: String = "", val posterUrl: String = "", val backdropUrl: String = "", var runtime: Int = 0)
|
||||
|
@ -1,13 +1,15 @@
|
||||
package org.mosad.teapod.util
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import kotlinx.android.synthetic.main.component_episode.view.*
|
||||
import org.mosad.teapod.R
|
||||
|
||||
class EpisodesAdapter(private val data: List<String>) : RecyclerView.Adapter<EpisodesAdapter.MyViewHolder>() {
|
||||
class EpisodesAdapter(private val data: List<Episode>, private val context: Context) : RecyclerView.Adapter<EpisodesAdapter.MyViewHolder>() {
|
||||
|
||||
var onItemClick: ((String, Int) -> Unit)? = null
|
||||
|
||||
@ -18,7 +20,11 @@ class EpisodesAdapter(private val data: List<String>) : RecyclerView.Adapter<Epi
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
|
||||
holder.view .text_episode_title.text = data[position]
|
||||
holder.view.text_episode_title.text = "Episode ${data[position].number} ${data[position].description}"
|
||||
|
||||
if (data[position].posterLink.isNotEmpty()) {
|
||||
Glide.with(context).load(data[position].posterLink).into(holder.view.image_episode)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
@ -28,7 +34,7 @@ class EpisodesAdapter(private val data: List<String>) : RecyclerView.Adapter<Epi
|
||||
inner class MyViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||
init {
|
||||
view.setOnClickListener {
|
||||
onItemClick?.invoke(data[adapterPosition], adapterPosition)
|
||||
onItemClick?.invoke(data[adapterPosition].title, adapterPosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ class TMDBApiController {
|
||||
private val apiUrl = "https://api.themoviedb.org/3"
|
||||
private val searchMovieUrl = "$apiUrl/search/movie"
|
||||
private val searchTVUrl = "$apiUrl/search/tv"
|
||||
private val getMovieUrl = "$apiUrl/movie"
|
||||
private val apiKey = "de959cf9c07a08b5ca7cb51cda9a40c2"
|
||||
private val language = "de"
|
||||
private val preparedParameters = "?api_key=$apiKey&language=$language"
|
||||
@ -44,11 +45,12 @@ class TMDBApiController {
|
||||
|
||||
return@async 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")
|
||||
val posterPath = getStringNotNullPrefix(it, "poster_path", imageUrl)
|
||||
val backdropPath = getStringNotNullPrefix(it, "backdrop_path", imageUrl)
|
||||
|
||||
TMDBResponse("", overview, posterPath, backdropPath)
|
||||
TMDBResponse(id, "", overview, posterPath, backdropPath)
|
||||
}
|
||||
} else {
|
||||
TMDBResponse()
|
||||
@ -66,11 +68,13 @@ class TMDBApiController {
|
||||
|
||||
return@async 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")
|
||||
val posterPath = getStringNotNullPrefix(it, "poster_path", imageUrl)
|
||||
val backdropPath = getStringNotNullPrefix(it, "backdrop_path", imageUrl)
|
||||
val runtime = getMovieRuntime(id)
|
||||
|
||||
TMDBResponse("", overview, posterPath, backdropPath)
|
||||
TMDBResponse(id, "", overview, posterPath, backdropPath, runtime)
|
||||
}
|
||||
} else {
|
||||
TMDBResponse()
|
||||
@ -80,6 +84,24 @@ class TMDBApiController {
|
||||
}.await()
|
||||
}
|
||||
|
||||
/**
|
||||
* currently only used for runtime, need a rework
|
||||
*/
|
||||
fun getMovieRuntime(id: Int): Int = runBlocking {
|
||||
val url = URL("$getMovieUrl/$id?api_key=$apiKey&language=$language")
|
||||
|
||||
GlobalScope.async {
|
||||
val response = JsonParser.parseString(url.readText()).asJsonObject
|
||||
//println(response)
|
||||
|
||||
val runtime = getStringNotNull(response,"runtime").toInt()
|
||||
println(runtime)
|
||||
|
||||
|
||||
return@async runtime
|
||||
}.await()
|
||||
}
|
||||
|
||||
/**
|
||||
* return memberName as string if it's not JsonNull,
|
||||
* else return an empty string
|
||||
|
Reference in New Issue
Block a user