2020-02-17 13:34:52 -08:00
|
|
|
package com.geeksville.mesh.model
|
|
|
|
|
|
2020-04-09 16:33:42 -07:00
|
|
|
import android.app.Application
|
2020-02-18 10:40:02 -08:00
|
|
|
import android.content.Context
|
2020-03-02 08:41:16 -08:00
|
|
|
import android.content.SharedPreferences
|
2020-03-17 11:35:19 -07:00
|
|
|
import android.net.Uri
|
2020-02-18 12:22:45 -08:00
|
|
|
import android.os.RemoteException
|
2020-07-17 17:06:29 -04:00
|
|
|
import android.view.Menu
|
2020-02-18 10:40:02 -08:00
|
|
|
import androidx.core.content.edit
|
2020-04-09 16:33:42 -07:00
|
|
|
import androidx.lifecycle.AndroidViewModel
|
2020-09-23 22:47:45 -04:00
|
|
|
import androidx.lifecycle.LiveData
|
2020-04-07 17:42:31 -07:00
|
|
|
import androidx.lifecycle.MutableLiveData
|
2020-09-23 22:47:45 -04:00
|
|
|
import androidx.lifecycle.viewModelScope
|
2020-02-29 13:21:05 -08:00
|
|
|
import com.geeksville.android.Logging
|
2021-02-27 13:43:55 +08:00
|
|
|
import com.geeksville.mesh.*
|
2020-09-23 22:47:45 -04:00
|
|
|
import com.geeksville.mesh.database.MeshtasticDatabase
|
|
|
|
|
import com.geeksville.mesh.database.PacketRepository
|
|
|
|
|
import com.geeksville.mesh.database.entity.Packet
|
2020-04-04 15:29:16 -07:00
|
|
|
import com.geeksville.mesh.service.MeshService
|
2020-09-23 22:47:45 -04:00
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
|
import kotlinx.coroutines.launch
|
2021-02-06 22:08:49 -08:00
|
|
|
import kotlin.math.max
|
2020-04-08 09:53:04 -07:00
|
|
|
|
|
|
|
|
/// Given a human name, strip out the first letter of the first three words and return that as the initials for
|
2020-09-17 00:37:51 -07:00
|
|
|
/// that user. If the original name is only one word, strip vowels from the original name and if the result is
|
|
|
|
|
/// 3 or more characters, use the first three characters. If not, just take the first 3 characters of the
|
|
|
|
|
/// original name.
|
2020-09-17 12:03:27 -07:00
|
|
|
fun getInitials(nameIn: String): String {
|
2020-09-17 00:37:51 -07:00
|
|
|
val nchars = 3
|
|
|
|
|
val minchars = 2
|
2020-09-17 12:03:27 -07:00
|
|
|
val name = nameIn.trim()
|
2020-09-17 00:37:51 -07:00
|
|
|
val words = name.split(Regex("\\s+")).filter { it.isNotEmpty() }
|
2020-04-08 09:53:04 -07:00
|
|
|
|
2020-09-17 00:37:51 -07:00
|
|
|
val initials = when (words.size) {
|
2020-09-17 12:03:27 -07:00
|
|
|
in 0..minchars - 1 -> {
|
2020-09-27 14:05:10 -07:00
|
|
|
val nm = if (name.length >= 1)
|
|
|
|
|
name.first() + name.drop(1).filterNot { c -> c.toLowerCase() in "aeiou" }
|
|
|
|
|
else
|
|
|
|
|
""
|
2020-09-17 00:37:51 -07:00
|
|
|
if (nm.length >= nchars) nm else name
|
|
|
|
|
}
|
2020-09-17 12:03:27 -07:00
|
|
|
else -> words.map { it.first() }.joinToString("")
|
2020-09-17 00:37:51 -07:00
|
|
|
}
|
|
|
|
|
return initials.take(nchars)
|
2020-04-08 09:53:04 -07:00
|
|
|
}
|
2020-03-15 16:30:12 -07:00
|
|
|
|
2021-02-21 12:07:53 +08:00
|
|
|
class UIViewModel(private val app: Application) : AndroidViewModel(app), Logging {
|
2020-09-23 22:47:45 -04:00
|
|
|
|
|
|
|
|
private val repository: PacketRepository
|
|
|
|
|
|
|
|
|
|
val allPackets: LiveData<List<Packet>>
|
|
|
|
|
|
2020-04-07 17:42:31 -07:00
|
|
|
init {
|
2020-09-23 22:47:45 -04:00
|
|
|
val packetsDao = MeshtasticDatabase.getDatabase(app).packetDao()
|
|
|
|
|
repository = PacketRepository(packetsDao)
|
|
|
|
|
allPackets = repository.allPackets
|
2020-04-07 17:42:31 -07:00
|
|
|
debug("ViewModel created")
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-23 22:47:45 -04:00
|
|
|
|
|
|
|
|
fun insertPacket(packet: Packet) = viewModelScope.launch(Dispatchers.IO) {
|
|
|
|
|
repository.insert(packet)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun deleteAllPacket() = viewModelScope.launch(Dispatchers.IO) {
|
|
|
|
|
repository.deleteAll()
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-07 17:42:31 -07:00
|
|
|
companion object {
|
|
|
|
|
fun getPreferences(context: Context): SharedPreferences =
|
|
|
|
|
context.getSharedPreferences("ui-prefs", Context.MODE_PRIVATE)
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-21 12:07:53 +08:00
|
|
|
private val context: Context get() = app.applicationContext
|
2020-04-09 16:33:42 -07:00
|
|
|
|
2020-07-17 17:06:29 -04:00
|
|
|
var actionBarMenu: Menu? = null
|
|
|
|
|
|
2020-04-07 17:42:31 -07:00
|
|
|
var meshService: IMeshService? = null
|
|
|
|
|
|
2020-04-08 09:53:04 -07:00
|
|
|
val nodeDB = NodeDB(this)
|
|
|
|
|
val messagesState = MessagesState(this)
|
|
|
|
|
|
2020-04-07 17:42:31 -07:00
|
|
|
/// Are we connected to our radio device
|
2020-04-08 08:16:06 -07:00
|
|
|
val isConnected =
|
2020-04-08 09:53:04 -07:00
|
|
|
object :
|
|
|
|
|
MutableLiveData<MeshService.ConnectionState>(MeshService.ConnectionState.DISCONNECTED) {
|
2020-04-08 08:16:06 -07:00
|
|
|
}
|
2020-04-07 17:42:31 -07:00
|
|
|
|
|
|
|
|
/// various radio settings (including the channel)
|
2021-02-27 13:43:55 +08:00
|
|
|
val radioConfig = object : MutableLiveData<RadioConfigProtos.RadioConfig?>(null) {
|
2020-04-08 08:16:06 -07:00
|
|
|
}
|
2020-04-07 17:42:31 -07:00
|
|
|
|
2021-02-27 13:43:55 +08:00
|
|
|
val channels = object : MutableLiveData<ChannelSet?>(null) {
|
2021-02-27 11:44:05 +08:00
|
|
|
}
|
|
|
|
|
|
2021-02-05 21:29:28 -08:00
|
|
|
var positionBroadcastSecs: Int?
|
|
|
|
|
get() {
|
|
|
|
|
radioConfig.value?.preferences?.let {
|
2021-02-27 13:43:55 +08:00
|
|
|
if (it.locationShare == RadioConfigProtos.LocationSharing.LocDisabled) return 0
|
2021-02-05 21:29:28 -08:00
|
|
|
if (it.positionBroadcastSecs > 0) return it.positionBroadcastSecs
|
|
|
|
|
// These default values are borrowed from the device code.
|
|
|
|
|
if (it.isRouter) return 60 * 60
|
|
|
|
|
return 15 * 60
|
|
|
|
|
}
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
set(value) {
|
|
|
|
|
val config = radioConfig.value
|
|
|
|
|
if (value != null && config != null) {
|
|
|
|
|
val builder = config.toBuilder()
|
|
|
|
|
if (value > 0) {
|
|
|
|
|
builder.preferencesBuilder.positionBroadcastSecs = value
|
2021-02-06 22:08:49 -08:00
|
|
|
builder.preferencesBuilder.gpsUpdateInterval = value
|
|
|
|
|
builder.preferencesBuilder.sendOwnerInterval = max(1, 3600 / value).toInt()
|
2021-02-05 21:29:28 -08:00
|
|
|
builder.preferencesBuilder.locationShare =
|
2021-02-27 13:43:55 +08:00
|
|
|
RadioConfigProtos.LocationSharing.LocEnabled
|
2021-02-06 22:08:49 -08:00
|
|
|
} else {
|
|
|
|
|
builder.preferencesBuilder.positionBroadcastSecs = Int.MAX_VALUE
|
2021-02-05 21:29:28 -08:00
|
|
|
builder.preferencesBuilder.locationShare =
|
2021-02-27 13:43:55 +08:00
|
|
|
RadioConfigProtos.LocationSharing.LocDisabled
|
2021-02-06 22:08:49 -08:00
|
|
|
}
|
2021-02-05 21:29:28 -08:00
|
|
|
setRadioConfig(builder.build())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var lsSleepSecs: Int?
|
|
|
|
|
get() {
|
|
|
|
|
radioConfig.value?.preferences?.let {
|
|
|
|
|
return it.lsSecs
|
|
|
|
|
}
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
set(value) {
|
|
|
|
|
val config = radioConfig.value
|
|
|
|
|
if (value != null && config != null) {
|
|
|
|
|
val builder = config.toBuilder()
|
|
|
|
|
builder.preferencesBuilder.lsSecs = value
|
|
|
|
|
setRadioConfig(builder.build())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-04 09:08:29 +08:00
|
|
|
/// hardware info about our local device (can be null)
|
|
|
|
|
val myNodeInfo = object : MutableLiveData<MyNodeInfo?>(null) {}
|
2020-05-13 17:00:23 -07:00
|
|
|
|
2020-04-08 09:53:04 -07:00
|
|
|
override fun onCleared() {
|
|
|
|
|
super.onCleared()
|
|
|
|
|
debug("ViewModel cleared")
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-27 11:44:05 +08:00
|
|
|
/**
|
|
|
|
|
* Return the primary channel info
|
|
|
|
|
*/
|
2021-02-27 13:43:55 +08:00
|
|
|
val primaryChannel: Channel? get() = channels.value?.primaryChannel
|
|
|
|
|
|
|
|
|
|
///
|
|
|
|
|
// Set the radio config (also updates our saved copy in preferences)
|
|
|
|
|
private fun setRadioConfig(c: RadioConfigProtos.RadioConfig) {
|
2020-04-07 17:42:31 -07:00
|
|
|
debug("Setting new radio config!")
|
|
|
|
|
meshService?.radioConfig = c.toByteArray()
|
2020-04-15 07:49:39 -07:00
|
|
|
radioConfig.value =
|
|
|
|
|
c // Must be done after calling the service, so we will will properly throw if the service failed (and therefore not cache invalid new settings)
|
2021-02-27 11:44:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set the radio config (also updates our saved copy in preferences)
|
2021-02-27 13:43:55 +08:00
|
|
|
fun setChannels(c: ChannelSet) {
|
2021-02-27 11:44:05 +08:00
|
|
|
debug("Setting new channels!")
|
2021-02-27 13:43:55 +08:00
|
|
|
meshService?.channels = c.protobuf.toByteArray()
|
2021-02-27 11:44:05 +08:00
|
|
|
channels.value =
|
|
|
|
|
c // Must be done after calling the service, so we will will properly throw if the service failed (and therefore not cache invalid new settings)
|
2020-04-07 17:42:31 -07:00
|
|
|
|
|
|
|
|
getPreferences(context).edit(commit = true) {
|
2021-02-27 13:43:55 +08:00
|
|
|
this.putString("channel-url", c.getChannelUrl().toString())
|
2020-04-09 16:33:42 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-17 13:34:52 -08:00
|
|
|
/// Kinda ugly - created in the activity but used from Compose - figure out if there is a cleaner way GIXME
|
|
|
|
|
// lateinit var googleSignInClient: GoogleSignInClient
|
|
|
|
|
|
|
|
|
|
/// our name in hte radio
|
|
|
|
|
/// Note, we generate owner initials automatically for now
|
2020-02-18 12:22:45 -08:00
|
|
|
/// our activity will read this from prefs or set it to the empty string
|
2020-04-08 09:53:04 -07:00
|
|
|
val ownerName = object : MutableLiveData<String>("MrIDE Test") {
|
2020-03-15 18:44:10 -07:00
|
|
|
}
|
2020-03-15 16:30:12 -07:00
|
|
|
|
2020-02-18 10:40:02 -08:00
|
|
|
|
2020-04-20 09:56:38 -07:00
|
|
|
val bluetoothEnabled = object : MutableLiveData<Boolean>(false) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2020-04-08 09:53:04 -07:00
|
|
|
/// If the app was launched because we received a new channel intent, the Url will be here
|
|
|
|
|
var requestedChannelUrl: Uri? = null
|
2020-03-02 07:46:03 -08:00
|
|
|
|
2020-02-18 10:40:02 -08:00
|
|
|
// clean up all this nasty owner state management FIXME
|
2020-04-09 16:33:42 -07:00
|
|
|
fun setOwner(s: String? = null) {
|
2020-02-18 12:22:45 -08:00
|
|
|
|
2020-02-18 10:40:02 -08:00
|
|
|
if (s != null) {
|
2020-04-08 09:53:04 -07:00
|
|
|
ownerName.value = s
|
2020-02-18 12:22:45 -08:00
|
|
|
|
|
|
|
|
// note: we allow an empty userstring to be written to prefs
|
2020-03-02 08:41:16 -08:00
|
|
|
getPreferences(context).edit(commit = true) {
|
2020-02-18 10:40:02 -08:00
|
|
|
putString("owner", s)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Note: we are careful to not set a new unique ID
|
2020-04-08 09:53:04 -07:00
|
|
|
if (ownerName.value!!.isNotEmpty())
|
2020-02-18 12:22:45 -08:00
|
|
|
try {
|
|
|
|
|
meshService?.setOwner(
|
|
|
|
|
null,
|
2020-04-08 09:53:04 -07:00
|
|
|
ownerName.value,
|
|
|
|
|
getInitials(ownerName.value!!)
|
2020-02-18 12:22:45 -08:00
|
|
|
) // Note: we use ?. here because we might be running in the emulator
|
|
|
|
|
} catch (ex: RemoteException) {
|
2020-02-29 13:21:05 -08:00
|
|
|
errormsg("Can't set username on device, is device offline? ${ex.message}")
|
2020-02-18 12:22:45 -08:00
|
|
|
}
|
2020-02-18 10:40:02 -08:00
|
|
|
}
|
2020-02-17 13:34:52 -08:00
|
|
|
}
|
2020-04-08 09:53:04 -07:00
|
|
|
|