Skip to content

Commit

Permalink
Add performAction function to SignInPrompt (google#2076)
Browse files Browse the repository at this point in the history
  • Loading branch information
luizgrp authored and kul3r4 committed Feb 29, 2024
1 parent 268fad5 commit d0f4e2f
Show file tree
Hide file tree
Showing 10 changed files with 335 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,10 @@ internal class SignInBottomSheetActivity : ComponentActivity() {
// Can't use the Activity's lifecycleScope as it is going to finish the activity immediately
// after this call
coroutineAppScope.launch {
phoneDataLayerAppHelper.startRemoteOwnApp(nodeId = nodeId)
SignInPromptAction.run(
phoneDataLayerAppHelper = phoneDataLayerAppHelper,
nodeId = nodeId,
)
}

// It returns OK to indicate that the user tapped on the positive button.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,14 @@ public class SignInPrompt(
positiveButtonLabel = positiveButtonLabel,
negativeButtonLabel = negativeButtonLabel,
)

/**
* Performs the same action taken by the prompt when the user taps on the positive button.
*/
public suspend fun performAction(nodeId: String) {
SignInPromptAction.run(
phoneDataLayerAppHelper = phoneDataLayerAppHelper,
nodeId = nodeId,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.horologist.datalayer.phone.ui.prompt.signin

import com.google.android.horologist.datalayer.phone.PhoneDataLayerAppHelper

internal object SignInPromptAction {
suspend fun run(phoneDataLayerAppHelper: PhoneDataLayerAppHelper, nodeId: String) =
phoneDataLayerAppHelper.startRemoteOwnApp(nodeId = nodeId)
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ sealed class Screen(
data object ReEngagePromptDemoScreen : Screen("reEngagePromptDemoScreen")
data object SignInPromptDemoScreen : Screen("signInPromptDemoScreen")
data object InstallAppCustomPromptDemoScreen : Screen("installAppCustomPromptDemoScreen")
data object SignInCustomPromptDemoScreen : Screen("signInCustomPromptDemoScreen")
data object CounterScreen : Screen("counterScreen")
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.horologist.datalayer.sample.screens.inappprompts.custom.signin

import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Watch
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.android.horologist.datalayer.sample.R

@Composable
fun SignInCustomPromptDemoScreen(
modifier: Modifier = Modifier,
viewModel: SignInCustomPromptDemoViewModel = hiltViewModel(),
) {
val state by viewModel.uiState.collectAsStateWithLifecycle()

if (state == SignInCustomPromptDemoScreenState.Idle) {
SideEffect {
viewModel.initialize()
}
}

SignInCustomPromptDemoScreen(
state = state,
onRunDemoClick = viewModel::onRunDemoClick,
onPromptSignInClick = viewModel::onPromptSignInClick,
onPromptDismiss = viewModel::onPromptDismiss,
modifier = modifier,
)
}

@Composable
fun SignInCustomPromptDemoScreen(
state: SignInCustomPromptDemoScreenState,
onRunDemoClick: () -> Unit,
onPromptSignInClick: (nodeId: String) -> Unit,
onPromptDismiss: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier.padding(all = 10.dp),
) {
Text(text = stringResource(id = R.string.signin_custom_prompt_api_call_demo_message))

Button(
onClick = onRunDemoClick,
modifier = Modifier
.padding(top = 10.dp)
.align(Alignment.CenterHorizontally),
enabled = state != SignInCustomPromptDemoScreenState.ApiNotAvailable,
) {
Text(text = stringResource(id = R.string.signin_custom_prompt_run_demo_button_label))
}

when (state) {
SignInCustomPromptDemoScreenState.Idle,
SignInCustomPromptDemoScreenState.Loaded,
-> {
/* do nothing */
}

SignInCustomPromptDemoScreenState.Loading -> {
CircularProgressIndicator()
}

is SignInCustomPromptDemoScreenState.WatchFound -> {
Row(
modifier = Modifier
.padding(top = 30.dp)
.border(width = 1.dp, color = MaterialTheme.colorScheme.onSurface)
.padding(horizontal = 20.dp, vertical = 10.dp),
) {
Icon(
imageVector = Icons.Default.Watch,
contentDescription = null,
modifier = Modifier.padding(top = 20.dp, end = 20.dp),
)
Column {
Text(text = stringResource(id = R.string.signin_custom_prompt_demo_prompt_top_message))
Text(text = stringResource(id = R.string.signin_custom_prompt_demo_prompt_bottom_message))
Row {
TextButton(
onClick = onPromptDismiss,
) {
Text(text = stringResource(id = R.string.signin_custom_prompt_demo_prompt_dismiss_button_label))
}

Spacer(modifier = Modifier.weight(1f))

TextButton(
onClick = { onPromptSignInClick(state.nodeId) },
) {
Text(text = stringResource(id = R.string.signin_custom_prompt_demo_prompt_confirm_button_label))
}
}
}
}
}

SignInCustomPromptDemoScreenState.WatchNotFound -> {
Text(
stringResource(
id = R.string.signin_custom_prompt_demo_result_label,
stringResource(id = R.string.signin_custom_prompt_demo_no_watches_found_label),
),
)
}

SignInCustomPromptDemoScreenState.PromptSignInClicked -> {
Text(
stringResource(
id = R.string.signin_custom_prompt_demo_result_label,
stringResource(id = R.string.signin_custom_prompt_demo_prompt_positive_result_label),
),
)
}

SignInCustomPromptDemoScreenState.PromptDismissed -> {
Text(
stringResource(
id = R.string.signin_custom_prompt_demo_result_label,
stringResource(id = R.string.signin_custom_prompt_demo_prompt_dismiss_result_label),
),
)
}

SignInCustomPromptDemoScreenState.ApiNotAvailable -> {
Text(stringResource(id = R.string.wearable_message_api_unavailable))
}
}
}
}

@Preview(showBackground = true)
@Composable
fun SignInCustomPromptDemoScreenPreview() {
SignInCustomPromptDemoScreen(
state = SignInCustomPromptDemoScreenState.WatchFound("nodeId"),
onRunDemoClick = { },
onPromptSignInClick = { },
onPromptDismiss = { },
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.horologist.datalayer.sample.screens.inappprompts.custom.signin

import androidx.annotation.MainThread
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.android.horologist.datalayer.phone.PhoneDataLayerAppHelper
import com.google.android.horologist.datalayer.phone.ui.prompt.signin.SignInPrompt
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class SignInCustomPromptDemoViewModel
@Inject
constructor(
private val phoneDataLayerAppHelper: PhoneDataLayerAppHelper,
val signInCustomPrompt: SignInPrompt,
) : ViewModel() {

private var initializeCalled = false

private val _uiState =
MutableStateFlow<SignInCustomPromptDemoScreenState>(SignInCustomPromptDemoScreenState.Idle)
public val uiState: StateFlow<SignInCustomPromptDemoScreenState> = _uiState

@MainThread
fun initialize() {
if (initializeCalled) return
initializeCalled = true

_uiState.value = SignInCustomPromptDemoScreenState.Loading

viewModelScope.launch {
if (!phoneDataLayerAppHelper.isAvailable()) {
_uiState.value = SignInCustomPromptDemoScreenState.ApiNotAvailable
} else {
_uiState.value = SignInCustomPromptDemoScreenState.Loaded
}
}
}

fun onRunDemoClick() {
_uiState.value = SignInCustomPromptDemoScreenState.Loading

viewModelScope.launch {
val node = signInCustomPrompt.shouldDisplayPrompt()

_uiState.value = if (node != null) {
SignInCustomPromptDemoScreenState.WatchFound(node.id)
} else {
SignInCustomPromptDemoScreenState.WatchNotFound
}
}
}

fun onPromptSignInClick(nodeId: String) {
viewModelScope.launch {
signInCustomPrompt.performAction(nodeId = nodeId)
}

_uiState.value = SignInCustomPromptDemoScreenState.PromptSignInClicked
}

fun onPromptDismiss() {
_uiState.value = SignInCustomPromptDemoScreenState.PromptDismissed
}
}

sealed class SignInCustomPromptDemoScreenState {
data object Idle : SignInCustomPromptDemoScreenState()
data object Loading : SignInCustomPromptDemoScreenState()
data object Loaded : SignInCustomPromptDemoScreenState()
data class WatchFound(val nodeId: String) : SignInCustomPromptDemoScreenState()
data object WatchNotFound : SignInCustomPromptDemoScreenState()
data object PromptSignInClicked : SignInCustomPromptDemoScreenState()
data object PromptDismissed : SignInCustomPromptDemoScreenState()
data object ApiNotAvailable : SignInCustomPromptDemoScreenState()
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import androidx.navigation.compose.rememberNavController
import com.google.android.horologist.datalayer.sample.screens.Screen
import com.google.android.horologist.datalayer.sample.screens.counter.CounterScreen
import com.google.android.horologist.datalayer.sample.screens.inappprompts.custom.installapp.InstallAppCustomPromptDemoScreen
import com.google.android.horologist.datalayer.sample.screens.inappprompts.custom.signin.SignInCustomPromptDemoScreen
import com.google.android.horologist.datalayer.sample.screens.inappprompts.installapp.InstallAppPromptDemoScreen
import com.google.android.horologist.datalayer.sample.screens.inappprompts.reengage.ReEngagePromptDemoScreen
import com.google.android.horologist.datalayer.sample.screens.inappprompts.signin.SignInPromptDemoScreen
Expand Down Expand Up @@ -79,6 +80,9 @@ fun MainScreen(
composable(route = Screen.InstallAppCustomPromptDemoScreen.route) {
InstallAppCustomPromptDemoScreen()
}
composable(route = Screen.SignInCustomPromptDemoScreen.route) {
SignInCustomPromptDemoScreen()
}
composable(route = Screen.CounterScreen.route) {
CounterScreen()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ fun MenuScreen(
Text(text = stringResource(id = R.string.menu_screen_install_app_custom_demo_item))
}

Button(onClick = { navController.navigate(Screen.SignInCustomPromptDemoScreen.route) }) {
Text(text = stringResource(id = R.string.menu_screen_signin_custom_demo_item))
}

Text(
text = stringResource(id = R.string.menu_screen_datalayer_header),
modifier = Modifier.padding(top = 10.dp),
Expand Down
Loading

0 comments on commit d0f4e2f

Please sign in to comment.