Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
1df6c3d
- Allowing Preview of Shared screens.
lukeduncan-scot Jul 17, 2026
041c941
- Implementing WakeButton to be used on Sleep Screen
lukeduncan-scot Jul 17, 2026
1a06382
- Implementing state of Shared Sleep screen.
lukeduncan-scot Jul 17, 2026
b26fda3
- Refactoring to use state to display.
lukeduncan-scot Jul 17, 2026
65ff0b0
- Moved AndroidLocationProvider to shared module to fulfill "expect" …
lukeduncan-scot Jul 17, 2026
920cbdb
- Added "start" function to abstract LocationProvider class to manual…
lukeduncan-scot Jul 17, 2026
21b15d5
- Removed redundant platform specific code as dependencies are provided.
lukeduncan-scot Jul 20, 2026
8371a81
- Added `onExit` back to composable
lukeduncan-scot Jul 20, 2026
6096eff
- Fixed button width on Snoozing -> Sleeping state
lukeduncan-scot Jul 20, 2026
2c4514b
- The Sleep Screen VM now handles the platform callbacks directly.
lukeduncan-scot Jul 20, 2026
4e47ba4
- Added some tests for SleepScreenViewModel
lukeduncan-scot Jul 20, 2026
21f219a
- Created "Accuracy" sealed class for LocationProvider to allow for l…
lukeduncan-scot Jul 22, 2026
7e3ecca
- Added minimum distance property for Accuracy sealed class to allow …
lukeduncan-scot Jul 22, 2026
38c9eb2
- Removed redundant constant
lukeduncan-scot Jul 22, 2026
fcf0c20
- Refactored "start()" function to use default parameter instead of h…
lukeduncan-scot Jul 22, 2026
bbcd659
- Fixed Preview
lukeduncan-scot Jul 22, 2026
4d6bef0
- Fixed failing test
lukeduncan-scot Jul 22, 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
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import android.os.Looper
import androidx.core.app.ActivityCompat
import org.scottishtecharmy.soundscape.geoengine.filters.KalmanLocationFilter
import org.scottishtecharmy.soundscape.geojsonparser.geojson.LngLatAlt
import kotlin.time.Duration.Companion.seconds

class AndroidLocationProvider(context: Context) : LocationProvider() {

Expand Down Expand Up @@ -88,30 +87,31 @@ class AndroidLocationProvider(context: Context) : LocationProvider() {
}

@SuppressLint("MissingPermission")
fun start(context: Context) {
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
LOCATION_UPDATES_INTERVAL_MS,
MIN_DISTANCE_METERS,
locationListener,
Looper.getMainLooper()
)
}
override fun start(accuracy: Accuracy) {
when (accuracy) {
Accuracy.High -> {
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
accuracy.updateInterval.inWholeMilliseconds,
accuracy.minimumDistanceM,
locationListener,
Looper.getMainLooper()
)
}
}

if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
LOCATION_UPDATES_INTERVAL_MS,
MIN_DISTANCE_METERS,
locationListener,
Looper.getMainLooper()
)
Accuracy.Balanced -> {
if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
accuracy.updateInterval.inWholeMilliseconds,
accuracy.minimumDistanceM,
locationListener,
Looper.getMainLooper()
)
}
}
}
}

companion object {
private val LOCATION_UPDATES_INTERVAL_MS = 1.seconds.inWholeMilliseconds
private const val MIN_DISTANCE_METERS = 1f
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.Priority
import org.scottishtecharmy.soundscape.geoengine.filters.KalmanLocationFilter
import org.scottishtecharmy.soundscape.geojsonparser.geojson.LngLatAlt
import kotlin.time.Duration.Companion.seconds

class GooglePlayLocationProvider(context: Context) :
LocationProvider() {
Expand Down Expand Up @@ -72,23 +71,22 @@ class GooglePlayLocationProvider(context: Context) :
}

@SuppressLint("MissingPermission")
fun start(context: Context) {
override fun start(accuracy: Accuracy) {

fusedLocationClient.requestLocationUpdates(
LocationRequest.Builder(
Priority.PRIORITY_HIGH_ACCURACY,
LOCATION_UPDATES_INTERVAL_MS
when (accuracy) {
Accuracy.Balanced -> Priority.PRIORITY_BALANCED_POWER_ACCURACY
Accuracy.High -> Priority.PRIORITY_HIGH_ACCURACY
},
accuracy.updateInterval.inWholeMilliseconds
).apply {
setMinUpdateDistanceMeters(1f)
setMinUpdateDistanceMeters(accuracy.minimumDistanceM)
setGranularity(Granularity.GRANULARITY_PERMISSION_LEVEL)
setWaitForAccurateLocation(true)
}.build(),
locationCallback,
Looper.getMainLooper(),
)
}

companion object {
private val LOCATION_UPDATES_INTERVAL_MS = 1.seconds.inWholeMilliseconds
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavHostController
Expand Down Expand Up @@ -222,6 +221,16 @@ fun HomeScreen(
activity.setServiceState(false)
},
onGetLanguageMismatch = { getLanguageMismatch() },
provideLocationProvider = {
if (org.scottishtecharmy.soundscape.hasPlayServices(context)) {
org.scottishtecharmy.soundscape.locationprovider.GooglePlayLocationProvider(
context
).apply { start() }
} else {
org.scottishtecharmy.soundscape.locationprovider.AndroidLocationProvider(context)
.apply { start() }
}
},
getOpenSourceLicensesJson = {
context.assets.open("open_source_licenses.json")
.bufferedReader()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,8 +407,8 @@ class SoundscapeService : MediaSessionService(), GeoEngineListener, MediaControl

private fun startProviders() {
when (val lp = locationProvider) {
is GooglePlayLocationProvider -> lp.start(this)
is AndroidLocationProvider -> lp.start(this)
is GooglePlayLocationProvider -> lp.start()
is AndroidLocationProvider -> lp.start()
is StaticLocationProvider -> lp.start()
}
when (val dp = directionProvider) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import org.scottishtecharmy.soundscape.screens.home.home.SharedLanguageMismatchD
import org.scottishtecharmy.soundscape.screens.home.home.SharedNewReleaseDialog
import org.scottishtecharmy.soundscape.screens.home.home.SharedOpenSourceLicensesScreen
import org.scottishtecharmy.soundscape.screens.home.home.SharedSleepScreen
import org.scottishtecharmy.soundscape.screens.home.home.SleepScreenState
import org.scottishtecharmy.soundscape.screens.home.home.StreetPreviewFunctions
import org.scottishtecharmy.soundscape.screens.home.locationDetails.SharedLocationDetailsScreen
import org.scottishtecharmy.soundscape.screens.home.locationDetails.SharedSaveAndEditMarkerScreen
Expand Down Expand Up @@ -416,7 +417,12 @@ fun HomeRoutePreview() {
@Preview(showBackground = true)
@Composable
fun SleepScreenPreview() {
SharedSleepScreen(onWakeUp = {}, onExit = {}, modifier = Modifier)
SharedSleepScreen(
modifier = Modifier,
onWakeUpNowClicked = {},
onWakeOnLeaveClicked = {},
state = SleepScreenState.Sleeping
)
}

@Preview(showBackground = true)
Expand Down
8 changes: 8 additions & 0 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.gradle.kotlin.dsl.implementation

plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.android.library)
Expand Down Expand Up @@ -58,8 +60,11 @@ kotlin {
implementation(libs.kotlinx.coroutines.test)
}
androidMain.dependencies {
implementation(project.dependencies.platform(libs.androidx.compose.bom))
implementation(libs.jts.core)
implementation(libs.ktor.client.okhttp)
implementation(libs.ui.tooling.preview)
implementation(libs.ui.tooling)
}
iosMain.dependencies {
implementation(libs.ktor.client.darwin)
Expand All @@ -77,6 +82,9 @@ android {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
buildFeatures {
compose = true
}
// Reuse shared resources for the Android target so JSON data files live
// in a single canonical location consumed by both platforms.
sourceSets["main"].resources.srcDir("src/commonMain/resources")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.scottishtecharmy.soundscape.screens.home.home

import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import org.scottishtecharmy.soundscape.geojsonparser.geojson.LngLatAlt

@Preview(showBackground = true)
@Composable
fun SharedSleepScreenPreview() {
SharedSleepScreen(
onWakeUpNowClicked = {},
onWakeOnLeaveClicked = { },
state = SleepScreenState.Sleeping,
)
}

@Preview(showBackground = true)
@Composable
fun SharedSleepScreenWakeOnLeaveEnabledPreview() {
SharedSleepScreen(
onWakeUpNowClicked = {},
onWakeOnLeaveClicked = {},
state = SleepScreenState.Snoozing(
userLocation = LngLatAlt(),
startLocation = LngLatAlt()
)
)
}

@Preview(showBackground = true)
@Composable
fun WakeButtonPreview() {
WakeButton(
text = "Wake On Leave",
onClick = { },
)
}

@Preview(showBackground = true)
@Composable
fun WakeButtonsWakeOnLeaveVisiblePreview() {
WakeButtons(
wakeUpNowOnClick = {},
wakeOnLeaveOnClick = {},
sleepScreenState = SleepScreenState.Sleeping,
)
}

@Preview(showBackground = true)
@Composable
fun WakeButtonsWakeOnLeaveNotVisiblePreview() {
WakeButtons(
wakeUpNowOnClick = {},
wakeOnLeaveOnClick = {},
sleepScreenState = SleepScreenState.Snoozing(
userLocation = LngLatAlt(),
startLocation = LngLatAlt()
),
)
}
6 changes: 6 additions & 0 deletions shared/src/commonMain/composeResources/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -336,12 +336,18 @@
<string name="sleep_sleep">Sleep</string>
<!-- App is currently Sleeping. Putting the app in an idle state for a period of time. -->
<string name="sleep_sleeping">Sleeping</string>
<!-- App is currently Snoozing. Putting the app in a state that waits for the user to manually exit the state or leave their current location. -->
<string name="sleep_snoozing">Snoozing</string>
<!-- Text for sleep page explaining what it does -->
<string name="sleep_sleeping_message">Soundscape is currently sleeping. This prevents Soundscape from using Location Services or downloading data. This conserves battery when you are not using Soundscape. Tap the button below to wake up Soundscape.</string>
<!-- Text for sleep page explaining what Wake On Leave does -->
<string name="sleep_sleeping_wake_on_leave_message">Soundscape is currently snoozing. When you leave your current location, Soundscape will automatically wake up. This uses Location Services in a low power mode to conserve your phone's battery. Tap the button below to wake up now.</string>
<!-- Talkback hint for the sleep button to indicate what it does. This is always preceded by "Double tap to " -->
<string name="sleep_sleep_acc_hint">put Soundscape to sleep</string>
<!-- Text for large button on sleep screen which wakes the app up -->
<string name="sleep_wake_up_now">Wake Up Now</string>
<!-- Text for large button on sleep screen which wakes the app on leaving geofence -->
<string name="sleep_wake_on_leave">Wake On Leave</string>
<!-- Callout that a road junction is getting near -->
<string name="intersection_approaching_intersection">Approaching intersection</string>
<!-- Notification, %s is a point of interest, "At Starbucks" -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.scottishtecharmy.soundscape.geojsonparser.geojson.Feature
import org.scottishtecharmy.soundscape.geojsonparser.geojson.LngLatAlt
import org.scottishtecharmy.soundscape.intents.IncomingIntent
import org.scottishtecharmy.soundscape.locationprovider.DeviceDirection
import org.scottishtecharmy.soundscape.locationprovider.LocationProvider
import org.scottishtecharmy.soundscape.locationprovider.SoundscapeLocation
import org.scottishtecharmy.soundscape.navigation.NavigationStateHolder
import org.scottishtecharmy.soundscape.navigation.SharedNavHost
Expand Down Expand Up @@ -98,6 +99,7 @@ data class AppCallbacks(
},
val onSetApplicationLocale: (String?) -> Unit = {},
val onGetLanguageMismatch: () -> Language? = { null },
val provideLocationProvider: (() -> LocationProvider)? = null,
val getOpenSourceLicensesJson: (() -> String)? = null,
/**
* Wipes preferences and immediately restarts the app. When non-null,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.scottishtecharmy.soundscape.geojsonparser.geojson

import org.scottishtecharmy.soundscape.locationprovider.SoundscapeLocation

open class LngLatAlt(
var longitude: Double = 0.toDouble(),
var latitude: Double = 0.toDouble(),
Expand Down Expand Up @@ -31,3 +33,10 @@ open class LngLatAlt(
return "$longitude,$latitude"
}
}

fun SoundscapeLocation.asLngLatAlt(): LngLatAlt {
return LngLatAlt(
longitude = longitude,
latitude = latitude,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,27 @@ package org.scottishtecharmy.soundscape.locationprovider

import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

sealed class Accuracy {
abstract val updateInterval: Duration
abstract val minimumDistanceM: Float

object High : Accuracy() {
override val updateInterval: Duration = 1.seconds
override val minimumDistanceM: Float = 1.0f

}

object Balanced : Accuracy() {
override val updateInterval: Duration = 30.seconds
override val minimumDistanceM: Float = 5.0f
}
}

abstract class LocationProvider {
abstract fun start(accuracy: Accuracy = Accuracy.High)
abstract fun destroy()
open fun updateLocation(newLocation: SoundscapeLocation) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class StaticLocationProvider(private var location: LngLatAlt) : LocationProvider

override fun destroy() {}

fun start() {
override fun start(accuracy: Accuracy) {
val loc = SoundscapeLocation(
latitude = location.latitude,
longitude = location.longitude,
Expand Down
Loading
Loading