Skip to content

Commit c017eca

Browse files
authored
Merge pull request #141 from CrisisCleanup/transfer-org
Transfer org
2 parents 713e2fa + 50ba751 commit c017eca

File tree

61 files changed

+1092
-355
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+1092
-355
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ plugins {
1414

1515
android {
1616
defaultConfig {
17-
val buildVersion = 264
17+
val buildVersion = 268
1818
applicationId = "com.crisiscleanup"
1919
versionCode = buildVersion
2020
versionName = "0.9.${buildVersion - 168}"

app/src/main/java/com/crisiscleanup/MainActivity.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.crisiscleanup
22

33
import android.content.Intent
4+
import android.graphics.Color
45
import android.os.Bundle
56
import androidx.activity.ComponentActivity
67
import androidx.activity.SystemBarStyle
@@ -15,7 +16,6 @@ import androidx.compose.runtime.CompositionLocalProvider
1516
import androidx.compose.runtime.getValue
1617
import androidx.compose.runtime.mutableStateOf
1718
import androidx.compose.runtime.setValue
18-
import androidx.compose.ui.graphics.toArgb
1919
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
2020
import androidx.lifecycle.DefaultLifecycleObserver
2121
import androidx.lifecycle.Lifecycle
@@ -38,7 +38,6 @@ import com.crisiscleanup.core.data.repository.EndOfLifeRepository
3838
import com.crisiscleanup.core.data.repository.LanguageTranslationsRepository
3939
import com.crisiscleanup.core.data.repository.LocalAppMetricsRepository
4040
import com.crisiscleanup.core.designsystem.theme.CrisisCleanupTheme
41-
import com.crisiscleanup.core.designsystem.theme.navigationContainerColor
4241
import com.crisiscleanup.core.model.data.DarkThemeConfig
4342
import com.crisiscleanup.sync.initializers.scheduleSyncWorksites
4443
import com.crisiscleanup.ui.CrisisCleanupApp
@@ -127,10 +126,9 @@ class MainActivity : ComponentActivity() {
127126
windowSizeClass = windowSizeClass,
128127
)
129128

130-
val barColor = navigationContainerColor.toArgb()
131129
enableEdgeToEdge(
132-
statusBarStyle = SystemBarStyle.dark(barColor),
133-
navigationBarStyle = SystemBarStyle.dark(barColor),
130+
statusBarStyle = SystemBarStyle.dark(Color.TRANSPARENT),
131+
navigationBarStyle = SystemBarStyle.dark(Color.TRANSPARENT),
134132
)
135133

136134
CompositionLocalProvider {

app/src/main/java/com/crisiscleanup/MainActivityViewModel.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.crisiscleanup.core.common.log.Logger
2121
import com.crisiscleanup.core.common.network.CrisisCleanupDispatchers.IO
2222
import com.crisiscleanup.core.common.network.Dispatcher
2323
import com.crisiscleanup.core.common.sync.SyncPuller
24+
import com.crisiscleanup.core.common.sync.SyncPusher
2425
import com.crisiscleanup.core.common.throttleLatest
2526
import com.crisiscleanup.core.data.IncidentSelector
2627
import com.crisiscleanup.core.data.repository.AccountDataRefresher
@@ -67,6 +68,7 @@ class MainActivityViewModel @Inject constructor(
6768
val tutorialViewTracker: TutorialViewTracker,
6869
val translator: KeyResourceTranslator,
6970
private val syncPuller: SyncPuller,
71+
private val syncPusher: SyncPusher,
7072
appSettingsProvider: AppSettingsProvider,
7173
private val appEnv: AppEnv,
7274
firebaseAnalytics: FirebaseAnalytics,
@@ -205,6 +207,8 @@ class MainActivityViewModel @Inject constructor(
205207
syncPuller.appPullLanguage()
206208
syncPuller.appPullStatuses()
207209

210+
syncPusher.scheduleSyncMedia()
211+
208212
accountDataRepository.accountData
209213
.mapLatest { it.hasAcceptedTerms }
210214
.filter { !it }
@@ -263,10 +267,9 @@ class MainActivityViewModel @Inject constructor(
263267
return
264268
}
265269

266-
if (isUpdatingTermsAcceptance.value) {
270+
if (!isUpdatingTermsAcceptance.compareAndSet(expect = false, update = true)) {
267271
return
268272
}
269-
isUpdatingTermsAcceptance.value = true
270273
viewModelScope.launch(ioDispatcher) {
271274
try {
272275
val isAccepted = accountUpdateRepository.acceptTerms()

app/src/main/java/com/crisiscleanup/navigation/CrisisCleanupAuthNavHost.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ fun CrisisCleanupAuthNavHost(
5858
navController.navigateToLoginWithPhone()
5959
}
6060
}
61+
val navToLogin = navController::popToAuth
62+
val navToForgotPasswordClearStack = remember(navController) {
63+
{
64+
navController.popToAuth()
65+
navController.navigateToForgotPassword()
66+
}
67+
}
6168

6269
NavHost(
6370
navController = navController,
@@ -110,8 +117,11 @@ fun CrisisCleanupAuthNavHost(
110117
closeAuthentication = closeAuthentication,
111118
)
112119
requestAccessScreen(
120+
false,
113121
onBack = onBack,
114122
closeRequestAccess = navToLoginWithEmail,
123+
openAuth = navToAuth,
124+
openForgotPassword = navToForgotPasswordClearStack,
115125
)
116126
orgPersistentInviteScreen(
117127
onBack = onBack,

app/src/main/java/com/crisiscleanup/navigation/CrisisCleanupNavHost.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import com.crisiscleanup.core.appnav.navigateToExistingCase
2222
import com.crisiscleanup.core.data.model.ExistingWorksiteIdentifier
2323
import com.crisiscleanup.core.model.data.EmptyIncident
2424
import com.crisiscleanup.core.model.data.EmptyWorksite
25+
import com.crisiscleanup.feature.authentication.navigation.requestAccessScreen
2526
import com.crisiscleanup.feature.authentication.navigation.resetPasswordScreen
2627
import com.crisiscleanup.feature.caseeditor.navigation.caseAddFlagScreen
2728
import com.crisiscleanup.feature.caseeditor.navigation.caseEditMoveLocationOnMapScreen
@@ -246,5 +247,13 @@ fun CrisisCleanupNavHost(
246247
onBack = onBack,
247248
closeResetPassword = onBack,
248249
)
250+
251+
requestAccessScreen(
252+
true,
253+
onBack = onBack,
254+
closeRequestAccess = onBack,
255+
openAuth = {},
256+
openForgotPassword = {},
257+
)
249258
}
250259
}

app/src/main/java/com/crisiscleanup/ui/AppNavigation.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import androidx.compose.runtime.Composable
1515
import androidx.compose.runtime.getValue
1616
import androidx.compose.runtime.remember
1717
import androidx.compose.ui.Modifier
18-
import androidx.compose.ui.graphics.Color
1918
import androidx.compose.ui.graphics.vector.ImageVector
2019
import androidx.compose.ui.platform.testTag
2120
import androidx.compose.ui.res.painterResource
@@ -42,17 +41,18 @@ private fun TopLevelDestination.Icon(isSelected: Boolean, description: String) {
4241
} else {
4342
unselectedIcon
4443
}
44+
var tint = LocalContentColor.current
45+
if (!isSelected) {
46+
tint = tint.disabledAlpha()
47+
}
4548
when (icon) {
4649
is Icon.ImageVectorIcon -> Icon(
4750
imageVector = icon.imageVector,
4851
contentDescription = description,
52+
tint = tint,
4953
)
5054

5155
is Icon.DrawableResourceIcon -> {
52-
var tint = LocalContentColor.current
53-
if (isSelected) {
54-
tint = Color.White
55-
}
5656
Icon(
5757
painter = painterResource(id = icon.id),
5858
contentDescription = description,

app/src/main/java/com/crisiscleanup/ui/CrisisCleanupApp.kt

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import androidx.compose.animation.AnimatedVisibility
44
import androidx.compose.animation.animateContentSize
55
import androidx.compose.animation.slideIn
66
import androidx.compose.animation.slideOut
7+
import androidx.compose.foundation.background
78
import androidx.compose.foundation.layout.Box
89
import androidx.compose.foundation.layout.BoxScope
910
import androidx.compose.foundation.layout.Column
@@ -58,6 +59,7 @@ import com.crisiscleanup.core.designsystem.component.CrisisCleanupAlertDialog
5859
import com.crisiscleanup.core.designsystem.component.CrisisCleanupBackground
5960
import com.crisiscleanup.core.designsystem.component.CrisisCleanupTextButton
6061
import com.crisiscleanup.core.designsystem.theme.LocalDimensions
62+
import com.crisiscleanup.core.designsystem.theme.navigationContainerColor
6163
import com.crisiscleanup.core.model.data.TutorialViewId
6264
import com.crisiscleanup.core.ui.AppLayoutArea
6365
import com.crisiscleanup.core.ui.LayoutSizePosition
@@ -142,6 +144,8 @@ private fun BoxScope.LoadedContent(
142144
var openAuthentication by rememberSaveable { mutableStateOf(isNotAuthenticatedState) }
143145

144146
val showPasswordReset by viewModel.showPasswordReset.collectAsStateWithLifecycle(false)
147+
val orgUserInviteCode by viewModel.orgUserInvites.collectAsStateWithLifecycle("")
148+
val showOrgInviteTransfer = orgUserInviteCode.isNotBlank()
145149

146150
if (openAuthentication ||
147151
isNotAuthenticatedState
@@ -158,19 +162,22 @@ private fun BoxScope.LoadedContent(
158162

159163
if (isNotAuthenticatedState) {
160164
val showMagicLinkLogin by viewModel.showMagicLinkLogin.collectAsStateWithLifecycle(false)
161-
val orgUserInviteCode by viewModel.orgUserInvites.collectAsStateWithLifecycle("")
162165
val orgPersistentInvite by viewModel.orgPersistentInvites.collectAsStateWithLifecycle()
163166

164-
if (showPasswordReset) {
165-
LaunchedEffect(Unit) {
166-
appState.navController.navigateToPasswordReset(false)
167+
with(appState.navController) {
168+
if (showPasswordReset) {
169+
LaunchedEffect(Unit) {
170+
navigateToPasswordReset(false)
171+
}
172+
} else if (showMagicLinkLogin) {
173+
navigateToMagicLinkLogin()
174+
} else if (showOrgInviteTransfer) {
175+
LaunchedEffect(Unit) {
176+
navigateToRequestAccess(orgUserInviteCode, false)
177+
}
178+
} else if (orgPersistentInvite.isValidInvite) {
179+
navigateToOrgPersistentInvite()
167180
}
168-
} else if (showMagicLinkLogin) {
169-
appState.navController.navigateToMagicLinkLogin()
170-
} else if (orgUserInviteCode.isNotBlank()) {
171-
appState.navController.navigateToRequestAccess(orgUserInviteCode)
172-
} else if (orgPersistentInvite.isValidInvite) {
173-
appState.navController.navigateToOrgPersistentInvite()
174181
}
175182
}
176183
} else if (!hasAcceptedTerms) {
@@ -225,9 +232,15 @@ private fun BoxScope.LoadedContent(
225232
}
226233
}
227234

228-
if (showPasswordReset) {
229-
LaunchedEffect(Unit) {
230-
appState.navController.navigateToPasswordReset(true)
235+
with(appState.navController) {
236+
if (showPasswordReset) {
237+
LaunchedEffect(Unit) {
238+
navigateToPasswordReset(true)
239+
}
240+
} else if (showOrgInviteTransfer) {
241+
LaunchedEffect(Unit) {
242+
navigateToRequestAccess(orgUserInviteCode, true)
243+
}
231244
}
232245
}
233246
}
@@ -340,9 +353,11 @@ private fun NavigableContent(
340353
}
341354

342355
Scaffold(
343-
modifier = Modifier.semantics {
344-
testTagsAsResourceId = true
345-
},
356+
modifier = Modifier
357+
.background(navigationContainerColor)
358+
.semantics {
359+
testTagsAsResourceId = true
360+
},
346361
containerColor = Color.Transparent,
347362
contentColor = MaterialTheme.colorScheme.onBackground,
348363
contentWindowInsets = WindowInsets(0, 0, 0, 0),
@@ -396,7 +411,7 @@ private fun NavigableContent(
396411
}
397412

398413
val isKeyboardOpen = rememberIsKeyboardOpen()
399-
Column {
414+
Column(Modifier.background(Color.White)) {
400415
val snackbarAreaHeight =
401416
if (!showNavigation &&
402417
snackbarHostState.currentSnackbarData != null &&

build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
2828
import org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension
2929
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
3030

31-
internal const val DefaultConfigTargetSdk = 35
31+
internal const val DefaultConfigTargetSdk = 36
3232

3333
/**
3434
* Configure base Kotlin with Android options

core/appnav/src/main/java/com/crisiscleanup/core/appnav/RouteConstant.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ object RouteConstant {
1212
const val AUTH_RESET_PASSWORD_ROUTE = "$AUTH_ROUTE/auth_reset_password_route"
1313
const val MAGIC_LINK_ROUTE = "$AUTH_ROUTE/magic_link_login"
1414

15-
const val REQUEST_ACCESS_ROUTE = "$AUTH_ROUTE/request_access"
15+
const val AUTH_REQUEST_ACCESS_ROUTE = "$AUTH_ROUTE/request_access"
1616
const val ORG_PERSISTENT_INVITE_ROUTE = "$AUTH_ROUTE/org_persistent_invite"
1717

1818
const val VOLUNTEER_ORG_ROUTE = "$AUTH_ROUTE/volunteer_org"
@@ -51,6 +51,7 @@ object RouteConstant {
5151
const val WORKSITE_IMAGES_ROUTE = "worksite_images"
5252

5353
const val ACCOUNT_RESET_PASSWORD_ROUTE = "account_reset_password_route"
54+
const val ACCOUNT_TRANSFER_ORG_ROUTE = "account_transfer_org"
5455

5556
const val LISTS_ROUTE = "crisis_cleanup_lists"
5657
const val VIEW_LIST_ROUTE = "view_list"

core/data/src/main/java/com/crisiscleanup/core/data/repository/AccountUpdateRepository.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ interface AccountUpdateRepository {
1616
suspend fun initiatePasswordReset(emailAddress: String): PasswordResetInitiation
1717
suspend fun changePassword(password: String, token: String): Boolean
1818
suspend fun acceptTerms(): Boolean
19+
suspend fun acceptOrganizationChange(
20+
action: ChangeOrganizationAction,
21+
invitationToken: String,
22+
): Boolean
1923
}
2024

2125
class CrisisCleanupAccountUpdateRepository @Inject constructor(
@@ -75,4 +79,21 @@ class CrisisCleanupAccountUpdateRepository @Inject constructor(
7579
}
7680
return false
7781
}
82+
83+
override suspend fun acceptOrganizationChange(
84+
action: ChangeOrganizationAction,
85+
invitationToken: String,
86+
): Boolean {
87+
try {
88+
return accountApi.moveToOrganization(action.literal, invitationToken)
89+
} catch (e: Exception) {
90+
logger.logException(e)
91+
}
92+
return false
93+
}
94+
}
95+
96+
enum class ChangeOrganizationAction(val literal: String) {
97+
All("all"),
98+
Users("users"),
7899
}

0 commit comments

Comments
 (0)