mirror of
https://github.com/home-assistant/android
synced 2024-11-05 18:28:42 +00:00
Add nfc support (#689)
This commit is contained in:
parent
4354e817c6
commit
ec9ff0ae6b
30 changed files with 1034 additions and 0 deletions
|
@ -130,6 +130,8 @@ dependencies {
|
|||
implementation(Config.Dependency.AndroidX.constraintlayout)
|
||||
implementation(Config.Dependency.AndroidX.recyclerview)
|
||||
implementation(Config.Dependency.AndroidX.preference)
|
||||
implementation(Config.Dependency.AndroidX.navigationFragment)
|
||||
implementation(Config.Dependency.AndroidX.navigationUi)
|
||||
implementation(Config.Dependency.Google.material)
|
||||
|
||||
implementation(Config.Dependency.AndroidX.roomRuntime)
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.telephony" android:required="false"/>
|
||||
|
@ -120,6 +121,34 @@
|
|||
android:name=".settings.SettingsActivity"
|
||||
android:parentActivityName=".webview.WebViewActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".nfc.TagReaderActivity"
|
||||
android:label="@string/tag_reader_title">
|
||||
<tools:validation testUrl="https://www.home-assistant.io/tag/123e4567-e89b-12d3-a456-426614174000" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="www.home-assistant.io"
|
||||
android:pathPrefix="/tag/" />
|
||||
</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="www.home-assistant.io"
|
||||
android:pathPrefix="/tag/" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".nfc.NfcSetupActivity"
|
||||
android:label="@string/nfc_title_nfc_setup" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
package io.homeassistant.companion.android.nfc
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.nfc.NdefMessage
|
||||
import android.nfc.NdefRecord
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.Tag
|
||||
import android.nfc.tech.Ndef
|
||||
import android.nfc.tech.NdefFormatable
|
||||
import java.io.IOException
|
||||
|
||||
object NFCUtil {
|
||||
@Throws(Exception::class)
|
||||
fun createNFCMessage(url: String, intent: Intent?): Boolean {
|
||||
val nfcRecord = NdefRecord.createUri(url)
|
||||
val nfcMessage = NdefMessage(arrayOf(nfcRecord))
|
||||
intent?.let {
|
||||
val tag = it.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
|
||||
return writeMessageToTag(nfcMessage, tag)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun disableNFCInForeground(nfcAdapter: NfcAdapter, activity: Activity) {
|
||||
nfcAdapter.disableForegroundDispatch(activity)
|
||||
}
|
||||
|
||||
fun <T> enableNFCInForeground(nfcAdapter: NfcAdapter, activity: Activity, classType: Class<T>) {
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
activity, 0,
|
||||
Intent(activity, classType).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0
|
||||
)
|
||||
val nfcIntentFilter = IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED)
|
||||
val filters = arrayOf(nfcIntentFilter)
|
||||
|
||||
val techLists =
|
||||
arrayOf(arrayOf(Ndef::class.java.name), arrayOf(NdefFormatable::class.java.name))
|
||||
nfcAdapter.enableForegroundDispatch(activity, pendingIntent, filters, techLists)
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun writeMessageToTag(nfcMessage: NdefMessage, tag: Tag?): Boolean {
|
||||
val nDefTag = Ndef.get(tag)
|
||||
|
||||
nDefTag?.let {
|
||||
it.connect()
|
||||
if (it.maxSize < nfcMessage.toByteArray().size) {
|
||||
// Message to large to write to NFC tag
|
||||
throw Exception("Message is too large")
|
||||
}
|
||||
return if (it.isWritable) {
|
||||
it.writeNdefMessage(nfcMessage)
|
||||
it.close()
|
||||
// Message is written to tag
|
||||
true
|
||||
} else {
|
||||
throw Exception("NFC tag is read-only")
|
||||
}
|
||||
}
|
||||
|
||||
val nDefFormatableTag = NdefFormatable.get(tag)
|
||||
|
||||
nDefFormatableTag?.let {
|
||||
try {
|
||||
it.connect()
|
||||
it.format(nfcMessage)
|
||||
it.close()
|
||||
// The data is written to the tag
|
||||
} catch (e: IOException) {
|
||||
// Failed to format tag
|
||||
throw Exception("Failed to format tag", e)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package io.homeassistant.companion.android.nfc
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
|
||||
import io.homeassistant.companion.android.domain.integration.IntegrationUseCase
|
||||
import javax.inject.Inject
|
||||
import kotlinx.android.synthetic.main.fragment_nfc_edit.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* A simple [Fragment] subclass as the second destination in the navigation.
|
||||
*/
|
||||
class NfcEditFragment : Fragment() {
|
||||
|
||||
val TAG = NfcEditFragment::class.simpleName
|
||||
|
||||
private val mainScope: CoroutineScope = CoroutineScope(Dispatchers.Main + Job())
|
||||
|
||||
private lateinit var viewModel: NfcViewModel
|
||||
|
||||
@Inject
|
||||
lateinit var integrationUseCase: IntegrationUseCase
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
viewModel = ViewModelProvider(requireActivity()).get(NfcViewModel::class.java)
|
||||
|
||||
// Inject components
|
||||
DaggerProviderComponent
|
||||
.builder()
|
||||
.appComponent((activity?.application as GraphComponentAccessor).appComponent)
|
||||
.build()
|
||||
.inject(this)
|
||||
|
||||
// Inflate the layout for this fragment
|
||||
return inflater.inflate(R.layout.fragment_nfc_edit, container, false)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val nfcReadObserver = Observer<String> { uuid ->
|
||||
mainScope.launch {
|
||||
et_tag_identifier_content.setText(uuid)
|
||||
val deviceName = integrationUseCase.getRegistration().deviceName!!
|
||||
et_tag_example_trigger_content.setText("- platform: event\n event_type: tag_scanned\n event_data:\n device_id: $deviceName\n tag_id: $uuid")
|
||||
}
|
||||
}
|
||||
viewModel.nfcReadEvent.observe(viewLifecycleOwner, nfcReadObserver)
|
||||
|
||||
btn_tag_duplicate.setOnClickListener {
|
||||
viewModel.nfcWriteTagEvent.postValue(et_tag_identifier_content.text.toString())
|
||||
findNavController().navigate(R.id.action_NFC_WRITE)
|
||||
}
|
||||
|
||||
btn_tag_fire_event.setOnClickListener {
|
||||
mainScope.launch {
|
||||
val uuid: String = viewModel.nfcReadEvent.value.toString()
|
||||
try {
|
||||
integrationUseCase.scanTag(
|
||||
hashMapOf("tag_id" to uuid)
|
||||
)
|
||||
Toast.makeText(activity, R.string.nfc_event_fired_success, Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(activity, R.string.nfc_event_fired_fail, Toast.LENGTH_LONG)
|
||||
.show()
|
||||
Log.e(TAG, "Unable to send tag to Home Assistant.", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
btn_tag_share_example_trigger.setOnClickListener {
|
||||
val sendIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_TEXT, et_tag_example_trigger_content.text)
|
||||
type = "text/plain"
|
||||
}
|
||||
val shareIntent = Intent.createChooser(sendIntent, null)
|
||||
startActivity(shareIntent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
mainScope.cancel()
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package io.homeassistant.companion.android.nfc
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import io.homeassistant.companion.android.R
|
||||
|
||||
/**
|
||||
* A simple [Fragment] subclass as the second destination in the navigation.
|
||||
*/
|
||||
class NfcReadFragment : Fragment() {
|
||||
|
||||
private lateinit var viewModel: NfcViewModel
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
viewModel = ViewModelProvider(requireActivity()).get(NfcViewModel::class.java)
|
||||
|
||||
return inflater.inflate(R.layout.fragment_nfc_read, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val nfcReadObserver = Observer<String> {
|
||||
findNavController().navigate(R.id.action_NFC_EDIT)
|
||||
}
|
||||
viewModel.nfcReadEvent.observe(viewLifecycleOwner, nfcReadObserver)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package io.homeassistant.companion.android.nfc
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.nfc.NdefMessage
|
||||
import android.nfc.NfcAdapter
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.util.UrlHandler
|
||||
|
||||
class NfcSetupActivity : AppCompatActivity() {
|
||||
|
||||
val TAG = NfcSetupActivity::class.simpleName
|
||||
|
||||
// private val viewModel: NfcViewModel by viewModels()
|
||||
private lateinit var viewModel: NfcViewModel
|
||||
private var mNfcAdapter: NfcAdapter? = null
|
||||
|
||||
companion object {
|
||||
fun newInstance(context: Context): Intent {
|
||||
return Intent(context, NfcSetupActivity::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_nfc_setup)
|
||||
|
||||
setSupportActionBar(findViewById(R.id.toolbar))
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
mNfcAdapter = NfcAdapter.getDefaultAdapter(this)
|
||||
viewModel = ViewModelProvider(this).get(NfcViewModel::class.java)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
mNfcAdapter?.let {
|
||||
NFCUtil.enableNFCInForeground(it, this, javaClass)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
mNfcAdapter?.let {
|
||||
NFCUtil.disableNFCInForeground(it, this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
finish()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
|
||||
if (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action) {
|
||||
val nfcTagToWriteUUID = viewModel.nfcWriteTagEvent.value
|
||||
|
||||
// Create new nfc tag
|
||||
if (nfcTagToWriteUUID == null) {
|
||||
val rawMessages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
|
||||
val ndefMessage = rawMessages.first() as NdefMessage
|
||||
val url = ndefMessage?.records?.get(0)?.toUri().toString()
|
||||
val nfcTagId = UrlHandler.splitNfcTagId(url)
|
||||
if (nfcTagId == null) {
|
||||
viewModel.postNewUUID()
|
||||
} else {
|
||||
viewModel.nfcReadEvent.postValue(nfcTagId)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
val nfcTagUrl = "https://www.home-assistant.io/tag/$nfcTagToWriteUUID"
|
||||
NFCUtil.createNFCMessage(nfcTagUrl, intent)
|
||||
Log.d(TAG, "Wrote nfc tag with url: $nfcTagUrl")
|
||||
val message = R.string.nfc_write_tag_success
|
||||
Toast.makeText(applicationContext, message, Toast.LENGTH_LONG).show()
|
||||
|
||||
viewModel.nfcReadEvent.value = nfcTagToWriteUUID
|
||||
viewModel.nfcWriteTagDoneEvent.value = nfcTagToWriteUUID
|
||||
} catch (e: Exception) {
|
||||
val message = R.string.nfc_write_tag_error
|
||||
Toast.makeText(applicationContext, message, Toast.LENGTH_LONG).show()
|
||||
Log.e(TAG, "Unable to write tag.", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package io.homeassistant.companion.android.nfc
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import java.util.UUID
|
||||
|
||||
class NfcViewModel : ViewModel() {
|
||||
|
||||
// Create a LiveData with a String
|
||||
val nfcReadEvent: MutableLiveData<String> = MutableLiveData()
|
||||
val nfcWriteTagEvent: MutableLiveData<String> = MutableLiveData()
|
||||
val nfcWriteTagDoneEvent: SingleLiveEvent<String> = SingleLiveEvent()
|
||||
|
||||
init {
|
||||
Log.i("NfcViewModel", "NfcViewModel created!")
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
Log.i("NfcViewModel", "NfcViewModel destroyed!")
|
||||
}
|
||||
|
||||
fun postNewUUID() {
|
||||
nfcWriteTagEvent.postValue(UUID.randomUUID().toString())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package io.homeassistant.companion.android.nfc
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import io.homeassistant.companion.android.R
|
||||
import kotlinx.android.synthetic.main.fragment_nfc_welcome.*
|
||||
|
||||
/**
|
||||
* A simple [Fragment] subclass as the default destination in the navigation.
|
||||
*/
|
||||
class NfcWelcomeFragment : Fragment() {
|
||||
|
||||
private lateinit var viewModel: NfcViewModel
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
viewModel = ViewModelProvider(requireActivity()).get(NfcViewModel::class.java)
|
||||
|
||||
return inflater.inflate(R.layout.fragment_nfc_welcome, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val nfcReadObserver = Observer<String> {
|
||||
findNavController().navigate(R.id.action_NFC_READ)
|
||||
}
|
||||
viewModel.nfcReadEvent.observe(viewLifecycleOwner, nfcReadObserver)
|
||||
|
||||
val nfcWriteTagObserver = Observer<String> {
|
||||
findNavController().navigate(R.id.action_NFC_WRITE)
|
||||
}
|
||||
viewModel.nfcWriteTagEvent.observe(viewLifecycleOwner, nfcWriteTagObserver)
|
||||
|
||||
btn_nfc_read.setOnClickListener {
|
||||
findNavController().navigate(R.id.action_NFC_READ)
|
||||
}
|
||||
|
||||
btn_nfc_write.setOnClickListener {
|
||||
findNavController().navigate(R.id.action_NFC_WRITE)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package io.homeassistant.companion.android.nfc
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import io.homeassistant.companion.android.R
|
||||
import kotlinx.android.synthetic.main.fragment_nfc_write.*
|
||||
|
||||
/**
|
||||
* A simple [Fragment] subclass as the second destination in the navigation.
|
||||
*/
|
||||
class NfcWriteFragment : Fragment() {
|
||||
|
||||
private lateinit var viewModel: NfcViewModel
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
viewModel = ViewModelProvider(requireActivity()).get(NfcViewModel::class.java)
|
||||
|
||||
return inflater.inflate(R.layout.fragment_nfc_write, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val nfcWriteTagObserver = Observer<String> {
|
||||
tv_instructions_write_nfc.text = getString(R.string.nfc_write_tag_instructions, it)
|
||||
}
|
||||
viewModel.nfcWriteTagEvent.observe(viewLifecycleOwner, nfcWriteTagObserver)
|
||||
|
||||
val nfcWriteTagDoneObserver = Observer<String> {
|
||||
findNavController().navigate(R.id.action_NFC_EDIT)
|
||||
}
|
||||
viewModel.nfcWriteTagDoneEvent.observe(viewLifecycleOwner, nfcWriteTagDoneObserver)
|
||||
|
||||
viewModel.postNewUUID()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package io.homeassistant.companion.android.nfc
|
||||
|
||||
import dagger.Component
|
||||
import io.homeassistant.companion.android.common.dagger.AppComponent
|
||||
|
||||
@Component(dependencies = [AppComponent::class])
|
||||
interface ProviderComponent {
|
||||
|
||||
fun inject(activity: TagReaderActivity)
|
||||
fun inject(nfcEditFragment: NfcEditFragment)
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package io.homeassistant.companion.android.nfc
|
||||
|
||||
/*
|
||||
* Copyright 2017 Google Inc.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* http://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.
|
||||
*/
|
||||
|
||||
import android.util.Log
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* A lifecycle-aware observable that sends only new updates after subscription, used for events like
|
||||
* navigation and Snackbar messages.
|
||||
*
|
||||
*
|
||||
* This avoids a common problem with events: on configuration change (like rotation) an update
|
||||
* can be emitted if the observer is active. This LiveData only calls the observable if there's an
|
||||
* explicit call to setValue() or call().
|
||||
*
|
||||
*
|
||||
* Note that only one observer is going to be notified of changes.
|
||||
*/
|
||||
class SingleLiveEvent<T> : MutableLiveData<T>() {
|
||||
private val pending = AtomicBoolean(false)
|
||||
|
||||
@MainThread
|
||||
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
|
||||
if (hasActiveObservers()) {
|
||||
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
|
||||
}
|
||||
// Observe the internal MutableLiveData
|
||||
super.observe(owner, Observer { t ->
|
||||
if (pending.compareAndSet(true, false)) {
|
||||
observer.onChanged(t)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@MainThread
|
||||
override fun setValue(t: T?) {
|
||||
pending.set(true)
|
||||
super.setValue(t)
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for cases where T is Void, to make calls cleaner.
|
||||
*/
|
||||
@MainThread
|
||||
fun call() {
|
||||
value = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = "SingleLiveEvent"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package io.homeassistant.companion.android.nfc
|
||||
|
||||
import android.content.Intent
|
||||
import android.nfc.NdefMessage
|
||||
import android.nfc.NfcAdapter
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
|
||||
import io.homeassistant.companion.android.domain.integration.IntegrationUseCase
|
||||
import io.homeassistant.companion.android.util.UrlHandler
|
||||
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 TagReaderActivity : AppCompatActivity() {
|
||||
|
||||
val TAG = TagReaderActivity::class.simpleName
|
||||
|
||||
private val mainScope: CoroutineScope = CoroutineScope(Dispatchers.Main + Job())
|
||||
|
||||
@Inject
|
||||
lateinit var integrationUseCase: IntegrationUseCase
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_tag_reader)
|
||||
|
||||
setSupportActionBar(findViewById(R.id.toolbar))
|
||||
|
||||
// Inject components
|
||||
DaggerProviderComponent
|
||||
.builder()
|
||||
.appComponent((application as GraphComponentAccessor).appComponent)
|
||||
.build()
|
||||
.inject(this)
|
||||
|
||||
mainScope.launch {
|
||||
if (NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action) {
|
||||
val rawMessages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
|
||||
val ndefMessage = rawMessages[0] as NdefMessage?
|
||||
val url = ndefMessage?.records?.get(0)?.toUri().toString()
|
||||
try {
|
||||
handleTag(url)
|
||||
} catch (e: Exception) {
|
||||
val message = R.string.nfc_processing_tag_error
|
||||
Toast.makeText(this@TagReaderActivity, message, Toast.LENGTH_LONG).show()
|
||||
Log.e(TAG, "Unable to handle url (nfc): $url", e)
|
||||
finish()
|
||||
}
|
||||
} else if (Intent.ACTION_VIEW == intent.action) {
|
||||
val url: String = intent?.data.toString()
|
||||
try {
|
||||
handleTag(url)
|
||||
} catch (e: Exception) {
|
||||
val message = R.string.qrcode_processing_tag_error
|
||||
Toast.makeText(this@TagReaderActivity, message, Toast.LENGTH_LONG).show()
|
||||
Log.e(TAG, "Unable to handle url (qrcode): $url", e)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
mainScope.cancel()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private suspend fun handleTag(url: String) {
|
||||
// https://www.home-assistant.io/tag/5f0ba733-172f-430d-a7f8-e4ad940c88d7
|
||||
|
||||
val nfcTagId = UrlHandler.splitNfcTagId(url)
|
||||
if (nfcTagId != null) {
|
||||
integrationUseCase.scanTag(hashMapOf("tag_id" to nfcTagId))
|
||||
} else {
|
||||
Toast.makeText(this, R.string.nfc_processing_tag_error, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
finish()
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ import io.homeassistant.companion.android.PresenterModule
|
|||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.authenticator.Authenticator
|
||||
import io.homeassistant.companion.android.common.dagger.GraphComponentAccessor
|
||||
import io.homeassistant.companion.android.nfc.NfcSetupActivity
|
||||
import io.homeassistant.companion.android.settings.shortcuts.ShortcutsFragment
|
||||
import io.homeassistant.companion.android.settings.ssid.SsidDialogFragment
|
||||
import io.homeassistant.companion.android.settings.ssid.SsidPreference
|
||||
|
@ -91,6 +92,11 @@ class SettingsFragment : PreferenceFragmentCompat(), SettingsView {
|
|||
true
|
||||
}
|
||||
|
||||
findPreference<Preference>("nfc_tags")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
startActivity(NfcSetupActivity.newInstance(requireActivity()))
|
||||
true
|
||||
}
|
||||
|
||||
findPreference<EditTextPreference>("connection_internal")?.onPreferenceChangeListener =
|
||||
onChangeUrlValidator
|
||||
|
||||
|
|
|
@ -19,4 +19,12 @@ object UrlHandler {
|
|||
fun isAbsoluteUrl(it: String?): Boolean {
|
||||
return Regex("^https?://").containsMatchIn(it.toString())
|
||||
}
|
||||
|
||||
fun splitNfcTagId(it: String?): String? {
|
||||
val matches =
|
||||
Regex("^https?://www\\.home-assistant\\.io/tag/([0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12})").find(
|
||||
it.toString()
|
||||
)
|
||||
return matches?.groups?.get(1)?.value
|
||||
}
|
||||
}
|
||||
|
|
23
app/src/main/res/layout/activity_nfc_setup.xml
Normal file
23
app/src/main/res/layout/activity_nfc_setup.xml
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="io.homeassistant.companion.android.nfc.NfcSetupActivity">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:theme="@style/ThemeOverlay.HomeAssistant.ActionBar" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<include layout="@layout/content_nfc_setup" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
36
app/src/main/res/layout/activity_tag_reader.xml
Normal file
36
app/src/main/res/layout/activity_tag_reader.xml
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".nfc.TagReaderActivity">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:theme="@style/ThemeOverlay.HomeAssistant.ActionBar" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/nfc_loading"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
17
app/src/main/res/layout/content_nfc_setup.xml
Normal file
17
app/src/main/res/layout/content_nfc_setup.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/nav_host_fragment"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:defaultNavHost="true"
|
||||
app:navGraph="@navigation/nfc_nav_graph" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
83
app/src/main/res/layout/fragment_nfc_edit.xml
Normal file
83
app/src/main/res/layout/fragment_nfc_edit.xml
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/container_nfc_tag_preview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_tag_identifier_headline"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/nfc_tag_identifier"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_tag_identifier_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:gravity="start|top"
|
||||
android:inputType="textMultiLine"
|
||||
android:editable="false"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_tag_identifier_headline" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_tag_duplicate"
|
||||
style="@style/Widget.HomeAssistant.Button.Colored"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/nfc_btn_create_duplicate"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintHorizontal_chainStyle="spread"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/et_tag_identifier_content" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_tag_fire_event"
|
||||
style="@style/Widget.HomeAssistant.Button.Colored"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/nfc_btn_fire_event"
|
||||
android:layout_marginStart="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@+id/et_tag_identifier_content" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_tag_example_trigger_headline"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/nfc_example_trigger"
|
||||
app:layout_constraintTop_toBottomOf="@+id/btn_tag_duplicate"
|
||||
android:layout_marginTop="48dp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et_tag_example_trigger_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:gravity="start|top"
|
||||
android:inputType="textMultiLine"
|
||||
android:editable="false"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_tag_example_trigger_headline" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btn_tag_share_example_trigger"
|
||||
style="@style/Widget.HomeAssistant.Button.Colored"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/nfc_btn_share"
|
||||
app:layout_constraintTop_toBottomOf="@+id/et_tag_example_trigger_content" />
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.5" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
19
app/src/main/res/layout/fragment_nfc_read.xml
Normal file
19
app/src/main/res/layout/fragment_nfc_read.xml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/container_nfc_tag_preview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_instructions_read_nfc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/nfc_read_tag_instructions"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:gravity="center_horizontal" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
46
app/src/main/res/layout/fragment_nfc_welcome.xml
Normal file
46
app/src/main/res/layout/fragment_nfc_welcome.xml
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/container_mode_switch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_welcome_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/nfc_welcome_message"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:paddingBottom="16dp"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_nfc_read"
|
||||
style="@style/Widget.HomeAssistant.Button.Colored"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/nfc_btn_read_tag"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintHorizontal_chainStyle="spread"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_welcome_message" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_nfc_write"
|
||||
style="@style/Widget.HomeAssistant.Button.Colored"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/nfc_btn_write_tag"
|
||||
android:layout_marginStart="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_welcome_message" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.5" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
20
app/src/main/res/layout/fragment_nfc_write.xml
Normal file
20
app/src/main/res/layout/fragment_nfc_write.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/container_nfc_read"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp"
|
||||
android:visibility="visible"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_instructions_write_nfc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/nfc_write_tag_instructions"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:gravity="center_horizontal" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
55
app/src/main/res/navigation/nfc_nav_graph.xml
Normal file
55
app/src/main/res/navigation/nfc_nav_graph.xml
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/nav_graph"
|
||||
app:startDestination="@id/WelcomeFragment">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/WelcomeFragment"
|
||||
android:name="io.homeassistant.companion.android.nfc.NfcWelcomeFragment"
|
||||
android:label="@string/nfc_welcome_fragment_label"
|
||||
tools:layout="@layout/fragment_nfc_welcome">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_NFC_READ"
|
||||
app:destination="@id/ReadFragment" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_NFC_WRITE"
|
||||
app:destination="@id/WriteFragment" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/WriteFragment"
|
||||
android:name="io.homeassistant.companion.android.nfc.NfcWriteFragment"
|
||||
android:label="@string/nfc_write_fragment_label"
|
||||
tools:layout="@layout/fragment_nfc_write">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_NFC_EDIT"
|
||||
app:destination="@id/EditFragment" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/ReadFragment"
|
||||
android:name="io.homeassistant.companion.android.nfc.NfcReadFragment"
|
||||
android:label="@string/nfc_read_fragment_label"
|
||||
tools:layout="@layout/fragment_nfc_read">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_NFC_EDIT"
|
||||
app:destination="@id/EditFragment" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/EditFragment"
|
||||
android:name="io.homeassistant.companion.android.nfc.NfcEditFragment"
|
||||
android:label="@string/nfc_edit_fragment_label"
|
||||
tools:layout="@layout/fragment_nfc_edit">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_NFC_WRITE"
|
||||
app:destination="@id/WriteFragment" />
|
||||
</fragment>
|
||||
</navigation>
|
|
@ -1,5 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<string name="nfc_tag_identifier">Tag Identifier</string>
|
||||
<string name="nfc_btn_share">Share</string>
|
||||
<string name="nfc_btn_create_duplicate">Create duplicate</string>
|
||||
<string name="nfc_btn_fire_event">Fire event</string>
|
||||
<string name="nfc_btn_read_tag">Read NFC Tag</string>
|
||||
<string name="nfc_btn_write_tag">Write NFC Tag</string>
|
||||
<string name="nfc_event_fired_success">Event fired</string>
|
||||
<string name="nfc_event_fired_fail">Firing event failed. Please try again.</string>
|
||||
<string name="nfc_title_nfc_setup">NFC Tags</string>
|
||||
<string name="nfc_title_settings">NFC Tags</string>
|
||||
<string name="nfc_welcome_fragment_label">NFC Welcome Fragment</string>
|
||||
<string name="nfc_read_fragment_label">NFC Read Fragment</string>
|
||||
<string name="nfc_write_fragment_label">NFC Write Fragment</string>
|
||||
<string name="nfc_edit_fragment_label">NFC Edit Fragment</string>
|
||||
<string name="nfc_example_trigger">Example Trigger</string>
|
||||
<string name="nfc_read_tag_instructions">Place your phone over the nfc tag to read it.</string>
|
||||
<string name="nfc_write_tag_instructions">Place phone over nfc tag to write the following identifier to it %1$s</string>
|
||||
<string name="nfc_write_tag_too_early">Please fill out the form first</string>
|
||||
<string name="nfc_write_tag_success">Successfully wrote nfc tag</string>
|
||||
<string name="nfc_write_tag_error">Error while writing nfc tag</string>
|
||||
<string name="nfc_processing_tag_error">Error while processing nfc tag</string>
|
||||
<string name="nfc_summary">Setup NFC Tags</string>
|
||||
<string name="nfc_welcome_message">NFC tags written by the app will fire an event once you bring your device near them.\n\nTags will work on any device with Home Assistant installed which has hardware support to read them.</string>
|
||||
<string name="tag_reader_title">Processing Tag</string>
|
||||
<string name="qrcode_processing_tag_error">Error while processing qrcode tag</string>
|
||||
<string name="add_service_data_field">Add Field</string>
|
||||
<string name="add_widget">Add widget</string>
|
||||
<string name="app_name">Home Assistant</string>
|
||||
|
|
|
@ -62,6 +62,12 @@
|
|||
android:icon="@drawable/ic_plus"
|
||||
android:title="@string/shortcuts"
|
||||
android:summary="@string/shortcuts_summary" />
|
||||
<Preference
|
||||
android:key="nfc_tags"
|
||||
android:icon="@drawable/ic_plus"
|
||||
android:title="@string/nfc_title_settings"
|
||||
android:summary="@string/nfc_summary" />
|
||||
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:title="@string/app_version_info">
|
||||
|
|
|
@ -51,6 +51,9 @@ object Config {
|
|||
const val constraintlayout = "androidx.constraintlayout:constraintlayout:1.1.3"
|
||||
const val preference = "androidx.preference:preference-ktx:1.1.1"
|
||||
|
||||
const val navigationFragment = "androidx.navigation:navigation-fragment-ktx:2.3.0"
|
||||
const val navigationUi = "androidx.navigation:navigation-ui-ktx:2.3.0"
|
||||
|
||||
const val workManager = "androidx.work:work-runtime-ktx:2.3.4"
|
||||
const val biometric = "androidx.biometric:biometric:1.0.1"
|
||||
|
||||
|
|
|
@ -174,6 +174,30 @@ class IntegrationRepositoryImpl @Inject constructor(
|
|||
throw IntegrationException()
|
||||
}
|
||||
|
||||
override suspend fun scanTag(data: HashMap<String, Any>) {
|
||||
var wasSuccess = false
|
||||
|
||||
for (it in urlRepository.getApiUrls()) {
|
||||
try {
|
||||
wasSuccess =
|
||||
integrationService.scanTag(
|
||||
it.toHttpUrlOrNull()!!,
|
||||
IntegrationRequest(
|
||||
"scan_tag",
|
||||
data
|
||||
)
|
||||
).isSuccessful
|
||||
} catch (e: Exception) {
|
||||
// Ignore failure until we are out of URLS to try!
|
||||
}
|
||||
// if we had a successful call we can return
|
||||
if (wasSuccess)
|
||||
return
|
||||
}
|
||||
|
||||
throw IntegrationException()
|
||||
}
|
||||
|
||||
override suspend fun fireEvent(eventType: String, eventData: Map<String, Any>) {
|
||||
var wasSuccess = false
|
||||
|
||||
|
|
|
@ -59,6 +59,12 @@ interface IntegrationService {
|
|||
@Body request: IntegrationRequest
|
||||
): Response<ResponseBody>
|
||||
|
||||
@POST
|
||||
suspend fun scanTag(
|
||||
@Url url: HttpUrl,
|
||||
@Body request: IntegrationRequest
|
||||
): Response<ResponseBody>
|
||||
|
||||
@POST
|
||||
suspend fun fireEvent(
|
||||
@Url url: HttpUrl,
|
||||
|
|
|
@ -37,6 +37,8 @@ interface IntegrationRepository {
|
|||
|
||||
suspend fun callService(domain: String, service: String, serviceData: HashMap<String, Any>)
|
||||
|
||||
suspend fun scanTag(data: HashMap<String, Any>)
|
||||
|
||||
suspend fun fireEvent(eventType: String, eventData: Map<String, Any>)
|
||||
|
||||
suspend fun registerSensor(sensorRegistration: SensorRegistration<Any>)
|
||||
|
|
|
@ -23,6 +23,8 @@ interface IntegrationUseCase {
|
|||
|
||||
suspend fun fireEvent(eventType: String, eventData: Map<String, Any>)
|
||||
|
||||
suspend fun scanTag(data: HashMap<String, Any>)
|
||||
|
||||
suspend fun getZones(): Array<Entity<ZoneAttributes>>
|
||||
|
||||
suspend fun setZoneTrackingEnabled(enabled: Boolean)
|
||||
|
|
|
@ -48,6 +48,10 @@ class IntegrationUseCaseImpl @Inject constructor(
|
|||
return integrationRepository.fireEvent(eventType, eventData)
|
||||
}
|
||||
|
||||
override suspend fun scanTag(data: HashMap<String, Any>) {
|
||||
return integrationRepository.scanTag(data)
|
||||
}
|
||||
|
||||
override suspend fun getZones(): Array<Entity<ZoneAttributes>> {
|
||||
return integrationRepository.getZones()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue