refactor: move `ContactsFragment` to main activity ViewModel

pull/1240/head
andrekir 2024-09-09 19:57:45 -03:00
rodzic 7411455e17
commit 41fc43b215
3 zmienionych plików z 90 dodań i 115 usunięć

Wyświetl plik

@ -1,109 +0,0 @@
package com.geeksville.mesh.model
import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.geeksville.mesh.DataPacket
import com.geeksville.mesh.R
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.database.PacketRepository
import com.geeksville.mesh.database.entity.Packet
import com.geeksville.mesh.repository.datastore.ChannelSetRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import java.text.DateFormat
import java.util.Date
import javax.inject.Inject
data class Contact(
val contactKey: String,
val shortName: String,
val longName: String,
val lastMessageTime: String?,
val lastMessageText: String?,
val unreadCount: Int,
val messageCount: Int,
val isMuted: Boolean,
)
// return time if within 24 hours, otherwise date
internal fun getShortDateTime(time: Long): String? {
val date = if (time != 0L) Date(time) else return null
val isWithin24Hours = System.currentTimeMillis() - date.time <= 24 * 60 * 60 * 1000L
return if (isWithin24Hours) {
DateFormat.getTimeInstance(DateFormat.SHORT).format(date)
} else {
DateFormat.getDateInstance(DateFormat.SHORT).format(date)
}
}
@HiltViewModel
class ContactsViewModel @Inject constructor(
private val app: Application,
private val nodeDB: NodeDB,
channelSetRepository: ChannelSetRepository,
private val packetRepository: PacketRepository,
) : ViewModel(), Logging {
val contactList = combine(
nodeDB.myNodeInfo,
packetRepository.getContacts(),
channelSetRepository.channelSetFlow,
packetRepository.getContactSettings(),
) { myNodeInfo, contacts, channelSet, settings ->
val myNodeNum = myNodeInfo?.myNodeNum ?: return@combine emptyList()
// Add empty channel placeholders (always show Broadcast contacts, even when empty)
val placeholder = (0 until channelSet.settingsCount).associate { ch ->
val contactKey = "$ch${DataPacket.ID_BROADCAST}"
val data = DataPacket(bytes = null, dataType = 1, time = 0L, channel = ch)
contactKey to Packet(0L, myNodeNum, 1, contactKey, 0L, true, data)
}
(contacts + (placeholder - contacts.keys)).values.map { packet ->
val data = packet.data
val contactKey = packet.contact_key
// Determine if this is my message (originated on this device)
val fromLocal = data.from == DataPacket.ID_LOCAL
val toBroadcast = data.to == DataPacket.ID_BROADCAST
// grab usernames from NodeInfo
val node = nodeDB.nodes.value[if (fromLocal) data.to else data.from]
val shortName = node?.user?.shortName ?: app.getString(R.string.unknown_node_short_name)
val longName = if (toBroadcast) {
channelSet.getChannel(data.channel)?.name ?: app.getString(R.string.channel_name)
} else {
node?.user?.longName ?: app.getString(R.string.unknown_username)
}
Contact(
contactKey = contactKey,
shortName = if (toBroadcast) "${data.channel}" else shortName,
longName = longName,
lastMessageTime = getShortDateTime(data.time),
lastMessageText = if (fromLocal) data.text else "$shortName: ${data.text}",
unreadCount = packetRepository.getUnreadCount(contactKey),
messageCount = packetRepository.getMessageCount(contactKey),
isMuted = settings[contactKey]?.isMuted == true,
)
}
}.stateIn(
scope = viewModelScope,
started = WhileSubscribed(5_000),
initialValue = emptyList(),
)
fun setMuteUntil(contacts: List<String>, until: Long) = viewModelScope.launch(Dispatchers.IO) {
packetRepository.setMuteUntil(contacts, until)
}
fun deleteContacts(contacts: List<String>) = viewModelScope.launch(Dispatchers.IO) {
packetRepository.deleteContacts(contacts)
}
}

Wyświetl plik

@ -21,6 +21,7 @@ import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.database.MeshLogRepository
import com.geeksville.mesh.database.PacketRepository
import com.geeksville.mesh.database.QuickChatActionRepository
import com.geeksville.mesh.database.entity.Packet
import com.geeksville.mesh.database.entity.QuickChatAction
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
import com.geeksville.mesh.repository.radio.RadioInterfaceService
@ -30,7 +31,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
import kotlinx.coroutines.flow.SharingStarted.Companion.Eagerly
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
@ -45,8 +46,11 @@ import kotlinx.coroutines.withContext
import java.io.BufferedWriter
import java.io.FileNotFoundException
import java.io.FileWriter
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.math.roundToInt
@ -113,6 +117,17 @@ data class NodesUiState(
}
}
data class Contact(
val contactKey: String,
val shortName: String,
val longName: String,
val lastMessageTime: String?,
val lastMessageText: String?,
val unreadCount: Int,
val messageCount: Int,
val isMuted: Boolean,
)
data class Message(
val uuid: Long,
val receivedTime: Long,
@ -123,6 +138,18 @@ data class Message(
val status: MessageStatus?,
)
// return time if within 24 hours, otherwise date
internal fun getShortDateTime(time: Long): String? {
val date = if (time != 0L) Date(time) else return null
val isWithin24Hours = System.currentTimeMillis() - date.time <= TimeUnit.DAYS.toMillis(1)
return if (isWithin24Hours) {
DateFormat.getTimeInstance(DateFormat.SHORT).format(date)
} else {
DateFormat.getDateInstance(DateFormat.SHORT).format(date)
}
}
@HiltViewModel
class UIViewModel @Inject constructor(
private val app: Application,
@ -195,7 +222,7 @@ class UIViewModel @Inject constructor(
)
}.stateIn(
scope = viewModelScope,
started = WhileSubscribed(),
started = Eagerly,
initialValue = NodesUiState.Empty,
)
@ -204,7 +231,7 @@ class UIViewModel @Inject constructor(
nodeDB.getNodes(state.sort, state.filter, state.includeUnknown)
}.stateIn(
scope = viewModelScope,
started = WhileSubscribed(5_000),
started = Eagerly,
initialValue = emptyList(),
)
@ -239,6 +266,55 @@ class UIViewModel @Inject constructor(
debug("ViewModel created")
}
val contactList = combine(
nodeDB.myNodeInfo,
packetRepository.getContacts(),
channels,
packetRepository.getContactSettings(),
) { myNodeInfo, contacts, channelSet, settings ->
val myNodeNum = myNodeInfo?.myNodeNum ?: return@combine emptyList()
// Add empty channel placeholders (always show Broadcast contacts, even when empty)
val placeholder = (0 until channelSet.settingsCount).associate { ch ->
val contactKey = "$ch${DataPacket.ID_BROADCAST}"
val data = DataPacket(bytes = null, dataType = 1, time = 0L, channel = ch)
contactKey to Packet(0L, myNodeNum, 1, contactKey, 0L, true, data)
}
(contacts + (placeholder - contacts.keys)).values.map { packet ->
val data = packet.data
val contactKey = packet.contact_key
// Determine if this is my message (originated on this device)
val fromLocal = data.from == DataPacket.ID_LOCAL
val toBroadcast = data.to == DataPacket.ID_BROADCAST
// grab usernames from NodeInfo
val node = nodeDB.nodes.value[if (fromLocal) data.to else data.from]
val shortName = node?.user?.shortName ?: app.getString(R.string.unknown_node_short_name)
val longName = if (toBroadcast) {
channelSet.getChannel(data.channel)?.name ?: app.getString(R.string.channel_name)
} else {
node?.user?.longName ?: app.getString(R.string.unknown_username)
}
Contact(
contactKey = contactKey,
shortName = if (toBroadcast) "${data.channel}" else shortName,
longName = longName,
lastMessageTime = getShortDateTime(data.time),
lastMessageText = if (fromLocal) data.text else "$shortName: ${data.text}",
unreadCount = packetRepository.getUnreadCount(contactKey),
messageCount = packetRepository.getMessageCount(contactKey),
isMuted = settings[contactKey]?.isMuted == true,
)
}
}.stateIn(
scope = viewModelScope,
started = Eagerly,
initialValue = emptyList(),
)
fun getMessagesFrom(contactKey: String) = combine(
nodeDB.users,
packetRepository.getMessagesFrom(contactKey),
@ -333,6 +409,14 @@ class UIViewModel @Inject constructor(
}
}
fun setMuteUntil(contacts: List<String>, until: Long) = viewModelScope.launch(Dispatchers.IO) {
packetRepository.setMuteUntil(contacts, until)
}
fun deleteContacts(contacts: List<String>) = viewModelScope.launch(Dispatchers.IO) {
packetRepository.deleteContacts(contacts)
}
fun deleteMessages(uuidList: List<Long>) = viewModelScope.launch(Dispatchers.IO) {
packetRepository.deleteMessages(uuidList)
}

Wyświetl plik

@ -23,12 +23,12 @@ import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.unit.dp
import androidx.fragment.app.viewModels
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.R
import com.geeksville.mesh.model.Contact
import com.geeksville.mesh.model.ContactsViewModel
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.ui.theme.AppTheme
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
@ -39,7 +39,7 @@ class ContactsFragment : ScreenFragment("Messages"), Logging {
private val actionModeCallback: ActionModeCallback = ActionModeCallback()
private var actionMode: ActionMode? = null
private val model: ContactsViewModel by viewModels()
private val model: UIViewModel by activityViewModels()
private val contacts get() = model.contactList.value
private val selectedList = emptyList<String>().toMutableStateList()