feat: Implement iOS support and unify Compose Multiplatform infrastructure (#4876)

This commit is contained in:
James Rich 2026-03-21 18:19:13 -05:00 committed by GitHub
parent f04924ded5
commit d136b162a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
170 changed files with 2208 additions and 2432 deletions

View file

@ -0,0 +1,33 @@
/*
* 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.map.navigation
import androidx.compose.runtime.Composable
import org.koin.compose.viewmodel.koinViewModel
import org.meshtastic.feature.map.MapScreen
import org.meshtastic.feature.map.SharedMapViewModel
@Composable
actual fun MapMainScreen(onClickNodeChip: (Int) -> Unit, navigateToNodeDetails: (Int) -> Unit, waypointId: Int?) {
val viewModel = koinViewModel<SharedMapViewModel>()
MapScreen(
viewModel = viewModel,
onClickNodeChip = onClickNodeChip,
navigateToNodeDetails = navigateToNodeDetails,
waypointId = waypointId,
)
}

View file

@ -18,7 +18,6 @@ package org.meshtastic.feature.map
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@ -26,6 +25,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.StringResource
import org.meshtastic.core.common.util.ioDispatcher
import org.meshtastic.core.common.util.nowSeconds
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.Node
@ -139,7 +139,7 @@ open class BaseMapViewModel(
fun getNodeOrFallback(nodeNum: Int): Node = nodeRepository.nodeDBbyNum.value[nodeNum] ?: Node(num = nodeNum)
fun deleteWaypoint(id: Int) = viewModelScope.launch(Dispatchers.IO) { packetRepository.deleteWaypoint(id) }
fun deleteWaypoint(id: Int) = viewModelScope.launch(ioDispatcher) { packetRepository.deleteWaypoint(id) }
fun sendWaypoint(wpt: Waypoint, contactKey: String = "0${DataPacket.ID_BROADCAST}") {
// contactKey: unique contact key filter (channel)+(nodeId)
@ -151,7 +151,7 @@ open class BaseMapViewModel(
}
private fun sendDataPacket(p: DataPacket) {
viewModelScope.launch(Dispatchers.IO) { radioController.sendMessage(p) }
viewModelScope.launch(ioDispatcher) { radioController.sendMessage(p) }
}
fun generatePacketId(): Int = radioController.getPacketId()

View file

@ -16,23 +16,22 @@
*/
package org.meshtastic.feature.map.navigation
import androidx.compose.runtime.Composable
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavKey
import org.koin.compose.viewmodel.koinViewModel
import org.meshtastic.core.navigation.MapRoutes
import org.meshtastic.core.navigation.NodesRoutes
import org.meshtastic.feature.map.MapScreen
import org.meshtastic.feature.map.SharedMapViewModel
fun EntryProviderScope<NavKey>.mapGraph(backStack: NavBackStack<NavKey>) {
entry<MapRoutes.Map> { args ->
val viewModel = koinViewModel<SharedMapViewModel>()
MapScreen(
viewModel = viewModel,
MapMainScreen(
onClickNodeChip = { backStack.add(NodesRoutes.NodeDetailGraph(it)) },
navigateToNodeDetails = { backStack.add(NodesRoutes.NodeDetailGraph(it)) },
waypointId = args.waypointId,
)
}
}
@Composable
expect fun MapMainScreen(onClickNodeChip: (Int) -> Unit, navigateToNodeDetails: (Int) -> Unit, waypointId: Int?)

View file

@ -0,0 +1,24 @@
/*
* 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.map.navigation
import androidx.compose.runtime.Composable
@Composable
actual fun MapMainScreen(onClickNodeChip: (Int) -> Unit, navigateToNodeDetails: (Int) -> Unit, waypointId: Int?) {
// TODO: Implement iOS map main screen
}

View file

@ -0,0 +1,40 @@
/*
* 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.map.navigation
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
@Composable
actual fun MapMainScreen(onClickNodeChip: (Int) -> Unit, navigateToNodeDetails: (Int) -> Unit, waypointId: Int?) {
// Desktop placeholder for now
org.meshtastic.feature.map.navigation.PlaceholderScreen(name = "Map")
}
@Composable
internal fun PlaceholderScreen(name: String) {
androidx.compose.foundation.layout.Box(
modifier = androidx.compose.ui.Modifier.fillMaxSize(),
contentAlignment = androidx.compose.ui.Alignment.Center,
) {
androidx.compose.material3.Text(
text = name,
style = androidx.compose.material3.MaterialTheme.typography.headlineMedium,
color = androidx.compose.material3.MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}