Push Wear users to install app on phone (#3388)

* Push Wear users to install app on phone

 - Push Wear OS users to install the app on their phone if they don't already have it, as the sign in experience is a lot better and less sensitive to errors
 - Allow using 'Advanced' if the user cannot or doesn't want to install the app on their phone

* Fix import

* Add time
This commit is contained in:
Joris Pelgröm 2023-03-03 20:11:14 +01:00 committed by GitHub
parent 4984c9c1a3
commit b508114d75
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 203 additions and 8 deletions

View file

@ -14,6 +14,7 @@
<string name="add_ssid">Add</string>
<string name="add_ssid_name_suggestion">Add %1$s</string>
<string name="add_widget">Add widget</string>
<string name="advanced">Advanced</string>
<string name="all_entities">All entities</string>
<string name="allow">Allow</string>
<string name="always_show_first_view_on_app_start">Always show first view on app start</string>
@ -263,7 +264,9 @@
<string name="input_cloud">Use Home Assistant Cloud</string>
<string name="input_url_hint">https://example.duckdns.org:8123</string>
<string name="input_url">Home Assistant URL</string>
<string name="install">Install</string>
<string name="install_app">Install App on Wear Device</string>
<string name="install_phone_to_continue">Install Home Assistant on your phone to continue</string>
<plurals name="interval_hours">
<item quantity="one">%d hour</item>
<item quantity="other">%d hours</item>
@ -370,9 +373,9 @@
<string name="media_player">Media player</string>
<string name="media_player_widget_desc">Control any media player and see current now playing image</string>
<string name="message_checking">Checking Wear Devices with App</string>
<string name="message_missing_all">The Wear app is missing on your watch, click the button below to install the app.\n\nNote: Currently the Wear OS app requires you to be enrolled in the beta for the phone app. If the button does not work then please join the beta: https://play.google.com/apps/testing/io.homeassistant.companion.android</string>
<string name="message_missing_all">The Wear app is missing on your watch, click the button below to install the app.</string>
<string name="message_no_connected_nodes">No connected Wear devices, please make sure Bluetooth is on and your watch is paired.</string>
<string name="message_some_installed">The Wear app is installed on some of your Wear devices: (%1$s)\n\nClick the button below to install the app on the other devices.\n\nNote: Currently the Wear OS app requires you to be enrolled in the beta for the phone app. If the button does not work then please join the beta: https://play.google.com/apps/testing/io.homeassistant.companion.android</string>
<string name="message_some_installed">The Wear app is installed on some of your Wear devices: (%1$s)\n\nClick the button below to install the app on the other devices.</string>
<string name="missing_command_permission">Please open the Home Assistant app and send the command again in order to grant the proper permissions. You will be taken to a page to either grant the Home Assistant app the permission, or you will need to select Permissions from the details page and then grant the missing permission. For command_bluetooth the name of the permission is Nearby devices. If you are attempting to use command_activity to make a phone call you will also need to grant Phone permissions.</string>
<string name="areas">Areas</string>
<string name="more_entities">More entities</string>

View file

@ -72,6 +72,7 @@
<activity android:name=".onboarding.OnboardingActivity" />
<activity android:name=".onboarding.integration.MobileAppIntegrationActivity" />
<activity android:name=".onboarding.manual.ManualSetupActivity" />
<activity android:name=".onboarding.phoneinstall.PhoneInstallActivity" />
<activity android:name=".complications.ComplicationConfigActivity"
android:exported="true">
<intent-filter>

View file

@ -21,7 +21,7 @@ import com.google.android.gms.wearable.Wearable
import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.onboarding.integration.MobileAppIntegrationActivity
import io.homeassistant.companion.android.onboarding.manual.ManualSetupActivity
import io.homeassistant.companion.android.onboarding.phoneinstall.PhoneInstallActivity
import io.homeassistant.companion.android.util.LoadingView
import kotlinx.coroutines.guava.await
import kotlinx.coroutines.launch
@ -49,6 +49,7 @@ class OnboardingActivity : AppCompatActivity(), OnboardingView {
private lateinit var loadingView: LoadingView
private var phoneSignInAvailable = false
private var phoneInstallOpened = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -69,7 +70,7 @@ class OnboardingActivity : AppCompatActivity(), OnboardingView {
if (phoneSignInAvailable) {
startPhoneSignIn(null)
} else {
startManualSetup()
requestPhoneAppInstall()
}
}
@ -107,9 +108,7 @@ class OnboardingActivity : AppCompatActivity(), OnboardingView {
Wearable.getDataClient(this).removeListener(presenter)
}
private fun startManualSetup() {
startActivity(ManualSetupActivity.newInstance(this))
}
private fun requestPhoneAppInstall() = startActivity(PhoneInstallActivity.newInstance(this))
private fun startPhoneSignIn(instance: HomeAssistantInstance?) {
lifecycleScope.launch {
@ -131,7 +130,7 @@ class OnboardingActivity : AppCompatActivity(), OnboardingView {
if (instance != null) {
presenter.onInstanceClickedWithoutApp(this@OnboardingActivity, instance.url.toString())
} else {
startManualSetup()
requestPhoneAppInstall()
}
} else {
Log.e(TAG, "Unable to open sign in activity on phone", e)
@ -241,6 +240,11 @@ class OnboardingActivity : AppCompatActivity(), OnboardingView {
Log.d(TAG, "requestPhoneSignIn: found ${capabilityInfo.nodes.size} nodes")
phoneSignInAvailable = capabilityInfo.nodes.size > 0
if (!phoneSignInAvailable && !phoneInstallOpened) {
phoneInstallOpened = true
requestPhoneAppInstall()
}
}
override fun onDestroy() {

View file

@ -0,0 +1,85 @@
package io.homeassistant.companion.android.onboarding.phoneinstall
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.wear.activity.ConfirmationActivity
import androidx.wear.remote.interactions.RemoteActivityHelper
import io.homeassistant.companion.android.BuildConfig
import io.homeassistant.companion.android.onboarding.manual.ManualSetupActivity
import io.homeassistant.companion.android.theme.WearAppTheme
import kotlinx.coroutines.guava.await
import kotlinx.coroutines.launch
import io.homeassistant.companion.android.common.R as commonR
class PhoneInstallActivity : AppCompatActivity() {
companion object {
private const val TAG = "PhoneInstallActivity"
fun newInstance(context: Context): Intent {
return Intent(context, PhoneInstallActivity::class.java)
}
}
private lateinit var remoteActivityHelper: RemoteActivityHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
WearAppTheme {
PhoneInstallView(
onInstall = ::openPlayStoreOnPhone,
onRefresh = {
finish() // OnboardingActivity will refresh when resumed
},
onAdvanced = {
startActivity(ManualSetupActivity.newInstance(this@PhoneInstallActivity))
finish()
}
)
}
}
remoteActivityHelper = RemoteActivityHelper(this)
}
private fun openPlayStoreOnPhone() {
lifecycleScope.launch {
var success = true
try {
remoteActivityHelper.startRemoteActivity(
Intent(Intent.ACTION_VIEW).apply {
addCategory(Intent.CATEGORY_DEFAULT)
addCategory(Intent.CATEGORY_BROWSABLE)
data = Uri.parse("https://play.google.com/store/apps/details?id=${BuildConfig.APPLICATION_ID}")
},
null // a Wear device only has one companion device so this is not needed
).await()
} catch (e: Exception) {
Log.e(TAG, "Unable to open remote activity", e)
success = false
}
val confirmation =
Intent(this@PhoneInstallActivity, ConfirmationActivity::class.java).apply {
putExtra(
ConfirmationActivity.EXTRA_ANIMATION_TYPE,
if (success) { ConfirmationActivity.OPEN_ON_PHONE_ANIMATION } else { ConfirmationActivity.FAILURE_ANIMATION }
)
if (success) {
putExtra(ConfirmationActivity.EXTRA_ANIMATION_DURATION_MILLIS, 2000)
}
putExtra(
ConfirmationActivity.EXTRA_MESSAGE,
getString(if (success) { commonR.string.continue_on_phone } else { commonR.string.failed_phone_connection })
)
}
startActivity(confirmation)
}
}
}

View file

@ -0,0 +1,102 @@
package io.homeassistant.companion.android.onboarding.phoneinstall
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material.Button
import androidx.wear.compose.material.ButtonDefaults
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.PositionIndicator
import androidx.wear.compose.material.Scaffold
import androidx.wear.compose.material.Text
import androidx.wear.compose.material.rememberScalingLazyListState
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.home.views.TimeText
import io.homeassistant.companion.android.views.ThemeLazyColumn
import io.homeassistant.companion.android.common.R as commonR
@Composable
fun PhoneInstallView(
onInstall: () -> Unit,
onRefresh: () -> Unit,
onAdvanced: () -> Unit
) {
val scrollState = rememberScalingLazyListState()
Scaffold(
positionIndicator = {
if (scrollState.isScrollInProgress) {
PositionIndicator(scalingLazyListState = scrollState)
}
},
timeText = { TimeText(visible = !scrollState.isScrollInProgress) }
) {
Box(modifier = Modifier.background(MaterialTheme.colors.background)) {
ThemeLazyColumn(state = scrollState) {
item {
Image(
painter = painterResource(R.drawable.app_icon),
contentDescription = null,
modifier = Modifier.size(48.dp)
)
}
item {
Text(
text = stringResource(commonR.string.install_phone_to_continue),
style = MaterialTheme.typography.title3,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth().padding(vertical = 16.dp)
)
}
item {
Button(
onClick = onInstall,
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(commonR.string.install))
}
}
item {
Button(
onClick = onRefresh,
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.secondaryButtonColors()
) {
Text(stringResource(commonR.string.refresh))
}
}
item {
Button(
onClick = onAdvanced,
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp),
colors = ButtonDefaults.secondaryButtonColors()
) {
Text(stringResource(commonR.string.advanced))
}
}
}
}
}
}
@Preview(device = Devices.WEAR_OS_LARGE_ROUND)
@Composable
fun PhoneInstallViewPreview() {
PhoneInstallView(
onInstall = { },
onRefresh = { },
onAdvanced = { }
)
}