Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Merge branch 'main' into feature/compose-webview
  • Loading branch information
TimoPtr committed Jun 23, 2025
commit 19bf32018fa2339cc15966ca850e77fd2eaee554
8 changes: 8 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ jobs:
path: |
**/build/test-results/**/TEST-*.xml

- name: Upload test results
if: failure()
uses: actions/upload-artifact@v4
with:
name: Screenshot test failure HTML report
path: |
**/build/reports/screenshotTest/**

pr_build:
needs: [yamllint, lockfiles]
runs-on: ubuntu-latest
Expand Down
10 changes: 5 additions & 5 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ GEM
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.4.0)
aws-partitions (1.1116.0)
aws-sdk-core (3.225.2)
aws-partitions (1.1119.0)
aws-sdk-core (3.226.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
Expand All @@ -21,7 +21,7 @@ GEM
aws-sdk-kms (1.105.0)
aws-sdk-core (~> 3, >= 3.225.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.189.1)
aws-sdk-s3 (1.190.0)
aws-sdk-core (~> 3, >= 3.225.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
Expand Down Expand Up @@ -57,10 +57,10 @@ GEM
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-em_synchrony (1.0.1)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.1.0)
faraday-multipart (1.1.1)
multipart-post (~> 2.0)
faraday-net_http (1.0.2)
faraday-net_http_persistent (1.2.0)
Expand Down
4 changes: 4 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ android {

experimentalProperties["android.experimental.enableScreenshotTest"] = true

screenshotTests {
imageDifferenceThreshold = 0.00025f // 0.025%
}

firebaseAppDistribution {
serviceCredentialsFile = "firebaseAppDistributionServiceCredentialsFile.json"
releaseNotesFile = "./app/build/outputs/changelogBeta"
Expand Down
114 changes: 68 additions & 46 deletions app/gradle.lockfile

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="#F2F2F2"
android:pathData="M27,21A1,1 0 0,1 26,20A1,1 0 0,1 27,19A1,1 0 0,1 28,20A1,1 0 0,1 27,21M21,21A1,1 0 0,1 20,20A1,1 0 0,1 21,19A1,1 0 0,1 22,20A1,1 0 0,1 21,21M28.12,16.37L30.22,14.27L29.4,13.44L27.09,15.75C26.16,15.28 25.11,15 24,15C22.88,15 21.84,15.28 20.91,15.75L18.6,13.44L17.78,14.27L19.88,16.37C18.14,17.64 17,19.68 17,22V23H31V22C31,19.68 29.86,17.64 28.12,16.37M17,28C17,31.86 20.13,35 24,35A7,7 0 0,0 31,28V24H17V28Z" />
</vector>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_dev_playground_background"/>
<foreground android:drawable="@drawable/ic_launcher_dev_playground_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_dev_playground_foreground" />
</adaptive-icon>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_dev_playground_background"/>
<foreground android:drawable="@drawable/ic_launcher_dev_playground_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_dev_playground_foreground" />
</adaptive-icon>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_dev_playground_background">#639407</color>
</resources>
3 changes: 2 additions & 1 deletion app/src/debug/res/xml/shortcuts.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
tools:ignore="UnusedResources, UnusedAttribute">
<shortcut
android:enabled="true"
android:icon="@drawable/ic_android_debug_bridge"
android:icon="@mipmap/ic_launcher_dev_playground"
android:roundIcon="@mipmap/ic_launcher_dev_playground_round"
android:shortcutId="dev"
android:shortcutShortLabel="@string/dev_playground_label">
<intent
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 25 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@
</intent-filter>


<intent-filter android:autoVerify="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
Expand All @@ -307,8 +307,9 @@
</intent-filter>
</activity>

<activity android:name=".launch.my.MyActivity"
android:exported="true">
<activity android:name=".launch.link.LinkActivity"
android:exported="true"
android:launchMode="singleTask">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
Expand All @@ -319,6 +320,27 @@
android:host="my.home-assistant.io"
android:pathPrefix="/redirect/"/>
</intent-filter>

<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data
android:scheme="https"
android:host="my.home-assistant.io"
android:pathPrefix="/invite/"/>
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data
android:scheme="homeassistant"
android:host="invite" />
</intent-filter>
</activity>

<activity
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.homeassistant.companion.android.launch

import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
Expand Down Expand Up @@ -42,9 +43,19 @@ import kotlinx.coroutines.launch
import retrofit2.HttpException
import timber.log.Timber

private const val EXTRA_SERVER_URL_TO_ONBOARD = "extra_server_url_to_onboard"

@AndroidEntryPoint
class LaunchActivity : AppCompatActivity(), LaunchView {

companion object {
fun newInstance(context: Context, serverUrlToOnboard: String): Intent {
return Intent(context, LaunchActivity::class.java).apply {
putExtra(EXTRA_SERVER_URL_TO_ONBOARD, serverUrlToOnboard)
}
}
}

@Inject
lateinit var presenter: LaunchPresenter

Expand Down Expand Up @@ -74,7 +85,7 @@ class LaunchActivity : AppCompatActivity(), LaunchView {
}
}
}
presenter.onViewReady()
presenter.onViewReady(intent.getStringExtra(EXTRA_SERVER_URL_TO_ONBOARD))
}

override fun displayWebview() {
Expand Down Expand Up @@ -125,8 +136,8 @@ class LaunchActivity : AppCompatActivity(), LaunchView {
overridePendingTransition(0, 0) // Disable activity start/stop animation
}

override fun displayOnBoarding(sessionConnected: Boolean) {
registerActivityResult.launch(OnboardApp.Input())
override fun displayOnBoarding(sessionConnected: Boolean, serverUrlToOnboard: String?) {
registerActivityResult.launch(OnboardApp.Input(url = serverUrlToOnboard))
}

override fun onDestroy() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package io.homeassistant.companion.android.launch

interface LaunchPresenter {

fun onViewReady()
fun onViewReady(serverUrlToOnboard: String? = null)

fun setSessionExpireMillis(value: Long)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@ abstract class LaunchPresenterBase(
internal val mainScope: CoroutineScope = CoroutineScope(Dispatchers.Main + Job())
internal val ioScope: CoroutineScope = CoroutineScope(Dispatchers.IO)

override fun onViewReady() {
override fun onViewReady(serverUrlToOnboard: String?) {
mainScope.launch {
// Remove any invalid servers (incomplete, partly migrated from another device)
serverManager.defaultServers
.filter { serverManager.authenticationRepository(it.id).getSessionState() == SessionState.ANONYMOUS }
.forEach { serverManager.removeServer(it.id) }

try {
if (
if (serverUrlToOnboard != null) {
view.displayOnBoarding(false, serverUrlToOnboard)
} else if (
serverManager.isRegistered() &&
serverManager.authenticationRepository().getSessionState() == SessionState.CONNECTED
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ package io.homeassistant.companion.android.launch
interface LaunchView {
fun displayWebview()

fun displayOnBoarding(sessionConnected: Boolean)
fun displayOnBoarding(sessionConnected: Boolean, serverUrlToOnboard: String? = null)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package io.homeassistant.companion.android.launch.link

import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.BaseActivity
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.common.data.servers.ServerManager
import io.homeassistant.companion.android.common.util.FailFast
import io.homeassistant.companion.android.launch.LaunchActivity
import io.homeassistant.companion.android.settings.server.ServerChooserFragment
import io.homeassistant.companion.android.util.compose.HomeAssistantAppTheme
import io.homeassistant.companion.android.webview.WebViewActivity
import javax.inject.Inject

@AndroidEntryPoint
class LinkActivity : BaseActivity() {

companion object {
private const val EXTRA_URI = "EXTRA_URI"

fun newInstance(context: Context, uri: Uri): Intent {
return Intent(context, LinkActivity::class.java).apply {
putExtra(EXTRA_URI, uri.toString())
}
}
}

@Inject
lateinit var serverManager: ServerManager

@Inject
lateinit var linkHandler: LinkHandler

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// We display the Icon of the app since this screen might be displayed when the user has to choose a server
// before proceeding with the link.
setContent {
LinkActivityScreen()
}

val dataUri = intent?.takeIf { it.action == Intent.ACTION_VIEW }?.data

if (dataUri == null) {
FailFast.fail { "Missing data in caller Intent" }
} else {
when (val destination = linkHandler.handleLink(dataUri)) {
LinkDestination.NoDestination -> finish()
is LinkDestination.Onboarding -> {
startActivity(LaunchActivity.newInstance(this, destination.serverUrl))
finish()
}

is LinkDestination.Webview -> {
navigateTo(destination.path)
}
}
}
}

private fun navigateTo(path: String) {
if (serverManager.defaultServers.size > 1) {
openServerChooser(path)
} else {
startActivity(WebViewActivity.newInstance(context = this, path = path))
finish()
}
}

private fun openServerChooser(path: String) {
supportFragmentManager.setFragmentResultListener(ServerChooserFragment.RESULT_KEY, this) { _, bundle ->
if (bundle.containsKey(ServerChooserFragment.RESULT_SERVER)) {
startActivity(
WebViewActivity.newInstance(
context = this,
path = path,
serverId = bundle.getInt(ServerChooserFragment.RESULT_SERVER),
),
)
finish()
}
supportFragmentManager.clearFragmentResultListener(ServerChooserFragment.RESULT_KEY)
}
ServerChooserFragment().apply {
// To avoid being stuck on an empty screen by mistake we make the dialog not cancelable.
// The counterpart is that it forces the user to select a server.
isCancelable = false
}.show(supportFragmentManager, ServerChooserFragment.TAG)
}
}

@Composable
@VisibleForTesting
fun LinkActivityScreen() {
HomeAssistantAppTheme {
Box(modifier = Modifier.fillMaxSize()) {
Image(
imageVector = ImageVector.vectorResource(R.drawable.app_icon_launch),
contentDescription = null,
modifier = Modifier.size(112.dp).align(Alignment.Center),
)
}
}
}

@Preview
@Composable
private fun LinkActivityScreenPreview() {
LinkActivityScreen()
}
Loading
You are viewing a condensed version of this merge commit. You can view the full changes here.