Skip to content

Commit

Permalink
Merge pull request #96 from HLCaptain/94-final-fixups-for-release
Browse files Browse the repository at this point in the history
94 final fixups for release
  • Loading branch information
HLCaptain authored Apr 8, 2023
2 parents 0727d6b + 1b756d4 commit 0d22185
Show file tree
Hide file tree
Showing 37 changed files with 1,398 additions and 487 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ android {
applicationId "illyan.jay"
minSdk 21
targetSdk 33
versionCode 9
versionName "0.2.6-alpha"
versionCode 10
versionName "0.2.7-alpha"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/illyan/jay/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import com.ramcosta.composedestinations.DestinationsNavHost
import dagger.hilt.android.AndroidEntryPoint
import illyan.jay.domain.interactor.AuthInteractor
import illyan.jay.ui.NavGraphs
import illyan.jay.ui.components.PreviewLightDarkTheme
import illyan.jay.ui.components.PreviewThemesScreensFonts
import illyan.jay.ui.theme.JayTheme
import javax.inject.Inject

Expand Down Expand Up @@ -65,7 +65,7 @@ class MainActivity : AppCompatActivity() {
}
}

@PreviewLightDarkTheme
@PreviewThemesScreensFonts
@Composable
fun MainScreen(
modifier: Modifier = Modifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,9 @@
* If not, see <https://www.gnu.org/licenses/>.
*/

package illyan.jay.ui.components
package illyan.jay.data

import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.ui.tooling.preview.Preview

@Preview(
name = "Dark theme",
group = "themes",
uiMode = UI_MODE_NIGHT_YES,
showBackground = true,
)
@Preview(
name = "Light theme",
group = "themes",
showBackground = true,
data class DataStatus<T>(
val data: T? = null,
val isLoading: Boolean? = null
)
annotation class PreviewLightDarkTheme
16 changes: 16 additions & 0 deletions app/src/main/java/illyan/jay/data/network/Mapping.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ import com.google.firebase.Timestamp
import com.google.firebase.firestore.Blob
import com.google.firebase.firestore.DocumentSnapshot
import illyan.jay.BuildConfig
import illyan.jay.data.DataStatus
import illyan.jay.data.network.model.FirestoreLocation
import illyan.jay.data.network.model.FirestorePath
import illyan.jay.data.network.model.FirestoreSession
import illyan.jay.data.network.model.FirestoreUser
import illyan.jay.data.network.model.FirestoreUserPreferences
import illyan.jay.data.network.serializers.TimestampSerializer
import illyan.jay.domain.model.DomainLocation
Expand Down Expand Up @@ -379,3 +381,17 @@ fun FirestoreLocation.toDomainModel(
speedAccuracy = speedAccuracy,
verticalAccuracy = verticalAccuracy.toShort()
)

fun DataStatus<FirestoreUser>.toDomainPreferencesStatus(): DataStatus<DomainPreferences> {
return DataStatus(
data = data?.run { preferences?.toDomainModel(uuid) },
isLoading = isLoading
)
}

fun DataStatus<FirestoreUser>.toDomainSessionsStatus(): DataStatus<List<DomainSession>> {
return DataStatus(
data = data?.run { sessions.map { it.toDomainModel(uuid) } },
isLoading = isLoading
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,18 @@ package illyan.jay.data.network.datasource
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.SetOptions
import com.google.firebase.firestore.WriteBatch
import illyan.jay.data.DataStatus
import illyan.jay.data.network.model.FirestoreUser
import illyan.jay.data.network.toDomainModel
import illyan.jay.data.network.toDomainPreferencesStatus
import illyan.jay.data.network.toFirestoreModel
import illyan.jay.di.CoroutineScopeIO
import illyan.jay.domain.interactor.AuthInteractor
import illyan.jay.domain.model.DomainPreferences
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import timber.log.Timber
import javax.inject.Inject
Expand All @@ -41,41 +43,48 @@ class PreferencesNetworkDataSource @Inject constructor(
private val userNetworkDataSource: UserNetworkDataSource,
@CoroutineScopeIO private val coroutineScopeIO: CoroutineScope
) {
val isLoading = userNetworkDataSource.isLoading
val isLoadingFromCloud = userNetworkDataSource.isLoadingFromCloud

val preferences: StateFlow<DomainPreferences?> by lazy {
combine(
userNetworkDataSource.user,
userNetworkDataSource.isLoading
) { user, loading ->
if (user?.preferences != null) {
val preferences = user.preferences.toDomainModel(userUUID = user.uuid)
Timber.d("Firebase got preferences for user ${user.uuid.take(4)}")
preferences
} else if (loading) {
null
} else {
null
val preferences: StateFlow<DataStatus<DomainPreferences>> by lazy {
userNetworkDataSource.userStatus.map { userStatus ->
val status = resolvePreferencesFromStatus(userStatus)
status.data?.let {
Timber.d("Firebase got preferences for user ${it.userUUID?.take(4)}")
}
}.stateIn(coroutineScopeIO, SharingStarted.Eagerly, null)
status
}.stateIn(
coroutineScopeIO,
SharingStarted.Eagerly,
userNetworkDataSource.userStatus.value.toDomainPreferencesStatus()
)
}

val cloudPreferences: StateFlow<DomainPreferences?> by lazy {
combine(
userNetworkDataSource.cloudUser,
userNetworkDataSource.isLoadingFromCloud
) { user, loading ->
if (user?.preferences != null) {
val preferences = user.preferences.toDomainModel(userUUID = user.uuid)
Timber.d("Firebase got cloud preferences for user ${user.uuid.take(4)}")
preferences
} else if (loading) {
null
} else {
null
val cloudPreferencesStatus: StateFlow<DataStatus<DomainPreferences>> by lazy {
userNetworkDataSource.cloudUserStatus.map { userStatus ->
val status = resolvePreferencesFromStatus(userStatus)
status.data?.let {
Timber.d("Firebase got cloud preferences for user ${it.userUUID?.take(4)}")
}
}.stateIn(coroutineScopeIO, SharingStarted.Eagerly, null)
status
}.stateIn(
coroutineScopeIO,
SharingStarted.Eagerly,
userNetworkDataSource.cloudUserStatus.value.toDomainPreferencesStatus()
)
}

private fun resolvePreferencesFromStatus(
status: DataStatus<FirestoreUser>
): DataStatus<DomainPreferences> {
val user = status.data
val loading = status.isLoading
val preferences = if (user?.preferences != null) {
val userPreferences = user.preferences.toDomainModel(userUUID = user.uuid)
userPreferences
} else if (loading != false) { // If loading or not initialized
null
} else {
null
}
return DataStatus(data = preferences, isLoading = loading)
}

fun setPreferences(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,19 @@ import com.google.firebase.firestore.FieldValue
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.SetOptions
import com.google.firebase.firestore.WriteBatch
import illyan.jay.data.DataStatus
import illyan.jay.data.network.model.FirestoreUser
import illyan.jay.data.network.toDomainModel
import illyan.jay.data.network.toDomainSessionsStatus
import illyan.jay.data.network.toFirestoreModel
import illyan.jay.di.CoroutineScopeIO
import illyan.jay.domain.interactor.AuthInteractor
import illyan.jay.domain.model.DomainSession
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import timber.log.Timber
Expand All @@ -46,24 +48,55 @@ class SessionNetworkDataSource @Inject constructor(
private val userNetworkDataSource: UserNetworkDataSource,
@CoroutineScopeIO private val coroutineScopeIO: CoroutineScope
) {
val sessions: StateFlow<List<DomainSession>?> by lazy {
combine(
userNetworkDataSource.user,
userNetworkDataSource.isLoading
) { user, loading ->
if (user != null) {
val domainSessions = user.sessions.map { it.toDomainModel(user.uuid) }
Timber.d("Firebase got sessions with IDs: ${domainSessions.map { it.uuid.take(4) }}")
domainSessions
} else if (loading) {
null
} else {
emptyList()
val sessionsStatus: StateFlow<DataStatus<List<DomainSession>>> by lazy {
userNetworkDataSource.userStatus.map { userStatus ->
val status = resolveSessionsFromStatus(userStatus)
status.data?.let { sessions ->
Timber.d("Firebase got sessions with IDs: ${sessions.map { it.uuid.take(4) }}")
}
}.stateIn(coroutineScopeIO, SharingStarted.Eagerly, null)
status
}.stateIn(
coroutineScopeIO,
SharingStarted.Eagerly,
userNetworkDataSource.userStatus.value.toDomainSessionsStatus()
)
}

val cloudSessionsStatus: StateFlow<DataStatus<List<DomainSession>>> by lazy {
userNetworkDataSource.cloudUserStatus.map { userStatus ->
val status = resolveSessionsFromStatus(userStatus)
status.data?.let { sessions ->
Timber.d("Firebase got sessions with IDs: ${sessions.map { it.uuid.take(4) }}")
}
status
}.stateIn(
coroutineScopeIO,
SharingStarted.Eagerly,
userNetworkDataSource.cloudUserStatus.value.toDomainSessionsStatus()
)
}

// FIXME: may user `lazy` more often or change SharingStarted to Lazily instead of Eagerly
val sessions = sessionsStatus.map { it.data }
.stateIn(coroutineScopeIO, SharingStarted.Eagerly, sessionsStatus.value.data)

val cloudSessions = cloudSessionsStatus.map { it.data }
.stateIn(coroutineScopeIO, SharingStarted.Eagerly, cloudSessionsStatus.value.data)

private fun resolveSessionsFromStatus(
status: DataStatus<FirestoreUser>
): DataStatus<List<DomainSession>> {
val user = status.data
val loading = status.isLoading
val sessions = if (user != null) {
val domainSessions = user.sessions.map { it.toDomainModel(user.uuid) }
domainSessions
} else if (loading != false) { // If loading or not initialized
null
} else {
emptyList()
}
return DataStatus(data = sessions, isLoading = loading)
}

fun deleteSession(
sessionUUID: String,
Expand All @@ -77,25 +110,36 @@ class SessionNetworkDataSource @Inject constructor(
onSuccess = onSuccess,
)

fun deleteAllSessions(
suspend fun deleteAllSessions(
onFailure: (Exception) -> Unit = { Timber.e(it, "Error while deleting sessions: ${it.message}") },
onCancel: () -> Unit = { Timber.i("Deleting sessions canceled") },
onSuccess: () -> Unit = { Timber.i("Deleted sessions") }
) = deleteSessions(
sessionUUIDs = userNetworkDataSource.user.value?.sessions?.map { it.uuid } ?: emptyList(),
onFailure = onFailure,
onCancel = onCancel,
onSuccess = onSuccess,
)
) {
cloudSessions.first { sessions ->
deleteSessions(
sessionUUIDs = sessions?.map { it.uuid } ?: emptyList(),
onFailure = onFailure,
onCancel = onCancel,
onSuccess = onSuccess,
)
true
}

}

fun deleteAllSessions(
suspend fun deleteAllSessions(
batch: WriteBatch,
onWriteFinished: () -> Unit = {}
) = deleteSessions(
batch = batch,
sessionUUIDs = userNetworkDataSource.user.value?.sessions?.map { it.uuid } ?: emptyList(),
onWriteFinished = onWriteFinished,
)
) {
cloudSessions.first { sessions ->
deleteSessions(
batch = batch,
sessionUUIDs = sessions?.map { it.uuid } ?: emptyList(),
onWriteFinished = onWriteFinished,
)
true
}
}

@JvmName("deleteSessionsByUUIDs")
fun deleteSessions(
Expand All @@ -104,7 +148,10 @@ class SessionNetworkDataSource @Inject constructor(
userUUID: String = authInteractor.userUUID.toString(),
onWriteFinished: () -> Unit = {}
) {
if (!authInteractor.isUserSignedIn || sessionUUIDs.isEmpty()) return
if (!authInteractor.isUserSignedIn || sessionUUIDs.isEmpty()) {
onWriteFinished()
return
}
coroutineScopeIO.launch {
userNetworkDataSource.user.first { user ->
user?.let {
Expand Down Expand Up @@ -151,7 +198,10 @@ class SessionNetworkDataSource @Inject constructor(
userUUID: String = authInteractor.userUUID.toString(),
onWriteFinished: () -> Unit = {}
) {
if (!authInteractor.isUserSignedIn || domainSessions.isEmpty()) return
if (!authInteractor.isUserSignedIn || domainSessions.isEmpty()) {
onWriteFinished()
return
}
val userRef = firestore
.collection(FirestoreUser.CollectionName)
.document(userUUID)
Expand All @@ -171,7 +221,10 @@ class SessionNetworkDataSource @Inject constructor(
onCancel: () -> Unit = { Timber.i("Deleting ${domainSessions.size} sessions canceled") },
onSuccess: () -> Unit = { Timber.i("Deleted ${domainSessions.size} sessions") }
) {
if (!authInteractor.isUserSignedIn || domainSessions.isEmpty()) return
if (!authInteractor.isUserSignedIn || domainSessions.isEmpty()) {
onSuccess()
return
}
firestore.runBatch { batch ->
deleteSessions(
batch = batch,
Expand Down
Loading

0 comments on commit 0d22185

Please sign in to comment.