Merge pull request #87 from yschimke/deeplinks

ISS Map and deeplinks
This commit is contained in:
John O'Reilly 2021-10-29 16:47:19 +01:00 committed by GitHub
commit 14f1ca190c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 162 additions and 21 deletions

View file

@ -41,6 +41,10 @@ android {
dependencies {
with(Deps.Android) {
implementation(osmdroidAndroid)
}
with(Deps.AndroidX) {
implementation(activityCompose)
}

View file

@ -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)

View file

@ -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>

View 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)
}
)
}
}
}

View file

@ -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()
}
}
}
}

View file

@ -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 = {})
}

View 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>