kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
feat: add `ActionMenu` option to mute contacts (#1003)
rodzic
b409c17fe8
commit
ecaf35d7f3
|
@ -0,0 +1,459 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 7,
|
||||
"identityHash": "7493c554cd0cf342aeaddb745d49e4b5",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "MyNodeInfo",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`myNodeNum` INTEGER NOT NULL, `hasGPS` INTEGER NOT NULL, `model` TEXT, `firmwareVersion` TEXT, `couldUpdate` INTEGER NOT NULL, `shouldUpdate` INTEGER NOT NULL, `currentPacketId` INTEGER NOT NULL, `messageTimeoutMsec` INTEGER NOT NULL, `minAppVersion` INTEGER NOT NULL, `maxChannels` INTEGER NOT NULL, `hasWifi` INTEGER NOT NULL, `channelUtilization` REAL NOT NULL, `airUtilTx` REAL NOT NULL, PRIMARY KEY(`myNodeNum`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "myNodeNum",
|
||||
"columnName": "myNodeNum",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasGPS",
|
||||
"columnName": "hasGPS",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "model",
|
||||
"columnName": "model",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "firmwareVersion",
|
||||
"columnName": "firmwareVersion",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "couldUpdate",
|
||||
"columnName": "couldUpdate",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "shouldUpdate",
|
||||
"columnName": "shouldUpdate",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "currentPacketId",
|
||||
"columnName": "currentPacketId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageTimeoutMsec",
|
||||
"columnName": "messageTimeoutMsec",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "minAppVersion",
|
||||
"columnName": "minAppVersion",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "maxChannels",
|
||||
"columnName": "maxChannels",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasWifi",
|
||||
"columnName": "hasWifi",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "channelUtilization",
|
||||
"columnName": "channelUtilization",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "airUtilTx",
|
||||
"columnName": "airUtilTx",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"myNodeNum"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "NodeInfo",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`num` INTEGER NOT NULL, `snr` REAL NOT NULL, `rssi` INTEGER NOT NULL, `lastHeard` INTEGER NOT NULL, `channel` INTEGER NOT NULL, `hopsAway` INTEGER NOT NULL DEFAULT 0, `user_id` TEXT, `user_longName` TEXT, `user_shortName` TEXT, `user_hwModel` TEXT, `user_isLicensed` INTEGER, `position_latitude` REAL, `position_longitude` REAL, `position_altitude` INTEGER, `position_time` INTEGER, `position_satellitesInView` INTEGER, `position_groundSpeed` INTEGER, `position_groundTrack` INTEGER, `position_precisionBits` INTEGER, `devMetrics_time` INTEGER, `devMetrics_batteryLevel` INTEGER, `devMetrics_voltage` REAL, `devMetrics_channelUtilization` REAL, `devMetrics_airUtilTx` REAL, `envMetrics_time` INTEGER, `envMetrics_temperature` REAL, `envMetrics_relativeHumidity` REAL, `envMetrics_barometricPressure` REAL, `envMetrics_gasResistance` REAL, `envMetrics_voltage` REAL, `envMetrics_current` REAL, PRIMARY KEY(`num`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "num",
|
||||
"columnName": "num",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "snr",
|
||||
"columnName": "snr",
|
||||
"affinity": "REAL",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "rssi",
|
||||
"columnName": "rssi",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastHeard",
|
||||
"columnName": "lastHeard",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "channel",
|
||||
"columnName": "channel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hopsAway",
|
||||
"columnName": "hopsAway",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true,
|
||||
"defaultValue": "0"
|
||||
},
|
||||
{
|
||||
"fieldPath": "user.id",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "user.longName",
|
||||
"columnName": "user_longName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "user.shortName",
|
||||
"columnName": "user_shortName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "user.hwModel",
|
||||
"columnName": "user_hwModel",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "user.isLicensed",
|
||||
"columnName": "user_isLicensed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.latitude",
|
||||
"columnName": "position_latitude",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.longitude",
|
||||
"columnName": "position_longitude",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.altitude",
|
||||
"columnName": "position_altitude",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.time",
|
||||
"columnName": "position_time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.satellitesInView",
|
||||
"columnName": "position_satellitesInView",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.groundSpeed",
|
||||
"columnName": "position_groundSpeed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.groundTrack",
|
||||
"columnName": "position_groundTrack",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "position.precisionBits",
|
||||
"columnName": "position_precisionBits",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deviceMetrics.time",
|
||||
"columnName": "devMetrics_time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deviceMetrics.batteryLevel",
|
||||
"columnName": "devMetrics_batteryLevel",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deviceMetrics.voltage",
|
||||
"columnName": "devMetrics_voltage",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deviceMetrics.channelUtilization",
|
||||
"columnName": "devMetrics_channelUtilization",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deviceMetrics.airUtilTx",
|
||||
"columnName": "devMetrics_airUtilTx",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.time",
|
||||
"columnName": "envMetrics_time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.temperature",
|
||||
"columnName": "envMetrics_temperature",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.relativeHumidity",
|
||||
"columnName": "envMetrics_relativeHumidity",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.barometricPressure",
|
||||
"columnName": "envMetrics_barometricPressure",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.gasResistance",
|
||||
"columnName": "envMetrics_gasResistance",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.voltage",
|
||||
"columnName": "envMetrics_voltage",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "environmentMetrics.current",
|
||||
"columnName": "envMetrics_current",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"num"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "packet",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `port_num` INTEGER NOT NULL, `contact_key` TEXT NOT NULL, `received_time` INTEGER NOT NULL, `data` TEXT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "uuid",
|
||||
"columnName": "uuid",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "port_num",
|
||||
"columnName": "port_num",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "contact_key",
|
||||
"columnName": "contact_key",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "received_time",
|
||||
"columnName": "received_time",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "data",
|
||||
"columnName": "data",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"uuid"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "contact_settings",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`contact_key` TEXT NOT NULL, `muteUntil` INTEGER NOT NULL, PRIMARY KEY(`contact_key`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "contact_key",
|
||||
"columnName": "contact_key",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "muteUntil",
|
||||
"columnName": "muteUntil",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"contact_key"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "log",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` TEXT NOT NULL, `type` TEXT NOT NULL, `received_date` INTEGER NOT NULL, `message` TEXT NOT NULL, PRIMARY KEY(`uuid`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "uuid",
|
||||
"columnName": "uuid",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message_type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "received_date",
|
||||
"columnName": "received_date",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "raw_message",
|
||||
"columnName": "message",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"uuid"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "quick_chat",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `message` TEXT NOT NULL, `mode` TEXT NOT NULL, `position` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "uuid",
|
||||
"columnName": "uuid",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "mode",
|
||||
"columnName": "mode",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "position",
|
||||
"columnName": "position",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"uuid"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7493c554cd0cf342aeaddb745d49e4b5')"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ import com.geeksville.mesh.database.dao.PacketDao
|
|||
import com.geeksville.mesh.database.dao.MeshLogDao
|
||||
import com.geeksville.mesh.database.dao.NodeInfoDao
|
||||
import com.geeksville.mesh.database.dao.QuickChatActionDao
|
||||
import com.geeksville.mesh.database.entity.ContactSettings
|
||||
import com.geeksville.mesh.database.entity.MeshLog
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import com.geeksville.mesh.database.entity.QuickChatAction
|
||||
|
@ -21,6 +22,7 @@ import com.geeksville.mesh.database.entity.QuickChatAction
|
|||
MyNodeInfo::class,
|
||||
NodeInfo::class,
|
||||
Packet::class,
|
||||
ContactSettings::class,
|
||||
MeshLog::class,
|
||||
QuickChatAction::class
|
||||
],
|
||||
|
@ -28,8 +30,9 @@ import com.geeksville.mesh.database.entity.QuickChatAction
|
|||
AutoMigration (from = 3, to = 4),
|
||||
AutoMigration (from = 4, to = 5),
|
||||
AutoMigration (from = 5, to = 6),
|
||||
AutoMigration (from = 6, to = 7),
|
||||
],
|
||||
version = 6,
|
||||
version = 7,
|
||||
exportSchema = true,
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.geeksville.mesh.database
|
|||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MessageStatus
|
||||
import com.geeksville.mesh.database.dao.PacketDao
|
||||
import com.geeksville.mesh.database.entity.ContactSettings
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
@ -65,4 +66,14 @@ class PacketRepository @Inject constructor(private val packetDaoLazy: dagger.Laz
|
|||
suspend fun update(packet: Packet) = withContext(Dispatchers.IO) {
|
||||
packetDao.update(packet)
|
||||
}
|
||||
|
||||
fun getContactSettings(): Flow<Map<String, ContactSettings>> = packetDao.getContactSettings()
|
||||
|
||||
suspend fun getContactSettings(contact: String) = withContext(Dispatchers.IO) {
|
||||
packetDao.getContactSettings(contact) ?: ContactSettings(contact)
|
||||
}
|
||||
|
||||
suspend fun setMuteUntil(contacts: List<String>, until: Long) = withContext(Dispatchers.IO) {
|
||||
packetDao.setMuteUntil(contacts, until)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@ import androidx.room.MapColumn
|
|||
import androidx.room.Update
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Upsert
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MessageStatus
|
||||
import com.geeksville.mesh.database.entity.ContactSettings
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
|
@ -78,4 +80,22 @@ interface PacketDao {
|
|||
val uuidList = getAllWaypoints().filter { it.data.waypoint?.id == id }.map { it.uuid }
|
||||
deleteMessages(uuidList)
|
||||
}
|
||||
|
||||
@Query("SELECT * FROM contact_settings")
|
||||
fun getContactSettings(): Flow<Map<@MapColumn(columnName = "contact_key") String, ContactSettings>>
|
||||
|
||||
@Query("SELECT * FROM contact_settings WHERE contact_key = :contact")
|
||||
suspend fun getContactSettings(contact:String): ContactSettings?
|
||||
|
||||
@Upsert
|
||||
fun upsertContactSettings(contacts: List<ContactSettings>)
|
||||
|
||||
@Transaction
|
||||
suspend fun setMuteUntil(contacts: List<String>, until: Long) {
|
||||
val contactList = contacts.map { contact ->
|
||||
getContactSettings(contact)?.copy(muteUntil = until)
|
||||
?: ContactSettings(contact_key = contact, muteUntil = until)
|
||||
}
|
||||
upsertContactSettings(contactList)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,5 +12,12 @@ data class Packet(
|
|||
@ColumnInfo(name = "contact_key") val contact_key: String,
|
||||
@ColumnInfo(name = "received_time") val received_time: Long,
|
||||
@ColumnInfo(name = "data") val data: DataPacket
|
||||
)
|
||||
|
||||
@Entity(tableName = "contact_settings")
|
||||
data class ContactSettings(
|
||||
@PrimaryKey val contact_key: String,
|
||||
val muteUntil: Long = 0L,
|
||||
) {
|
||||
val isMuted get() = System.currentTimeMillis() <= muteUntil
|
||||
}
|
||||
|
|
|
@ -222,6 +222,12 @@ class UIViewModel @Inject constructor(
|
|||
contacts + (placeholder - contacts.keys)
|
||||
}.asLiveData()
|
||||
|
||||
val contactSettings get() = packetRepository.getContactSettings()
|
||||
|
||||
fun setMuteUntil(contacts: List<String>, until: Long) = viewModelScope.launch(Dispatchers.IO) {
|
||||
packetRepository.setMuteUntil(contacts, until)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val waypoints: LiveData<Map<Int, Packet>> = _packets.mapLatest { list ->
|
||||
list.filter { it.port_num == Portnums.PortNum.WAYPOINT_APP_VALUE }
|
||||
|
|
|
@ -574,7 +574,7 @@ class MeshService : Service(), Logging {
|
|||
Portnums.PortNum.WAYPOINT_APP_VALUE,
|
||||
)
|
||||
|
||||
private fun rememberDataPacket(dataPacket: DataPacket) {
|
||||
private fun rememberDataPacket(dataPacket: DataPacket, updateNotification: Boolean = true) {
|
||||
if (dataPacket.dataType !in rememberDataType) return
|
||||
val fromLocal = dataPacket.from == DataPacket.ID_LOCAL
|
||||
val toBroadcast = dataPacket.to == DataPacket.ID_BROADCAST
|
||||
|
@ -590,7 +590,13 @@ class MeshService : Service(), Logging {
|
|||
System.currentTimeMillis(),
|
||||
dataPacket
|
||||
)
|
||||
insertPacket(packetToSave)
|
||||
serviceScope.handledLaunch {
|
||||
packetRepository.get().apply {
|
||||
insert(packetToSave)
|
||||
val isMuted = getContactSettings(contactKey).isMuted
|
||||
if (updateNotification && !isMuted) updateMessageNotification(dataPacket)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Update our model and resend as needed for a MeshPacket we just received from the radio
|
||||
|
@ -625,15 +631,13 @@ class MeshService : Service(), Logging {
|
|||
|
||||
debug("Received CLEAR_TEXT from $fromId")
|
||||
rememberDataPacket(dataPacket)
|
||||
updateMessageNotification(dataPacket)
|
||||
}
|
||||
|
||||
Portnums.PortNum.WAYPOINT_APP_VALUE -> {
|
||||
val u = MeshProtos.Waypoint.parseFrom(data.payload)
|
||||
// Validate locked Waypoints from the original sender
|
||||
if (u.lockedTo != 0 && u.lockedTo != packet.from) return
|
||||
rememberDataPacket(dataPacket)
|
||||
if (u.expire > currentSecond()) updateMessageNotification(dataPacket)
|
||||
rememberDataPacket(dataPacket, u.expire > currentSecond())
|
||||
}
|
||||
|
||||
// Handle new style position info
|
||||
|
@ -694,13 +698,11 @@ class MeshService : Service(), Logging {
|
|||
if (!moduleConfig.rangeTest.enabled) return
|
||||
val u = dataPacket.copy(dataType = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE)
|
||||
rememberDataPacket(u)
|
||||
updateMessageNotification(u)
|
||||
}
|
||||
|
||||
Portnums.PortNum.DETECTION_SENSOR_APP_VALUE -> {
|
||||
val u = dataPacket.copy(dataType = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE)
|
||||
rememberDataPacket(u)
|
||||
updateMessageNotification(u)
|
||||
}
|
||||
|
||||
Portnums.PortNum.TRACEROUTE_APP_VALUE -> {
|
||||
|
@ -849,7 +851,6 @@ class MeshService : Service(), Logging {
|
|||
dataType = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE,
|
||||
)
|
||||
rememberDataPacket(u)
|
||||
updateMessageNotification(u)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
|
@ -1030,12 +1031,6 @@ class MeshService : Service(), Logging {
|
|||
}
|
||||
}
|
||||
|
||||
private fun insertPacket(packet: Packet) {
|
||||
serviceScope.handledLaunch {
|
||||
packetRepository.get().insert(packet)
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertMeshLog(packetToSave: MeshLog) {
|
||||
serviceScope.handledLaunch {
|
||||
// Do not log, because might contain PII
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.view.*
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
|
@ -14,6 +15,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.database.entity.ContactSettings
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import com.geeksville.mesh.databinding.AdapterContactLayoutBinding
|
||||
import com.geeksville.mesh.databinding.FragmentContactsBinding
|
||||
|
@ -21,7 +23,8 @@ import com.geeksville.mesh.model.Channel
|
|||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.util.*
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ContactsFragment : ScreenFragment("Messages"), Logging {
|
||||
|
@ -43,6 +46,7 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
|
|||
val longName = itemView.longName
|
||||
val lastMessageTime = itemView.lastMessageTime
|
||||
val lastMessageText = itemView.lastMessageText
|
||||
val mutedIcon = itemView.mutedIcon
|
||||
}
|
||||
|
||||
private val contactsAdapter = object : RecyclerView.Adapter<ViewHolder>() {
|
||||
|
@ -60,6 +64,9 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
|
|||
var contacts = arrayOf<Packet>()
|
||||
var selectedList = ArrayList<String>()
|
||||
|
||||
var contactSettings = mapOf<String, ContactSettings>()
|
||||
val isAllMuted get() = selectedList.all { contactSettings[it]?.isMuted == true }
|
||||
|
||||
override fun getItemCount(): Int = contacts.size
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
|
@ -94,6 +101,8 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
|
|||
holder.lastMessageTime.text = getShortDateTime(Date(contact.time))
|
||||
} else holder.lastMessageTime.visibility = View.INVISIBLE
|
||||
|
||||
holder.mutedIcon.isVisible = contactSettings[packet.contact_key]?.isMuted == true
|
||||
|
||||
holder.itemView.setOnLongClickListener {
|
||||
clickItem(holder, packet.contact_key)
|
||||
if (actionMode == null) {
|
||||
|
@ -148,6 +157,7 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
|
|||
// show total items selected on action mode title
|
||||
actionMode?.title = selectedList.size.toString()
|
||||
}
|
||||
actionMode?.invalidate()
|
||||
notifyItemChanged(position)
|
||||
}
|
||||
|
||||
|
@ -155,10 +165,6 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
|
|||
this.contacts = contacts.values.toTypedArray()
|
||||
notifyDataSetChanged() // FIXME, this is super expensive and redraws all nodes
|
||||
}
|
||||
|
||||
fun onChannelsChanged() {
|
||||
onContactsChanged(contacts.associateBy { it.contact_key })
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
@ -180,10 +186,6 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
|
|||
binding.contactsView.adapter = contactsAdapter
|
||||
binding.contactsView.layoutManager = LinearLayoutManager(requireContext())
|
||||
|
||||
model.channels.asLiveData().observe(viewLifecycleOwner) {
|
||||
contactsAdapter.onChannelsChanged()
|
||||
}
|
||||
|
||||
model.nodeDB.nodes.asLiveData().observe(viewLifecycleOwner) {
|
||||
contactsAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
@ -192,6 +194,11 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
|
|||
debug("New contacts received: ${it.size}")
|
||||
contactsAdapter.onContactsChanged(it)
|
||||
}
|
||||
|
||||
model.contactSettings.asLiveData().observe(viewLifecycleOwner) {
|
||||
contactsAdapter.contactSettings = it
|
||||
contactsAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
@ -210,11 +217,49 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
|
|||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
menu.findItem(R.id.muteButton).setIcon(
|
||||
if (contactsAdapter.isAllMuted) {
|
||||
R.drawable.ic_twotone_volume_up_24
|
||||
} else {
|
||||
R.drawable.ic_twotone_volume_off_24
|
||||
}
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.muteButton -> if (contactsAdapter.isAllMuted) {
|
||||
model.setMuteUntil(contactsAdapter.selectedList.toList(), 0L)
|
||||
mode.finish()
|
||||
} else {
|
||||
var muteUntil: Long = Long.MAX_VALUE
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.mute_notifications)
|
||||
.setSingleChoiceItems(
|
||||
setOf(
|
||||
R.string.mute_8_hours,
|
||||
R.string.mute_1_week,
|
||||
R.string.mute_always,
|
||||
).map(::getString).toTypedArray(),
|
||||
2
|
||||
) { _, which ->
|
||||
muteUntil = when (which) {
|
||||
0 -> System.currentTimeMillis() + TimeUnit.HOURS.toMillis(8)
|
||||
1 -> System.currentTimeMillis() + TimeUnit.DAYS.toMillis(7)
|
||||
else -> Long.MAX_VALUE // always
|
||||
}
|
||||
}
|
||||
.setPositiveButton(getString(R.string.okay)) { _, _ ->
|
||||
debug("User clicked muteButton")
|
||||
model.setMuteUntil(contactsAdapter.selectedList.toList(), muteUntil)
|
||||
mode.finish()
|
||||
}
|
||||
.setNeutralButton(R.string.cancel) { _, _ ->
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
R.id.deleteButton -> {
|
||||
val messagesTotal = model.packets.value.filter { it.port_num == 1 }
|
||||
val selectedList = contactsAdapter.selectedList
|
||||
|
|
|
@ -333,6 +333,7 @@ class MessagesFragment : Fragment(), Logging {
|
|||
private inner class ActionModeCallback : ActionMode.Callback {
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
mode.menuInflater.inflate(R.menu.menu_messages, menu)
|
||||
menu.findItem(R.id.muteButton).isVisible = false
|
||||
mode.title = "1"
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillAlpha="0.3"
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7.83,11H5v2h2.83L10,15.17v-3.76l-1.29,-1.29z"
|
||||
android:strokeAlpha="0.3" />
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M4.34,2.93L2.93,4.34 7.29,8.7 7,9L3,9v6h4l5,5v-6.59l4.18,4.18c-0.65,0.49 -1.38,0.88 -2.18,1.11v2.06c1.34,-0.3 2.57,-0.92 3.61,-1.75l2.05,2.05 1.41,-1.41L4.34,2.93zM10,15.17L7.83,13L5,13v-2h2.83l0.88,-0.88L10,11.41v3.76zM19,12c0,0.82 -0.15,1.61 -0.41,2.34l1.53,1.53c0.56,-1.17 0.88,-2.48 0.88,-3.87 0,-4.28 -2.99,-7.86 -7,-8.77v2.06c2.89,0.86 5,3.54 5,6.71zM12,4l-1.88,1.88L12,7.76zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v1.79l2.48,2.48c0.01,-0.08 0.02,-0.16 0.02,-0.24z" />
|
||||
</vector>
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillAlpha="0.3"
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M5,13h2.83L10,15.17V8.83L7.83,11H5z"
|
||||
android:strokeAlpha="0.3" />
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM10,8.83v6.34L7.83,13L5,13v-2h2.83L10,8.83zM14,7.97v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02 0,-1.77 -1.02,-3.29 -2.5,-4.03zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77 0,-4.28 -2.99,-7.86 -7,-8.77z" />
|
||||
</vector>
|
|
@ -44,7 +44,7 @@
|
|||
android:maxLines="2"
|
||||
android:text="@string/sample_message"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/mutedIcon"
|
||||
app:layout_constraintStart_toEndOf="@id/shortName"
|
||||
app:layout_constraintTop_toBottomOf="@id/longName" />
|
||||
|
||||
|
@ -57,6 +57,18 @@
|
|||
android:text="3 minutes ago"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/mutedIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:layout_margin="8dp"
|
||||
android:contentDescription="@string/mute"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:srcCompat="@drawable/ic_twotone_volume_off_24" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</LinearLayout>
|
|
@ -1,6 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/muteButton"
|
||||
android:icon="@drawable/ic_twotone_volume_off_24"
|
||||
android:title="@string/mute"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/resendButton"
|
||||
android:icon="@drawable/ic_twotone_content_paste_go_24"
|
||||
|
|
|
@ -189,4 +189,9 @@
|
|||
<string name="error_duty_cycle">Duty Cycle limit reached. Cannot send messages right now, please try again later.</string>
|
||||
<string name="forget_node">Forget Node</string>
|
||||
<string name="forget_node_message">This node will be removed from your list until your node receives NodeInfo data from it again.</string>
|
||||
<string name="mute">Mute</string>
|
||||
<string name="mute_notifications">Mute notifications</string>
|
||||
<string name="mute_8_hours">8 hours</string>
|
||||
<string name="mute_1_week">1 week</string>
|
||||
<string name="mute_always">Always</string>
|
||||
</resources>
|
||||
|
|
Ładowanie…
Reference in New Issue