Compare commits
8 Commits
1.0.0-beta
...
1.0.0
Author | SHA1 | Date | |
---|---|---|---|
d33de371d1 | |||
1ecd25bb06
|
|||
fa28eb35ab
|
|||
d3fe81224b
|
|||
34c7f9d081
|
|||
19552d3950 | |||
49e0b1ec29 | |||
af66d968cc |
@ -12,8 +12,8 @@ android {
|
|||||||
applicationId "org.mosad.teapod"
|
applicationId "org.mosad.teapod"
|
||||||
minSdkVersion 23
|
minSdkVersion 23
|
||||||
targetSdkVersion 32
|
targetSdkVersion 32
|
||||||
versionCode 9020 //00.09.020
|
versionCode 100000 //01.00.000
|
||||||
versionName "1.0.0-beta3"
|
versionName "1.0.0"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
resValue "string", "build_time", buildTime()
|
resValue "string", "build_time", buildTime()
|
||||||
|
@ -39,13 +39,13 @@ import com.facebook.shimmer.ShimmerFrameLayout
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.mosad.teapod.R
|
import org.mosad.teapod.R
|
||||||
import org.mosad.teapod.databinding.FragmentHomeBinding
|
import org.mosad.teapod.databinding.FragmentHomeBinding
|
||||||
import org.mosad.teapod.ui.activity.main.MainActivity
|
|
||||||
import org.mosad.teapod.ui.activity.main.viewmodel.HomeViewModel
|
import org.mosad.teapod.ui.activity.main.viewmodel.HomeViewModel
|
||||||
import org.mosad.teapod.util.adapter.MediaEpisodeListAdapter
|
import org.mosad.teapod.util.adapter.MediaEpisodeListAdapter
|
||||||
import org.mosad.teapod.util.adapter.MediaItemListAdapter
|
import org.mosad.teapod.util.adapter.MediaItemListAdapter
|
||||||
import org.mosad.teapod.util.decoration.MediaItemDecoration
|
import org.mosad.teapod.util.decoration.MediaItemDecoration
|
||||||
import org.mosad.teapod.util.setDrawableTop
|
import org.mosad.teapod.util.setDrawableTop
|
||||||
import org.mosad.teapod.util.showFragment
|
import org.mosad.teapod.util.showFragment
|
||||||
|
import org.mosad.teapod.util.startPlayer
|
||||||
import org.mosad.teapod.util.toItemMediaList
|
import org.mosad.teapod.util.toItemMediaList
|
||||||
|
|
||||||
class HomeFragment : Fragment() {
|
class HomeFragment : Fragment() {
|
||||||
@ -70,10 +70,7 @@ class HomeFragment : Fragment() {
|
|||||||
|
|
||||||
binding.recyclerUpNext.adapter = MediaEpisodeListAdapter(
|
binding.recyclerUpNext.adapter = MediaEpisodeListAdapter(
|
||||||
MediaEpisodeListAdapter.OnClickListener {
|
MediaEpisodeListAdapter.OnClickListener {
|
||||||
val activity = activity
|
activity?.startPlayer(it.panel.episodeMetadata.seasonId, it.panel.id)
|
||||||
if (activity is MainActivity) {
|
|
||||||
activity.startPlayer(it.panel.episodeMetadata.seasonId, it.panel.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -109,16 +106,6 @@ class HomeFragment : Fragment() {
|
|||||||
// TODO since this might take a few seconds show a loading animation for the watchlist button
|
// TODO since this might take a few seconds show a loading animation for the watchlist button
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.buttonPlayHighlight.setOnClickListener {
|
|
||||||
// TODO implement
|
|
||||||
lifecycleScope.launch {
|
|
||||||
//val media = AoDParser.getMediaById(0)
|
|
||||||
|
|
||||||
// Log.d(javaClass.name, "Starting Player with mediaId: ${media.aodId}")
|
|
||||||
//(activity as MainActivity).startPlayer(media.aodId, media.playlist.first().mediaId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
model.onUiState(viewLifecycleOwner.lifecycleScope) { uiState ->
|
model.onUiState(viewLifecycleOwner.lifecycleScope) { uiState ->
|
||||||
@ -165,7 +152,37 @@ class HomeFragment : Fragment() {
|
|||||||
activity?.showFragment(MediaFragment(uiState.highlightItem.id))
|
activity?.showFragment(MediaFragment(uiState.highlightItem.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
// disable the shimmer effect and hide the shimmer layouts
|
binding.buttonPlayHighlight.setOnClickListener {
|
||||||
|
val panel = uiState.highlightItemUpNext.panel
|
||||||
|
activity?.startPlayer(panel.episodeMetadata.seasonId, panel.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// disable the shimmer effect
|
||||||
|
disableShimmer()
|
||||||
|
|
||||||
|
// make highlights layout visible again
|
||||||
|
binding.linearHighlight.isVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindUiStateLoading() {
|
||||||
|
// hide highlights layout
|
||||||
|
binding.linearHighlight.isVisible = false
|
||||||
|
println(binding.root.childCount)
|
||||||
|
binding.root.children.filter { it is ShimmerFrameLayout }.forEach {
|
||||||
|
it as ShimmerFrameLayout
|
||||||
|
it.startShimmer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindUiStateError(uiState: HomeViewModel.UiState.Error) {
|
||||||
|
// currently not used
|
||||||
|
Log.e(classTag, "A error occurred while loading a UiState: ${uiState.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable the shimmer effect for all shimmer layouts and hide them.
|
||||||
|
*/
|
||||||
|
private fun disableShimmer() {
|
||||||
binding.shimmerLayoutHighlight.apply {
|
binding.shimmerLayoutHighlight.apply {
|
||||||
stopShimmer()
|
stopShimmer()
|
||||||
isVisible = false
|
isVisible = false
|
||||||
@ -190,23 +207,6 @@ class HomeFragment : Fragment() {
|
|||||||
stopShimmer()
|
stopShimmer()
|
||||||
isVisible = false
|
isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// make highlights layout visible again
|
|
||||||
binding.linearHighlight.isVisible = true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun bindUiStateLoading() {
|
|
||||||
// hide highlights layout
|
|
||||||
binding.linearHighlight.isVisible = false
|
|
||||||
binding.root.children.filter { it is ShimmerFrameLayout }.forEach {
|
|
||||||
it as ShimmerFrameLayout
|
|
||||||
it.startShimmer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun bindUiStateError(uiState: HomeViewModel.UiState.Error) {
|
|
||||||
// currently not used
|
|
||||||
Log.e(classTag, "A error occurred while loading a UiState: ${uiState.message}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ class HomeViewModel : ViewModel() {
|
|||||||
val recentlyAddedItems: List<Item>,
|
val recentlyAddedItems: List<Item>,
|
||||||
val topTenItems: List<Item>,
|
val topTenItems: List<Item>,
|
||||||
val highlightItem: Item,
|
val highlightItem: Item,
|
||||||
|
val highlightItemUpNext: UpNextSeriesItem,
|
||||||
val highlightIsWatchlist:Boolean
|
val highlightIsWatchlist:Boolean
|
||||||
) : UiState()
|
) : UiState()
|
||||||
data class Error(val message: String?) : UiState()
|
data class Error(val message: String?) : UiState()
|
||||||
@ -77,12 +78,17 @@ class HomeViewModel : ViewModel() {
|
|||||||
val recentlyAddedItems = recentlyAddedJob.await()
|
val recentlyAddedItems = recentlyAddedJob.await()
|
||||||
// FIXME crashes on newTitles.items.size == 0
|
// FIXME crashes on newTitles.items.size == 0
|
||||||
val highlightItem = recentlyAddedItems[Random.nextInt(recentlyAddedItems.size)]
|
val highlightItem = recentlyAddedItems[Random.nextInt(recentlyAddedItems.size)]
|
||||||
val highlightItemIsWatchlist = Crunchyroll.isWatchlist(highlightItem.id)
|
val highlightItemUpNextJob = viewModelScope.async {
|
||||||
|
Crunchyroll.upNextSeries(highlightItem.id)
|
||||||
|
}
|
||||||
|
val highlightItemIsWatchlistJob = viewModelScope.async {
|
||||||
|
Crunchyroll.isWatchlist(highlightItem.id)
|
||||||
|
}
|
||||||
|
|
||||||
uiState.emit(UiState.Normal(
|
uiState.emit(UiState.Normal(
|
||||||
upNextJob.await(), watchlistJob.await(), recommendationsJob.await(),
|
upNextJob.await(), watchlistJob.await(), recommendationsJob.await(),
|
||||||
recentlyAddedJob.await(), topTenJob.await(), highlightItem,
|
recentlyAddedJob.await(), topTenJob.await(), highlightItem,
|
||||||
highlightItemIsWatchlist
|
highlightItemUpNextJob.await(), highlightItemIsWatchlistJob.await()
|
||||||
))
|
))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
uiState.emit(UiState.Error(e.message))
|
uiState.emit(UiState.Error(e.message))
|
||||||
@ -115,9 +121,6 @@ class HomeViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -9,6 +9,7 @@ import androidx.fragment.app.Fragment
|
|||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.fragment.app.commit
|
import androidx.fragment.app.commit
|
||||||
import org.mosad.teapod.R
|
import org.mosad.teapod.R
|
||||||
|
import org.mosad.teapod.ui.activity.player.PlayerActivity
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,6 +25,20 @@ fun FragmentActivity.showFragment(fragment: Fragment) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the player as new activity.
|
||||||
|
*
|
||||||
|
* @param seasonId The ID of the season the episode to be played is in
|
||||||
|
* @param episodeId The ID of the episode to play
|
||||||
|
*/
|
||||||
|
fun Activity.startPlayer(seasonId: String, episodeId: String) {
|
||||||
|
val intent = Intent(this, PlayerActivity::class.java).apply {
|
||||||
|
putExtra(getString(R.string.intent_season_id), seasonId)
|
||||||
|
putExtra(getString(R.string.intent_episode_id), episodeId)
|
||||||
|
}
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* hide the status and navigation bar
|
* hide the status and navigation bar
|
||||||
*/
|
*/
|
||||||
|
@ -90,7 +90,7 @@ class TMDBApiController {
|
|||||||
* NoneTMDBSearchMovie if nothing was found
|
* NoneTMDBSearchMovie if nothing was found
|
||||||
*/
|
*/
|
||||||
suspend fun searchMovie(query: String): TMDBSearch<TMDBSearchResultMovie> {
|
suspend fun searchMovie(query: String): TMDBSearch<TMDBSearchResultMovie> {
|
||||||
val searchEndpoint = "/search/multi"
|
val searchEndpoint = "/search/movie"
|
||||||
val parameters = listOf("query" to query, "include_adult" to false)
|
val parameters = listOf("query" to query, "include_adult" to false)
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
|
@ -32,7 +32,7 @@ import kotlinx.serialization.Serializable
|
|||||||
|
|
||||||
interface TMDBResult {
|
interface TMDBResult {
|
||||||
val id: Int
|
val id: Int
|
||||||
val name: String
|
val name: String? // for movies tmdb return string or null
|
||||||
val overview: String? // for movies tmdb return string or null
|
val overview: String? // for movies tmdb return string or null
|
||||||
val posterPath: String?
|
val posterPath: String?
|
||||||
val backdropPath: String?
|
val backdropPath: String?
|
||||||
@ -40,7 +40,7 @@ interface TMDBResult {
|
|||||||
|
|
||||||
data class TMDBBase(
|
data class TMDBBase(
|
||||||
override val id: Int,
|
override val id: Int,
|
||||||
override val name: String,
|
override val name: String?,
|
||||||
override val overview: String?,
|
override val overview: String?,
|
||||||
override val posterPath: String?,
|
override val posterPath: String?,
|
||||||
override val backdropPath: String?
|
override val backdropPath: String?
|
||||||
@ -59,7 +59,7 @@ data class TMDBSearch<T>(
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class TMDBSearchResultMovie(
|
data class TMDBSearchResultMovie(
|
||||||
@SerialName("id") override val id: Int,
|
@SerialName("id") override val id: Int,
|
||||||
@SerialName("title") override val name: String,
|
@SerialName("title") override val name: String?,
|
||||||
@SerialName("overview") override val overview: String?,
|
@SerialName("overview") override val overview: String?,
|
||||||
@SerialName("poster_path") override val posterPath: String?,
|
@SerialName("poster_path") override val posterPath: String?,
|
||||||
@SerialName("backdrop_path") override val backdropPath: String?,
|
@SerialName("backdrop_path") override val backdropPath: String?,
|
||||||
@ -68,7 +68,7 @@ data class TMDBSearchResultMovie(
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class TMDBSearchResultTVShow(
|
data class TMDBSearchResultTVShow(
|
||||||
@SerialName("id") override val id: Int,
|
@SerialName("id") override val id: Int,
|
||||||
@SerialName("name") override val name: String,
|
@SerialName("name") override val name: String?,
|
||||||
@SerialName("overview") override val overview: String?,
|
@SerialName("overview") override val overview: String?,
|
||||||
@SerialName("poster_path") override val posterPath: String?,
|
@SerialName("poster_path") override val posterPath: String?,
|
||||||
@SerialName("backdrop_path") override val backdropPath: String?,
|
@SerialName("backdrop_path") override val backdropPath: String?,
|
||||||
@ -92,7 +92,7 @@ data class TMDBMovie(
|
|||||||
@SerialName("release_date") val releaseDate: String,
|
@SerialName("release_date") val releaseDate: String,
|
||||||
@SerialName("runtime") val runtime: Int?,
|
@SerialName("runtime") val runtime: Int?,
|
||||||
@SerialName("status") val status: String,
|
@SerialName("status") val status: String,
|
||||||
// TODO generes
|
// TODO genres
|
||||||
) : TMDBResult
|
) : TMDBResult
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -105,7 +105,7 @@ data class TMDBTVShow(
|
|||||||
@SerialName("first_air_date") val firstAirDate: String,
|
@SerialName("first_air_date") val firstAirDate: String,
|
||||||
@SerialName("last_air_date") val lastAirDate: String,
|
@SerialName("last_air_date") val lastAirDate: String,
|
||||||
@SerialName("status") val status: String,
|
@SerialName("status") val status: String,
|
||||||
// TODO generes
|
// TODO genres
|
||||||
) : TMDBResult
|
) : TMDBResult
|
||||||
|
|
||||||
// use null for nullable types, the gui needs to handle/implement a fallback for null values
|
// use null for nullable types, the gui needs to handle/implement a fallback for null values
|
||||||
|
@ -29,14 +29,14 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/shimmer_image_highlight">
|
app:layout_constraintTop_toBottomOf="@+id/shimmer_image_highlight">
|
||||||
|
|
||||||
<TextView
|
<ImageView
|
||||||
android:id="@+id/shimmer_text_highlight_title"
|
android:id="@+id/image_dummy_text"
|
||||||
android:layout_width="120dp"
|
android:layout_width="128dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="21dp"
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_marginTop="7dp"
|
android:layout_marginTop="7dp"
|
||||||
android:background="?shapeTextBackground"
|
android:layout_gravity="center"
|
||||||
android:textSize="16sp" />
|
app:srcCompat="@drawable/shape_rounded_corner"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
android:id="@+id/frame_image_progress"
|
android:id="@+id/frame_image_progress"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/text_title"
|
|
||||||
app:layout_constraintDimensionRatio="H,16:9"
|
app:layout_constraintDimensionRatio="H,16:9"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@ -35,17 +34,17 @@
|
|||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<TextView
|
<ImageView
|
||||||
android:id="@+id/text_title"
|
android:id="@+id/image_dummy_text"
|
||||||
android:layout_width="128dp"
|
android:layout_width="128dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="19dp"
|
||||||
android:layout_margin="11dp"
|
android:layout_margin="11dp"
|
||||||
android:background="?shapeTextBackground"
|
|
||||||
android:textSize="15sp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/frame_image_progress" />
|
app:layout_constraintTop_toBottomOf="@+id/frame_image_progress"
|
||||||
|
app:srcCompat="@drawable/shape_rounded_corner"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
6
fastlane/metadata/android/de/changelogs/100000.txt
Normal file
6
fastlane/metadata/android/de/changelogs/100000.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Dies ist der erste stabile Release von Teapod mit Unterstützung für Cunchyroll.
|
||||||
|
|
||||||
|
* Unterstützung für Crunchyroll hinzugefügt (Ein premium Account wird benötigt)
|
||||||
|
* Diverse UI/UX Verbesserungen
|
||||||
|
|
||||||
|
Alle Änderungen: https://git.mosad.xyz/Seil0/teapod/compare/0.4.2...1.0.0
|
6
fastlane/metadata/android/en-US/changelogs/100000.txt
Normal file
6
fastlane/metadata/android/en-US/changelogs/100000.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
This is the first stable release of Teapod with support for crunchyroll.
|
||||||
|
|
||||||
|
* Support for crunchyroll (a premium account is needed)
|
||||||
|
* UI/UX improvements
|
||||||
|
|
||||||
|
Full changelog: https://git.mosad.xyz/Seil0/teapod/compare/0.4.2...1.0.0
|
Reference in New Issue
Block a user