Skip to content

Commit 9599b93

Browse files
authored
Merge branch 'feature/gsdk_places' into develop
2 parents fe06621 + db987f1 commit 9599b93

File tree

13 files changed

+443
-27
lines changed

13 files changed

+443
-27
lines changed

app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/Journal3Application.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import com.hadisatrio.libs.kotlin.foundation.event.EventSource
3535
import com.hadisatrio.libs.kotlin.foundation.modal.Modal
3636
import com.hadisatrio.libs.kotlin.foundation.presentation.Presenter
3737
import com.hadisatrio.libs.kotlin.geography.Coordinates
38+
import com.hadisatrio.libs.kotlin.geography.Place
3839
import com.hadisatrio.libs.kotlin.geography.Places
3940
import com.hadisatrio.libs.kotlin.geography.Speed
4041
import com.hadisatrio.libs.kotlin.paraphrase.Paraphraser
@@ -46,6 +47,7 @@ abstract class Journal3Application : Application() {
4647
abstract val coordinates: Coordinates
4748
abstract val speed: Speed
4849
abstract val places: Places
50+
abstract val currentPlace: Place
4951
abstract val moments: EditableMoments
5052
abstract val notableMoments: Moments
5153
abstract val reflections: Stories

app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/RealJournal3Application.kt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ import com.hadisatrio.libs.android.foundation.os.SystemLog
5858
import com.hadisatrio.libs.android.foundation.presentation.ExecutorDispatchingPresenter
5959
import com.hadisatrio.libs.android.geography.AndroidCoordinates
6060
import com.hadisatrio.libs.android.geography.PermissionAwareCoordinates
61+
import com.hadisatrio.libs.android.geography.google.GoogleSdkCurrentPlace
62+
import com.hadisatrio.libs.android.geography.google.GoogleSdkNearbyPlaces
6163
import com.hadisatrio.libs.android.io.content.ContentResolverSources
6264
import com.hadisatrio.libs.kotlin.foundation.Decor
6365
import com.hadisatrio.libs.kotlin.foundation.UseCase
@@ -70,9 +72,9 @@ import com.hadisatrio.libs.kotlin.foundation.modal.Modal
7072
import com.hadisatrio.libs.kotlin.foundation.presentation.PerfTrackingPresenter
7173
import com.hadisatrio.libs.kotlin.foundation.presentation.Presenter
7274
import com.hadisatrio.libs.kotlin.geography.Coordinates
75+
import com.hadisatrio.libs.kotlin.geography.Place
7376
import com.hadisatrio.libs.kotlin.geography.Places
7477
import com.hadisatrio.libs.kotlin.geography.Speed
75-
import com.hadisatrio.libs.kotlin.geography.google.GoogleNearbyPlaces
7678
import com.hadisatrio.libs.kotlin.io.SchemeWiseSources
7779
import com.hadisatrio.libs.kotlin.io.filesystem.FileSystemSources
7880
import com.hadisatrio.libs.kotlin.paraphrase.OpenAiParaphraser
@@ -97,11 +99,19 @@ class RealJournal3Application : Journal3Application() {
9799
}
98100

99101
override val places: Places by lazy {
100-
GoogleNearbyPlaces(
102+
GoogleSdkNearbyPlaces(
101103
coordinates = coordinates,
102104
limit = 100,
103105
apiKey = BuildConfig.KEY_GOOGLE_API,
104-
httpClient = HttpClient()
106+
application = this
107+
)
108+
}
109+
110+
override val currentPlace: Place by lazy {
111+
GoogleSdkCurrentPlace(
112+
apiKey = BuildConfig.KEY_GOOGLE_API,
113+
application = this,
114+
clock = clock
105115
)
106116
}
107117

app-android-journal3/src/main/kotlin/com/hadisatrio/apps/android/journal3/moment/MomentCapturingWork.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class MomentCapturingWork(
3939
}
4040
CaptureAMomentUseCase(
4141
moments = journal3Application.moments,
42-
places = journal3Application.places,
42+
currentPlace = journal3Application.currentPlace,
4343
speed = journal3Application.speed,
4444
coordinates = journal3Application.coordinates,
4545
clock = journal3Application.clock

app-android-journal3/src/test/kotlin/com/hadisatrio/apps/android/journal3/FakeJournal3Application.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,12 @@ import com.hadisatrio.libs.kotlin.foundation.presentation.Presenter
4141
import com.hadisatrio.libs.kotlin.foundation.presentation.fake.FakePresenter
4242
import com.hadisatrio.libs.kotlin.geography.Coordinates
4343
import com.hadisatrio.libs.kotlin.geography.LiteralCoordinates
44+
import com.hadisatrio.libs.kotlin.geography.Place
4445
import com.hadisatrio.libs.kotlin.geography.Places
4546
import com.hadisatrio.libs.kotlin.geography.SelfPopulatingPlaces
4647
import com.hadisatrio.libs.kotlin.geography.Speed
4748
import com.hadisatrio.libs.kotlin.geography.StaticSpeed
49+
import com.hadisatrio.libs.kotlin.geography.fake.FakePlace
4850
import com.hadisatrio.libs.kotlin.geography.fake.FakePlaces
4951
import com.hadisatrio.libs.kotlin.paraphrase.DumbParaphraser
5052
import com.hadisatrio.libs.kotlin.paraphrase.Paraphraser
@@ -59,6 +61,7 @@ class FakeJournal3Application : Journal3Application() {
5961
override val coordinates: Coordinates by lazy { LiteralCoordinates("-6.275489,107.050648") }
6062
override val speed: Speed by lazy { StaticSpeed(5.0) }
6163
override val places: Places by lazy { SelfPopulatingPlaces(10, FakePlaces()) }
64+
override val currentPlace: Place by lazy { FakePlace() }
6265
override val moments: EditableMoments by lazy { SelfPopulatingMoments(10, FakeMoments()) }
6366
override val notableMoments: Moments by lazy { NotabilityFilteringMoments(true, moments) }
6467
override val reflections: Stories by lazy { FakeStories() }

app-kmm-journal3/src/commonMain/kotlin/com/hadisatrio/apps/kotlin/journal3/moment/CaptureAMomentUseCase.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ import com.hadisatrio.apps.kotlin.journal3.datetime.LiteralTimestamp
2121
import com.hadisatrio.apps.kotlin.journal3.moment.cache.CachingMoments
2222
import com.hadisatrio.libs.kotlin.foundation.UseCase
2323
import com.hadisatrio.libs.kotlin.geography.Coordinates
24-
import com.hadisatrio.libs.kotlin.geography.Places
24+
import com.hadisatrio.libs.kotlin.geography.Place
2525
import com.hadisatrio.libs.kotlin.geography.Speed
2626
import kotlinx.datetime.Clock
2727
import kotlin.time.Duration.Companion.hours
2828

2929
class CaptureAMomentUseCase(
3030
private val moments: EditableMoments,
31-
private val places: Places,
31+
private val currentPlace: Place,
3232
private val coordinates: Coordinates,
3333
private val speed: Speed,
3434
private val clock: Clock
@@ -45,7 +45,7 @@ class CaptureAMomentUseCase(
4545
val last24h = (currentTimestamp - 24.hours)..currentTimestamp
4646
val todaysMoments = TimeRangedMoments(last24h, cached)
4747

48-
var place = places.first()
48+
var place = currentPlace
4949
val vicinityMoments = VicinityMoments(place.coordinates, coordinates.accuracyInMeters, cached)
5050
place = vicinityMoments.firstOrNull()?.place ?: place
5151

app-kmm-journal3/src/commonTest/kotlin/com/hadisatrio/apps/kotlin/journal3/moment/CaptureAMomentUseCaseTest.kt

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@ package com.hadisatrio.apps.kotlin.journal3.moment
2020
import com.hadisatrio.apps.kotlin.journal3.moment.fake.FakeMoments
2121
import com.hadisatrio.libs.kotlin.geography.Coordinates
2222
import com.hadisatrio.libs.kotlin.geography.LiteralCoordinates
23-
import com.hadisatrio.libs.kotlin.geography.Place
2423
import com.hadisatrio.libs.kotlin.geography.Speed
2524
import com.hadisatrio.libs.kotlin.geography.fake.FakePlace
26-
import com.hadisatrio.libs.kotlin.geography.fake.FakePlaces
2725
import io.kotest.matchers.booleans.shouldBeFalse
2826
import io.kotest.matchers.collections.shouldBeEmpty
2927
import io.kotest.matchers.collections.shouldHaveSize
@@ -42,13 +40,12 @@ class CaptureAMomentUseCaseTest {
4240
private val moments = FakeMoments()
4341
private val gambirStation = FakePlace(coordinates = LiteralCoordinates("-6.1765638,106.8299464"))
4442
private val gambirBusDepot = FakePlace(coordinates = LiteralCoordinates("-6.1766638,106.8300464"))
45-
private val places = FakePlaces(gambirStation, gambirBusDepot)
4643
private val coordinates = mockk<Coordinates>()
4744
private val speed = mockk<Speed>()
4845
private val arbitraryInstant = Instant.fromEpochMilliseconds(1735316550)
4946
private val clock = mockk<Clock>()
5047

51-
private val useCase = CaptureAMomentUseCase(moments, places, coordinates, speed, clock)
48+
private val useCase = CaptureAMomentUseCase(moments, gambirStation, coordinates, speed, clock)
5249

5350
@BeforeTest
5451
fun `Init mocks`() {
@@ -65,19 +62,14 @@ class CaptureAMomentUseCaseTest {
6562
val captured = moments.first()
6663
captured.timestamp.value.shouldBeEqual(arbitraryInstant)
6764
captured.isNotable.shouldBeFalse()
68-
captured.place.id.shouldBeEqual(places.first().id)
65+
captured.place.id.shouldBeEqual(gambirStation.id)
6966
}
7067

7168
@Test
7269
fun `Prioritizes a known nearby place whilst capturing`() {
73-
val placesList = mutableListOf<Place>(gambirStation, gambirBusDepot)
74-
val places = FakePlaces(placesList)
75-
val useCase = CaptureAMomentUseCase(moments, places, coordinates, speed, clock)
76-
77-
useCase() // …captures visit to Gambir Station.
78-
placesList.removeFirst() // …so that the next execution would pick up the bus depot.
70+
CaptureAMomentUseCase(moments, gambirStation, coordinates, speed, clock)()
7971
every { clock.now() } returns arbitraryInstant + 1.days + 1.seconds
80-
useCase() // …attempts to capture the bus depot.
72+
CaptureAMomentUseCase(moments, gambirBusDepot, coordinates, speed, clock)()
8173

8274
moments.shouldHaveSize(2)
8375
moments.distinctBy { it.place.id }.shouldHaveSize(1)
@@ -95,14 +87,8 @@ class CaptureAMomentUseCaseTest {
9587

9688
@Test
9789
fun `Skips capturing if the place, post correction, is already written for today`() {
98-
val placesList = mutableListOf<Place>(gambirStation, gambirBusDepot)
99-
val places = FakePlaces(placesList)
100-
val useCase = CaptureAMomentUseCase(moments, places, coordinates, speed, clock)
101-
102-
useCase() // …captures visit to Gambir Station.
103-
placesList.removeFirst() // …so that the next execution would pick up the bus depot.
104-
// Unlike the previous test case, we don't advance the time.
105-
useCase() // …attempts to capture the bus depot.
90+
CaptureAMomentUseCase(moments, gambirStation, coordinates, speed, clock)()
91+
CaptureAMomentUseCase(moments, gambirBusDepot, coordinates, speed, clock)()
10692

10793
moments.shouldHaveSize(1)
10894
moments.distinctBy { it.place.id }.shouldHaveSize(1)

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ androidx-test-runner = { module = "androidx.test:runner", version = "1.6.2" }
1616

1717
gms-location = { module = "com.google.android.gms:play-services-location", version = "21.3.0" }
1818

19+
google-places = { module = "com.google.android.libraries.places:places", version = "3.5.0" }
20+
1921
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version = "0.6.1" }
2022
kotlinx-json-okio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version = "1.7.3" }
2123

lib-kmm-geography/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ kotlin {
3434
dependencies {
3535
implementation(libs.assent)
3636
implementation(libs.gms.location)
37+
implementation(libs.google.places)
3738
}
3839
}
3940
val androidUnitTest by getting {
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright (C) 2022 Hadi Satrio
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.hadisatrio.libs.android.geography.google
19+
20+
import android.Manifest.permission.ACCESS_COARSE_LOCATION
21+
import android.Manifest.permission.ACCESS_FINE_LOCATION
22+
import android.annotation.SuppressLint
23+
import android.app.Application
24+
import androidx.annotation.RequiresPermission
25+
import com.benasher44.uuid.Uuid
26+
import com.google.android.gms.tasks.Tasks
27+
import com.google.android.libraries.places.api.Places
28+
import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest
29+
import com.google.android.libraries.places.api.net.PlacesClient
30+
import com.hadisatrio.libs.kotlin.geography.Coordinates
31+
import com.hadisatrio.libs.kotlin.geography.Place
32+
import kotlinx.datetime.Clock
33+
import kotlinx.datetime.Instant
34+
import kotlin.time.Duration.Companion.seconds
35+
36+
@SuppressLint(
37+
"MissingPermission"
38+
/* We are expecting the client to handle permissions as expressed on the annotated
39+
* properties seen in this class. However, as the delegate (from which this requirement
40+
* is coming from) is being instantiated lazily, we could not specify the annotation
41+
* on the delegate itself. Hence the suppression. */
42+
)
43+
class GoogleSdkCurrentPlace(
44+
private val client: PlacesClient,
45+
private val clock: Clock
46+
) : Place {
47+
48+
override val id: Uuid
49+
@RequiresPermission(allOf = [ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION])
50+
get() = delegate().id
51+
52+
override val name: String
53+
@RequiresPermission(allOf = [ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION])
54+
get() = delegate().name
55+
56+
override val address: String
57+
@RequiresPermission(allOf = [ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION])
58+
get() = delegate().address
59+
60+
override val coordinates: Coordinates
61+
@RequiresPermission(allOf = [ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION])
62+
get() = delegate().coordinates
63+
64+
private var lastKnownPlace: Place? = null
65+
private var lastFetchInstant: Instant? = null
66+
67+
@Synchronized
68+
@RequiresPermission(allOf = [ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION])
69+
private fun delegate(): Place {
70+
val lastFetchInstant = this.lastFetchInstant
71+
val currentInstant = clock.now()
72+
val updateRequired = lastFetchInstant == null || currentInstant - lastFetchInstant > 10.seconds
73+
74+
if (updateRequired) {
75+
val request = FindCurrentPlaceRequest.builder(GoogleSdkPlaceFields).build()
76+
val response = Tasks.await(client.findCurrentPlace(request))
77+
val googlePlace = response.placeLikelihoods.maxBy { it.likelihood }.place
78+
this.lastKnownPlace = GoogleSdkPlace(googlePlace)
79+
this.lastFetchInstant = clock.now()
80+
}
81+
82+
return this.lastKnownPlace!!
83+
}
84+
85+
constructor(
86+
apiKey: String,
87+
application: Application,
88+
clock: Clock
89+
) : this(
90+
application.run {
91+
Places.initializeWithNewPlacesApiEnabled(application, apiKey)
92+
Places.createClient(application)
93+
},
94+
clock
95+
)
96+
}

0 commit comments

Comments
 (0)