diff --git a/wearApp/build.gradle.kts b/wearApp/build.gradle.kts
index 4668e66..05eb325 100644
--- a/wearApp/build.gradle.kts
+++ b/wearApp/build.gradle.kts
@@ -41,6 +41,10 @@ android {
dependencies {
+ with(Deps.Android) {
+ implementation(osmdroidAndroid)
+ }
+
with(Deps.AndroidX) {
implementation(activityCompose)
}
diff --git a/wearApp/src/androidTest/java/com/surrus/peopleinspace/wear/PeopleInSpaceTest.kt b/wearApp/src/androidTest/java/com/surrus/peopleinspace/wear/PeopleInSpaceTest.kt
index fde15eb..907d56a 100644
--- a/wearApp/src/androidTest/java/com/surrus/peopleinspace/wear/PeopleInSpaceTest.kt
+++ b/wearApp/src/androidTest/java/com/surrus/peopleinspace/wear/PeopleInSpaceTest.kt
@@ -1,6 +1,5 @@
package com.surrus.peopleinspace.wear
-import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.test.assertContentDescriptionEquals
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
@@ -8,10 +7,8 @@ import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onParent
import com.surrus.common.remote.Assignment
-import com.surrus.peopleinspace.LocalRotaryEventDispatcher
import com.surrus.peopleinspace.PersonListScreen
import com.surrus.peopleinspace.PersonListTag
-import com.surrus.peopleinspace.RotaryEventDispatcher
import org.junit.Rule
import org.junit.Test
@@ -35,7 +32,7 @@ class PeopleInSpaceTest {
@Test
fun testPeopleListScreenEmpty() {
composeTestRule.setContent {
- PersonListScreen(personSelected = {}, people = listOf())
+ PersonListScreen(personSelected = {}, issMapClick = {}, people = listOf())
}
composeTestRule.onNodeWithText("No people in space!")
@@ -45,7 +42,7 @@ class PeopleInSpaceTest {
@Test
fun testPeopleListScreen() {
composeTestRule.setContent {
- PersonListScreen(personSelected = {}, people = peopleList)
+ PersonListScreen(personSelected = {}, issMapClick = {}, people = peopleList)
}
val personListNode = composeTestRule.onNodeWithTag(PersonListTag)
diff --git a/wearApp/src/main/AndroidManifest.xml b/wearApp/src/main/AndroidManifest.xml
index 9db7585..c99c37e 100644
--- a/wearApp/src/main/AndroidManifest.xml
+++ b/wearApp/src/main/AndroidManifest.xml
@@ -4,6 +4,7 @@
+
@@ -32,6 +33,17 @@
+
+
+
+
+
+
+
+
+
diff --git a/wearApp/src/main/java/com/surrus/peopleinspace/IssMap.kt b/wearApp/src/main/java/com/surrus/peopleinspace/IssMap.kt
new file mode 100644
index 0000000..371e715
--- /dev/null
+++ b/wearApp/src/main/java/com/surrus/peopleinspace/IssMap.kt
@@ -0,0 +1,80 @@
+package com.surrus.peopleinspace
+
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsPropertyKey
+import androidx.compose.ui.semantics.SemanticsPropertyReceiver
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.wear.compose.material.MaterialTheme
+import androidx.wear.compose.material.Scaffold
+import com.surrus.common.remote.IssPosition
+import org.koin.androidx.compose.getViewModel
+import org.osmdroid.tileprovider.tilesource.TileSourceFactory
+import org.osmdroid.util.GeoPoint
+import org.osmdroid.views.CustomZoomButtonsController
+import org.osmdroid.views.MapView
+import org.osmdroid.views.overlay.Marker
+import java.io.File
+
+const val ISSPositionMapTag = "ISSPositionMap"
+
+val IssPositionKey = SemanticsPropertyKey("IssPosition")
+var SemanticsPropertyReceiver.observedIssPosition by IssPositionKey
+
+@Composable
+fun IssMap() {
+ val context = LocalContext.current
+
+ org.osmdroid.config.Configuration.getInstance().userAgentValue = BuildConfig.APPLICATION_ID
+ org.osmdroid.config.Configuration.getInstance().tileFileSystemCacheMaxBytes = 50L * 1024 * 1024
+ org.osmdroid.config.Configuration.getInstance().osmdroidTileCache =
+ File(context.cacheDir, "osmdroid").also { it.mkdir() }
+
+ val peopleInSpaceViewModel = getViewModel()
+
+ val issPosition by peopleInSpaceViewModel.issPosition
+ .collectAsState(IssPosition(0.0, 0.0))
+
+ val mapView = remember { MapView(context) }
+
+ MaterialTheme {
+ Scaffold {
+ AndroidView(
+ factory = {
+ mapView.apply {
+ setTileSource(TileSourceFactory.MAPNIK);
+ zoomController.setVisibility(CustomZoomButtonsController.Visibility.SHOW_AND_FADEOUT)
+ setMultiTouchControls(false)
+ controller.setZoom(3.0)
+ // disable movement
+ setClickable(false)
+ isFocusable = false
+ setOnTouchListener { v, event -> true }
+ }
+ },
+ modifier = Modifier
+ .fillMaxHeight()
+ .testTag(ISSPositionMapTag)
+ .semantics { observedIssPosition = issPosition },
+ update = { map ->
+ println(issPosition)
+ val issPositionPoint = GeoPoint(issPosition.latitude, issPosition.longitude)
+ map.controller.setCenter(issPositionPoint)
+
+ map.overlays.clear()
+ val stationMarker = Marker(map)
+ stationMarker.position = issPositionPoint
+ stationMarker.title = "ISS"
+ map.overlays.add(stationMarker)
+ }
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/wearApp/src/main/java/com/surrus/peopleinspace/MainActivity.kt b/wearApp/src/main/java/com/surrus/peopleinspace/MainActivity.kt
index 2be05ce..897cc03 100644
--- a/wearApp/src/main/java/com/surrus/peopleinspace/MainActivity.kt
+++ b/wearApp/src/main/java/com/surrus/peopleinspace/MainActivity.kt
@@ -5,6 +5,10 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.runtime.CompositionLocalProvider
+import androidx.navigation.NavDeepLink
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import androidx.navigation.navDeepLink
import androidx.wear.compose.material.ExperimentalWearMaterialApi
import androidx.wear.compose.navigation.SwipeDismissableNavHost
import androidx.wear.compose.navigation.composable
@@ -16,9 +20,11 @@ import org.koin.android.ext.android.inject
sealed class Screen(val route: String) {
object PersonList : Screen("personList")
object PersonDetails : Screen("personDetails")
+ object IssMap : Screen("issMap")
}
const val PERSON_NAME_NAV_ARGUMENT = "personName"
+const val DEEPLINK_URI = "peopleinspace://peopleinspace.dev/"
class MainActivity : ComponentActivity() {
private val imageLoader: ImageLoader by inject()
@@ -43,20 +49,39 @@ class MainActivity : ComponentActivity() {
startDestination = Screen.PersonList.route
) {
- composable(Screen.PersonList.route) {
- PersonListScreen(personSelected = {
- navController.navigate(Screen.PersonDetails.route + "/${it.name}")
- })
+ composable(
+ route = Screen.PersonList.route,
+ deepLinks = listOf(NavDeepLink(DEEPLINK_URI + "personList"))
+ ) {
+ PersonListScreen(
+ personSelected = {
+ navController.navigate(Screen.PersonDetails.route + "/${it.name}")
+ },
+ issMapClick = {
+ navController.navigate(Screen.IssMap.route)
+ })
}
composable(
- route = Screen.PersonDetails.route + "/{$PERSON_NAME_NAV_ARGUMENT}"
+ route = Screen.PersonDetails.route + "/{$PERSON_NAME_NAV_ARGUMENT}",
+ arguments = listOf(
+ navArgument(PERSON_NAME_NAV_ARGUMENT, builder = {
+ this.type = NavType.StringType
+ })),
+ deepLinks = listOf(navDeepLink { uriPattern = DEEPLINK_URI + "personList/{${PERSON_NAME_NAV_ARGUMENT}}" })
) { navBackStackEntry ->
val personName = navBackStackEntry.arguments?.getString(PERSON_NAME_NAV_ARGUMENT)
personName?.let {
PersonDetailsScreen(personName)
}
}
+
+ composable(
+ route = Screen.IssMap.route,
+ deepLinks = listOf(NavDeepLink(DEEPLINK_URI + "issMap"))
+ ) {
+ IssMap()
+ }
}
}
}
diff --git a/wearApp/src/main/java/com/surrus/peopleinspace/PersonListScreen.kt b/wearApp/src/main/java/com/surrus/peopleinspace/PersonListScreen.kt
index 8693803..1005a2a 100644
--- a/wearApp/src/main/java/com/surrus/peopleinspace/PersonListScreen.kt
+++ b/wearApp/src/main/java/com/surrus/peopleinspace/PersonListScreen.kt
@@ -5,6 +5,7 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.slideInVertically
import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -13,20 +14,25 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material.Button
+import androidx.wear.compose.material.ButtonDefaults
import androidx.wear.compose.material.Card
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.ScalingLazyColumn
@@ -45,12 +51,13 @@ const val NoPeopleTag = "NoPeople"
@Composable
fun PersonListScreen(
personSelected: (person: Assignment) -> Unit,
+ issMapClick: () -> Unit,
peopleInSpaceViewModel: PeopleInSpaceViewModel = getViewModel()
) {
val peopleState by peopleInSpaceViewModel.peopleInSpace.collectAsState()
val scrollState = rememberScalingLazyListState()
RotaryEventState(scrollState)
- PersonListScreen(peopleState, personSelected, scrollState)
+ PersonListScreen(peopleState, personSelected, issMapClick, scrollState)
}
@OptIn(ExperimentalAnimationApi::class)
@@ -58,6 +65,7 @@ fun PersonListScreen(
fun PersonListScreen(
people: List?,
personSelected: (person: Assignment) -> Unit,
+ issMapClick: () -> Unit,
scrollState: ScalingLazyListState = rememberScalingLazyListState(),
) {
MaterialTheme {
@@ -67,7 +75,7 @@ fun PersonListScreen(
) {
if (people != null) {
if (people.isNotEmpty()) {
- PersonList(people, personSelected, scrollState)
+ PersonList(people, personSelected, issMapClick, scrollState)
} else {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Card(
@@ -87,19 +95,29 @@ fun PersonListScreen(
fun PersonList(
people: List,
personSelected: (person: Assignment) -> Unit,
+ issMapClick: () -> Unit,
scrollState: ScalingLazyListState = rememberScalingLazyListState(),
) {
- val paddingHeight = if (LocalConfiguration.current.isScreenRound) 50.dp else 8.dp
ScalingLazyColumn(
- contentPadding = PaddingValues(
- start = 8.dp,
- end = 8.dp,
- top = paddingHeight,
- bottom = paddingHeight
- ),
+ contentPadding = PaddingValues(8.dp),
modifier = Modifier.testTag(PersonListTag),
state = scrollState,
) {
+ item {
+ Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
+ Button(
+ modifier = Modifier.size(ButtonDefaults.SmallButtonSize).wrapContentSize(),
+ onClick = issMapClick
+ ) {
+ // https://www.svgrepo.com/svg/170716/international-space-station
+ Image(
+ modifier = Modifier.scale(0.5f),
+ painter = painterResource(id = R.drawable.ic_iss),
+ contentDescription = "ISS Map"
+ )
+ }
+ }
+ }
items(people.size) { offset ->
PersonView(people[offset], personSelected)
}
@@ -197,7 +215,7 @@ fun PersonListSquarePreview() {
"Buzz Aldrin",
"https://nypost.com/wp-content/uploads/sites/2/2018/06/buzz-aldrin.jpg?quality=80&strip=all"
)
- ), personSelected = {})
+ ), personSelected = {}, issMapClick = {})
}
@Preview(
@@ -210,5 +228,5 @@ fun PersonListSquarePreview() {
)
@Composable
fun PersonListSquareEmptyPreview() {
- PersonListScreen(people = listOf(), personSelected = {})
+ PersonListScreen(people = listOf(), personSelected = {}, issMapClick = {})
}
diff --git a/wearApp/src/main/res/drawable/ic_iss.xml b/wearApp/src/main/res/drawable/ic_iss.xml
new file mode 100644
index 0000000..cdd0d01
--- /dev/null
+++ b/wearApp/src/main/res/drawable/ic_iss.xml
@@ -0,0 +1,5 @@
+
+
+
+