Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ plugins {

android {
defaultConfig {
val buildVersion = 250
val buildVersion = 252
applicationId = "com.crisiscleanup"
versionCode = buildVersion
versionName = "0.9.${buildVersion - 168}"
Expand Down
8 changes: 7 additions & 1 deletion app/src/main/java/com/crisiscleanup/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,12 @@ class MainActivity : ComponentActivity() {
logUnprocessedExternalUri(it)
}
}

if (savedInstanceState == null) {
lifecycleScope.launch {
appMetricsRepository.setAppOpen()
}
}
}

override fun onNewIntent(intent: Intent) {
Expand All @@ -185,7 +191,7 @@ class MainActivity : ComponentActivity() {
super.onStart()
syncPuller.appPullIncidentData()
visualAlertManager.setNonProductionAppAlert(true)
viewModel.onAppOpen()
viewModel.onAppFocus()

endOfLifeRepository.saveEndOfLifeData()
appMetricsRepository.saveAppSupportInfo()
Expand Down
31 changes: 3 additions & 28 deletions app/src/main/java/com/crisiscleanup/MainActivityViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.crisiscleanup.core.common.AppEnv
import com.crisiscleanup.core.common.AppSettingsProvider
import com.crisiscleanup.core.common.AppVersionProvider
import com.crisiscleanup.core.common.CrisisCleanupTutorialDirectors.Menu
import com.crisiscleanup.core.common.KeyResourceTranslator
import com.crisiscleanup.core.common.NetworkMonitor
Expand All @@ -33,7 +32,6 @@ import com.crisiscleanup.core.data.repository.LocalAppPreferencesRepository
import com.crisiscleanup.core.data.repository.ShareLocationRepository
import com.crisiscleanup.core.model.data.AccountData
import com.crisiscleanup.core.model.data.AppMetricsData
import com.crisiscleanup.core.model.data.AppOpenInstant
import com.crisiscleanup.core.model.data.EmptyIncident
import com.crisiscleanup.core.model.data.MinSupportedAppVersion
import com.crisiscleanup.core.model.data.UserData
Expand All @@ -54,10 +52,7 @@ import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import java.util.concurrent.atomic.AtomicReference
import javax.inject.Inject
import kotlin.time.Duration.Companion.hours

@HiltViewModel
class MainActivityViewModel @Inject constructor(
Expand All @@ -72,7 +67,6 @@ class MainActivityViewModel @Inject constructor(
val tutorialViewTracker: TutorialViewTracker,
val translator: KeyResourceTranslator,
private val syncPuller: SyncPuller,
private val appVersionProvider: AppVersionProvider,
appSettingsProvider: AppSettingsProvider,
private val appEnv: AppEnv,
firebaseAnalytics: FirebaseAnalytics,
Expand All @@ -83,25 +77,15 @@ class MainActivityViewModel @Inject constructor(
@Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher,
@Logger(CrisisCleanupLoggers.App) private val logger: AppLogger,
) : ViewModel() {
/**
* Previous app open
*
* Sets only once every app session.
*/
private val initialAppOpen = AtomicReference<AppOpenInstant>(null)

val viewState = combine(
appPreferencesRepository.userPreferences,
appMetricsRepository.metrics.distinctUntilChanged(),
::Pair,
)
.map { (preferences, metrics) ->
if (initialAppOpen.compareAndSet(null, metrics.appOpen)) {
onAppOpen()
}

MainActivityViewState.Success(preferences, metrics)
}.stateIn(
}
.stateIn(
scope = viewModelScope,
initialValue = MainActivityViewState.Loading,
started = SharingStarted.WhileSubscribed(5_000),
Expand Down Expand Up @@ -258,16 +242,7 @@ class MainActivityViewModel @Inject constructor(
)
}

fun onAppOpen() {
initialAppOpen.get()?.let {
viewModelScope.launch {
val previousOpen = appMetricsRepository.metrics.first().appOpen
if (Clock.System.now() - previousOpen.date > 1.hours) {
appMetricsRepository.setAppOpen(appVersionProvider.versionCode)
}
}
}

fun onAppFocus() {
viewModelScope.launch(ioDispatcher) {
shareLocationWithOrganization()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ fun CrisisCleanupNavHost(
modifier = modifier,
) {
casesGraph(
navController,
nestedGraphs = {
casesSearchScreen(searchCasesOnBack, viewCase)
casesFilterScreen(onBack)
Expand Down Expand Up @@ -217,6 +218,7 @@ fun CrisisCleanupNavHost(
openViewTeam = navController::navigateToViewTeam,
)
menuScreen(
navController,
openAuthentication = openAuthentication,
openIncidentCache = openIncidentCache,
openLists = openLists,
Expand Down
3 changes: 3 additions & 0 deletions core/appnav/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.nowinandroid.android.library)
alias(libs.plugins.nowinandroid.android.library.compose)
alias(libs.plugins.nowinandroid.android.library.jacoco)
}

Expand All @@ -9,6 +10,8 @@ android {

dependencies {
implementation(projects.core.common)

implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.lifecycle.viewModelCompose)
implementation(libs.androidx.navigation.compose)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package com.crisiscleanup.core.appnav

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
import com.crisiscleanup.core.appnav.RouteConstant.VIEW_CASE_ROUTE

Expand All @@ -10,3 +15,15 @@ fun NavController.navigateToExistingCase(incidentId: Long, worksiteId: Long) {
// Must match composable route signature
this.navigate("${VIEW_CASE_ROUTE}?$INCIDENT_ID_ARG=$incidentId&$WORKSITE_ID_ARG=$worksiteId")
}

// https://medium.com/@mahbooberezaee68/retrieving-viewmodels-within-navigation-graphs-jetpack-compose-4eb5a1293d25
@Composable
inline fun <reified T : ViewModel> NavBackStackEntry.sharedViewModel(
navController: NavController,
navGraphRoute: String,
): T {
val parentEntry = remember(this) {
navController.getBackStackEntry(navGraphRoute)
}
return hiltViewModel(parentEntry)
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ class AndroidResourceTranslator @Inject constructor(
override fun translate(phraseKey: String, @StringRes fallbackResId: Int) =
keyTranslator.translate(phraseKey) ?: (
if (fallbackResId != 0) {
context.getString(
fallbackResId,
)
context.getString(fallbackResId)
} else {
phraseKey
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.crisiscleanup.core.designsystem.theme.disabledAlpha
import com.crisiscleanup.core.designsystem.theme.incidentDisasterContainerColor
import com.crisiscleanup.core.designsystem.theme.incidentDisasterContentColor
import com.crisiscleanup.core.model.data.Disaster
Expand Down Expand Up @@ -41,12 +42,17 @@ fun getDisasterIcon(disaster: Disaster) = statusIcons[disaster] ?: R.drawable.ic
fun DisasterIcon(
@DrawableRes disasterResId: Int,
incidentName: String,
enabled: Boolean,
modifier: Modifier = Modifier,
) {
Surface(
modifier = modifier,
shape = CircleShape,
color = incidentDisasterContainerColor,
color = if (enabled) {
incidentDisasterContainerColor
} else {
incidentDisasterContainerColor.disabledAlpha()
},
contentColor = incidentDisasterContentColor,
) {
Icon(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ fun IncidentDropdownSelect(
CompositionLocalProvider(
LocalContentColor provides if (enabled) contentColor else contentColor.disabledAlpha(),
) {
DisasterIcon(disasterIconResId, title)
DisasterIcon(disasterIconResId, title, enabled)
TruncatedAppBarText(
title = title,
modifier = Modifier.padding(start = 8.dp),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fun IncidentHeaderView(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = listItemSpacedBy,
) {
DisasterIcon(disasterResId, incidentName)
DisasterIcon(disasterResId, incidentName, true)
Text(
incidentName,
Modifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ fun NetworkWorksiteFull.asEntity() = WorksiteEntity(
caseNumber = caseNumber,
caseNumberOrder = parseCaseNumberOrder(caseNumber),
city = city,
county = county,
county = county ?: "",
email = email,
favoriteId = favorite?.id,
keyWorkTypeType = newestKeyWorkType?.workType ?: "",
Expand Down Expand Up @@ -56,7 +56,7 @@ fun NetworkWorksiteCoreData.asEntity() = WorksiteEntity(
caseNumber = caseNumber,
caseNumberOrder = parseCaseNumberOrder(caseNumber),
city = city,
county = county,
county = county ?: "",
email = email,
favoriteId = favorite?.id,
keyWorkTypeType = "",
Expand Down Expand Up @@ -84,7 +84,7 @@ fun NetworkWorksiteShort.asEntity() = WorksiteEntity(
caseNumber = caseNumber,
caseNumberOrder = parseCaseNumberOrder(caseNumber),
city = city,
county = county,
county = county ?: "",
createdAt = createdAt,
favoriteId = favoriteId,
keyWorkTypeType = newestKeyWorkType?.workType ?: "",
Expand Down Expand Up @@ -115,7 +115,7 @@ fun NetworkWorksitePage.asEntity() = WorksiteEntity(
caseNumber = caseNumber,
caseNumberOrder = parseCaseNumberOrder(caseNumber),
city = city,
county = county,
county = county ?: "",
createdAt = createdAt,
favoriteId = favoriteId,
keyWorkTypeType = newestKeyWorkType?.workType ?: "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ interface LocalAppMetricsRepository {
suspend fun setEarlybirdEnd(end: BuildEndOfLife)

suspend fun setAppOpen(
appVersion: Long,
timestamp: Instant = Clock.System.now(),
)

Expand All @@ -51,11 +50,8 @@ class AppMetricsRepository @Inject constructor(
dataSource.setEarlybirdEnd(end)
}

override suspend fun setAppOpen(
appVersion: Long,
timestamp: Instant,
) {
dataSource.setAppOpen(appVersion, timestamp)
override suspend fun setAppOpen(timestamp: Instant) {
dataSource.setAppOpen(timestamp)
}

override fun saveAppSupportInfo() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,8 @@ class AppPreferencesRepository @Inject constructor(
override suspend fun setTeamMapBounds(bounds: IncidentCoordinateBounds) {
preferencesDataSource.saveTeamMapBounds(bounds)
}

override suspend fun setWorkScreenView(isTableView: Boolean) {
preferencesDataSource.saveWorkScreenView(isTableView)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ class IncidentWorksitesCacheRepository @Inject constructor(
// TODO If not preloaded and times out try caching around coordinates
val shortResult = cacheWorksitesCore(
incidentId,
syncPlan.timestamp,
isPaused,
syncStats,
worksitesCoreStatsUpdater,
Expand All @@ -406,6 +407,7 @@ class IncidentWorksitesCacheRepository @Inject constructor(
) {
cacheWorksitesCore(
incidentId,
syncPlan.timestamp,
false,
syncStats,
worksitesCoreStatsUpdater,
Expand Down Expand Up @@ -465,6 +467,7 @@ class IncidentWorksitesCacheRepository @Inject constructor(

val additionalResult = cacheAdditionalWorksiteData(
incidentId,
syncPlan.timestamp,
isPaused,
syncStats,
worksitesAdditionalStatsUpdater,
Expand All @@ -476,6 +479,7 @@ class IncidentWorksitesCacheRepository @Inject constructor(
) {
cacheAdditionalWorksiteData(
incidentId,
syncPlan.timestamp,
false,
syncStats,
worksitesAdditionalStatsUpdater,
Expand Down Expand Up @@ -845,6 +849,7 @@ class IncidentWorksitesCacheRepository @Inject constructor(

private suspend fun cacheWorksitesCore(
incidentId: Long,
syncStart: Instant,
isPaused: Boolean,
syncParameters: IncidentDataSyncParameters,
statsUpdater: IncidentDataPullStatsUpdater,
Expand Down Expand Up @@ -888,6 +893,7 @@ class IncidentWorksitesCacheRepository @Inject constructor(
val afterResult = cacheWorksitesAfter(
IncidentCacheStage.WorksitesCore,
incidentId,
syncStart,
isPaused,
unmeteredDataCountThreshold = 9000,
timeMarkers,
Expand Down Expand Up @@ -1016,6 +1022,7 @@ class IncidentWorksitesCacheRepository @Inject constructor(
private suspend fun <T, U> cacheWorksitesAfter(
stage: IncidentCacheStage,
incidentId: Long,
syncStart: Instant,
isPaused: Boolean,
unmeteredDataCountThreshold: Int,
timeMarkers: IncidentDataSyncParameters.SyncTimeMarker,
Expand Down Expand Up @@ -1054,7 +1061,17 @@ class IncidentWorksitesCacheRepository @Inject constructor(
result.data ?: emptyList()
}

fun updateUpdatedAfter(timestamp: Instant) {
if (stage == IncidentCacheStage.WorksitesCore) {
syncParameterDao.updateUpdatedAfter(incidentId, timestamp)
} else {
syncParameterDao.updateAdditionalUpdatedAfter(incidentId, timestamp)
}
}

if (networkData.isEmpty()) {
updateUpdatedAfter(syncStart)

log("Cached $savedCount/$initialCount after. No Cases after $afterTimeMarker")
} else {
downloadSpeedTracker.averageSpeed()?.let {
Expand Down Expand Up @@ -1082,11 +1099,7 @@ class IncidentWorksitesCacheRepository @Inject constructor(
queryCount = (queryCount * 2).coerceAtMost(maxQueryCount)
afterTimeMarker = networkData.last().updatedAt

if (stage == IncidentCacheStage.WorksitesCore) {
syncParameterDao.updateUpdatedAfter(incidentId, afterTimeMarker)
} else {
syncParameterDao.updateAdditionalUpdatedAfter(incidentId, afterTimeMarker)
}
updateUpdatedAfter(afterTimeMarker)

log("Cached ${deduplicateWorksites.size} ($savedCount/$initialCount) after, up to $afterTimeMarker")
}
Expand Down Expand Up @@ -1152,6 +1165,7 @@ class IncidentWorksitesCacheRepository @Inject constructor(

private suspend fun cacheAdditionalWorksiteData(
incidentId: Long,
syncStart: Instant,
isPaused: Boolean,
syncParameters: IncidentDataSyncParameters,
statsUpdater: IncidentDataPullStatsUpdater,
Expand Down Expand Up @@ -1199,6 +1213,7 @@ class IncidentWorksitesCacheRepository @Inject constructor(
val afterResult = cacheWorksitesAfter(
IncidentCacheStage.WorksitesAdditional,
incidentId,
syncStart,
isPaused,
unmeteredDataCountThreshold = 3000,
timeMarkers,
Expand Down
Loading
Loading