From 1ead40ccd18d7330f50673785fcf7a4f08cbd95b Mon Sep 17 00:00:00 2001 From: andrekir Date: Tue, 3 Dec 2024 08:03:25 -0300 Subject: [PATCH] refactor: relation db and other changes --- .../14.json | 47 +++-- .../mesh/database/MeshtasticDatabase.kt | 6 +- .../mesh/database/PacketRepository.kt | 13 +- .../geeksville/mesh/database/dao/PacketDao.kt | 61 ++++--- .../geeksville/mesh/database/entity/Packet.kt | 61 +++++++ .../mesh/database/entity/TapBack.kt | 13 -- .../java/com/geeksville/mesh/model/Message.kt | 10 +- .../java/com/geeksville/mesh/model/UIState.kt | 36 +--- .../geeksville/mesh/service/MeshService.kt | 48 ++--- .../com/geeksville/mesh/ui/MessageItem.kt | 145 +++++++-------- .../com/geeksville/mesh/ui/MessageListView.kt | 17 +- .../geeksville/mesh/ui/MessagesFragment.kt | 3 +- .../mesh/ui/components/EmojiPicker.kt | 63 +++++++ .../{TapBackEmojiItem.kt => Reaction.kt} | 165 ++++++++---------- .../mesh/ui/map/EditWaypointDialog.kt | 35 +--- 15 files changed, 387 insertions(+), 336 deletions(-) delete mode 100644 app/src/main/java/com/geeksville/mesh/database/entity/TapBack.kt create mode 100644 app/src/main/java/com/geeksville/mesh/ui/components/EmojiPicker.kt rename app/src/main/java/com/geeksville/mesh/ui/components/{TapBackEmojiItem.kt => Reaction.kt} (53%) diff --git a/app/schemas/com.geeksville.mesh.database.MeshtasticDatabase/14.json b/app/schemas/com.geeksville.mesh.database.MeshtasticDatabase/14.json index 54a98df18..93b3614dd 100644 --- a/app/schemas/com.geeksville.mesh.database.MeshtasticDatabase/14.json +++ b/app/schemas/com.geeksville.mesh.database.MeshtasticDatabase/14.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 14, - "identityHash": "39c7546c92fbdd0c05e7cfed2a4eed98", + "identityHash": "b610881191518f933ee6bb694d12d0a2", "entities": [ { "tableName": "my_node", @@ -202,7 +202,7 @@ }, { "tableName": "packet", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `myNodeNum` INTEGER NOT NULL DEFAULT 0, `port_num` INTEGER NOT NULL, `contact_key` TEXT NOT NULL, `received_time` INTEGER NOT NULL, `read` INTEGER NOT NULL DEFAULT 1, `data` TEXT NOT NULL, `packet_id` INTEGER NOT NULL DEFAULT 0, `routing_error` INTEGER NOT NULL DEFAULT -1)", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uuid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `myNodeNum` INTEGER NOT NULL DEFAULT 0, `port_num` INTEGER NOT NULL, `contact_key` TEXT NOT NULL, `received_time` INTEGER NOT NULL, `read` INTEGER NOT NULL DEFAULT 1, `data` TEXT NOT NULL, `packet_id` INTEGER NOT NULL DEFAULT 0, `routing_error` INTEGER NOT NULL DEFAULT -1, `reply_id` INTEGER NOT NULL DEFAULT 0)", "fields": [ { "fieldPath": "uuid", @@ -261,6 +261,13 @@ "affinity": "INTEGER", "notNull": true, "defaultValue": "-1" + }, + { + "fieldPath": "replyId", + "columnName": "reply_id", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" } ], "primaryKey": { @@ -449,24 +456,18 @@ "foreignKeys": [] }, { - "tableName": "tapbacks", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `messageId` INTEGER NOT NULL, `userId` TEXT NOT NULL, `emoji` TEXT NOT NULL, `timestamp` INTEGER NOT NULL)", + "tableName": "reactions", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`reply_id` INTEGER NOT NULL, `user_id` TEXT NOT NULL, `emoji` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`reply_id`, `user_id`, `emoji`))", "fields": [ { - "fieldPath": "id", - "columnName": "id", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "messageId", - "columnName": "messageId", + "fieldPath": "replyId", + "columnName": "reply_id", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "userId", - "columnName": "userId", + "columnName": "user_id", "affinity": "TEXT", "notNull": true }, @@ -484,19 +485,31 @@ } ], "primaryKey": { - "autoGenerate": true, + "autoGenerate": false, "columnNames": [ - "id" + "reply_id", + "user_id", + "emoji" ] }, - "indices": [], + "indices": [ + { + "name": "index_reactions_reply_id", + "unique": false, + "columnNames": [ + "reply_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_reactions_reply_id` ON `${TABLE_NAME}` (`reply_id`)" + } + ], "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, '39c7546c92fbdd0c05e7cfed2a4eed98')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b610881191518f933ee6bb694d12d0a2')" ] } } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt b/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt index 06314d234..7454d0ed1 100644 --- a/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt +++ b/app/src/main/java/com/geeksville/mesh/database/MeshtasticDatabase.kt @@ -18,7 +18,7 @@ import com.geeksville.mesh.database.entity.MyNodeEntity import com.geeksville.mesh.database.entity.NodeEntity import com.geeksville.mesh.database.entity.Packet import com.geeksville.mesh.database.entity.QuickChatAction -import com.geeksville.mesh.database.entity.TapBack +import com.geeksville.mesh.database.entity.ReactionEntity @Database( entities = [ @@ -28,7 +28,7 @@ import com.geeksville.mesh.database.entity.TapBack ContactSettings::class, MeshLog::class, QuickChatAction::class, - TapBack::class, + ReactionEntity::class, ], autoMigrations = [ AutoMigration(from = 3, to = 4), @@ -41,7 +41,7 @@ import com.geeksville.mesh.database.entity.TapBack AutoMigration(from = 10, to = 11), AutoMigration(from = 11, to = 12), AutoMigration(from = 12, to = 13, spec = AutoMigration12to13::class), - AutoMigration(from = 13, to = 14), // add TapBack + AutoMigration(from = 13, to = 14), ], version = 14, exportSchema = true, diff --git a/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt b/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt index 8e0a2b56e..c362b66db 100644 --- a/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/database/PacketRepository.kt @@ -23,7 +23,7 @@ import com.geeksville.mesh.Portnums.PortNum import com.geeksville.mesh.database.dao.PacketDao import com.geeksville.mesh.database.entity.ContactSettings import com.geeksville.mesh.database.entity.Packet -import com.geeksville.mesh.database.entity.TapBack +import com.geeksville.mesh.database.entity.ReactionEntity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.withContext @@ -68,13 +68,6 @@ class PacketRepository @Inject constructor(private val packetDaoLazy: dagger.Laz packetDao.updateMessageId(d, id) } - suspend fun insertTapBack(tapBack: TapBack) = withContext(Dispatchers.IO) { - packetDao.insertTapBack(tapBack) - } - suspend fun getTapBacksForMessage(messageId: Int) = withContext(Dispatchers.IO) { - packetDao.getTapBacksForMessage(messageId) - } - suspend fun getPacketById(requestId: Int) = withContext(Dispatchers.IO) { packetDao.getPacketById(requestId) } @@ -110,4 +103,8 @@ class PacketRepository @Inject constructor(private val packetDaoLazy: dagger.Laz suspend fun setMuteUntil(contacts: List, until: Long) = withContext(Dispatchers.IO) { packetDao.setMuteUntil(contacts, until) } + + suspend fun insertReaction(reaction: ReactionEntity) = withContext(Dispatchers.IO) { + packetDao.insert(reaction) + } } diff --git a/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt b/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt index e7ca27202..ea7e59595 100644 --- a/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt +++ b/app/src/main/java/com/geeksville/mesh/database/dao/PacketDao.kt @@ -18,7 +18,6 @@ package com.geeksville.mesh.database.dao import androidx.room.Dao -import androidx.room.Insert import androidx.room.MapColumn import androidx.room.Update import androidx.room.Query @@ -27,8 +26,9 @@ 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.PacketEntity import com.geeksville.mesh.database.entity.Packet -import com.geeksville.mesh.database.entity.TapBack +import com.geeksville.mesh.database.entity.ReactionEntity import kotlinx.coroutines.flow.Flow @Dao @@ -82,8 +82,8 @@ interface PacketDao { ) suspend fun clearUnreadCount(contact: String, timestamp: Long) - @Insert - fun insert(packet: Packet) + @Upsert + suspend fun insert(packet: Packet) @Query( """ @@ -93,7 +93,8 @@ interface PacketDao { ORDER BY received_time DESC """ ) - fun getMessagesFrom(contact: String): Flow> + @Transaction + fun getMessagesFrom(contact: String): Flow> @Query( """ @@ -102,10 +103,10 @@ interface PacketDao { AND data = :data """ ) - fun findDataPacket(data: DataPacket): Packet? + suspend fun findDataPacket(data: DataPacket): Packet? @Query("DELETE FROM packet WHERE uuid in (:uuidList)") - fun deleteMessages(uuidList: List) + suspend fun deletePackets(uuidList: List) @Query( """ @@ -114,27 +115,42 @@ interface PacketDao { AND contact_key IN (:contactList) """ ) - fun deleteContacts(contactList: List) + suspend fun deleteContacts(contactList: List) @Query("DELETE FROM packet WHERE uuid=:uuid") - fun _delete(uuid: Long) + suspend fun _delete(uuid: Long) @Transaction - fun delete(packet: Packet) { + suspend fun delete(packet: Packet) { _delete(packet.uuid) } + @Query("SELECT packet_id FROM packet WHERE uuid IN (:uuidList)") + suspend fun getPacketIdsFrom(uuidList: List): List + + @Query("DELETE FROM reactions WHERE reply_id IN (:packetIds)") + suspend fun deleteReactions(packetIds: List) + + @Transaction + suspend fun deleteMessages(uuidList: List) { + val packetIds = getPacketIdsFrom(uuidList) + if (packetIds.isNotEmpty()) { + deleteReactions(packetIds) + } + deletePackets(uuidList) + } + @Update - fun update(packet: Packet) + suspend fun update(packet: Packet) @Transaction - fun updateMessageStatus(data: DataPacket, m: MessageStatus) { + suspend fun updateMessageStatus(data: DataPacket, m: MessageStatus) { val new = data.copy(status = m) findDataPacket(data)?.let { update(it.copy(data = new)) } } @Transaction - fun updateMessageId(data: DataPacket, id: Int) { + suspend fun updateMessageId(data: DataPacket, id: Int) { val new = data.copy(id = id) findDataPacket(data)?.let { update(it.copy(data = new)) } } @@ -146,7 +162,7 @@ interface PacketDao { ORDER BY received_time ASC """ ) - fun getDataPackets(): List + suspend fun getDataPackets(): List @Query( """ @@ -156,10 +172,10 @@ interface PacketDao { ORDER BY received_time DESC """ ) - fun getPacketById(requestId: Int): Packet? + suspend fun getPacketById(requestId: Int): Packet? @Transaction - fun getQueuedPackets(): List? = + suspend fun getQueuedPackets(): List? = getDataPackets().filter { it.status == MessageStatus.QUEUED } @Query( @@ -170,10 +186,10 @@ interface PacketDao { ORDER BY received_time ASC """ ) - fun getAllWaypoints(): List + suspend fun getAllWaypoints(): List @Transaction - fun deleteWaypoint(id: Int) { + suspend fun deleteWaypoint(id: Int) { val uuidList = getAllWaypoints().filter { it.data.waypoint?.id == id }.map { it.uuid } deleteMessages(uuidList) } @@ -185,7 +201,7 @@ interface PacketDao { suspend fun getContactSettings(contact: String): ContactSettings? @Upsert - fun upsertContactSettings(contacts: List) + suspend fun upsertContactSettings(contacts: List) @Transaction suspend fun setMuteUntil(contacts: List, until: Long) { @@ -196,9 +212,6 @@ interface PacketDao { upsertContactSettings(contactList) } - @Query("SELECT * FROM tapbacks WHERE messageId = :messageId") - fun getTapBacksForMessage(messageId: Int): List? - - @Insert - fun insertTapBack(tapBack: TapBack) + @Upsert + suspend fun insert(reaction: ReactionEntity) } diff --git a/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt b/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt index 4045ebf1c..3634826c0 100644 --- a/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt +++ b/app/src/main/java/com/geeksville/mesh/database/entity/Packet.kt @@ -18,10 +18,36 @@ package com.geeksville.mesh.database.entity import androidx.room.ColumnInfo +import androidx.room.Embedded import androidx.room.Entity import androidx.room.Index import androidx.room.PrimaryKey +import androidx.room.Relation import com.geeksville.mesh.DataPacket +import com.geeksville.mesh.MeshProtos.User +import com.geeksville.mesh.model.Message +import com.geeksville.mesh.util.getShortDateTime + +data class PacketEntity( + @Embedded val packet: Packet, + @Relation(entity = ReactionEntity::class, parentColumn = "packet_id", entityColumn = "reply_id") + val reactions: List = emptyList(), +) { + suspend fun toMessage(getUser: suspend (userId: String?) -> User) = with(packet) { + Message( + uuid = uuid, + receivedTime = received_time, + user = getUser(data.from), + text = data.text.orEmpty(), + time = getShortDateTime(data.time), + read = read, + status = data.status, + routingError = routingError, + packetId = packetId, + emojis = reactions.toReaction(getUser), + ) + } +} @Entity( tableName = "packet", @@ -42,6 +68,7 @@ data class Packet( @ColumnInfo(name = "data") val data: DataPacket, @ColumnInfo(name = "packet_id", defaultValue = "0") val packetId: Int = 0, @ColumnInfo(name = "routing_error", defaultValue = "-1") var routingError: Int = -1, + @ColumnInfo(name = "reply_id", defaultValue = "0") val replyId: Int = 0, ) @Entity(tableName = "contact_settings") @@ -51,3 +78,37 @@ data class ContactSettings( ) { val isMuted get() = System.currentTimeMillis() <= muteUntil } + +data class Reaction( + val replyId: Int, + val user: User, + val emoji: String, + val timestamp: Long, +) + +@Entity( + tableName = "reactions", + primaryKeys = ["reply_id", "user_id", "emoji"], + indices = [ + Index(value = ["reply_id"]), + ], +) +data class ReactionEntity( + @ColumnInfo(name = "reply_id") val replyId: Int, + @ColumnInfo(name = "user_id") val userId: String, + val emoji: String, + val timestamp: Long, +) + +private suspend fun ReactionEntity.toReaction( + getUser: suspend (userId: String?) -> User +) = Reaction( + replyId = replyId, + user = getUser(userId), + emoji = emoji, + timestamp = timestamp, +) + +private suspend fun List.toReaction( + getUser: suspend (userId: String?) -> User +) = this.map { it.toReaction(getUser) } diff --git a/app/src/main/java/com/geeksville/mesh/database/entity/TapBack.kt b/app/src/main/java/com/geeksville/mesh/database/entity/TapBack.kt deleted file mode 100644 index a09224755..000000000 --- a/app/src/main/java/com/geeksville/mesh/database/entity/TapBack.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.geeksville.mesh.database.entity - -import androidx.room.Entity -import androidx.room.PrimaryKey - -@Entity(tableName = "tapbacks") -data class TapBack( - @PrimaryKey(autoGenerate = true) val id: Long = 0, - val messageId: Int, - val userId: String, - val emoji: String, - val timestamp: Long, -) \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/model/Message.kt b/app/src/main/java/com/geeksville/mesh/model/Message.kt index 5551a513a..61ef926fc 100644 --- a/app/src/main/java/com/geeksville/mesh/model/Message.kt +++ b/app/src/main/java/com/geeksville/mesh/model/Message.kt @@ -17,11 +17,11 @@ package com.geeksville.mesh.model -import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.MeshProtos.Routing +import com.geeksville.mesh.MeshProtos.User import com.geeksville.mesh.MessageStatus import com.geeksville.mesh.R -import com.geeksville.mesh.database.entity.TapBack +import com.geeksville.mesh.database.entity.Reaction val Routing.Error.stringRes: Int get() = when (this) { @@ -46,15 +46,15 @@ val Routing.Error.stringRes: Int data class Message( val uuid: Long, - val messageId: Int, val receivedTime: Long, - val user: MeshProtos.User, + val user: User, val text: String, val time: String, val read: Boolean, val status: MessageStatus?, val routingError: Int, - val emojis: List, + val packetId: Int, + val emojis: List, ) { private fun getStatusStringRes(value: Int): Int { val error = Routing.Error.forNumber(value) ?: Routing.Error.UNRECOGNIZED diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index 6145c9006..a8917719e 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -37,7 +37,6 @@ import com.geeksville.mesh.DataPacket import com.geeksville.mesh.IMeshService import com.geeksville.mesh.LocalOnlyProtos.LocalConfig import com.geeksville.mesh.LocalOnlyProtos.LocalModuleConfig -import com.geeksville.mesh.MeshProtos.MeshPacket import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.Portnums import com.geeksville.mesh.Position @@ -56,14 +55,12 @@ import com.geeksville.mesh.database.entity.MyNodeEntity import com.geeksville.mesh.database.entity.NodeEntity import com.geeksville.mesh.database.entity.Packet import com.geeksville.mesh.database.entity.QuickChatAction -import com.geeksville.mesh.database.entity.TapBack import com.geeksville.mesh.repository.datastore.RadioConfigRepository import com.geeksville.mesh.repository.radio.RadioInterfaceService import com.geeksville.mesh.service.MeshService import com.geeksville.mesh.service.ServiceAction import com.geeksville.mesh.ui.map.MAP_STYLE_ID import com.geeksville.mesh.util.getShortDate -import com.geeksville.mesh.util.getShortDateTime import com.geeksville.mesh.util.positionToMeter import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers @@ -332,22 +329,8 @@ class UIViewModel @Inject constructor( ) @OptIn(ExperimentalCoroutinesApi::class) - fun getMessagesFrom(contactKey: String) = packetRepository.getMessagesFrom(contactKey).mapLatest { messages -> - messages.map { dataPacket -> - Message( - uuid = dataPacket.uuid, - messageId = dataPacket.data.id, - receivedTime = dataPacket.received_time, - user = getUser(dataPacket.data.from), - text = dataPacket.data.text.orEmpty(), - time = getShortDateTime(dataPacket.data.time), - read = dataPacket.read, - status = dataPacket.data.status, - routingError = dataPacket.routingError, - emojis = packetRepository.getTapBacksForMessage(dataPacket.data.id).orEmpty(), - ) - } - } + fun getMessagesFrom(contactKey: String) = packetRepository.getMessagesFrom(contactKey) + .mapLatest { list -> list.map { it.toMessage(::getUser) } } @OptIn(ExperimentalCoroutinesApi::class) val waypoints = packetRepository.getWaypoints().mapLatest { list -> @@ -373,15 +356,6 @@ class UIViewModel @Inject constructor( sendDataPacket(p) } - fun sendTapBack(emoji: String, messageId: Int, contactKey: String = "0${DataPacket.ID_BROADCAST}") { - // contactKey: unique contact key filter (channel)+(nodeId) - val channel = contactKey[0].digitToIntOrNull() - val dest = if (channel != null) contactKey.substring(1) else contactKey - val p = DataPacket(dest, channel ?: 0, emoji) - // todo actually add the tapback - not sure how to add `replyId = messageId` and "emoji=1" to the packet - sendDataPacket(p) - } - fun sendWaypoint(wpt: MeshProtos.Waypoint, contactKey: String = "0${DataPacket.ID_BROADCAST}") { // contactKey: unique contact key filter (channel)+(nodeId) val channel = contactKey[0].digitToIntOrNull() @@ -399,10 +373,8 @@ class UIViewModel @Inject constructor( } } - fun sendTapback(emoji: String, replyId: Int, contactKey: String) { - viewModelScope.launch { - radioConfigRepository.onServiceAction(ServiceAction.Tapback(emoji, replyId, contactKey)) - } + fun sendReaction(emoji: String, replyId: Int, contactKey: String) = viewModelScope.launch { + radioConfigRepository.onServiceAction(ServiceAction.Reaction(emoji, replyId, contactKey)) } fun requestTraceroute(destNum: Int) { diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index 3a791ca2b..2968785bb 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -43,7 +43,7 @@ import com.geeksville.mesh.database.entity.MeshLog import com.geeksville.mesh.database.entity.MyNodeEntity import com.geeksville.mesh.database.entity.NodeEntity import com.geeksville.mesh.database.entity.Packet -import com.geeksville.mesh.database.entity.TapBack +import com.geeksville.mesh.database.entity.ReactionEntity import com.geeksville.mesh.database.entity.toNodeInfo import com.geeksville.mesh.model.DeviceVersion import com.geeksville.mesh.model.getTracerouteResponse @@ -77,7 +77,7 @@ import javax.inject.Inject import kotlin.math.absoluteValue sealed class ServiceAction { - data class Tapback(val emoji: String, val replyId: Int, val contactKey: String) : ServiceAction() + data class Reaction(val emoji: String, val replyId: Int, val contactKey: String) : ServiceAction() } /** @@ -303,7 +303,7 @@ class MeshService : Service(), Logging { .launchIn(serviceScope) radioConfigRepository.serviceAction.onEach { action -> when (action) { - is ServiceAction.Tapback -> sendTapback(action) + is ServiceAction.Reaction -> sendReaction(action) } }.launchIn(serviceScope) @@ -631,18 +631,14 @@ class MeshService : Service(), Logging { Portnums.PortNum.WAYPOINT_APP_VALUE, ) - private fun rememberTapBack(data: MeshProtos.Data) { - val tapBack = TapBack( - messageId = data.replyId, - userId = toNodeID(data.source), - emoji = data.payload.toStringUtf8(), + private fun rememberReaction(packet: MeshPacket) = serviceScope.handledLaunch { + val reaction = ReactionEntity( + replyId = packet.decoded.replyId, + userId = toNodeID(packet.from), + emoji = packet.decoded.payload.toByteArray().decodeToString(), timestamp = System.currentTimeMillis(), ) - serviceScope.handledLaunch { - packetRepository.get().apply { - insertTapBack(tapBack) - } - } + packetRepository.get().insertReaction(reaction) } private fun rememberDataPacket(dataPacket: DataPacket, updateNotification: Boolean = true) { @@ -697,8 +693,13 @@ class MeshService : Service(), Logging { when (data.portnumValue) { Portnums.PortNum.TEXT_MESSAGE_APP_VALUE -> { - debug("Received CLEAR_TEXT from $fromId") - rememberDataPacket(dataPacket) + if (data.emoji != 0) { + debug("Received EMOJI from $fromId") + rememberReaction(packet) + } else { + debug("Received CLEAR_TEXT from $fromId") + rememberDataPacket(dataPacket) + } } Portnums.PortNum.WAYPOINT_APP_VALUE -> { @@ -1756,19 +1757,22 @@ class MeshService : Service(), Logging { } } - private fun sendTapback(tapback: ServiceAction.Tapback) = toRemoteExceptions { + private fun sendReaction(reaction: ServiceAction.Reaction) = toRemoteExceptions { // contactKey: unique contact key filter (channel)+(nodeId) - val channel = tapback.contactKey[0].digitToInt() - val destNum = tapback.contactKey.substring(1) + val channel = reaction.contactKey[0].digitToInt() + val destNum = reaction.contactKey.substring(1) - sendToRadio(newMeshPacketTo(destNum).buildMeshPacket( + val packet = newMeshPacketTo(destNum).buildMeshPacket( channel = channel, priority = MeshPacket.Priority.BACKGROUND, ) { - replyId = tapback.replyId + emoji = 1 + replyId = reaction.replyId portnumValue = Portnums.PortNum.TEXT_MESSAGE_APP_VALUE - payload = ByteString.copyFrom(tapback.emoji.encodeToByteArray()) - }) + payload = ByteString.copyFrom(reaction.emoji.encodeToByteArray()) + } + sendToRadio(packet) + rememberReaction(packet.copy { from = myNodeNum }) } private val binder = object : IMeshService.Stub() { diff --git a/app/src/main/java/com/geeksville/mesh/ui/MessageItem.kt b/app/src/main/java/com/geeksville/mesh/ui/MessageItem.kt index 61b2eaee5..1f11cd020 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MessageItem.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MessageItem.kt @@ -24,7 +24,6 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -58,16 +57,11 @@ import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import com.geeksville.mesh.MessageStatus import com.geeksville.mesh.R -import com.geeksville.mesh.database.entity.TapBack import com.geeksville.mesh.ui.components.AutoLinkText -import com.geeksville.mesh.ui.components.TapBackRow import com.geeksville.mesh.ui.theme.AppTheme @Suppress("LongMethod") -@OptIn( - ExperimentalMaterialApi::class, ExperimentalFoundationApi::class, - ExperimentalLayoutApi::class -) +@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class) @Composable internal fun MessageItem( shortName: String?, @@ -80,8 +74,6 @@ internal fun MessageItem( onLongClick: () -> Unit = {}, onChipClick: () -> Unit = {}, onStatusClick: () -> Unit = {}, - emojis: List = emptyList(), - onSendTapBack: (String) -> Unit = { _, -> }, ) { val fromLocal = shortName == null val messageColor = if (fromLocal) R.color.colorMyMsg else R.color.colorMsg @@ -92,90 +84,87 @@ internal fun MessageItem( Modifier.padding(start = 8.dp, top = 8.dp, end = 48.dp, bottom = 6.dp) } - Column { - Card( - modifier = Modifier - .background(color = if (selected) Color.Gray else MaterialTheme.colors.background) - .fillMaxWidth() - .then(messageModifier), - elevation = 4.dp, - shape = RoundedCornerShape(topStart, topEnd, 12.dp, 12.dp), + Card( + modifier = Modifier + .background(color = if (selected) Color.Gray else MaterialTheme.colors.background) + .fillMaxWidth() + .then(messageModifier), + elevation = 4.dp, + shape = RoundedCornerShape(topStart, topEnd, 12.dp, 12.dp), + ) { + Surface( + modifier = modifier.combinedClickable( + onClick = onClick, + onLongClick = onLongClick, + ), + color = colorResource(id = messageColor), ) { - Surface( - modifier = modifier.combinedClickable( - onClick = onClick, - onLongClick = onLongClick, - ), - color = colorResource(id = messageColor), + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp), + verticalAlignment = Alignment.CenterVertically, ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - if (shortName != null) { - Chip( - onClick = onChipClick, - modifier = Modifier - .padding(end = 8.dp) - .width(72.dp), - ) { - Text( - text = shortName, - modifier = Modifier.fillMaxWidth(), - fontSize = MaterialTheme.typography.button.fontSize, - fontWeight = FontWeight.Normal, - textAlign = TextAlign.Center, - ) - } - } - Column( - modifier = Modifier.padding(top = 8.dp), + if (shortName != null) { + Chip( + onClick = onChipClick, + modifier = Modifier + .padding(end = 8.dp) + .width(72.dp), ) { + Text( + text = shortName, + modifier = Modifier.fillMaxWidth(), + fontSize = MaterialTheme.typography.button.fontSize, + fontWeight = FontWeight.Normal, + textAlign = TextAlign.Center, + ) + } + } + Column( + modifier = Modifier.padding(top = 8.dp), + ) { // Text( // text = longName ?: stringResource(id = R.string.unknown_username), // color = MaterialTheme.colors.onSurface, // fontSize = MaterialTheme.typography.button.fontSize, // ) - AutoLinkText( - text = messageText.orEmpty(), - style = LocalTextStyle.current.copy( - color = LocalContentColor.current, - ), + AutoLinkText( + text = messageText.orEmpty(), + style = LocalTextStyle.current.copy( + color = LocalContentColor.current, + ), + ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = messageTime, + color = MaterialTheme.colors.onSurface, + fontSize = MaterialTheme.typography.caption.fontSize, ) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = messageTime, - color = MaterialTheme.colors.onSurface, - fontSize = MaterialTheme.typography.caption.fontSize, + AnimatedVisibility(visible = fromLocal) { + Icon( + imageVector = when (messageStatus) { + MessageStatus.RECEIVED -> Icons.TwoTone.HowToReg + MessageStatus.QUEUED -> Icons.TwoTone.CloudUpload + MessageStatus.DELIVERED -> Icons.TwoTone.CloudDone + MessageStatus.ENROUTE -> Icons.TwoTone.Cloud + MessageStatus.ERROR -> Icons.TwoTone.CloudOff + else -> Icons.TwoTone.Warning + }, + contentDescription = stringResource(R.string.message_delivery_status), + modifier = Modifier + .padding(start = 8.dp) + .clickable { onStatusClick() }, ) - AnimatedVisibility(visible = fromLocal) { - Icon( - imageVector = when (messageStatus) { - MessageStatus.RECEIVED -> Icons.TwoTone.HowToReg - MessageStatus.QUEUED -> Icons.TwoTone.CloudUpload - MessageStatus.DELIVERED -> Icons.TwoTone.CloudDone - MessageStatus.ENROUTE -> Icons.TwoTone.Cloud - MessageStatus.ERROR -> Icons.TwoTone.CloudOff - else -> Icons.TwoTone.Warning - }, - contentDescription = stringResource(R.string.message_delivery_status), - modifier = Modifier - .padding(start = 8.dp) - .clickable { onStatusClick() }, - ) - } } } } } } - TapBackRow(fromLocal, emojis = emojis, onSendTapBack = onSendTapBack) } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/MessageListView.kt b/app/src/main/java/com/geeksville/mesh/ui/MessageListView.kt index 187987390..08c58f16d 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MessageListView.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MessageListView.kt @@ -35,6 +35,7 @@ import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import com.geeksville.mesh.DataPacket import com.geeksville.mesh.model.Message +import com.geeksville.mesh.ui.components.ReactionRow import com.geeksville.mesh.ui.components.SimpleAlertDialog import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.collectLatest @@ -45,8 +46,8 @@ internal fun MessageListView( messages: List, selectedIds: MutableState>, onUnreadChanged: (Long) -> Unit, - onSendTapBack: (String, Int) -> Unit, contentPadding: PaddingValues, + onSendReaction: (String, Int) -> Unit, onClick: (Message) -> Unit = {} ) { val inSelectionMode by remember { derivedStateOf { selectedIds.value.isNotEmpty() } } @@ -76,20 +77,20 @@ internal fun MessageListView( contentPadding = contentPadding ) { items(messages, key = { it.uuid }) { msg -> + val fromLocal = msg.user.id == DataPacket.ID_LOCAL val selected by remember { derivedStateOf { selectedIds.value.contains(msg.uuid) } } + ReactionRow(fromLocal, msg.emojis) { onSendReaction(it, msg.packetId) } MessageItem( - shortName = msg.user.shortName.takeIf { msg.user.id != DataPacket.ID_LOCAL }, + shortName = msg.user.shortName.takeIf { !fromLocal }, messageText = msg.text, messageTime = msg.time, messageStatus = msg.status, selected = selected, - onClick = { onClick(msg) }, - onLongClick = { onLongClick(msg) }, - onChipClick = { onChipClick(msg) }, - onStatusClick = { showStatusDialog = msg }, - emojis = msg.emojis, - onSendTapBack = { emoji -> onSendTapBack(emoji, msg.messageId) } + onClick = { if (inSelectionMode) toggle(msg.uuid) }, + onLongClick = { toggle(msg.uuid) }, + onChipClick = { onClick(msg) }, + onStatusClick = { showStatusDialog = msg } ) } } 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 56a99b825..f021191c9 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt @@ -251,7 +251,8 @@ internal fun MessageScreen( messages = messages, selectedIds = selectedIds, onUnreadChanged = { viewModel.clearUnreadCount(contactKey, it) }, - contentPadding = innerPadding + contentPadding = innerPadding, + onSendReaction = { emoji, id -> viewModel.sendReaction(emoji, id, contactKey) }, ) { // TODO onCLick() } diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/EmojiPicker.kt b/app/src/main/java/com/geeksville/mesh/ui/components/EmojiPicker.kt new file mode 100644 index 000000000..87336bfff --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/EmojiPicker.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.geeksville.mesh.ui.components + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.AndroidView +import androidx.emoji2.emojipicker.RecentEmojiProviderAdapter +import com.geeksville.mesh.util.CustomRecentEmojiProvider + +@Composable +fun EmojiPicker( + onDismiss: () -> Unit = {}, + onConfirm: (String) -> Unit +) { + Column( + verticalArrangement = Arrangement.Bottom + ) { + BackHandler { + onDismiss() + } + AndroidView( + factory = { context -> + androidx.emoji2.emojipicker.EmojiPickerView(context).apply { + clipToOutline = true + setRecentEmojiProvider( + RecentEmojiProviderAdapter(CustomRecentEmojiProvider(context)) + ) + setOnEmojiPickedListener { emoji -> + onDismiss() + onConfirm(emoji.emoji) + } + } + }, + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight(fraction = 0.4f) + .background(MaterialTheme.colors.background) + ) + } +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/TapBackEmojiItem.kt b/app/src/main/java/com/geeksville/mesh/ui/components/Reaction.kt similarity index 53% rename from app/src/main/java/com/geeksville/mesh/ui/components/TapBackEmojiItem.kt rename to app/src/main/java/com/geeksville/mesh/ui/components/Reaction.kt index 60d0823da..b6deecebe 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/TapBackEmojiItem.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/Reaction.kt @@ -1,6 +1,22 @@ +/* + * Copyright (c) 2024 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.geeksville.mesh.ui.components -import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -8,7 +24,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CircleShape @@ -33,23 +48,20 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp -import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Dialog -import androidx.emoji2.emojipicker.EmojiPickerView -import androidx.emoji2.emojipicker.RecentEmojiProviderAdapter -import com.geeksville.mesh.database.entity.TapBack +import com.geeksville.mesh.MeshProtos +import com.geeksville.mesh.database.entity.Reaction import com.geeksville.mesh.ui.theme.AppTheme -import com.geeksville.mesh.util.CustomRecentEmojiProvider @Composable -fun TapBackEmojiItem( +private fun ReactionItem( emoji: String, isAddEmojiItem: Boolean = false, emojiCount: Int = 1, - emojiTapped: () -> Unit = {}, + onClick: () -> Unit = {}, ) { BadgedBox( - modifier = Modifier.padding(8.dp), + modifier = Modifier.padding(horizontal = 4.dp, vertical = 8.dp), badge = { if (emojiCount > 1) { Badge( @@ -66,8 +78,8 @@ fun TapBackEmojiItem( ) { Surface( modifier = Modifier - .clickable { emojiTapped() }, - color = MaterialTheme.colors.primary, + .clickable { onClick() }, + color = MaterialTheme.colors.surface, shape = RoundedCornerShape(32.dp), elevation = 4.dp, ) { @@ -94,39 +106,39 @@ fun TapBackEmojiItem( @OptIn(ExperimentalLayoutApi::class) @Composable -fun TapBackRow( +fun ReactionRow( fromLocal: Boolean, - emojis: List = emptyList(), - onSendTapBack: (String) -> Unit = { _ -> }, + reactions: List = emptyList(), + onSendReaction: (String) -> Unit = {} ) { - val emojiList by remember { + val emojiList by remember(reactions) { mutableStateOf( reduceEmojis( if (fromLocal) { - emojis.map { it.emoji } + reactions.map { it.emoji } } else { - emojis.map { it.emoji }.reversed() + reactions.map { it.emoji }.reversed() } ).entries ) } - var showEmojiPickerView by remember { mutableStateOf(false) } - if (showEmojiPickerView) { - EmojiPickerView( - emojiSelected = { - showEmojiPickerView = false - onSendTapBack(it) + var showEmojiPickerDialog by remember { mutableStateOf(false) } + if (showEmojiPickerDialog) { + EmojiPickerDialog( + onConfirm = { + showEmojiPickerDialog = false + onSendReaction(it) }, - dismissPickerView = { showEmojiPickerView = false } + onDismiss = { showEmojiPickerDialog = false } ) } @Composable fun AddEmojiItem() { - TapBackEmojiItem( + ReactionItem( emoji = "\uD83D\uDE42", isAddEmojiItem = true, - emojiTapped = { - showEmojiPickerView = true + onClick = { + showEmojiPickerDialog = true } ) } @@ -134,106 +146,71 @@ fun TapBackRow( @Composable fun EmojiList() { emojiList.forEach { entry -> - TapBackEmojiItem( + ReactionItem( emoji = entry.key, emojiCount = entry.value, - emojiTapped = { - onSendTapBack(entry.key) + onClick = { + onSendReaction(entry.key) } ) } } - if (fromLocal) { - FlowRow( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End - ) { - EmojiList() - AddEmojiItem() - } - } else { - FlowRow( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Start - ) { - AddEmojiItem() - EmojiList() - } + FlowRow( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = if (fromLocal) Arrangement.End else Arrangement.Start + ) { + EmojiList() + AddEmojiItem() } } -fun reduceEmojis(emojis: List): Map { - return emojis.groupingBy { it }.eachCount() -} +fun reduceEmojis(emojis: List): Map = emojis.groupingBy { it }.eachCount() -@Suppress("MagicNumber") @Composable -fun EmojiPickerView( - emojiSelected: (String) -> Unit, - dismissPickerView: () -> Unit = {}, +fun EmojiPickerDialog( + onConfirm: (String) -> Unit, + onDismiss: () -> Unit = {}, ) { Dialog( - onDismissRequest = { dismissPickerView() } + onDismissRequest = onDismiss, ) { - Column( - verticalArrangement = Arrangement.Bottom - ) { - BackHandler { - dismissPickerView() - } - AndroidView( - factory = { context -> - EmojiPickerView(context).apply { - clipToOutline = true - setRecentEmojiProvider( - RecentEmojiProviderAdapter(CustomRecentEmojiProvider(context)) - ) - setOnEmojiPickedListener { emoji -> - dismissPickerView() - emojiSelected(emoji.emoji) - } - } - }, - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(0.4f) - .background(MaterialTheme.colors.background) - ) - } + EmojiPicker( + onConfirm = onConfirm, + onDismiss = onDismiss, + ) } } @PreviewLightDark @Composable -fun TapBackEmojiPreview() { +fun ReactionItemPreview() { AppTheme { Column( modifier = Modifier.background(MaterialTheme.colors.background) ) { - TapBackEmojiItem(emoji = "\uD83D\uDE42") - TapBackEmojiItem(emoji = "\uD83D\uDE42", emojiCount = 2) - TapBackEmojiItem(emoji = "\uD83D\uDE42", isAddEmojiItem = true) + ReactionItem(emoji = "\uD83D\uDE42") + ReactionItem(emoji = "\uD83D\uDE42", emojiCount = 2) + ReactionItem(emoji = "\uD83D\uDE42", isAddEmojiItem = true) } } } @Preview @Composable -fun TapBackRowPreview() { +fun ReactionRowPreview() { AppTheme { - - TapBackRow( - fromLocal = true, emojis = listOf( - TapBack( - messageId = 1, - userId = "1", + ReactionRow( + fromLocal = true, reactions = listOf( + Reaction( + replyId = 1, + user = MeshProtos.User.getDefaultInstance(), emoji = "\uD83D\uDE42", timestamp = 1L ), - TapBack( - messageId = 1, - userId = "1", + Reaction( + replyId = 1, + user = MeshProtos.User.getDefaultInstance(), emoji = "\uD83D\uDE42", timestamp = 1L ), diff --git a/app/src/main/java/com/geeksville/mesh/ui/map/EditWaypointDialog.kt b/app/src/main/java/com/geeksville/mesh/ui/map/EditWaypointDialog.kt index 33954af53..b1a035c3a 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/map/EditWaypointDialog.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/map/EditWaypointDialog.kt @@ -17,7 +17,6 @@ package com.geeksville.mesh.ui.map -import androidx.activity.compose.BackHandler import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -25,7 +24,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -58,15 +56,12 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.viewinterop.AndroidView -import androidx.emoji2.emojipicker.EmojiPickerView -import androidx.emoji2.emojipicker.RecentEmojiProviderAdapter import com.geeksville.mesh.MeshProtos.Waypoint import com.geeksville.mesh.R import com.geeksville.mesh.copy import com.geeksville.mesh.ui.components.EditTextPreference +import com.geeksville.mesh.ui.components.EmojiPicker import com.geeksville.mesh.ui.theme.AppTheme -import com.geeksville.mesh.util.CustomRecentEmojiProvider import com.geeksville.mesh.waypoint @Suppress("LongMethod") @@ -184,31 +179,9 @@ internal fun EditWaypointDialog( } }, ) else { - Column( - verticalArrangement = Arrangement.Bottom - ) { - BackHandler { - showEmojiPickerView = false - } - - AndroidView( - factory = { context -> - EmojiPickerView(context).apply { - clipToOutline = true - setRecentEmojiProvider( - RecentEmojiProviderAdapter(CustomRecentEmojiProvider(context)) - ) - setOnEmojiPickedListener { emoji -> - showEmojiPickerView = false - waypointInput = waypointInput.copy { icon = emoji.emoji.codePointAt(0) } - } - } - }, - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(0.4f) - .background(MaterialTheme.colors.background) - ) + EmojiPicker(onDismiss = { showEmojiPickerView = false }) { + showEmojiPickerView = false + waypointInput = waypointInput.copy { icon = it.codePointAt(0) } } } }