-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3220 from CruGlobal/deleteAccount
GT-2178 Add Support for a user deleting their account
- Loading branch information
Showing
17 changed files
with
740 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
app/src/main/kotlin/org/cru/godtools/dagger/CircuitModule.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package org.cru.godtools.dagger | ||
|
||
import com.slack.circuit.foundation.Circuit | ||
import com.slack.circuit.runtime.presenter.Presenter | ||
import com.slack.circuit.runtime.ui.Ui | ||
import dagger.Module | ||
import dagger.Provides | ||
import dagger.Reusable | ||
import dagger.hilt.InstallIn | ||
import dagger.hilt.components.SingletonComponent | ||
import dagger.multibindings.Multibinds | ||
|
||
@Module | ||
@InstallIn(SingletonComponent::class) | ||
abstract class CircuitModule { | ||
@Multibinds | ||
abstract fun presenterFactories(): Set<Presenter.Factory> | ||
|
||
@Multibinds | ||
abstract fun uiFactories(): Set<Ui.Factory> | ||
|
||
companion object { | ||
@Provides | ||
@Reusable | ||
fun circuit( | ||
presenterFactories: Set<@JvmSuppressWildcards Presenter.Factory>, | ||
uiFactories: Set<@JvmSuppressWildcards Ui.Factory> | ||
) = Circuit.Builder() | ||
.addPresenterFactories(presenterFactories) | ||
.addUiFactories(uiFactories) | ||
.build() | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
app/src/main/kotlin/org/cru/godtools/ui/account/delete/DeleteAccountActivity.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package org.cru.godtools.ui.account.delete | ||
|
||
import android.content.Context | ||
import android.content.Intent | ||
import android.os.Bundle | ||
import androidx.activity.compose.setContent | ||
import com.slack.circuit.backstack.rememberSaveableBackStack | ||
import com.slack.circuit.foundation.Circuit | ||
import com.slack.circuit.foundation.CircuitCompositionLocals | ||
import com.slack.circuit.foundation.NavigableCircuitContent | ||
import com.slack.circuit.foundation.rememberCircuitNavigator | ||
import dagger.hilt.android.AndroidEntryPoint | ||
import javax.inject.Inject | ||
import org.cru.godtools.base.ui.activity.BaseActivity | ||
import org.cru.godtools.base.ui.theme.GodToolsTheme | ||
|
||
fun Context.startDeleteAccountActivity() = startActivity(Intent(this, DeleteAccountActivity::class.java)) | ||
|
||
@AndroidEntryPoint | ||
class DeleteAccountActivity : BaseActivity() { | ||
@Inject | ||
lateinit var circuit: Circuit | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
setContent { | ||
CircuitCompositionLocals(circuit) { | ||
GodToolsTheme { | ||
val backStack = rememberSaveableBackStack { push(DeleteAccountScreen) } | ||
val navigator = rememberCircuitNavigator(backStack) | ||
NavigableCircuitContent(navigator, backStack) | ||
} | ||
} | ||
} | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
app/src/main/kotlin/org/cru/godtools/ui/account/delete/DeleteAccountLayout+Preview.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package org.cru.godtools.ui.account.delete | ||
|
||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.tooling.preview.Preview | ||
import org.cru.godtools.base.ui.theme.GodToolsTheme | ||
|
||
@Preview | ||
@Composable | ||
private fun DeleteAccountLayoutDisplayPreview() { | ||
GodToolsTheme { DeleteAccountLayout(DeleteAccountScreen.State.Display { }) } | ||
} | ||
|
||
@Preview | ||
@Composable | ||
private fun DeleteAccountLayoutDeletingPreview() { | ||
GodToolsTheme { DeleteAccountLayout(DeleteAccountScreen.State.Deleting { }) } | ||
} | ||
|
||
@Preview | ||
@Composable | ||
private fun DeleteAccountLayoutErrorPreview() { | ||
GodToolsTheme { DeleteAccountLayout(DeleteAccountScreen.State.Error { }) } | ||
} |
139 changes: 139 additions & 0 deletions
139
app/src/main/kotlin/org/cru/godtools/ui/account/delete/DeleteAccountLayout.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
package org.cru.godtools.ui.account.delete | ||
|
||
import androidx.compose.foundation.Image | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.Spacer | ||
import androidx.compose.foundation.layout.fillMaxSize | ||
import androidx.compose.foundation.layout.fillMaxWidth | ||
import androidx.compose.foundation.layout.height | ||
import androidx.compose.foundation.layout.padding | ||
import androidx.compose.foundation.rememberScrollState | ||
import androidx.compose.foundation.verticalScroll | ||
import androidx.compose.material.icons.Icons | ||
import androidx.compose.material.icons.filled.Close | ||
import androidx.compose.material3.AlertDialog | ||
import androidx.compose.material3.Button | ||
import androidx.compose.material3.ExperimentalMaterial3Api | ||
import androidx.compose.material3.Icon | ||
import androidx.compose.material3.IconButton | ||
import androidx.compose.material3.MaterialTheme | ||
import androidx.compose.material3.OutlinedButton | ||
import androidx.compose.material3.Scaffold | ||
import androidx.compose.material3.Text | ||
import androidx.compose.material3.TextButton | ||
import androidx.compose.material3.TopAppBar | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.layout.ContentScale | ||
import androidx.compose.ui.platform.testTag | ||
import androidx.compose.ui.res.painterResource | ||
import androidx.compose.ui.res.stringResource | ||
import androidx.compose.ui.unit.dp | ||
import com.slack.circuit.codegen.annotations.CircuitInject | ||
import dagger.hilt.components.SingletonComponent | ||
import org.ccci.gto.android.common.androidx.compose.foundation.layout.padding | ||
import org.cru.godtools.R | ||
import org.cru.godtools.ui.account.delete.DeleteAccountScreen.Event | ||
import org.cru.godtools.ui.account.delete.DeleteAccountScreen.State | ||
|
||
private val MARGIN_HORIZONTAL = 32.dp | ||
|
||
internal const val TEST_TAG_ICON_CLOSE = "icon_close" | ||
internal const val TEST_TAG_BUTTON_DELETE = "button_delete" | ||
internal const val TEST_TAG_BUTTON_CANCEL = "button_cancel" | ||
internal const val TEST_TAG_ERROR_DIALOG = "error_dialog" | ||
internal const val TEST_TAG_ERROR_DIALOG_BUTTON_CONFIRM = "error_dialog_button_confirm" | ||
|
||
@Composable | ||
@OptIn(ExperimentalMaterial3Api::class) | ||
@CircuitInject(DeleteAccountScreen::class, SingletonComponent::class) | ||
fun DeleteAccountLayout(state: State, modifier: Modifier = Modifier) { | ||
DeleteAccountError(state) | ||
|
||
Scaffold(modifier = modifier) { | ||
Column( | ||
horizontalAlignment = Alignment.CenterHorizontally, | ||
modifier = Modifier | ||
.padding(it) | ||
.fillMaxSize() | ||
.verticalScroll(rememberScrollState()) | ||
) { | ||
TopAppBar( | ||
title = {}, | ||
navigationIcon = { | ||
IconButton( | ||
onClick = { state.eventSink(Event.Close) }, | ||
modifier = Modifier.testTag(TEST_TAG_ICON_CLOSE) | ||
) { | ||
Icon(Icons.Default.Close, null) | ||
} | ||
}, | ||
) | ||
|
||
Spacer(Modifier.weight(1f)) | ||
Image( | ||
painterResource(R.drawable.banner_account_login), | ||
contentDescription = null, | ||
contentScale = ContentScale.FillWidth, | ||
modifier = Modifier.fillMaxWidth(), | ||
) | ||
Spacer(Modifier.weight(1f)) | ||
|
||
Text( | ||
stringResource(R.string.account_delete_heading), | ||
style = MaterialTheme.typography.displayMedium, | ||
modifier = Modifier | ||
.padding(horizontal = MARGIN_HORIZONTAL) | ||
.align(Alignment.Start) | ||
) | ||
Text( | ||
stringResource(R.string.account_delete_description), | ||
style = MaterialTheme.typography.bodyLarge, | ||
modifier = Modifier.padding(horizontal = MARGIN_HORIZONTAL, top = 8.dp, bottom = 32.dp) | ||
) | ||
|
||
val actionsEnabled = state !is State.Deleting && state !is State.Error | ||
OutlinedButton( | ||
enabled = actionsEnabled, | ||
onClick = { state.eventSink(Event.DeleteAccount) }, | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.padding(horizontal = MARGIN_HORIZONTAL) | ||
.testTag(TEST_TAG_BUTTON_DELETE) | ||
) { | ||
Text(stringResource(R.string.account_delete_action_delete)) | ||
} | ||
Button( | ||
enabled = actionsEnabled, | ||
onClick = { state.eventSink(Event.Close) }, | ||
modifier = Modifier | ||
.fillMaxWidth() | ||
.padding(horizontal = MARGIN_HORIZONTAL) | ||
.testTag(TEST_TAG_BUTTON_CANCEL) | ||
) { | ||
Text(stringResource(R.string.account_delete_action_cancel)) | ||
} | ||
Spacer(Modifier.height(32.dp)) | ||
} | ||
} | ||
} | ||
|
||
@Composable | ||
private fun DeleteAccountError(state: State) { | ||
if (state is State.Error) { | ||
AlertDialog( | ||
text = { Text(stringResource(R.string.account_delete_error)) }, | ||
confirmButton = { | ||
TextButton( | ||
onClick = { state.eventSink(Event.ClearError) }, | ||
modifier = Modifier.testTag(TEST_TAG_ERROR_DIALOG_BUTTON_CONFIRM), | ||
) { | ||
Text(stringResource(R.string.account_delete_error_dismiss)) | ||
} | ||
}, | ||
onDismissRequest = { state.eventSink(Event.ClearError) }, | ||
modifier = Modifier.testTag(TEST_TAG_ERROR_DIALOG), | ||
) | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
app/src/main/kotlin/org/cru/godtools/ui/account/delete/DeleteAccountModule.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package org.cru.godtools.ui.account.delete | ||
|
||
import com.slack.circuit.runtime.presenter.Presenter | ||
import com.slack.circuit.runtime.ui.Ui | ||
import com.slack.circuit.runtime.ui.ui | ||
import dagger.Module | ||
import dagger.Provides | ||
import dagger.Reusable | ||
import dagger.hilt.InstallIn | ||
import dagger.hilt.components.SingletonComponent | ||
import dagger.multibindings.IntoSet | ||
|
||
// TODO: switch to codegen for this once https://github.com/slackhq/circuit/pull/963 is released | ||
@Module | ||
@InstallIn(SingletonComponent::class) | ||
object DeleteAccountModule { | ||
@Provides | ||
@Reusable | ||
@IntoSet | ||
fun presenterFactory(factory: DeleteAccountPresenter.Factory) = Presenter.Factory { screen, navigator, _ -> | ||
when (screen) { | ||
DeleteAccountScreen -> factory.create(navigator = navigator) | ||
else -> null | ||
} | ||
} | ||
|
||
@Provides | ||
@Reusable | ||
@IntoSet | ||
fun uiFactory() = Ui.Factory { screen, _ -> | ||
when (screen) { | ||
DeleteAccountScreen -> ui<DeleteAccountScreen.State> { state, modifier -> | ||
DeleteAccountLayout(state = state, modifier = modifier) | ||
} | ||
else -> null | ||
} | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
app/src/main/kotlin/org/cru/godtools/ui/account/delete/DeleteAccountPresenter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package org.cru.godtools.ui.account.delete | ||
|
||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.getValue | ||
import androidx.compose.runtime.mutableStateOf | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.runtime.rememberCoroutineScope | ||
import androidx.compose.runtime.setValue | ||
import com.slack.circuit.codegen.annotations.CircuitInject | ||
import com.slack.circuit.retained.rememberRetained | ||
import com.slack.circuit.runtime.Navigator | ||
import com.slack.circuit.runtime.presenter.Presenter | ||
import dagger.assisted.Assisted | ||
import dagger.assisted.AssistedFactory | ||
import dagger.assisted.AssistedInject | ||
import dagger.hilt.components.SingletonComponent | ||
import kotlinx.coroutines.launch | ||
import org.cru.godtools.account.GodToolsAccountManager | ||
import org.cru.godtools.ui.account.delete.DeleteAccountScreen.Event | ||
|
||
class DeleteAccountPresenter @AssistedInject constructor( | ||
@Assisted private val navigator: Navigator, | ||
private val accountManager: GodToolsAccountManager, | ||
) : Presenter<DeleteAccountScreen.State> { | ||
@Composable | ||
override fun present(): DeleteAccountScreen.State { | ||
val coroutineScope = rememberCoroutineScope() | ||
|
||
var deleting by remember { mutableStateOf(false) } | ||
var error by rememberRetained { mutableStateOf(false) } | ||
|
||
val eventSink: (Event) -> Unit = remember { | ||
{ | ||
when (it) { | ||
Event.DeleteAccount -> { | ||
deleting = true | ||
coroutineScope.launch { | ||
if (accountManager.deleteAccount()) { | ||
navigator.pop() | ||
} else { | ||
error = true | ||
deleting = false | ||
} | ||
} | ||
} | ||
Event.ClearError -> error = false | ||
Event.Close -> navigator.pop() | ||
} | ||
} | ||
} | ||
|
||
return when { | ||
error -> DeleteAccountScreen.State.Error(eventSink) | ||
deleting -> DeleteAccountScreen.State.Deleting(eventSink) | ||
else -> DeleteAccountScreen.State.Display(eventSink) | ||
} | ||
} | ||
|
||
@AssistedFactory | ||
@CircuitInject(DeleteAccountScreen::class, SingletonComponent::class) | ||
interface Factory { | ||
fun create(navigator: Navigator): DeleteAccountPresenter | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
app/src/main/kotlin/org/cru/godtools/ui/account/delete/DeleteAccountScreen.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package org.cru.godtools.ui.account.delete | ||
|
||
import com.slack.circuit.runtime.CircuitUiEvent | ||
import com.slack.circuit.runtime.CircuitUiState | ||
import com.slack.circuit.runtime.screen.Screen | ||
import kotlinx.parcelize.Parcelize | ||
|
||
@Parcelize | ||
data object DeleteAccountScreen : Screen { | ||
sealed class State : CircuitUiState { | ||
data class Display(override val eventSink: (Event) -> Unit) : State() | ||
data class Deleting(override val eventSink: (Event) -> Unit) : State() | ||
data class Error(override val eventSink: (Event) -> Unit) : State() | ||
|
||
abstract val eventSink: (Event) -> Unit | ||
} | ||
|
||
sealed class Event : CircuitUiEvent { | ||
data object DeleteAccount : Event() | ||
data object ClearError : Event() | ||
data object Close : Event() | ||
} | ||
} |
Oops, something went wrong.