some work on v4 authentication

This commit is contained in:
Owen LeJeune
2022-06-20 15:48:31 -04:00
parent 24a0e56fd7
commit 007db6324e
11 changed files with 216 additions and 48 deletions

View File

@@ -18,12 +18,16 @@
android:exported="true" android:exported="true"
android:theme="@style/Theme.TVTime" android:theme="@style/Theme.TVTime"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<!-- android:screenOrientation="portrait">-->
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="app" android:host="@string/intent_route_auth_return" />
</intent-filter>
</activity> </activity>
</application> </application>

View File

@@ -1,6 +1,7 @@
package com.owenlejeune.tvtime package com.owenlejeune.tvtime
import android.os.Bundle import android.os.Bundle
import android.widget.Toast
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.animation.rememberSplineBasedDecay import androidx.compose.animation.rememberSplineBasedDecay
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
@@ -59,6 +60,13 @@ class MainActivity : MonetCompatActivity() {
SessionManager.initialize() SessionManager.initialize()
} }
var mainNavStartRoute = BottomNavItem.Items[0].route
intent.data?.let {
when (it.host) {
getString(R.string.intent_route_auth_return) -> mainNavStartRoute = BottomNavItem.Account.route
}
}
lifecycleScope.launchWhenCreated { lifecycleScope.launchWhenCreated {
monet.awaitMonetReady() monet.awaitMonetReady()
setContent { setContent {
@@ -66,7 +74,7 @@ class MainActivity : MonetCompatActivity() {
TVTimeTheme(monetCompat = monet) { TVTimeTheme(monetCompat = monet) {
val appNavController = rememberNavController() val appNavController = rememberNavController()
Box { Box {
MainNavigationRoutes(appNavController = appNavController) MainNavigationRoutes(appNavController = appNavController, mainNavStartRoute = mainNavStartRoute)
} }
} }
} }
@@ -74,7 +82,11 @@ class MainActivity : MonetCompatActivity() {
} }
@Composable @Composable
private fun AppScaffold(appNavController: NavHostController, preferences: AppPreferences = get(AppPreferences::class.java)) { private fun AppScaffold(
appNavController: NavHostController,
mainNavStartRoute: String = BottomNavItem.Items[0].route,
preferences: AppPreferences = get(AppPreferences::class.java)
) {
val windowSize = rememberWindowSizeClass() val windowSize = rememberWindowSizeClass()
val navController = rememberNavController() val navController = rememberNavController()
@@ -127,7 +139,8 @@ class MainActivity : MonetCompatActivity() {
navController = navController, navController = navController,
appBarTitle = appBarTitle, appBarTitle = appBarTitle,
appBarActions = appBarActions, appBarActions = appBarActions,
topBarScrollBehaviour = scrollBehavior topBarScrollBehaviour = scrollBehavior,
mainNavStartRoute = mainNavStartRoute
) )
} }
} }
@@ -180,7 +193,11 @@ class MainActivity : MonetCompatActivity() {
item: BottomNavItem item: BottomNavItem
) { ) {
appBarTitle.value = item.name appBarTitle.value = item.name
navController.navigate(item.route) { navigateToRoute(navController, item.route)
}
private fun navigateToRoute(navController: NavController, route: String) {
navController.navigate(route) {
navController.graph.startDestinationRoute?.let { screenRoute -> navController.graph.startDestinationRoute?.let { screenRoute ->
popUpTo(screenRoute) { popUpTo(screenRoute) {
saveState = true saveState = true
@@ -220,7 +237,8 @@ class MainActivity : MonetCompatActivity() {
navController: NavHostController, navController: NavHostController,
topBarScrollBehaviour: TopAppBarScrollBehavior, topBarScrollBehaviour: TopAppBarScrollBehavior,
appBarTitle: MutableState<String>, appBarTitle: MutableState<String>,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}) appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
mainNavStartRoute: String = BottomNavItem.Items[0].route
) { ) {
if (windowSize == WindowSizeClass.Expanded) { if (windowSize == WindowSizeClass.Expanded) {
DualColumnMainContent( DualColumnMainContent(
@@ -228,14 +246,16 @@ class MainActivity : MonetCompatActivity() {
navController = navController, navController = navController,
appBarTitle = appBarTitle, appBarTitle = appBarTitle,
appBarActions = appBarActions, appBarActions = appBarActions,
topBarScrollBehaviour = topBarScrollBehaviour topBarScrollBehaviour = topBarScrollBehaviour,
mainNavStartRoute = mainNavStartRoute
) )
} else { } else {
SingleColumnMainContent( SingleColumnMainContent(
appNavController = appNavController, appNavController = appNavController,
navController = navController, navController = navController,
appBarTitle = appBarTitle, appBarTitle = appBarTitle,
appBarActions = appBarActions appBarActions = appBarActions,
mainNavStartRoute = mainNavStartRoute
) )
} }
} }
@@ -245,13 +265,15 @@ class MainActivity : MonetCompatActivity() {
appNavController: NavHostController, appNavController: NavHostController,
navController: NavHostController, navController: NavHostController,
appBarTitle: MutableState<String>, appBarTitle: MutableState<String>,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}) appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
mainNavStartRoute: String = BottomNavItem.Items[0].route
) { ) {
MainMediaView( MainMediaView(
appNavController = appNavController, appNavController = appNavController,
navController = navController, navController = navController,
appBarTitle = appBarTitle, appBarTitle = appBarTitle,
appBarActions = appBarActions appBarActions = appBarActions,
mainNavStartRoute = mainNavStartRoute
) )
} }
@@ -261,7 +283,8 @@ class MainActivity : MonetCompatActivity() {
navController: NavHostController, navController: NavHostController,
topBarScrollBehaviour: TopAppBarScrollBehavior, topBarScrollBehaviour: TopAppBarScrollBehavior,
appBarTitle: MutableState<String>, appBarTitle: MutableState<String>,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}) appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
mainNavStartRoute: String = BottomNavItem.Items[0].route
) { ) {
val navBackStackEntry by navController.currentBackStackEntryAsState() val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route val currentRoute = navBackStackEntry?.destination?.route
@@ -295,7 +318,8 @@ class MainActivity : MonetCompatActivity() {
appNavController = appNavController, appNavController = appNavController,
navController = navController, navController = navController,
appBarTitle = appBarTitle, appBarTitle = appBarTitle,
appBarActions = appBarActions appBarActions = appBarActions,
mainNavStartRoute = mainNavStartRoute
) )
} }
} }
@@ -306,7 +330,8 @@ class MainActivity : MonetCompatActivity() {
appNavController: NavHostController, appNavController: NavHostController,
navController: NavHostController, navController: NavHostController,
appBarTitle: MutableState<String>, appBarTitle: MutableState<String>,
appBarActions: MutableState<RowScope.() -> Unit> = mutableStateOf({}) appBarActions: MutableState<RowScope.() -> Unit> = mutableStateOf({}),
mainNavStartRoute: String = BottomNavItem.Items[0].route
) { ) {
Column { Column {
val navBackStackEntry by navController.currentBackStackEntryAsState() val navBackStackEntry by navController.currentBackStackEntryAsState()
@@ -324,7 +349,8 @@ class MainActivity : MonetCompatActivity() {
appNavController = appNavController, appNavController = appNavController,
navController = navController, navController = navController,
appBarTitle = appBarTitle, appBarTitle = appBarTitle,
appBarActions = appBarActions appBarActions = appBarActions,
startDestination = mainNavStartRoute
) )
} }
} }
@@ -337,11 +363,12 @@ class MainActivity : MonetCompatActivity() {
@Composable @Composable
private fun MainNavigationRoutes( private fun MainNavigationRoutes(
startDestination: String = MainNavItem.MainView.route, startDestination: String = MainNavItem.MainView.route,
mainNavStartRoute: String = BottomNavItem.Items[0].route,
appNavController: NavHostController, appNavController: NavHostController,
) { ) {
NavHost(navController = appNavController, startDestination = startDestination) { NavHost(navController = appNavController, startDestination = startDestination) {
composable(MainNavItem.MainView.route) { composable(MainNavItem.MainView.route) {
AppScaffold(appNavController = appNavController) AppScaffold(appNavController = appNavController, mainNavStartRoute = mainNavStartRoute)
} }
composable( composable(
MainNavItem.DetailView.route.plus("/{${NavConstants.TYPE_KEY}}/{${NavConstants.ID_KEY}}"), MainNavItem.DetailView.route.plus("/{${NavConstants.TYPE_KEY}}/{${NavConstants.ID_KEY}}"),

View File

@@ -2,6 +2,7 @@ package com.owenlejeune.tvtime
import android.app.Application import android.app.Application
import com.facebook.stetho.Stetho import com.facebook.stetho.Stetho
import com.kieronquinn.monetcompat.core.MonetCompat
import com.owenlejeune.tvtime.di.modules.appModule import com.owenlejeune.tvtime.di.modules.appModule
import com.owenlejeune.tvtime.di.modules.networkModule import com.owenlejeune.tvtime.di.modules.networkModule
import com.owenlejeune.tvtime.di.modules.preferencesModule import com.owenlejeune.tvtime.di.modules.preferencesModule
@@ -28,6 +29,8 @@ class TvTimeApplication: Application() {
) )
} }
MonetCompat.enablePaletteCompat()
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Stetho.initializeWithDefaults(this) Stetho.initializeWithDefaults(this)
} }

View File

@@ -98,7 +98,13 @@ class TmdbClient: KoinComponent {
private inner class V4Interceptor: Interceptor { private inner class V4Interceptor: Interceptor {
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
val builder = chain.request().newBuilder() val builder = chain.request().newBuilder()
builder.header("Authorization", "Bearer ${BuildConfig.TMDB_Api_v4Key}") with(chain.request()) {
if (url.encodedPathSegments.contains("auth")) {
builder.header("Authorization", "Bearer ${BuildConfig.TMDB_Api_v4Key}")
} else {
builder.header("Authorization", "Bearer ${SessionManager.currentSession!!.accessToken}")
}
}
val locale = Locale.current val locale = Locale.current
val languageCode = "${locale.language}-${locale.region}" val languageCode = "${locale.language}-${locale.region}"

View File

@@ -23,4 +23,7 @@ interface AuthenticationApi {
@POST("authentication/token/validate_with_login") @POST("authentication/token/validate_with_login")
suspend fun validateTokenWithLogin(@Body body: TokenValidationBody): Response<CreateTokenResponse> suspend fun validateTokenWithLogin(@Body body: TokenValidationBody): Response<CreateTokenResponse>
@POST("authentication/session/convert/4")
suspend fun createSessionFromV4Token(@Body body: V4TokenBody): Response<CreateSessionResponse>
} }

View File

@@ -27,4 +27,8 @@ class AuthenticationService {
suspend fun validateTokenWithLogin(body: TokenValidationBody): Response<CreateTokenResponse> { suspend fun validateTokenWithLogin(body: TokenValidationBody): Response<CreateTokenResponse> {
return service.validateTokenWithLogin(body) return service.validateTokenWithLogin(body)
} }
suspend fun createSessionFromV4Token(body: V4TokenBody): Response<CreateSessionResponse> {
return service.createSessionFromV4Token(body)
}
} }

View File

@@ -0,0 +1,7 @@
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
import com.google.gson.annotations.SerializedName
class V4TokenBody(
@SerializedName("access_token") val accessToken: String
)

View File

@@ -59,9 +59,10 @@ fun MainNavGraph(
appNavController: NavHostController, appNavController: NavHostController,
navController: NavHostController, navController: NavHostController,
appBarTitle: MutableState<String>, appBarTitle: MutableState<String>,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}) appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
startDestination: String = BottomNavItem.Items[0].route
) { ) {
NavHost(navController = navController, startDestination = BottomNavItem.Movies.route) { NavHost(navController = navController, startDestination = startDestination) {
composable(BottomNavItem.Movies.route) { composable(BottomNavItem.Movies.route) {
appBarActions.value = {} appBarActions.value = {}
MediaTab(appNavController = appNavController, mediaType = MediaViewType.MOVIE) MediaTab(appNavController = appNavController, mediaType = MediaViewType.MOVIE)

View File

@@ -1,5 +1,6 @@
package com.owenlejeune.tvtime.ui.screens.tabs.bottom package com.owenlejeune.tvtime.ui.screens.tabs.bottom
import android.content.Context
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
@@ -7,13 +8,11 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -52,7 +51,13 @@ fun AccountTab(
appBarTitle: MutableState<String>, appBarTitle: MutableState<String>,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}) appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({})
) { ) {
if (appBarTitle.value.equals(stringResource(id = R.string.nav_account_title))) { val lastSelectedOption = remember { mutableStateOf("") }
if (SessionManager.isV4SignInInProgress) {
v4SignInPart2(lastSelectedOption)
}
if (appBarTitle.value == stringResource(id = R.string.nav_account_title)) {
when (SessionManager.currentSession?.isAuthorized) { when (SessionManager.currentSession?.isAuthorized) {
false -> { false -> {
appBarTitle.value = appBarTitle.value =
@@ -74,34 +79,30 @@ fun AccountTab(
} }
} }
val lastSelectedOption = remember { mutableStateOf("") }
appBarActions.value = { appBarActions.value = {
AccountDropdownMenu(session = SessionManager.currentSession, lastSelectedOption = lastSelectedOption) AccountDropdownMenu(session = SessionManager.currentSession, lastSelectedOption = lastSelectedOption)
} }
if (lastSelectedOption.value.isNotBlank() || lastSelectedOption.value.isBlank()) { SessionManager.currentSession?.let { session ->
SessionManager.currentSession?.let { session -> val tabs = if (session.isAuthorized) {
val tabs = if (session.isAuthorized) { AccountTabNavItem.AuthorizedItems
AccountTabNavItem.AuthorizedItems } else {
} else { AccountTabNavItem.GuestItems
AccountTabNavItem.GuestItems }
Column {
when(session.isAuthorized) {
true -> { AuthorizedSessionIcon() }
false -> { GuestSessionIcon() }
} }
Column { val pagerState = rememberPagerState()
when(session.isAuthorized) { ScrollableTabs(tabs = tabs, pagerState = pagerState)
true -> { AuthorizedSessionIcon() } AccountTabs(
false -> { GuestSessionIcon() } appNavController = appNavController,
} tabs = tabs,
pagerState = pagerState
val pagerState = rememberPagerState() )
ScrollableTabs(tabs = tabs, pagerState = pagerState)
AccountTabs(
appNavController = appNavController,
tabs = tabs,
pagerState = pagerState
)
}
} }
} }
} }
@@ -338,6 +339,11 @@ private fun NoSessionMenuItems(
text = { Text(text = stringResource(id = R.string.action_sign_in)) }, text = { Text(text = stringResource(id = R.string.action_sign_in)) },
onClick = { showSignInDialog.value = true } onClick = { showSignInDialog.value = true }
) )
// val context = LocalContext.current
// DropdownMenuItem(
// text = { Text(text = stringResource(id = R.string.action_sign_in)) },
// onClick = { v4SignInPart1(context) }
// )
DropdownMenuItem( DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_sign_in_as_guest)) }, text = { Text(text = stringResource(id = R.string.action_sign_in_as_guest)) },
@@ -348,6 +354,21 @@ private fun NoSessionMenuItems(
) )
} }
private fun v4SignInPart1(context: Context) {
CoroutineScope(Dispatchers.IO).launch {
SessionManager.signInWithV4Part1(context)
}
}
private fun v4SignInPart2(lastSelectedOption: MutableState<String>) {
CoroutineScope(Dispatchers.IO).launch {
val signIn = SessionManager.signInWithV4Part2()
if (signIn) {
lastSelectedOption.value = NO_SESSION_SIGN_IN
}
}
}
@Composable @Composable
private fun GuestSessionIcon() { private fun GuestSessionIcon() {
val guestName = stringResource(id = R.string.account_name_guest) val guestName = stringResource(id = R.string.account_name_guest)

View File

@@ -1,10 +1,18 @@
package com.owenlejeune.tvtime.utils package com.owenlejeune.tvtime.utils
import android.content.Context
import android.content.Intent
import android.net.Uri
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.api.v3.AccountService import com.owenlejeune.tvtime.api.tmdb.api.v3.AccountService
import com.owenlejeune.tvtime.api.tmdb.api.v3.AuthenticationService import com.owenlejeune.tvtime.api.tmdb.api.v3.AuthenticationService
import com.owenlejeune.tvtime.api.tmdb.api.v3.GuestSessionService import com.owenlejeune.tvtime.api.tmdb.api.v3.GuestSessionService
import com.owenlejeune.tvtime.api.tmdb.TmdbClient import com.owenlejeune.tvtime.api.tmdb.TmdbClient
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.* import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
import com.owenlejeune.tvtime.api.tmdb.api.v4.AuthenticationV4Service
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthAccessBody
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthDeleteBody
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthRequestBody
import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.preferences.AppPreferences
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -21,7 +29,10 @@ object SessionManager: KoinComponent {
val currentSession: Session? val currentSession: Session?
get() = _currentSession get() = _currentSession
var isV4SignInInProgress: Boolean = false
private val authenticationService by lazy { TmdbClient().createAuthenticationService() } private val authenticationService by lazy { TmdbClient().createAuthenticationService() }
private val authenticationV4Service by lazy { TmdbClient().createV4AuthenticationService() }
fun clearSession(onResponse: (isSuccessful: Boolean) -> Unit) { fun clearSession(onResponse: (isSuccessful: Boolean) -> Unit) {
currentSession?.let { session -> currentSession?.let { session ->
@@ -39,6 +50,22 @@ object SessionManager: KoinComponent {
} }
} }
fun clearSessionV4(onResponse: (isSuccessful: Boolean) -> Unit) {
currentSession?.let { session ->
CoroutineScope(Dispatchers.IO).launch {
val deleteResponse = authenticationV4Service.deleteAccessToken(AuthDeleteBody(session.sessionId))
withContext(Dispatchers.Main) {
if (deleteResponse.isSuccessful) {
_currentSession = null
preferences.guestSessionId = ""
preferences.authorizedSessionId = ""
}
onResponse(deleteResponse.isSuccessful)
}
}
}
}
suspend fun initialize() { suspend fun initialize() {
if (preferences.guestSessionId.isNotEmpty()) { if (preferences.guestSessionId.isNotEmpty()) {
val session = GuestSession() val session = GuestSession()
@@ -91,7 +118,57 @@ object SessionManager: KoinComponent {
return false return false
} }
abstract class Session(val sessionId: String, val isAuthorized: Boolean) { suspend fun signInWithV4Part1(context: Context) {
isV4SignInInProgress = true
val service = AuthenticationV4Service()
val requestTokenResponse = service.createRequestToken(AuthRequestBody(redirect = ""))
if (requestTokenResponse.isSuccessful) {
requestTokenResponse.body()?.let { ctr ->
_currentSession = InProgressSession(ctr.requestToken)
val browserIntent = Intent(
Intent.ACTION_VIEW,
Uri.parse(
context.getString(R.string.tmdb_auth_url, ctr.requestToken)
)
)
context.startActivity(browserIntent)
}
}
}
suspend fun signInWithV4Part2(): Boolean {
if (isV4SignInInProgress && _currentSession is InProgressSession) {
val requestToken = _currentSession!!.sessionId
val authResponse = authenticationV4Service.createAccessToken(AuthAccessBody(requestToken))
if (authResponse.isSuccessful) {
authResponse.body()?.let { ar ->
if (ar.success) {
val sessionResponse = authenticationService.createSessionFromV4Token(V4TokenBody(ar.accessToken))
if (sessionResponse.isSuccessful) {
sessionResponse.body()?.let { sr ->
preferences.authorizedSessionId = sr.sessionId
preferences.guestSessionId = ""
_currentSession = AuthorizedSession(accessToken = ar.accessToken)
_currentSession?.initialize()
isV4SignInInProgress = false
return true
}
}
// preferences.authorizedSessionId = ar.accessToken
// preferences.guestSessionId = ""
// _currentSession = AuthorizedSession()
// _currentSession?.initialize()
// isV4SignInInProgress = false
// return true
}
}
}
}
return false
}
abstract class Session(val sessionId: String, val isAuthorized: Boolean, val accessToken: String = "") {
protected open var _ratedMovies: List<RatedMovie> = emptyList() protected open var _ratedMovies: List<RatedMovie> = emptyList()
val ratedMovies: List<RatedMovie> val ratedMovies: List<RatedMovie>
get() = _ratedMovies get() = _ratedMovies
@@ -187,7 +264,18 @@ object SessionManager: KoinComponent {
} }
} }
private class AuthorizedSession: Session(preferences.authorizedSessionId, true) { private class InProgressSession(requestToken: String): Session(requestToken, false) {
override suspend fun initialize() {
// do nothing
}
override suspend fun refresh(changed: Array<Changed>) {
// do nothing
}
}
private class AuthorizedSession(accessToken: String = ""): Session(preferences.authorizedSessionId, true, accessToken) {
private val service by lazy { AccountService() } private val service by lazy { AccountService() }
override suspend fun initialize() { override suspend fun initialize() {

View File

@@ -100,4 +100,8 @@
<string name="username_label">Username</string> <string name="username_label">Username</string>
<string name="password_label">Password</string> <string name="password_label">Password</string>
<string name="no_account_message">Don\'t have an account?</string> <string name="no_account_message">Don\'t have an account?</string>
<string name="tmdb_auth_url">"https://www.themoviedb.org/auth/access?request_token=%1$s"</string>
<!-- intent routes -->
<string name="intent_route_auth_return">tvtime.auth.return</string>
</resources> </resources>