kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
editing and saving channels kinda works
rodzic
1d32dad6de
commit
2b588ac7e7
|
@ -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 =
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
Ładowanie…
Reference in New Issue