move auth to internal webview

This commit is contained in:
Owen LeJeune
2023-06-02 17:24:12 -04:00
parent 20390b0d50
commit fbc1719523
8 changed files with 148 additions and 32 deletions

View File

@@ -90,6 +90,7 @@ dependencies {
implementation "androidx.paging:paging-compose:$compose_paging" implementation "androidx.paging:paging-compose:$compose_paging"
implementation "androidx.constraintlayout:constraintlayout-compose:$compose_constraint_layout" implementation "androidx.constraintlayout:constraintlayout-compose:$compose_constraint_layout"
implementation "androidx.paging:paging-compose:$compose_paging" implementation "androidx.paging:paging-compose:$compose_paging"
implementation "com.google.accompanist:accompanist-webview:0.28.0"
// material you // material you
def monet_compat = "0.4.1" def monet_compat = "0.4.1"
@@ -124,10 +125,10 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
// youtube player // youtube player
def player_core = "11.0.1" // def player_core = "11.0.1"
def chromecast_sender = "0.26" // def chromecast_sender = "0.26"
implementation "com.pierfrancescosoffritti.androidyoutubeplayer:core:$player_core" // implementation "com.pierfrancescosoffritti.androidyoutubeplayer:core:$player_core"
implementation "com.pierfrancescosoffritti.androidyoutubeplayer:chromecast-sender:$chromecast_sender" // implementation "com.pierfrancescosoffritti.androidyoutubeplayer:chromecast-sender:$chromecast_sender"
// markdown // markdown
def markdown = "0.2.1" def markdown = "0.2.1"

View File

@@ -446,6 +446,17 @@ class MainActivity : MonetCompatActivity() {
) )
} }
} }
composable(
route = MainNavItem.WebLinkView.route.plus("/{${NavConstants.WEB_LINK_KEY}}"),
arguments = listOf(
navArgument(NavConstants.WEB_LINK_KEY) { type = NavType.StringType }
)
) {
val url = it.arguments?.getString(NavConstants.WEB_LINK_KEY)
url?.let {
WebLinkView(url = url, appNavController = appNavController)
}
}
} }
} }

View File

@@ -10,5 +10,6 @@ sealed class MainNavItem(val route: String) {
object DetailView: MainNavItem("detail_route") object DetailView: MainNavItem("detail_route")
object SettingsView: MainNavItem("settings_route") object SettingsView: MainNavItem("settings_route")
object SearchView: MainNavItem("search_route") object SearchView: MainNavItem("search_route")
object WebLinkView: MainNavItem("web_link_route")
} }

View File

@@ -1,6 +1,5 @@
package com.owenlejeune.tvtime.ui.screens.main package com.owenlejeune.tvtime.ui.screens.main
import android.content.Context
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
@@ -8,9 +7,11 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@@ -56,11 +57,17 @@ fun AccountTab(
val currentSessionState = remember { SessionManager.currentSession } val currentSessionState = remember { SessionManager.currentSession }
val currentSession = currentSessionState.value val currentSession = currentSessionState.value
val scope = rememberCoroutineScope()
if (currentSession?.isAuthorized == false) { if (currentSession?.isAuthorized == false) {
appBarTitle.value = stringResource(id = R.string.account_not_logged_in) appBarTitle.value = stringResource(id = R.string.account_not_logged_in)
if (doSignInPartTwo) { if (doSignInPartTwo) {
AccountLoadingView() AccountLoadingView()
signInPart2() LaunchedEffect(Unit) {
scope.launch {
SessionManager.singInPart2()
}
}
} }
} else { } else {
if (currentSession?.isAuthorized == true) { if (currentSession?.isAuthorized == true) {
@@ -76,7 +83,8 @@ fun AccountTab(
appBarActions.value = { appBarActions.value = {
AccountDropdownMenu( AccountDropdownMenu(
session = currentSession session = currentSession,
appNavController = appNavController
) )
} }
@@ -285,7 +293,7 @@ private fun MediaItemRow(
} }
@Composable @Composable
private fun AccountDropdownMenu(session: SessionManager.Session?) { private fun AccountDropdownMenu(session: SessionManager.Session?, appNavController: NavHostController) {
val expanded = remember { mutableStateOf(false) } val expanded = remember { mutableStateOf(false) }
IconButton( IconButton(
@@ -301,7 +309,7 @@ private fun AccountDropdownMenu(session: SessionManager.Session?) {
if(session?.isAuthorized == true) { if(session?.isAuthorized == true) {
AuthorizedSessionMenuItems(expanded = expanded) AuthorizedSessionMenuItems(expanded = expanded)
} else { } else {
NoSessionMenuItems(expanded = expanded) NoSessionMenuItems(expanded = expanded, appNavController = appNavController)
} }
} }
} }
@@ -318,29 +326,21 @@ private fun AuthorizedSessionMenuItems(expanded: MutableState<Boolean>) {
} }
@Composable @Composable
private fun NoSessionMenuItems(expanded: MutableState<Boolean>) { private fun NoSessionMenuItems(expanded: MutableState<Boolean>, appNavController: NavHostController) {
val context = LocalContext.current val context = LocalContext.current
DropdownMenuItem( DropdownMenuItem(
text = { Text(text = stringResource(id = R.string.action_sign_in)) }, text = { Text(text = stringResource(id = R.string.action_sign_in)) },
onClick = { onClick = {
signInPart1(context) CoroutineScope(Dispatchers.IO).launch {
SessionManager.signInPart1(context) {
appNavController.navigate(MainNavItem.WebLinkView.route.plus("/$it"))
}
}
expanded.value = false expanded.value = false
} }
) )
} }
private fun signInPart1(context: Context) {
CoroutineScope(Dispatchers.IO).launch {
SessionManager.signInPart1(context)
}
}
private fun signInPart2() {
CoroutineScope(Dispatchers.IO).launch {
SessionManager.singInPart2()
}
}
@Composable @Composable
private fun AuthorizedSessionIcon() { private fun AuthorizedSessionIcon() {
val accountDetails = SessionManager.currentSession.value?.accountDetails?.value val accountDetails = SessionManager.currentSession.value?.accountDetails?.value

View File

@@ -0,0 +1,99 @@
package com.owenlejeune.tvtime.ui.screens.main
import android.content.Context
import android.content.Intent
import android.view.View
import android.webkit.WebResourceRequest
import android.webkit.WebSettings
import android.webkit.WebView
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SmallTopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavController
import com.google.accompanist.web.AccompanistWebViewClient
import com.google.accompanist.web.WebView
import com.google.accompanist.web.rememberWebViewState
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun WebLinkView(
url: String,
appNavController: NavController
) {
Scaffold(
topBar = {
SmallTopAppBar(
title = {},
navigationIcon = {
IconButton(
onClick = { appNavController.popBackStack() }
) {
Icon(
imageVector = Icons.Filled.Close,
contentDescription = "Close"
)
}
}
)
}
) {
Box(modifier = Modifier.padding(it)) {
val webViewState = rememberWebViewState(url = url)
WebView(
state = webViewState,
modifier = Modifier.fillMaxSize(),
client = MYmdbWebClient(),
onCreated = {
it.settings.javaScriptCanOpenWindowsAutomatically = true
it.settings.useWideViewPort = false
it.settings.allowContentAccess = true
it.settings.blockNetworkImage = false
it.settings.blockNetworkLoads = false
it.settings.loadsImagesAutomatically = true
it.clipToOutline = false
it.isNestedScrollingEnabled = true
it.settings.displayZoomControls = true
it.settings.setSupportZoom(true)
it.settings.layoutAlgorithm = WebSettings.LayoutAlgorithm.NORMAL
it.isVerticalScrollBarEnabled = true
it.isHorizontalScrollBarEnabled = true
it.settings.domStorageEnabled = true
it.settings.databaseEnabled = true
it.settings.javaScriptEnabled = true
it.settings.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
it.setLayerType(View.LAYER_TYPE_HARDWARE,null)
it.postInvalidate()
it.isClickable = true
}
)
}
}
}
class MYmdbWebClient: AccompanistWebViewClient(), KoinComponent {
private val context: Context by inject()
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
if (request?.url.toString().contains("tvtime")) {
val uri = request!!.url
val intent = Intent(Intent.ACTION_VIEW, uri).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
context.startActivity(intent)
return true
}
return super.shouldOverrideUrlLoading(view, request)
}
}

View File

@@ -30,7 +30,7 @@ class ItemMoveCallback(private val mAdapter: ItemTouchHelperContract): ItemTouch
viewHolder: RecyclerView.ViewHolder, viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder target: RecyclerView.ViewHolder
): Boolean { ): Boolean {
mAdapter.onRowMoved(viewHolder.bindingAdapterPosition, target.bindingAdapterPosition) mAdapter.onRowMoved(viewHolder.adapterPosition, target.adapterPosition)
return true return true
} }

View File

@@ -8,4 +8,5 @@ object NavConstants {
const val SEARCH_TITLE_KEY = "search_title_key" const val SEARCH_TITLE_KEY = "search_title_key"
const val ACCOUNT_KEY = "account_key" const val ACCOUNT_KEY = "account_key"
const val AUTH_REDIRECT_PAGE = "return" const val AUTH_REDIRECT_PAGE = "return"
const val WEB_LINK_KEY = "web_link_key"
} }

View File

@@ -27,6 +27,8 @@ import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
import org.koin.java.KoinJavaComponent.get import org.koin.java.KoinJavaComponent.get
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
object SessionManager: KoinComponent { object SessionManager: KoinComponent {
@@ -69,19 +71,20 @@ object SessionManager: KoinComponent {
} }
} }
suspend fun signInPart1(context: Context) { suspend fun signInPart1(
context: Context,
onRedirect: (url: String) -> Unit
) {
val service = AuthenticationV4Service() val service = AuthenticationV4Service()
val requestTokenResponse = service.createRequestToken(AuthRequestBody(redirect = "app://tvtime.auth.return")) val requestTokenResponse = service.createRequestToken(AuthRequestBody(redirect = "app://tvtime.auth.return"))
if (requestTokenResponse.isSuccessful) { if (requestTokenResponse.isSuccessful) {
requestTokenResponse.body()?.let { ctr -> requestTokenResponse.body()?.let { ctr ->
currentSession.value = InProgressSession(ctr.requestToken) currentSession.value = InProgressSession(ctr.requestToken)
val browserIntent = Intent( val url = context.getString(R.string.tmdb_auth_url, ctr.requestToken)
Intent.ACTION_VIEW, val encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8.toString())
Uri.parse( withContext(Dispatchers.Main) {
context.getString(R.string.tmdb_auth_url, ctr.requestToken) onRedirect(encodedUrl)
) }
)
context.startActivity(browserIntent)
} }
} }
} }