Repository: woocommerce/woocommerce-android
Pull request #14036: [WOOMOB-472] Replace Activity Toolbar With Fragment Toolbar in EditCouponFragment
Author: login: malinajirka
Labels: name: feature: coupons
description: Related to coupons for a store.
name: feature: POS
Created: 2025-05-12T06:50:09Z | Merged: 2025-05-13T07:32:27Z

<!-- Remember about a good descriptive title. -->
Closes: #WOOMOB-472
<!-- Id number of the GitHub issue this PR addresses. -->

Do not merge - https://github.com/woocommerce/woocommerce-android/pull/14031 needs to be merged first.

### Description
<!-- Take the time to write a good summary. Why is it needed? What does it do? When fixing bugs try to avoid just writing “See original issue” – clarify what the problem was and how you’ve fixed it. -->

Series of PRs with Toolbar fixes for coupons creation flow
1. **THIS PR** https://github.com/woocommerce/woocommerce-android/pull/14036
2. https://github.com/woocommerce/woocommerce-android/pull/14038
3. https://github.com/woocommerce/woocommerce-android/pull/14039
4. https://github.com/woocommerce/woocommerce-android/pull/14040
5. https://github.com/woocommerce/woocommerce-android/pull/14041


Goal of this PR is to replace the MainActivity toolbar with fragment specific toolbar on the EditCouponFragment.

Known issues
- The status bar on CouponEditScreen within POS has dark overlay - not related to this PR.


### Steps to reproduce
<!-- Step-by-step testing instructions. For new user flows, consider instead stating the goal of the workflow and see if your PR reviewer can accomplish the workflow without specific steps! -->

1. Open POS
2. Tap on coupons tab
4. Tap on plus icon
5. Select one of the coupon types
6. Notice the Toolbar is not visible


### Testing information
<!-- This is your opportunity to break out individual scenarios that need testing (when necessary) and/or include a checklist for the reviewer to go through. Consider documenting the following from your own completed testing: devices used, alternate workflows, edge cases, affected areas, critical flows, areas not tested, and any remaining unknowns. Provide feedback on this new section of the PR template through Sept 30, 2024 to Apps Quality; additional context here: p91TBi-b8z-p2#comment-12036 -->

**POS testing**
1. Open POS
2. Tap on coupons tab
3. Tap on plus icon
4. Select one of the coupon types
5. Notice the Toolbar is visible
------------
**Regression testing**
1. Open More menu
2. Tap on Coupons
3. Tap on plus FAB
4. Select one of the coupon types
5. Notice the Toolbar is visible

### The tests that have been performed
<!-- To give the reviewer an idea of what could be missed in terms of testing -->
I've tested the above on tablet and phone.

### Images/gif
<!-- Include before and after images or gifs when appropriate. -->
| Coupons (300px)                                | POS (300px)                                   |
|-----------------------------------------------|-----------------------------------------------|
| **Before**                                     | **Before**                                    |
| <img src="https://github.com/user-attachments/assets/acd29c97-6b22-4357-9323-1274222dc4ce" width="300"> | <img src="https://github.com/user-attachments/assets/0e8f84c0-4a4d-45ef-b61c-ef3848660a2d" width="300"> |
| **After**                                      | **After**                                     |
| <img src="https://github.com/user-attachments/assets/bb4c5089-814f-4f94-9d44-078ee1f69e95" width="300"> | <img src="https://github.com/user-attachments/assets/38481f6f-6b9a-4eb0-b2a2-4110e439078f" width="300"> |

- [x] I have considered if this change warrants release notes and have added them to `RELEASE-NOTES.txt` if necessary. Use the "[Internal]" label for non-user-facing changes.

## Reviewer (or Author, in the case of optional code reviews):

Please make sure these conditions are met before approving the PR, or request changes if the PR needs improvement:

- [x] The PR is small and has a clear, single focus, or a valid explanation is provided in the description. If needed, please request to split it into smaller PRs.
- [x] Ensure Adequate Unit Test Coverage: The changes are reasonably covered by unit tests or an explanation is provided in the PR description.
- [x] Manual Testing: The author listed all the tests they ran, including smoke tests when needed (e.g., for refactorings). The reviewer confirmed that the PR works as expected on big (tablet) and small (phone) in case of UI changes, and no regressions are added.

<!-- Pull request guidelines: https://github.com/woocommerce/woocommerce-android/blob/develop/docs/pull-request-guidelines.md -->


Diff:
diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/edit/EditCouponFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/edit/EditCouponFragment.kt
index 619cbb559fe4..29e793c2cc30 100644
--- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/edit/EditCouponFragment.kt
+++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/edit/EditCouponFragment.kt
@@ -9,7 +9,6 @@ import androidx.compose.ui.platform.ViewCompositionStrategy
 import androidx.fragment.app.setFragmentResult
 import androidx.fragment.app.viewModels
 import androidx.navigation.fragment.findNavController
-import com.woocommerce.android.R
 import com.woocommerce.android.extensions.handleResult
 import com.woocommerce.android.model.Coupon
 import com.woocommerce.android.ui.base.BaseFragment
@@ -25,7 +24,6 @@ import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit
 import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.ShowUiStringSnackbar
 import dagger.hilt.android.AndroidEntryPoint
 import javax.inject.Inject
-import kotlin.properties.Delegates.observable
 
 @AndroidEntryPoint
 class EditCouponFragment : BaseFragment() {
@@ -34,15 +32,7 @@ class EditCouponFragment : BaseFragment() {
     @Inject lateinit var uiMessageResolver: UIMessageResolver
 
     override val activityAppBarStatus: AppBarStatus
-        get() = AppBarStatus.Visible(
-            navigationIcon = R.drawable.ic_gridicons_cross_24dp
-        )
-
-    private var screenTitle: String by observable("") { _, oldValue, newValue ->
-        if (oldValue != newValue) {
-            updateActivityTitle()
-        }
-    }
+        get() = AppBarStatus.Hidden
 
     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
         return ComposeView(requireContext()).apply {
@@ -62,10 +52,6 @@ class EditCouponFragment : BaseFragment() {
     }
 
     private fun setupObservers() {
-        viewModel.viewState.observe(viewLifecycleOwner) {
-            screenTitle = it.screenTitle
-        }
-
         viewModel.event.observe(viewLifecycleOwner) { event ->
             when (event) {
                 is EditCouponNavigationTarget -> EditCouponNavigator.navigate(this, event)
@@ -96,8 +82,6 @@ class EditCouponFragment : BaseFragment() {
         }
     }
 
-    override fun getFragmentTitle() = screenTitle
-
     private fun setNewCouponIdAsResultIfAvailable(event: EditCouponViewModel.NavigateBackToPOS) {
         val bundle = event.couponId?.let {
             Bundle().apply {
diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/edit/EditCouponScreen.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/edit/EditCouponScreen.kt
index 6d7f6a786107..d3dfb68521d9 100644
--- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/edit/EditCouponScreen.kt
+++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/edit/EditCouponScreen.kt
@@ -16,10 +16,12 @@ import androidx.compose.material.ButtonDefaults
 import androidx.compose.material.Divider
 import androidx.compose.material.Icon
 import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Scaffold
 import androidx.compose.material.Text
 import androidx.compose.material.TextButton
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Clear
 import androidx.compose.material.icons.filled.Edit
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -47,6 +49,7 @@ import com.woocommerce.android.model.Coupon.Type.Percent
 import com.woocommerce.android.ui.compose.component.BigDecimalTextFieldValueMapper
 import com.woocommerce.android.ui.compose.component.DatePickerDialog
 import com.woocommerce.android.ui.compose.component.ProgressDialog
+import com.woocommerce.android.ui.compose.component.Toolbar
 import com.woocommerce.android.ui.compose.component.WCColoredButton
 import com.woocommerce.android.ui.compose.component.WCOutlinedButton
 import com.woocommerce.android.ui.compose.component.WCOutlinedSpinner
@@ -74,7 +77,8 @@ fun EditCouponScreen(viewModel: EditCouponViewModel) {
             onUsageRestrictionsClick = viewModel::onUsageRestrictionsClick,
             onSelectProductsButtonClick = viewModel::onSelectProductsButtonClick,
             onSelectCategoriesButtonClick = viewModel::onSelectCategoriesButtonClick,
-            onSaveClick = viewModel::onSaveClick
+            onSaveClick = viewModel::onSaveClick,
+            onBackPressed = viewModel::onBackPressed,
         )
     }
 }
@@ -91,36 +95,48 @@ fun EditCouponScreen(
     onUsageRestrictionsClick: () -> Unit = {},
     onSelectProductsButtonClick: () -> Unit = {},
     onSelectCategoriesButtonClick: () -> Unit = {},
-    onSaveClick: () -> Unit = {}
+    onSaveClick: () -> Unit = {},
+    onBackPressed: () -> Unit = {},
 ) {
     val scrollState = rememberScrollState()
-    Column(
-        verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.major_100)),
-        modifier = Modifier
-            .background(color = MaterialTheme.colors.surface)
-            .verticalScroll(scrollState)
-            .padding(vertical = dimensionResource(id = R.dimen.major_100))
-            .fillMaxSize()
-    ) {
-        DetailsSection(
-            viewState = viewState,
-            onAmountChanged = onAmountChanged,
-            onCouponCodeChanged = onCouponCodeChanged,
-            onRegenerateCodeClick = onRegenerateCodeClick,
-            onDescriptionButtonClick = onDescriptionButtonClick,
-            onExpiryDateChanged = onExpiryDateChanged,
-            onFreeShippingChanged = onFreeShippingChanged
-        )
-        ConditionsSection(viewState, onSelectProductsButtonClick, onSelectCategoriesButtonClick)
-        UsageRestrictionsSection(onUsageRestrictionsClick)
-        WCColoredButton(
-            onClick = onSaveClick,
-            text = stringResource(id = viewState.saveButtonText),
+    Scaffold(
+        topBar = {
+            Toolbar(
+                title = viewState.screenTitle,
+                onNavigationButtonClick = { onBackPressed() },
+                navigationIcon = Icons.Default.Clear
+            )
+        }
+    ) { paddingValues ->
+        Column(
+            verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.major_100)),
             modifier = Modifier
-                .padding(horizontal = dimensionResource(id = R.dimen.major_100))
-                .fillMaxWidth(),
-            enabled = viewState.hasChanges
-        )
+                .padding(paddingValues)
+                .fillMaxSize()
+                .background(color = MaterialTheme.colors.surface)
+                .verticalScroll(scrollState)
+                .padding(vertical = dimensionResource(id = R.dimen.major_100))
+        ) {
+            DetailsSection(
+                viewState = viewState,
+                onAmountChanged = onAmountChanged,
+                onCouponCodeChanged = onCouponCodeChanged,
+                onRegenerateCodeClick = onRegenerateCodeClick,
+                onDescriptionButtonClick = onDescriptionButtonClick,
+                onExpiryDateChanged = onExpiryDateChanged,
+                onFreeShippingChanged = onFreeShippingChanged
+            )
+            ConditionsSection(viewState, onSelectProductsButtonClick, onSelectCategoriesButtonClick)
+            UsageRestrictionsSection(onUsageRestrictionsClick)
+            WCColoredButton(
+                onClick = onSaveClick,
+                text = stringResource(id = viewState.saveButtonText),
+                modifier = Modifier
+                    .padding(horizontal = dimensionResource(id = R.dimen.major_100))
+                    .fillMaxWidth(),
+                enabled = viewState.hasChanges
+            )
+        }
     }
 
     if (viewState.isSaving) {
diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/edit/EditCouponViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/edit/EditCouponViewModel.kt
index 173617dad51c..90dcc770af96 100644
--- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/edit/EditCouponViewModel.kt
+++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/edit/EditCouponViewModel.kt
@@ -258,6 +258,10 @@ class EditCouponViewModel @Inject constructor(
         isSaving.value = false
     }
 
+    fun onBackPressed() {
+        exitFlow()
+    }
+
     private fun getSaveButtonText(): Int = when (mode.value) {
         is Mode.Edit -> R.string.coupon_edit_save_button
         is Mode.Create -> R.string.coupon_create_save_button


Reviews:
user: login: kidinov
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T07:01:59Z
user: login: malinajirka
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T07:09:02Z
user: login: kidinov
body: LGTM!

A few things which you are probably aware of:
* Back animation does not match the rest of the outside of the POS activities

https://github.com/user-attachments/assets/b36f9ecc-96db-4cb2-b769-27657092cc36

* Edge-to-edge support for some reason is not good when in the "pos" mode (notice above the toolbar)

https://github.com/user-attachments/assets/cd43fba1-ccbe-4446-adb0-fa762ccd5b43

* Not related to this PR, just noticed. It looks like that 
```
private val isPOSMode: StateFlow<Boolean> =
        savedStateHandle.getStateFlow(this, navArgs.isPOSMode, "key_is_pos_mode")
```

is redundant and `navArgs.isPOSMode` will do the same, won't it?


state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T07:09:54Z
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-12T07:17:30Z
user: login: malinajirka
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T07:23:47Z

Review comments:
diff_hunk: @@ -74,7 +77,8 @@ fun EditCouponScreen(viewModel: EditCouponViewModel) {
             onUsageRestrictionsClick = viewModel::onUsageRestrictionsClick,
             onSelectProductsButtonClick = viewModel::onSelectProductsButtonClick,
             onSelectCategoriesButtonClick = viewModel::onSelectCategoriesButtonClick,
-            onSaveClick = viewModel::onSaveClick
+            onSaveClick = viewModel::onSaveClick,
+            onBackPressed = viewModel::onBackPressed,
path: WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/edit/EditCouponScreen.kt
user: login: kidinov
body: np: this probably is not onBackPress but `onClearClick` or something like that
created_at: 2025-05-12T07:01:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,7 +77,8 @@ fun EditCouponScreen(viewModel: EditCouponViewModel) {
             onUsageRestrictionsClick = viewModel::onUsageRestrictionsClick,
             onSelectProductsButtonClick = viewModel::onSelectProductsButtonClick,
             onSelectCategoriesButtonClick = viewModel::onSelectCategoriesButtonClick,
-            onSaveClick = viewModel::onSaveClick
+            onSaveClick = viewModel::onSaveClick,
+            onBackPressed = viewModel::onBackPressed,
path: WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/edit/EditCouponScreen.kt
user: login: malinajirka
body: Do we use this pattern somewhere? I mean, I realize the icon is not `back arrow`, however, in my experience the callback is usually still called the same -> technically, it should be called `onUpClicked` but we know how things are regarding back vs up in our codebase. Wdyt?
created_at: 2025-05-12T07:09:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16,10 +16,12 @@
 import androidx.compose.material.Divider
 import androidx.compose.material.Icon
 import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Scaffold
path: WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/edit/EditCouponScreen.kt
user: login: github-advanced-security[bot]
body: ## material and material3 are separate, incompatible design system libraries

Using a material import while also using the material3 library

[Show more details](https://github.com/woocommerce/woocommerce-android/security/code-scanning/85803)
created_at: 2025-05-12T07:17:30Z
author_association: NONE
diff_hunk: @@ -16,10 +16,12 @@
 import androidx.compose.material.Divider
 import androidx.compose.material.Icon
 import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Scaffold
path: WooCommerce/src/main/kotlin/com/woocommerce/android/ui/coupons/edit/EditCouponScreen.kt
user: login: malinajirka
body: Coupons flow still uses material not material3.
created_at: 2025-05-12T07:23:46Z
author_association: CONTRIBUTOR

PR comments:
user: login: wpmobilebot
created_at: 2025-05-12T06:54:58Z
body: <!-- REUSE_ID: WooCommerce-Wear-prototype-build-link -->

<details><summary>📲 You can test the changes from this Pull Request in <b>WooCommerce-Wear Android</b> by scanning the QR code below to install the corresponding build.</summary>
<table>
<tr>
  <td rowspan='6' width='260px'><img src='https://api.qrserver.com/v1/create-qr-code/?size=500x500&qzone=4&data=https%3A%2F%2Fcdn.a8c-ci.services%2F70ad533e752cbd4dcd515ed6cfb9f823d6f9f819%2Fwoocommerce-wear-prototype-build-pr14036-759dad9.apk' width='250' height='250' /></td>
  <td><b>App Name</b></td><td> WooCommerce-Wear Android</td>
</tr>
<tr><td><b>Platform</b></td><td>⌚️ Wear OS</td></tr>
<tr><td><b>Flavor</b></td><td>Jalapeno</td></tr>
<tr><td><b>Build Type</b></td><td>Debug</td></tr>
<tr><td><b>Commit</b></td><td>759dad927c0770f59c2cb9f5b7788d14cc5433bf</td></tr>
<tr><td><b>Direct Download</b></td><td><a href='https://cdn.a8c-ci.services/70ad533e752cbd4dcd515ed6cfb9f823d6f9f819/woocommerce-wear-prototype-build-pr14036-759dad9.apk'><code>woocommerce-wear-prototype-build-pr14036-759dad9.apk</code></a></td></tr>
</table>

</details>

author_association: COLLABORATOR
user: login: wpmobilebot
created_at: 2025-05-12T07:06:25Z
body: <!-- REUSE_ID: WooCommerce-prototype-build-link -->

<p>📲 You can test the changes from this Pull Request in <b>WooCommerce Android</b> by scanning the QR code below to install the corresponding build.</p>
<table>
<tr>
  <td rowspan='6' width='260px'><img src='https://api.qrserver.com/v1/create-qr-code/?size=500x500&qzone=4&data=https%3A%2F%2Fcdn.a8c-ci.services%2Fa8b6e90d9d969f1b573b3c0dd090796761ee106e%2Fwoocommerce-prototype-build-pr14036-759dad9.apk' width='250' height='250' /></td>
  <td><b>App Name</b></td><td> WooCommerce Android</td>
</tr>
<tr><td><b>Platform</b></td><td>📱 Mobile</td></tr>
<tr><td><b>Flavor</b></td><td>Jalapeno</td></tr>
<tr><td><b>Build Type</b></td><td>Debug</td></tr>
<tr><td><b>Commit</b></td><td>759dad927c0770f59c2cb9f5b7788d14cc5433bf</td></tr>
<tr><td><b>Direct Download</b></td><td><a href='https://cdn.a8c-ci.services/a8b6e90d9d969f1b573b3c0dd090796761ee106e/woocommerce-prototype-build-pr14036-759dad9.apk'><code>woocommerce-prototype-build-pr14036-759dad9.apk</code></a></td></tr>
</table>


author_association: COLLABORATOR
user: login: malinajirka
created_at: 2025-05-12T07:22:58Z
body: Thanks for the super fast review @kidinov !

> Edge-to-edge support for some reason is not good when in the "pos" mode (notice above the toolbar)

I'm not 100% sure if you are referring to the same issue. Are you referring to the dark overlay over the status bar? If you are, that's mentioned in the description and it's not related to this PR.

>Not related to this PR, just noticed. It looks like that
private val isPOSMode: StateFlow<Boolean> =
        savedStateHandle.getStateFlow(this, navArgs.isPOSMode, "key_is_pos_mode")
is redundant and navArgs.isPOSMode will do the same, won't it?

Yes, it is technically redundant, since the value is not changing. However, I think it's a good practice to approach all values inside VM using the same approach for consistency. Wdyt?

> Back animation does not match the rest of the outside of the POS activities

Good catch, I'll create an issue for this. UPD https://linear.app/a8c/issue/WOOPRD-500/woo-pos-fix-enter-and-back-animation-for-the-coupon-flow


author_association: CONTRIBUTOR
user: login: kidinov
created_at: 2025-05-12T07:34:05Z
body: > I'm not 100% sure if you are referring to the same issue. Are you referring to the dark overlay over the status bar? If you are, that's mentioned in the description and it's not related to this PR.

👍

> Yes, it is technically redundant. However, I think it's a good practice to approach all values inside VM using the same approach for consistency. Wdyt?

The way `mode` is accessed also doesn't bring any value; it adds only overhead in terms of memory and performance, on top of the extra code complexity. We don't need any flow here - there is always one value available at the time of VM creation, unless I am missing something. It's the same as 

```
class class @Inject constructor(
   isGood: Boolean
) {
  val isGoodFlow: StateFlow<Boolean> = savedStateHandle.getStateFlow(this, isGood, "is_good_key")

  fun function() { 
   if (isGoodFlow.value) {....} 
   
}

}
```

VS

```
class class @Inject constructor(
   isGood: Boolean
) {
  fun function() { 
   if (isGood) {....} 
   
}
}
```

> Good catch, I'll create an issue for this. UPD https://linear.app/a8c/issue/WOOPRD-500/woo-pos-fix-enter-and-back-animation-for-the-coupon-flow

👍
author_association: CONTRIBUTOR
user: login: malinajirka
created_at: 2025-05-12T08:00:01Z
body: > The way mode is accessed also doesn't bring any value; it adds only overhead in terms of memory and performance, on top of the extra code complexity. We don't need any flow here - there is always one value available at the time of VM creation, unless I am missing something. It's the same as

My reasoning was that if we access all navArgs using this approach, we are guaranteed to handle process death correctly. However, if we differentiate between those which are immutable and which mutable, we are at a higher risk of forgetting to use this approach when necessary (btw also what Claude suggests). Having said that, I don't think it's important, so I'll revert this change in the parent PR. https://github.com/woocommerce/woocommerce-android/pull/14031/commits/edc30ba4d4a2f817e98c8fdd0601ac4c800533b0


author_association: CONTRIBUTOR