wait for configuration details and use them for image loading

This commit is contained in:
Owen LeJeune
2023-06-14 16:50:35 -04:00
parent 76cfd18bb5
commit 7941200b13
6 changed files with 80 additions and 78 deletions

View File

@@ -2,9 +2,13 @@ package com.owenlejeune.tvtime
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.owenlejeune.tvtime.extensions.launchActivity
import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.viewmodel.ConfigurationViewModel
import com.owenlejeune.tvtime.utils.TmdbUtils
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.getViewModel
class AppRoutingActivity: AppCompatActivity() {
@@ -13,10 +17,16 @@ class AppRoutingActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (preferences.firstLaunchTesting || preferences.firstLaunch) {
launchActivity(OnboardingActivity::class.java)
} else {
launchActivity(MainActivity::class.java)
lifecycleScope.launchWhenCreated {
val configurationViewModel = getViewModel<ConfigurationViewModel>()
configurationViewModel.getConfigurations()
TmdbUtils.setup(configurationViewModel)
if (preferences.firstLaunchTesting || preferences.firstLaunch) {
launchActivity(OnboardingActivity::class.java)
} else {
launchActivity(MainActivity::class.java)
}
}
}

View File

@@ -36,9 +36,6 @@ class MainActivity : MonetCompatActivity() {
lifecycleScope.launchWhenCreated {
monet.awaitMonetReady()
setContent {
val configurationViewModel = viewModel<ConfigurationViewModel>()
configurationViewModel.getConfigurations()
AppKeyboardFocusManager()
TVTimeTheme(monetCompat = monet) {
val windowSize = rememberWindowSizeClass()

View File

@@ -10,7 +10,6 @@ import androidx.compose.animation.core.tween
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
@@ -19,7 +18,6 @@ import androidx.compose.material.icons.filled.Movie
import androidx.compose.material.icons.filled.Send
import androidx.compose.material.icons.outlined.ExpandMore
import androidx.compose.material3.*
import androidx.compose.material.TabRow
import androidx.compose.material.icons.filled.Bookmark
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.List
@@ -35,7 +33,6 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
@@ -44,14 +41,12 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.fastForEachIndexed
import androidx.navigation.NavController
import coil.compose.AsyncImage
import com.google.accompanist.flowlayout.FlowRow
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.PagerState
import com.google.accompanist.pager.pagerTabIndicatorOffset
import com.google.accompanist.pager.rememberPagerState
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.owenlejeune.tvtime.R
@@ -73,13 +68,10 @@ import com.owenlejeune.tvtime.ui.theme.FavoriteSelected
import com.owenlejeune.tvtime.ui.theme.RatingSelected
import com.owenlejeune.tvtime.ui.theme.WatchlistSelected
import com.owenlejeune.tvtime.ui.theme.actionButtonColor
import com.owenlejeune.tvtime.ui.viewmodel.ConfigurationViewModel
import com.owenlejeune.tvtime.utils.SessionManager
import com.owenlejeune.tvtime.utils.TmdbUtils
import kotlinx.coroutines.*
import okhttp3.internal.notify
import org.json.JSONObject
import org.koin.androidx.compose.koinViewModel
import org.koin.java.KoinJavaComponent.get
import java.text.DecimalFormat
@@ -1425,7 +1417,7 @@ private fun WatchProviderContainer(
}
) {
AsyncImage(
model = TmdbUtils.fullImagePath(item.logoPath),
model = TmdbUtils.fullLogoPath(item.logoPath),
contentDescription = null,
modifier = Modifier
.size(48.dp)

View File

@@ -23,16 +23,25 @@ class ConfigurationViewModel: ViewModel(), KoinComponent {
private const val TAG = "ConfigurationViewModel"
}
private object Backer {
val detailsConfiguration = mutableStateOf(ConfigurationDetails.Empty)
val countriesConfiguration = mutableStateListOf<ConfigurationCountry>()
val jobsConfiguration = mutableStateListOf<ConfigurationJob>()
val languagesConfiguration = mutableStateListOf<ConfigurationLanguage>()
val primaryTranslationsConfiguration = mutableStateListOf<String>()
val timezonesConfiguration = mutableStateListOf<ConfigurationTimezone>()
}
private val service: ConfigurationApi by inject()
val detailsConfiguration = mutableStateOf(ConfigurationDetails.Empty)
val countriesConfiguration = mutableStateListOf<ConfigurationCountry>()
val jobsConfiguration = mutableStateListOf<ConfigurationJob>()
val languagesConfiguration = mutableStateListOf<ConfigurationLanguage>()
val primaryTranslationsConfiguration = mutableStateListOf<String>()
val timezonesConfiguration = mutableStateListOf<ConfigurationTimezone>()
val detailsConfiguration = Backer.detailsConfiguration
val countriesConfiguration = Backer.countriesConfiguration
val jobsConfiguration = Backer.jobsConfiguration
val languagesConfiguration = Backer.languagesConfiguration
val primaryTranslationsConfiguration = Backer.primaryTranslationsConfiguration
val timezonesConfiguration = Backer.timezonesConfiguration
fun getConfigurations() {
suspend fun getConfigurations() {
getDetailsConfiguration()
getCountriesConfiguration()
getJobsConfiguration()
@@ -41,14 +50,14 @@ class ConfigurationViewModel: ViewModel(), KoinComponent {
getTimezonesConfiguration()
}
fun getDetailsConfiguration() {
suspend fun getDetailsConfiguration() {
getConfiguration(
{ service.getDetailsConfiguration() },
{ detailsConfiguration.value = it }
)
}
fun getCountriesConfiguration() {
suspend fun getCountriesConfiguration() {
getConfiguration(
{ service.getCountriesConfiguration() },
{
@@ -58,7 +67,7 @@ class ConfigurationViewModel: ViewModel(), KoinComponent {
)
}
fun getJobsConfiguration() {
suspend fun getJobsConfiguration() {
getConfiguration(
{ service.getJobsConfiguration() },
{
@@ -68,7 +77,7 @@ class ConfigurationViewModel: ViewModel(), KoinComponent {
)
}
fun getLanguagesConfiguration() {
suspend fun getLanguagesConfiguration() {
getConfiguration(
{ service.getLanguagesConfiguration() },
{
@@ -78,7 +87,7 @@ class ConfigurationViewModel: ViewModel(), KoinComponent {
)
}
fun getPrimaryTranslationsConfiguration() {
suspend fun getPrimaryTranslationsConfiguration() {
getConfiguration(
{ service.getPrimaryTranslationsConfiguration() },
{
@@ -88,7 +97,7 @@ class ConfigurationViewModel: ViewModel(), KoinComponent {
)
}
fun getTimezonesConfiguration() {
suspend fun getTimezonesConfiguration() {
getConfiguration(
{ service.getTimezonesConfiguration() },
{
@@ -98,20 +107,18 @@ class ConfigurationViewModel: ViewModel(), KoinComponent {
)
}
private fun <T> getConfiguration(
private suspend fun <T> getConfiguration(
fetcher: suspend () -> Response<T>,
bodyHandler: (T) -> Unit
) {
CoroutineScope(Dispatchers.IO).launch {
val response = fetcher()
if (response.isSuccessful) {
response.body()?.let {
Log.d(TAG, "Successfully got configuration: $it")
bodyHandler(it)
}
} else {
Log.e(TAG, "Issue getting configuration")
val response = fetcher()
if (response.isSuccessful) {
response.body()?.let {
Log.d(TAG, "Successfully got configuration: $it")
bodyHandler(it)
}
} else {
Log.e(TAG, "Issue getting configuration")
}
}

View File

@@ -7,41 +7,34 @@ import kotlinx.coroutines.flow.asStateFlow
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class SettingsViewModel: ViewModel(), KoinComponent {
class SettingsViewModel: ViewModel() {
private val preferences: AppPreferences by inject()
private companion object Backer: KoinComponent {
private val preferences: AppPreferences by inject()
private val _showSearchBar = MutableStateFlow(preferences.showSearchBar)
private val _useMultiSearch = MutableStateFlow(preferences.multiSearch)
private val _useWallpaperColor = MutableStateFlow(preferences.useWallpaperColors)
private val _darkTheme = MutableStateFlow(preferences.darkTheme)
private val _useSystemColors = MutableStateFlow(preferences.useSystemColors)
private val _chromaMultiplier = MutableStateFlow(preferences.chromaMultiplier)
private val _selectedColor = MutableStateFlow(preferences.selectedColor)
private val _showBottomTabLabels = MutableStateFlow(preferences.showBottomTabLabels)
private val _showPosterTitles = MutableStateFlow(preferences.showPosterTitles)
private val _firstLaunchTesting = MutableStateFlow(preferences.firstLaunchTesting)
private val _showBackdropGallery = MutableStateFlow(preferences.showBackdropGallery)
}
private val _showSearchBar = MutableStateFlow(preferences.showSearchBar)
val showSearchBar = _showSearchBar.asStateFlow()
private val _useMultiSearch = MutableStateFlow(preferences.multiSearch)
val useMultiSearch = _useMultiSearch.asStateFlow()
private val _useWallpaperColor = MutableStateFlow(preferences.useWallpaperColors)
val useWallpaperColors = _useWallpaperColor.asStateFlow()
private val _darkTheme = MutableStateFlow(preferences.darkTheme)
val darkTheme = _darkTheme.asStateFlow()
private val _useSystemColors = MutableStateFlow(preferences.useSystemColors)
val useSystemColors = _useSystemColors.asStateFlow()
private val _chromaMultiplier = MutableStateFlow(preferences.chromaMultiplier)
val chromaMultiplier = _chromaMultiplier.asStateFlow()
private val _selectedColor = MutableStateFlow(preferences.selectedColor)
val selectedColor = _selectedColor.asStateFlow()
private val _showBottomTabLabels = MutableStateFlow(preferences.showBottomTabLabels)
val showBottomTabLabels = _showBottomTabLabels.asStateFlow()
private val _showPosterTitles = MutableStateFlow(preferences.showPosterTitles)
val showPosterTitles = _showPosterTitles.asStateFlow()
private val _firstLaunchTesting = MutableStateFlow(preferences.firstLaunchTesting)
val firstLaunchTesting = _firstLaunchTesting.asStateFlow()
private val _showBackdropGallery = MutableStateFlow(preferences.showBackdropGallery)
val showBackdropGallery = _showBackdropGallery.asStateFlow()
fun toggleShowSearchBar() {

View File

@@ -14,28 +14,31 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Status
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvContentRatings
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Video
import com.owenlejeune.tvtime.ui.viewmodel.ConfigurationViewModel
import java.text.SimpleDateFormat
object TmdbUtils {
private const val POSTER_BASE = "https://image.tmdb.org/t/p/original"
// private const val BACKDROP_BASE = "https://www.themoviedb.org/t/p/original"
// private const val PERSON_BASE = "https://www.themoviedb.org/t/p/w600_and_h900_bestv2"
private const val GRAVATAR_BASE = "https://www.gravatar.com/avatar/"
// private const val AVATAR_BASE = "https://www.themoviedb.org/t/p/w150_and_h150_face"
// private const val STILL_BASE = "https://www.themoviedb.org/t/p/w454_and_h254_bestv2/"
private const val BACKDROP_BASE = POSTER_BASE
private const val PERSON_BASE = POSTER_BASE
private const val AVATAR_BASE = POSTER_BASE
private const val STILL_BASE = POSTER_BASE
private const val DEF_REGION = "US"
fun fullImagePath(sourcePath: String) = POSTER_BASE.plus(sourcePath)
private var IMAGE_BASE = ""
private var BACKDROP_SIZE = "original"
private var LOGO_SIZE = "original"
private var POSTER_SIZE = "original"
private var PROFILE_SIZE = "original"
private var STILL_SIZE = "original"
fun setup(configurationViewModel: ConfigurationViewModel) {
IMAGE_BASE = configurationViewModel.detailsConfiguration.value.images.secureBaseUrl
}
fun fullLogoPath(sourcePath: String) = IMAGE_BASE.plus(LOGO_SIZE).plus(sourcePath)
fun getFullPosterPath(posterPath: String?): String? {
return posterPath?.let {
if (posterPath.isEmpty()) null else "${POSTER_BASE}${posterPath}"
if (posterPath.isEmpty()) null else IMAGE_BASE.plus(POSTER_SIZE).plus(posterPath)
}
}
@@ -49,7 +52,7 @@ object TmdbUtils {
fun getFullBackdropPath(backdropPath: String?): String? {
return backdropPath?.let {
if (backdropPath.isEmpty()) null else "${BACKDROP_BASE}${backdropPath}"
if (backdropPath.isEmpty()) null else IMAGE_BASE.plus(BACKDROP_SIZE).plus(backdropPath)
}
}
@@ -62,7 +65,7 @@ object TmdbUtils {
}
fun getFullPersonImagePath(path: String?): String? {
return path?.let { "${PERSON_BASE}${path}" }
return path?.let { IMAGE_BASE.plus(PROFILE_SIZE).plus(it) }
}
fun getFullPersonImagePath(person: Person): String? {
@@ -74,7 +77,7 @@ object TmdbUtils {
if (path.contains("http")) {
return path.substring(startIndex = 1)
}
"${AVATAR_BASE}${path}"
IMAGE_BASE.plus(LOGO_SIZE).plus(path)
}
}
@@ -84,7 +87,7 @@ object TmdbUtils {
fun getFullEpisodeStillPath(path: String?): String? {
return path?.let {
"${STILL_BASE}${path}"
IMAGE_BASE.plus(STILL_SIZE).plus(path)
}
}
@@ -211,7 +214,7 @@ object TmdbUtils {
fun getAccountAvatarUrl(accountDetails: AccountDetails): String {
val path = accountDetails.avatar.tmdb?.avatarPath
return "${AVATAR_BASE}${path}"
return IMAGE_BASE.plus(LOGO_SIZE).plus(path)
}
fun releaseYearFromData(releaseDate: String): String {