Code formatting and linter (#73)

* add ktlint to the project

* Auto format and fix linter issues

* Add Quality section to README
This commit is contained in:
Marcio Granzotto Rodrigues 2019-12-01 05:05:37 -03:00 committed by Cedrick Flocon
parent cc35db99e4
commit 7d7fc0bd6e
77 changed files with 261 additions and 225 deletions

View file

@ -7,6 +7,7 @@ export VERSION_CODE=`git rev-list --count HEAD`
./gradlew test
./gradlew lint
./gradlew ktlintCheck
if [ "$TRAVIS_PULL_REQUEST" = "false" ]
then

View file

@ -18,4 +18,18 @@
## Continuous Integration
We are using [Travis](https://travis-ci.com/home-assistant/home-assistant-android) to perform continuous integration both by unit testing and deploying to [Firebase App Distribution](https://appdistribution.firebase.dev/i/8zf5W4zz) or [Play Store](https://play.google.com/store/apps/details?id=io.homeassistant.companion.android) when we add a git tag.
We are using [Travis](https://travis-ci.com/home-assistant/home-assistant-android) to perform continuous integration both by unit testing and deploying to [Firebase App Distribution](https://appdistribution.firebase.dev/i/8zf5W4zz) or [Play Store](https://play.google.com/store/apps/details?id=io.homeassistant.companion.android) when we add a git tag.
## Quality
We are using [ktlint](https://ktlint.github.io/) as our linter.
You can run a check locally on your machine with:
```bash
./gradlew ktlintCheck
```
This commands runs on our CI to check if your PR passes all tests. So we strongly recommend running it before committing.
To run a check with an auto-format:
```bash
./gradlew ktlintFormat
```

View file

@ -32,7 +32,7 @@ android {
firebaseAppDistribution {
serviceCredentialsFile = "firebaseAppDistributionServiceCredentialsFile.json"
releaseNotesFile = "CHANGES.md"
groups="continuous-deployment"
groups = "continuous-deployment"
}
signingConfigs {

View file

@ -11,11 +11,11 @@
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.HomeAssistant"
android:usesCleartextTraffic="true"
tools:ignore="GoogleAppIndexingWarning"
android:roundIcon="@mipmap/ic_launcher_round">
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".launch.LaunchActivity">
<intent-filter>

View file

@ -23,5 +23,4 @@ class HomeAssistantApplication : Application(), GraphComponentAccessor {
override fun urlUpdated() {
graph.urlUpdated()
}
}
}

View file

@ -20,5 +20,4 @@ interface PresenterComponent {
fun inject(fragment: MobileAppIntegrationFragment)
fun inject(activity: WebViewActivity)
}
}

View file

@ -80,7 +80,5 @@ class PresenterModule {
@Binds
fun bindWebViewPresenterImpl(presenter: WebViewPresenterImpl): WebViewPresenter
}
}

View file

@ -9,10 +9,10 @@ import io.homeassistant.companion.android.onboarding.OnboardingActivity
import io.homeassistant.companion.android.webview.WebViewActivity
import javax.inject.Inject
class LaunchActivity : AppCompatActivity(), LaunchView {
@Inject lateinit var presenter: LaunchPresenter
@Inject
lateinit var presenter: LaunchPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -41,4 +41,4 @@ class LaunchActivity : AppCompatActivity(), LaunchView {
presenter.onFinish()
super.onDestroy()
}
}
}

View file

@ -1,10 +1,8 @@
package io.homeassistant.companion.android.launch
interface LaunchPresenter {
fun onViewReady()
fun onFinish()
}
}

View file

@ -2,8 +2,12 @@ package io.homeassistant.companion.android.launch
import io.homeassistant.companion.android.domain.authentication.AuthenticationUseCase
import io.homeassistant.companion.android.domain.authentication.SessionState
import kotlinx.coroutines.*
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
class LaunchPresenterImpl @Inject constructor(
private val view: LaunchView,
@ -25,5 +29,4 @@ class LaunchPresenterImpl @Inject constructor(
override fun onFinish() {
mainScope.cancel()
}
}
}

View file

@ -1,10 +1,8 @@
package io.homeassistant.companion.android.launch
interface LaunchView {
fun displayWebview()
fun displayOnBoarding()
}

View file

@ -15,8 +15,8 @@ import io.homeassistant.companion.android.onboarding.manual.ManualSetupFragment
import io.homeassistant.companion.android.onboarding.manual.ManualSetupListener
import io.homeassistant.companion.android.webview.WebViewActivity
class OnboardingActivity : AppCompatActivity(), DiscoveryListener, ManualSetupListener, AuthenticationListener, MobileAppIntegrationListener {
class OnboardingActivity : AppCompatActivity(), DiscoveryListener, ManualSetupListener,
AuthenticationListener, MobileAppIntegrationListener {
companion object {
private const val TAG = "OnboardingActivity"
@ -79,9 +79,8 @@ class OnboardingActivity : AppCompatActivity(), DiscoveryListener, ManualSetupLi
startWebView()
}
private fun startWebView(){
private fun startWebView() {
startActivity(WebViewActivity.newInstance(this))
finish()
}
}
}

View file

@ -13,7 +13,6 @@ import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
import javax.inject.Inject
class AuthenticationFragment : Fragment(), AuthenticationView {
companion object {
@ -24,7 +23,8 @@ class AuthenticationFragment : Fragment(), AuthenticationView {
}
}
@Inject lateinit var presenter: AuthenticationPresenter
@Inject
lateinit var presenter: AuthenticationPresenter
private lateinit var webView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
@ -38,7 +38,11 @@ class AuthenticationFragment : Fragment(), AuthenticationView {
.inject(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_authentication, container, false).apply {
webView = findViewById(R.id.webview)
@ -72,4 +76,4 @@ class AuthenticationFragment : Fragment(), AuthenticationView {
presenter.onFinish()
super.onDestroy()
}
}
}

View file

@ -3,5 +3,4 @@ package io.homeassistant.companion.android.onboarding.authentication
interface AuthenticationListener {
fun onAuthenticationSuccess()
}

View file

@ -1,6 +1,5 @@
package io.homeassistant.companion.android.onboarding.authentication
interface AuthenticationPresenter {
fun onViewReady()
@ -8,5 +7,4 @@ interface AuthenticationPresenter {
fun onRedirectUrl(redirectUrl: String): Boolean
fun onFinish()
}
}

View file

@ -3,9 +3,12 @@ package io.homeassistant.companion.android.onboarding.authentication
import android.net.Uri
import android.util.Log
import io.homeassistant.companion.android.domain.authentication.AuthenticationUseCase
import kotlinx.coroutines.*
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
class AuthenticationPresenterImpl @Inject constructor(
private val view: AuthenticationView,
@ -45,5 +48,4 @@ class AuthenticationPresenterImpl @Inject constructor(
override fun onFinish() {
mainScope.cancel()
}
}

View file

@ -1,10 +1,8 @@
package io.homeassistant.companion.android.onboarding.authentication
interface AuthenticationView {
fun loadUrl(url: String)
fun openWebview()
}

View file

@ -10,7 +10,6 @@ import android.widget.ViewFlipper
import androidx.fragment.app.Fragment
import io.homeassistant.companion.android.R
class DiscoveryFragment : Fragment() {
companion object {
@ -24,10 +23,15 @@ class DiscoveryFragment : Fragment() {
private lateinit var viewFlipper: ViewFlipper
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_discovery, container, false).apply {
viewFlipper = this.findViewById(R.id.view_flipper)
this.findViewById<Button>(R.id.manual_setup).setOnClickListener { (activity as DiscoveryListener).onSelectManualSetup() }
this.findViewById<Button>(R.id.manual_setup)
.setOnClickListener { (activity as DiscoveryListener).onSelectManualSetup() }
this.findViewById<Button>(R.id.retry).setOnClickListener { scan() }
}
}

View file

@ -5,5 +5,4 @@ interface DiscoveryListener {
fun onSelectManualSetup()
fun onHomeAssistantDiscover()
}

View file

@ -13,7 +13,6 @@ import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
import javax.inject.Inject
class MobileAppIntegrationFragment : Fragment(), MobileAppIntegrationView {
companion object {
@ -82,6 +81,4 @@ class MobileAppIntegrationFragment : Fragment(), MobileAppIntegrationView {
presenter.onFinish()
super.onDestroy()
}
}

View file

@ -5,5 +5,4 @@ interface MobileAppIntegrationListener {
fun onIntegrationRegistrationComplete()
fun onIntegrationRegistrationSkipped()
}
}

View file

@ -4,4 +4,4 @@ interface MobileAppIntegrationPresenter {
fun onRegistrationAttempt()
fun onSkip()
fun onFinish()
}
}

View file

@ -5,8 +5,12 @@ import android.util.Log
import io.homeassistant.companion.android.BuildConfig
import io.homeassistant.companion.android.domain.integration.DeviceRegistration
import io.homeassistant.companion.android.domain.integration.IntegrationUseCase
import kotlinx.coroutines.*
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
class MobileAppIntegrationPresenterImpl @Inject constructor(
private val view: MobileAppIntegrationView,

View file

@ -9,5 +9,4 @@ interface MobileAppIntegrationView {
fun showLoading()
fun showError()
}

View file

@ -14,7 +14,6 @@ import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
import javax.inject.Inject
class ManualSetupFragment : Fragment(), ManualSetupView {
companion object {
@ -23,7 +22,8 @@ class ManualSetupFragment : Fragment(), ManualSetupView {
}
}
@Inject lateinit var presenter: ManualSetupPresenter
@Inject
lateinit var presenter: ManualSetupPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -36,7 +36,11 @@ class ManualSetupFragment : Fragment(), ManualSetupView {
.inject(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_manual_setup, container, false).apply {
findViewById<Button>(R.id.ok).setOnClickListener {
presenter.onClickOk(findViewById<EditText>(R.id.home_assistant_url).text.toString())
@ -49,13 +53,13 @@ class ManualSetupFragment : Fragment(), ManualSetupView {
(activity as ManualSetupListener).onSelectUrl()
}
override fun displayUrlError(){
view?.findViewById<TextInputLayout>(R.id.url_text_layout)?.error = context?.getString(R.string.url_parse_error)
override fun displayUrlError() {
view?.findViewById<TextInputLayout>(R.id.url_text_layout)?.error =
context?.getString(R.string.url_parse_error)
}
override fun onDestroy() {
presenter.onFinish()
super.onDestroy()
}
}

View file

@ -1,8 +1,6 @@
package io.homeassistant.companion.android.onboarding.manual
interface ManualSetupListener {
fun onSelectUrl()
}

View file

@ -1,10 +1,8 @@
package io.homeassistant.companion.android.onboarding.manual
interface ManualSetupPresenter {
fun onClickOk(urlString: String)
fun onFinish()
}
}

View file

@ -2,10 +2,14 @@ package io.homeassistant.companion.android.onboarding.manual
import android.util.Log
import io.homeassistant.companion.android.domain.authentication.AuthenticationUseCase
import kotlinx.coroutines.*
import java.net.MalformedURLException
import java.net.URL
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
class ManualSetupPresenterImpl @Inject constructor(
private val view: ManualSetupView,
@ -37,5 +41,4 @@ class ManualSetupPresenterImpl @Inject constructor(
override fun onFinish() {
mainScope.cancel()
}
}
}

View file

@ -5,5 +5,4 @@ interface ManualSetupView {
fun urlSaved()
fun displayUrlError()
}

View file

@ -8,7 +8,6 @@ import androidx.appcompat.app.AppCompatActivity
import io.homeassistant.companion.android.BuildConfig
import io.homeassistant.companion.android.R
class SettingsActivity : AppCompatActivity() {
companion object {
@ -17,11 +16,10 @@ class SettingsActivity : AppCompatActivity() {
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
findViewById<TextView>(R.id.version_text_view).text = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})"
findViewById<TextView>(R.id.version_text_view).text =
"${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})"
}
}

View file

@ -1,6 +1,5 @@
package io.homeassistant.companion.android.webview
interface WebView {
fun loadUrl(url: String)
@ -8,5 +7,4 @@ interface WebView {
fun setExternalAuth(script: String)
fun openOnBoarding()
}

View file

@ -4,8 +4,11 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.webkit.*
import android.webkit.JavascriptInterface
import android.webkit.JsResult
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import io.homeassistant.companion.android.BuildConfig
@ -15,9 +18,8 @@ import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
import io.homeassistant.companion.android.onboarding.OnboardingActivity
import io.homeassistant.companion.android.settings.SettingsActivity
import org.json.JSONObject
import javax.inject.Inject
import org.json.JSONObject
class WebViewActivity : AppCompatActivity(), io.homeassistant.companion.android.webview.WebView {
@ -29,7 +31,8 @@ class WebViewActivity : AppCompatActivity(), io.homeassistant.companion.android.
}
}
@Inject lateinit var presenter: WebViewPresenter
@Inject
lateinit var presenter: WebViewPresenter
private lateinit var webView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
@ -54,7 +57,12 @@ class WebViewActivity : AppCompatActivity(), io.homeassistant.companion.android.
webViewClient = WebViewClient()
webChromeClient = object : WebChromeClient() {
override fun onJsConfirm(view: WebView, url: String, message: String, result: JsResult): Boolean {
override fun onJsConfirm(
view: WebView,
url: String,
message: String,
result: JsResult
): Boolean {
AlertDialog
.Builder(this@WebViewActivity)
.setTitle(R.string.app_name)
@ -86,21 +94,23 @@ class WebViewActivity : AppCompatActivity(), io.homeassistant.companion.android.
when {
JSONObject(message).get("type") == "config/get" -> {
val script = "externalBus(" +
"${JSONObject(
mapOf(
"id" to JSONObject(message).get("id"),
"type" to "result",
"success" to true,
"result" to JSONObject(mapOf("hasSettingsScreen" to true))
)
)}" +
");"
"${JSONObject(
mapOf(
"id" to JSONObject(message).get("id"),
"type" to "result",
"success" to true,
"result" to JSONObject(mapOf("hasSettingsScreen" to true))
)
)}" +
");"
Log.d(TAG, script)
webView.evaluateJavascript(script) {
Log.d(TAG, "Callback $it")
}
}
JSONObject(message).get("type") == "config_screen/show" -> startActivity(SettingsActivity.newInstance(this@WebViewActivity))
JSONObject(message).get("type") == "config_screen/show" -> startActivity(
SettingsActivity.newInstance(this@WebViewActivity)
)
}
}
}

View file

@ -1,6 +1,5 @@
package io.homeassistant.companion.android.webview
interface WebViewPresenter {
fun onViewReady()
@ -10,5 +9,4 @@ interface WebViewPresenter {
fun onRevokeExternalAuth(callback: String)
fun onFinish()
}

View file

@ -3,9 +3,12 @@ package io.homeassistant.companion.android.webview
import android.net.Uri
import android.util.Log
import io.homeassistant.companion.android.domain.authentication.AuthenticationUseCase
import kotlinx.coroutines.*
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
class WebViewPresenterImpl @Inject constructor(
private val view: WebView,
@ -20,7 +23,8 @@ class WebViewPresenterImpl @Inject constructor(
override fun onViewReady() {
mainScope.launch {
val url = authenticationUseCase.getUrl() ?: throw IllegalStateException("Unable to display the webview if we do not have url")
val url = authenticationUseCase.getUrl()
?: throw IllegalStateException("Unable to display the webview if we do not have url")
view.loadUrl(
Uri.parse(url.toString())
@ -59,5 +63,4 @@ class WebViewPresenterImpl @Inject constructor(
override fun onFinish() {
mainScope.cancel()
}
}
}

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/colorPrimary"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<background android:drawable="@color/colorPrimary" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/colorPrimary"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<background android:drawable="@color/colorPrimary" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View file

@ -2,15 +2,15 @@ package io.homeassistant.companion.android.launch
import io.homeassistant.companion.android.domain.authentication.AuthenticationUseCase
import io.homeassistant.companion.android.domain.authentication.SessionState
import io.mockk.*
import io.mockk.coEvery
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
object LaunchPresenterImplSpec : Spek({
beforeEachTest {
@ -58,4 +58,4 @@ object LaunchPresenterImplSpec : Spek({
}
}
}
})
})

View file

@ -2,16 +2,20 @@ package io.homeassistant.companion.android.onboarding.authentication
import android.net.Uri
import io.homeassistant.companion.android.domain.authentication.AuthenticationUseCase
import io.mockk.*
import io.mockk.Called
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.verify
import java.net.URL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.assertj.core.api.Assertions.assertThat
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
import java.net.URL
object AuthenticationPresenterImplSpec : Spek({
@ -75,4 +79,4 @@ object AuthenticationPresenterImplSpec : Spek({
}
}
}
})
})

View file

@ -4,14 +4,18 @@ import android.os.Build
import io.homeassistant.companion.android.BuildConfig
import io.homeassistant.companion.android.domain.integration.DeviceRegistration
import io.homeassistant.companion.android.domain.integration.IntegrationUseCase
import io.mockk.*
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.coVerifyAll
import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
object MobileAppIntegrationPresenterImplSpec : Spek({
beforeEachTest {
@ -71,7 +75,7 @@ object MobileAppIntegrationPresenterImplSpec : Spek({
beforeEachTest {
coEvery { integrationUseCase.registerDevice(any()) } throws Exception()
}
describe("register"){
describe("register") {
beforeEachTest {
presenter.onRegistrationAttempt()
}
@ -86,4 +90,4 @@ object MobileAppIntegrationPresenterImplSpec : Spek({
}
}
}
})
})

View file

@ -5,13 +5,12 @@ import io.mockk.Called
import io.mockk.coVerifyAll
import io.mockk.mockk
import io.mockk.verify
import java.net.URL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
import java.net.URL
object ManualSetupPresenterImplSpec : Spek({
@ -59,5 +58,4 @@ object ManualSetupPresenterImplSpec : Spek({
}
}
}
})
})

View file

@ -2,14 +2,19 @@ package io.homeassistant.companion.android.webview
import android.net.Uri
import io.homeassistant.companion.android.domain.authentication.AuthenticationUseCase
import io.mockk.*
import io.mockk.coEvery
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.runs
import io.mockk.verify
import java.net.URL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
import java.net.URL
object WebViewPresenterImplSpec : Spek({
@ -31,7 +36,9 @@ object WebViewPresenterImplSpec : Spek({
coEvery { authenticationUseCase.getUrl() } returns URL("https://demo.home-assistant.io/")
mockkStatic(Uri::class)
every { Uri.parse("https://demo.home-assistant.io/") } returns mockk {
every { buildUpon().appendQueryParameter("external_auth", "1").build().toString() } returns "https://demo.home-assistant.io?external_auth=1"
every {
buildUpon().appendQueryParameter("external_auth", "1").build().toString()
} returns "https://demo.home-assistant.io?external_auth=1"
}
presenter.onViewReady()
@ -87,4 +94,4 @@ object WebViewPresenterImplSpec : Spek({
}
}
}
})
})

View file

@ -6,24 +6,31 @@ buildscript {
google()
jcenter()
maven { url 'https://maven.fabric.io/public' }
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.2'
classpath 'io.fabric.tools:gradle:1.31.2'
classpath "org.jlleitschuh.gradle:ktlint-gradle:9.1.1"
}
}
apply plugin: "org.jlleitschuh.gradle.ktlint"
allprojects {
repositories {
google()
jcenter()
}
apply plugin: "org.jlleitschuh.gradle.ktlint"
}
task clean(type: Delete) {
delete rootProject.buildDir
}
ktlint {
android = true
}

View file

@ -28,5 +28,4 @@ class LocalStorageImpl(private val sharedPreferences: SharedPreferences) : Local
null
}
}
}

View file

@ -10,5 +10,4 @@ interface AppComponent {
fun authenticationUseCase(): AuthenticationUseCase
fun integrationUseCase(): IntegrationUseCase
}
}

View file

@ -10,5 +10,4 @@ interface DataComponent {
fun authenticationRepository(): AuthenticationRepository
fun integrationRepository(): IntegrationRepository
}
}

View file

@ -24,10 +24,12 @@ class DataModule(
fun provideEndPoint() = url
@Provides
fun provideAuthenticationService(homeAssistantRetrofit: HomeAssistantRetrofit) = homeAssistantRetrofit.retrofit.create(AuthenticationService::class.java)
fun provideAuthenticationService(homeAssistantRetrofit: HomeAssistantRetrofit) =
homeAssistantRetrofit.retrofit.create(AuthenticationService::class.java)
@Provides
fun providesIntegrationService(homeAssistantRetrofit: HomeAssistantRetrofit) = homeAssistantRetrofit.retrofit.create(IntegrationService::class.java)
fun providesIntegrationService(homeAssistantRetrofit: HomeAssistantRetrofit) =
homeAssistantRetrofit.retrofit.create(IntegrationService::class.java)
@Provides
@Named("session")
@ -37,8 +39,6 @@ class DataModule(
@Named("integration")
fun provideIntegrationLocalStorage() = integrationLocalStorage
@Module
interface Declaration {
@ -47,7 +47,5 @@ class DataModule(
@Binds
fun bindIntegrationService(repository: IntegrationRepositoryImpl): IntegrationRepository
}
}

View file

@ -10,5 +10,4 @@ interface DomainComponent {
fun authenticationUseCase(): AuthenticationUseCase
fun integrationUseCase(): IntegrationUseCase
}
}

View file

@ -11,9 +11,8 @@ import io.homeassistant.companion.android.domain.integration.IntegrationUseCaseI
interface DomainModule {
@Binds
fun bindAuthenticationUseCase(useCaseImpl: AuthenticationUseCaseImpl) : AuthenticationUseCase
fun bindAuthenticationUseCase(useCaseImpl: AuthenticationUseCaseImpl): AuthenticationUseCase
@Binds
fun bindIntegrationUseCase(useCaseImpl: IntegrationUseCaseImpl) : IntegrationUseCase
fun bindIntegrationUseCase(useCaseImpl: IntegrationUseCaseImpl): IntegrationUseCase
}

View file

@ -5,7 +5,6 @@ import android.content.Context
import io.homeassistant.companion.android.common.LocalStorageImpl
import kotlinx.coroutines.runBlocking
class Graph(
private val application: Application
) {
@ -35,11 +34,23 @@ class Graph(
private fun buildComponent() {
dataComponent = DaggerDataComponent
.builder()
.dataModule(DataModule(
url,
LocalStorageImpl(application.getSharedPreferences("session", Context.MODE_PRIVATE)),
LocalStorageImpl(application.getSharedPreferences("integration", Context.MODE_PRIVATE))
))
.dataModule(
DataModule(
url,
LocalStorageImpl(
application.getSharedPreferences(
"session",
Context.MODE_PRIVATE
)
),
LocalStorageImpl(
application.getSharedPreferences(
"integration",
Context.MODE_PRIVATE
)
)
)
)
.build()
domainComponent = DaggerDomainComponent
@ -51,5 +62,4 @@ class Graph(
.domainComponent(domainComponent)
.build()
}
}
}

View file

@ -1,10 +1,8 @@
package io.homeassistant.companion.android.common.dagger
interface GraphComponentAccessor {
val appComponent: AppComponent
fun urlUpdated()
}

View file

@ -3,11 +3,11 @@ package io.homeassistant.companion.android.data
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.PropertyNamingStrategy
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import javax.inject.Inject
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.jackson.JacksonConverterFactory
import javax.inject.Inject
class HomeAssistantRetrofit @Inject constructor(url: String) {

View file

@ -1,6 +1,5 @@
package io.homeassistant.companion.android.data
interface LocalStorage {
suspend fun putString(key: String, value: String?)
@ -10,5 +9,4 @@ interface LocalStorage {
suspend fun putLong(key: String, value: Long?)
suspend fun getLong(key: String): Long?
}

View file

@ -4,13 +4,12 @@ import com.fasterxml.jackson.databind.ObjectMapper
import io.homeassistant.companion.android.data.LocalStorage
import io.homeassistant.companion.android.domain.authentication.AuthenticationRepository
import io.homeassistant.companion.android.domain.authentication.SessionState
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.threeten.bp.Instant
import java.net.URL
import javax.inject.Inject
import javax.inject.Named
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.threeten.bp.Instant
class AuthenticationRepositoryImpl @Inject constructor(
private val authenticationService: AuthenticationService,
@ -136,5 +135,4 @@ class AuthenticationRepositoryImpl @Inject constructor(
localStorage.putString(PREF_REFRESH_TOKEN, session?.refreshToken)
localStorage.putString(PREF_TOKEN_TYPE, session?.tokenType)
}
}
}

View file

@ -4,7 +4,6 @@ import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
interface AuthenticationService {
companion object {
@ -36,5 +35,4 @@ interface AuthenticationService {
@Field("token") refreshToken: String,
@Field("action") action: String
)
}
}

View file

@ -1,4 +1,3 @@
package io.homeassistant.companion.android.data.authentication
class AuthorizationException : Exception()
class AuthorizationException : Exception()

View file

@ -2,7 +2,6 @@ package io.homeassistant.companion.android.data.authentication
import org.threeten.bp.Instant
data class Session(
val accessToken: String,
val expiresTimestamp: Long,
@ -13,5 +12,4 @@ data class Session(
fun isExpired() = expiresIn() < 0
fun expiresIn() = expiresTimestamp - Instant.now().epochSecond
}

View file

@ -2,7 +2,6 @@ package io.homeassistant.companion.android.data.authentication
import com.fasterxml.jackson.annotation.JsonProperty
data class Token(
@JsonProperty("access_token")
val accessToken: String,
@ -12,4 +11,4 @@ data class Token(
val refreshToken: String?,
@JsonProperty("token_type")
val tokenType: String
)
)

View file

@ -31,7 +31,6 @@ class IntegrationRepositoryImpl @Inject constructor(
localStorage.putString(PREF_REMOTE_UI_URL, response.remoteUiUrl)
localStorage.putString(PREF_SECRET, response.secret)
localStorage.putString(PREF_WEBHOOK_ID, response.webhookId)
}
override suspend fun isRegistered(): Boolean {
@ -52,4 +51,4 @@ class IntegrationRepositoryImpl @Inject constructor(
deviceRegistration.appData
)
}
}
}

View file

@ -11,5 +11,4 @@ interface IntegrationService {
@Header("Authorization") auth: String,
@Body request: RegisterDeviceRequest
): RegisterDeviceResponse
}

View file

@ -1,7 +1,8 @@
package io.homeassistant.companion.android.data.integration
import com.fasterxml.jackson.annotation.JsonInclude
import java.util.*
import java.util.Dictionary
import java.util.Objects
data class RegisterDeviceRequest(
var appId: String,

View file

@ -1,14 +1,14 @@
package io.homeassistant.companion.android.data
import java.io.IOException
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import java.io.IOException
class HomeAssistantMockService<T>(private val c: Class<T>) {
private val mockServer: MockWebServer = MockWebServer()
private val homeAssistantRetrofit = HomeAssistantRetrofit(mockServer.url("/").toString()).retrofit
private val homeAssistantRetrofit =
HomeAssistantRetrofit(mockServer.url("/").toString()).retrofit
fun get(): T {
return homeAssistantRetrofit.create(c)
@ -38,5 +38,4 @@ class HomeAssistantMockService<T>(private val c: Class<T>) {
throw RuntimeException()
}
}
}
}

View file

@ -2,15 +2,21 @@ package io.homeassistant.companion.android.data.authentication
import io.homeassistant.companion.android.data.LocalStorage
import io.homeassistant.companion.android.domain.authentication.SessionState
import io.mockk.*
import io.mockk.Called
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.coVerifyAll
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.verify
import java.net.URL
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.catchThrowable
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
import org.threeten.bp.Instant
import java.net.URL
object AuthenticationRepositoryImplSpec : Spek({
@ -334,4 +340,4 @@ object AuthenticationRepositoryImplSpec : Spek({
}
}
}
})
})

View file

@ -7,7 +7,6 @@ import org.assertj.core.api.Assertions.assertThat
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
object AuthenticationServiceSpec : Spek({
describe("an authentication service") {
@ -19,7 +18,11 @@ object AuthenticationServiceSpec : Spek({
beforeEachTest {
mockService.enqueueResponse(200, "authentication/authorization_code.json")
token = runBlocking {
mockService.get().getToken(AuthenticationService.GRANT_TYPE_CODE, "12345", AuthenticationService.CLIENT_ID)
mockService.get().getToken(
AuthenticationService.GRANT_TYPE_CODE,
"12345",
AuthenticationService.CLIENT_ID
)
}
request = mockService.takeRequest()
}
@ -47,7 +50,11 @@ object AuthenticationServiceSpec : Spek({
beforeEachTest {
mockService.enqueueResponse(200, "authentication/refresh_token.json")
token = runBlocking {
mockService.get().refreshToken(AuthenticationService.GRANT_TYPE_REFRESH, "IJKLMNOPQRST", AuthenticationService.CLIENT_ID)
mockService.get().refreshToken(
AuthenticationService.GRANT_TYPE_REFRESH,
"IJKLMNOPQRST",
AuthenticationService.CLIENT_ID
)
}
request = mockService.takeRequest()
}
@ -74,7 +81,8 @@ object AuthenticationServiceSpec : Spek({
beforeEachTest {
mockService.enqueueResponse(200)
runBlocking {
mockService.get().revokeToken("IJKLMNOPQRST", AuthenticationService.REVOKE_ACTION)
mockService.get()
.revokeToken("IJKLMNOPQRST", AuthenticationService.REVOKE_ACTION)
}
request = mockService.takeRequest()
}
@ -88,4 +96,4 @@ object AuthenticationServiceSpec : Spek({
}
}
}
})
})

View file

@ -5,7 +5,6 @@ import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
import org.threeten.bp.Instant
object SessionSpec : Spek({
describe("an expired session") {
@ -27,5 +26,4 @@ object SessionSpec : Spek({
assertThat(session.expiresIn()).isEqualTo(1800)
}
}
})
})

View file

@ -7,11 +7,11 @@ import io.mockk.coEvery
import io.mockk.coVerifyAll
import io.mockk.every
import io.mockk.mockk
import kotlin.properties.Delegates
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
import kotlin.properties.Delegates
object IntegrationRepositoryImplSpec : Spek({
@ -107,9 +107,7 @@ object IntegrationRepositoryImplSpec : Spek({
it("should return false when webhook has no value") {
Assertions.assertThat(isRegistered).isFalse()
}
}
}
}
})
})

View file

@ -7,7 +7,6 @@ import org.assertj.core.api.Assertions.assertThat
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
object IntegrationServiceSpec : Spek({
describe("an integration service") {
val mockService by memoized { HomeAssistantMockService(IntegrationService::class.java) }
@ -44,5 +43,4 @@ object IntegrationServiceSpec : Spek({
assertThat(registrationResponse.webhookId).isEqualTo("ABC")
}
}
})
})

View file

@ -2,7 +2,6 @@ package io.homeassistant.companion.android.domain.authentication
import java.net.URL
interface AuthenticationRepository {
suspend fun saveUrl(url: URL)
@ -20,5 +19,4 @@ interface AuthenticationRepository {
suspend fun buildAuthenticationUrl(callbackUrl: String): URL
suspend fun buildBearerToken(): String
}

View file

@ -2,7 +2,6 @@ package io.homeassistant.companion.android.domain.authentication
import java.net.URL
interface AuthenticationUseCase {
suspend fun saveUrl(url: URL)
@ -18,5 +17,4 @@ interface AuthenticationUseCase {
suspend fun getUrl(): URL?
suspend fun buildAuthenticationUrl(callbackUrl: String): URL
}
}

View file

@ -34,5 +34,4 @@ class AuthenticationUseCaseImpl @Inject constructor(
override suspend fun buildAuthenticationUrl(callbackUrl: String): URL {
return authenticationRepository.buildAuthenticationUrl(callbackUrl)
}
}
}

View file

@ -1,7 +1,6 @@
package io.homeassistant.companion.android.domain.authentication
enum class SessionState {
ANONYMOUS,
CONNECTED,
}
}

View file

@ -1,6 +1,7 @@
package io.homeassistant.companion.android.domain.integration
import java.util.*
import java.util.Dictionary
import java.util.Objects
data class DeviceRegistration(
val appId: String,
@ -13,4 +14,4 @@ data class DeviceRegistration(
val osVersion: String,
val supportsEncryption: Boolean,
val appData: Dictionary<String, Objects>?
)
)

View file

@ -5,4 +5,4 @@ interface IntegrationRepository {
suspend fun registerDevice(deviceRegistration: DeviceRegistration)
suspend fun isRegistered(): Boolean
}
}

View file

@ -5,5 +5,4 @@ interface IntegrationUseCase {
suspend fun registerDevice(deviceRegistration: DeviceRegistration)
suspend fun isRegistered(): Boolean
}
}

View file

@ -12,4 +12,4 @@ class IntegrationUseCaseImpl @Inject constructor(
override suspend fun isRegistered(): Boolean {
return integrationRepository.isRegistered()
}
}
}

View file

@ -3,12 +3,11 @@ package io.homeassistant.companion.android.domain.authentication
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import java.net.URL
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
import java.net.URL
object AuthenticationUseCaseImplSpec : Spek({
@ -96,4 +95,4 @@ object AuthenticationUseCaseImplSpec : Spek({
}
}
}
})
})

View file

@ -1,6 +1,10 @@
package io.homeassistant.companion.android.domain.integration
import io.mockk.*
import io.mockk.Runs
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.just
import io.mockk.mockk
import kotlinx.coroutines.runBlocking
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
@ -45,4 +49,4 @@ object IntegrationUseCaseImplSpec : Spek({
}
}
}
})
})