diff --git a/app/src/main/java/com/owenlejeune/tvtime/MainActivity.kt b/app/src/main/java/com/owenlejeune/tvtime/MainActivity.kt index c86e510..979ac2b 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/MainActivity.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/MainActivity.kt @@ -4,12 +4,15 @@ import android.os.Bundle import androidx.activity.compose.setContent import androidx.compose.animation.rememberSplineBasedDecay import androidx.compose.foundation.Image +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Scaffold import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.input.nestedscroll.nestedScroll @@ -27,6 +30,7 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument +import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.kieronquinn.monetcompat.app.MonetCompatActivity import com.owenlejeune.tvtime.extensions.WindowSizeClass import com.owenlejeune.tvtime.extensions.rememberWindowSizeClass @@ -171,6 +175,9 @@ class MainActivity : MonetCompatActivity() { NavigationBar { BottomNavItem.Items.forEach { item -> NavigationBarItem( + modifier = Modifier + .padding(4.dp) + .clip(RoundedCornerShape(24.dp)), icon = { Icon(painter = painterResource(id = item.icon), contentDescription = null) }, label = { Text(item.name) }, selected = currentRoute == item.route, @@ -191,8 +198,8 @@ class MainActivity : MonetCompatActivity() { appBarTitle: MutableState, item: BottomNavItem ) { - appBarTitle.value = item.name navigateToRoute(navController, item.route) + appBarTitle.value = item.name } private fun navigateToRoute(navController: NavController, route: String) { diff --git a/app/src/main/java/com/owenlejeune/tvtime/OnboardingActivity.kt b/app/src/main/java/com/owenlejeune/tvtime/OnboardingActivity.kt index 56dcf46..03fcddb 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/OnboardingActivity.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/OnboardingActivity.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.lifecycleScope import com.google.accompanist.pager.* +import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.kieronquinn.monetcompat.app.MonetCompatActivity import com.owenlejeune.tvtime.extensions.launchActivity import com.owenlejeune.tvtime.preferences.AppPreferences diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/TmdbClient.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/TmdbClient.kt index ef6f6f9..f1595ac 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/TmdbClient.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/TmdbClient.kt @@ -99,6 +99,9 @@ class TmdbClient: KoinComponent { sessionIdParam = QueryParam("session_id", SessionManager.currentSession!!.sessionId) } else if (preferences.authorizedSessionId.isNotEmpty()) { sessionIdParam = QueryParam("session_id", preferences.authorizedSessionId) + } else if (preferences.authorizedSessionValues != null) { + val sessionId = preferences.authorizedSessionValues!!.sessionId + sessionIdParam = QueryParam("session_id", sessionId) } } return sessionIdParam @@ -113,16 +116,15 @@ class TmdbClient: KoinComponent { builder.header("Authorization", "Bearer ${BuildConfig.TMDB_Api_v4Key}") } else { builder.header("Authorization", "Bearer ${SessionManager.currentSession!!.accessToken}") + val locale = Locale.current + val languageCode = "${locale.language}-${locale.region}" + val languageParam = QueryParam("language", languageCode) + + val newUrl = url.newBuilder().addQueryParams(languageParam).build() + builder.url(newUrl) } } - val locale = Locale.current - val languageCode = "${locale.language}-${locale.region}" - val languageParam = QueryParam("language", languageCode) - - val url = chain.request().url.newBuilder().addQueryParams(languageParam).build() - builder.url(url) - return chain.proceed(builder.build()) } } diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v4/AccountV4Api.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v4/AccountV4Api.kt index e1fef5b..99ab459 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v4/AccountV4Api.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v4/AccountV4Api.kt @@ -12,28 +12,28 @@ import retrofit2.http.Path interface AccountV4Api { - @GET("/account/{account_id}/lists") + @GET("account/{account_id}/lists") suspend fun getLists(@Path("account_id") accountId: String, page: Int = 1): Response> - @GET("/account/{account_id}/movie/favorites") + @GET("account/{account_id}/movie/favorites") suspend fun getFavoriteMovies(@Path("account_id") accountId: String, page: Int = 1): Response> - @GET("/account/{account_id}/tv/favorites") + @GET("account/{account_id}/tv/favorites") suspend fun getFavoriteTvShows(@Path("account_id") accountId: String, page: Int = 1): Response> - @GET("/account/{account_id}/movie/recommendations") + @GET("account/{account_id}/movie/recommendations") suspend fun getMovieRecommendations(@Path("account_id") accountId: String, page: Int = 1): Response> - @GET("/account/{account_id}/tv/recommendations") + @GET("account/{account_id}/tv/recommendations") suspend fun getTvShowRecommendations(@Path("account_id") accountId: String, page: Int = 1): Response> - @GET("/account/{account_id}/movie/watchlist") + @GET("account/{account_id}/movie/watchlist") suspend fun getMovieWatchlist(@Path("account_id") accountId: String, page: Int = 1): Response> - @GET("/account/{account_id}/tv/watchlist") + @GET("account/{account_id}/tv/watchlist") suspend fun getTvShowWatchlist(@Path("account_id") accountId: String, page: Int = 1): Response> - @GET("/account/{account_id}/movie/rated") + @GET("account/{account_id}/movie/rated") suspend fun getRatedMovies(@Path("account_id") accountId: String, page: Int = 1): Response> @GET("account/{account_id}/tv/rated") diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v4/AuthenticationV4Api.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v4/AuthenticationV4Api.kt index f485615..7e0a8b7 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v4/AuthenticationV4Api.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v4/AuthenticationV4Api.kt @@ -3,18 +3,21 @@ package com.owenlejeune.tvtime.api.tmdb.api.v4 import com.owenlejeune.tvtime.api.tmdb.api.v3.model.StatusResponse import com.owenlejeune.tvtime.api.tmdb.api.v4.model.* import retrofit2.Response +import retrofit2.http.Body import retrofit2.http.DELETE +import retrofit2.http.HTTP import retrofit2.http.POST interface AuthenticationV4Api { @POST("auth/request_token") - suspend fun createRequestToken(body: AuthRequestBody): Response + suspend fun createRequestToken(@Body body: AuthRequestBody): Response @POST("auth/access_token") - suspend fun createAccessToken(body: AuthAccessBody): Response + suspend fun createAccessToken(@Body body: AuthAccessBody): Response - @DELETE("auth/access_token") - suspend fun deleteAccessToken(body: AuthDeleteBody): Response +// @DELETE("auth/access_token") + @HTTP(method = "DELETE", path = "auth/access_token", hasBody = true) + suspend fun deleteAccessToken(@Body body: AuthDeleteBody): Response } \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/preferences/AppPreferences.kt b/app/src/main/java/com/owenlejeune/tvtime/preferences/AppPreferences.kt index 88f4c3a..fd5027f 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/preferences/AppPreferences.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/preferences/AppPreferences.kt @@ -2,8 +2,10 @@ package com.owenlejeune.tvtime.preferences import android.content.Context import android.content.SharedPreferences +import com.google.gson.Gson import com.kieronquinn.monetcompat.core.MonetCompat import com.owenlejeune.tvtime.BuildConfig +import com.owenlejeune.tvtime.utils.SessionManager class AppPreferences(context: Context) { @@ -15,11 +17,13 @@ class AppPreferences(context: Context) { private val HIDE_TITLE = "hide_title" private val GUEST_SESSION = "guest_session_id" private val AUTHORIZED_SESSION = "authorized_session_id" + private val AUTHORIZED_SESSION_VALUES = "authorized_session_values" private val FIRST_LAUNCH = "first_launch" private val FIRST_LAUNCH_TESTING = "first_launch_testing" private val CHROMA_MULTIPLIER = "chroma_multiplier" private val USE_SYSTEM_COLORS = "use_system_colors" private val SELECTED_COLOR = "selected_color" + private val USE_V4_API = "use_v4_api" } private val preferences: SharedPreferences = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE) @@ -36,6 +40,12 @@ class AppPreferences(context: Context) { get() = preferences.getString(GUEST_SESSION, "") ?: "" set(value) { preferences.put(GUEST_SESSION, value) } + var authorizedSessionValues: SessionManager.AuthorizedSessionValues? + get() = preferences.getString(AUTHORIZED_SESSION_VALUES, null)?.let { + Gson().fromJson(it, SessionManager.AuthorizedSessionValues::class.java) + } + set(value) { preferences.putNullableString(AUTHORIZED_SESSION_VALUES, value?.let { Gson().toJson(value) }) } + var authorizedSessionId: String get() = preferences.getString(AUTHORIZED_SESSION, "") ?: "" set(value) { preferences.put(AUTHORIZED_SESSION, value) } @@ -64,6 +74,10 @@ class AppPreferences(context: Context) { get() = preferences.getInt(SELECTED_COLOR, Int.MAX_VALUE) set(value) { preferences.put(SELECTED_COLOR, value) } + var useV4Api: Boolean + get() = preferences.getBoolean(USE_V4_API, true) + set(value) { preferences.put(USE_V4_API, value) } + private fun SharedPreferences.put(key: String, value: Any?) { edit().apply { when (value) { @@ -79,25 +93,10 @@ class AppPreferences(context: Context) { } } + private fun SharedPreferences.putNullableString(key: String, value: String?) { + edit().putString(key, value).apply() + } + class UnsupportedTypeError: Exception() -// private fun SharedPreferences.get(key: String, java: Class): T { -// -// } -// -// inner class PrefsMutableState(val key: String): MutableState { -// override var value: T -// get() = preferences.get(key, T::class.java) -// set(value) { preferences.put(key, value) } -// -// override fun component1(): T { -// TODO("Not yet implemented") -// } -// -// override fun component2(): (T) -> Unit { -// TODO("Not yet implemented") -// } -// -// } - } \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/BottomNavItem.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/BottomNavItem.kt index b67dc9c..fd8c654 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/BottomNavItem.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/BottomNavItem.kt @@ -20,7 +20,7 @@ sealed class BottomNavItem(stringRes: Int, val icon: Int, val route: String): Ko Movies.route -> Movies TV.route -> TV Account.route -> Account - Favourites.route -> Favourites +// Favourites.route -> Favourites Settings.route -> Settings else -> null } @@ -30,7 +30,7 @@ sealed class BottomNavItem(stringRes: Int, val icon: Int, val route: String): Ko object Movies: BottomNavItem(R.string.nav_movies_title, R.drawable.ic_movie, "movies_route") object TV: BottomNavItem(R.string.nav_tv_title, R.drawable.ic_tv, "tv_route") object Account: BottomNavItem(R.string.nav_account_title, R.drawable.ic_person, "account_route") - object Favourites: BottomNavItem(R.string.nav_favourites_title, R.drawable.ic_favorite, "favourites_route") +// object Favourites: BottomNavItem(R.string.nav_favourites_title, R.drawable.ic_favorite, "favourites_route") object Settings: BottomNavItem(R.string.nav_settings_title, R.drawable.ic_settings, "settings_route") object People: BottomNavItem(R.string.nav_people_title, R.drawable.ic_face, "people_route") diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/Routes.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/Routes.kt index a37cea0..ad1cc3c 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/Routes.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/Routes.kt @@ -63,26 +63,26 @@ fun MainNavGraph( NavHost(navController = navController, startDestination = startDestination) { composable(BottomNavItem.Movies.route) { appBarActions.value = {} - MediaTab(appNavController = appNavController, mediaType = MediaViewType.MOVIE) + MediaTab(appBarTitle = appBarTitle, appNavController = appNavController, mediaType = MediaViewType.MOVIE) } composable(BottomNavItem.TV.route) { appBarActions.value = {} - MediaTab(appNavController = appNavController, mediaType = MediaViewType.TV) + MediaTab(appBarTitle = appBarTitle, appNavController = appNavController, mediaType = MediaViewType.TV) } composable(BottomNavItem.Account.route) { AccountTab(appBarTitle = appBarTitle, appNavController = appNavController, appBarActions = appBarActions) } composable(BottomNavItem.People.route) { appBarActions.value = {} - PeopleTab(appBarTitle, appNavController = appNavController) - } - composable(BottomNavItem.Favourites.route) { - appBarActions.value = {} - FavouritesTab() + PeopleTab(appBarTitle = appBarTitle, appNavController = appNavController) } +// composable(BottomNavItem.Favourites.route) { +// appBarActions.value = {} +// FavouritesTab() +// } composable(BottomNavItem.Settings.route) { appBarActions.value = {} - SettingsTab() + SettingsTab(appBarTitle = appBarTitle) } } } \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/AccountTab.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/AccountTab.kt index 568a511..571484b 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/AccountTab.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/AccountTab.kt @@ -8,10 +8,15 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -24,7 +29,9 @@ import com.google.accompanist.pager.PagerState import com.google.accompanist.pager.rememberPagerState import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.api.tmdb.api.v3.model.* -import com.owenlejeune.tvtime.ui.components.* +import com.owenlejeune.tvtime.preferences.AppPreferences +import com.owenlejeune.tvtime.ui.components.RoundedLetterImage +import com.owenlejeune.tvtime.ui.components.SignInDialog import com.owenlejeune.tvtime.ui.navigation.AccountTabNavItem import com.owenlejeune.tvtime.ui.navigation.ListFetchFun import com.owenlejeune.tvtime.ui.navigation.MainNavItem @@ -35,6 +42,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.koin.java.KoinJavaComponent +import org.koin.java.KoinJavaComponent.get import kotlin.reflect.KClass private const val GUEST_SIGN_IN = "guest_sign_in" @@ -51,11 +60,12 @@ fun AccountTab( ) { val lastSelectedOption = remember { mutableStateOf("") } + val lso = lastSelectedOption.value if (SessionManager.isV4SignInInProgress) { + appBarTitle.value = stringResource(id = R.string.account_not_logged_in) + AccountLoadingView() v4SignInPart2(lastSelectedOption) - } - - if (appBarTitle.value == stringResource(id = R.string.nav_account_title)) { + } else { when (SessionManager.currentSession?.isAuthorized) { false -> { appBarTitle.value = @@ -75,33 +85,54 @@ fun AccountTab( appBarTitle.value = stringResource(id = R.string.account_not_logged_in) } } - } - appBarActions.value = { - AccountDropdownMenu(session = SessionManager.currentSession, lastSelectedOption = lastSelectedOption) - } - - SessionManager.currentSession?.let { session -> - val tabs = if (session.isAuthorized) { - AccountTabNavItem.AuthorizedItems - } else { - AccountTabNavItem.GuestItems - } - - Column { - when(session.isAuthorized) { - true -> { AuthorizedSessionIcon() } - false -> { GuestSessionIcon() } - } - - val pagerState = rememberPagerState() - ScrollableTabs(tabs = tabs, pagerState = pagerState) - AccountTabs( - appNavController = appNavController, - tabs = tabs, - pagerState = pagerState + appBarActions.value = { + AccountDropdownMenu( + session = SessionManager.currentSession, + lastSelectedOption = lastSelectedOption ) } + + if (!SessionManager.isV4SignInInProgress) { + SessionManager.currentSession?.let { session -> + val tabs = if (session.isAuthorized) { + AccountTabNavItem.AuthorizedItems + } else { + AccountTabNavItem.GuestItems + } + + Column { + when (session.isAuthorized) { + true -> { + AuthorizedSessionIcon() + } + false -> { + GuestSessionIcon() + } + } + + val pagerState = rememberPagerState() + ScrollableTabs(tabs = tabs, pagerState = pagerState) + AccountTabs( + appNavController = appNavController, + tabs = tabs, + pagerState = pagerState + ) + } + } + } + } +} + +@Composable +private fun AccountLoadingView() { + Box(modifier = Modifier.fillMaxSize()) { + CircularProgressIndicator( + modifier = Modifier + .align(Alignment.Center) + .size(200.dp), + color = MaterialTheme.colorScheme.secondary + ) } } @@ -276,12 +307,17 @@ private fun AccountDropdownMenu( @Composable private fun AuthorizedSessionMenuItems( expanded: MutableState, - lastSelectedOption: MutableState + lastSelectedOption: MutableState, + preferences: AppPreferences = get(AppPreferences::class.java) ) { DropdownMenuItem( text = { Text(text = stringResource(id = R.string.action_sign_out)) }, onClick = { - signOut(lastSelectedOption) + if (preferences.useV4Api) { + signOutV4(lastSelectedOption) + } else { + signOut(lastSelectedOption) + } expanded.value = false } ) @@ -320,7 +356,8 @@ private fun GuestSessionMenuItems( @Composable private fun NoSessionMenuItems( expanded: MutableState, - lastSelectedOption: MutableState + lastSelectedOption: MutableState, + preferences: AppPreferences = get(AppPreferences::class.java) ) { val showSignInDialog = remember { mutableStateOf(false) } @@ -333,15 +370,18 @@ private fun NoSessionMenuItems( } } - DropdownMenuItem( - text = { Text(text = stringResource(id = R.string.action_sign_in)) }, - onClick = { showSignInDialog.value = true } - ) -// val context = LocalContext.current -// DropdownMenuItem( -// text = { Text(text = stringResource(id = R.string.action_sign_in)) }, -// onClick = { v4SignInPart1(context) } -// ) + if (!preferences.useV4Api) { + DropdownMenuItem( + text = { Text(text = stringResource(id = R.string.action_sign_in)) }, + onClick = { showSignInDialog.value = true } + ) + } else { + val context = LocalContext.current + DropdownMenuItem( + text = { Text(text = stringResource(id = R.string.action_sign_in)) }, + onClick = { v4SignInPart1(context) } + ) + } DropdownMenuItem( text = { Text(text = stringResource(id = R.string.action_sign_in_as_guest)) }, @@ -362,7 +402,9 @@ private fun v4SignInPart2(lastSelectedOption: MutableState) { CoroutineScope(Dispatchers.IO).launch { val signIn = SessionManager.signInWithV4Part2() if (signIn) { - lastSelectedOption.value = NO_SESSION_SIGN_IN + withContext(Dispatchers.Main) { + lastSelectedOption.value = NO_SESSION_SIGN_IN + } } } } @@ -419,9 +461,21 @@ private fun createGuestSession(lastSelectedOption: MutableState) { } private fun signOut(lastSelectedOption: MutableState) { - SessionManager.clearSession { isSuccessful -> - if (isSuccessful) { - lastSelectedOption.value = SIGN_OUT + CoroutineScope(Dispatchers.IO).launch { + SessionManager.clearSession { isSuccessful -> + if (isSuccessful) { + lastSelectedOption.value = SIGN_OUT + } + } + } +} + +private fun signOutV4(lastSelectedOption: MutableState) { + CoroutineScope(Dispatchers.IO).launch { + SessionManager.clearSessionV4 { isSuccessful -> + if (isSuccessful) { + lastSelectedOption.value = SIGN_OUT + } } } } diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/MediaTab.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/MediaTab.kt index 48a6b4d..2b7a62f 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/MediaTab.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/MediaTab.kt @@ -2,6 +2,8 @@ package com.owenlejeune.tvtime.ui.screens.main import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController @@ -9,6 +11,7 @@ import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.PagerState import com.google.accompanist.pager.rememberPagerState +import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.api.tmdb.api.v3.HomePageService import com.owenlejeune.tvtime.api.tmdb.api.v3.MoviesService import com.owenlejeune.tvtime.api.tmdb.api.v3.TvService @@ -24,7 +27,17 @@ import kotlinx.coroutines.withContext @OptIn(ExperimentalPagerApi::class) @Composable -fun MediaTab(appNavController: NavHostController, mediaType: MediaViewType) { +fun MediaTab( + appBarTitle: MutableState, + appNavController: NavHostController, + mediaType: MediaViewType +) { + appBarTitle.value = when (mediaType) { + MediaViewType.MOVIE -> stringResource(id = R.string.nav_movies_title) + MediaViewType.TV -> stringResource(id = R.string.nav_tv_title) + else -> "" + } + Column { val tabs = when (mediaType) { MediaViewType.MOVIE -> MediaTabNavItem.MovieItems diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/PeopleTab.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/PeopleTab.kt index 4447fa3..71e2009 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/PeopleTab.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/PeopleTab.kt @@ -24,7 +24,7 @@ fun PeopleTab( appBarTitle: MutableState, appNavController: NavHostController ) { -// appBarTitle.value = stringResource(id = R.string.nav_people_title) + appBarTitle.value = stringResource(id = R.string.nav_people_title) val service = PeopleService() diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/SettingsTab.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/SettingsTab.kt index f10ee90..e922442 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/SettingsTab.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/SettingsTab.kt @@ -40,7 +40,12 @@ import kotlinx.coroutines.launch import org.koin.java.KoinJavaComponent.get @Composable -fun SettingsTab(preferences: AppPreferences = get(AppPreferences::class.java)) { +fun SettingsTab( + appBarTitle: MutableState, + preferences: AppPreferences = get(AppPreferences::class.java) +) { + appBarTitle.value = stringResource(id = R.string.nav_settings_title) + val scrollState = rememberScrollState() Column( @@ -238,6 +243,7 @@ private fun WallpaperPicker( @Composable private fun DebugOptions(preferences: AppPreferences = get(AppPreferences::class.java)) { val context = LocalContext.current + val coroutineScope = rememberCoroutineScope() val firstLaunchTesting = remember { mutableStateOf(preferences.firstLaunchTesting) } SwitchPreference( @@ -285,12 +291,29 @@ private fun DebugOptions(preferences: AppPreferences = get(AppPreferences::class .clickable( onClick = { preferences.guestSessionId = "" - SessionManager.clearSession { - Toast - .makeText(context, "Cleared session: $it", Toast.LENGTH_SHORT) - .show() + coroutineScope.launch { + SessionManager.clearSession { + Toast + .makeText(context, "Cleared session: $it", Toast.LENGTH_SHORT) + .show() + } + SessionManager.clearSessionV4 { + Toast + .makeText(context, "Cleared session v4: $it", Toast.LENGTH_SHORT) + .show() + } } } ) ) + + val useV4Api = remember { mutableStateOf(preferences.useV4Api) } + SwitchPreference( + titleText = "Use v4 API", + checkState = useV4Api.value, + onCheckedChange = { isChecked -> + useV4Api.value = isChecked + preferences.useV4Api = isChecked + } + ) } diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/theme/Theme.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/theme/Theme.kt index 39168bb..0589821 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/theme/Theme.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/theme/Theme.kt @@ -6,6 +6,7 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material3.darkColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.kieronquinn.monetcompat.core.MonetCompat private val DarkColorPalette = darkColorScheme( @@ -127,7 +128,12 @@ fun TVTimeTheme( isLight = !isDarkTheme ), shapes = Shapes, - content = content + content = { + val systemUiController = rememberSystemUiController() + systemUiController.setSystemBarsColor(color = androidx.compose.material3.MaterialTheme.colorScheme.background) + + content() + } ) } } \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/utils/SessionManager.kt b/app/src/main/java/com/owenlejeune/tvtime/utils/SessionManager.kt index cb4b85a..8bd041f 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/utils/SessionManager.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/utils/SessionManager.kt @@ -3,6 +3,8 @@ package com.owenlejeune.tvtime.utils import android.content.Context import android.content.Intent import android.net.Uri +import android.widget.Toast +import com.google.gson.annotations.SerializedName import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.api.tmdb.api.v3.AccountService import com.owenlejeune.tvtime.api.tmdb.api.v3.AuthenticationService @@ -34,7 +36,13 @@ object SessionManager: KoinComponent { private val authenticationService by lazy { TmdbClient().createAuthenticationService() } private val authenticationV4Service by lazy { TmdbClient().createV4AuthenticationService() } - fun clearSession(onResponse: (isSuccessful: Boolean) -> Unit) { + class AuthorizedSessionValues( + @SerializedName("session_id") val sessionId: String, + @SerializedName("access_token") val accessToken: String, + @SerializedName("account_id") val accountId: String + ) + + suspend fun clearSession(onResponse: (isSuccessful: Boolean) -> Unit) { currentSession?.let { session -> CoroutineScope(Dispatchers.IO).launch { val deleteResponse = authenticationService.deleteSession(SessionBody(session.sessionId)) @@ -42,7 +50,8 @@ object SessionManager: KoinComponent { if (deleteResponse.isSuccessful) { _currentSession = null preferences.guestSessionId = "" - preferences.authorizedSessionId = "" + preferences.authorizedSessionValues = null +// preferences.authorizedSessionId = "" } onResponse(deleteResponse.isSuccessful) } @@ -53,12 +62,13 @@ object SessionManager: KoinComponent { fun clearSessionV4(onResponse: (isSuccessful: Boolean) -> Unit) { currentSession?.let { session -> CoroutineScope(Dispatchers.IO).launch { - val deleteResponse = authenticationV4Service.deleteAccessToken(AuthDeleteBody(session.sessionId)) + val deleteResponse = authenticationV4Service.deleteAccessToken(AuthDeleteBody(session.accessToken)) withContext(Dispatchers.Main) { if (deleteResponse.isSuccessful) { _currentSession = null preferences.guestSessionId = "" - preferences.authorizedSessionId = "" + preferences.authorizedSessionValues = null +// preferences.authorizedSessionId = "" } onResponse(deleteResponse.isSuccessful) } @@ -75,6 +85,16 @@ object SessionManager: KoinComponent { val session = AuthorizedSession() session.initialize() _currentSession = session + } else { + preferences.authorizedSessionValues?.let { values -> + val session = AuthorizedSession( + sessionId = values.sessionId, + accessToken = values.accessToken, + accountId = values.accountId + ) + session.initialize() + _currentSession = session + } } } @@ -104,6 +124,7 @@ object SessionManager: KoinComponent { if (sr.isSuccess) { preferences.authorizedSessionId = sr.sessionId preferences.guestSessionId = "" + preferences.authorizedSessionValues = null _currentSession = AuthorizedSession() _currentSession?.initialize() return true @@ -122,7 +143,7 @@ object SessionManager: KoinComponent { isV4SignInInProgress = true val service = AuthenticationV4Service() - val requestTokenResponse = service.createRequestToken(AuthRequestBody(redirect = "")) + val requestTokenResponse = service.createRequestToken(AuthRequestBody(redirect = "app://tvtime.auth.return")) if (requestTokenResponse.isSuccessful) { requestTokenResponse.body()?.let { ctr -> _currentSession = InProgressSession(ctr.requestToken) @@ -147,20 +168,23 @@ object SessionManager: KoinComponent { val sessionResponse = authenticationService.createSessionFromV4Token(V4TokenBody(ar.accessToken)) if (sessionResponse.isSuccessful) { sessionResponse.body()?.let { sr -> - preferences.authorizedSessionId = sr.sessionId + preferences.authorizedSessionValues = AuthorizedSessionValues( + sessionId = sr.sessionId, + accountId = ar.accountId, + accessToken = ar.accessToken + ) + preferences.authorizedSessionId = "" preferences.guestSessionId = "" - _currentSession = AuthorizedSession(accessToken = ar.accessToken, accountId = ar.accountId) + _currentSession = AuthorizedSession( + sessionId = sr.sessionId, + accessToken = ar.accessToken, + accountId = ar.accountId + ) _currentSession?.initialize() isV4SignInInProgress = false return true } } -// preferences.authorizedSessionId = ar.accessToken -// preferences.guestSessionId = "" -// _currentSession = AuthorizedSession() -// _currentSession?.initialize() -// isV4SignInInProgress = false -// return true } } } @@ -275,7 +299,11 @@ object SessionManager: KoinComponent { } - private class AuthorizedSession(accessToken: String = "", accountId: String = ""): Session(preferences.authorizedSessionId, true, accessToken, accountId) { + private class AuthorizedSession( + sessionId: String = preferences.authorizedSessionId, + accessToken: String = "", + accountId: String = "" + ): Session(sessionId, true, accessToken, accountId) { private val service by lazy { AccountService() } override suspend fun initialize() { @@ -286,14 +314,14 @@ object SessionManager: KoinComponent { if (changed.contains(Changed.AccountDetails)) { service.getAccountDetails().apply { if (isSuccessful) { - withContext(Dispatchers.Main) { +// withContext(Dispatchers.Main) { _accountDetails = body() ?: _accountDetails accountDetails?.let { - CoroutineScope(Dispatchers.IO).launch { +// CoroutineScope(Dispatchers.IO).launch { refreshWithAccountId(it.id, changed) - } +// } } - } +// } } } } else if (accountDetails != null) {