editing and saving channels kinda works

pull/28/head
geeksville 2020-04-09 16:33:42 -07:00
rodzic 1d32dad6de
commit 2b588ac7e7
7 zmienionych plików z 137 dodań i 64 usunięć

Wyświetl plik

@ -385,9 +385,8 @@ class MainActivity : AppCompatActivity(), Logging,
if (connected == MeshService.ConnectionState.CONNECTED) {
// everytime the radio reconnects, we slam in our current owner data, the radio is smart enough to only broadcast if needed
model.setOwner(this)
model.setOwner()
model.meshService?.let { service ->
debug("Getting latest radioconfig from service")
model.radioConfig.value =

Wyświetl plik

@ -11,9 +11,7 @@ import java.net.MalformedURLException
data class Channel(
var name: String,
var modemConfig: MeshProtos.ChannelSettings.ModemConfig,
var settings: MeshProtos.ChannelSettings? = MeshProtos.ChannelSettings.getDefaultInstance()
val settings: MeshProtos.ChannelSettings = MeshProtos.ChannelSettings.getDefaultInstance()
) {
companion object {
// Placeholder when emulating
@ -37,10 +35,11 @@ data class Channel(
}
}
constructor(c: MeshProtos.ChannelSettings) : this(c.name, c.modemConfig, c)
constructor(url: Uri) : this(urlToSettings(url))
val name: String get() = settings.name
val modemConfig: MeshProtos.ChannelSettings.ModemConfig get() = settings.modemConfig
/// Can this channel be changed right now?
var editable = false

Wyświetl plik

@ -1,12 +1,13 @@
package com.geeksville.mesh.model
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import android.os.RemoteException
import androidx.core.content.edit
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.geeksville.android.BuildUtils.isEmulator
import com.geeksville.android.Logging
import com.geeksville.mesh.IMeshService
@ -22,7 +23,7 @@ fun getInitials(name: String): String {
return words
}
class UIViewModel : ViewModel(), Logging {
class UIViewModel(app: Application) : AndroidViewModel(app), Logging {
init {
debug("ViewModel created")
}
@ -46,6 +47,8 @@ class UIViewModel : ViewModel(), Logging {
context.getSharedPreferences("ui-prefs", Context.MODE_PRIVATE)
}
private val context = app.applicationContext
var meshService: IMeshService? = null
val nodeDB = NodeDB(this)
@ -67,7 +70,7 @@ class UIViewModel : ViewModel(), Logging {
}
/// Set the radio config (also updates our saved copy in preferences)
fun setRadioConfig(context: Context, c: MeshProtos.RadioConfig) {
fun setRadioConfig(c: MeshProtos.RadioConfig) {
debug("Setting new radio config!")
meshService?.radioConfig = c.toByteArray()
radioConfig.value = c
@ -77,6 +80,15 @@ class UIViewModel : ViewModel(), Logging {
}
}
/** Update just the channel settings portion of our config (both in the device and in saved preferences) */
fun setChannel(c: MeshProtos.ChannelSettings) {
// When running on the emulator, radio config might not really be available, in that case, just ignore attempts to change the config
radioConfig.value?.toBuilder()?.let { config ->
config.channelSettings = c
setRadioConfig(config.build())
}
}
/// Kinda ugly - created in the activity but used from Compose - figure out if there is a cleaner way GIXME
// lateinit var googleSignInClient: GoogleSignInClient
@ -91,7 +103,7 @@ class UIViewModel : ViewModel(), Logging {
var requestedChannelUrl: Uri? = null
// clean up all this nasty owner state management FIXME
fun setOwner(context: Context, s: String? = null) {
fun setOwner(s: String? = null) {
if (s != null) {
ownerName.value = s

Wyświetl plik

@ -1,22 +1,42 @@
package com.geeksville.mesh.ui
import android.content.Intent
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.ArrayAdapter
import android.widget.ImageView
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import com.geeksville.analytics.DataPair
import com.geeksville.android.GeeksvilleApplication
import com.geeksville.android.Logging
import com.geeksville.android.hideKeyboard
import com.geeksville.mesh.R
import com.geeksville.mesh.model.UIViewModel
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.android.synthetic.main.channel_fragment.*
// Make an image view dim
fun ImageView.setDim() {
val matrix = ColorMatrix()
matrix.setSaturation(0f) //0 means grayscale
val cf = ColorMatrixColorFilter(matrix)
colorFilter = cf
imageAlpha = 64 // 128 = 0.5
}
/// Return image view to normal
fun ImageView.setOpaque() {
colorFilter = null
imageAlpha = 255
}
class ChannelFragment : ScreenFragment("Channel"), Logging {
private val model: UIViewModel by activityViewModels()
@ -28,77 +48,116 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
return inflater.inflate(R.layout.channel_fragment, container, false)
}
/// Called when the lock/unlock icon has changed
private fun onEditingChanged() {
val isEditing = editableCheckbox.isChecked
channelOptions.isEnabled = false // Not yet ready
shareButton.isEnabled = !isEditing
channelNameView.isEnabled = isEditing
qrView.visibility =
if (isEditing) View.INVISIBLE else View.VISIBLE // Don't show the user a stale QR code
if (isEditing) // Dim the (stale) QR code while editing...
qrView.setDim()
else
qrView.setOpaque()
}
/// Pull the latest data from the model (discarding any user edits)
private fun setGUIfromModel() {
val channel = UIViewModel.getChannel(model.radioConfig.value)
editableCheckbox.isChecked = false // start locked
if (channel != null) {
qrView.visibility = View.VISIBLE
channelNameEdit.visibility = View.VISIBLE
channelNameEdit.setText(channel.name)
editableCheckbox.isEnabled = true
qrView.setImageBitmap(channel.getChannelQR())
} else {
qrView.visibility = View.INVISIBLE
channelNameEdit.visibility = View.INVISIBLE
editableCheckbox.isEnabled = false
}
onEditingChanged() // we just locked the gui
val adapter = ArrayAdapter(
requireContext(),
R.layout.dropdown_menu_popup_item,
arrayOf("Item 1", "Item 2", "Item 3", "Item 4")
)
filled_exposed_dropdown.setAdapter(adapter)
}
private fun shareChannel() {
UIViewModel.getChannel(model.radioConfig.value)?.let { channel ->
GeeksvilleApplication.analytics.track(
"share",
DataPair("content_type", "channel")
) // track how many times users share channels
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, channel.getChannelUrl().toString())
putExtra(
Intent.EXTRA_TITLE,
getString(R.string.url_for_join)
)
type = "text/plain"
}
val shareIntent = Intent.createChooser(sendIntent, null)
requireActivity().startActivity(shareIntent)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
onEditingChanged() // Set initial state
channelNameEdit.on(EditorInfo.IME_ACTION_DONE) {
requireActivity().hideKeyboard()
}
editableCheckbox.setOnCheckedChangeListener { _, checked ->
onEditingChanged()
if (!checked) {
// User just locked it, we should warn and then apply changes to radio FIXME not ready yet
Snackbar.make(
// User just locked it, we should warn and then apply changes to radio
/* Snackbar.make(
editableCheckbox,
"Changing channels is not yet supported",
Snackbar.LENGTH_SHORT
).show()
).show() */
MaterialAlertDialogBuilder(requireContext())
.setTitle("Change channel")
.setMessage("Are you sure you want to change the channel? All communication with other nodes will stop until you share the new channel settings.")
.setNeutralButton("Cancel") { _, _ ->
setGUIfromModel()
}
.setPositiveButton("Accept") { _, _ ->
// Generate a new channel with only the changes the user can change in the GUI
UIViewModel.getChannel(model.radioConfig.value)?.let { old ->
val newSettings = old.settings.toBuilder()
newSettings.name = channelNameEdit.text.toString().trim()
// FIXME, regenerate a new preshared key!
model.setChannel(newSettings.build())
// Since we are writing to radioconfig, that will trigger the rest of the GUI update (QR code etc)
}
}
.show()
}
onEditingChanged() // update GUI on what user is allowed to edit/share
}
// Share this particular channel if someone clicks share
shareButton.setOnClickListener {
shareChannel()
}
model.radioConfig.observe(viewLifecycleOwner, Observer { config ->
val channel = UIViewModel.getChannel(config)
if (channel != null) {
qrView.visibility = View.VISIBLE
channelNameEdit.visibility = View.VISIBLE
channelNameEdit.setText(channel.name)
editableCheckbox.isEnabled = true
qrView.setImageBitmap(channel.getChannelQR())
// Share this particular channel if someone clicks share
shareButton.setOnClickListener {
GeeksvilleApplication.analytics.track(
"share",
DataPair("content_type", "channel")
) // track how many times users share channels
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, channel.getChannelUrl().toString())
putExtra(
Intent.EXTRA_TITLE,
"A URL for joining a Meshtastic mesh"
)
type = "text/plain"
}
val shareIntent = Intent.createChooser(sendIntent, null)
requireActivity().startActivity(shareIntent)
}
} else {
qrView.visibility = View.INVISIBLE
channelNameEdit.visibility = View.INVISIBLE
editableCheckbox.isEnabled = false
}
val adapter = ArrayAdapter(
requireContext(),
R.layout.dropdown_menu_popup_item,
arrayOf("Item 1", "Item 2", "Item 3", "Item 4")
)
filled_exposed_dropdown.setAdapter(adapter)
setGUIfromModel()
})
}
}

Wyświetl plik

@ -254,7 +254,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
debug("did IME action")
val n = usernameEditText.text.toString().trim()
if (n.isNotEmpty())
model.setOwner(requireContext(), n)
model.setOwner(n)
requireActivity().hideKeyboard()
}

Wyświetl plik

@ -21,6 +21,9 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/channel_name"
android:imeOptions="actionDone"
android:maxLength="12"
android:singleLine="true"
android:text="@string/unset" />
</com.google.android.material.textfield.TextInputLayout>

Wyświetl plik

@ -25,4 +25,5 @@
<string name="error_bluetooth">Error - this app requires bluetooth</string>
<string name="starting_pairing">Starting pairing</string>
<string name="pairing_failed">Pairing failed</string>
<string name="url_for_join">A URL for joining a Meshtastic mesh</string>
</resources>