diff --git a/.gitignore b/.gitignore
index f2bb714a..0a06454c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,7 +22,7 @@
# Needed for configuration only
*.iml
# Comment if you like setup things
-/.idea
+.idea
# Customize more directories if needed
#/.idea/caches
#/.idea/libraries
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 525587b4..9d5a94f5 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -37,14 +37,14 @@ plugins {
val localProperties = loadProperties("$projectDir/../local.properties")
android {
- compileSdk = 35
+ compileSdk = 36
namespace = "illyan.jay"
defaultConfig {
applicationId = "illyan.jay"
minSdk = 23
- targetSdk = 35
- versionCode = 19
- versionName = "0.4.1-alpha"
+ targetSdk = 36
+ versionCode = 20
+ versionName = "0.5.0-alpha"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
@@ -101,13 +101,13 @@ android {
}
compileOptions {
- sourceCompatibility = JavaVersion.VERSION_17
- targetCompatibility = JavaVersion.VERSION_17
+ sourceCompatibility = JavaVersion.VERSION_21
+ targetCompatibility = JavaVersion.VERSION_21
isCoreLibraryDesugaringEnabled = true
}
kotlin {
- jvmToolchain(17)
+ jvmToolchain(21)
}
buildFeatures {
@@ -137,17 +137,18 @@ dependencies {
// Core
implementation(libs.jetbrains.kotlin.stdlib)
implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.splash)
implementation(libs.androidx.collection.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.activity.ktx)
implementation(libs.google.material)
+ implementation(libs.google.material.icons)
+ implementation(libs.jetbrains.kotlinx.datetime)
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar", "*.jar"))))
// Compose
- implementation(libs.androidx.constraintlayout.compose)
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.ui)
- implementation(libs.androidx.compose.material)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.ui.util)
@@ -169,10 +170,6 @@ dependencies {
// Biometric Auth
//implementation "androidx.biometric:biometric:1.2.0-alpha05"
- // Material design icons
- implementation(libs.androidx.compose.material.icons.core)
- implementation(libs.androidx.compose.material.icons.extended)
-
// Day-Night Cycle in Theming
implementation(libs.solarized)
@@ -184,13 +181,13 @@ dependencies {
// Mapbox
implementation(libs.mapbox.maps)
+ implementation(libs.mapbox.maps.compose)
implementation(libs.mapbox.search)
implementation(libs.mapbox.navigation)
// Accompanist
- implementation(libs.accompanist.systemuicontroller)
implementation(libs.accompanist.permissions)
- implementation(libs.accompanist.placeholder.material)
+ implementation(libs.placeholder)
// Hilt
implementation(libs.hilt)
@@ -201,7 +198,8 @@ dependencies {
implementation(libs.timber)
// Navigation
- implementation(libs.compose.destinations.animations.core)
+ implementation(libs.compose.destinations.core)
+ implementation(libs.compose.destinations.bottom.sheet)
ksp(libs.compose.destinations.ksp)
// Coil
@@ -234,6 +232,11 @@ dependencies {
implementation(libs.google.maps.utils)
implementation(libs.google.maps.utils.ktx)
+ // Credential Manager + Google ID
+ implementation(libs.androidx.credentials)
+ implementation(libs.androidx.credentials.play.services.auth)
+ implementation(libs.google.identity.googleid)
+
// Firebase
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.auth)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5db5cba0..d2e3344d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -37,21 +37,16 @@
android:name=".MainApplication"
android:allowBackup="false"
android:enableOnBackInvokedCallback="true"
- android:icon="@mipmap/ic_launcher"
+ android:icon="@mipmap/ic_launcher_foreground"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.Jay"
android:usesCleartextTraffic="false">
-
-
-
+
diff --git a/app/src/main/java/illyan/jay/MainActivity.kt b/app/src/main/java/illyan/jay/MainActivity.kt
index caa3ae27..80a7433e 100644
--- a/app/src/main/java/illyan/jay/MainActivity.kt
+++ b/app/src/main/java/illyan/jay/MainActivity.kt
@@ -18,11 +18,8 @@
package illyan.jay
-import android.content.Intent
import android.os.Bundle
import androidx.activity.compose.setContent
-import androidx.activity.result.ActivityResultLauncher
-import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
@@ -34,18 +31,17 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
-import com.google.android.gms.auth.api.signin.GoogleSignIn
-import com.google.android.gms.auth.api.signin.GoogleSignInAccount
-import com.google.android.gms.tasks.Task
import com.mapbox.navigation.base.options.NavigationOptions
import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
import com.ramcosta.composedestinations.DestinationsNavHost
+import com.ramcosta.composedestinations.generated.NavGraphs
import dagger.hilt.android.AndroidEntryPoint
import illyan.jay.domain.interactor.AuthInteractor
-import illyan.jay.ui.NavGraphs
import illyan.jay.ui.components.PreviewAccessibility
+import illyan.jay.ui.theme.DefaultDestinationTransitions
import illyan.jay.ui.theme.JayThemeWithViewModel
import illyan.jay.util.MapboxExceptionHandler
import javax.inject.Inject
@@ -56,8 +52,6 @@ class MainActivity : AppCompatActivity() {
@Inject
lateinit var authInteractor: AuthInteractor
- lateinit var googleSignInLauncher: ActivityResultLauncher
-
init {
lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
@@ -73,12 +67,7 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- googleSignInLauncher = registerForActivityResult(
- ActivityResultContracts.StartActivityForResult()
- ) {
- val task: Task = GoogleSignIn.getSignedInAccountFromIntent(it.data)
- authInteractor.handleGoogleSignInResult(this, task)
- }
+ installSplashScreen()
if (!MapboxNavigationApp.isSetup()) {
MapboxNavigationApp.setup {
@@ -114,7 +103,8 @@ fun MainScreen(
modifier: Modifier = Modifier
) {
DestinationsNavHost(
- navGraph = NavGraphs.home,
- modifier = modifier
+ navGraph = NavGraphs.root,
+ modifier = modifier,
+ defaultTransitions = DefaultDestinationTransitions
)
}
diff --git a/app/src/main/java/illyan/jay/data/datastore/datasource/AppSettingsDataSource.kt b/app/src/main/java/illyan/jay/data/datastore/datasource/AppSettingsDataSource.kt
index b0cdc924..cbfa5313 100644
--- a/app/src/main/java/illyan/jay/data/datastore/datasource/AppSettingsDataSource.kt
+++ b/app/src/main/java/illyan/jay/data/datastore/datasource/AppSettingsDataSource.kt
@@ -23,11 +23,13 @@ import illyan.jay.data.datastore.model.AppSettings
import illyan.jay.domain.model.DomainPreferences
import kotlinx.coroutines.flow.map
import timber.log.Timber
-import java.time.ZonedDateTime
import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton
+import kotlin.time.Clock
+import kotlin.time.ExperimentalTime
+@OptIn(ExperimentalTime::class)
@Singleton
class AppSettingsDataSource @Inject constructor(
private val appSettingsDataStore: DataStore
@@ -55,7 +57,7 @@ class AppSettingsDataSource @Inject constructor(
suspend fun updateAppPreferences(transform: (DomainPreferences) -> DomainPreferences) {
Timber.v("Updating App Preferences requested")
updateAppSettings {
- it.copy(preferences = transform(it.preferences).copy(lastUpdate = ZonedDateTime.now()))
+ it.copy(preferences = transform(it.preferences).copy(lastUpdate = Clock.System.now()))
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/illyan/jay/data/firestore/Mapping.kt b/app/src/main/java/illyan/jay/data/firestore/Mapping.kt
index 321c6d40..425d7f05 100644
--- a/app/src/main/java/illyan/jay/data/firestore/Mapping.kt
+++ b/app/src/main/java/illyan/jay/data/firestore/Mapping.kt
@@ -16,6 +16,8 @@
* If not, see .
*/
+@file:OptIn(ExperimentalTime::class, ExperimentalSerializationApi::class)
+
package illyan.jay.data.firestore
import android.os.Parcel
@@ -39,8 +41,8 @@ import illyan.jay.domain.model.DomainPreferences
import illyan.jay.domain.model.DomainSensorEvent
import illyan.jay.domain.model.DomainSession
import illyan.jay.util.toGeoPoint
+import illyan.jay.util.toKotlinInstant
import illyan.jay.util.toTimestamp
-import illyan.jay.util.toZonedDateTime
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
@@ -57,7 +59,9 @@ import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
+import kotlin.time.ExperimentalTime
+@OptIn(ExperimentalTime::class)
fun DomainSession.toFirestoreModel() = FirestoreSession(
uuid = uuid,
startDateTime = startDateTime.toTimestamp(),
@@ -74,8 +78,8 @@ fun FirestoreSession.toDomainModel(
ownerUUID: String
) = DomainSession(
uuid = uuid,
- startDateTime = startDateTime.toZonedDateTime(),
- endDateTime = endDateTime?.toZonedDateTime(),
+ startDateTime = startDateTime.toKotlinInstant(),
+ endDateTime = endDateTime?.toKotlinInstant(),
startLocationName = startLocationName,
endLocationName = endLocationName,
clientUUID = clientUUID,
@@ -96,8 +100,8 @@ fun FirestoreUserPreferences.toDomainModel(
showAds = showAds,
theme = theme,
dynamicColorEnabled = dynamicColorEnabled,
- lastUpdate = lastUpdate.toZonedDateTime(),
- lastUpdateToAnalytics = lastUpdateToAnalytics?.toZonedDateTime(),
+ lastUpdate = lastUpdate.toKotlinInstant(),
+ lastUpdateToAnalytics = lastUpdateToAnalytics?.toKotlinInstant(),
shouldSync = true,
)
@@ -118,7 +122,7 @@ fun List.toPath(
): FirestorePath {
val pathLocations = map {
FirestoreLocation(
- timestamp = it.zonedDateTime.toTimestamp(),
+ timestamp = it.timestamp.toTimestamp(),
latitude = it.latitude,
longitude = it.longitude,
speed = it.speed,
@@ -162,7 +166,7 @@ fun List.toFirebaseSensorEvents(
): FirestoreSensorEvents {
val events = map {
FirestoreSensorEvent(
- timestamp = it.zonedDateTime.toTimestamp(),
+ timestamp = it.timestamp.toTimestamp(),
accuracy = it.accuracy.toInt(),
type = it.type.toInt(),
x = it.x,
@@ -185,7 +189,7 @@ fun List.toFirebaseSensorEvents(
ProtoBuf.encodeToByteArray(
data.map {
FirestoreSensorEvent(
- timestamp = it.zonedDateTime.toTimestamp(),
+ timestamp = it.timestamp.toTimestamp(),
accuracy = it.accuracy.toInt(),
type = it.type.toInt(),
x = it.x,
@@ -197,11 +201,9 @@ fun List.toFirebaseSensorEvents(
},
dataSizeHeuristic = { data ->
val startMilli = data
- .minBy { it.zonedDateTime.toInstant().toEpochMilli() }
- .zonedDateTime.toInstant().toEpochMilli()
+ .minOf { it.timestamp.toEpochMilliseconds() }
val endMilli = data
- .maxBy { it.zonedDateTime.toInstant().toEpochMilli() }
- .zonedDateTime.toInstant().toEpochMilli()
+ .maxOf { it.timestamp.toEpochMilliseconds() }
val durationInMinutes = (endMilli - startMilli).milliseconds.inWholeMinutes
"$durationInMinutes minutes of sensor data"
}
@@ -254,7 +256,7 @@ private fun testCompressions(domainLocations: List) {
ProtoBuf.encodeToByteArray(
data.map {
LocationWithoutSessionIdOptimized(
- zonedDateTime = it.zonedDateTime.toTimestamp(),
+ zonedDateTime = it.timestamp.toTimestamp(),
latitude = it.latitude,
longitude = it.longitude,
speed = it.speed,
@@ -272,7 +274,7 @@ private fun testCompressions(domainLocations: List) {
ProtoBuf.encodeToByteArray(
data.map {
LocationWithoutSessionId(
- zonedDateTime = it.zonedDateTime.toTimestamp(),
+ zonedDateTime = it.timestamp.toTimestamp(),
latitude = it.latitude,
longitude = it.longitude,
speed = it.speed,
@@ -290,7 +292,7 @@ private fun testCompressions(domainLocations: List) {
ProtoBuf.encodeToByteArray(
data.map {
LocationWithoutSessionIdUnoptimized(
- zonedDateTime = it.zonedDateTime.toTimestamp(),
+ zonedDateTime = it.timestamp.toTimestamp(),
latitude = it.latitude.toDouble(),
longitude = it.longitude.toDouble(),
speed = it.speed.toDouble(),
@@ -330,11 +332,9 @@ private fun testCompressions(domainLocations: List) {
),
dataSizeHeuristic = { locations ->
val startMilli = locations
- .minBy { it.zonedDateTime.toInstant().toEpochMilli() }
- .zonedDateTime.toInstant().toEpochMilli()
+ .minOf { it.timestamp.toEpochMilliseconds() }
val endMilli = locations
- .maxBy { it.zonedDateTime.toInstant().toEpochMilli() }
- .zonedDateTime.toInstant().toEpochMilli()
+ .maxOf { it.timestamp.toEpochMilliseconds() }
val durationInMinutes = (endMilli - startMilli).milliseconds.inWholeMinutes
"$durationInMinutes minutes of location data"
}
@@ -436,14 +436,14 @@ fun List.toChunkedFirebaseSensorEvents(
thresholdInMinutes: Int = 5
): List {
if (isEmpty()) return emptyList()
- val startMilli = minOf { it.zonedDateTime.toInstant().toEpochMilli() }
+ val startMilli = minOf { it.timestamp.toEpochMilliseconds() }
val groupedByTime = groupBy {
- (it.zonedDateTime.toInstant().toEpochMilli() - startMilli) /
+ (it.timestamp.toEpochMilliseconds() - startMilli) /
thresholdInMinutes.minutes.inWholeMilliseconds
}
return groupedByTime.map { groups ->
groups.value
- .sortedBy { it.zonedDateTime.toInstant().toEpochMilli() }
+ .sortedBy { it.timestamp.toEpochMilliseconds() }
.toFirebaseSensorEvents(sessionUUID, ownerUUID)
}
}
@@ -458,7 +458,7 @@ fun List.toDomainLocations(): List {
domainLocations.addAll(locations.map { it.toDomainModel(path.sessionUUID) })
}
- return domainLocations.sortedBy { it.zonedDateTime.toInstant().toEpochMilli() }
+ return domainLocations.sortedBy { it.timestamp.toEpochMilliseconds() }
}
@OptIn(ExperimentalSerializationApi::class)
@@ -484,14 +484,14 @@ fun List.toDomainSensorEvents(): List
domainSensorEvents.addAll(sensorEvents.map { it.toDomainModel(events.sessionUUID) })
}
- return domainSensorEvents.sortedBy { it.zonedDateTime.toInstant().toEpochMilli() }
+ return domainSensorEvents.sortedBy { it.timestamp.toEpochMilliseconds() }
}
fun FirestoreLocation.toDomainModel(
sessionUUID: String
) = DomainLocation(
latitude = latitude,
- zonedDateTime = timestamp.toZonedDateTime(),
+ timestamp = timestamp.toKotlinInstant(),
longitude = longitude,
speed = speed,
sessionUUID = sessionUUID,
@@ -506,7 +506,7 @@ fun FirestoreLocation.toDomainModel(
fun FirestoreSensorEvent.toDomainModel(
sessionUUID: String
) = DomainSensorEvent(
- zonedDateTime = timestamp.toZonedDateTime(),
+ timestamp = timestamp.toKotlinInstant(),
sessionUUID = sessionUUID,
accuracy = accuracy.toByte(),
type = type.toByte(),
diff --git a/app/src/main/java/illyan/jay/data/firestore/datasource/PathFirestoreDataSource.kt b/app/src/main/java/illyan/jay/data/firestore/datasource/PathFirestoreDataSource.kt
index 74670f5b..5878f9fd 100644
--- a/app/src/main/java/illyan/jay/data/firestore/datasource/PathFirestoreDataSource.kt
+++ b/app/src/main/java/illyan/jay/data/firestore/datasource/PathFirestoreDataSource.kt
@@ -23,8 +23,8 @@ import com.google.firebase.firestore.DocumentReference
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.MetadataChanges
import com.google.firebase.firestore.WriteBatch
-import com.google.firebase.firestore.ktx.snapshots
-import com.google.firebase.firestore.ktx.toObjects
+import com.google.firebase.firestore.snapshots
+import com.google.firebase.firestore.toObjects
import com.google.maps.android.ktx.utils.sphericalPathLength
import illyan.jay.data.firestore.model.FirestorePath
import illyan.jay.data.firestore.toDomainAggressions
@@ -44,7 +44,9 @@ import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.time.Duration.Companion.minutes
+import kotlin.time.ExperimentalTime
+@OptIn(ExperimentalTime::class)
@Singleton
class PathFirestoreDataSource @Inject constructor(
private val firestore: FirebaseFirestore,
@@ -145,18 +147,18 @@ class PathFirestoreDataSource @Inject constructor(
val aggressionsForThisSession = domainAggressions.filter { it.sessionUUID.contentEquals(session.uuid) }
if (session.distance == null) {
session.distance = locationsForThisSession
- .sortedBy { it.zonedDateTime.toInstant().toEpochMilli() }
+ .sortedBy { it.timestamp }
.map { it.latLng }.sphericalPathLength().toFloat()
}
val thresholdInMinutes = 300
if (locationsForThisSession.isEmpty()) return emptyList()
- val startMilli = locationsForThisSession.minOf { it.zonedDateTime.toInstant().toEpochMilli() }
+ val startMilli = locationsForThisSession.minOf { it.timestamp.toEpochMilliseconds() }
val groupedByTime = locationsForThisSession.groupBy {
- (it.zonedDateTime.toInstant().toEpochMilli() - startMilli) / thresholdInMinutes.minutes.inWholeMilliseconds
+ (it.timestamp.toEpochMilliseconds() - startMilli) / thresholdInMinutes.minutes.inWholeMilliseconds
}
paths.addAll(groupedByTime.map { groups ->
groups.value
- .sortedBy { it.zonedDateTime.toInstant().toEpochMilli() }
+ .sortedBy { it.timestamp }
.toPath(session.uuid, session.ownerUUID!!)
})
val aggressionsGroupedByTime = aggressionsForThisSession.groupBy {
diff --git a/app/src/main/java/illyan/jay/data/firestore/datasource/PreferencesFirestoreDataSource.kt b/app/src/main/java/illyan/jay/data/firestore/datasource/PreferencesFirestoreDataSource.kt
index 65575365..e0d490b3 100644
--- a/app/src/main/java/illyan/jay/data/firestore/datasource/PreferencesFirestoreDataSource.kt
+++ b/app/src/main/java/illyan/jay/data/firestore/datasource/PreferencesFirestoreDataSource.kt
@@ -36,7 +36,9 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import timber.log.Timber
import javax.inject.Inject
+import kotlin.time.ExperimentalTime
+@OptIn(ExperimentalTime::class)
class PreferencesFirestoreDataSource @Inject constructor(
private val firestore: FirebaseFirestore,
private val authInteractor: AuthInteractor,
diff --git a/app/src/main/java/illyan/jay/data/firestore/datasource/SensorEventsFirestoreDataSource.kt b/app/src/main/java/illyan/jay/data/firestore/datasource/SensorEventsFirestoreDataSource.kt
index dd9456f7..60933b76 100644
--- a/app/src/main/java/illyan/jay/data/firestore/datasource/SensorEventsFirestoreDataSource.kt
+++ b/app/src/main/java/illyan/jay/data/firestore/datasource/SensorEventsFirestoreDataSource.kt
@@ -23,8 +23,8 @@ import com.google.firebase.firestore.DocumentReference
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.MetadataChanges
import com.google.firebase.firestore.WriteBatch
-import com.google.firebase.firestore.ktx.snapshots
-import com.google.firebase.firestore.ktx.toObjects
+import com.google.firebase.firestore.snapshots
+import com.google.firebase.firestore.toObjects
import illyan.jay.data.firestore.model.FirestoreSensorEvents
import illyan.jay.data.firestore.toChunkedFirebaseSensorEvents
import illyan.jay.data.firestore.toDomainSensorEvents
diff --git a/app/src/main/java/illyan/jay/data/firestore/model/FirestoreSession.kt b/app/src/main/java/illyan/jay/data/firestore/model/FirestoreSession.kt
index 6cf12f0f..08916ba6 100644
--- a/app/src/main/java/illyan/jay/data/firestore/model/FirestoreSession.kt
+++ b/app/src/main/java/illyan/jay/data/firestore/model/FirestoreSession.kt
@@ -22,11 +22,14 @@ import com.google.firebase.Timestamp
import com.google.firebase.firestore.GeoPoint
import com.google.firebase.firestore.PropertyName
import illyan.jay.util.toTimestamp
-import java.time.Instant
+import kotlin.time.Clock
+import kotlin.time.ExperimentalTime
+import kotlin.time.Instant
+@OptIn(ExperimentalTime::class)
data class FirestoreSession(
@PropertyName(FieldUUID) val uuid: String = "",
- @PropertyName(FieldStartDateTime) val startDateTime: Timestamp = Instant.EPOCH.toTimestamp(),
+ @PropertyName(FieldStartDateTime) val startDateTime: Timestamp = Clock.System.now().toTimestamp(),
@PropertyName(FieldEndDateTime) val endDateTime: Timestamp? = null,
@PropertyName(FieldStartLocation) val startLocation: GeoPoint? = null,
@PropertyName(FieldEndLocation) val endLocation: GeoPoint? = null,
diff --git a/app/src/main/java/illyan/jay/data/resolver/PreferencesResolver.kt b/app/src/main/java/illyan/jay/data/resolver/PreferencesResolver.kt
index d873470c..64501fdb 100644
--- a/app/src/main/java/illyan/jay/data/resolver/PreferencesResolver.kt
+++ b/app/src/main/java/illyan/jay/data/resolver/PreferencesResolver.kt
@@ -43,7 +43,9 @@ import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
import kotlin.coroutines.cancellation.CancellationException
+import kotlin.time.ExperimentalTime
+@OptIn(ExperimentalTime::class)
class PreferencesResolver @Inject constructor(
private val authInteractor: AuthInteractor,
private val appSettingsDataSource: AppSettingsDataSource,
diff --git a/app/src/main/java/illyan/jay/data/room/Mapping.kt b/app/src/main/java/illyan/jay/data/room/Mapping.kt
index 3a92c968..b562e1d4 100644
--- a/app/src/main/java/illyan/jay/data/room/Mapping.kt
+++ b/app/src/main/java/illyan/jay/data/room/Mapping.kt
@@ -16,6 +16,8 @@
* If not, see .
*/
+@file:OptIn(ExperimentalTime::class)
+
package illyan.jay.data.room
import android.hardware.SensorEvent
@@ -33,16 +35,14 @@ import illyan.jay.domain.model.DomainPreferences
import illyan.jay.domain.model.DomainSensorEvent
import illyan.jay.domain.model.DomainSession
import illyan.jay.util.sensorTimestampToAbsoluteTime
-import illyan.jay.util.toZonedDateTime
-import java.time.Instant
-import java.time.ZoneOffset
-import java.time.ZonedDateTime
+import kotlin.time.ExperimentalTime
+import kotlin.time.Instant
// Session
fun RoomSession.toDomainModel() = DomainSession(
uuid = uuid,
- startDateTime = Instant.ofEpochMilli(startDateTime).atZone(ZoneOffset.UTC),
- endDateTime = endDateTime?.let { Instant.ofEpochMilli(it).atZone(ZoneOffset.UTC) },
+ startDateTime = Instant.fromEpochMilliseconds(startDateTime),
+ endDateTime = endDateTime?.let { Instant.fromEpochMilliseconds(it) },
startLocationLatitude = startLocationLatitude,
startLocationLongitude = startLocationLongitude,
endLocationLatitude = endLocationLatitude,
@@ -54,10 +54,11 @@ fun RoomSession.toDomainModel() = DomainSession(
clientUUID = clientUUID,
)
+@OptIn(ExperimentalTime::class)
fun DomainSession.toRoomModel() = RoomSession(
uuid = uuid,
- startDateTime = startDateTime.toInstant().toEpochMilli(),
- endDateTime = endDateTime?.toInstant()?.toEpochMilli(),
+ startDateTime = startDateTime.toEpochMilliseconds(),
+ endDateTime = endDateTime?.toEpochMilliseconds(),
startLocationLatitude = startLocationLatitude,
startLocationLongitude = startLocationLongitude,
endLocationLatitude = endLocationLatitude,
@@ -72,7 +73,7 @@ fun DomainSession.toRoomModel() = RoomSession(
// Location
fun RoomLocation.toDomainModel() = DomainLocation(
latitude = latitude,
- zonedDateTime = Instant.ofEpochMilli(time).atZone(ZoneOffset.UTC),
+ timestamp = Instant.fromEpochMilliseconds(time),
longitude = longitude,
speed = speed,
sessionUUID = sessionUUID,
@@ -86,7 +87,7 @@ fun RoomLocation.toDomainModel() = DomainLocation(
fun DomainLocation.toRoomModel() = RoomLocation(
sessionUUID = sessionUUID,
- time = zonedDateTime.toInstant().toEpochMilli(),
+ time = timestamp.toEpochMilliseconds(),
latitude = latitude,
longitude = longitude,
speed = speed,
@@ -103,7 +104,7 @@ fun Location.toDomainModel(
): DomainLocation {
val domainLocation = DomainLocation(
sessionUUID = sessionUUID,
- zonedDateTime = Instant.ofEpochMilli(time).atZone(ZoneOffset.UTC),
+ timestamp = Instant.fromEpochMilliseconds(time),
latitude = latitude.toFloat(),
longitude = longitude.toFloat()
)
@@ -123,7 +124,7 @@ fun Location.toDomainModel(
// Rotation
fun RoomSensorEvent.toDomainModel() = DomainSensorEvent(
- zonedDateTime = Instant.ofEpochMilli(time).atZone(ZoneOffset.UTC),
+ timestamp = Instant.fromEpochMilliseconds(time),
sessionUUID = sessionUUID,
accuracy = accuracy,
x = x,
@@ -134,7 +135,7 @@ fun RoomSensorEvent.toDomainModel() = DomainSensorEvent(
fun DomainSensorEvent.toRoomModel() = RoomSensorEvent(
sessionUUID = sessionUUID,
- time = zonedDateTime.toInstant().toEpochMilli(),
+ time = timestamp.toEpochMilliseconds(),
type = type,
accuracy = accuracy,
x = x,
@@ -145,7 +146,7 @@ fun DomainSensorEvent.toRoomModel() = RoomSensorEvent(
// Sensors
fun SensorEvent.toDomainModel(sessionUUID: String) = DomainSensorEvent(
sessionUUID = sessionUUID,
- zonedDateTime = Instant.ofEpochMilli(sensorTimestampToAbsoluteTime(timestamp)).atZone(ZoneOffset.UTC),
+ timestamp = Instant.fromEpochMilliseconds(sensorTimestampToAbsoluteTime(timestamp)),
type = sensor.type.toByte(),
accuracy = accuracy.toByte(),
x = values[0],
@@ -161,14 +162,14 @@ fun RoomPreferences.toDomainModel() = DomainPreferences(
showAds = showAds,
theme = theme,
dynamicColorEnabled = dynamicColorEnabled,
- lastUpdate = Instant.ofEpochMilli(lastUpdate).toZonedDateTime(),
- lastUpdateToAnalytics = lastUpdateToAnalytics?.let { return@let Instant.ofEpochMilli(it).toZonedDateTime() },
+ lastUpdate = Instant.fromEpochMilliseconds(lastUpdate),
+ lastUpdateToAnalytics = lastUpdateToAnalytics?.let { return@let Instant.fromEpochMilliseconds(it) },
shouldSync = shouldSync,
)
fun DomainPreferences.toRoomModel(
userUUID: String,
- lastUpdate: ZonedDateTime = this.lastUpdate
+ lastUpdate: Instant = this.lastUpdate
) = RoomPreferences(
userUUID = userUUID,
analyticsEnabled = analyticsEnabled,
@@ -176,8 +177,8 @@ fun DomainPreferences.toRoomModel(
showAds = showAds,
theme = theme,
dynamicColorEnabled = dynamicColorEnabled,
- lastUpdate = lastUpdate.toInstant().toEpochMilli(),
- lastUpdateToAnalytics = lastUpdateToAnalytics?.toInstant()?.toEpochMilli(),
+ lastUpdate = lastUpdate.toEpochMilliseconds(),
+ lastUpdateToAnalytics = lastUpdateToAnalytics?.toEpochMilliseconds(),
shouldSync = shouldSync,
)
diff --git a/app/src/main/java/illyan/jay/data/room/datasource/PreferencesRoomDataSource.kt b/app/src/main/java/illyan/jay/data/room/datasource/PreferencesRoomDataSource.kt
index ccaa5bcf..2cef6bfd 100644
--- a/app/src/main/java/illyan/jay/data/room/datasource/PreferencesRoomDataSource.kt
+++ b/app/src/main/java/illyan/jay/data/room/datasource/PreferencesRoomDataSource.kt
@@ -27,7 +27,9 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import timber.log.Timber
import javax.inject.Inject
+import kotlin.time.ExperimentalTime
+@OptIn(ExperimentalTime::class)
class PreferencesRoomDataSource @Inject constructor(
private val preferencesDao: PreferencesDao
) {
diff --git a/app/src/main/java/illyan/jay/data/room/datasource/SessionRoomDataSource.kt b/app/src/main/java/illyan/jay/data/room/datasource/SessionRoomDataSource.kt
index c8092b7f..2ea0a939 100644
--- a/app/src/main/java/illyan/jay/data/room/datasource/SessionRoomDataSource.kt
+++ b/app/src/main/java/illyan/jay/data/room/datasource/SessionRoomDataSource.kt
@@ -26,12 +26,13 @@ import illyan.jay.data.room.toRoomModel
import illyan.jay.domain.model.DomainSession
import kotlinx.coroutines.flow.map
import timber.log.Timber
-import java.time.Instant
-import java.time.ZoneId
-import java.time.ZonedDateTime
-import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton
+import kotlin.time.Clock
+import kotlin.time.ExperimentalTime
+import kotlin.time.Instant
+import kotlin.uuid.ExperimentalUuidApi
+import kotlin.uuid.Uuid
/**
* Session disk data source using Room to communicate with the SQLite database.
@@ -39,6 +40,7 @@ import javax.inject.Singleton
* @property sessionDao used to insert, update, delete and query commands using Room.
* @constructor Create empty Session disk data source
*/
+@OptIn(ExperimentalTime::class, ExperimentalUuidApi::class)
@Singleton
class SessionRoomDataSource @Inject constructor(
private val sessionDao: SessionDao,
@@ -119,13 +121,11 @@ class SessionRoomDataSource @Inject constructor(
ownerUUID: String? = null,
clientUUID: String? = null,
): String {
- val uuid = UUID.randomUUID().toString()
+ val uuid = Uuid.random().toString()
sessionDao.insertSession(
RoomSession(
uuid = uuid,
- startDateTime = Instant.now()
- .atZone(ZoneId.systemDefault())
- .toInstant().toEpochMilli(),
+ startDateTime = Clock.System.now().toEpochMilliseconds(),
endDateTime = null,
ownerUUID = ownerUUID,
clientUUID = clientUUID
@@ -168,7 +168,7 @@ class SessionRoomDataSource @Inject constructor(
*/
fun stopSession(
session: DomainSession,
- endTime: ZonedDateTime = Instant.now().atZone(ZoneId.systemDefault())
+ endTime: Instant = Clock.System.now()
): Long {
if (session.endDateTime == null) session.endDateTime = endTime
return saveSession(session)
@@ -183,7 +183,7 @@ class SessionRoomDataSource @Inject constructor(
*/
fun stopSessions(
sessions: List,
- endTime: ZonedDateTime = Instant.now().atZone(ZoneId.systemDefault())
+ endTime: Instant = Clock.System.now()
) {
sessions.forEach { if (it.endDateTime == null) it.endDateTime = endTime }
saveSessions(sessions)
@@ -240,4 +240,3 @@ class SessionRoomDataSource @Inject constructor(
sessionDao.saveDistanceForSession(sessionUUID, distance)
}
}
-
diff --git a/app/src/main/java/illyan/jay/data/sensor/SensorFusion.kt b/app/src/main/java/illyan/jay/data/sensor/SensorFusion.kt
index ebc3d0b4..132bf3ff 100644
--- a/app/src/main/java/illyan/jay/data/sensor/SensorFusion.kt
+++ b/app/src/main/java/illyan/jay/data/sensor/SensorFusion.kt
@@ -24,7 +24,9 @@ import illyan.jay.domain.model.DomainSensorEvent
import timber.log.Timber
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.ExperimentalTime
+@OptIn(ExperimentalTime::class)
object SensorFusion {
fun fuseSensorsWithInterval(
interval: Duration = 10.milliseconds,
@@ -37,7 +39,7 @@ object SensorFusion {
angAccel: List,
): List {
val allTimestamps = (accRaw + accSmooth + dirX + dirY + dirZ + angVel + angAccel)
- .map { it.zonedDateTime.toInstant().toEpochMilli() }
+ .map { it.timestamp.toEpochMilliseconds() }
.distinct()
.sorted()
@@ -70,7 +72,7 @@ object SensorFusion {
angVel: List,
angAccel: List,
intervals: List = (accRaw + accSmooth + dirX + dirY + dirZ + angVel + angAccel)
- .map { it.zonedDateTime.toInstant().toEpochMilli() }
+ .map { it.timestamp.toEpochMilliseconds() }
.distinct()
.sorted()
): List {
@@ -109,11 +111,11 @@ object SensorFusion {
val firstEvent = events.first()
val lastEvent = events.last()
return timestamps.map { timestamp ->
- val beforeEvent = events.firstOrNull { it.zonedDateTime.toInstant().toEpochMilli() <= timestamp } ?: firstEvent
- val afterEvent = events.firstOrNull { it.zonedDateTime.toInstant().toEpochMilli() >= timestamp } ?: lastEvent
+ val beforeEvent = events.firstOrNull { it.timestamp.toEpochMilliseconds() <= timestamp } ?: firstEvent
+ val afterEvent = events.firstOrNull { it.timestamp.toEpochMilliseconds() >= timestamp } ?: lastEvent
if (beforeEvent == afterEvent) return@map Triple(beforeEvent.x.toDouble(), beforeEvent.y.toDouble(), beforeEvent.z.toDouble())
- val fraction = (timestamp - beforeEvent.zonedDateTime.toInstant().toEpochMilli()).toFloat() /
- (afterEvent.zonedDateTime.toInstant().toEpochMilli() - beforeEvent.zonedDateTime.toInstant().toEpochMilli()).toFloat()
+ val fraction = (timestamp - beforeEvent.timestamp.toEpochMilliseconds()).toFloat() /
+ (afterEvent.timestamp - beforeEvent.timestamp).inWholeMilliseconds
val interpolatedEventX = lerp(
beforeEvent.x,
afterEvent.x,
diff --git a/app/src/main/java/illyan/jay/data/serializers/ZonedDateTimeSerializer.kt b/app/src/main/java/illyan/jay/data/serializers/InstantSerializer.kt
similarity index 72%
rename from app/src/main/java/illyan/jay/data/serializers/ZonedDateTimeSerializer.kt
rename to app/src/main/java/illyan/jay/data/serializers/InstantSerializer.kt
index 57bc7108..bccd2140 100644
--- a/app/src/main/java/illyan/jay/data/serializers/ZonedDateTimeSerializer.kt
+++ b/app/src/main/java/illyan/jay/data/serializers/InstantSerializer.kt
@@ -25,16 +25,22 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.time.ZonedDateTime
+import kotlin.time.ExperimentalTime
+import kotlin.time.Instant
-object ZonedDateTimeSerializer : KSerializer {
+@OptIn(ExperimentalTime::class)
+object InstantSerializer : KSerializer {
override val descriptor: SerialDescriptor
- get() = PrimitiveSerialDescriptor("java.time.ZonedDateTime", PrimitiveKind.STRING)
+ get() = PrimitiveSerialDescriptor(
+ "illyan.jay.data.serializers.InstantSerializer",
+ PrimitiveKind.STRING
+ )
- override fun deserialize(decoder: Decoder): ZonedDateTime {
- return ZonedDateTime.parse(decoder.decodeString())
+ override fun deserialize(decoder: Decoder): Instant {
+ return Instant.parse(decoder.decodeString())
}
- override fun serialize(encoder: Encoder, value: ZonedDateTime) {
+ override fun serialize(encoder: Encoder, value: Instant) {
encoder.encodeString(value.toString())
}
}
diff --git a/app/src/main/java/illyan/jay/data/serializers/ZonedDateTimeNullableSerializer.kt b/app/src/main/java/illyan/jay/data/serializers/ZonedDateTimeNullableSerializer.kt
deleted file mode 100644
index c4865cc5..00000000
--- a/app/src/main/java/illyan/jay/data/serializers/ZonedDateTimeNullableSerializer.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package illyan.jay.data.serializers
-
-import kotlinx.serialization.KSerializer
-import kotlinx.serialization.descriptors.PrimitiveKind
-import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
-import kotlinx.serialization.descriptors.SerialDescriptor
-import kotlinx.serialization.encoding.Decoder
-import kotlinx.serialization.encoding.Encoder
-import java.time.ZonedDateTime
-
-object ZonedDateTimeNullableSerializer : KSerializer {
- override val descriptor: SerialDescriptor
- get() = PrimitiveSerialDescriptor("java.time.ZonedDateTime?", PrimitiveKind.STRING)
-
- override fun deserialize(decoder: Decoder): ZonedDateTime? {
- val decoded = decoder.decodeString()
- if (decoded == "null") return null
- return ZonedDateTime.parse(decoded)
- }
-
- override fun serialize(encoder: Encoder, value: ZonedDateTime?) {
- encoder.encodeString(value.toString())
- }
-}
diff --git a/app/src/main/java/illyan/jay/di/AppModule.kt b/app/src/main/java/illyan/jay/di/AppModule.kt
index 370e2a35..d8ddbf0d 100644
--- a/app/src/main/java/illyan/jay/di/AppModule.kt
+++ b/app/src/main/java/illyan/jay/di/AppModule.kt
@@ -26,10 +26,10 @@ import androidx.core.graphics.drawable.IconCompat
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.google.android.gms.location.LocationServices
-import com.google.firebase.analytics.ktx.analytics
-import com.google.firebase.crashlytics.ktx.crashlytics
-import com.google.firebase.ktx.Firebase
-import com.google.firebase.perf.ktx.performance
+import com.google.firebase.Firebase
+import com.google.firebase.analytics.analytics
+import com.google.firebase.crashlytics.crashlytics
+import com.google.firebase.perf.performance
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
diff --git a/app/src/main/java/illyan/jay/di/FirebaseModule.kt b/app/src/main/java/illyan/jay/di/FirebaseModule.kt
index 74e41e94..e6251435 100644
--- a/app/src/main/java/illyan/jay/di/FirebaseModule.kt
+++ b/app/src/main/java/illyan/jay/di/FirebaseModule.kt
@@ -21,16 +21,16 @@ package illyan.jay.di
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.NetworkRequest
-import com.google.firebase.auth.ktx.auth
+import com.google.firebase.Firebase
+import com.google.firebase.auth.auth
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.FirebaseFirestoreSettings
import com.google.firebase.firestore.PersistentCacheSettings
-import com.google.firebase.firestore.ktx.firestore
-import com.google.firebase.ktx.Firebase
+import com.google.firebase.firestore.firestore
import com.google.firebase.ml.modeldownloader.FirebaseModelDownloader
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
-import com.google.firebase.remoteconfig.ktx.remoteConfig
-import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
+import com.google.firebase.remoteconfig.remoteConfig
+import com.google.firebase.remoteconfig.remoteConfigSettings
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
diff --git a/app/src/main/java/illyan/jay/di/FirestoreModule.kt b/app/src/main/java/illyan/jay/di/FirestoreModule.kt
index 1c079b30..a59fd329 100644
--- a/app/src/main/java/illyan/jay/di/FirestoreModule.kt
+++ b/app/src/main/java/illyan/jay/di/FirestoreModule.kt
@@ -23,9 +23,9 @@ import com.google.firebase.firestore.DocumentSnapshot
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.MetadataChanges
import com.google.firebase.firestore.QuerySnapshot
-import com.google.firebase.firestore.ktx.snapshots
-import com.google.firebase.firestore.ktx.toObject
-import com.google.firebase.firestore.ktx.toObjects
+import com.google.firebase.firestore.snapshots
+import com.google.firebase.firestore.toObject
+import com.google.firebase.firestore.toObjects
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
diff --git a/app/src/main/java/illyan/jay/domain/interactor/AuthInteractor.kt b/app/src/main/java/illyan/jay/domain/interactor/AuthInteractor.kt
index 73b918a7..b8b47cec 100644
--- a/app/src/main/java/illyan/jay/domain/interactor/AuthInteractor.kt
+++ b/app/src/main/java/illyan/jay/domain/interactor/AuthInteractor.kt
@@ -20,19 +20,20 @@ package illyan.jay.domain.interactor
import android.app.Activity
import androidx.compose.runtime.mutableStateListOf
-import com.google.android.gms.auth.api.signin.GoogleSignIn
-import com.google.android.gms.auth.api.signin.GoogleSignInAccount
-import com.google.android.gms.auth.api.signin.GoogleSignInClient
-import com.google.android.gms.auth.api.signin.GoogleSignInOptions
-import com.google.android.gms.common.api.ApiException
-import com.google.android.gms.tasks.Task
+import androidx.credentials.CredentialManager
+import androidx.credentials.CustomCredential
+import androidx.credentials.GetCredentialRequest
+import androidx.credentials.exceptions.GetCredentialException
+import com.google.android.libraries.identity.googleid.GetGoogleIdOption
+import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
import com.google.firebase.analytics.FirebaseAnalytics
-import com.google.firebase.analytics.ktx.logEvent
+import com.google.firebase.analytics.logEvent
import com.google.firebase.auth.AuthCredential
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.GoogleAuthProvider
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
-import com.google.firebase.remoteconfig.ktx.get
+import com.google.firebase.remoteconfig.get
+import illyan.jay.BuildConfig
import illyan.jay.MainActivity
import illyan.jay.di.CoroutineScopeIO
import illyan.jay.util.awaitOperations
@@ -74,9 +75,6 @@ class AuthInteractor @Inject constructor(
private val _userDisplayNameStateFlow = MutableStateFlow(auth.currentUser?.displayName)
val userDisplayNameStateFlow = _userDisplayNameStateFlow.asStateFlow()
- private val _googleSignInClient = MutableStateFlow(null)
- private val googleSignInClient = _googleSignInClient.asStateFlow()
-
private val googleAuthStateListeners = mutableStateListOf<(Int) -> Unit>()
val isUserSignedIn get() = auth.currentUser != null
@@ -109,6 +107,8 @@ class AuthInteractor @Inject constructor(
val size = onSignOutListeners.size
if (size == 0) {
Timber.i("No sign out listeners detected, signing out user ${userUUID?.take(4)}")
+ auth.signOut()
+ _isSigningOut.update { false }
} else {
Timber.i("Notifying sign out listeners")
coroutineScopeIO.launch {
@@ -120,71 +120,71 @@ class AuthInteractor @Inject constructor(
}
}
Timber.i("All listeners notified, signing out user ${userUUID?.take(4)}")
+ auth.signOut()
+ _isSigningOut.update { false }
}
- auth.signOut()
- googleSignInClient.value?.signOut()
- _isSigningOut.update { false }
}
}
fun signInViaGoogle(activity: MainActivity) {
if (isUserSignedIn) return
- if (_googleSignInClient.value == null) {
- remoteConfig.fetchAndActivate().addOnSuccessListener {
- remoteConfig.ensureInitialized().addOnSuccessListener {
- val defaultWebClientId = remoteConfig["default_web_client_id"].asString()
- if (defaultWebClientId.isEmpty()) {
- // TODO: throw error or show error message (use local broadcast manager to send a broadcast to UI?)
- } else {
- _googleSignInClient.update {
- GoogleSignIn.getClient(
- activity,
- GoogleSignInOptions
- .Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
- .requestIdToken(defaultWebClientId)
- .requestEmail()
- .build()
- )
+ remoteConfig.fetchAndActivate().addOnSuccessListener {
+ remoteConfig.ensureInitialized().addOnSuccessListener {
+ val serverClientId = remoteConfig["default_web_client_id"].asString()
+ if (serverClientId.isBlank()) {
+ Timber.e("Server client ID is blank in Remote Config")
+ googleAuthStateListeners.forEach { it(-1) }
+ googleAuthStateListeners.clear()
+ analytics.logEvent(FirebaseAnalytics.Event.LOGIN) {
+ param(FirebaseAnalytics.Param.METHOD, "Google")
+ }
+ } else {
+ coroutineScopeIO.launch {
+ try {
+ val credentialManager = CredentialManager.create(activity)
+ val googleIdOption = GetGoogleIdOption.Builder()
+ .setFilterByAuthorizedAccounts(false)
+ .setServerClientId(serverClientId)
+ .setAutoSelectEnabled(true)
+ .build()
+ val request = GetCredentialRequest.Builder()
+ .addCredentialOption(googleIdOption)
+ .build()
+ val result = credentialManager.getCredential(activity, request)
+ val credential = result.credential
+ if (credential is CustomCredential &&
+ credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL
+ ) {
+ val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.data)
+ val idToken = googleIdTokenCredential.idToken
+ signInWithCredential(
+ activity,
+ GoogleAuthProvider.getCredential(idToken, null)
+ )
+ } else {
+ Timber.e("Unexpected credential type: ${'$'}{credential.javaClass.name}")
+ }
+ } catch (e: GetCredentialException) {
+ Timber.e(e, "Credential retrieval failed")
+ googleAuthStateListeners.forEach { it(-1) }
+ googleAuthStateListeners.clear()
+ analytics.logEvent(FirebaseAnalytics.Event.LOGIN) {
+ param(FirebaseAnalytics.Param.METHOD, "Google")
+ }
+ } catch (e: Exception) {
+ Timber.e(e, "Credential retrieval error")
+ googleAuthStateListeners.forEach { it(-1) }
+ googleAuthStateListeners.clear()
+ analytics.logEvent(FirebaseAnalytics.Event.LOGIN) {
+ param(FirebaseAnalytics.Param.METHOD, "Google")
+ }
}
- activity.googleSignInLauncher.launch(googleSignInClient.value!!.signInIntent)
}
}
}
- } else {
- activity.googleSignInLauncher.launch(googleSignInClient.value!!.signInIntent)
}
}
- fun handleGoogleSignInResult(
- activity: Activity,
- completedTask: Task
- ) {
- try {
- val account = completedTask.getResult(ApiException::class.java)
- account.idToken?.let {
- signInWithCredential(
- activity,
- GoogleAuthProvider.getCredential(it, null)
- )
- }
- } catch (e: ApiException) {
- // The ApiException status code indicates the detailed failure reason.
- // Please refer to the GoogleSignInStatusCodes class reference for more information.
- googleAuthStateListeners.forEach { it(e.statusCode) }
- googleAuthStateListeners.clear()
- Timber.e(
- e,
- "signInResult:failed code = ${e.statusCode}\n" +
- "Used api key: " +
- remoteConfig["default_web_client_id"].asString()
- .take(4) + "..." +
- "\n${e.message}"
- )
- analytics.logEvent(FirebaseAnalytics.Event.LOGIN) {
- param(FirebaseAnalytics.Param.METHOD, "Google")
- }
- }
- }
private fun signInWithCredential(
activity: Activity,
@@ -196,7 +196,7 @@ class AuthInteractor @Inject constructor(
Timber.i("Firebase authentication successful")
} else {
// If sign in fails, display a message to the user.
- Timber.e(task.exception, task.exception?.message)
+ Timber.e(task.exception)
}
}
}
diff --git a/app/src/main/java/illyan/jay/domain/interactor/ModelInteractor.kt b/app/src/main/java/illyan/jay/domain/interactor/ModelInteractor.kt
index e0a95147..6f611170 100644
--- a/app/src/main/java/illyan/jay/domain/interactor/ModelInteractor.kt
+++ b/app/src/main/java/illyan/jay/domain/interactor/ModelInteractor.kt
@@ -25,7 +25,6 @@ import com.google.firebase.ml.modeldownloader.DownloadType
import illyan.jay.data.firebaseml.datasource.FirebaseMLDataSource
import illyan.jay.data.sensor.SensorFusion
import illyan.jay.di.CoroutineScopeIO
-import illyan.jay.util.toZonedDateTime
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -36,11 +35,12 @@ import org.tensorflow.lite.Interpreter
import timber.log.Timber
import java.nio.ByteBuffer
import java.nio.ByteOrder
-import java.time.Instant
-import java.time.ZonedDateTime
import javax.inject.Inject
import javax.inject.Singleton
+import kotlin.time.ExperimentalTime
+import kotlin.time.Instant
+@OptIn(ExperimentalTime::class)
@Singleton
class ModelInteractor @Inject constructor(
private val firebaseMLDataSource: FirebaseMLDataSource,
@@ -75,10 +75,10 @@ class ModelInteractor @Inject constructor(
suspend fun getFilteredDriverAggression(
modelName: String,
sessionUUID: String
- ): Flow