kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
refactor: move `ContactsFragment` to main activity ViewModel
rodzic
7411455e17
commit
41fc43b215
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
Ładowanie…
Reference in New Issue