From 5a90a0658029311efaa6d65f4ffb09aff4946d49 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 9 Apr 2020 11:03:17 -0700 Subject: [PATCH] get ready for translations --- app/src/main/AndroidManifest.xml | 5 +- .../java/com/geeksville/mesh/MainActivity.kt | 1 + .../geeksville/mesh/ui/MessagesFragment.kt | 2 + .../geeksville/mesh/ui/SettingsFragment.kt | 337 ++++++------------ app/src/main/res/layout/settings_fragment.xml | 4 +- app/src/main/res/values/strings.xml | 8 +- geeksville-androidlib | 2 +- 7 files changed, 128 insertions(+), 231 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 70757f77d..aab32d86a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -34,7 +34,9 @@ - + diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index d38d150b5..7fb09f22b 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -283,6 +283,7 @@ class MainActivity : AppCompatActivity(), Logging, pager.adapter = tabsAdapter pager.isUserInputEnabled = false // Gestures for screen switching doesn't work so good with the map view + // pager.offscreenPageLimit = 0 // Don't keep any offscreen pages around, because we want to make sure our bluetooth scanning stops TabLayoutMediator(tab_layout, pager) { tab, position -> // tab.text = tabInfos[position].text // I think it looks better with icons only tab.icon = getDrawable(tabInfos[position].icon) diff --git a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt index 56a2b324f..600943e40 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt @@ -151,6 +151,8 @@ class MessagesFragment : ScreenFragment("Messages"), Logging { val str = messageInputText.text.toString() model.messagesState.sendMessage(str) messageInputText.setText("") // blow away the string the user just entered + + // requireActivity().hideKeyboard() } messageListView.adapter = messagesAdapter diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt index 842973124..90b4ade79 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -13,14 +13,16 @@ import android.os.ParcelUuid import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.inputmethod.EditorInfo import android.widget.RadioButton import androidx.fragment.app.activityViewModels import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer import com.geeksville.android.Logging +import com.geeksville.android.hideKeyboard import com.geeksville.mesh.R +import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.service.RadioInterfaceService import com.geeksville.util.exceptionReporter import kotlinx.android.synthetic.main.settings_fragment.* @@ -50,97 +52,99 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { var selectedMacAddr: String? = null val errorText = object : MutableLiveData(null) {} - val devices = object : LiveData>(mapOf()) { - private var scanner: BluetoothLeScanner? = null + private var scanner: BluetoothLeScanner? = null - private val scanCallback = object : ScanCallback() { - override fun onScanFailed(errorCode: Int) { - val msg = "Unexpected bluetooth scan failure: $errorCode" - // error code2 seeems to be indicate hung bluetooth stack - errorText.value = msg - } - - // For each device that appears in our scan, ask for its GATT, when the gatt arrives, - // check if it is an eligable device and store it in our list of candidates - // if that device later disconnects remove it as a candidate - override fun onScanResult(callbackType: Int, result: ScanResult) { - - val addr = result.device.address - // prevent logspam because weill get get lots of redundant scan results - val isBonded = result.device.bondState == BluetoothDevice.BOND_BONDED - val oldDevs = value!! - val oldEntry = oldDevs[addr] - if (oldEntry == null || oldEntry.bonded != isBonded) { - val entry = BTScanEntry( - result.device.name, - addr, - isBonded - ) - debug("onScanResult ${entry}") - - // If nothing was selected, by default select the first thing we see - if (selectedMacAddr == null && entry.bonded) - changeSelection(context, addr) - - value = oldDevs + Pair(addr, entry) // trigger gui updates - } - } + private val scanCallback = object : ScanCallback() { + override fun onScanFailed(errorCode: Int) { + val msg = "Unexpected bluetooth scan failure: $errorCode" + // error code2 seeems to be indicate hung bluetooth stack + errorText.value = msg } - private fun stopScan() { - if (scanner != null) { - debug("stopping scan") - try { - scanner?.stopScan(scanCallback) - } catch (ex: Throwable) { - warn("Ignoring error stopping scan, probably BT adapter was disabled suddenly: ${ex.message}") - } - scanner = null - } - } + // For each device that appears in our scan, ask for its GATT, when the gatt arrives, + // check if it is an eligable device and store it in our list of candidates + // if that device later disconnects remove it as a candidate + override fun onScanResult(callbackType: Int, result: ScanResult) { - private fun startScan() { - debug("BTScan component active") - selectedMacAddr = RadioInterfaceService.getBondedDeviceAddress(context) - - if (bluetoothAdapter == null) { - warn("No bluetooth adapter. Running under emulation?") - - val testnodes = listOf( - BTScanEntry("Meshtastic_ab12", "xx", false), - BTScanEntry("Meshtastic_32ac", "xb", true) + val addr = result.device.address + // prevent logspam because weill get get lots of redundant scan results + val isBonded = result.device.bondState == BluetoothDevice.BOND_BONDED + val oldDevs = devices.value!! + val oldEntry = oldDevs[addr] + if (oldEntry == null || oldEntry.bonded != isBonded) { + val entry = BTScanEntry( + result.device.name, + addr, + isBonded ) - - value = (testnodes.map { it.macAddress to it }).toMap() + debug("onScanResult ${entry}") // If nothing was selected, by default select the first thing we see - if (selectedMacAddr == null) - changeSelection(context, testnodes.first().macAddress) - } else { - /// The following call might return null if the user doesn't have bluetooth access permissions - val s: BluetoothLeScanner? = bluetoothAdapter.bluetoothLeScanner + if (selectedMacAddr == null && entry.bonded) + changeSelection(context, addr) - if (s == null) { - errorText.value = - "This application requires bluetooth access. Please grant access in android settings." - } else { - debug("starting scan") - - // filter and only accept devices that have a sw update service - val filter = - ScanFilter.Builder() - .setServiceUuid(ParcelUuid(RadioInterfaceService.BTM_SERVICE_UUID)) - .build() - - val settings = - ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) - .build() - s.startScan(listOf(filter), settings, scanCallback) - scanner = s - } + devices.value = oldDevs + Pair(addr, entry) // trigger gui updates } } + } + + fun stopScan() { + if (scanner != null) { + debug("stopping scan") + try { + scanner?.stopScan(scanCallback) + } catch (ex: Throwable) { + warn("Ignoring error stopping scan, probably BT adapter was disabled suddenly: ${ex.message}") + } + scanner = null + } + } + + fun startScan() { + debug("BTScan component active") + selectedMacAddr = RadioInterfaceService.getBondedDeviceAddress(context) + + if (bluetoothAdapter == null) { + warn("No bluetooth adapter. Running under emulation?") + + val testnodes = listOf( + BTScanEntry("Meshtastic_ab12", "xx", false), + BTScanEntry("Meshtastic_32ac", "xb", true) + ) + + devices.value = (testnodes.map { it.macAddress to it }).toMap() + + // If nothing was selected, by default select the first thing we see + if (selectedMacAddr == null) + changeSelection(context, testnodes.first().macAddress) + } else { + /// The following call might return null if the user doesn't have bluetooth access permissions + val s: BluetoothLeScanner? = bluetoothAdapter.bluetoothLeScanner + + if (s == null) { + errorText.value = + "This application requires bluetooth access. Please grant access in android settings." + } else { + debug("starting scan") + + // filter and only accept devices that have a sw update service + val filter = + ScanFilter.Builder() + .setServiceUuid(ParcelUuid(RadioInterfaceService.BTM_SERVICE_UUID)) + .build() + + val settings = + ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) + .build() + s.startScan(listOf(filter), settings, scanCallback) + scanner = s + } + } + } + + val devices = object : MutableLiveData>(mapOf()) { + /** * Called when the number of active observers change from 1 to 0. @@ -157,18 +161,6 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { super.onInactive() stopScan() } - - /** - * Called when the number of active observers change to 1 from 0. - * - * - * This callback can be used to know that this LiveData is being used thus should be kept - * up to date. - */ - override fun onActive() { - super.onActive() - startScan() - } } /// Called by the GUI when a new device has been selected by the user @@ -230,6 +222,7 @@ class BTScanModel(app: Application) : AndroidViewModel(app), Logging { class SettingsFragment : ScreenFragment("Settings"), Logging { private val scanModel: BTScanModel by activityViewModels() + private val model: UIViewModel by activityViewModels() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -241,6 +234,24 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + model.ownerName.observe(viewLifecycleOwner, Observer { name -> + usernameEditText.setText(name) + }) + + usernameEditText.on(EditorInfo.IME_ACTION_DONE) { + debug("did IME action") + val n = usernameEditText.text.toString().trim() + if (n.isNotEmpty()) + model.setOwner(requireContext(), n) + + requireActivity().hideKeyboard() + } + + analyticsOkayCheckbox.setOnCheckedChangeListener { _, isChecked -> + // FIXME, preserve this in settings + analyticsOkayCheckbox.isChecked = true // so users will complain and I'll fix the bug + } + scanModel.errorText.observe(viewLifecycleOwner, Observer { errMsg -> if (errMsg != null) { scanStatusText.text = errMsg @@ -251,10 +262,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { // Remove the old radio buttons and repopulate deviceRadioGroup.removeAllViews() - var hasBonded = false // Have any of our devices been bonded devices.values.forEach { device -> - hasBonded = hasBonded || device.bonded - val b = RadioButton(requireActivity()) b.text = device.name b.id = View.generateViewId() @@ -268,140 +276,21 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } } + val hasBonded = RadioInterfaceService.getBondedDeviceAddress(requireContext()) != null + // get rid of the warning text once at least one device is paired warningNotPaired.visibility = if (hasBonded) View.GONE else View.VISIBLE }) } -} + override fun onPause() { + super.onPause() + scanModel.stopScan() + } -/* -import androidx.compose.Composable -import androidx.compose.state -import androidx.ui.core.ContextAmbient -import androidx.ui.foundation.Text -import androidx.ui.input.ImeAction -import androidx.ui.layout.* -import androidx.ui.material.MaterialTheme -import androidx.ui.text.TextStyle -import androidx.ui.tooling.preview.Preview -import androidx.ui.unit.dp -import com.geeksville.android.Logging -import com.geeksville.mesh.model.MessagesState -import com.geeksville.mesh.model.UIState -import com.geeksville.mesh.service.RadioInterfaceService - - -object SettingsLog : Logging - -@Composable -fun SettingsContent() { - //val typography = MaterialTheme.typography() - - val context = ContextAmbient.current - Column(modifier = LayoutSize.Fill + LayoutPadding(16.dp)) { - - Row { - Text("Your name ", modifier = LayoutGravity.Center) - - val name = state { UIState.ownerName } - StyledTextField( - value = name.value, - onValueChange = { name.value = it }, - textStyle = TextStyle( - color = palette.onSecondary.copy(alpha = 0.8f) - ), - imeAction = ImeAction.Done, - onImeActionPerformed = { - MessagesState.info("did IME action") - val n = name.value.trim() - if (n.isNotEmpty()) - UIState.setOwner(context, n) - }, - hintText = "Type your name here...", - modifier = LayoutGravity.Center - ) - } - - BTScanScreen() - - val bonded = RadioInterfaceService.getBondedDeviceAddress(context) != null - if (!bonded) { - - val typography = MaterialTheme.typography - - Text( - text = - """ - You haven't yet paired a Meshtastic compatible radio with this phone. - - This application is an early alpha release, if you find problems please post on our website chat. - - For more information see our web page - www.meshtastic.org. - """.trimIndent(), style = typography.body2 - ) - } + override fun onResume() { + super.onResume() + scanModel.startScan() } } -*/ - -/* -@Model -object ScanUIState { - -} - -/// FIXME, remove once compose has better lifecycle management -object ScanState : Logging { - -} - - - - - -@Composable -fun BTScanScreen() { - val context = ContextAmbient.current - - - // FIXME - remove onCommit now that we have a fragement to run in - - } - - Column { - if (ScanUIState.errorText != null) { - Text(text = ScanUIState.errorText!!) - } else { - if (ScanUIState.devices.isEmpty()) { - Text( - text = "Looking for Meshtastic devices... (zero found)", - modifier = LayoutGravity.Center - ) - - CircularProgressIndicator() // Show that we are searching still - } else { - // val allPaired = bluetoothAdapter?.bondedDevices.orEmpty().map { it.address }.toSet() - - RadioGroup { - Column { - ScanUIState.devices.values.forEach { - // disabled pending https://issuetracker.google.com/issues/149528535 - ProvideEmphasis(emphasis = if (it.bonded) MaterialTheme.emphasisLevels.high else MaterialTheme.emphasisLevels.disabled) { - RadioGroupTextItem( - selected = (it.isSelected), - onSelect = { - - }, - text = it.name - ) - } - } - } - } - } - } - } -} -*/ \ No newline at end of file diff --git a/app/src/main/res/layout/settings_fragment.xml b/app/src/main/res/layout/settings_fragment.xml index 6d2055166..c68655d8c 100644 --- a/app/src/main/res/layout/settings_fragment.xml +++ b/app/src/main/res/layout/settings_fragment.xml @@ -31,9 +31,11 @@ app:layout_constraintTop_toTopOf="parent"> + android:singleLine="true" + android:imeOptions="actionDone" /> - Meshtastic + Meshtastic Settings Channel Name Channel options @@ -10,7 +10,7 @@ application icon Unknown Username User avatar - 2.13 km + 2.13 km hey I found the cache, it is over here next to the big tiger. I\'m kinda scared. Some Username Send Text @@ -19,6 +19,6 @@ Your Name Anonymous usage statistics and crash reports. Looking for Meshtastic devices... - Meshtastic_ac23 - Meshtastic_1267 + Meshtastic_ac23 + Meshtastic_1267 diff --git a/geeksville-androidlib b/geeksville-androidlib index 953a65718..ebc40c05f 160000 --- a/geeksville-androidlib +++ b/geeksville-androidlib @@ -1 +1 @@ -Subproject commit 953a657184b2d4cde8a0867d1b520e1f7190cfd6 +Subproject commit ebc40c05fd8c30aabb3070468627e5fe6ae59cd5