commit
14f1ca190c
7 changed files with 162 additions and 21 deletions
|
@ -41,6 +41,10 @@ android {
|
|||
|
||||
dependencies {
|
||||
|
||||
with(Deps.Android) {
|
||||
implementation(osmdroidAndroid)
|
||||
}
|
||||
|
||||
with(Deps.AndroidX) {
|
||||
implementation(activityCompose)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<uses-feature android:name="android.hardware.type.watch" />
|
||||
|
||||
|
@ -32,6 +33,17 @@
|
|||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="peopleinspace.dev"
|
||||
android:scheme="peopleinspace" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
|
|
80
wearApp/src/main/java/com/surrus/peopleinspace/IssMap.kt
Normal file
80
wearApp/src/main/java/com/surrus/peopleinspace/IssMap.kt
Normal file
|
@ -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>("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<PeopleInSpaceViewModel>()
|
||||
|
||||
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)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Assignment>?,
|
||||
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<Assignment>,
|
||||
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 = {})
|
||||
}
|
||||
|
|
5
wearApp/src/main/res/drawable/ic_iss.xml
Normal file
5
wearApp/src/main/res/drawable/ic_iss.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="64dp" android:viewportHeight="401.294"
|
||||
android:viewportWidth="401.294" android:width="64dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M115.342,188.015c-7.025,0 -12.741,5.716 -12.741,12.741s5.716,12.741 12.741,12.741c7.026,0 12.742,-5.716 12.742,-12.741S122.368,188.015 115.342,188.015zM115.342,205.497c-2.614,0 -4.741,-2.127 -4.741,-4.741s2.127,-4.741 4.741,-4.741c2.615,0 4.742,2.127 4.742,4.741S117.957,205.497 115.342,205.497z"/>
|
||||
<path android:fillColor="#FF000000" android:pathData="M397.294,164.141c2.209,0 4,-1.791 4,-4V89.215c0,-2.209 -1.791,-4 -4,-4h-33.219c-2.209,0 -4,1.791 -4,4v70.926c0,2.209 1.791,4 4,4h12.61v8.999h-36.205c-6.008,-4.585 -13.401,-7.094 -20.986,-7.094c-17.762,0 -32.434,13.455 -34.376,30.706h-3.421v-9.79c0,-2.209 -1.791,-4 -4,-4h-28.993l-6.173,-11.125c-0.705,-1.271 -2.044,-2.059 -3.498,-2.059h-39.513v-6.524h16.196c2.209,0 4,-1.791 4,-4V73.281c0,-2.209 -1.791,-4 -4,-4h-40.266c-2.209,0 -4,1.791 -4,4v85.972c0,2.209 1.791,4 4,4h16.069v6.524h-38.468c-2.209,0 -4,1.791 -4,4v22.974h-4.52c-1.97,-15.047 -14.865,-26.705 -30.44,-26.705c-10.313,0 -19.703,5.125 -25.326,13.331c-5.623,-8.206 -15.012,-13.331 -25.326,-13.331H44.653v-5.905h6.324c2.209,0 4,-1.791 4,-4V89.215c0,-2.209 -1.791,-4 -4,-4H17.758c-2.209,0 -4,1.791 -4,4v70.926c0,2.209 1.791,4 4,4h6.325v5.905H4c-2.209,0 -4,1.791 -4,4v53.419c0,2.209 1.791,4 4,4h20.083v5.905h-6.325c-2.209,0 -4,1.791 -4,4v70.927c0,2.209 1.791,4 4,4h33.219c2.209,0 4,-1.791 4,-4v-70.927c0,-2.209 -1.791,-4 -4,-4h-6.325v-5.905h18.789c10.313,0 19.703,-5.125 25.326,-13.331c5.624,8.205 15.013,13.331 25.326,13.331c15.578,0 28.476,-11.663 30.441,-26.714h4.518v22.765c0,2.209 1.791,4 4,4h38.468v6.524h-16.069c-2.209,0 -4,1.791 -4,4v85.972c0,2.209 1.791,4 4,4h40.266c2.209,0 4,-1.791 4,-4V242.04c0,-2.209 -1.791,-4 -4,-4h-16.196v-6.524h39.513c1.454,0 2.792,-0.789 3.498,-2.059l6.173,-11.125h28.993c2.209,0 4,-1.791 4,-4v-9.581h3.447c2.037,17.151 16.66,30.497 34.35,30.497c7.581,0 14.972,-2.507 20.978,-7.087h36.214v9.209h-12.61c-2.209,0 -4,1.791 -4,4v70.927c0,2.209 1.791,4 4,4h33.219c2.209,0 4,-1.791 4,-4v-70.927c0,-2.209 -1.791,-4 -4,-4h-12.609v-9.209h12.435c2.209,0 4,-1.791 4,-4V177.14c0,-2.209 -1.791,-4 -4,-4h-12.435v-8.999H397.294zM199.521,155.253v-13.221h12.196v13.221H199.521zM191.521,112.267h-12.069V98.502h12.069V112.267zM199.521,98.502h12.196v13.765h-12.196V98.502zM191.521,120.267v13.765h-12.069v-13.765H191.521zM199.521,120.267h12.196v13.765h-12.196V120.267zM211.717,90.502h-12.196V77.281h12.196V90.502zM191.521,77.281v13.221h-12.069V77.281H191.521zM179.451,155.253v-13.221h12.069v13.221H179.451zM24.689,223.466v-45.419h22.038v45.419H24.689zM54.727,178.046h6.641v45.419h-6.641V178.046zM46.977,128.678v9.956H21.758v-9.956H46.977zM21.758,120.678v-9.957h25.219v9.957H21.758zM46.977,93.215v9.507H21.758v-9.507H46.977zM21.758,146.634h25.219v9.507H21.758V146.634zM32.083,164.141h4.569v5.905h-4.569V164.141zM8,178.046h8.689v45.419H8V178.046zM21.758,272.834v-9.956h25.219v9.956H21.758zM46.977,280.834v9.956H21.758v-9.956H46.977zM21.758,308.298v-9.507h25.219v9.507H21.758zM46.977,254.878H21.758v-9.507h25.219V254.878zM36.652,237.371h-4.569v-5.905h4.569V237.371zM69.368,222.667v-43.822c6.952,1.876 12.703,6.983 15.31,13.918v15.987C82.071,215.684 76.32,220.791 69.368,222.667zM114.093,223.466c-9.664,0 -18.222,-6.091 -21.415,-15.184v-15.05c3.193,-9.094 11.751,-15.185 21.415,-15.185c12.522,0 22.709,10.188 22.709,22.71C136.802,213.278 126.615,223.466 114.093,223.466zM191.521,246.04v13.221h-12.069V246.04H191.521zM199.521,289.027h12.196v13.765h-12.196V289.027zM191.521,302.792h-12.069v-13.765h12.069V302.792zM199.521,281.027v-13.765h12.196v13.765H199.521zM191.521,281.027h-12.069v-13.765h12.069V281.027zM179.451,310.792h12.069v13.221h-12.069V310.792zM199.521,324.012v-13.221h12.196v13.221H199.521zM211.717,246.04v13.221h-12.196V246.04H211.717zM157.052,177.778h11.046v45.739h-11.046V177.778zM236.678,223.516h-60.58v-45.739h60.58l5.311,9.571v26.596L236.678,223.516zM273.696,210.332h-23.707v-19.371h23.707V210.332zM368.075,272.834v-9.956h25.219v9.956H368.075zM393.294,280.834v9.956h-25.219v-9.956H393.294zM368.075,308.298v-9.507h25.219v9.507H368.075zM393.294,254.878h-25.219v-9.507h25.219V254.878zM292.892,200.647c0,-8.817 4.315,-16.641 10.94,-21.484v42.968C297.207,217.288 292.892,209.463 292.892,200.647zM319.493,227.248c-2.663,0 -5.234,-0.398 -7.661,-1.129v-50.944c2.428,-0.731 4.998,-1.129 7.661,-1.129c6.217,0 12.267,2.193 17.036,6.175c0.248,0.208 0.521,0.368 0.803,0.506v39.849c-0.29,0.141 -0.57,0.304 -0.821,0.513C331.747,225.061 325.703,227.248 319.493,227.248zM393.119,220.161h-47.787V181.14h47.787V220.161zM393.294,128.678v9.956h-25.219v-9.956H393.294zM368.075,120.678v-9.957h25.219v9.957H368.075zM393.294,93.215v9.507h-25.219v-9.507H393.294zM368.075,146.634h25.219v9.507h-25.219V146.634z"/>
|
||||
</vector>
|
Loading…
Reference in a new issue