Skip to content

Commit dae7609

Browse files
authored
Merge pull request #470 from yschimke/orbits
Add animated ISS orbit to Remote Compose widget
2 parents 2e2fbdd + efc9824 commit dae7609

18 files changed

Lines changed: 588 additions & 154 deletions

File tree

app/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ dependencies {
9191
implementation(libs.androidx.remote.player.compose)
9292
implementation(libs.androidx.remote.tooling.preview)
9393
implementation(libs.androidx.wear.remote.material3)
94+
implementation(libs.androidx.preference.ktx)
9495

9596
implementation(libs.koin.core)
9697
implementation(libs.koin.android)
@@ -101,6 +102,7 @@ dependencies {
101102
androidTestImplementation(libs.androidx.compose.ui.test)
102103
androidTestImplementation(libs.androidx.compose.ui.test.junit)
103104
androidTestImplementation(libs.androidx.navigation.compose.testing)
105+
androidTestImplementation(libs.androidx.truth)
104106
debugImplementation(libs.androidx.compose.ui.test.manifest)
105107

106108

app/src/androidTest/java/dev/johnoreilly/peopleinspace/peopleinspace/PeopleInSpaceRepositoryFake.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package dev.johnoreilly.peopleinspace
22

33
import dev.johnoreilly.common.remote.Assignment
44
import dev.johnoreilly.common.remote.IssPosition
5+
import dev.johnoreilly.common.remote.OrbitPoint
56
import dev.johnoreilly.common.repository.PeopleInSpaceRepositoryInterface
67
import kotlinx.coroutines.flow.Flow
78
import kotlinx.coroutines.flow.flowOf
@@ -20,6 +21,10 @@ class PeopleInSpaceRepositoryFake: PeopleInSpaceRepositoryInterface {
2021
return flowOf(issPosition)
2122
}
2223

24+
override suspend fun fetchISSFuturePosition(): List<OrbitPoint> {
25+
return listOf(OrbitPoint(System.currentTimeMillis() / 1000, issPosition.latitude, issPosition.longitude))
26+
}
27+
2328
override suspend fun fetchAndStorePeople() {
2429
}
2530
}

app/src/debug/AndroidManifest.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<application>
5+
<activity
6+
android:name=".peopleinspace.remotecompose.RemoteComposeTestActivity"
7+
android:exported="true"
8+
android:label="@string/app_name">
9+
</activity>
10+
</application>
11+
12+
</manifest>
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
@file:SuppressLint("RestrictedApi")
2+
3+
package dev.johnoreilly.peopleinspace.peopleinspace.remotecompose
4+
5+
import android.annotation.SuppressLint
6+
import android.os.Bundle
7+
import androidx.activity.ComponentActivity
8+
import androidx.activity.compose.setContent
9+
import androidx.activity.enableEdgeToEdge
10+
import androidx.compose.foundation.layout.Arrangement
11+
import androidx.compose.foundation.layout.Box
12+
import androidx.compose.foundation.layout.Column
13+
import androidx.compose.foundation.layout.fillMaxSize
14+
import androidx.compose.foundation.layout.size
15+
import androidx.compose.material3.CircularProgressIndicator
16+
import androidx.compose.remote.tooling.preview.RemotePreview
17+
import androidx.compose.runtime.Composable
18+
import androidx.compose.runtime.LaunchedEffect
19+
import androidx.compose.runtime.getValue
20+
import androidx.compose.runtime.mutableStateOf
21+
import androidx.compose.runtime.remember
22+
import androidx.compose.runtime.setValue
23+
import androidx.compose.ui.Alignment
24+
import androidx.compose.ui.Modifier
25+
import androidx.compose.ui.platform.LocalDensity
26+
import androidx.compose.ui.platform.LocalWindowInfo
27+
import androidx.compose.ui.unit.DpSize
28+
import androidx.compose.ui.unit.dp
29+
import dev.johnoreilly.common.repository.PeopleInSpaceRepositoryInterface
30+
import dev.johnoreilly.peopleinspace.peopleinspace.remotecompose.util.MapResult
31+
import dev.johnoreilly.peopleinspace.peopleinspace.remotecompose.util.fetchMapBitmapInRange
32+
import dev.johnoreilly.peopleinspace.peopleinspace.remotecompose.util.filterRecent
33+
import org.koin.core.component.KoinComponent
34+
import org.koin.core.component.inject
35+
36+
class RemoteComposeTestActivity : ComponentActivity(), KoinComponent {
37+
private val repository: PeopleInSpaceRepositoryInterface by inject()
38+
39+
override fun onCreate(savedInstanceState: Bundle?) {
40+
super.onCreate(savedInstanceState)
41+
enableEdgeToEdge()
42+
43+
setContent { PeopleInSpaceDebugView() }
44+
}
45+
46+
@Composable
47+
fun PeopleInSpaceDebugView() {
48+
var mapResult by remember { mutableStateOf<MapResult?>(null) }
49+
50+
val windowInfo = LocalWindowInfo.current
51+
val sizeDp = DpSize(windowInfo.containerDpSize.width, 300.dp)
52+
val sizePx = with(LocalDensity.current) { sizeDp.toSize() }
53+
54+
LaunchedEffect(Unit) {
55+
mapResult = fetchMapBitmapInRange(
56+
filterRecent(repository.fetchISSFuturePosition()),
57+
this@RemoteComposeTestActivity,
58+
size = sizePx,
59+
)
60+
}
61+
Column(
62+
modifier = Modifier.fillMaxSize(),
63+
horizontalAlignment = Alignment.CenterHorizontally,
64+
verticalArrangement = Arrangement.Center
65+
) {
66+
if (mapResult != null) {
67+
Box(
68+
modifier = Modifier.size(sizeDp)
69+
) {
70+
ShowRCPreview(mapResult!!)
71+
}
72+
} else {
73+
CircularProgressIndicator()
74+
}
75+
}
76+
}
77+
78+
@Composable
79+
private fun ShowRCPreview(mapResult: MapResult) {
80+
RemotePreview { PeopleInSpaceCard(mapResult) }
81+
}
82+
}

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656

5757
<receiver
5858
android:name=".peopleinspace.remotecompose.PeopleInSpaceWidgetReceiver"
59-
android:label="ISS Map"
59+
android:label="ISS Map (RC)"
6060
android:enabled="@bool/remotecompose_appwidget_available"
6161
android:exported="false"
6262
tools:targetApi="36">
Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
package dev.johnoreilly.peopleinspace.peopleinspace.glance
22

33
import android.content.Context
4+
import android.content.Context.MODE_PRIVATE
45
import android.graphics.Bitmap
56
import androidx.compose.ui.graphics.ImageBitmap
67
import androidx.compose.ui.graphics.asImageBitmap
8+
import androidx.core.content.ContextCompat.getDrawable
79
import dev.johnoreilly.common.remote.IssPosition
810
import dev.johnoreilly.common.repository.PeopleInSpaceRepositoryInterface
911
import dev.johnoreilly.peopleinspace.R
1012
import kotlinx.coroutines.Dispatchers
1113
import kotlinx.coroutines.flow.first
1214
import kotlinx.coroutines.launch
1315
import kotlinx.coroutines.withContext
16+
import org.osmdroid.config.Configuration
1417
import org.osmdroid.tileprovider.MapTileProviderBasic
1518
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
1619
import org.osmdroid.util.GeoPoint
@@ -25,7 +28,6 @@ suspend fun fetchIssPosition(repository: PeopleInSpaceRepositoryInterface): GeoP
2528
val issPosition: IssPosition = repository.pollISSPosition().first()
2629

2730
val issPositionPoint = GeoPoint(issPosition.latitude, issPosition.longitude)
28-
println("ISS Position: $issPositionPoint")
2931
return issPositionPoint
3032
}
3133

@@ -39,31 +41,41 @@ suspend fun fetchMapBitmap(
3941
): ImageBitmap {
4042
val stationMarker = IconOverlay(
4143
issPositionPoint,
42-
context.resources.getDrawable(R.drawable.ic_iss, context.theme)
44+
getDrawable(context, R.drawable.ic_iss)
4345
)
4446

4547
val source = TileSourceFactory.DEFAULT_TILE_SOURCE
4648
val projection = Projection(zoomLevel, pWidth, pHeight, issPositionPoint, 0f, true, false, 0, 0)
4749

48-
val bitmap = withContext(Dispatchers.Main) {
49-
suspendCoroutine<Bitmap> { cont ->
50-
val mapSnapshot = MapSnapshot(
51-
{
52-
if (it.status == MapSnapshot.Status.CANVAS_OK) {
53-
val bitmap = Bitmap.createBitmap(it.bitmap)
54-
cont.resume(bitmap)
55-
}
56-
},
57-
MapSnapshot.INCLUDE_FLAG_UPTODATE or MapSnapshot.INCLUDE_FLAG_SCALED,
58-
MapTileProviderBasic(context, source, null),
59-
if (includeStationMarker) listOf(stationMarker) else listOf(),
60-
projection
61-
)
50+
Configuration.getInstance().load(
51+
context.applicationContext,
52+
context.getSharedPreferences("osmdroid", MODE_PRIVATE)
53+
)
54+
55+
val mapTileProvider = MapTileProviderBasic(context, source, null)
56+
try {
57+
val bitmap = withContext(Dispatchers.Main) {
58+
suspendCoroutine { cont ->
59+
val mapSnapshot = MapSnapshot(
60+
{
61+
if (it.status == MapSnapshot.Status.CANVAS_OK) {
62+
val bitmap = Bitmap.createBitmap(it.bitmap)
63+
cont.resume(bitmap)
64+
}
65+
},
66+
MapSnapshot.INCLUDE_FLAG_UPTODATE or MapSnapshot.INCLUDE_FLAG_SCALED,
67+
mapTileProvider,
68+
if (includeStationMarker) listOf(stationMarker) else listOf(),
69+
projection
70+
)
6271

63-
launch(Dispatchers.IO) {
64-
mapSnapshot.run()
72+
launch(Dispatchers.IO) {
73+
mapSnapshot.run()
74+
}
6575
}
6676
}
77+
return bitmap.asImageBitmap()
78+
} finally {
79+
mapTileProvider.detach()
6780
}
68-
return bitmap.asImageBitmap()
6981
}

0 commit comments

Comments
 (0)