diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml new file mode 100644 index 0000000000..c74716e987 --- /dev/null +++ b/.github/workflows/continuous-delivery.yml @@ -0,0 +1,54 @@ +name: Continuous Delivery + +env: + + main_project_module: app + +on: + workflow_dispatch: + push: + branches: + - main + - develop + - release/* + +jobs: + deployment_job: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # Set Current Date + - name: Set current date + id: date + run: echo "date=$(date +'%Y-%m-%d')" >> "$GITHUB_OUTPUT" + + # Set Repository Name As Env Variable + - name: Set repository name as env variable + run: echo "repository_name=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV + + - name: Set Up JDK + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + cache: 'gradle' + + - name: Change wrapper permissions + run: chmod +x ./gradlew + + # Create APK Debug + - name: Build apk debug project (APK) - ${{ env.main_project_module }} module + run: ./gradlew assembleDhisDebug + + - name: Read version name from file + working-directory: ./gradle + id: read-version + run: echo "vName=$(grep 'vName' libs.versions.toml | awk -F' = ' '{print $2}' | tr -d '"')" >> "$GITHUB_OUTPUT" + + # Upload Artifact Build + - name: Upload Android artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ env.repository_name }} - Android APK - ${{ steps.date.outputs.date }} + path: ${{ env.main_project_module }}/build/outputs/apk/dhis/debug/dhis2-v${{ steps.read-version.outputs.vName }}-dhis-debug.apk diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml new file mode 100644 index 0000000000..12ae94f7ee --- /dev/null +++ b/.github/workflows/deploy-release.yml @@ -0,0 +1,32 @@ +# This is a basic workflow that is manually triggered + +name: Deploy Release + +# Controls when the action will run. Workflow runs when manually triggered using the UI +# or API. +on: + workflow_dispatch: + # Inputs the workflow accepts. + inputs: + name: + # Friendly description to be shown in the UI instead of 'name' + description: 'Person to greet' + # Default value if no value is explicitly provided + default: 'World' + # Input has to be provided for the workflow to run + required: true + # The data type of the input + type: string + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "greet" + greet: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Runs a single command using the runners shell + - name: Send greeting + run: echo "Hello ${{ inputs.name }}" diff --git a/app/src/androidTest/java/org/dhis2/usescases/BaseTest.kt b/app/src/androidTest/java/org/dhis2/usescases/BaseTest.kt index f94746d2c9..e9d76fa684 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/BaseTest.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/BaseTest.kt @@ -24,7 +24,6 @@ import org.dhis2.commons.idlingresource.SearchIdlingResourceSingleton import org.dhis2.commons.prefs.Preference import org.dhis2.form.ui.idling.FormCountingIdlingResource import org.dhis2.usescases.eventsWithoutRegistration.EventIdlingResourceSingleton -import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.ui.EventDetailIdlingResourceSingleton import org.dhis2.usescases.programEventDetail.eventList.EventListIdlingResourceSingleton import org.dhis2.usescases.teiDashboard.dashboardfragments.teidata.TeiDataIdlingResourceSingleton import org.junit.After @@ -87,7 +86,6 @@ open class BaseTest { SearchIdlingResourceSingleton.countingIdlingResource, TeiDataIdlingResourceSingleton.countingIdlingResource, EventIdlingResourceSingleton.countingIdlingResource, - EventDetailIdlingResourceSingleton.countingIdlingResource, ) } @@ -100,7 +98,6 @@ open class BaseTest { SearchIdlingResourceSingleton.countingIdlingResource, TeiDataIdlingResourceSingleton.countingIdlingResource, EventIdlingResourceSingleton.countingIdlingResource, - EventDetailIdlingResourceSingleton.countingIdlingResource, ) } diff --git a/app/src/androidTest/java/org/dhis2/usescases/teidashboard/TeiDashboardTest.kt b/app/src/androidTest/java/org/dhis2/usescases/teidashboard/TeiDashboardTest.kt index 4557f174bf..b6dc9db96f 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/teidashboard/TeiDashboardTest.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/teidashboard/TeiDashboardTest.kt @@ -125,6 +125,7 @@ class TeiDashboardTest : BaseTest() { } } + @Ignore("next button is sometimes not reached. Review feature.") @Test fun shouldShowQRWhenClickOnShare() { prepareTeiCompletedProgrammeAndLaunchActivity(rule) diff --git a/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/TeiDashboardRobot.kt b/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/TeiDashboardRobot.kt index dc50165dbe..8a6c68c2b8 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/TeiDashboardRobot.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/TeiDashboardRobot.kt @@ -12,7 +12,9 @@ import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.recyclerview.widget.RecyclerView import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.NoMatchingViewException import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition @@ -395,7 +397,15 @@ class TeiDashboardRobot(val composeTestRule: ComposeTestRule) : BaseRobot() { } fun clickOnTimelineEvents() { - onView(withText(R.string.show_events_timeline)).perform(click()) + try { + onView(withText(R.string.show_events_timeline)).perform(click()) + }catch (e: NoMatchingViewException){ + checkIfGroupedEventsIsVisible() + } + } + + private fun checkIfGroupedEventsIsVisible(){ + onView(withText(R.string.group_events_by_stage)).check(matches(isDisplayed())) } fun checkEventWasScheduled(eventName: String, position: Int) { diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/ui/EventDetailIdlingResourceSingleton.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/ui/EventDetailIdlingResourceSingleton.kt deleted file mode 100644 index 5a24fe2882..0000000000 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/ui/EventDetailIdlingResourceSingleton.kt +++ /dev/null @@ -1,20 +0,0 @@ -package org.dhis2.usescases.eventsWithoutRegistration.eventDetails.ui - -import androidx.test.espresso.idling.CountingIdlingResource - -object EventDetailIdlingResourceSingleton { - - private const val RESOURCE = "EVENT_DETAIL" - - @JvmField val countingIdlingResource = CountingIdlingResource(RESOURCE) - - fun increment() { - countingIdlingResource.increment() - } - - fun decrement() { - if (!countingIdlingResource.isIdleNow) { - countingIdlingResource.decrement() - } - } -} diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/ui/EventDetailsViewModel.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/ui/EventDetailsViewModel.kt index 5d33d4db0b..e7f6233cd0 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/ui/EventDetailsViewModel.kt +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/ui/EventDetailsViewModel.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.launch import org.dhis2.commons.extensions.truncate import org.dhis2.commons.locationprovider.LocationProvider import org.dhis2.form.data.GeometryController +import org.dhis2.usescases.eventsWithoutRegistration.EventIdlingResourceSingleton import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.domain.ConfigureEventCatCombo import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.domain.ConfigureEventCoordinates import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.domain.ConfigureEventDetails @@ -141,7 +142,7 @@ class EventDetailsViewModel( } private fun setUpEventDetails() { - EventDetailIdlingResourceSingleton.increment() + EventIdlingResourceSingleton.increment() viewModelScope.launch { configureEventDetails( selectedDate = eventDate.value.currentDate, @@ -154,13 +155,13 @@ class EventDetailsViewModel( .flowOn(Dispatchers.IO) .collect { _eventDetails.value = it - EventDetailIdlingResourceSingleton.decrement() + EventIdlingResourceSingleton.decrement() } } } fun setUpEventReportDate(selectedDate: Date? = null) { - EventDetailIdlingResourceSingleton.increment() + EventIdlingResourceSingleton.increment() viewModelScope.launch { configureEventReportDate(selectedDate) .flowOn(Dispatchers.IO) @@ -168,7 +169,7 @@ class EventDetailsViewModel( _eventDate.value = it setUpEventDetails() setUpOrgUnit(selectedDate = it.currentDate) - EventDetailIdlingResourceSingleton.decrement() + EventIdlingResourceSingleton.decrement() } } } @@ -195,14 +196,14 @@ class EventDetailsViewModel( } fun setUpCategoryCombo(categoryOption: Pair? = null) { - EventDetailIdlingResourceSingleton.increment() + EventIdlingResourceSingleton.increment() viewModelScope.launch { configureEventCatCombo(categoryOption) .flowOn(Dispatchers.IO) .collect { _eventCatCombo.value = it setUpEventDetails() - EventDetailIdlingResourceSingleton.decrement() + EventIdlingResourceSingleton.decrement() } } } @@ -213,7 +214,7 @@ class EventDetailsViewModel( } private fun setUpCoordinates(value: String? = "") { - EventDetailIdlingResourceSingleton.increment() + EventIdlingResourceSingleton.increment() viewModelScope.launch { configureEventCoordinates(value) .flowOn(Dispatchers.IO) @@ -233,20 +234,20 @@ class EventDetailsViewModel( ) _eventCoordinates.value = eventCoordinates setUpEventDetails() - EventDetailIdlingResourceSingleton.decrement() + EventIdlingResourceSingleton.decrement() } } } fun setUpEventTemp(status: EventTempStatus? = null, isChecked: Boolean = true) { - EventDetailIdlingResourceSingleton.increment() + EventIdlingResourceSingleton.increment() if (isChecked) { configureEventTemp(status).apply { _eventTemp.value = this setUpEventDetails() } } - EventDetailIdlingResourceSingleton.decrement() + EventIdlingResourceSingleton.decrement() } fun getSelectableDates(eventDate: EventDate): SelectableDates { diff --git a/app/src/main/java/org/dhis2/usescases/settings/ui/ExportOption.kt b/app/src/main/java/org/dhis2/usescases/settings/ui/ExportOption.kt index 5215b119dd..c5f18198b1 100644 --- a/app/src/main/java/org/dhis2/usescases/settings/ui/ExportOption.kt +++ b/app/src/main/java/org/dhis2/usescases/settings/ui/ExportOption.kt @@ -1,11 +1,13 @@ package org.dhis2.usescases.settings.ui +import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.provider.Settings import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.tween @@ -19,8 +21,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.Icon -import androidx.compose.material.MaterialTheme import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.Share import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -30,17 +32,12 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import androidx.lifecycle.LiveData -import com.google.accompanist.themeadapter.material3.Mdc3Theme -import com.google.android.material.composethemeadapter.MdcTheme import org.dhis2.R import org.dhis2.ui.dialogs.alert.Dhis2AlertDialogUi import org.dhis2.ui.model.ButtonUiModel @@ -48,6 +45,8 @@ import org.hisp.dhis.mobile.ui.designsystem.component.Button import org.hisp.dhis.mobile.ui.designsystem.component.ButtonStyle import org.hisp.dhis.mobile.ui.designsystem.component.ProgressIndicator import org.hisp.dhis.mobile.ui.designsystem.component.ProgressIndicatorType +import org.hisp.dhis.mobile.ui.designsystem.theme.Spacing +import org.hisp.dhis.mobile.ui.designsystem.theme.SurfaceColor @Composable fun ExportOption( @@ -62,9 +61,7 @@ fun ExportOption( val launcher = rememberLauncherForActivityResult( contract = ActivityResultContracts.RequestPermission(), ) { isGranted -> - if (isGranted) { - onPermissionGrantedCallback() - } else { + onPermissionGrantedCallback.takeIf { isGranted }?.invoke() ?: run { showPermissionDialog = true } } @@ -95,38 +92,29 @@ fun ExportOption( Row( modifier = Modifier .fillMaxWidth() - .height(72.dp) + .height(Spacing.Spacing72) .padding( - start = 72.dp, - top = 16.dp, - end = 16.dp, - bottom = 16.dp, + start = Spacing.Spacing48, + top = Spacing.Spacing16, + end = Spacing.Spacing16, + bottom = Spacing.Spacing16, ), verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = if (displayProgress) Arrangement.Center else spacedBy(16.dp), + horizontalArrangement = getHorizontalArrangement(displayProgress), ) { Button( modifier = Modifier.weight(1f), onClick = { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU || - ContextCompat.checkSelfPermission( - context, - android.Manifest.permission.WRITE_EXTERNAL_STORAGE, - ) == PackageManager.PERMISSION_GRANTED - ) { - onDownload() - } else { - onPermissionGrantedCallback = onDownload - launcher.launch(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) - } + onDownloadCLick(context, onDownload, launcher) + onPermissionGrantedCallback = onDownload }, style = ButtonStyle.TEXT, text = stringResource(id = R.string.download), icon = { Icon( - imageVector = ImageVector.vectorResource(R.drawable.ic_file_download), + imageVector = Icons.Filled.Download, contentDescription = "Download", - tint = MaterialTheme.colors.primary, + tint = SurfaceColor.Primary, ) }, ) @@ -134,17 +122,8 @@ fun ExportOption( Button( modifier = Modifier.weight(1f), onClick = { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU || - ContextCompat.checkSelfPermission( - context, - android.Manifest.permission.WRITE_EXTERNAL_STORAGE, - ) == PackageManager.PERMISSION_GRANTED - ) { - onShare() - } else { - onPermissionGrantedCallback = onShare - launcher.launch(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) - } + onShareClick(context, onShare, launcher) + onPermissionGrantedCallback = onShare }, style = ButtonStyle.TEXT, text = stringResource(id = R.string.share), @@ -152,7 +131,7 @@ fun ExportOption( Icon( imageVector = Icons.Filled.Share, contentDescription = "Share", - tint = MaterialTheme.colors.primary, + tint = SurfaceColor.Primary, ) }, ) @@ -161,10 +140,10 @@ fun ExportOption( Row( modifier = Modifier .fillMaxWidth() - .height(72.dp) - .padding(16.dp), + .height(Spacing.Spacing72) + .padding(Spacing.Spacing16), verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = if (displayProgress) Arrangement.Center else spacedBy(16.dp), + horizontalArrangement = getHorizontalArrangement(displayProgress), ) { ProgressIndicator(type = ProgressIndicatorType.CIRCULAR) } @@ -192,20 +171,42 @@ fun ExportOption( } } +private fun onDownloadCLick(context: Context, onSuccess: () -> Unit, launcher: ActivityResultLauncher) { + if (checkPermissionAndAndroidVersion(context)) { + onSuccess() + } else { + launcher.launch(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) + } +} + +private fun onShareClick(context: Context, onSuccess: () -> Unit, launcher: ActivityResultLauncher) { + if (checkPermissionAndAndroidVersion(context)) { + onSuccess() + } else { + launcher.launch(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) + } +} + +private fun checkPermissionAndAndroidVersion(context: Context) = + Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU || + ContextCompat.checkSelfPermission( + context, + android.Manifest.permission.WRITE_EXTERNAL_STORAGE, + ) == PackageManager.PERMISSION_GRANTED + +private fun getHorizontalArrangement(displayProgress: Boolean) = + if (displayProgress) Arrangement.Center else spacedBy(Spacing.Spacing16) + @Preview @Composable fun PreviewExportOption() { - Mdc3Theme { - ExportOption(onDownload = { }, onShare = { }, false) - } + ExportOption(onDownload = { }, onShare = { }, false) } @Preview @Composable fun PreviewExportOptionProgress() { - Mdc3Theme { - ExportOption(onDownload = { }, onShare = { }, true) - } + ExportOption(onDownload = { }, onShare = { }, true) } fun ComposeView.setExportOption( @@ -215,8 +216,6 @@ fun ComposeView.setExportOption( ) { setContent { val displayProgress by displayProgressProvider().observeAsState(false) - MdcTheme { - ExportOption(onShare, onDownload, displayProgress) - } + ExportOption(onShare, onDownload, displayProgress) } } diff --git a/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardMobileActivity.kt b/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardMobileActivity.kt index cf3251fc02..dc0db0ec6e 100644 --- a/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardMobileActivity.kt +++ b/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardMobileActivity.kt @@ -404,10 +404,10 @@ class TeiDashboardMobileActivity : if (this.isLandscape()) { if (binding.teiTablePager?.adapter == null) { setViewpagerAdapter() + supportFragmentManager.beginTransaction() + .replace(R.id.tei_main_view, newInstance(programUid, teiUid, enrollmentUid)) + .commitAllowingStateLoss() } - supportFragmentManager.beginTransaction() - .replace(R.id.tei_main_view, newInstance(programUid, teiUid, enrollmentUid)) - .commitAllowingStateLoss() } else { if (binding.teiPager?.adapter == null) { setViewpagerAdapter() diff --git a/ui-components/src/main/java/org/dhis2/ui/dialogs/alert/AlertDialog.kt b/ui-components/src/main/java/org/dhis2/ui/dialogs/alert/AlertDialog.kt index 9bb7127f5c..afe0d6ed14 100644 --- a/ui-components/src/main/java/org/dhis2/ui/dialogs/alert/AlertDialog.kt +++ b/ui-components/src/main/java/org/dhis2/ui/dialogs/alert/AlertDialog.kt @@ -123,87 +123,88 @@ fun Dhis2AlertDialogUi( ) var confirmButtonClick = remember { mutableStateOf(false) } - - AlertDialog( - onDismissRequest = dismissButton.onClick, - title = { Text(text = labelText, textAlign = TextAlign.Center) }, - text = { - Column { - Text( - text = buildAnnotatedString { - append(descriptionText) - spanText?.let { - addStyle( - style = SpanStyle(MaterialTheme.colorScheme.primary), - start = descriptionText.indexOf(spanText), - end = descriptionText.indexOf(spanText) + spanText.length, - ) - } - }, - ) - animationRes?.let { - Spacer(modifier = Modifier.size(16.dp)) - if (!confirmButtonClick.value) { - LottieAnimation( - modifier = Modifier - .fillMaxWidth() - .height(200.dp), - composition = composition, - iterations = LottieConstants.IterateForever, - ) - } else { - Row( - modifier = Modifier - .fillMaxWidth() - .height(200.dp), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, - ) { - ProgressIndicator( + Dhis2Theme { + AlertDialog( + onDismissRequest = dismissButton.onClick, + title = { Text(text = labelText, textAlign = TextAlign.Center) }, + text = { + Column { + Text( + text = buildAnnotatedString { + append(descriptionText) + spanText?.let { + addStyle( + style = SpanStyle(MaterialTheme.colorScheme.primary), + start = descriptionText.indexOf(spanText), + end = descriptionText.indexOf(spanText) + spanText.length, + ) + } + }, + ) + animationRes?.let { + Spacer(modifier = Modifier.size(16.dp)) + if (!confirmButtonClick.value) { + LottieAnimation( modifier = Modifier - .width(100.dp) - .height(100.dp), - type = ProgressIndicatorType.CIRCULAR, + .fillMaxWidth() + .height(200.dp), + composition = composition, + iterations = LottieConstants.IterateForever, ) + } else { + Row( + modifier = Modifier + .fillMaxWidth() + .height(200.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + ) { + ProgressIndicator( + modifier = Modifier + .width(100.dp) + .height(100.dp), + type = ProgressIndicatorType.CIRCULAR, + ) + } } } } - } - }, - icon = { - iconResource?.let { - Icon( - painter = painterResource(id = iconResource), - tint = MaterialTheme.colorScheme.primary, - contentDescription = "notification alert", - ) - } - }, - confirmButton = { - TextButton( - modifier = Modifier.testTag(CONFIRM_BUTTON_TAG), - onClick = { - confirmButtonClick.value = true - animationRes?.let { - val job = Job() - val scope = CoroutineScope(job) + }, + icon = { + iconResource?.let { + Icon( + painter = painterResource(id = iconResource), + tint = MaterialTheme.colorScheme.primary, + contentDescription = "notification alert", + ) + } + }, + confirmButton = { + TextButton( + modifier = Modifier.testTag(CONFIRM_BUTTON_TAG), + onClick = { + confirmButtonClick.value = true + animationRes?.let { + val job = Job() + val scope = CoroutineScope(job) - scope.launch { - delay(5000) - confirmButton.onClick.invoke() - } - } ?: confirmButton.onClick.invoke() - }, - ) { - Text(text = confirmButton.text) - } - }, - dismissButton = { - TextButton(onClick = dismissButton.onClick) { - Text(text = dismissButton.text) - } - }, - ) + scope.launch { + delay(5000) + confirmButton.onClick.invoke() + } + } ?: confirmButton.onClick.invoke() + }, + ) { + Text(text = confirmButton.text) + } + }, + dismissButton = { + TextButton(onClick = dismissButton.onClick) { + Text(text = dismissButton.text) + } + }, + ) + } } const val CONFIRM_BUTTON_TAG = "CONFIRM_BUTTON_TAG"