diff --git a/WearOAuth/oauth-device-grant/src/main/java/com/example/android/wearable/oauth/devicegrant/AuthDeviceGrantActivity.kt b/WearOAuth/oauth-device-grant/src/main/java/com/example/android/wearable/oauth/devicegrant/AuthDeviceGrantActivity.kt index 45776e1e2..6d5f9b47c 100644 --- a/WearOAuth/oauth-device-grant/src/main/java/com/example/android/wearable/oauth/devicegrant/AuthDeviceGrantActivity.kt +++ b/WearOAuth/oauth-device-grant/src/main/java/com/example/android/wearable/oauth/devicegrant/AuthDeviceGrantActivity.kt @@ -23,13 +23,14 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.lifecycle.viewmodel.compose.viewModel import androidx.wear.compose.material3.Button import androidx.wear.compose.material3.ListHeader import androidx.wear.compose.material3.Text +import androidx.wear.compose.ui.tooling.preview.WearPreviewDevices +import androidx.wear.compose.ui.tooling.preview.WearPreviewFontScales import com.google.android.horologist.annotations.ExperimentalHorologistApi import com.google.android.horologist.compose.layout.AppScaffold import com.google.android.horologist.compose.layout.ScalingLazyColumn @@ -59,42 +60,76 @@ class AuthDeviceGrantActivity : ComponentActivity() { } } -@OptIn(ExperimentalHorologistApi::class) @Composable fun AuthenticateApp(deviceGrantViewModel: AuthDeviceGrantViewModel) { AppScaffold { val uiState = deviceGrantViewModel.uiState.collectAsState() - val localContext = LocalContext.current - val columnState = rememberResponsiveColumnState( - contentPadding = ScalingLazyColumnDefaults.padding( - first = ItemType.Text, - last = ItemType.Text - ) + AuthenticateScreen( + uiState.value.statusCode, + uiState.value.resultMessage, + deviceGrantViewModel::startAuthFlow + ) + } +} + +@OptIn(ExperimentalHorologistApi::class) +@Composable +fun AuthenticateScreen( + statusCode: Int, + resultMessage: String, + startAuthFlow: () -> Unit +) { + val columnState = rememberResponsiveColumnState( + contentPadding = ScalingLazyColumnDefaults.padding( + first = ItemType.Text, + last = ItemType.Text ) - ScreenScaffold(scrollState = columnState) { - ScalingLazyColumn(columnState = columnState) { - item { - ListHeader { - Text( - stringResource(R.string.oauth_device_auth_grant), - textAlign = TextAlign.Center - ) - } + ) + ScreenScaffold(scrollState = columnState) { + ScalingLazyColumn(columnState = columnState) { + item { + ListHeader { + Text( + stringResource(R.string.oauth_device_auth_grant), + textAlign = TextAlign.Center + ) } - item { - Button( - onClick = { deviceGrantViewModel.startAuthFlow(localContext) }, - modifier = Modifier.fillMaxSize() - ) { - Text( - text = stringResource(R.string.get_grant_from_phone), - modifier = Modifier.align(Alignment.CenterVertically) - ) - } + } + item { + Button( + onClick = { startAuthFlow() }, + modifier = Modifier.fillMaxSize() + ) { + Text( + text = stringResource(R.string.get_grant_from_phone), + modifier = Modifier.align(Alignment.CenterVertically) + ) } - item { Text(uiState.value.statusCode.toString()) } - item { Text(uiState.value.resultMessage) } } + item { Text(stringResource(id = statusCode)) } + item { Text(resultMessage) } } } } + +@WearPreviewDevices +@WearPreviewFontScales +@Composable +fun AuthenticateScreenPreview() { + AuthenticateScreen( + statusCode = R.string.status_retrieved, + resultMessage = "User name", + startAuthFlow = {} + ) +} + +@WearPreviewDevices +@WearPreviewFontScales +@Composable +fun AuthenticateScreenFailedPreview() { + AuthenticateScreen( + statusCode = R.string.status_failed, + resultMessage = "", + startAuthFlow = {} + ) +} diff --git a/WearOAuth/oauth-device-grant/src/main/java/com/example/android/wearable/oauth/devicegrant/AuthDeviceGrantViewModel.kt b/WearOAuth/oauth-device-grant/src/main/java/com/example/android/wearable/oauth/devicegrant/AuthDeviceGrantViewModel.kt index f0d1a5824..021eb3f91 100644 --- a/WearOAuth/oauth-device-grant/src/main/java/com/example/android/wearable/oauth/devicegrant/AuthDeviceGrantViewModel.kt +++ b/WearOAuth/oauth-device-grant/src/main/java/com/example/android/wearable/oauth/devicegrant/AuthDeviceGrantViewModel.kt @@ -15,11 +15,12 @@ */ package com.example.android.wearable.oauth.devicegrant +import android.app.Application import android.content.Context import android.content.Intent import android.net.Uri import android.util.Log -import androidx.lifecycle.ViewModel +import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import androidx.wear.remote.interactions.RemoteActivityHelper import com.example.android.wearable.oauth.util.doGetRequest @@ -29,6 +30,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch private const val TAG = "AuthDeviceGrantViewModel" @@ -38,7 +40,7 @@ private const val CLIENT_ID = "" private const val CLIENT_SECRET = "" data class DeviceGrantState( - val statusCode: Int = 0, + val statusCode: Int = R.string.oauth_device_authorization_default, val resultMessage: String = "" ) @@ -48,16 +50,19 @@ data class DeviceGrantState( * different steps of the flow. It first retrieves the URL that should be opened on the paired * device, then polls for the access token, and uses it to retrieve the user's name. */ -class AuthDeviceGrantViewModel : ViewModel() { +class AuthDeviceGrantViewModel(application: Application) : AndroidViewModel(application) { + private val context = getApplication().applicationContext private val _uiState = MutableStateFlow(DeviceGrantState()) + val uiState: StateFlow = _uiState.asStateFlow() private fun showStatus(statusString: Int = 0, resultString: String = "") { - _uiState.value = - _uiState.value.copy(statusCode = statusString, resultMessage = resultString) + _uiState.update { + DeviceGrantState(statusString, resultString) + } } - fun startAuthFlow(context: Context) { + fun startAuthFlow() { viewModelScope.launch { // Step 1: Retrieve the verification URI showStatus(statusString = R.string.status_switch_to_phone) diff --git a/WearOAuth/oauth-device-grant/src/main/res/values/strings.xml b/WearOAuth/oauth-device-grant/src/main/res/values/strings.xml index 40da32a0f..26a099ece 100644 --- a/WearOAuth/oauth-device-grant/src/main/res/values/strings.xml +++ b/WearOAuth/oauth-device-grant/src/main/res/values/strings.xml @@ -17,6 +17,7 @@ Authenticate OAuth Device Authorization Grant + Not authorized Starting authorization… Switch to your phone to authenticate. Authorization failed "Code: "