mirror of
https://github.com/home-assistant/android
synced 2024-10-15 20:43:06 +00:00
Convert NFC tags interface to Compose and update design (#2441)
* Convert NFC tags interface to Compose - Rebuild the NFC tags interface with Compose instead of multiple fragments, move more functionality to the NfcViewModel - Design updates to match other settings screens (white top app bar, additional icons, don't use text inputs for showing data) - Update strings for consistency - Update Navigation version to latest stable, and remove non-Compose dependency because it is no longer required * Update NFC trigger example - Add slightly more text to explain how to use it as a trigger in the visual automation editor, which most users will use - Add an easier example for scanning the tag, and rename the existing example to clarify that it is only for this device * Add link to tag documentation to top app bar * Handle NFC turned off - Check to see if NFC is actually turned on when the user opens settings, and prompt them to turn it on if required. Also do the same for the tag writing screen because that might be opened directly by the frontend. - Update strings to always use 'device' instead of a mix of 'device' and 'phone' * Add option to change ID written to tag - Allow the user to change the ID that is written to the NFC tag when using the 'write tag' button from the app settings, to match frontend and iOS which also allow the user to change the tag ID * Don't allow setting state outside NfcViewModel * Simplify device trigger example even further * Don't duplicate tag data extraction code * Also convert tag reader activity to Compose - Shouldn't be seen in most cases by users
This commit is contained in:
parent
6c64b31f7e
commit
8ce64f5ed6
|
@ -151,8 +151,6 @@ dependencies {
|
|||
implementation("androidx.constraintlayout:constraintlayout:2.1.3")
|
||||
implementation("androidx.recyclerview:recyclerview:1.2.1")
|
||||
implementation("androidx.preference:preference-ktx:1.1.1")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.3.5")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.3.5")
|
||||
implementation("com.google.android.material:material:1.5.0")
|
||||
|
||||
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||||
|
@ -191,7 +189,7 @@ dependencies {
|
|||
implementation("androidx.compose.ui:ui:1.1.1")
|
||||
implementation("androidx.compose.ui:ui-tooling:1.1.1")
|
||||
implementation("androidx.activity:activity-compose:1.4.0")
|
||||
implementation("androidx.navigation:navigation-compose:2.4.0-rc01")
|
||||
implementation("androidx.navigation:navigation-compose:2.4.2")
|
||||
implementation("com.google.android.material:compose-theme-adapter:1.1.3")
|
||||
implementation("com.google.accompanist:accompanist-appcompat-theme:0.23.1")
|
||||
|
||||
|
|
|
@ -353,7 +353,8 @@
|
|||
<activity
|
||||
android:name=".nfc.NfcSetupActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/nfc_title_nfc_setup" />
|
||||
android:label="@string/nfc_title_nfc_setup"
|
||||
android:theme="@style/Theme.HomeAssistant.Config" />
|
||||
|
||||
<activity android:name=".share.ShareActivity"
|
||||
android:exported="true">
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.app.Activity
|
|||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.net.Uri
|
||||
import android.nfc.NdefMessage
|
||||
import android.nfc.NdefRecord
|
||||
import android.nfc.NfcAdapter
|
||||
|
@ -14,6 +15,16 @@ import io.homeassistant.companion.android.BuildConfig
|
|||
import java.io.IOException
|
||||
|
||||
object NFCUtil {
|
||||
fun extractUrlFromNFCIntent(intent: Intent): Uri? {
|
||||
if (intent.action != NfcAdapter.ACTION_NDEF_DISCOVERED && intent.action != NfcAdapter.ACTION_TECH_DISCOVERED) {
|
||||
return null
|
||||
}
|
||||
|
||||
val rawMessages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
|
||||
val ndefMessage = rawMessages?.get(0) as NdefMessage?
|
||||
return ndefMessage?.records?.get(0)?.toUri()
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun createNFCMessage(url: String, intent: Intent?): Boolean {
|
||||
val nfcRecord = NdefRecord.createUri(url)
|
||||
|
|
|
@ -1,107 +0,0 @@
|
|||
package io.homeassistant.companion.android.nfc
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
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.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.databinding.FragmentNfcEditBinding
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
/**
|
||||
* A simple [Fragment] subclass as the second destination in the navigation.
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class NfcEditFragment : Fragment() {
|
||||
|
||||
val TAG = NfcEditFragment::class.simpleName
|
||||
|
||||
private val mainScope: CoroutineScope = CoroutineScope(Dispatchers.Main + Job())
|
||||
|
||||
private var _binding: FragmentNfcEditBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val viewModel: NfcViewModel by activityViewModels()
|
||||
|
||||
@Inject
|
||||
lateinit var integrationUseCase: IntegrationRepository
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
// Inflate the layout for this fragment
|
||||
_binding = FragmentNfcEditBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n", "HardwareIds")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val nfcReadObserver = Observer<String> { uuid ->
|
||||
binding.etTagIdentifierContent.setText(uuid)
|
||||
val deviceId = Settings.Secure.getString(requireActivity().contentResolver, Settings.Secure.ANDROID_ID)
|
||||
binding.etTagExampleTriggerContent.setText("- platform: event\n event_type: tag_scanned\n event_data:\n device_id: $deviceId\n tag_id: $uuid")
|
||||
}
|
||||
viewModel.nfcReadEvent.observe(viewLifecycleOwner, nfcReadObserver)
|
||||
|
||||
binding.btnTagDuplicate.setOnClickListener {
|
||||
viewModel.nfcWriteTagEvent.postValue(binding.etTagIdentifierContent.text.toString())
|
||||
findNavController().navigate(R.id.action_NFC_WRITE)
|
||||
}
|
||||
|
||||
binding.btnTagFireEvent.setOnClickListener {
|
||||
mainScope.launch {
|
||||
val uuid: String = viewModel.nfcReadEvent.value.toString()
|
||||
try {
|
||||
integrationUseCase.scanTag(
|
||||
hashMapOf("tag_id" to uuid)
|
||||
)
|
||||
Toast.makeText(activity, commonR.string.nfc_event_fired_success, Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(activity, commonR.string.nfc_event_fired_fail, Toast.LENGTH_LONG)
|
||||
.show()
|
||||
Log.e(TAG, "Unable to send tag to Home Assistant.", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.btnTagShareExampleTrigger.setOnClickListener {
|
||||
val sendIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_TEXT, binding.etTagExampleTriggerContent.text)
|
||||
type = "text/plain"
|
||||
}
|
||||
val shareIntent = Intent.createChooser(sendIntent, null)
|
||||
startActivity(shareIntent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
mainScope.cancel()
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package io.homeassistant.companion.android.nfc
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Observer
|
||||
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(R.layout.fragment_nfc_read) {
|
||||
|
||||
private val viewModel: NfcViewModel by activityViewModels()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val nfcReadObserver = Observer<String> {
|
||||
findNavController().navigate(R.id.action_NFC_EDIT)
|
||||
}
|
||||
viewModel.nfcReadEvent.observe(viewLifecycleOwner, nfcReadObserver)
|
||||
}
|
||||
}
|
|
@ -1,18 +1,22 @@
|
|||
package io.homeassistant.companion.android.nfc
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.nfc.NdefMessage
|
||||
import android.content.IntentFilter
|
||||
import android.nfc.NfcAdapter
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.composethemeadapter.MdcTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.homeassistant.companion.android.BaseActivity
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.nfc.views.LoadNfcView
|
||||
import io.homeassistant.companion.android.util.UrlHandler
|
||||
import kotlinx.coroutines.launch
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
@AndroidEntryPoint
|
||||
|
@ -23,11 +27,22 @@ class NfcSetupActivity : BaseActivity() {
|
|||
private var simpleWrite = false
|
||||
private var messageId: Int = -1
|
||||
|
||||
private val nfcStateChangedReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (intent?.action == NfcAdapter.ACTION_ADAPTER_STATE_CHANGED) viewModel.checkNfcEnabled()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG = NfcSetupActivity::class.simpleName
|
||||
const val EXTRA_TAG_VALUE = "tag_value"
|
||||
const val EXTRA_MESSAGE_ID = "message_id"
|
||||
|
||||
const val NAV_WELCOME = "nfc_welcome"
|
||||
const val NAV_READ = "nfc_read"
|
||||
const val NAV_WRITE = "nfc_write"
|
||||
const val NAV_EDIT = "nfc_edit"
|
||||
|
||||
fun newInstance(context: Context, tagId: String? = null, messageId: Int = -1): Intent {
|
||||
return Intent(context, NfcSetupActivity::class.java).apply {
|
||||
putExtra(EXTRA_MESSAGE_ID, messageId)
|
||||
|
@ -39,26 +54,37 @@ class NfcSetupActivity : BaseActivity() {
|
|||
|
||||
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)
|
||||
|
||||
intent.getStringExtra(EXTRA_TAG_VALUE)?.let {
|
||||
simpleWrite = true
|
||||
viewModel.nfcWriteTagEvent.postValue(it)
|
||||
viewModel.writeNewTagSimple(it)
|
||||
}
|
||||
|
||||
setContent {
|
||||
MdcTheme {
|
||||
LoadNfcView(
|
||||
viewModel = viewModel,
|
||||
startDestination = if (simpleWrite) NAV_WRITE else NAV_WELCOME,
|
||||
pressedUpAtRoot = { finish() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
mNfcAdapter = NfcAdapter.getDefaultAdapter(this)
|
||||
|
||||
messageId = intent.getIntExtra(EXTRA_MESSAGE_ID, -1)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewModel.checkNfcEnabled()
|
||||
mNfcAdapter?.let {
|
||||
NFCUtil.enableNFCInForeground(it, this, javaClass)
|
||||
}
|
||||
registerReceiver(
|
||||
nfcStateChangedReceiver,
|
||||
IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
@ -66,57 +92,47 @@ class NfcSetupActivity : BaseActivity() {
|
|||
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)
|
||||
unregisterReceiver(nfcStateChangedReceiver)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
|
||||
if (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action) {
|
||||
val nfcTagToWriteUUID = viewModel.nfcWriteTagEvent.value
|
||||
if (intent.action == NfcAdapter.ACTION_TECH_DISCOVERED) {
|
||||
lifecycleScope.launch {
|
||||
val nfcTagToWriteUUID = viewModel.nfcTagIdentifier
|
||||
|
||||
// Create new nfc tag
|
||||
if (nfcTagToWriteUUID == null) {
|
||||
val rawMessages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
|
||||
val ndefMessage = rawMessages?.firstOrNull() as NdefMessage?
|
||||
val url = ndefMessage?.records?.get(0)?.toUri().toString()
|
||||
val nfcTagId = UrlHandler.splitNfcTagId(url)
|
||||
if (nfcTagId == null) {
|
||||
Log.w(TAG, "Unable to read tag!")
|
||||
Toast.makeText(this, commonR.string.nfc_invalid_tag, Toast.LENGTH_LONG).show()
|
||||
} 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 = commonR.string.nfc_write_tag_success
|
||||
Toast.makeText(applicationContext, message, Toast.LENGTH_LONG).show()
|
||||
|
||||
viewModel.nfcReadEvent.value = nfcTagToWriteUUID
|
||||
viewModel.nfcWriteTagDoneEvent.value = nfcTagToWriteUUID
|
||||
// If we are a simple write it means the fontend asked us to write. This means
|
||||
// we should return the user as fast as possible back to the UI to continue what
|
||||
// they were doing!
|
||||
if (simpleWrite) {
|
||||
setResult(messageId)
|
||||
finish()
|
||||
// Create new nfc tag
|
||||
if (!viewModel.nfcEventShouldWrite) {
|
||||
val url = NFCUtil.extractUrlFromNFCIntent(intent)
|
||||
val nfcTagId = UrlHandler.splitNfcTagId(url)
|
||||
if (nfcTagId == null) {
|
||||
viewModel.onNfcReadEmpty()
|
||||
} else {
|
||||
viewModel.onNfcReadSuccess(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")
|
||||
|
||||
// If we are a simple write it means the frontend asked us to write. This means
|
||||
// we should return the user as fast as possible back to the UI to continue what
|
||||
// they were doing!
|
||||
if (simpleWrite) {
|
||||
val message = commonR.string.nfc_write_tag_success
|
||||
Toast.makeText(applicationContext, message, Toast.LENGTH_LONG).show()
|
||||
|
||||
setResult(messageId)
|
||||
finish()
|
||||
} else {
|
||||
viewModel.onNfcWriteSuccess(nfcTagToWriteUUID!!)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
viewModel.onNfcWriteFailure()
|
||||
Log.e(TAG, "Unable to write tag.", e)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
val message = commonR.string.nfc_write_tag_error
|
||||
Toast.makeText(applicationContext, message, Toast.LENGTH_LONG).show()
|
||||
Log.e(TAG, "Unable to write tag.", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,116 @@
|
|||
package io.homeassistant.companion.android.nfc
|
||||
|
||||
import android.app.Application
|
||||
import android.nfc.NfcAdapter
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.util.Navigator
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
class NfcViewModel : ViewModel() {
|
||||
@HiltViewModel
|
||||
class NfcViewModel @Inject constructor(
|
||||
private val integrationUseCase: IntegrationRepository,
|
||||
application: Application
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
// 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!")
|
||||
companion object {
|
||||
const val TAG = "NfcViewModel"
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
Log.i("NfcViewModel", "NfcViewModel destroyed!")
|
||||
var isNfcEnabled by mutableStateOf(false)
|
||||
private set
|
||||
var nfcTagIdentifier by mutableStateOf<String?>(null)
|
||||
private set
|
||||
var nfcIdentifierIsEditable by mutableStateOf(true)
|
||||
private set
|
||||
var nfcEventShouldWrite = false
|
||||
private set
|
||||
|
||||
val navigator = Navigator()
|
||||
|
||||
private val _nfcResultSnackbar = MutableSharedFlow<Int>()
|
||||
var nfcResultSnackbar = _nfcResultSnackbar.asSharedFlow()
|
||||
|
||||
fun setDestination(destination: String?) {
|
||||
nfcEventShouldWrite = nfcTagIdentifier != null && destination == NfcSetupActivity.NAV_WRITE
|
||||
}
|
||||
|
||||
fun postNewUUID() {
|
||||
nfcWriteTagEvent.postValue(UUID.randomUUID().toString())
|
||||
fun checkNfcEnabled() {
|
||||
isNfcEnabled = NfcAdapter.getDefaultAdapter(getApplication()).isEnabled
|
||||
}
|
||||
|
||||
fun setTagIdentifier(value: String) {
|
||||
if (nfcIdentifierIsEditable && value.trim().isNotEmpty()) nfcTagIdentifier = value
|
||||
}
|
||||
|
||||
fun writeNewTagSimple(value: String) {
|
||||
nfcTagIdentifier = value
|
||||
nfcIdentifierIsEditable = false
|
||||
// We don't need to perform navigation here because it will be set as the startDestination
|
||||
}
|
||||
|
||||
fun writeNewTag() {
|
||||
nfcTagIdentifier = UUID.randomUUID().toString()
|
||||
nfcIdentifierIsEditable = true
|
||||
navigator.navigateTo(NfcSetupActivity.NAV_WRITE)
|
||||
}
|
||||
|
||||
fun onNfcReadSuccess(identifier: String) {
|
||||
nfcTagIdentifier = identifier
|
||||
|
||||
navigator.navigateTo(
|
||||
Navigator.NavigatorItem(
|
||||
id = NfcSetupActivity.NAV_EDIT,
|
||||
popBackstackTo = NfcSetupActivity.NAV_WELCOME
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun onNfcReadEmpty() = _nfcResultSnackbar.emit(commonR.string.nfc_invalid_tag)
|
||||
|
||||
suspend fun onNfcWriteSuccess(identifier: String) {
|
||||
_nfcResultSnackbar.emit(commonR.string.nfc_write_tag_success)
|
||||
nfcTagIdentifier = identifier
|
||||
|
||||
navigator.navigateTo(
|
||||
Navigator.NavigatorItem(
|
||||
id = NfcSetupActivity.NAV_EDIT,
|
||||
popBackstackTo = NfcSetupActivity.NAV_WELCOME
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun onNfcWriteFailure() = _nfcResultSnackbar.emit(commonR.string.nfc_write_tag_error)
|
||||
|
||||
fun duplicateNfcTag() {
|
||||
nfcIdentifierIsEditable = false
|
||||
navigator.navigateTo(NfcSetupActivity.NAV_WRITE)
|
||||
}
|
||||
|
||||
fun fireNfcTagEvent() {
|
||||
viewModelScope.launch {
|
||||
nfcTagIdentifier?.let {
|
||||
try {
|
||||
integrationUseCase.scanTag(
|
||||
hashMapOf("tag_id" to it)
|
||||
)
|
||||
_nfcResultSnackbar.emit(commonR.string.nfc_event_fired_success)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to send tag to Home Assistant.", e)
|
||||
_nfcResultSnackbar.emit(commonR.string.nfc_event_fired_fail)
|
||||
}
|
||||
} ?: _nfcResultSnackbar.emit(commonR.string.nfc_event_fired_fail)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
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.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.databinding.FragmentNfcWelcomeBinding
|
||||
|
||||
/**
|
||||
* A simple [Fragment] subclass as the default destination in the navigation.
|
||||
*/
|
||||
class NfcWelcomeFragment : Fragment() {
|
||||
|
||||
private var _binding: FragmentNfcWelcomeBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val viewModel: NfcViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentNfcWelcomeBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
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)
|
||||
|
||||
binding.btnNfcRead.setOnClickListener {
|
||||
findNavController().navigate(R.id.action_NFC_READ)
|
||||
}
|
||||
|
||||
binding.btnNfcWrite.setOnClickListener {
|
||||
viewModel.postNewUUID()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
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.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.databinding.FragmentNfcWriteBinding
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
/**
|
||||
* A simple [Fragment] subclass as the second destination in the navigation.
|
||||
*/
|
||||
class NfcWriteFragment : Fragment() {
|
||||
|
||||
private var _binding: FragmentNfcWriteBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val viewModel: NfcViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentNfcWriteBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val nfcWriteTagObserver = Observer<String> {
|
||||
binding.tvInstructionsWriteNfc.text = getString(commonR.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)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
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) { 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"
|
||||
}
|
||||
}
|
|
@ -1,20 +1,19 @@
|
|||
package io.homeassistant.companion.android.nfc
|
||||
|
||||
import android.content.Intent
|
||||
import android.nfc.NdefMessage
|
||||
import android.net.Uri
|
||||
import android.nfc.NfcAdapter
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.composethemeadapter.MdcTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.homeassistant.companion.android.BaseActivity
|
||||
import io.homeassistant.companion.android.R
|
||||
import io.homeassistant.companion.android.common.data.integration.IntegrationRepository
|
||||
import io.homeassistant.companion.android.nfc.views.TagReaderView
|
||||
import io.homeassistant.companion.android.util.UrlHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
@ -22,62 +21,58 @@ import io.homeassistant.companion.android.common.R as commonR
|
|||
@AndroidEntryPoint
|
||||
class TagReaderActivity : BaseActivity() {
|
||||
|
||||
val TAG = TagReaderActivity::class.simpleName
|
||||
|
||||
private val mainScope: CoroutineScope = CoroutineScope(Dispatchers.Main + Job())
|
||||
companion object {
|
||||
const val TAG = "TagReaderActivity"
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var integrationUseCase: IntegrationRepository
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_tag_reader)
|
||||
|
||||
setSupportActionBar(findViewById(R.id.toolbar))
|
||||
setContent {
|
||||
MdcTheme {
|
||||
TagReaderView()
|
||||
}
|
||||
}
|
||||
|
||||
mainScope.launch {
|
||||
if (NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action) {
|
||||
val rawMessages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
|
||||
val ndefMessage = rawMessages?.get(0) as NdefMessage?
|
||||
val url = ndefMessage?.records?.get(0)?.toUri().toString()
|
||||
lifecycleScope.launch {
|
||||
if (intent.action == NfcAdapter.ACTION_NDEF_DISCOVERED || intent.action == Intent.ACTION_VIEW) {
|
||||
val isNfcTag = intent.action == NfcAdapter.ACTION_NDEF_DISCOVERED
|
||||
|
||||
val url =
|
||||
if (isNfcTag) NFCUtil.extractUrlFromNFCIntent(intent)
|
||||
else intent.data
|
||||
try {
|
||||
handleTag(url)
|
||||
handleTag(url, isNfcTag)
|
||||
} catch (e: Exception) {
|
||||
val message = commonR.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 = commonR.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()
|
||||
showProcessingError(isNfcTag)
|
||||
Log.e(TAG, "Unable to handle url (${if (isNfcTag) "nfc" else "qr"}}): $url", e)
|
||||
}
|
||||
}
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
mainScope.cancel()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private suspend fun handleTag(url: String) {
|
||||
private suspend fun handleTag(url: Uri?, isNfcTag: Boolean) {
|
||||
// https://www.home-assistant.io/tag/5f0ba733-172f-430d-a7f8-e4ad940c88d7
|
||||
|
||||
val nfcTagId = UrlHandler.splitNfcTagId(url)
|
||||
Log.d(TAG, "nfcTagId: $nfcTagId")
|
||||
Log.d(TAG, "Tag ID: $nfcTagId")
|
||||
if (nfcTagId != null) {
|
||||
integrationUseCase.scanTag(hashMapOf("tag_id" to nfcTagId))
|
||||
Log.d(TAG, "Tag scanned to HA successfully")
|
||||
} else {
|
||||
Toast.makeText(this, commonR.string.nfc_processing_tag_error, Toast.LENGTH_LONG).show()
|
||||
showProcessingError(isNfcTag)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun showProcessingError(isNfcTag: Boolean) {
|
||||
Toast.makeText(
|
||||
this,
|
||||
if (isNfcTag) commonR.string.nfc_processing_tag_error else commonR.string.qrcode_processing_tag_error,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
package io.homeassistant.companion.android.nfc.views
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.provider.Settings
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
@SuppressLint("HardwareIds")
|
||||
@Composable
|
||||
fun NfcEditView(
|
||||
identifier: String?,
|
||||
onDuplicateClicked: () -> Unit,
|
||||
onFireEventClicked: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
LazyColumn(contentPadding = PaddingValues(all = 16.dp)) {
|
||||
item {
|
||||
Text(
|
||||
text = stringResource(commonR.string.nfc_tag_identifier),
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
}
|
||||
item {
|
||||
NfcCodeContainer(text = identifier ?: "")
|
||||
}
|
||||
item {
|
||||
Row(modifier = Modifier.padding(top = 8.dp)) {
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.padding(end = 8.dp)
|
||||
.weight(1f),
|
||||
onClick = onDuplicateClicked
|
||||
) {
|
||||
Text(stringResource(commonR.string.nfc_btn_create_duplicate))
|
||||
}
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.weight(1f),
|
||||
onClick = onFireEventClicked
|
||||
) {
|
||||
Text(stringResource(commonR.string.nfc_btn_fire_event))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val deviceId = Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
|
||||
val tagTriggerExample = "- platform: tag\n tag_id: $identifier"
|
||||
val deviceTriggerExample = "- platform: tag\n tag_id: $identifier\n device_id: $deviceId"
|
||||
item {
|
||||
Text(
|
||||
text = stringResource(commonR.string.nfc_trigger_summary),
|
||||
modifier = Modifier.padding(top = 48.dp, bottom = 8.dp)
|
||||
)
|
||||
}
|
||||
item {
|
||||
NfcTriggerExample(
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
description = stringResource(commonR.string.nfc_trigger_any),
|
||||
example = tagTriggerExample
|
||||
)
|
||||
}
|
||||
item {
|
||||
NfcTriggerExample(
|
||||
description = stringResource(commonR.string.nfc_trigger_device),
|
||||
example = deviceTriggerExample
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NfcCodeContainer(
|
||||
text: String
|
||||
) {
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
color = colorResource(commonR.color.colorCodeBackground),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
SelectionContainer {
|
||||
Text(
|
||||
text = text,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
modifier = Modifier.padding(horizontal = 4.dp, vertical = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NfcTriggerExample(
|
||||
modifier: Modifier = Modifier,
|
||||
description: String,
|
||||
example: String
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
Column(modifier = modifier) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = description,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
IconButton(
|
||||
modifier = Modifier.padding(all = 8.dp),
|
||||
onClick = {
|
||||
val sendIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_TEXT, example)
|
||||
type = "text/plain"
|
||||
}
|
||||
val shareIntent = Intent.createChooser(sendIntent, null)
|
||||
context.startActivity(shareIntent)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Share,
|
||||
contentDescription = stringResource(commonR.string.nfc_btn_share)
|
||||
)
|
||||
}
|
||||
}
|
||||
NfcCodeContainer(text = example)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
package io.homeassistant.companion.android.nfc.views
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.HelpOutline
|
||||
import androidx.compose.material.rememberScaffoldState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import io.homeassistant.companion.android.nfc.NfcSetupActivity
|
||||
import io.homeassistant.companion.android.nfc.NfcViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
@Composable
|
||||
fun LoadNfcView(
|
||||
viewModel: NfcViewModel,
|
||||
startDestination: String,
|
||||
pressedUpAtRoot: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val navController = rememberNavController()
|
||||
val canNavigateUp = remember { mutableStateOf(false) }
|
||||
navController.addOnDestinationChangedListener { controller, destination, _ ->
|
||||
canNavigateUp.value = controller.previousBackStackEntry != null
|
||||
viewModel.setDestination(destination.route)
|
||||
}
|
||||
LaunchedEffect("navigation") {
|
||||
viewModel.navigator.flow.onEach {
|
||||
navController.navigate(it.id) {
|
||||
if (it.popBackstackTo != null) {
|
||||
popUpTo(it.popBackstackTo) { inclusive = it.popBackstackInclusive }
|
||||
}
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
|
||||
val scaffoldState = rememberScaffoldState()
|
||||
LaunchedEffect("snackbar") {
|
||||
viewModel.nfcResultSnackbar.onEach {
|
||||
if (it != 0) {
|
||||
scaffoldState.snackbarHostState.showSnackbar(context.getString(it))
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
scaffoldState = scaffoldState,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(commonR.string.nfc_title_settings)) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = {
|
||||
if (canNavigateUp.value) navController.navigateUp()
|
||||
else pressedUpAtRoot()
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.ArrowBack,
|
||||
contentDescription = stringResource(commonR.string.navigate_up)
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://companion.home-assistant.io/docs/integrations/universal-links"))
|
||||
context.startActivity(intent)
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.HelpOutline,
|
||||
contentDescription = stringResource(commonR.string.get_help),
|
||||
tint = colorResource(commonR.color.colorOnBackground)
|
||||
)
|
||||
}
|
||||
},
|
||||
backgroundColor = colorResource(commonR.color.colorBackground),
|
||||
contentColor = colorResource(commonR.color.colorOnBackground)
|
||||
)
|
||||
}
|
||||
) {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = startDestination,
|
||||
modifier = Modifier
|
||||
) {
|
||||
composable(NfcSetupActivity.NAV_WELCOME) {
|
||||
NfcWelcomeView(
|
||||
isNfcEnabled = viewModel.isNfcEnabled,
|
||||
onReadClicked = { viewModel.navigator.navigateTo(NfcSetupActivity.NAV_READ) },
|
||||
onWriteClicked = { viewModel.writeNewTag() }
|
||||
)
|
||||
}
|
||||
composable(NfcSetupActivity.NAV_READ) {
|
||||
NfcReadView()
|
||||
}
|
||||
composable(NfcSetupActivity.NAV_WRITE) {
|
||||
NfcWriteView(
|
||||
isNfcEnabled = viewModel.isNfcEnabled,
|
||||
identifier = viewModel.nfcTagIdentifier,
|
||||
onSetIdentifier = if (viewModel.nfcIdentifierIsEditable) {
|
||||
{ viewModel.setTagIdentifier(it) }
|
||||
} else null
|
||||
)
|
||||
}
|
||||
composable(NfcSetupActivity.NAV_EDIT) {
|
||||
NfcEditView(
|
||||
identifier = viewModel.nfcTagIdentifier,
|
||||
onDuplicateClicked = { viewModel.duplicateNfcTag() },
|
||||
onFireEventClicked = { viewModel.fireNfcTagEvent() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package io.homeassistant.companion.android.nfc.views
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Nfc
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
@Composable
|
||||
fun NfcReadView() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(all = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Nfc,
|
||||
contentDescription = null
|
||||
)
|
||||
Text(
|
||||
text = stringResource(commonR.string.nfc_read_tag_instructions),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.75f)
|
||||
.padding(top = 16.dp)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package io.homeassistant.companion.android.nfc.views
|
||||
|
||||
import android.content.Intent
|
||||
import android.provider.Settings
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
@Composable
|
||||
fun NfcWelcomeView(
|
||||
isNfcEnabled: Boolean,
|
||||
onReadClicked: () -> Unit,
|
||||
onWriteClicked: () -> Unit,
|
||||
) {
|
||||
LazyColumn(contentPadding = PaddingValues(all = 16.dp)) {
|
||||
item {
|
||||
Text(stringResource(commonR.string.nfc_welcome_message))
|
||||
}
|
||||
item {
|
||||
Row(modifier = Modifier.padding(top = 16.dp)) {
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.padding(end = 8.dp)
|
||||
.weight(1f),
|
||||
enabled = isNfcEnabled,
|
||||
onClick = onReadClicked
|
||||
) {
|
||||
Text(stringResource(commonR.string.nfc_btn_read_tag))
|
||||
}
|
||||
Button(
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.weight(1f),
|
||||
enabled = isNfcEnabled,
|
||||
onClick = onWriteClicked
|
||||
) {
|
||||
Text(stringResource(commonR.string.nfc_btn_write_tag))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isNfcEnabled) {
|
||||
item {
|
||||
Text(
|
||||
text = stringResource(commonR.string.nfc_welcome_turnon),
|
||||
modifier = Modifier.padding(top = 48.dp)
|
||||
)
|
||||
}
|
||||
item {
|
||||
val context = LocalContext.current
|
||||
TextButton(
|
||||
contentPadding = PaddingValues(horizontal = 0.dp),
|
||||
onClick = {
|
||||
context.startActivity(Intent(Settings.ACTION_NFC_SETTINGS))
|
||||
}
|
||||
) {
|
||||
Text(stringResource(commonR.string.settings))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package io.homeassistant.companion.android.nfc.views
|
||||
|
||||
import android.content.Intent
|
||||
import android.provider.Settings
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.mikepenz.iconics.compose.Image
|
||||
import com.mikepenz.iconics.typeface.library.community.material.CommunityMaterial
|
||||
import io.homeassistant.companion.android.util.compose.MdcAlertDialog
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
@Composable
|
||||
fun NfcWriteView(
|
||||
isNfcEnabled: Boolean,
|
||||
identifier: String?,
|
||||
onSetIdentifier: ((String) -> Unit)? = null
|
||||
) {
|
||||
var identifierDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (identifierDialog && onSetIdentifier != null) {
|
||||
NfcWriteIdentifierDialog(
|
||||
onCancel = { identifierDialog = false },
|
||||
onSubmit = {
|
||||
onSetIdentifier(it)
|
||||
identifierDialog = false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(all = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Image(
|
||||
asset = CommunityMaterial.Icon3.cmd_nfc_tap,
|
||||
colorFilter = ColorFilter.tint(MaterialTheme.colors.onSurface),
|
||||
)
|
||||
Text(
|
||||
text =
|
||||
if (isNfcEnabled) stringResource(commonR.string.nfc_write_tag_instructions, identifier ?: "")
|
||||
else stringResource(commonR.string.nfc_write_tag_turnon),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.75f)
|
||||
.padding(top = 16.dp)
|
||||
)
|
||||
if (!isNfcEnabled) {
|
||||
val context = LocalContext.current
|
||||
TextButton(onClick = {
|
||||
context.startActivity(Intent(Settings.ACTION_NFC_SETTINGS))
|
||||
}) {
|
||||
Text(stringResource(commonR.string.settings))
|
||||
}
|
||||
} else if (onSetIdentifier != null) {
|
||||
TextButton(onClick = { identifierDialog = true }) {
|
||||
Text(stringResource(commonR.string.nfc_write_tag_change))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NfcWriteIdentifierDialog(
|
||||
onCancel: () -> Unit,
|
||||
onSubmit: (String) -> Unit
|
||||
) {
|
||||
val inputValue = remember { mutableStateOf("") }
|
||||
|
||||
MdcAlertDialog(
|
||||
onDismissRequest = onCancel,
|
||||
title = { Text(stringResource(commonR.string.nfc_write_tag_enter_identifier)) },
|
||||
content = {
|
||||
OutlinedTextField(
|
||||
value = inputValue.value,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
onValueChange = { input -> inputValue.value = input }
|
||||
)
|
||||
},
|
||||
onCancel = onCancel,
|
||||
onSave = { onSubmit(inputValue.value) }
|
||||
)
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package io.homeassistant.companion.android.nfc.views
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.homeassistant.companion.android.common.R
|
||||
|
||||
@Composable
|
||||
fun TagReaderView() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(all = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
Text(
|
||||
text = stringResource(R.string.tag_reader_title),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.75f)
|
||||
.padding(top = 16.dp)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package io.homeassistant.companion.android.util
|
||||
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
|
||||
class Navigator {
|
||||
private val _sharedFlow = MutableSharedFlow<NavigatorItem>(extraBufferCapacity = 1)
|
||||
val flow = _sharedFlow.asSharedFlow()
|
||||
|
||||
fun navigateTo(navTarget: String) {
|
||||
_sharedFlow.tryEmit(NavigatorItem(navTarget))
|
||||
}
|
||||
|
||||
fun navigateTo(navItem: NavigatorItem) {
|
||||
_sharedFlow.tryEmit(navItem)
|
||||
}
|
||||
|
||||
data class NavigatorItem(
|
||||
val id: String,
|
||||
val popBackstackTo: String? = null,
|
||||
val popBackstackInclusive: Boolean = false
|
||||
)
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package io.homeassistant.companion.android.util
|
||||
|
||||
import android.net.Uri
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import java.net.URL
|
||||
|
||||
|
@ -22,7 +23,7 @@ object UrlHandler {
|
|||
return Regex("^https?://").containsMatchIn(it.toString())
|
||||
}
|
||||
|
||||
fun splitNfcTagId(it: String?): String? {
|
||||
fun splitNfcTagId(it: Uri?): String? {
|
||||
val matches =
|
||||
Regex("^https?://www\\.home-assistant\\.io/tag/(.*)").find(
|
||||
it.toString()
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
<?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"
|
||||
android:id="@+id/nfc_view"
|
||||
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:theme="@style/ThemeOverlay.HomeAssistant.ActionBar" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<include layout="@layout/content_nfc_setup" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -1,36 +0,0 @@
|
|||
<?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>
|
|
@ -1,17 +0,0 @@
|
|||
<?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>
|
|
@ -1,83 +0,0 @@
|
|||
<?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>
|
|
@ -1,19 +0,0 @@
|
|||
<?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>
|
|
@ -1,46 +0,0 @@
|
|||
<?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>
|
|
@ -1,20 +0,0 @@
|
|||
<?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>
|
|
@ -1,55 +0,0 @@
|
|||
<?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>
|
|
@ -21,4 +21,5 @@
|
|||
<color name="colorWidgetButtonLabel">#E6E6E6</color>
|
||||
<color name="colorActionBarPopupBackground">#2B2B2B</color>
|
||||
<color name="colorLaunchScreenBackground">#111111</color>
|
||||
<color name="colorCodeBackground">#282828</color>
|
||||
</resources>
|
|
@ -22,4 +22,5 @@
|
|||
<color name="colorWidgetButtonLabel">#3A3A3A</color>
|
||||
<color name="colorActionBarPopupBackground">@android:color/white</color>
|
||||
<color name="colorLaunchScreenBackground">#fafafa</color>
|
||||
<color name="colorCodeBackground">#f5f5f5</color>
|
||||
</resources>
|
||||
|
|
|
@ -309,30 +309,32 @@
|
|||
<string name="areas">Areas</string>
|
||||
<string name="more_entities">More entities</string>
|
||||
<string name="need_help">Need Help?</string>
|
||||
<string name="navigate_up">Navigate up</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_share">Share</string>
|
||||
<string name="nfc_btn_write_tag">Write NFC Tag</string>
|
||||
<string name="nfc_edit_fragment_label">NFC Edit Fragment</string>
|
||||
<string name="nfc_event_fired_fail">Firing event failed. Please try again.</string>
|
||||
<string name="nfc_event_fired_fail">Firing event failed, please try again</string>
|
||||
<string name="nfc_event_fired_success">Event fired</string>
|
||||
<string name="nfc_example_trigger">Example Trigger</string>
|
||||
<string name="nfc_trigger_summary">Use this tag as a trigger for an automation. Select the \'Tag\' trigger type and choose the identifier in the visual editor, or use the following YAML code:</string>
|
||||
<string name="nfc_trigger_any">Scanned by any device:</string>
|
||||
<string name="nfc_trigger_device">Scanned by this device:</string>
|
||||
<string name="nfc_invalid_tag">This tag does not contain Home Assistant data</string>
|
||||
<string name="nfc_processing_tag_error">Error while processing nfc tag</string>
|
||||
<string name="nfc_read_fragment_label">NFC Read Fragment</string>
|
||||
<string name="nfc_read_tag_instructions">Place your phone over the nfc tag to read it.</string>
|
||||
<string name="nfc_processing_tag_error">Error while processing NFC tag</string>
|
||||
<string name="nfc_read_tag_instructions">Hold your device near an NFC tag</string>
|
||||
<string name="nfc_summary">Setup NFC Tags</string>
|
||||
<string name="nfc_tag_identifier">Tag Identifier</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_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="nfc_write_fragment_label">NFC Write Fragment</string>
|
||||
<string name="nfc_write_tag_error">Error while writing nfc tag</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_success">Successfully wrote nfc tag</string>
|
||||
<string name="nfc_write_tag_too_early">Please fill out the form first</string>
|
||||
<string name="nfc_welcome_turnon">NFC is turned off. To get started, please turn on NFC in your device\'s settings.</string>
|
||||
<string name="nfc_write_tag_error">Error while writing NFC tag</string>
|
||||
<string name="nfc_write_tag_instructions">Hold your device near an NFC tag to write the following identifier to it:\n\n%1$s</string>
|
||||
<string name="nfc_write_tag_change">Change identifier</string>
|
||||
<string name="nfc_write_tag_enter_identifier">Tag identifier</string>
|
||||
<string name="nfc_write_tag_turnon">Please turn on NFC to enable writing to an NFC tag</string>
|
||||
<string name="nfc_write_tag_success">Successfully wrote NFC tag</string>
|
||||
<string name="no_notifications_summary">You have not received any notifications yet</string>
|
||||
<string name="no_notifications">No Notifications</string>
|
||||
<string name="no_supported_entities">No Supported Entities</string>
|
||||
|
@ -374,7 +376,7 @@
|
|||
<string name="privacy_policy">Privacy Policy</string>
|
||||
<string name="privacy_url">https://www.home-assistant.io/privacy</string>
|
||||
<string name="profile">Profile</string>
|
||||
<string name="qrcode_processing_tag_error">Error while processing qrcode tag</string>
|
||||
<string name="qrcode_processing_tag_error">Error while processing QR code tag</string>
|
||||
<string name="quick_settings">Quick Settings</string>
|
||||
<string name="rate_limit_notification.body">You have now sent more than %s notifications today. You will not receive new notifications until midnight UTC.</string>
|
||||
<string name="rate_limit_notification.title">Notifications Rate Limited</string>
|
||||
|
|
Loading…
Reference in a new issue