Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f359475
refactor: migrate repository interfaces and models to common KMP modules
jamesarich Mar 2, 2026
17448d8
refactor: transition repositories and managers to interface-based arc…
jamesarich Mar 2, 2026
56b4828
refactor: migrate history management and manager logic to core modules
jamesarich Mar 2, 2026
df43d53
refactor: extract RadioController interface and move mesh logic to co…
jamesarich Mar 2, 2026
41b26f2
refactor: migrate repository interfaces and models to common KMP modules
jamesarich Mar 2, 2026
3ea8f81
refactor: transition repositories and managers to interface-based arc…
jamesarich Mar 2, 2026
159490b
refactor: migrate history management and manager logic to core modules
jamesarich Mar 2, 2026
836bf4b
refactor: extract RadioController interface and move mesh logic to co…
jamesarich Mar 2, 2026
f2fc3bc
refactor: extract interfaces and migrate mesh service handlers to cor…
jamesarich Mar 2, 2026
b2f8e0f
refactor: move core services and logic to core modules
jamesarich Mar 2, 2026
36ffc7d
refactor: reformat code and consolidate DI bindings
jamesarich Mar 2, 2026
a00ce2d
feat: add node upsert and improve messaging reliability
jamesarich Mar 2, 2026
0389ea4
ci: refactor reusable-check task logic and separate static analysis
jamesarich Mar 2, 2026
a62933c
spotless
jamesarich Mar 2, 2026
789a0fd
fix tests
jamesarich Mar 2, 2026
f07d52a
refactor: move UNSET region to end and strip base64 padding from URLs
jamesarich Mar 3, 2026
c9cf875
feat: use goAsync and suspend functions in ReplyReceiver
jamesarich Mar 3, 2026
d661dc2
refactor: move DatabaseManager to repository and rename getCacheLimit…
jamesarich Mar 3, 2026
b616567
feat(ble): add isBonded check to BluetoothRepository
jamesarich Mar 3, 2026
3b39fa4
refactor: match packets by key fields instead of full object equality
jamesarich Mar 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 20 additions & 30 deletions .github/workflows/reusable-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,51 +89,41 @@ jobs:
- name: Determine Tasks
id: tasks
run: |
TASKS=""
# Only run Lint and Unit Tests on the first API level and first flavor in the matrix to save time and resources
FLAVOR="${{ matrix.flavor }}"
FLAVOR_CAP=$(echo $FLAVOR | awk '{print toupper(substr($0,1,1))substr($0,2)}')
IS_FIRST_API=$(echo '${{ inputs.api_levels }}' | jq -r '.[0] == ${{ matrix.api_level }}')
IS_FIRST_FLAVOR=$(echo '${{ inputs.flavors }}' | jq -r '.[0] == "${{ matrix.flavor }}"')

if [ "$IS_FIRST_API" = "true" ] && [ "$IS_FIRST_FLAVOR" = "true" ]; then
[ "${{ inputs.run_lint }}" = "true" ] && TASKS="$TASKS spotlessCheck detekt "
[ "${{ inputs.run_unit_tests }}" = "true" ] && TASKS="$TASKS testDebugUnitTest "
fi

FLAVOR="${{ matrix.flavor }}"
if [ "$IS_FIRST_API" = "true" ]; then
if [ "$FLAVOR" = "google" ]; then
TASKS="$TASKS assembleGoogleDebug "
[ "${{ inputs.run_unit_tests }}" = "true" ] && TASKS="$TASKS testGoogleDebugUnitTest "
elif [ "$FLAVOR" = "fdroid" ]; then
TASKS="$TASKS assembleFdroidDebug "
[ "${{ inputs.run_unit_tests }}" = "true" ] && TASKS="$TASKS testFdroidDebugUnitTest "
fi
fi
# Matrix-specific tasks
TASKS="assemble${FLAVOR_CAP}Debug "
[ "${{ inputs.run_lint }}" = "true" ] && TASKS="$TASKS lint${FLAVOR_CAP}Debug "
[ "${{ inputs.run_unit_tests }}" = "true" ] && TASKS="$TASKS test${FLAVOR_CAP}DebugUnitTest "

# Instrumented Test Tasks
if [ "${{ inputs.run_instrumented_tests }}" = "true" ]; then
[ "$IS_FIRST_FLAVOR" = "true" ] && TASKS="$TASKS connectedDebugAndroidTest "
if [ "$FLAVOR" = "google" ]; then
TASKS="$TASKS connectedGoogleDebugAndroidTest "
elif [ "$FLAVOR" = "fdroid" ]; then
TASKS="$TASKS connectedFdroidDebugAndroidTest "
fi
fi

# Run coverage report if unit tests were executed
if [ "${{ inputs.run_unit_tests }}" = "true" ] && [ "$IS_FIRST_API" = "true" ]; then
if [ "$IS_FIRST_FLAVOR" = "true" ]; then
TASKS="$TASKS koverXmlReportDebug "
fi
if [ "$FLAVOR" = "google" ]; then
TASKS="$TASKS koverXmlReportGoogleDebug "
elif [ "$FLAVOR" = "fdroid" ]; then
TASKS="$TASKS koverXmlReportFdroidDebug "
fi
# Run coverage report for this flavor
if [ "${{ inputs.run_unit_tests }}" = "true" ]; then
TASKS="$TASKS koverXmlReport${FLAVOR_CAP}Debug "
fi

echo "tasks=$TASKS" >> $GITHUB_OUTPUT
echo "is_first_api=$IS_FIRST_API" >> $GITHUB_OUTPUT
echo "is_first_flavor=$IS_FIRST_FLAVOR" >> $GITHUB_OUTPUT

- name: Code Style & Static Analysis
if: steps.tasks.outputs.is_first_api == 'true' && steps.tasks.outputs.is_first_flavor == 'true'
run: ./gradlew spotlessCheck detekt -Pci=true

- name: Shared Unit Tests
if: steps.tasks.outputs.is_first_api == 'true' && steps.tasks.outputs.is_first_flavor == 'true' && inputs.run_unit_tests == true
run: ./gradlew testDebugUnitTest koverXmlReportDebug -Pci=true --continue

- name: Enable KVM group perms
if: inputs.run_instrumented_tests == true
Expand All @@ -142,7 +132,7 @@ jobs:
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: Run Check (with Emulator)
- name: Run Flavor Check (with Emulator)
if: inputs.run_instrumented_tests == true
uses: reactivecircus/android-emulator-runner@v2
env:
Expand All @@ -155,7 +145,7 @@ jobs:
disable-animations: true
script: ./gradlew ${{ steps.tasks.outputs.tasks }} -Pci=true -PenableComposeCompilerMetrics=true -PenableComposeCompilerReports=true --continue --scan

- name: Run Check (no Emulator)
- name: Run Flavor Check (no Emulator)
if: inputs.run_instrumented_tests == false
env:
VERSION_CODE: ${{ steps.calculate_version_code.outputs.versionCode }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.meshtastic.core.prefs.filter.FilterPrefs
import org.meshtastic.core.service.filter.MessageFilterService
import org.meshtastic.core.repository.MessageFilter
import javax.inject.Inject

@HiltAndroidTest
Expand All @@ -37,7 +37,7 @@ class MessageFilterIntegrationTest {

@Inject lateinit var filterPrefs: FilterPrefs

@Inject lateinit var filterService: MessageFilterService
@Inject lateinit var filterService: MessageFilter

@Before
fun setup() {
Expand Down
24 changes: 21 additions & 3 deletions app/src/main/java/com/geeksville/mesh/ApplicationModule.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* 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
Expand All @@ -14,21 +14,25 @@
* 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 com.geeksville.mesh

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import com.geeksville.mesh.repository.radio.AndroidRadioInterfaceService
import com.geeksville.mesh.service.AndroidAppWidgetUpdater
import com.geeksville.mesh.service.AndroidMeshLocationManager
import com.geeksville.mesh.service.AndroidMeshWorkerManager
import com.geeksville.mesh.service.MeshServiceNotificationsImpl
import com.geeksville.mesh.service.ServiceBroadcasts
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.di.ProcessLifecycle
import org.meshtastic.core.service.MeshServiceNotifications
import org.meshtastic.core.repository.MeshServiceNotifications
import javax.inject.Singleton

@InstallIn(SingletonComponent::class)
Expand All @@ -37,6 +41,20 @@ interface ApplicationModule {

@Binds fun bindMeshServiceNotifications(impl: MeshServiceNotificationsImpl): MeshServiceNotifications

@Binds
fun bindMeshLocationManager(impl: AndroidMeshLocationManager): org.meshtastic.core.repository.MeshLocationManager

@Binds fun bindMeshWorkerManager(impl: AndroidMeshWorkerManager): org.meshtastic.core.repository.MeshWorkerManager

@Binds fun bindAppWidgetUpdater(impl: AndroidAppWidgetUpdater): org.meshtastic.core.repository.AppWidgetUpdater

@Binds
fun bindRadioInterfaceService(
impl: AndroidRadioInterfaceService,
): org.meshtastic.core.repository.RadioInterfaceService

@Binds fun bindServiceBroadcasts(impl: ServiceBroadcasts): org.meshtastic.core.repository.ServiceBroadcasts

companion object {
@Provides @ProcessLifecycle
fun provideProcessLifecycleOwner(): LifecycleOwner = ProcessLifecycleOwner.get()
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/com/geeksville/mesh/MeshServiceClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ import dagger.hilt.android.qualifiers.ActivityContext
import dagger.hilt.android.scopes.ActivityScoped
import kotlinx.coroutines.launch
import org.meshtastic.core.common.util.SequentialJob
import org.meshtastic.core.service.AndroidServiceRepository
import org.meshtastic.core.service.BindFailedException
import org.meshtastic.core.service.IMeshService
import org.meshtastic.core.service.ServiceClient
import org.meshtastic.core.service.ServiceRepository
import javax.inject.Inject

/** A Activity-lifecycle-aware [ServiceClient] that binds [MeshService] once the Activity is started. */
Expand All @@ -41,7 +41,7 @@ class MeshServiceClient
@Inject
constructor(
@ActivityContext private val context: Context,
private val serviceRepository: ServiceRepository,
private val serviceRepository: AndroidServiceRepository,
private val serviceSetupJob: SequentialJob,
) : ServiceClient<IMeshService>(IMeshService.Stub::asInterface),
DefaultLifecycleObserver {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@ import com.geeksville.mesh.model.DeviceListEntry
import com.geeksville.mesh.model.getMeshtasticShortName
import com.geeksville.mesh.repository.network.NetworkRepository
import com.geeksville.mesh.repository.network.NetworkRepository.Companion.toAddressString
import com.geeksville.mesh.repository.radio.RadioInterfaceService
import com.geeksville.mesh.repository.usb.UsbRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import org.jetbrains.compose.resources.getString
import org.meshtastic.core.ble.BluetoothRepository
import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.database.DatabaseManager
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.datastore.RecentAddressesDataSource
import org.meshtastic.core.datastore.model.RecentAddress
import org.meshtastic.core.model.Node
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.repository.RadioInterfaceService
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.meshtastic
import java.util.Locale
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
package com.geeksville.mesh.model

import android.hardware.usb.UsbManager
import com.geeksville.mesh.repository.radio.InterfaceId
import com.geeksville.mesh.repository.radio.RadioInterfaceService
import com.hoho.android.usbserial.driver.UsbSerialDriver
import no.nordicsemi.kotlin.ble.client.android.Peripheral
import no.nordicsemi.kotlin.ble.core.BondState
import org.meshtastic.core.ble.MeshtasticBleConstants.BLE_NAME_PATTERN
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.model.InterfaceId
import org.meshtastic.core.model.Node
import org.meshtastic.core.model.util.anonymize
import org.meshtastic.core.repository.RadioInterfaceService

/**
* A sealed class is used here to represent the different types of devices that can be displayed in the list. This is
Expand Down
26 changes: 16 additions & 10 deletions app/src/main/java/com/geeksville/mesh/model/UIViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController
import co.touchlab.kermit.Logger
import com.geeksville.mesh.repository.radio.MeshActivity
import com.geeksville.mesh.repository.radio.RadioInterfaceService
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
Expand All @@ -45,21 +43,24 @@ import org.jetbrains.compose.resources.getString
import org.meshtastic.core.analytics.platform.PlatformAnalytics
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.database.entity.MyNodeEntity
import org.meshtastic.core.database.entity.asDeviceVersion
import org.meshtastic.core.datastore.UiPreferencesDataSource
import org.meshtastic.core.model.MeshActivity
import org.meshtastic.core.model.MyNodeInfo
import org.meshtastic.core.model.RadioController
import org.meshtastic.core.model.TracerouteMapAvailability
import org.meshtastic.core.model.evaluateTracerouteMapAvailability
import org.meshtastic.core.model.service.TracerouteResponse
import org.meshtastic.core.model.util.dispatchMeshtasticUri
import org.meshtastic.core.repository.MeshServiceNotifications
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.repository.PacketRepository
import org.meshtastic.core.repository.RadioInterfaceService
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.client_notification
import org.meshtastic.core.resources.compromised_keys
import org.meshtastic.core.service.AndroidServiceRepository
import org.meshtastic.core.service.IMeshService
import org.meshtastic.core.service.MeshServiceNotifications
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.service.TracerouteResponse
import org.meshtastic.core.ui.component.ScrollToTopEvent
import org.meshtastic.core.ui.util.AlertManager
import org.meshtastic.core.ui.util.ComposableContent
Expand All @@ -75,7 +76,8 @@ class UIViewModel
@Inject
constructor(
private val nodeDB: NodeRepository,
private val serviceRepository: ServiceRepository,
private val serviceRepository: AndroidServiceRepository,
private val radioController: RadioController,
radioInterfaceService: RadioInterfaceService,
meshLogRepository: MeshLogRepository,
firmwareReleaseRepository: FirmwareReleaseRepository,
Expand Down Expand Up @@ -161,6 +163,10 @@ constructor(
val meshService: IMeshService?
get() = serviceRepository.meshService

fun setDeviceAddress(address: String) {
radioController.setDeviceAddress(address)
}

val unreadMessageCount =
packetRepository.getUnreadCountTotal().map { it.coerceAtLeast(0) }.stateInWhileSubscribed(initialValue = 0)

Expand All @@ -172,7 +178,7 @@ constructor(
}

// hardware info about our local device (can be null)
val myNodeInfo: StateFlow<MyNodeEntity?>
val myNodeInfo: StateFlow<MyNodeInfo?>
get() = nodeDB.myNodeInfo

init {
Expand Down
Loading
Loading