diff --git a/app/src/main/java/energy/octopus/octopusenergy/teslaandroidauth/MainActivity.kt b/app/src/main/java/energy/octopus/octopusenergy/teslaandroidauth/MainActivity.kt index 5e45a7f..f405ff5 100644 --- a/app/src/main/java/energy/octopus/octopusenergy/teslaandroidauth/MainActivity.kt +++ b/app/src/main/java/energy/octopus/octopusenergy/teslaandroidauth/MainActivity.kt @@ -7,6 +7,7 @@ import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -25,29 +26,34 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { - var message by remember { mutableStateOf(null) } - if (message == null) { - TeslAuth( - logLevel = LogLevel.DEFAULT, - onAuthorizationCodeReceived = { - Log.i(TAG, "Authorization Code: $it") - }, - onBearerTokenReceived = { - Log.i(TAG, "Bearer token: $it") - }, - onAccessTokenReceived = { - message = it.toString() - }, onError = { - message = it.toString() - }) - } else { - Box(Modifier.padding(16.dp)) { - Text( - message ?: "", - Modifier - .wrapContentSize() - .align(Alignment.Center) - ) + MaterialTheme { + var message by remember { mutableStateOf(null) } + if (message == null) { + TeslAuth( + logLevel = LogLevel.DEFAULT, + onAuthorizationCodeReceived = { + Log.i(TAG, "Authorization Code: $it") + }, + onBearerTokenReceived = { + Log.i(TAG, "Bearer token: $it") + }, + onAccessTokenReceived = { + message = it.toString() + }, onError = { + message = it.toString() + }, onDismiss = { + message = "Dismissed" + }) + } else { + Box(Modifier.padding(16.dp)) { + Text( + text = message ?: "", + modifier = Modifier + .wrapContentSize() + .align(Alignment.Center), + style = MaterialTheme.typography.body2.copy(color = MaterialTheme.colors.onPrimary) + ) + } } } } diff --git a/build.gradle b/build.gradle index 206b57b..17c99d3 100644 --- a/build.gradle +++ b/build.gradle @@ -22,4 +22,4 @@ plugins { apply from: "${rootDir}/scripts/publish-root.gradle" group = "energy.octopus" -version = "0.1.2" \ No newline at end of file +version = "0.1.2ยก" \ No newline at end of file diff --git a/teslauth/src/main/java/energy/octopus/octopusenergy/teslauth/TeslaAuth.kt b/teslauth/src/main/java/energy/octopus/octopusenergy/teslauth/TeslaAuth.kt index 0c0dca0..eeaa766 100644 --- a/teslauth/src/main/java/energy/octopus/octopusenergy/teslauth/TeslaAuth.kt +++ b/teslauth/src/main/java/energy/octopus/octopusenergy/teslauth/TeslaAuth.kt @@ -40,6 +40,7 @@ typealias OnTokenReceivedCallback = (AuthToken) -> Unit * @param onAccessTokenReceived callback called with the [AuthToken] as parameter if getting the authorization token was successful, * @see https://tesla-api.timdorr.com/api-basics/authentication#refreshing-an-access-token * @param onError callback called with the [Throwable] that occurred when trying to get the authorization token + * @param onDismiss called when the underlying [WebView] can't go back and the back press represents a dismiss of the Authentication WebView * @param loadingIndicator optional composable, default is [CircularProgressIndicator] */ @@ -51,6 +52,7 @@ fun TeslAuth( onBearerTokenReceived: OnTokenReceivedCallback? = null, onAccessTokenReceived: OnTokenReceivedCallback? = null, onError: (Throwable) -> Unit = {}, + onDismiss: () -> Unit = {}, loadingIndicator: @Composable BoxScope.() -> Unit = { CircularProgressIndicator( Modifier.align( @@ -67,15 +69,9 @@ fun TeslAuth( WebAuth( url = viewModel.url, modifier = modifier.fillMaxSize(), - onCodeReceived = { - if (it != null) { - onAuthorizationCodeReceived?.invoke(it) - if (onBearerTokenReceived != null || onAccessTokenReceived != null) { - viewModel.getBearerToken(it) - } - } - }, + onCodeReceived = viewModel::onCodeReceived, onPageLoaded = viewModel::onPageLoaded, + onDismiss = onDismiss, ) if (isLoading) { loadingIndicator() @@ -93,6 +89,10 @@ fun TeslAuth( } is ReceivedAccessToken -> onAccessTokenReceived?.invoke(it.token) is Error -> onError(it.t) + is AuthorizationCodeReceived -> if (onBearerTokenReceived != null || onAccessTokenReceived != null) { + viewModel.getBearerToken(it.code) + } + Dismiss -> onDismiss() } }.launchIn(this) } @@ -104,6 +104,7 @@ private fun WebAuth( modifier: Modifier = Modifier, onCodeReceived: (String?) -> Unit = {}, onPageLoaded: () -> Unit = {}, + onDismiss: () -> Unit = {}, ) { AndroidView( modifier = modifier, @@ -128,16 +129,16 @@ private fun WebAuth( Logger.log("User agent: ${settings.userAgentString}") Logger.log("Opening url for login: $url") loadUrl(url) - setBackListener() + setBackListener(onDismiss = onDismiss) } } ) } -private fun WebView.setBackListener() { +private fun WebView.setBackListener(onDismiss: () -> Unit = {}) { setOnKeyListener { _, keyCode, keyEvent -> - if (keyEvent.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_BACK && canGoBack()) { - goBack() + if (keyEvent.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_BACK) { + onDismiss() true } else { false diff --git a/teslauth/src/main/java/energy/octopus/octopusenergy/teslauth/TeslaAuthViewModel.kt b/teslauth/src/main/java/energy/octopus/octopusenergy/teslauth/TeslaAuthViewModel.kt index 265c5af..9ef4742 100644 --- a/teslauth/src/main/java/energy/octopus/octopusenergy/teslauth/TeslaAuthViewModel.kt +++ b/teslauth/src/main/java/energy/octopus/octopusenergy/teslauth/TeslaAuthViewModel.kt @@ -3,6 +3,7 @@ package energy.octopus.octopusenergy.teslauth import android.net.Uri import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import energy.octopus.octopusenergy.teslauth.TeslaAuthViewModel.Event.* import energy.octopus.octopusenergy.teslauth.api.TeslaApi import energy.octopus.octopusenergy.teslauth.logging.Logger import energy.octopus.octopusenergy.teslauth.model.AccessTokenRequest @@ -54,7 +55,7 @@ internal class TeslaAuthViewModel(private val api: TeslaApi = TeslaApi()) : View ) Logger.log("Got bearer token $bearerTokenResponse") _event.emit( - Event.ReceivedBearerToken( + ReceivedBearerToken( AuthToken( accessToken = bearerTokenResponse.accessToken, refreshToken = bearerTokenResponse.refreshToken, @@ -64,7 +65,7 @@ internal class TeslaAuthViewModel(private val api: TeslaApi = TeslaApi()) : View ) ) } catch (t: Throwable) { - _event.emit(Event.Error(t)) + _event.emit(Error(t)) } finally { _isLoading.value = false } @@ -84,7 +85,7 @@ internal class TeslaAuthViewModel(private val api: TeslaApi = TeslaApi()) : View ) Logger.log("Got auth token $authTokenResponse") _event.emit( - Event.ReceivedAccessToken( + ReceivedAccessToken( AuthToken( accessToken = authTokenResponse.accessToken, refreshToken = authTokenResponse.refreshToken, @@ -94,19 +95,36 @@ internal class TeslaAuthViewModel(private val api: TeslaApi = TeslaApi()) : View ) ) } catch (t: Throwable) { - _event.emit(Event.Error(t)) + _event.emit(Error(t)) } finally { _isLoading.value = false } } } + fun onCodeReceived(code: String?) { + code ?: return + + viewModelScope.launch { + val error = code.substringAfter("error", missingDelimiterValue = "") + _event.emit( + if (error.isNotBlank()) { + if (error.contains("cancelled", ignoreCase = true)) Dismiss + else Error(IllegalStateException("Auth failed with: $error")) + } else AuthorizationCodeReceived(code) + ) + } + } + + fun onPageLoaded() { _isLoading.value = false } sealed class Event { + object Dismiss : Event() data class Error(val t: Throwable) : Event() + data class AuthorizationCodeReceived(val code: String) : Event() data class ReceivedBearerToken(val token: AuthToken) : Event() data class ReceivedAccessToken(val token: AuthToken) : Event() }