Skip to content

Commit

Permalink
Refactoring, bug fixing. Fixed bug with offline user preferences not …
Browse files Browse the repository at this point in the history
…updating. Created `AppSettingsDataSource` to get the app's settings, clientID and offline user preferences. Fixed issue with UI initialization not animating correctly (sheet does not expand, camera animates badly).
  • Loading branch information
HLCaptain committed Apr 27, 2023
1 parent da56b79 commit 3ec32db
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 132 deletions.
3 changes: 0 additions & 3 deletions app/src/main/java/illyan/jay/MainApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,11 @@
package illyan.jay

import android.app.Application
import androidx.datastore.core.DataStore
import com.google.android.gms.ads.MobileAds
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.perf.FirebasePerformance
import dagger.hilt.android.HiltAndroidApp
import illyan.jay.data.datastore.model.AppSettings
import illyan.jay.di.CoroutineScopeIO
import illyan.jay.domain.interactor.AuthInteractor
import illyan.jay.domain.interactor.SettingsInteractor
Expand All @@ -42,7 +40,6 @@ import kotlin.coroutines.cancellation.CancellationException
class MainApplication : Application() {
@Inject lateinit var crashlyticsTree: CrashlyticsTree
@Inject lateinit var debugTree: Timber.DebugTree
@Inject lateinit var appSettingsDataStore: DataStore<AppSettings>
@Inject lateinit var authInteractor: AuthInteractor
@Inject lateinit var crashlytics: FirebaseCrashlytics
@Inject lateinit var analytics: FirebaseAnalytics
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2023 Balázs Püspök-Kiss (Illyan)
*
* Jay is a driver behaviour analytics app.
*
* This file is part of Jay.
*
* Jay is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
* Jay is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Jay.
* If not, see <https://www.gnu.org/licenses/>.
*/

package illyan.jay.data.datastore.datasource

import androidx.datastore.core.DataStore
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

@Singleton
class AppSettingsDataSource @Inject constructor(
private val appSettingsDataStore: DataStore<AppSettings>
) {
val appSettings by lazy {
appSettingsDataStore.data.map { settings ->
if (settings.clientUUID == null) {
val newSettings = settings.copy(clientUUID = UUID.randomUUID().toString())
updateAppSettings { newSettings }
newSettings
} else {
settings
}
}
}

suspend fun updateAppSettings(transform: (AppSettings) -> AppSettings) {
appSettingsDataStore.updateData {
val newSettings = transform(it)
Timber.v("Changed settings from $it to $newSettings")
newSettings
}
}

suspend fun updateAppPreferences(transform: (DomainPreferences) -> DomainPreferences) {
Timber.v("Updating App Preferences requested")
updateAppSettings {
it.copy(preferences = transform(it.preferences).copy(lastUpdate = ZonedDateTime.now()))
}
}
}
2 changes: 1 addition & 1 deletion app/src/main/java/illyan/jay/data/resolver/DataResolver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ abstract class DataResolver<DataType>(
localDataLoading,
enableSyncedData
) { cloudDataLoading, localDataLoading, enableSyncedData ->
if (enableSyncedData) {
if (!enableSyncedData) {
localDataLoading
} else {
if (cloudDataLoading == null && localDataLoading == null) {
Expand Down
70 changes: 49 additions & 21 deletions app/src/main/java/illyan/jay/data/resolver/PreferencesResolver.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
/*
* Copyright (c) 2023 Balázs Püspök-Kiss (Illyan)
*
* Jay is a driver behaviour analytics app.
*
* This file is part of Jay.
*
* Jay is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
* Jay is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Jay.
* If not, see <https://www.gnu.org/licenses/>.
*/

package illyan.jay.data.resolver

import androidx.datastore.core.DataStore
import illyan.jay.data.DataStatus
import illyan.jay.data.datastore.model.AppSettings
import illyan.jay.data.datastore.datasource.AppSettingsDataSource
import illyan.jay.data.firestore.datasource.PreferencesFirestoreDataSource
import illyan.jay.data.room.datasource.PreferencesRoomDataSource
import illyan.jay.di.CoroutineScopeIO
Expand All @@ -14,6 +31,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
Expand All @@ -22,7 +40,7 @@ import kotlin.coroutines.cancellation.CancellationException

class PreferencesResolver @Inject constructor(
private val authInteractor: AuthInteractor,
private val appSettingsDataStore: DataStore<AppSettings>,
private val appSettingsDataSource: AppSettingsDataSource,
private val preferencesFirestoreDataSource: PreferencesFirestoreDataSource,
private val preferencesRoomDataSource: PreferencesRoomDataSource,
@CoroutineScopeIO private val coroutineScopeIO: CoroutineScope,
Expand All @@ -35,32 +53,42 @@ class PreferencesResolver @Inject constructor(
override val localDataStatus: StateFlow<DataStatus<DomainPreferences>> by lazy {
val statusStateFlow = MutableStateFlow(DataStatus<DomainPreferences>())
Timber.v("Refreshing local user preferences data collection")
var dataCollectionJob: Job? = null
var dataCollectionJob: Job?
coroutineScopeIO.launch {
dataCollectionJob = refreshLocalPreferences(authInteractor.userUUID, statusStateFlow)
authInteractor.userUUIDStateFlow.collectLatest { uuid ->
statusStateFlow.update { DataStatus(data = null, isLoading = true) }
dataCollectionJob?.cancel(CancellationException("User Authentication changed, need to cancel jobs depending on User Authentication"))
if (uuid != null) { // User signed in
Timber.v("Collecting signed in user preferences from disk")
dataCollectionJob = coroutineScopeIO.launch {
preferencesRoomDataSource.getPreferences(uuid).collectLatest { preferences ->
statusStateFlow.update { DataStatus(preferences, false) }
}
}
} else { // Offline user
Timber.v("Collecting offline user preferences from disk")
// Simple, we only use the baseline preferences for offline users
dataCollectionJob = coroutineScopeIO.launch {
appSettingsDataStore.data.collectLatest { settings ->
statusStateFlow.update { DataStatus(settings.preferences, false) }
}
}
}
dataCollectionJob = refreshLocalPreferences(uuid, statusStateFlow)
}
}
statusStateFlow.asStateFlow()
}

private suspend fun refreshLocalPreferences(
uuid: String?,
statusStateFlow: MutableStateFlow<DataStatus<DomainPreferences>>,
): Job {
statusStateFlow.update { DataStatus(data = null, isLoading = true) }
return if (uuid != null) { // User signed in
Timber.v("Collecting signed in user preferences from disk")
coroutineScopeIO.launch {
preferencesRoomDataSource.getPreferences(uuid).collectLatest { preferences ->
statusStateFlow.update { DataStatus(preferences, false) }
}
}
} else { // Offline user
Timber.v("Collecting offline user preferences from disk")
// Simple, we only use the baseline preferences for offline users
coroutineScopeIO.launch {
appPreferences.collectLatest { preferences ->
statusStateFlow.update { DataStatus(preferences, false) }
}
}
}
}

val appPreferences by lazy { appSettingsDataSource.appSettings.map { it.preferences } }

override fun shouldSyncData(localData: DomainPreferences?): Boolean {
return localData?.shouldSync ?: DomainPreferences.Default.shouldSync
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.WriteBatch
import com.mapbox.geojson.Point
import com.mapbox.search.ReverseGeoOptions
import illyan.jay.data.datastore.datasource.AppSettingsDataSource
import illyan.jay.data.firestore.datasource.PathFirestoreDataSource
import illyan.jay.data.firestore.datasource.SessionFirestoreDataSource
import illyan.jay.data.room.datasource.LocationRoomDataSource
Expand Down Expand Up @@ -63,7 +64,7 @@ class SessionInteractor @Inject constructor(
private val searchInteractor: SearchInteractor,
private val sessionFirestoreDataSource: SessionFirestoreDataSource,
private val authInteractor: AuthInteractor,
private val settingsInteractor: SettingsInteractor,
private val appSettingsDataSource: AppSettingsDataSource,
private val serviceInteractor: ServiceInteractor,
private val pathFirestoreDataSource: PathFirestoreDataSource,
private val firestore: FirebaseFirestore,
Expand Down Expand Up @@ -289,12 +290,12 @@ class SessionInteractor @Inject constructor(
getSession(sessionUUID).first { session ->
session?.let {
Timber.i("Trying to assign client to session $sessionUUID")
settingsInteractor.appSettingsFlow.first { settings ->
appSettingsDataSource.appSettings.first { settings ->
Timber.d("Client UUID = ${settings.clientUUID}")
if (settings.clientUUID == null) {
val clientUUID = UUID.randomUUID().toString()
Timber.d("Generating new client UUID: $clientUUID")
settingsInteractor.updateAppSettings {
appSettingsDataSource.updateAppSettings {
it.copy(clientUUID = clientUUID)
}
it.clientUUID = clientUUID
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,38 +18,27 @@

package illyan.jay.domain.interactor

import androidx.datastore.core.DataStore
import illyan.jay.data.datastore.model.AppSettings
import illyan.jay.data.datastore.datasource.AppSettingsDataSource
import illyan.jay.data.resolver.PreferencesResolver
import illyan.jay.data.room.datasource.PreferencesRoomDataSource
import illyan.jay.di.CoroutineScopeIO
import illyan.jay.domain.model.DomainPreferences
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import timber.log.Timber
import java.time.ZonedDateTime
import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class SettingsInteractor @Inject constructor(
private val appSettingsDataStore: DataStore<AppSettings>,
private val appSettingsDataSource: AppSettingsDataSource,
private val preferencesResolver: PreferencesResolver,
private val preferencesRoomDataSource: PreferencesRoomDataSource,
private val authInteractor: AuthInteractor,
@CoroutineScopeIO private val coroutineScopeIO: CoroutineScope
) {
val appSettingsFlow = appSettingsDataStore.data.map { settings ->
if (settings.clientUUID == null) {
val newSettings = settings.copy(clientUUID = UUID.randomUUID().toString())
updateAppSettings { newSettings }
newSettings
} else {
settings
}
}
val appSettings = appSettingsDataSource.appSettings

var freeDriveAutoStart: Boolean?
get() = userPreferences.value?.freeDriveAutoStart
Expand All @@ -60,7 +49,7 @@ class SettingsInteractor @Inject constructor(
if (authInteractor.isUserSignedIn) {
preferencesRoomDataSource.setFreeDriveAutoStart(authInteractor.userUUID!!, value)
} else {
updateAppPreferences { it.copy(freeDriveAutoStart = value) }
appSettingsDataSource.updateAppPreferences { it.copy(freeDriveAutoStart = value) }
}
}
}
Expand All @@ -77,7 +66,7 @@ class SettingsInteractor @Inject constructor(
preferencesRoomDataSource.setAnalyticsEnabled(authInteractor.userUUID!!, value)
} else {
Timber.v("Setting Analytics $value for offline user")
updateAppPreferences {
appSettingsDataSource.updateAppPreferences {
it.copy(
analyticsEnabled = value,
lastUpdateToAnalytics = ZonedDateTime.now()
Expand All @@ -97,7 +86,7 @@ class SettingsInteractor @Inject constructor(
if (authInteractor.isUserSignedIn) {
preferencesRoomDataSource.setShowAds(authInteractor.userUUID!!, value)
} else {
updateAppPreferences {
appSettingsDataSource.updateAppPreferences {
it.copy(showAds = value)
}
}
Expand All @@ -124,22 +113,9 @@ class SettingsInteractor @Inject constructor(
val localUserPreferences = preferencesResolver.localData
val userPreferences = preferencesResolver.data

suspend fun updateAppSettings(transform: (AppSettings) -> AppSettings) {
appSettingsDataStore.updateData {
val newSettings = transform(it)
Timber.v("Changed settings from $it to $newSettings")
newSettings
}
}

/**
* Automatically updates [DomainPreferences.lastUpdate]
*/
suspend fun updateAppPreferences(transform: (DomainPreferences) -> DomainPreferences) {
updateAppSettings {
it.copy(preferences = transform(it.preferences).copy(lastUpdate = ZonedDateTime.now()))
}
}

fun deleteLocalUserPreferences() {
if (authInteractor.isUserSignedIn) {
Expand Down
24 changes: 11 additions & 13 deletions app/src/main/java/illyan/jay/ui/freedrive/FreeDrive.kt
Original file line number Diff line number Diff line change
Expand Up @@ -120,20 +120,18 @@ fun FreeDrive(
onDispose {
viewModel.disposeViewport()
viewModel.lastLocation.value?.let { location ->
flyToLocation(
extraCameraOptions = { builder ->
builder
.pitch(0.0)
.bearing(0.0)
.zoom(12.0)
.center(
Point.fromLngLat(
location.longitude,
location.latitude
)
flyToLocation {
it
.pitch(0.0)
.bearing(0.0)
.zoom(12.0)
.center(
Point.fromLngLat(
location.longitude,
location.latitude
)
}
)
)
}
}
}
}
Expand Down
Loading

0 comments on commit 3ec32db

Please sign in to comment.