Skip to content

Commit

Permalink
Migrate onboarding flow to View Model and Activity Results (#1995)
Browse files Browse the repository at this point in the history
* DiscoveryView now on viewmodels!

* Migrate to OnboardingViewModel

* ManualSetup now on viewmodels!

* Auth view now on view model.

* Flow working but no persisting.... yet!

* Functional manual onboarding.

* Fix clicking on actual discovered instance.

* Fix discovery not authing
Fix logout causing issues.

* Fix firebase token issues
Theme is less hard to see for switch

* Cleanup DiscoveryFragment.kt to keep crashes from crashing.

* ktlint

* Fix minimal build.

* Really fix minimal build.

* Fix issue with device name being broken.

* Make sure location toggle stays active when selected.
  • Loading branch information
JBassett authored Dec 10, 2021
1 parent bacfb9a commit 4a501bd
Show file tree
Hide file tree
Showing 40 changed files with 828 additions and 1,032 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.homeassistant.companion.android.onboarding

import android.util.Log
import com.google.firebase.messaging.FirebaseMessaging
import kotlinx.coroutines.tasks.await

suspend fun getMessagingToken(): String {
return try {
FirebaseMessaging.getInstance().token.await()
} catch (e: Exception) {
Log.e("MessagingToken", "Issue getting token", e)
""
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,20 +1,74 @@
package io.homeassistant.companion.android.launch

import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.google.android.material.composethemeadapter.MdcTheme
import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.BaseActivity
import io.homeassistant.companion.android.BuildConfig
import io.homeassistant.companion.android.common.data.authentication.AuthenticationRepository
import io.homeassistant.companion.android.common.data.integration.DeviceRegistration
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
import io.homeassistant.companion.android.common.data.url.UrlRepository
import io.homeassistant.companion.android.database.AppDatabase
import io.homeassistant.companion.android.database.sensor.Sensor
import io.homeassistant.companion.android.onboarding.OnboardingActivity
import io.homeassistant.companion.android.onboarding.getMessagingToken
import io.homeassistant.companion.android.sensors.LocationSensorManager
import io.homeassistant.companion.android.webview.WebViewActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import javax.inject.Inject
import io.homeassistant.companion.android.common.R as commonR

@AndroidEntryPoint
class LaunchActivity : BaseActivity(), LaunchView {
class LaunchActivity : AppCompatActivity(), LaunchView {

companion object {
const val TAG = "LaunchActivity"
}

@Inject
lateinit var presenter: LaunchPresenter

@Inject
lateinit var urlRepository: UrlRepository

@Inject
lateinit var authenticationRepository: AuthenticationRepository

@Inject
lateinit var integrationRepository: IntegrationRepository

private val mainScope = CoroutineScope(Dispatchers.Main + Job())

private val registerActivityResult =
registerForActivityResult(
ActivityResultContracts.StartActivityForResult(),
this::onOnboardingComplete
)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Box(modifier = Modifier.fillMaxSize()) {
MdcTheme {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center)
)
}
}
}
presenter.onViewReady()
}

Expand All @@ -28,15 +82,86 @@ class LaunchActivity : BaseActivity(), LaunchView {

override fun displayOnBoarding(sessionConnected: Boolean) {
val intent = OnboardingActivity.newInstance(this)
intent.putExtra(OnboardingActivity.SESSION_CONNECTED, sessionConnected)

startActivity(intent)
finish()
overridePendingTransition(0, 0) // Disable activity start/stop animation
registerActivityResult.launch(intent)
}

override fun onDestroy() {
presenter.onFinish()
super.onDestroy()
}

private fun onOnboardingComplete(result: ActivityResult) {
mainScope.launch {
val intent = result.data!!
val url = intent.getStringExtra("URL").toString()
val authCode = intent.getStringExtra("AuthCode").toString()
val deviceName = intent.getStringExtra("DeviceName").toString()
val deviceTrackingEnabled = intent.getBooleanExtra("LocationTracking", false)
val messagingToken = getMessagingToken()
if (messagingToken.isNullOrBlank()) {
AlertDialog.Builder(this@LaunchActivity)
.setTitle(commonR.string.firebase_error_title)
.setMessage(commonR.string.firebase_error_message)
.setPositiveButton(commonR.string.skip) { _, _ ->
mainScope.launch {
registerAndOpenWebview(
url,
authCode,
deviceName,
messagingToken,
deviceTrackingEnabled
)
}
}
.show()
} else {
registerAndOpenWebview(
url,
authCode,
deviceName,
messagingToken,
deviceTrackingEnabled
)
}
}
}

private suspend fun registerAndOpenWebview(
url: String,
authCode: String,
deviceName: String,
messagingToken: String,
deviceTrackingEnabled: Boolean
) {
urlRepository.saveUrl(url)
authenticationRepository.registerAuthorizationCode(authCode)
integrationRepository.registerDevice(
DeviceRegistration(
"${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})",
deviceName,
messagingToken
)
)
setLocationTracking(deviceTrackingEnabled)
displayWebview()
}

private fun setLocationTracking(enabled: Boolean) {
val sensorDao = AppDatabase.getInstance(applicationContext).sensorDao()
arrayOf(
LocationSensorManager.backgroundLocation,
LocationSensorManager.zoneLocation,
LocationSensorManager.singleAccurateLocation
).forEach { basicSensor ->
var sensorEntity = sensorDao.get(basicSensor.id)
if (sensorEntity != null) {
sensorEntity.enabled = enabled
sensorEntity.lastSentState = ""
sensorDao.update(sensorEntity)
} else {
sensorEntity = Sensor(basicSensor.id, enabled, false, "")
sensorDao.add(sensorEntity)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,14 @@ import android.content.Intent
import android.os.Bundle
import android.view.KeyEvent
import androidx.appcompat.app.AppCompatActivity
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.android.components.ActivityComponent
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.onboarding.authentication.AuthenticationFragment
import io.homeassistant.companion.android.onboarding.authentication.AuthenticationListener
import io.homeassistant.companion.android.onboarding.discovery.DiscoveryListener
import io.homeassistant.companion.android.onboarding.integration.MobileAppIntegrationFragment
import io.homeassistant.companion.android.onboarding.integration.MobileAppIntegrationListener
import io.homeassistant.companion.android.onboarding.manual.ManualSetupFragment
import io.homeassistant.companion.android.onboarding.manual.ManualSetupListener
import io.homeassistant.companion.android.webview.WebViewActivity
import io.homeassistant.companion.android.onboarding.welcome.WelcomeFragment

@AndroidEntryPoint
class OnboardingActivity :
AppCompatActivity(),
DiscoveryListener,
ManualSetupListener,
AuthenticationListener,
MobileAppIntegrationListener {
class OnboardingActivity : AppCompatActivity() {

companion object {
const val SESSION_CONNECTED = "is_registered"
private const val AUTHENTICATION_FRAGMENT = "authentication_fragment"
private const val TAG = "OnboardingActivity"

Expand All @@ -39,24 +22,13 @@ class OnboardingActivity :
}

override fun onCreate(savedInstanceState: Bundle?) {
val entryPoint = EntryPointAccessors.fromActivity(this, OnboardingFragmentFactoryEntryPoint::class.java)
supportFragmentManager.fragmentFactory = entryPoint.getFragmentFactory()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_onboarding)

val sessionConnected = intent.extras?.getBoolean(SESSION_CONNECTED) ?: false

if (sessionConnected) {
supportFragmentManager
.beginTransaction()
.add(R.id.content, MobileAppIntegrationFragment::class.java, null)
.commit()
} else {
supportFragmentManager
.beginTransaction()
.add(R.id.content, WelcomeFragment::class.java, null)
.commit()
}
supportFragmentManager
.beginTransaction()
.add(R.id.content, WelcomeFragment::class.java, null)
.commit()
}

override fun onBackPressed() {
Expand All @@ -67,47 +39,6 @@ class OnboardingActivity :
}
}

override fun onSelectManualSetup() {
supportFragmentManager
.beginTransaction()
.replace(R.id.content, ManualSetupFragment::class.java, null)
.addToBackStack(null)
.commit()
}

override fun onHomeAssistantDiscover() {
supportFragmentManager
.beginTransaction()
.replace(R.id.content, AuthenticationFragment::class.java, null)
.addToBackStack(null)
.commit()
}

override fun onSelectUrl() {
supportFragmentManager
.beginTransaction()
.replace(R.id.content, AuthenticationFragment::class.java, null)
.addToBackStack(null)
.commit()
}

override fun onAuthenticationSuccess() {
supportFragmentManager
.beginTransaction()
.replace(R.id.content, MobileAppIntegrationFragment::class.java, null)
.addToBackStack(null)
.commit()
}

override fun onIntegrationRegistrationComplete() {
startWebView()
}

private fun startWebView() {
startActivity(WebViewActivity.newInstance(this))
finish()
}

override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
// Temporary workaround to sideload on Android TV and use a remote for basic navigation in WebView
val fragmentManager = supportFragmentManager.findFragmentByTag(AUTHENTICATION_FRAGMENT)
Expand All @@ -120,10 +51,4 @@ class OnboardingActivity :

return super.dispatchKeyEvent(event)
}

@EntryPoint
@InstallIn(ActivityComponent::class)
interface OnboardingFragmentFactoryEntryPoint {
fun getFragmentFactory(): OnboardingFragmentFactory
}
}

This file was deleted.

Loading

0 comments on commit 4a501bd

Please sign in to comment.