mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Modularize messaging code (#3435)
This commit is contained in:
parent
cd1a54f506
commit
886e9cfede
37 changed files with 297 additions and 143 deletions
|
|
@ -204,6 +204,7 @@ dependencies {
|
|||
implementation(projects.core.strings)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.feature.intro)
|
||||
implementation(projects.feature.messaging)
|
||||
implementation(projects.feature.map)
|
||||
implementation(projects.feature.node)
|
||||
implementation(projects.feature.settings)
|
||||
|
|
|
|||
|
|
@ -16,16 +16,8 @@
|
|||
<ID>ComposableParamOrder:EnvironmentCharts.kt$MetricPlottingCanvas</ID>
|
||||
<ID>ComposableParamOrder:HostMetricsLog.kt$HostMetricsItem</ID>
|
||||
<ID>ComposableParamOrder:HostMetricsLog.kt$LogLine</ID>
|
||||
<ID>ComposableParamOrder:Message.kt$MessageScreen</ID>
|
||||
<ID>ComposableParamOrder:Message.kt$QuickChatRow</ID>
|
||||
<ID>ComposableParamOrder:MessageActions.kt$MessageActions</ID>
|
||||
<ID>ComposableParamOrder:MessageActions.kt$MessageStatusButton</ID>
|
||||
<ID>ComposableParamOrder:MessageItem.kt$MessageItem</ID>
|
||||
<ID>ComposableParamOrder:MessageList.kt$DeliveryInfo</ID>
|
||||
<ID>ComposableParamOrder:MessageList.kt$MessageList</ID>
|
||||
<ID>ComposableParamOrder:PaxMetrics.kt$PaxMetricsChart</ID>
|
||||
<ID>ComposableParamOrder:PowerMetrics.kt$PowerMetricsChart</ID>
|
||||
<ID>ComposableParamOrder:QuickChat.kt$OutlinedTextFieldWithCounter</ID>
|
||||
<ID>ComposableParamOrder:Share.kt$ShareScreen</ID>
|
||||
<ID>ComposableParamOrder:SignalMetrics.kt$SignalMetricsChart</ID>
|
||||
<ID>CyclomaticComplexMethod:MeshService.kt$MeshService$private fun handleReceivedData(packet: MeshPacket)</ID>
|
||||
|
|
@ -54,18 +46,11 @@
|
|||
<ID>FinalNewline:UsbRepositoryModule.kt$com.geeksville.mesh.repository.usb.UsbRepositoryModule.kt</ID>
|
||||
<ID>ForbiddenComment:SafeBluetooth.kt$SafeBluetooth$// TODO: display some kind of UI about restarting BLE</ID>
|
||||
<ID>LambdaParameterEventTrailing:Channel.kt$onConfirm</ID>
|
||||
<ID>LambdaParameterEventTrailing:ContactSharing.kt$onSharedContactRequested</ID>
|
||||
<ID>LambdaParameterEventTrailing:Message.kt$onClick</ID>
|
||||
<ID>LambdaParameterEventTrailing:Message.kt$onSendMessage</ID>
|
||||
<ID>LambdaParameterEventTrailing:MessageList.kt$onReply</ID>
|
||||
<ID>LambdaParameterEventTrailing:QuickChat.kt$onNavigateUp</ID>
|
||||
<ID>LambdaParameterEventTrailing:TracerouteLog.kt$onNavigateUp</ID>
|
||||
<ID>LambdaParameterInRestartableEffect:Channel.kt$onConfirm</ID>
|
||||
<ID>LambdaParameterInRestartableEffect:MessageList.kt$onUnreadChanged</ID>
|
||||
<ID>LargeClass:MeshService.kt$MeshService : Service</ID>
|
||||
<ID>LongMethod:EnvironmentMetrics.kt$@Composable fun EnvironmentMetricsScreen(viewModel: MetricsViewModel = hiltViewModel(), onNavigateUp: () -> Unit)</ID>
|
||||
<ID>LongMethod:MeshService.kt$MeshService$private fun handleReceivedData(packet: MeshPacket)</ID>
|
||||
<ID>LongParameterList:MessageViewModel.kt$MessageViewModel$( private val nodeRepository: NodeRepository, radioConfigRepository: RadioConfigRepository, quickChatActionRepository: QuickChatActionRepository, private val serviceRepository: ServiceRepository, private val packetRepository: PacketRepository, private val uiPrefs: UiPrefs, private val meshServiceNotifications: MeshServiceNotifications, )</ID>
|
||||
<ID>MagicNumber:BluetoothInterface.kt$BluetoothInterface$1000</ID>
|
||||
<ID>MagicNumber:BluetoothInterface.kt$BluetoothInterface$500</ID>
|
||||
<ID>MagicNumber:BluetoothInterface.kt$BluetoothInterface$512</ID>
|
||||
|
|
@ -112,7 +97,6 @@
|
|||
<ID>ModifierMissing:CommonCharts.kt$ChartHeader</ID>
|
||||
<ID>ModifierMissing:CommonCharts.kt$Legend</ID>
|
||||
<ID>ModifierMissing:CommonCharts.kt$TimeLabels</ID>
|
||||
<ID>ModifierMissing:ContactSharing.kt$SharedContactDialog</ID>
|
||||
<ID>ModifierMissing:Contacts.kt$ContactListView</ID>
|
||||
<ID>ModifierMissing:Contacts.kt$ContactsScreen</ID>
|
||||
<ID>ModifierMissing:Contacts.kt$SelectionToolbar</ID>
|
||||
|
|
@ -121,9 +105,6 @@
|
|||
<ID>ModifierMissing:EnvironmentMetrics.kt$EnvironmentMetricsScreen</ID>
|
||||
<ID>ModifierMissing:HostMetricsLog.kt$HostMetricsLogScreen</ID>
|
||||
<ID>ModifierMissing:Main.kt$MainScreen</ID>
|
||||
<ID>ModifierMissing:MessageActions.kt$MessageStatusButton</ID>
|
||||
<ID>ModifierMissing:MessageActions.kt$ReactionButton</ID>
|
||||
<ID>ModifierMissing:MessageActions.kt$ReplyButton</ID>
|
||||
<ID>ModifierMissing:NetworkDevices.kt$NetworkDevices</ID>
|
||||
<ID>ModifierMissing:NodeListScreen.kt$NodeListScreen</ID>
|
||||
<ID>ModifierMissing:PaxMetrics.kt$PaxMetricsItem</ID>
|
||||
|
|
@ -131,9 +112,7 @@
|
|||
<ID>ModifierMissing:PositionLog.kt$PositionItem</ID>
|
||||
<ID>ModifierMissing:PositionLog.kt$PositionLogScreen</ID>
|
||||
<ID>ModifierMissing:PowerMetrics.kt$PowerMetricsScreen</ID>
|
||||
<ID>ModifierMissing:Reaction.kt$ReactionDialog</ID>
|
||||
<ID>ModifierMissing:Share.kt$ShareScreen</ID>
|
||||
<ID>ModifierMissing:SharedContactDialog.kt$SharedContactDialog</ID>
|
||||
<ID>ModifierMissing:SignalMetrics.kt$SignalMetricsScreen</ID>
|
||||
<ID>ModifierNotUsedAtRoot:DeviceMetrics.kt$modifier = modifier.weight(weight = Y_AXIS_WEIGHT)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:DeviceMetrics.kt$modifier = modifier.width(dp)</ID>
|
||||
|
|
@ -144,7 +123,6 @@
|
|||
<ID>ModifierNotUsedAtRoot:PowerMetrics.kt$modifier = modifier.weight(weight = Y_AXIS_WEIGHT)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:PowerMetrics.kt$modifier = modifier.width(dp)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:PowerMetrics.kt$modifier.width(dp)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:QuickChat.kt$modifier = modifier.fillMaxSize().padding(innerPadding)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:SignalMetrics.kt$modifier = modifier.weight(weight = Y_AXIS_WEIGHT)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:SignalMetrics.kt$modifier = modifier.width(dp)</ID>
|
||||
<ID>ModifierNotUsedAtRoot:SignalMetrics.kt$modifier.width(dp)</ID>
|
||||
|
|
@ -178,7 +156,6 @@
|
|||
<ID>MultipleEmitters:PowerMetrics.kt$PowerMetricsChart</ID>
|
||||
<ID>MultipleEmitters:SignalMetrics.kt$SignalMetricsChart</ID>
|
||||
<ID>MutableStateAutoboxing:Contacts.kt$mutableStateOf(2)</ID>
|
||||
<ID>MutableStateParam:MessageList.kt$selectedIds</ID>
|
||||
<ID>NestedBlockDepth:MeshService.kt$MeshService$private fun handleReceivedAdmin(fromNodeNum: Int, a: AdminProtos.AdminMessage)</ID>
|
||||
<ID>NestedBlockDepth:MeshService.kt$MeshService$private fun handleReceivedData(packet: MeshPacket)</ID>
|
||||
<ID>NewLineAtEndOfFile:BLEException.kt$com.geeksville.mesh.service.BLEException.kt</ID>
|
||||
|
|
@ -207,15 +184,11 @@
|
|||
<ID>NoEmptyClassBody:DebugLogFile.kt$BinaryLogFile${ }</ID>
|
||||
<ID>NoSemicolons:DateUtils.kt$DateUtils$;</ID>
|
||||
<ID>OptionalAbstractKeyword:SyncContinuation.kt$Continuation$abstract</ID>
|
||||
<ID>ParameterNaming:ContactSharing.kt$onSharedContactRequested</ID>
|
||||
<ID>ParameterNaming:Contacts.kt$onDeleteSelected</ID>
|
||||
<ID>ParameterNaming:Contacts.kt$onMuteSelected</ID>
|
||||
<ID>ParameterNaming:MessageList.kt$onUnreadChanged</ID>
|
||||
<ID>ParameterNaming:UsbDevices.kt$onDeviceSelected</ID>
|
||||
<ID>PreviewPublic:Channel.kt$ModemPresetInfoPreview</ID>
|
||||
<ID>PreviewPublic:EmptyStateContent.kt$EmptyStateContentPreview</ID>
|
||||
<ID>PreviewPublic:Reaction.kt$ReactionItemPreview</ID>
|
||||
<ID>PreviewPublic:Reaction.kt$ReactionRowPreview</ID>
|
||||
<ID>RethrowCaughtException:SyncContinuation.kt$Continuation$throw ex</ID>
|
||||
<ID>SwallowedException:BluetoothInterface.kt$BluetoothInterface$ex: CancellationException</ID>
|
||||
<ID>SwallowedException:Exceptions.kt$ex: Throwable</ID>
|
||||
|
|
@ -246,13 +219,11 @@
|
|||
<ID>TooManyFunctions:BluetoothInterface.kt$BluetoothInterface : IRadioInterface</ID>
|
||||
<ID>TooManyFunctions:MeshService.kt$MeshService : Service</ID>
|
||||
<ID>TooManyFunctions:MeshService.kt$MeshService$<no name provided> : Stub</ID>
|
||||
<ID>TooManyFunctions:MessageViewModel.kt$MessageViewModel : ViewModel</ID>
|
||||
<ID>TooManyFunctions:RadioInterfaceService.kt$RadioInterfaceService</ID>
|
||||
<ID>TooManyFunctions:SafeBluetooth.kt$SafeBluetooth : Closeable</ID>
|
||||
<ID>TooManyFunctions:UIState.kt$UIViewModel : ViewModel</ID>
|
||||
<ID>TopLevelPropertyNaming:Constants.kt$const val prefix = "com.geeksville.mesh"</ID>
|
||||
<ID>UtilityClassWithPublicConstructor:NetworkRepositoryModule.kt$NetworkRepositoryModule</ID>
|
||||
<ID>ViewModelForwarding:Main.kt$VersionChecks(uIViewModel)</ID>
|
||||
<ID>Wrapping:Message.kt${ event -> when (event) { is MessageScreenEvent.SendMessage -> { viewModel.sendMessage(event.text, contactKey, event.replyingToPacketId) if (event.replyingToPacketId != null) replyingToPacketId = null messageInputState.clearText() } is MessageScreenEvent.SendReaction -> viewModel.sendReaction(event.emoji, event.messageId, contactKey) is MessageScreenEvent.DeleteMessages -> { viewModel.deleteMessages(event.ids) selectedMessageIds.value = emptySet() showDeleteDialog = false } is MessageScreenEvent.ClearUnreadCount -> viewModel.clearUnreadCount(contactKey, event.lastReadMessageId) is MessageScreenEvent.NodeDetails -> navigateToNodeDetails(event.node.num) is MessageScreenEvent.SetTitle -> viewModel.setTitle(event.title) is MessageScreenEvent.NavigateToMessages -> navigateToMessages(event.contactKey) is MessageScreenEvent.NavigateToNodeDetails -> navigateToNodeDetails(event.nodeNum) MessageScreenEvent.NavigateBack -> onNavigateBack() is MessageScreenEvent.CopyToClipboard -> { clipboardManager.nativeClipboard.setPrimaryClip(ClipData.newPlainText(event.text, event.text)) selectedMessageIds.value = emptySet() } } }</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
|
|||
|
|
@ -17,39 +17,40 @@
|
|||
|
||||
package com.geeksville.mesh
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import com.geeksville.mesh.service.MeshServiceNotifications
|
||||
import com.geeksville.mesh.service.MeshServiceNotificationsImpl
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.meshtastic.core.common.BuildConfigProvider
|
||||
import org.meshtastic.core.service.MeshServiceNotifications
|
||||
import javax.inject.Singleton
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
object ApplicationModule {
|
||||
interface ApplicationModule {
|
||||
|
||||
@Provides fun provideProcessLifecycleOwner(): LifecycleOwner = ProcessLifecycleOwner.get()
|
||||
@Binds fun bindMeshServiceNotifications(impl: MeshServiceNotificationsImpl): MeshServiceNotifications
|
||||
|
||||
@Provides
|
||||
fun provideProcessLifecycle(processLifecycleOwner: LifecycleOwner): Lifecycle = processLifecycleOwner.lifecycle
|
||||
companion object {
|
||||
@Provides fun provideProcessLifecycleOwner(): LifecycleOwner = ProcessLifecycleOwner.get()
|
||||
|
||||
@Provides
|
||||
fun providesMeshServiceNotifications(application: Application): MeshServiceNotifications =
|
||||
MeshServiceNotifications(application)
|
||||
@Provides
|
||||
fun provideProcessLifecycle(processLifecycleOwner: LifecycleOwner): Lifecycle = processLifecycleOwner.lifecycle
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideBuildConfigProvider(): BuildConfigProvider = object : BuildConfigProvider {
|
||||
override val isDebug: Boolean = BuildConfig.DEBUG
|
||||
override val applicationId: String = BuildConfig.APPLICATION_ID
|
||||
override val versionCode: Int = BuildConfig.VERSION_CODE
|
||||
override val versionName: String = BuildConfig.VERSION_NAME
|
||||
override val absoluteMinFwVersion: String = BuildConfig.ABS_MIN_FW_VERSION
|
||||
override val minFwVersion: String = BuildConfig.MIN_FW_VERSION
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideBuildConfigProvider(): BuildConfigProvider = object : BuildConfigProvider {
|
||||
override val isDebug: Boolean = BuildConfig.DEBUG
|
||||
override val applicationId: String = BuildConfig.APPLICATION_ID
|
||||
override val versionCode: Int = BuildConfig.VERSION_CODE
|
||||
override val versionName: String = BuildConfig.VERSION_NAME
|
||||
override val absoluteMinFwVersion: String = BuildConfig.ABS_MIN_FW_VERSION
|
||||
override val minFwVersion: String = BuildConfig.MIN_FW_VERSION
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,10 +31,7 @@ import androidx.lifecycle.viewModelScope
|
|||
import androidx.navigation.NavHostController
|
||||
import com.geeksville.mesh.repository.radio.MeshActivity
|
||||
import com.geeksville.mesh.repository.radio.RadioInterfaceService
|
||||
import com.geeksville.mesh.service.MeshServiceNotifications
|
||||
import com.geeksville.mesh.ui.sharing.toSharedContact
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
|
|
@ -53,17 +50,17 @@ import org.meshtastic.core.data.repository.FirmwareReleaseRepository
|
|||
import org.meshtastic.core.data.repository.MeshLogRepository
|
||||
import org.meshtastic.core.data.repository.NodeRepository
|
||||
import org.meshtastic.core.data.repository.PacketRepository
|
||||
import org.meshtastic.core.data.repository.QuickChatActionRepository
|
||||
import org.meshtastic.core.data.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.database.entity.MyNodeEntity
|
||||
import org.meshtastic.core.database.entity.QuickChatAction
|
||||
import org.meshtastic.core.database.entity.asDeviceVersion
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.datastore.UiPreferencesDataSource
|
||||
import org.meshtastic.core.model.util.toChannelSet
|
||||
import org.meshtastic.core.service.IMeshService
|
||||
import org.meshtastic.core.service.MeshServiceNotifications
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.toSharedContact
|
||||
import org.meshtastic.proto.AdminProtos
|
||||
import org.meshtastic.proto.AppOnlyProtos
|
||||
import org.meshtastic.proto.ConfigProtos.Config
|
||||
|
|
@ -130,7 +127,6 @@ constructor(
|
|||
private val serviceRepository: ServiceRepository,
|
||||
radioInterfaceService: RadioInterfaceService,
|
||||
meshLogRepository: MeshLogRepository,
|
||||
private val quickChatActionRepository: QuickChatActionRepository,
|
||||
firmwareReleaseRepository: FirmwareReleaseRepository,
|
||||
private val uiPreferencesDataSource: UiPreferencesDataSource,
|
||||
private val meshServiceNotifications: MeshServiceNotifications,
|
||||
|
|
@ -217,12 +213,6 @@ constructor(
|
|||
.map { it.coerceAtLeast(0) }
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000L), 0)
|
||||
|
||||
val quickChatActions
|
||||
get() =
|
||||
quickChatActionRepository
|
||||
.getAllActions()
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
|
||||
|
||||
// hardware info about our local device (can be null)
|
||||
val myNodeInfo: StateFlow<MyNodeEntity?>
|
||||
get() = nodeDB.myNodeInfo
|
||||
|
|
@ -337,20 +327,6 @@ constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun addQuickChatAction(action: QuickChatAction) =
|
||||
viewModelScope.launch(Dispatchers.IO) { quickChatActionRepository.upsert(action) }
|
||||
|
||||
fun deleteQuickChatAction(action: QuickChatAction) =
|
||||
viewModelScope.launch(Dispatchers.IO) { quickChatActionRepository.delete(action) }
|
||||
|
||||
fun updateActionPositions(actions: List<QuickChatAction>) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
for (position in actions.indices) {
|
||||
quickChatActionRepository.setItemPosition(actions[position].uuid, position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val tracerouteResponse: LiveData<String?>
|
||||
get() = serviceRepository.tracerouteResponse.asLiveData()
|
||||
|
||||
|
|
|
|||
|
|
@ -24,13 +24,13 @@ import androidx.navigation.navDeepLink
|
|||
import androidx.navigation.navigation
|
||||
import androidx.navigation.toRoute
|
||||
import com.geeksville.mesh.ui.contact.ContactsScreen
|
||||
import com.geeksville.mesh.ui.message.MessageScreen
|
||||
import com.geeksville.mesh.ui.message.QuickChatScreen
|
||||
import com.geeksville.mesh.ui.sharing.ShareScreen
|
||||
import org.meshtastic.core.navigation.ChannelsRoutes
|
||||
import org.meshtastic.core.navigation.ContactsRoutes
|
||||
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
|
||||
import org.meshtastic.core.navigation.NodesRoutes
|
||||
import org.meshtastic.feature.messaging.MessageScreen
|
||||
import org.meshtastic.feature.messaging.QuickChatScreen
|
||||
|
||||
@Suppress("LongMethod")
|
||||
fun NavGraphBuilder.contactsGraph(navController: NavHostController) {
|
||||
|
|
|
|||
|
|
@ -81,6 +81,8 @@ import org.meshtastic.core.prefs.mesh.MeshPrefs
|
|||
import org.meshtastic.core.prefs.ui.UiPrefs
|
||||
import org.meshtastic.core.service.ConnectionState
|
||||
import org.meshtastic.core.service.IMeshService
|
||||
import org.meshtastic.core.service.MeshServiceNotifications
|
||||
import org.meshtastic.core.service.SERVICE_NOTIFY_ID
|
||||
import org.meshtastic.core.service.ServiceAction
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.core.strings.R
|
||||
|
|
@ -354,7 +356,7 @@ class MeshService : Service() {
|
|||
try {
|
||||
ServiceCompat.startForeground(
|
||||
this,
|
||||
MeshServiceNotifications.SERVICE_NOTIFY_ID,
|
||||
SERVICE_NOTIFY_ID,
|
||||
notification,
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
if (hasLocationPermission()) {
|
||||
|
|
|
|||
|
|
@ -37,13 +37,17 @@ import androidx.core.net.toUri
|
|||
import com.geeksville.mesh.MainActivity
|
||||
import com.geeksville.mesh.R.raw
|
||||
import com.geeksville.mesh.service.ReplyReceiver.Companion.KEY_TEXT_REPLY
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import org.meshtastic.core.database.entity.NodeEntity
|
||||
import org.meshtastic.core.model.util.formatUptime
|
||||
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
|
||||
import org.meshtastic.core.service.MeshServiceNotifications
|
||||
import org.meshtastic.core.service.SERVICE_NOTIFY_ID
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.proto.MeshProtos
|
||||
import org.meshtastic.proto.TelemetryProtos
|
||||
import org.meshtastic.proto.TelemetryProtos.LocalStats
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Manages the creation and display of all app notifications.
|
||||
|
|
@ -52,14 +56,14 @@ import org.meshtastic.proto.TelemetryProtos.LocalStats
|
|||
* notifications for various events like new messages, alerts, and service status changes.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
class MeshServiceNotifications(private val context: Context) {
|
||||
class MeshServiceNotificationsImpl @Inject constructor(@ApplicationContext private val context: Context) :
|
||||
MeshServiceNotifications {
|
||||
|
||||
private val notificationManager = context.getSystemService<NotificationManager>()!!
|
||||
|
||||
companion object {
|
||||
private const val FIFTEEN_MINUTES_IN_MILLIS = 15L * 60 * 1000
|
||||
const val MAX_BATTERY_LEVEL = 100
|
||||
const val SERVICE_NOTIFY_ID = 101
|
||||
private val NOTIFICATION_LIGHT_COLOR = Color.BLUE
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +143,7 @@ class MeshServiceNotifications(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
fun clearNotifications() {
|
||||
override fun clearNotifications() {
|
||||
notificationManager.cancelAll()
|
||||
}
|
||||
|
||||
|
|
@ -147,7 +151,7 @@ class MeshServiceNotifications(private val context: Context) {
|
|||
* Creates all necessary notification channels on devices running Android O or newer. This should be called once
|
||||
* when the service is created.
|
||||
*/
|
||||
fun initChannels() {
|
||||
override fun initChannels() {
|
||||
NotificationType.allTypes().forEach { type -> createNotificationChannel(type) }
|
||||
}
|
||||
|
||||
|
|
@ -212,9 +216,9 @@ class MeshServiceNotifications(private val context: Context) {
|
|||
var cachedMessage: String? = null
|
||||
|
||||
// region Public Notification Methods
|
||||
fun updateServiceStateNotification(
|
||||
override fun updateServiceStateNotification(
|
||||
summaryString: String?,
|
||||
telemetry: TelemetryProtos.Telemetry? = cachedTelemetry,
|
||||
telemetry: TelemetryProtos.Telemetry?,
|
||||
): Notification {
|
||||
val hasLocalStats = telemetry?.hasLocalStats() == true
|
||||
val hasDeviceMetrics = telemetry?.hasDeviceMetrics() == true
|
||||
|
|
@ -249,39 +253,39 @@ class MeshServiceNotifications(private val context: Context) {
|
|||
return notification
|
||||
}
|
||||
|
||||
fun updateMessageNotification(contactKey: String, name: String, message: String, isBroadcast: Boolean) {
|
||||
override fun updateMessageNotification(contactKey: String, name: String, message: String, isBroadcast: Boolean) {
|
||||
val notification = createMessageNotification(contactKey, name, message, isBroadcast)
|
||||
// Use a consistent, unique ID for each message conversation.
|
||||
notificationManager.notify(contactKey.hashCode(), notification)
|
||||
}
|
||||
|
||||
fun showAlertNotification(contactKey: String, name: String, alert: String) {
|
||||
override fun showAlertNotification(contactKey: String, name: String, alert: String) {
|
||||
val notification = createAlertNotification(contactKey, name, alert)
|
||||
// Use a consistent, unique ID for each alert source.
|
||||
notificationManager.notify(name.hashCode(), notification)
|
||||
}
|
||||
|
||||
fun showNewNodeSeenNotification(node: NodeEntity) {
|
||||
override fun showNewNodeSeenNotification(node: NodeEntity) {
|
||||
val notification = createNewNodeSeenNotification(node.user.shortName, node.user.longName)
|
||||
notificationManager.notify(node.num, notification)
|
||||
}
|
||||
|
||||
fun showOrUpdateLowBatteryNotification(node: NodeEntity, isRemote: Boolean) {
|
||||
override fun showOrUpdateLowBatteryNotification(node: NodeEntity, isRemote: Boolean) {
|
||||
val notification = createLowBatteryNotification(node, isRemote)
|
||||
notificationManager.notify(node.num, notification)
|
||||
}
|
||||
|
||||
fun showClientNotification(clientNotification: MeshProtos.ClientNotification) {
|
||||
override fun showClientNotification(clientNotification: MeshProtos.ClientNotification) {
|
||||
val notification =
|
||||
createClientNotification(context.getString(R.string.client_notification), clientNotification.message)
|
||||
notificationManager.notify(clientNotification.toString().hashCode(), notification)
|
||||
}
|
||||
|
||||
fun cancelMessageNotification(contactKey: String) = notificationManager.cancel(contactKey.hashCode())
|
||||
override fun cancelMessageNotification(contactKey: String) = notificationManager.cancel(contactKey.hashCode())
|
||||
|
||||
fun cancelLowBatteryNotification(node: NodeEntity) = notificationManager.cancel(node.num)
|
||||
override fun cancelLowBatteryNotification(node: NodeEntity) = notificationManager.cancel(node.num)
|
||||
|
||||
fun clearClientNotification(notification: MeshProtos.ClientNotification) =
|
||||
override fun clearClientNotification(notification: MeshProtos.ClientNotification) =
|
||||
notificationManager.cancel(notification.toString().hashCode())
|
||||
|
||||
// endregion
|
||||
|
|
@ -22,6 +22,7 @@ import androidx.core.app.RemoteInput
|
|||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import jakarta.inject.Inject
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.service.MeshServiceNotifications
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
|
||||
/**
|
||||
|
|
@ -58,7 +59,7 @@ class ReplyReceiver : BroadcastReceiver() {
|
|||
val contactKey = intent.getStringExtra(CONTACT_KEY) ?: ""
|
||||
val message = remoteInput.getCharSequence(KEY_TEXT_REPLY)?.toString() ?: ""
|
||||
sendMessage(message, contactKey)
|
||||
MeshServiceNotifications(context).cancelMessageNotification(contactKey)
|
||||
MeshServiceNotificationsImpl(context).cancelMessageNotification(contactKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,6 @@ import com.geeksville.mesh.ui.common.components.ScannedQrCodeDialog
|
|||
import com.geeksville.mesh.ui.connections.DeviceType
|
||||
import com.geeksville.mesh.ui.connections.components.ConnectionsNavIcon
|
||||
import com.geeksville.mesh.ui.metrics.annotateTraceroute
|
||||
import com.geeksville.mesh.ui.sharing.SharedContactDialog
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
|
|
@ -119,6 +118,7 @@ import org.meshtastic.core.ui.icon.Map
|
|||
import org.meshtastic.core.ui.icon.MeshtasticIcons
|
||||
import org.meshtastic.core.ui.icon.Nodes
|
||||
import org.meshtastic.core.ui.icon.Settings
|
||||
import org.meshtastic.core.ui.share.SharedContactDialog
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusBlue
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
|
||||
import org.meshtastic.feature.settings.navigation.settingsGraph
|
||||
|
|
|
|||
|
|
@ -36,10 +36,10 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.sharing.SharedContactDialog
|
||||
import org.meshtastic.core.database.entity.FirmwareRelease
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.SharedContactDialog
|
||||
import org.meshtastic.core.ui.component.TitledCard
|
||||
import org.meshtastic.core.ui.component.preview.NodePreviewParameterProvider
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
|
|
|||
|
|
@ -57,14 +57,14 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ui.sharing.AddContactFAB
|
||||
import com.geeksville.mesh.ui.sharing.supportsQrCodeSharing
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.service.ConnectionState
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.AddContactFAB
|
||||
import org.meshtastic.core.ui.component.MainAppBar
|
||||
import org.meshtastic.core.ui.component.rememberTimeTickWithLifecycle
|
||||
import org.meshtastic.core.ui.component.supportsQrCodeSharing
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
|
||||
import org.meshtastic.feature.node.component.NodeActionDialogs
|
||||
import org.meshtastic.feature.node.component.NodeFilterTextField
|
||||
|
|
|
|||
|
|
@ -469,7 +469,7 @@ private fun QrCodeImage(
|
|||
) = Image(
|
||||
painter =
|
||||
channelSet.qrCode(shouldAddChannel)?.let { BitmapPainter(it.asImageBitmap()) }
|
||||
?: painterResource(id = com.geeksville.mesh.R.drawable.qrcode),
|
||||
?: painterResource(id = org.meshtastic.core.ui.R.drawable.qrcode),
|
||||
contentDescription = stringResource(R.string.qr_code),
|
||||
modifier = modifier,
|
||||
contentScale = ContentScale.Inside,
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ dependencies {
|
|||
kover(projects.core.network)
|
||||
kover(projects.core.prefs)
|
||||
kover(projects.feature.intro)
|
||||
kover(projects.feature.messaging)
|
||||
kover(projects.feature.map)
|
||||
kover(projects.feature.node)
|
||||
kover(projects.feature.settings)
|
||||
|
|
|
|||
7
core/service/detekt-baseline.xml
Normal file
7
core/service/detekt-baseline.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>TooManyFunctions:MeshServiceNotifications.kt$MeshServiceNotifications</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2025 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.core.service
|
||||
|
||||
import android.app.Notification
|
||||
import org.meshtastic.core.database.entity.NodeEntity
|
||||
import org.meshtastic.proto.MeshProtos
|
||||
import org.meshtastic.proto.TelemetryProtos
|
||||
|
||||
const val SERVICE_NOTIFY_ID = 101
|
||||
|
||||
interface MeshServiceNotifications {
|
||||
fun clearNotifications()
|
||||
|
||||
fun initChannels()
|
||||
|
||||
fun updateServiceStateNotification(summaryString: String?, telemetry: TelemetryProtos.Telemetry?): Notification
|
||||
|
||||
fun updateMessageNotification(contactKey: String, name: String, message: String, isBroadcast: Boolean)
|
||||
|
||||
fun showAlertNotification(contactKey: String, name: String, alert: String)
|
||||
|
||||
fun showNewNodeSeenNotification(node: NodeEntity)
|
||||
|
||||
fun showOrUpdateLowBatteryNotification(node: NodeEntity, isRemote: Boolean)
|
||||
|
||||
fun showClientNotification(clientNotification: MeshProtos.ClientNotification)
|
||||
|
||||
fun cancelMessageNotification(contactKey: String)
|
||||
|
||||
fun cancelLowBatteryNotification(node: NodeEntity)
|
||||
|
||||
fun clearClientNotification(notification: MeshProtos.ClientNotification)
|
||||
}
|
||||
|
|
@ -24,12 +24,15 @@ plugins {
|
|||
android { namespace = "org.meshtastic.core.ui" }
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.data)
|
||||
implementation(projects.core.database)
|
||||
implementation(projects.core.model)
|
||||
implementation(projects.core.prefs)
|
||||
implementation(projects.core.proto)
|
||||
implementation(projects.core.service)
|
||||
implementation(projects.core.strings)
|
||||
|
||||
implementation(libs.accompanist.permissions)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.compose.material.iconsExtended)
|
||||
implementation(libs.androidx.compose.material3)
|
||||
|
|
@ -38,4 +41,7 @@ dependencies {
|
|||
implementation(libs.androidx.emoji2.emojipicker)
|
||||
implementation(libs.androidx.hilt.lifecycle.viewmodel.compose)
|
||||
implementation(libs.guava)
|
||||
implementation(libs.zxing.core)
|
||||
implementation(libs.zxing.android.embedded)
|
||||
implementation(libs.timber)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@
|
|||
<ID>ComposableParamOrder:MainAppBar.kt$MainAppBar</ID>
|
||||
<ID>ComposableParamOrder:MaterialBatteryInfo.kt$MaterialBatteryInfo</ID>
|
||||
<ID>ComposableParamOrder:NodeChip.kt$NodeChip</ID>
|
||||
<ID>ComposableParamOrder:NodeKeyStatusIcon.kt$NodeKeyStatusIcon</ID>
|
||||
<ID>ComposableParamOrder:SignalInfo.kt$SignalInfo</ID>
|
||||
<ID>ComposableParamOrder:SwitchPreference.kt$SwitchPreference</ID>
|
||||
<ID>ContentSlotReused:AdaptiveTwoPane.kt$second</ID>
|
||||
<ID>LambdaParameterEventTrailing:ContactSharing.kt$onSharedContactRequested</ID>
|
||||
<ID>LambdaParameterEventTrailing:MainAppBar.kt$onClickChip</ID>
|
||||
<ID>MagicNumber:EditIPv4Preference.kt$0xff</ID>
|
||||
<ID>MagicNumber:EditIPv4Preference.kt$16</ID>
|
||||
|
|
@ -21,6 +23,7 @@
|
|||
<ID>MagicNumber:EditListPreference.kt$67890</ID>
|
||||
<ID>MagicNumber:LazyColumnDragAndDropDemo.kt$50</ID>
|
||||
<ID>ModifierMissing:AdaptiveTwoPane.kt$AdaptiveTwoPane</ID>
|
||||
<ID>ModifierMissing:ContactSharing.kt$SharedContactDialog</ID>
|
||||
<ID>ModifierMissing:EmojiPicker.kt$EmojiPicker</ID>
|
||||
<ID>ModifierMissing:EmojiPicker.kt$EmojiPickerDialog</ID>
|
||||
<ID>ModifierMissing:IndoorAirQuality.kt$IndoorAirQuality</ID>
|
||||
|
|
@ -33,6 +36,7 @@
|
|||
<ID>ModifierMissing:SettingsItem.kt$SettingsItem</ID>
|
||||
<ID>ModifierMissing:SettingsItem.kt$SettingsItemDetail</ID>
|
||||
<ID>ModifierMissing:SettingsItem.kt$SettingsItemSwitch</ID>
|
||||
<ID>ModifierMissing:SharedContactDialog.kt$SharedContactDialog</ID>
|
||||
<ID>ModifierMissing:SimpleAlertDialog.kt$SimpleAlertDialog</ID>
|
||||
<ID>ModifierMissing:SlidingSelector.kt$OptionLabel</ID>
|
||||
<ID>ModifierNotUsedAtRoot:TextDividerPreference.kt$modifier = modifier.fillMaxWidth().padding(all = 16.dp)</ID>
|
||||
|
|
@ -44,6 +48,7 @@
|
|||
<ID>ModifierReused:TextDividerPreference.kt$Row(modifier = modifier.fillMaxWidth().padding(all = 16.dp), verticalAlignment = Alignment.CenterVertically) { Text( text = title, style = MaterialTheme.typography.bodyLarge, color = if (!enabled) { MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f) } else { Color.Unspecified }, ) if (trailingIcon != null) { Icon(trailingIcon, "trailingIcon", modifier = modifier.fillMaxWidth().wrapContentWidth(Alignment.End)) } }</ID>
|
||||
<ID>MultipleEmitters:PreferenceCategory.kt$PreferenceCategory</ID>
|
||||
<ID>ParameterNaming:BitwisePreference.kt$onItemSelected</ID>
|
||||
<ID>ParameterNaming:ContactSharing.kt$onSharedContactRequested</ID>
|
||||
<ID>ParameterNaming:DropDownPreference.kt$onItemSelected</ID>
|
||||
<ID>ParameterNaming:EditIPv4Preference.kt$onValueChanged</ID>
|
||||
<ID>ParameterNaming:EditListPreference.kt$onValuesChanged</ID>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.sharing
|
||||
package org.meshtastic.core.ui.component
|
||||
|
||||
import android.Manifest
|
||||
import android.graphics.Bitmap
|
||||
|
|
@ -57,9 +57,8 @@ import com.journeyapps.barcodescanner.ScanContract
|
|||
import com.journeyapps.barcodescanner.ScanOptions
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.CopyIconButton
|
||||
import org.meshtastic.core.ui.component.SimpleAlertDialog
|
||||
import org.meshtastic.core.ui.R
|
||||
import org.meshtastic.core.ui.share.SharedContactDialog
|
||||
import org.meshtastic.proto.AdminProtos
|
||||
import org.meshtastic.proto.MeshProtos
|
||||
import timber.log.Timber
|
||||
|
|
@ -128,16 +127,17 @@ fun AddContactFAB(
|
|||
}
|
||||
},
|
||||
) {
|
||||
Icon(imageVector = Icons.TwoTone.QrCodeScanner, contentDescription = stringResource(R.string.scan_qr_code))
|
||||
Icon(
|
||||
imageVector = Icons.TwoTone.QrCodeScanner,
|
||||
contentDescription = stringResource(org.meshtastic.core.strings.R.string.scan_qr_code),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun QrCodeImage(uri: Uri, modifier: Modifier = Modifier) = Image(
|
||||
painter =
|
||||
uri.qrCode?.let { BitmapPainter(it.asImageBitmap()) }
|
||||
?: painterResource(id = com.geeksville.mesh.R.drawable.qrcode),
|
||||
contentDescription = stringResource(R.string.qr_code),
|
||||
painter = uri.qrCode?.let { BitmapPainter(it.asImageBitmap()) } ?: painterResource(id = R.drawable.qrcode),
|
||||
contentDescription = stringResource(org.meshtastic.core.strings.R.string.qr_code),
|
||||
modifier = modifier,
|
||||
contentScale = ContentScale.Inside,
|
||||
)
|
||||
|
|
@ -165,7 +165,7 @@ fun SharedContactDialog(contact: Node?, onDismiss: () -> Unit) {
|
|||
val sharedContact = AdminProtos.SharedContact.newBuilder().setUser(contact.user).setNodeNum(contact.num).build()
|
||||
val uri = sharedContact.getSharedContactUrl()
|
||||
SimpleAlertDialog(
|
||||
title = R.string.share_contact,
|
||||
title = org.meshtastic.core.strings.R.string.share_contact,
|
||||
text = {
|
||||
Column {
|
||||
Text(contact.user.longName)
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.feature.node.component
|
||||
package org.meshtastic.core.ui.component
|
||||
|
||||
import android.util.Base64
|
||||
import androidx.annotation.StringRes
|
||||
|
|
@ -57,7 +57,6 @@ import androidx.compose.ui.window.Dialog
|
|||
import com.google.protobuf.ByteString
|
||||
import org.meshtastic.core.model.Channel
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.CopyIconButton
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.sharing
|
||||
package org.meshtastic.core.ui.share
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
|
|
@ -28,6 +28,8 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.SimpleAlertDialog
|
||||
import org.meshtastic.core.ui.component.compareUsers
|
||||
import org.meshtastic.core.ui.component.userFieldsToString
|
||||
import org.meshtastic.proto.AdminProtos
|
||||
|
||||
/** A dialog for importing a shared contact that was scanned from a QR code. */
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.sharing
|
||||
package org.meshtastic.core.ui.share
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
|
Before Width: | Height: | Size: 690 B After Width: | Height: | Size: 690 B |
48
feature/messaging/build.gradle.kts
Normal file
48
feature/messaging/build.gradle.kts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2025 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kover)
|
||||
alias(libs.plugins.meshtastic.android.library)
|
||||
alias(libs.plugins.meshtastic.android.library.compose)
|
||||
alias(libs.plugins.meshtastic.hilt)
|
||||
}
|
||||
|
||||
android { namespace = "org.meshtastic.feature.messaging" }
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.data)
|
||||
implementation(projects.core.database)
|
||||
implementation(projects.core.model)
|
||||
implementation(projects.core.prefs)
|
||||
implementation(projects.core.proto)
|
||||
implementation(projects.core.service)
|
||||
implementation(projects.core.strings)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
implementation(libs.androidx.compose.material.iconsExtended)
|
||||
implementation(libs.androidx.compose.material3)
|
||||
implementation(libs.androidx.compose.ui.text)
|
||||
implementation(libs.androidx.compose.ui.tooling.preview)
|
||||
implementation(libs.androidx.hilt.lifecycle.viewmodel.compose)
|
||||
implementation(libs.timber)
|
||||
|
||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
|
||||
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
||||
androidTestImplementation(libs.androidx.test.ext.junit)
|
||||
}
|
||||
32
feature/messaging/detekt-baseline.xml
Normal file
32
feature/messaging/detekt-baseline.xml
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues>
|
||||
<ID>ComposableParamOrder:Message.kt$MessageScreen</ID>
|
||||
<ID>ComposableParamOrder:Message.kt$QuickChatRow</ID>
|
||||
<ID>ComposableParamOrder:MessageActions.kt$MessageActions</ID>
|
||||
<ID>ComposableParamOrder:MessageActions.kt$MessageStatusButton</ID>
|
||||
<ID>ComposableParamOrder:MessageItem.kt$MessageItem</ID>
|
||||
<ID>ComposableParamOrder:MessageList.kt$DeliveryInfo</ID>
|
||||
<ID>ComposableParamOrder:MessageList.kt$MessageList</ID>
|
||||
<ID>ComposableParamOrder:QuickChat.kt$OutlinedTextFieldWithCounter</ID>
|
||||
<ID>LambdaParameterEventTrailing:Message.kt$onClick</ID>
|
||||
<ID>LambdaParameterEventTrailing:Message.kt$onSendMessage</ID>
|
||||
<ID>LambdaParameterEventTrailing:MessageList.kt$onReply</ID>
|
||||
<ID>LambdaParameterEventTrailing:QuickChat.kt$onNavigateUp</ID>
|
||||
<ID>LambdaParameterInRestartableEffect:MessageList.kt$onUnreadChanged</ID>
|
||||
<ID>LongParameterList:MessageViewModel.kt$MessageViewModel$( private val nodeRepository: NodeRepository, radioConfigRepository: RadioConfigRepository, quickChatActionRepository: QuickChatActionRepository, private val serviceRepository: ServiceRepository, private val packetRepository: PacketRepository, private val uiPrefs: UiPrefs, private val meshServiceNotifications: MeshServiceNotifications, )</ID>
|
||||
<ID>ModifierMissing:Message.kt$MessageScreen</ID>
|
||||
<ID>ModifierMissing:MessageActions.kt$MessageStatusButton</ID>
|
||||
<ID>ModifierMissing:MessageActions.kt$ReactionButton</ID>
|
||||
<ID>ModifierMissing:MessageActions.kt$ReplyButton</ID>
|
||||
<ID>ModifierMissing:Reaction.kt$ReactionDialog</ID>
|
||||
<ID>ModifierNotUsedAtRoot:QuickChat.kt$modifier = modifier.fillMaxSize().padding(innerPadding)</ID>
|
||||
<ID>MutableStateParam:MessageList.kt$selectedIds</ID>
|
||||
<ID>ParameterNaming:MessageList.kt$onUnreadChanged</ID>
|
||||
<ID>PreviewPublic:Reaction.kt$ReactionItemPreview</ID>
|
||||
<ID>PreviewPublic:Reaction.kt$ReactionRowPreview</ID>
|
||||
<ID>TooManyFunctions:MessageViewModel.kt$MessageViewModel : ViewModel</ID>
|
||||
<ID>Wrapping:Message.kt${ event -> when (event) { is MessageScreenEvent.SendMessage -> { viewModel.sendMessage(event.text, contactKey, event.replyingToPacketId) if (event.replyingToPacketId != null) replyingToPacketId = null messageInputState.clearText() } is MessageScreenEvent.SendReaction -> viewModel.sendReaction(event.emoji, event.messageId, contactKey) is MessageScreenEvent.DeleteMessages -> { viewModel.deleteMessages(event.ids) selectedMessageIds.value = emptySet() showDeleteDialog = false } is MessageScreenEvent.ClearUnreadCount -> viewModel.clearUnreadCount(contactKey, event.lastReadMessageId) is MessageScreenEvent.NodeDetails -> navigateToNodeDetails(event.node.num) is MessageScreenEvent.SetTitle -> viewModel.setTitle(event.title) is MessageScreenEvent.NavigateToMessages -> navigateToMessages(event.contactKey) is MessageScreenEvent.NavigateToNodeDetails -> navigateToNodeDetails(event.nodeNum) MessageScreenEvent.NavigateBack -> onNavigateBack() is MessageScreenEvent.CopyToClipboard -> { clipboardManager.nativeClipboard.setPrimaryClip(ClipData.newPlainText(event.text, event.text)) selectedMessageIds.value = emptySet() } } }</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
@ -15,13 +15,12 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.compose
|
||||
package org.meshtastic.feature.messaging.component
|
||||
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.geeksville.mesh.ui.message.components.MessageItem
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
@file:Suppress("TooManyFunctions")
|
||||
|
||||
package com.geeksville.mesh.ui.message
|
||||
package org.meshtastic.feature.messaging
|
||||
|
||||
import android.content.ClipData
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
|
|
@ -91,7 +91,6 @@ import androidx.compose.ui.tooling.preview.PreviewLightDark
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ui.sharing.SharedContactDialog
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.database.entity.QuickChatAction
|
||||
|
|
@ -100,9 +99,10 @@ import org.meshtastic.core.database.model.Node
|
|||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.util.getChannel
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.NodeKeyStatusIcon
|
||||
import org.meshtastic.core.ui.component.SecurityIcon
|
||||
import org.meshtastic.core.ui.component.SharedContactDialog
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import org.meshtastic.feature.node.component.NodeKeyStatusIcon
|
||||
import org.meshtastic.proto.AppOnlyProtos
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ private const val ROUNDED_CORNER_PERCENT = 100
|
|||
*/
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod") // Due to multiple states and event handling
|
||||
@Composable
|
||||
internal fun MessageScreen(
|
||||
fun MessageScreen(
|
||||
contactKey: String,
|
||||
message: String,
|
||||
viewModel: MessageViewModel = hiltViewModel(),
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.message
|
||||
package org.meshtastic.feature.messaging
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
|
|
@ -46,8 +46,6 @@ import androidx.compose.ui.platform.LocalHapticFeedback
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.message.components.MessageItem
|
||||
import com.geeksville.mesh.ui.message.components.ReactionDialog
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
|
|
@ -57,6 +55,8 @@ import org.meshtastic.core.database.model.Message
|
|||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.MessageStatus
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.feature.messaging.component.MessageItem
|
||||
import org.meshtastic.feature.messaging.component.ReactionDialog
|
||||
|
||||
@Composable
|
||||
fun DeliveryInfo(
|
||||
|
|
@ -15,14 +15,11 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.message
|
||||
package org.meshtastic.feature.messaging
|
||||
|
||||
import org.meshtastic.core.database.model.Node
|
||||
|
||||
/**
|
||||
* Defines the various user interactions that can occur on the [MessageScreen]. These events are typically handled by
|
||||
* the [com.geeksville.mesh.model.UIViewModel].
|
||||
*/
|
||||
/** Defines the various user interactions that can occur on the MessageScreen. */
|
||||
internal sealed interface MessageScreenEvent {
|
||||
/** Send a new text message. */
|
||||
data class SendMessage(val text: String, val replyingToPacketId: Int? = null) : MessageScreenEvent
|
||||
|
|
@ -15,12 +15,11 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.message
|
||||
package org.meshtastic.feature.messaging
|
||||
|
||||
import android.os.RemoteException
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.geeksville.mesh.service.MeshServiceNotifications
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -41,6 +40,7 @@ import org.meshtastic.core.database.model.Node
|
|||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.DeviceVersion
|
||||
import org.meshtastic.core.prefs.ui.UiPrefs
|
||||
import org.meshtastic.core.service.MeshServiceNotifications
|
||||
import org.meshtastic.core.service.ServiceAction
|
||||
import org.meshtastic.core.service.ServiceRepository
|
||||
import org.meshtastic.proto.channelSet
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.message
|
||||
package org.meshtastic.feature.messaging
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
|
@ -71,7 +71,6 @@ import androidx.compose.ui.tooling.preview.PreviewLightDark
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import org.meshtastic.core.database.entity.QuickChatAction
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.MainAppBar
|
||||
|
|
@ -81,9 +80,9 @@ import org.meshtastic.core.ui.component.rememberDragDropState
|
|||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
internal fun QuickChatScreen(
|
||||
fun QuickChatScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: UIViewModel = hiltViewModel(),
|
||||
viewModel: QuickChatViewModel = hiltViewModel(),
|
||||
onNavigateUp: () -> Unit,
|
||||
) {
|
||||
val actions by viewModel.quickChatActions.collectAsStateWithLifecycle()
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2025 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.feature.messaging
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.data.repository.QuickChatActionRepository
|
||||
import org.meshtastic.core.database.entity.QuickChatAction
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class QuickChatViewModel @Inject constructor(private val quickChatActionRepository: QuickChatActionRepository) :
|
||||
ViewModel() {
|
||||
val quickChatActions
|
||||
get() =
|
||||
quickChatActionRepository
|
||||
.getAllActions()
|
||||
.stateIn(viewModelScope, SharingStarted.Companion.WhileSubscribed(5_000), emptyList())
|
||||
|
||||
fun updateActionPositions(actions: List<QuickChatAction>) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
for (position in actions.indices) {
|
||||
quickChatActionRepository.setItemPosition(actions[position].uuid, position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addQuickChatAction(action: QuickChatAction) =
|
||||
viewModelScope.launch(Dispatchers.IO) { quickChatActionRepository.upsert(action) }
|
||||
|
||||
fun deleteQuickChatAction(action: QuickChatAction) =
|
||||
viewModelScope.launch(Dispatchers.IO) { quickChatActionRepository.delete(action) }
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.message.components
|
||||
package org.meshtastic.feature.messaging.component
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
|
@ -44,7 +44,7 @@ import org.meshtastic.core.strings.R
|
|||
import org.meshtastic.core.ui.emoji.EmojiPickerDialog
|
||||
|
||||
@Composable
|
||||
fun ReactionButton(onSendReaction: (String) -> Unit = {}) {
|
||||
internal fun ReactionButton(onSendReaction: (String) -> Unit = {}) {
|
||||
var showEmojiPickerDialog by remember { mutableStateOf(false) }
|
||||
if (showEmojiPickerDialog) {
|
||||
EmojiPickerDialog(
|
||||
|
|
@ -61,7 +61,7 @@ fun ReactionButton(onSendReaction: (String) -> Unit = {}) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun ReplyButton(onClick: () -> Unit = {}) = IconButton(
|
||||
private fun ReplyButton(onClick: () -> Unit = {}) = IconButton(
|
||||
onClick = onClick,
|
||||
content = {
|
||||
Icon(imageVector = Icons.AutoMirrored.Filled.Reply, contentDescription = stringResource(R.string.reply))
|
||||
|
|
@ -69,7 +69,7 @@ fun ReplyButton(onClick: () -> Unit = {}) = IconButton(
|
|||
)
|
||||
|
||||
@Composable
|
||||
fun MessageStatusButton(onStatusClick: () -> Unit = {}, status: MessageStatus, fromLocal: Boolean) =
|
||||
private fun MessageStatusButton(onStatusClick: () -> Unit = {}, status: MessageStatus, fromLocal: Boolean) =
|
||||
AnimatedVisibility(visible = fromLocal) {
|
||||
IconButton(onClick = onStatusClick) {
|
||||
Icon(
|
||||
|
|
@ -89,7 +89,7 @@ fun MessageStatusButton(onStatusClick: () -> Unit = {}, status: MessageStatus, f
|
|||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun MessageActions(
|
||||
internal fun MessageActions(
|
||||
modifier: Modifier = Modifier,
|
||||
isLocal: Boolean = false,
|
||||
status: MessageStatus?,
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.message.components
|
||||
package org.meshtastic.feature.messaging.component
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.message.components
|
||||
package org.meshtastic.feature.messaging.component
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
|
|
@ -77,7 +77,7 @@ private fun ReactionItem(emoji: String, emojiCount: Int = 1, onClick: () -> Unit
|
|||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun ReactionRow(
|
||||
internal fun ReactionRow(
|
||||
modifier: Modifier = Modifier,
|
||||
reactions: List<Reaction> = emptyList(),
|
||||
onSendReaction: (String) -> Unit = {},
|
||||
|
|
@ -104,10 +104,10 @@ fun ReactionRow(
|
|||
}
|
||||
}
|
||||
|
||||
fun reduceEmojis(emojis: List<String>): Map<String, Int> = emojis.groupingBy { it }.eachCount()
|
||||
private fun reduceEmojis(emojis: List<String>): Map<String, Int> = emojis.groupingBy { it }.eachCount()
|
||||
|
||||
@Composable
|
||||
fun ReactionDialog(reactions: List<Reaction>, onDismiss: () -> Unit = {}) =
|
||||
internal fun ReactionDialog(reactions: List<Reaction>, onDismiss: () -> Unit = {}) =
|
||||
BottomSheetDialog(onDismiss = onDismiss, modifier = Modifier.fillMaxHeight(fraction = .3f)) {
|
||||
val groupedEmojis = reactions.groupBy { it.emoji }
|
||||
var selectedEmoji by remember { mutableStateOf<String?>(null) }
|
||||
|
|
@ -145,7 +145,7 @@ fun ReactionDialog(reactions: List<Reaction>, onDismiss: () -> Unit = {}) =
|
|||
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
fun ReactionItemPreview() {
|
||||
private fun ReactionItemPreview() {
|
||||
AppTheme {
|
||||
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
|
||||
ReactionItem(emoji = "\uD83D\uDE42")
|
||||
|
|
@ -157,7 +157,7 @@ fun ReactionItemPreview() {
|
|||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ReactionRowPreview() {
|
||||
private fun ReactionRowPreview() {
|
||||
AppTheme {
|
||||
ReactionRow(
|
||||
reactions =
|
||||
|
|
@ -7,7 +7,6 @@
|
|||
<ID>ComposableParamOrder:LinkedCoordinates.kt$LinkedCoordinates</ID>
|
||||
<ID>ComposableParamOrder:NodeFilterTextField.kt$NodeFilterTextField</ID>
|
||||
<ID>ComposableParamOrder:NodeItem.kt$NodeItem</ID>
|
||||
<ID>ComposableParamOrder:NodeKeyStatusIcon.kt$NodeKeyStatusIcon</ID>
|
||||
<ID>ComposableParamOrder:SatelliteCountInfo.kt$SatelliteCountInfo</ID>
|
||||
<ID>ComposableParamOrder:TracerouteButton.kt$TracerouteButton</ID>
|
||||
<ID>ModifierMissing:NodeStatusIcons.kt$NodeStatusIcons</ID>
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ import org.meshtastic.core.model.util.toDistanceString
|
|||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.MaterialBatteryInfo
|
||||
import org.meshtastic.core.ui.component.NodeChip
|
||||
import org.meshtastic.core.ui.component.NodeKeyStatusIcon
|
||||
import org.meshtastic.core.ui.component.SignalInfo
|
||||
import org.meshtastic.core.ui.component.preview.NodePreviewParameterProvider
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ include(
|
|||
":core:strings",
|
||||
":core:ui",
|
||||
":feature:intro",
|
||||
":feature:messaging",
|
||||
":feature:map",
|
||||
":feature:node",
|
||||
":feature:settings",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue