-
Notifications
You must be signed in to change notification settings - Fork 5
Dialogs
Tristan Elliott edited this page Apr 28, 2024
·
1 revision


- All Dialogs components are built on top of the base Jetpack Compose Dialog
- Naming conventions follow the Google naming convention standard, HERE
- All text sizes are defined at the ImprovedDialogScope level
- All dialogs used through this application are surrounded by a ImprovedDialogScopeclass. This follows the pattern described by Bumble tech design system article
- The entire library of custom dialog components can be found HERE
-
ImprovedBanDialog is a custom Jetpack Compose Dialog used to display information about the user currently being considered for banning
-
UI demonstration with code below:

var showBanDialog by remember { mutableStateOf(false) }
if(showBanDialog){
ImprovedBanDialog(
onDismissRequest ={
showBanDialog = false
},
changeBanReason = {},
username = "Bobberton42",
banUser ={},
banReason = "",
)
}
-
ImprovedBanDialog is a custom Jetpack Compose Dialog used to display information about the user currently being considered for timeout
-
UI demonstration with code below:

var showTimeoutDialog by remember { mutableStateOf(true) }
if(showTimeoutDialog){
ImprovedTimeoutDialog(
onDismissRequest ={
showTimeoutDialog = false
},
changeTimeoutDuration={},
changeTimeoutReason = {},
username = "Bobberton42",
timeOutUser={},
timeoutDuration=60,
timeoutReason=""
)
}
/**
* TimeListData is used inside of the dialogs to model the internal duration(sent to server) of the timeout/ban and the visual
* information shown to the user
*
* @param time a Int representing the data that is sent to the server
* @param textDescription a String representing the data that is shown to the user
* */
data class TimeListData(
val time:Int,
val textDescription:String
)
/**
* ImprovedBanDialog is a composable that represents the dialog a user sees when they want to ban a user
*
* @param onDismissRequest a function that is used to close this dialog
* @param username a String representing the display name for the user that this dialog will affect
* @param banReason a String representing the reason that this user is going to get banned
* @param changeBanReason a function used to change the [banReason]
* @param banUser a function that is used to make the server call to twitch to ban the user
* */
@Composable
fun ImprovedBanDialog(
onDismissRequest: () -> Unit,
username: String,
banReason: String,
changeBanReason: (String) -> Unit,
banUser: () -> Unit,
){
val timeList = listOf<TimeListData>(
TimeListData(0, stringResource(R.string.permanently))
)
val secondary = MaterialTheme.colorScheme.secondary
val primary = MaterialTheme.colorScheme.primary
val onPrimary =MaterialTheme.colorScheme.onPrimary
val onSecondary = MaterialTheme.colorScheme.onSecondary
DialogBuilder(
dialogHeaderContent = {
DialogHeaderRow(
username,
stringResource(R.string.ban)
)
},
dialogSubHeaderContent = {
SubHeader(
subTitleText = stringResource(R.string.duration_text)
)
},
dialogRadioButtonsContent = {
DialogRadioButtonsRow(
dialogDuration = 0,
changeDialogDuration = {},
timeList = timeList
)
},
dialogTextFieldContent = {
OutlinedTextField(
colors = TextFieldDefaults.textFieldColors(
textColor = onPrimary,
focusedLabelColor = onPrimary,
focusedIndicatorColor = onPrimary,
unfocusedIndicatorColor = onPrimary,
unfocusedLabelColor = onPrimary,
),
value = banReason,
onValueChange = { changeBanReason(it) },
label = { Text(stringResource(R.string.reason), color = onPrimary) },
modifier = Modifier.fillMaxWidth()
)
},
dialogConfirmCancelContent = {
DialogConfirmCancelButtonRow(
onDismissRequest = { onDismissRequest() },
cancelText = stringResource(R.string.cancel),
confirmText = stringResource(R.string.ban),
confirmAction = {
banUser()
}
)
},
onDismissRequest = { onDismissRequest() },
primary = primary,
secondary = secondary,
onPrimary = onPrimary
)
}
/**
* ImprovedTimeoutDialog is a composable that represents the dialog a user sees when they want to timeout a user
*
* @param onDismissRequest a function that is used to close this dialog
* @param username a String representing the display name for the user that this dialog will affect
* @param timeoutReason a String representing the reason that this user is going to get banned
* @param changeTimeoutReason a function used to change the [timeoutReason]
* @param timeoutDuration a Int used to represent what the current timeout duration
* @param changeTimeoutDuration a function used to change the [timeoutDuration] duration
* @param timeOutUser a function that is used to make the server call to Twitch to timeout the user
* */
@Composable
fun ImprovedTimeoutDialog(
onDismissRequest: () -> Unit,
username:String,
timeoutDuration: Int,
timeoutReason: String,
changeTimeoutDuration: (Int) -> Unit,
changeTimeoutReason: (String) -> Unit,
timeOutUser: () -> Unit
){
val secondary = MaterialTheme.colorScheme.secondary
val primary = MaterialTheme.colorScheme.primary
val onPrimary =MaterialTheme.colorScheme.onPrimary
val onSecondary = MaterialTheme.colorScheme.onSecondary
val timeList = listOf<TimeListData>(
TimeListData(60, stringResource(R.string.one_minute)),
TimeListData(600, stringResource(R.string.ten_minutes)),
TimeListData(1800, stringResource(R.string.thirty_minutes)),
TimeListData(604800, stringResource(R.string.one_week))
)
DialogBuilder(
dialogHeaderContent = {
DialogHeaderRow(
username,
stringResource(R.string.timeout_text),
)
},
dialogSubHeaderContent = {
SubHeader(
subTitleText = stringResource(R.string.duration_text)
)
},
dialogRadioButtonsContent = {
DialogRadioButtonsRow(
dialogDuration = timeoutDuration,
changeDialogDuration = { duration -> changeTimeoutDuration(duration) },
timeList = timeList
)
},
dialogTextFieldContent = {
DialogOutlinedTextField(
timeoutReason = timeoutReason,
changeTimeoutReason = { changeTimeoutReason(it) },
)
},
dialogConfirmCancelContent = {
DialogConfirmCancelButtonRow(
onDismissRequest = { onDismissRequest() },
confirmAction = { timeOutUser() },
confirmText = stringResource(R.string.timeout_confirm),
cancelText = stringResource(R.string.cancel)
)
},
onDismissRequest = { onDismissRequest() },
secondary = secondary,
primary=primary,
onPrimary = onPrimary
)
}
/**
* DialogBuilder is a private function that is used to quickly and consistently create properly styled [Dialog] composables
* */
@Composable
private fun DialogBuilder(
dialogHeaderContent:@Composable ImprovedDialogScope.() -> Unit,
dialogSubHeaderContent:@Composable ImprovedDialogScope.() -> Unit,
dialogRadioButtonsContent:@Composable ImprovedDialogScope.() -> Unit,
dialogTextFieldContent:@Composable ImprovedDialogScope.() -> Unit,
dialogConfirmCancelContent:@Composable ImprovedDialogScope.() -> Unit,
onDismissRequest: () -> Unit,
primary: Color,
onPrimary: Color,
secondary: Color,
){
val largeFontSize = MaterialTheme.typography.headlineLarge.fontSize
val mediumFontSize = MaterialTheme.typography.headlineMedium.fontSize
val dialogScope = remember{
ImprovedDialogScope(onPrimary,secondary,largeFontSize,mediumFontSize)
}
Dialog(onDismissRequest = { onDismissRequest() }) {
Card(
modifier = Modifier
.fillMaxWidth(),
shape = RoundedCornerShape(16.dp),
backgroundColor = primary,
border = BorderStroke(2.dp, secondary)
) {
Column(
modifier = Modifier
.padding(10.dp)
.background(primary)
) {
with(dialogScope){
dialogHeaderContent()
dialogSubHeaderContent()
dialogRadioButtonsContent()
dialogTextFieldContent()
dialogConfirmCancelContent()
}
}
}
}
}
/**
* ImprovedDialogScope is a private class that contains all the composables that are used to create Dialogs inside of this application
*
* @param onPrimary a [Color] that is used to show information that is on a dark background
* @param secondary a [Color] that is represents a purple color and is used to highlight non-primary information
*
* @property DialogHeaderRow
* @property SubHeader
* @property DialogRadioButtonsRow
* @property DialogOutlinedTextField
* @property DialogConfirmCancelButtonRow
* */
@Stable
private class ImprovedDialogScope(
val onPrimary: Color,
val secondary: Color,
val largeFontSize:TextUnit,
val mediumFontSize:TextUnit,
){
/**
* DialogHeaderRow is a [Row] composable that is meant to show the user very important information.
* [username] and [headerText] are shown to the user in the largest possible font size, `MaterialTheme.typography.headlineLarge.fontSize`
*
* @param username the display name of the user the dialog is about
* @param headerText the direct information that is meant to be shown to the user of this dialog, ie, `Timeout`/`Ban`.
*
* */
@Composable
fun DialogHeaderRow(
username:String,
headerText:String,
){
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceAround
) {
Text(headerText, fontSize = largeFontSize,color = onPrimary)
Text(username, fontSize = largeFontSize,color = onPrimary)
}
}
/**
* SubHeader is a composable that is meant to display information that is meant to be `secondary`. Information that is
* not as important as the information displayed in [DialogHeaderRow]
*
* @param subTitleText a String representing a small amount of information displayed to the user
*
* */
@Composable
fun SubHeader(
subTitleText:String
){
Divider(color = secondary, thickness = 1.dp, modifier = Modifier.fillMaxWidth())
Text(subTitleText,color = onPrimary, fontSize = mediumFontSize)
}
/**
* DialogRadioButtonsRow a [Row] composable will show the a number of [RadioButton] composables
*
* @param dialogDuration a Int used to determine what [RadioButton] is currently selected
* @param changeDialogDuration a function used to change the [dialogDuration]
* @param timeList a list of [TimeListData] objects. The length of this list will determine the number of
* [RadioButton] composables
*
* */
@Composable
fun DialogRadioButtonsRow(
dialogDuration: Int,
changeDialogDuration: (Int) -> Unit,
timeList:List<TimeListData>,
){
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = if(timeList.size ==1) Arrangement.Start else Arrangement.SpaceEvenly
) {
for(timeData in timeList){
Column {
RadioButton(
colors = RadioButtonDefaults.colors( selectedColor=secondary, unselectedColor = onPrimary),
selected = dialogDuration == timeData.time,
onClick = { changeDialogDuration(timeData.time) }
)
Text(timeData.textDescription,color = onPrimary)
}
}
}
}
/**
* DialogOutlinedTextField a [OutlinedTextField] composables that is used to capture user input
*
* @param timeoutReason a String meant ot represent the reason the dialog is being used
* @param changeTimeoutReason a function used to change [timeoutReason]
*
* */
@Composable
fun DialogOutlinedTextField(
timeoutReason:String,
changeTimeoutReason: (String) -> Unit
){
OutlinedTextField(
colors = TextFieldDefaults.textFieldColors(
textColor = onPrimary,
focusedLabelColor = onPrimary,
focusedIndicatorColor = onPrimary,
unfocusedIndicatorColor = onPrimary,
unfocusedLabelColor = onPrimary
),
value = timeoutReason,
onValueChange = { changeTimeoutReason(it) },
label = {
Text(stringResource(R.string.reason))
},
modifier = Modifier.fillMaxWidth()
)
}
/**
* DialogConfirmCancelButtonRow a
*
* @param onDismissRequest a function that is used to close the current dialog
* @param confirmAction a function that is used to confirm the action of the current dialog
* @param cancelText a String meant to represent the cancelation of the dialogs intended action.
* @param confirmText a String meant to represent the confirmation of the dialogs intended action.
*
* */
@Composable
fun DialogConfirmCancelButtonRow(
onDismissRequest: () -> Unit,
confirmAction: () -> Unit,
cancelText:String,
confirmText:String
){
val fontSize =MaterialTheme.typography.headlineSmall.fontSize
val buttonScope = remember(){ ButtonScope(fontSize) }
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
with(buttonScope){
this.Button(
text =cancelText,
onClick = { onDismissRequest()},
)
Spacer(modifier =Modifier.width(15.dp))
this.Button(
text =confirmText,
onClick = {
onDismissRequest()
confirmAction()
},
)
}
}
}
}