Refactor map layer management and navigation infrastructure (#4921)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-03-25 19:29:24 -05:00 committed by GitHub
parent b608a04ca4
commit a005231d94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
142 changed files with 5408 additions and 3090 deletions

View file

@ -1,59 +0,0 @@
/*
* Copyright (c) 2025-2026 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.navigation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavKey
import kotlinx.coroutines.flow.Flow
import org.koin.compose.viewmodel.koinViewModel
import org.meshtastic.core.ui.component.ScrollToTopEvent
import org.meshtastic.core.ui.viewmodel.UIViewModel
import org.meshtastic.feature.messaging.MessageViewModel
import org.meshtastic.feature.messaging.ui.contact.AdaptiveContactsScreen
import org.meshtastic.feature.messaging.ui.contact.ContactsViewModel
@Composable
actual fun ContactsEntryContent(
backStack: NavBackStack<NavKey>,
scrollToTopEvents: Flow<ScrollToTopEvent>,
initialContactKey: String?,
initialMessage: String,
) {
val uiViewModel: UIViewModel = koinViewModel()
val sharedContactRequested by uiViewModel.sharedContactRequested.collectAsStateWithLifecycle()
val requestChannelSet by uiViewModel.requestChannelSet.collectAsStateWithLifecycle()
val contactsViewModel = koinViewModel<ContactsViewModel>()
val messageViewModel = koinViewModel<MessageViewModel>()
initialContactKey?.let { messageViewModel.setContactKey(it) }
AdaptiveContactsScreen(
backStack = backStack,
contactsViewModel = contactsViewModel,
messageViewModel = messageViewModel,
scrollToTopEvents = scrollToTopEvents,
sharedContactRequested = sharedContactRequested,
requestChannelSet = requestChannelSet,
onHandleDeepLink = uiViewModel::handleDeepLink,
onClearSharedContactRequested = uiViewModel::clearSharedContactRequested,
onClearRequestChannelUrl = uiViewModel::clearRequestChannelUrl,
initialContactKey = initialContactKey,
initialMessage = initialMessage,
)
}

View file

@ -17,6 +17,8 @@
package org.meshtastic.feature.messaging.navigation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavKey
@ -27,6 +29,7 @@ import org.meshtastic.core.navigation.ContactsRoutes
import org.meshtastic.core.ui.component.ScrollToTopEvent
import org.meshtastic.feature.messaging.QuickChatScreen
import org.meshtastic.feature.messaging.QuickChatViewModel
import org.meshtastic.feature.messaging.ui.contact.AdaptiveContactsScreen
import org.meshtastic.feature.messaging.ui.contact.ContactsViewModel
import org.meshtastic.feature.messaging.ui.sharing.ShareScreen
@ -73,9 +76,44 @@ fun EntryProviderScope<NavKey>.contactsGraph(
}
@Composable
expect fun ContactsEntryContent(
fun ContactsEntryContent(
backStack: NavBackStack<NavKey>,
scrollToTopEvents: Flow<ScrollToTopEvent>,
initialContactKey: String? = null,
initialMessage: String = "",
)
) {
val uiViewModel: org.meshtastic.core.ui.viewmodel.UIViewModel = koinViewModel()
val sharedContactRequested by uiViewModel.sharedContactRequested.collectAsStateWithLifecycle()
val requestChannelSet by uiViewModel.requestChannelSet.collectAsStateWithLifecycle()
val contactsViewModel = koinViewModel<ContactsViewModel>()
AdaptiveContactsScreen(
backStack = backStack,
contactsViewModel = contactsViewModel,
messageViewModel = koinViewModel(), // Ignored by custom detail pane below
scrollToTopEvents = scrollToTopEvents,
sharedContactRequested = sharedContactRequested,
requestChannelSet = requestChannelSet,
onHandleDeepLink = uiViewModel::handleDeepLink,
onClearSharedContactRequested = uiViewModel::clearSharedContactRequested,
onClearRequestChannelUrl = uiViewModel::clearRequestChannelUrl,
initialContactKey = initialContactKey,
initialMessage = initialMessage,
detailPaneCustom = { contactKey ->
val messageViewModel: org.meshtastic.feature.messaging.MessageViewModel =
koinViewModel(key = "messages-$contactKey")
messageViewModel.setContactKey(contactKey)
org.meshtastic.feature.messaging.MessageScreen(
contactKey = contactKey,
message = if (contactKey == initialContactKey) initialMessage else "",
viewModel = messageViewModel,
navigateToNodeDetails = {
backStack.add(org.meshtastic.core.navigation.NodesRoutes.NodeDetailGraph(it))
},
navigateToQuickChatOptions = { backStack.add(org.meshtastic.core.navigation.ContactsRoutes.QuickChat) },
onNavigateBack = { backStack.removeLastOrNull() },
)
},
)
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
* Copyright (c) 2026 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
@ -14,20 +14,17 @@
* 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.navigation
package org.meshtastic.feature.messaging
import androidx.compose.runtime.Composable
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavKey
import kotlinx.coroutines.flow.Flow
import org.meshtastic.core.ui.component.ScrollToTopEvent
import kotlin.test.Test
import kotlin.test.assertEquals
@Composable
actual fun ContactsEntryContent(
backStack: NavBackStack<NavKey>,
scrollToTopEvents: Flow<ScrollToTopEvent>,
initialContactKey: String?,
initialMessage: String,
) {
// TODO: Implement iOS contacts screen
class UnreadUiDefaultsTest {
@Test
fun `defaults are set correctly`() {
assertEquals(5, UnreadUiDefaults.VISIBLE_CONTEXT_COUNT)
assertEquals(8, UnreadUiDefaults.AUTO_SCROLL_BOTTOM_OFFSET_TOLERANCE)
assertEquals(500L, UnreadUiDefaults.SCROLL_DEBOUNCE_MILLIS)
}
}

View file

@ -1,64 +0,0 @@
/*
* Copyright (c) 2025-2026 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.navigation
import androidx.compose.runtime.Composable
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavKey
import kotlinx.coroutines.flow.Flow
import org.koin.compose.viewmodel.koinViewModel
import org.meshtastic.core.ui.component.ScrollToTopEvent
import org.meshtastic.feature.messaging.MessageScreen
import org.meshtastic.feature.messaging.MessageViewModel
import org.meshtastic.feature.messaging.ui.contact.AdaptiveContactsScreen
import org.meshtastic.feature.messaging.ui.contact.ContactsViewModel
@Composable
actual fun ContactsEntryContent(
backStack: NavBackStack<NavKey>,
scrollToTopEvents: Flow<ScrollToTopEvent>,
initialContactKey: String?,
initialMessage: String,
) {
val viewModel: ContactsViewModel = koinViewModel()
AdaptiveContactsScreen(
backStack = backStack,
contactsViewModel = viewModel,
messageViewModel = koinViewModel(), // Used for desktop detail pane
scrollToTopEvents = scrollToTopEvents,
sharedContactRequested = null,
requestChannelSet = null,
onHandleDeepLink = { _, _ -> },
onClearSharedContactRequested = {},
onClearRequestChannelUrl = {},
initialContactKey = initialContactKey,
initialMessage = initialMessage,
detailPaneCustom = { contactKey ->
val messageViewModel: MessageViewModel = koinViewModel(key = "messages-$contactKey")
MessageScreen(
contactKey = contactKey,
message = if (contactKey == initialContactKey) initialMessage else "",
viewModel = messageViewModel,
navigateToNodeDetails = {
backStack.add(org.meshtastic.core.navigation.NodesRoutes.NodeDetailGraph(it))
},
navigateToQuickChatOptions = { backStack.add(org.meshtastic.core.navigation.ContactsRoutes.QuickChat) },
onNavigateBack = { backStack.removeLastOrNull() },
)
},
)
}