Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/update launch send modal mechanism #16718

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Khushboo-dev-cpp
Copy link
Contributor

@Khushboo-dev-cpp Khushboo-dev-cpp commented Nov 5, 2024

What does the PR do

Changes the SendModal launching mechanish.

Update signals in Global for openSendModal to be parameterised, instead of having to pass sendModal instance across the app.

Affected areas

SendModal Invocations points from-

  1. Wallet footer
  2. Asset/Collectibles context menu
  3. Asset/Collectibles details view
  4. Send Via 1-1 chat
  5. TokenOwnership flows
  6. Profile showcase
  7. Ens name
  8. Stickers
  9. Saved address (wallet + settings)
  10. History view and Transaction details

Architecture compliance

Screenshot of functionality (including design for comparison)

  • I've checked the design and this PR matches it
SendVia1-1Chat.mov
TransferOwnership.mov
TransactionsHistorySend.mov
Screen.Recording.2024-11-05.at.9.49.43.PM.mov
profileShowcaseMenu.mov
BuyStickers.mov
MainWalletSavedAddresses.mov
MainWalletViewSend.mov
RegisterEns.mov

@Khushboo-dev-cpp Khushboo-dev-cpp force-pushed the feat/UpdateLaunchSendModalMechanism branch from 379561d to db6dafa Compare November 5, 2024 18:56
@status-im-auto
Copy link
Member

status-im-auto commented Nov 5, 2024

Jenkins Builds

Click to see older builds (54)
Commit #️⃣ Finished (UTC) Duration Platform Result
✔️ db6dafa #2 2024-11-05 19:04:00 ~6 min macos/aarch64 🍎dmg
✔️ db6dafa #2 2024-11-05 19:04:40 ~7 min tests/nim 📄log
✔️ db6dafa #2 2024-11-05 19:09:05 ~11 min tests/ui 📄log
✔️ db6dafa #2 2024-11-05 19:16:43 ~19 min linux-nix/x86_64 📦tgz
✔️ db6dafa #2 2024-11-05 19:17:52 ~20 min linux/x86_64 📦tgz
✔️ db6dafa #2 2024-11-05 19:25:17 ~28 min windows/x86_64 💿exe
✔️ 9845809 #3 2024-11-05 20:02:49 ~5 min macos/aarch64 🍎dmg
✔️ 9845809 #3 2024-11-05 20:05:55 ~8 min tests/nim 📄log
✔️ 9845809 #3 2024-11-05 20:09:58 ~12 min macos/x86_64 🍎dmg
✔️ 9845809 #3 2024-11-05 20:10:05 ~12 min tests/ui 📄log
✔️ 9845809 #3 2024-11-05 20:14:48 ~17 min linux/x86_64 📦tgz
✔️ 9845809 #3 2024-11-05 20:17:46 ~20 min linux-nix/x86_64 📦tgz
✔️ ab5fe7f #4 2024-11-06 09:24:19 ~4 min macos/aarch64 🍎dmg
✔️ ab5fe7f #4 2024-11-06 09:27:30 ~7 min tests/nim 📄log
✔️ ab5fe7f #4 2024-11-06 09:30:46 ~11 min macos/x86_64 🍎dmg
ab5fe7f #4 2024-11-06 09:30:56 ~11 min tests/ui 📄log
✔️ 5c1e2e7 #5 2024-11-06 09:37:07 ~4 min macos/aarch64 🍎dmg
✔️ 5c1e2e7 #5 2024-11-06 09:40:39 ~7 min tests/nim 📄log
✔️ 5c1e2e7 #5 2024-11-06 09:43:06 ~10 min macos/x86_64 🍎dmg
5c1e2e7 #5 2024-11-06 09:44:32 ~11 min tests/ui 📄log
✔️ 5c1e2e7 #5 2024-11-06 09:52:14 ~19 min linux/x86_64 📦tgz
✔️ 5c1e2e7 #5 2024-11-06 09:53:21 ~20 min linux-nix/x86_64 📦tgz
✔️ 5c1e2e7 #6 2024-11-06 10:16:39 ~13 min tests/ui 📄log
✔️ d389808 #6 2024-11-07 10:48:49 ~5 min macos/aarch64 🍎dmg
✔️ d389808 #6 2024-11-07 10:52:17 ~8 min tests/nim 📄log
✔️ d389808 #6 2024-11-07 10:55:19 ~11 min macos/x86_64 🍎dmg
✔️ d389808 #7 2024-11-07 10:56:36 ~12 min tests/ui 📄log
✔️ d389808 #6 2024-11-07 11:01:47 ~18 min linux/x86_64 📦tgz
✔️ d389808 #6 2024-11-07 11:01:52 ~18 min linux-nix/x86_64 📦tgz
✔️ d389808 #6 2024-11-07 11:11:31 ~27 min windows/x86_64 💿exe
✔️ dce2237 #7 2024-11-21 16:02:30 ~5 min macos/aarch64 🍎dmg
✔️ dce2237 #7 2024-11-21 16:05:18 ~8 min tests/nim 📄log
✔️ dce2237 #7 2024-11-21 16:09:17 ~12 min macos/x86_64 🍎dmg
✔️ dce2237 #8 2024-11-21 16:09:46 ~12 min tests/ui 📄log
✔️ dce2237 #7 2024-11-21 16:12:34 ~15 min linux/x86_64 📦tgz
✔️ dce2237 #7 2024-11-21 16:19:03 ~21 min linux-nix/x86_64 📦tgz
✔️ 943ab34 #8 2024-11-22 09:21:04 ~4 min macos/aarch64 🍎dmg
✔️ 943ab34 #8 2024-11-22 09:24:52 ~8 min tests/nim 📄log
✔️ 943ab34 #8 2024-11-22 09:27:33 ~11 min macos/x86_64 🍎dmg
✔️ 943ab34 #9 2024-11-22 09:29:39 ~13 min tests/ui 📄log
✔️ 943ab34 #8 2024-11-22 09:32:37 ~16 min linux/x86_64 📦tgz
✔️ 943ab34 #8 2024-11-22 09:36:05 ~19 min linux-nix/x86_64 📦tgz
✔️ 139c6ea #9 2024-11-26 17:02:58 ~5 min macos/aarch64 🍎dmg
✔️ 139c6ea #9 2024-11-26 17:06:02 ~8 min tests/nim 📄log
✔️ 139c6ea #10 2024-11-26 17:10:10 ~12 min tests/ui 📄log
✔️ 139c6ea #9 2024-11-26 17:10:21 ~12 min macos/x86_64 🍎dmg
✔️ 139c6ea #9 2024-11-26 17:15:18 ~17 min linux/x86_64 📦tgz
✔️ 139c6ea #9 2024-11-26 17:18:00 ~20 min linux-nix/x86_64 📦tgz
✔️ 0638cf0 #10 2024-11-27 09:20:56 ~6 min macos/aarch64 🍎dmg
✔️ 0638cf0 #10 2024-11-27 09:22:15 ~7 min tests/nim 📄log
✔️ 0638cf0 #11 2024-11-27 09:25:50 ~11 min tests/ui 📄log
✔️ 0638cf0 #10 2024-11-27 09:26:17 ~11 min macos/x86_64 🍎dmg
✔️ 0638cf0 #10 2024-11-27 09:32:19 ~17 min linux/x86_64 📦tgz
✔️ 0638cf0 #10 2024-11-27 09:33:42 ~19 min linux-nix/x86_64 📦tgz
Commit #️⃣ Finished (UTC) Duration Platform Result
✔️ 061393c #11 2024-11-29 20:57:41 ~7 min macos/aarch64 🍎dmg
✔️ 061393c #11 2024-11-29 20:57:52 ~7 min tests/nim 📄log
✔️ 061393c #11 2024-11-29 21:02:15 ~11 min macos/x86_64 🍎dmg
✔️ 061393c #12 2024-11-29 21:02:32 ~12 min tests/ui 📄log
✔️ 061393c #11 2024-11-29 21:06:43 ~16 min linux-nix/x86_64 📦tgz
✔️ 061393c #11 2024-11-29 21:07:47 ~17 min linux/x86_64 📦tgz
✔️ 34ae4ae #12 2024-11-29 21:27:20 ~5 min macos/aarch64 🍎dmg
✔️ 34ae4ae #12 2024-11-29 21:29:43 ~7 min tests/nim 📄log
✔️ 34ae4ae #12 2024-11-29 21:33:20 ~11 min macos/x86_64 🍎dmg
✔️ 34ae4ae #13 2024-11-29 21:33:37 ~11 min tests/ui 📄log
✔️ 34ae4ae #12 2024-11-29 21:39:24 ~17 min linux-nix/x86_64 📦tgz
✔️ 34ae4ae #12 2024-11-29 21:40:18 ~18 min linux/x86_64 📦tgz

@Khushboo-dev-cpp Khushboo-dev-cpp force-pushed the feat/UpdateLaunchSendModalMechanism branch from db6dafa to 9845809 Compare November 5, 2024 19:57
@Khushboo-dev-cpp Khushboo-dev-cpp marked this pull request as ready for review November 6, 2024 09:18
@Khushboo-dev-cpp Khushboo-dev-cpp requested review from Cuteivist and removed request for a team November 6, 2024 09:18
@Khushboo-dev-cpp Khushboo-dev-cpp force-pushed the feat/UpdateLaunchSendModalMechanism branch from 9845809 to ab5fe7f Compare November 6, 2024 09:19
@Khushboo-dev-cpp Khushboo-dev-cpp force-pushed the feat/UpdateLaunchSendModalMechanism branch from ab5fe7f to 5c1e2e7 Compare November 6, 2024 09:32
@Khushboo-dev-cpp Khushboo-dev-cpp force-pushed the feat/UpdateLaunchSendModalMechanism branch from 5c1e2e7 to d389808 Compare November 7, 2024 10:43
Copy link
Member

@caybro caybro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be done in a much easier way :)

ui/app/mainui/ToastsManager.qml Outdated Show resolved Hide resolved
ui/imports/utils/Global.qml Outdated Show resolved Hide resolved
ui/app/mainui/AppMain.qml Outdated Show resolved Hide resolved
ui/imports/shared/views/HistoryView.qml Outdated Show resolved Hide resolved
@Khushboo-dev-cpp Khushboo-dev-cpp force-pushed the feat/UpdateLaunchSendModalMechanism branch from d389808 to dce2237 Compare November 21, 2024 15:56
Copy link
Member

@micieslak micieslak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm glad we are going to sort out launching SendModal from multiple places. It's about specific modal but the way we do it should be general and as good as possible, example for another similar cases. After initial review I have several thoughts/propositions regarding architecture I'd like to share. Please wait with merging, will provide details asap.

@Khushboo-dev-cpp Khushboo-dev-cpp force-pushed the feat/UpdateLaunchSendModalMechanism branch from dce2237 to 943ab34 Compare November 22, 2024 09:16
Copy link
Member

@caybro caybro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks better now!

I just realized though that you're touching a lot of views/popups (mainly removing the sendModal param) but you haven't updated any storybook page; almost sure some of them will be broken now :)

ui/app/mainui/ToastsManager.qml Outdated Show resolved Hide resolved
@Khushboo-dev-cpp
Copy link
Contributor Author

Looks better now!

I just realized though that you're touching a lot of views/popups (mainly removing the sendModal param) but you haven't updated any storybook page; almost sure some of them will be broken now :)

I dont think it will matter cause we dont test launch of send modal from the different places in the storybook right? also I thought the UI tests would complain had I missed something.

Copy link
Member

@micieslak micieslak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will try to explain what are the problems with the current approach used here (and already used in other places of the app), and also propose some solutions for further discussion.

Let's imagine that we want to add some additional parameter to our send modal which is relevant for only one location requesting the modal (e.g. WalletLayout). For other locations this parameter is irrelevant, default, fixed value can be always used.

How we could introduce that change now? We need to add that parameter to the Global.launchSendWithParams in order to use it the mentioned place. Unfortunately we need to search through the whole app and refactor all calls to Global.launchSendWithParams by providing default argument there. It's necessary to keep the signal emit working, even thought all those places don't care about our new parameter.

It's typical example of OCP (open-closed principle) violation. We want to extend in one place, but due to used approach, we need to modify multiple logically unrelated places.

Wrapping the signal invocation into a function to maintain default arguments there, as proposed by @caybro reduces the problem somewhat but does not eliminate it completely. When we want to provide the last argument, we need to provide all earlier arguments even though they are irrelevant for us.

Ok, how we could change it?

Let's take a look at call to Global.launchSendWithParams in MessageView.qml:

Global.launchSendWithParams(Constants.SendType.Transfer, //sendType
                     "", //senderAddress
                     "", //tokenId
                     Constants.TokenType.Unknown, //tokenType
                     "", //tokenAmount
                     0, //chainId
                     addressOrEns //recipientAddress
                     )

the only param that is relevant for here is recipientAddress. But we are forced to provide all parameters up to the last one that we really need.

From MessageView perspective we just want to do some send to a given address. This component shouldn't care how it will be done, especially that it can be done in various ways depending on the context. But here we provide some other parameters only because they are used in some other parts of the app. And we configure the component handling the action by providing specific send type, used by SendModal.

So how it should be done to avoid those problems?

The simplest options seems to be applicable here. Instead of the call above, we could just emit `sendRequested(addressOrEns).

where the signal is defined as

signal sendRequested(string addressOrEns)

The consequence is necessity to propagate the signal to the place where it's finally handled. E.g. in case of the MessageView the path may be relatively long:

ChatMessagesView -> ChatMessagesView -> ChatContentView -> ChatColumnView -> ChatView -> ChatLayout -> AppMain

However it shouldn't be considered as a drawback because those components indeed request send and it's better to do it in an explicit way.

The signature may change on that way. If there is some other component in the hierarchy requesting send with other parameters e.g. with custom amount, the parent (containing both that component and MessageView), will emit signal of following signature then: signal sendRequested(string addressOrEns, string amount) as both params are needed here to propagate sendRequested from both sub-components.

Even more interesting situation we have for components like OverviewSettingsPanel, where we call Global.openTransferOwnershipPopup:

Global.openTransferOwnershipPopup(root.communityId,
              root.name,
              root.logoImageData,
              {
                  key: "0",
                  privilegesLevel: root.ownerToken.privilegesLevel,
                  chainId: root.ownerToken.chainId,
                  name: root.ownerToken.name,
                  artworkSource: root.ownerToken.image,
                  accountAddress: root.ownerToken.accountAddress.toLowerCase(),
                  tokenAddress: root.ownerToken.tokenAddress.toLowerCase()
              })

this whole invocation could be replaced just by: ownershipRequestedRequested() signal. No parameters are needed because all of them are known to the entity instantiating OverviewSettingsPanel. There is no need to bypass all those parameters back to the caller via a signal. Moreover obtaining arguments may be completely detached from this component and it's parent, because only community id is needed to fetch rest of the info from proper model, higher in the hierarchy.

Thanks to that if we change the way of handling transfer, the OverviewSettingsPanel will stay intact because requesting side (OverviewSettingsPanel) is not tightly coupled with the handling side (TransferOwnershipPopup), which may be modified, e.g. to require more params, or. .e.g. completely replaced by other solution.

I think we should study this case deliberately because we need such mechanism designed carefully for this and other modals, potentially also menus, notifications and others. And loose coupling should be treated seriously to keep the app organized and easily maintainable when growing.

Reorganizing this part may cost some time but careful design should pay-off quickly.

@Khushboo-dev-cpp @alexjba @caybro please share your opinions :)

@Khushboo-dev-cpp
Copy link
Contributor Author

there is some other component in the hierarchy requesting send with other parameters e.g. with custom amount, the p

Hey @micieslak definitely something I thought of too, about the params list increasing and a call to launch send modal become more and more complex with a chance of missing something and in turn causing errors too.

The idea of emitting signals relative to the place it is called from and passing it down the qml hierarchy, is something which at first glance I feel like its an overhead. Also in case MessageVIew and some other component in the same chain want to launch send modal with different params but same type for example

openSend(var recipient)
openSend(var sender)

I dont think its possible to have 2 signals like this right?

@caybro
Copy link
Member

caybro commented Nov 25, 2024

there is some other component in the hierarchy requesting send with other parameters e.g. with custom amount, the p

Hey @micieslak definitely something I thought of too, about the params list increasing and a call to launch send modal become more and more complex with a chance of missing something and in turn causing errors too.

The idea of emitting signals relative to the place it is called from and passing it down the qml hierarchy, is something which at first glance I feel like its an overhead. Also in case MessageVIew and some other component in the same chain want to launch send modal with different params but same type for example

openSend(var recipient) openSend(var sender)

I dont think its possible to have 2 signals like this right?

Nope, it's not even possible to have a signal with different params and same name

@alexjba
Copy link
Contributor

alexjba commented Nov 26, 2024

Nice analysis @micieslak! Glad you've raised it and I totally agree that it would be great to have a discussion on this.

I'm not convienced though that reorganizing the code to favor signal-handler chains from the leaf to appMain over Globalis the better approach.

I'll favor the approach proposed by @caybro. Looks to me like it will reduce the boilerplate code needed to forward the signal from one leaf component all the way to AppMain. It's reducing the bug surface, implementation/debugging time and we'll also have loosely coupled components. We already have the Global emitter and I don't see it as a bad approach per se.

There are some caveats though, well described by @micieslak. But maybe we can solve it in the Global emitter itself, not necessarily to change the approach.

I see two main issues:

  1. Opening the same dialogue with different configurations. WDYT of the function approach specialized for a specific configuration. We can also use specialized signals if needed.
function sendTo(address) {
      root.openSendModal("", 0, 0, address, "")
}

function sendFrom(address) {
     root.openSendModal("", 0, 0, "", address)
}

function sendAmountTo(amount, token, address) {
    root.openSendModal("", amount, token, address, "")
}
// and so on..
  1. Sending too much information in the signal. We should definitely avoid this and stick to keys, user input and const data. Basically only data that cannot change in the lifetime of the modal.

One good example would be the call below. Here we have 2 issues. The first one is that the modal itself becomes a time bomb and could crash the app on a community tokens model change. The second one is that the name and logoImageData will be static in the modal.

Global.openTransferOwnershipPopup(root.communityId,
                                    root.name,
                                    root.logoImageData,
                                    root.ownerToken,
                                    root.sendModalPopup)

@Khushboo-dev-cpp Khushboo-dev-cpp force-pushed the feat/UpdateLaunchSendModalMechanism branch from 139c6ea to 0638cf0 Compare November 27, 2024 09:14
@Khushboo-dev-cpp
Copy link
Contributor Author

Nice analysis @micieslak! Glad you've raised it and I totally agree that it would be great to have a discussion on this.

I'm not convienced though that reorganizing the code to favor signal-handler chains from the leaf to appMain over Globalis the better approach.

I'll favor the approach proposed by @caybro. Looks to me like it will reduce the boilerplate code needed to forward the signal from one leaf component all the way to AppMain. It's reducing the bug surface, implementation/debugging time and we'll also have loosely coupled components. We already have the Global emitter and I don't see it as a bad approach per se.

There are some caveats though, well described by @micieslak. But maybe we can solve it in the Global emitter itself, not necessarily to change the approach.

I see two main issues:

  1. Opening the same dialogue with different configurations. WDYT of the function approach specialized for a specific configuration. We can also use specialized signals if needed.
function sendTo(address) {
      root.openSendModal("", 0, 0, address, "")
}

function sendFrom(address) {
     root.openSendModal("", 0, 0, "", address)
}

function sendAmountTo(amount, token, address) {
    root.openSendModal("", amount, token, address, "")
}
// and so on..
  1. Sending too much information in the signal. We should definitely avoid this and stick to keys, user input and const data. Basically only data that cannot change in the lifetime of the modal.

One good example would be the call below. Here we have 2 issues. The first one is that the modal itself becomes a time bomb and could crash the app on a community tokens model change. The second one is that the name and logoImageData will be static in the modal.

Global.openTransferOwnershipPopup(root.communityId,
                                    root.name,
                                    root.logoImageData,
                                    root.ownerToken,
                                    root.sendModalPopup)

Hey @alexjba yesterday I had a discussion with Michal and thought that the pasing signal down to Appmain is perhaps the best solution thus far. I have tried to show it here in this commit 0638cf0 so we can discuss if we like this approach or not and move forward accordingly.

I like the idea of multiple global signals too, however, s Michal pointed out, this introduces the need for a smaller component to know what should be done for the action it wants, and maybe thats best abstracted and handled elsewhere instead.

@caybro
Copy link
Member

caybro commented Nov 27, 2024

Nice analysis @micieslak! Glad you've raised it and I totally agree that it would be great to have a discussion on this.
I'm not convienced though that reorganizing the code to favor signal-handler chains from the leaf to appMain over Globalis the better approach.
I'll favor the approach proposed by @caybro. Looks to me like it will reduce the boilerplate code needed to forward the signal from one leaf component all the way to AppMain. It's reducing the bug surface, implementation/debugging time and we'll also have loosely coupled components. We already have the Global emitter and I don't see it as a bad approach per se.
There are some caveats though, well described by @micieslak. But maybe we can solve it in the Global emitter itself, not necessarily to change the approach.
I see two main issues:

  1. Opening the same dialogue with different configurations. WDYT of the function approach specialized for a specific configuration. We can also use specialized signals if needed.
function sendTo(address) {
      root.openSendModal("", 0, 0, address, "")
}

function sendFrom(address) {
     root.openSendModal("", 0, 0, "", address)
}

function sendAmountTo(amount, token, address) {
    root.openSendModal("", amount, token, address, "")
}
// and so on..

Yeah, that's the approach I'd prefer...

Hey @alexjba yesterday I had a discussion with Michal and thought that the passing signal down to Appmain is perhaps the best solution thus far. I have tried to show it here in this commit 0638cf0 so we can discuss if we like this approach or not and move forward accordingly.

I'm not a big fan of those long call chains, it's very error prone, esp. when you want to refactor the components in the middle, or add a new chain. That said, I think the SendModal itself should go to Popups.qml (just like any other shared modal), there's no need to instantiate it AppMain.qml. Then, combined with the above purpose-driven functions/signals, we can avoid the clutter of having a huge signal that contains every possible parameter. One objection that @micieslak had to this approach was that Popups.qml is already too big -> I proposed to split it into two: WalletPopups.qml (wallet related stuff) and Popups.qml (everything else), the usage would remain the same. Wdyt? :)

@alexjba
Copy link
Contributor

alexjba commented Nov 27, 2024

Hey @alexjba yesterday I had a discussion with Michal and thought that the pasing signal down to Appmain is perhaps the best solution thus far. I have tried to show it here in this commit 0638cf0 so we can discuss if we like this approach or not and move forward accordingly.

I don't mean to block your work here by extending the discussion! 😄 Glad you've reached an agreement to move forward! I'm all for it!

@micieslak has a good point and IMO it's not really about this PR here, but about how do we want to structure the "global" signals. It's more of a general issue with the Global and Popups we didn't pay much attention to. I think it worth continuing the discussion and getting an agreement outside of the scope of this PR.

@Khushboo-dev-cpp
Copy link
Contributor Author

Khushboo-dev-cpp commented Nov 27, 2024

Yeah, that's the approach I'd prefer...

Hey @caybro I have moved SendModal to Popups.qml for now. Discussed with Michal and maybe we still need to thin about how we wish to split this further.

I also like the approach of having multi signals better than passing signal along the whole chain. But I am open to whichever we think is best.

@micieslak any objections on having multi functions defined as per scope instead?

@Khushboo-dev-cpp
Copy link
Contributor Author

Hey @alexjba yesterday I had a discussion with Michal and thought that the pasing signal down to Appmain is perhaps the best solution thus far. I have tried to show it here in this commit 0638cf0 so we can discuss if we like this approach or not and move forward accordingly.

I don't mean to block your work here by extending the discussion! 😄 Glad you've reached an agreement to move forward! I'm all for it!

@micieslak has a good point and IMO it's not really about this PR here, but about how do we want to structure the "global" signals. It's more of a general issue with the Global and Popups we didn't pay much attention to. I think it worth continuing the discussion and getting an agreement outside of the scope of this PR.

Not at all. @alexjba I think its important we have this discussion so that we implement things the right way. I also prefer the multi function handling instead. Lets see what MIchal has to say.

@@ -40,6 +40,9 @@ Item {
signal changePubKey(ensUsername: string)
signal goToWelcome();
signal goToList();
signal connectUsername(string publicKey, string ensName)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd strongly recommend to follow convention with *Requested at the end, connectUsernameRequested. For consistency and to underline it's just a request at this stage, not the actual action.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whatever is known externally, should be skipped in the signal. So publicKey is not needed as it's always own pubkey and the caller "knows" that.

Comment on lines 261 to 284
onConnectUsername: root.launchSendModal(Constants.SendType.ENSSetPubKey /*sendType*/,
"" /*senderAddress*/,
Constants.ethToken /*tokenId*/,
Constants.TokenType.ERC20 /*tokenType*/,
LocaleUtils.numberToLocaleString(0) /*tokenAmount*/,
selectedChainId /*chainId*/,
publicKey,
ensName)
onRegisterUsername: root.launchSendModal(Constants.SendType.ENSRegister /*sendType*/,
"" /*senderAddress*/,
!!d.sntToken && !!d.sntToken.symbol ? d.sntToken.symbol: "" /*tokenId*/,
Constants.TokenType.ERC20 /*tokenType*/,
LocaleUtils.numberToLocaleString(10) /*tokenAmount*/,
selectedChainId /*chainId*/,
publicKey,
ensName)
onReleaseUsernameRequested: root.launchSendModal(Constants.SendType.ENSRelease /*sendType*/,
senderAddress,
Constants.ethToken /*tokenId*/,
Constants.TokenType.Native /*tokenType*/,
LocaleUtils.numberToLocaleString(0) /*tokenAmount*/,
selectedChainId /*chainId*/,
publicKey,
ensName)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be more pragmatic to just re-emit those signals there:

So, in public api:

signal connectUsernameRequested(string ensName)
signal registerUsernameRequested(string ensName)
signal releaseUsernameRequested(string senderAddress, string ensName)

and here just:

onConnectUsernameRequested: root.connectUsernameRequested(ensName)
// same for other two

Comment on lines 1634 to 1642
onLaunchSendModal: {
let ensUsernamesStore = appMain.rootStore.profileSectionStore.ensUsernamesStore
popups.openSendModal(sendType, senderAddress, tokenId, tokenType, tokenAmount, chainId,
ensUsernamesStore.getEnsRegisteredAddress() /*recipientAddress*/,
SendPopups.Helpers.RecipientAddressObjectType.Address /*recipientType*/,
false /*onlyAssets*/,
false /*interactive*/,
publicKey, ensName)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming that ProfileLayout has:

signal connectUsernameRequested(string ensName)
signal registerUsernameRequested(string ensName)
signal releaseUsernameRequested(string senderAddress, string ensName)

we could have sth like that here:

onConnectUsernameRequested: popups.connectUsername(ensName)

Then all the final calls to openSendModal (the one with the full list of parameters for send modal) can be done only in Popups (and at some point later maybe in dedicated component controlling all use cases of send modal). openSendModal may be kept as a private method of Popups.

It also allows to encapsulate any additional actions in Popups, like obtaining addtional info from some stores or models (like let ensUsernamesStore = appMain.rootStore.profileSectionStore.ensUsernamesStore).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, especially when switching from Popups to dedicated component, we will have one place handling all operations that are intended to be done via send modal. Only here we obtain additional data needed for send modal, only here we configure the actual send modal. Whenever we need to refactor usages of send modal, everything is basically in one place then.

Copy link
Contributor Author

@Khushboo-dev-cpp Khushboo-dev-cpp Nov 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm so we do end up having multiple function on the Popups.qml file for launching send. How do we feel about those being calls to specific Global function instead so that we dont have to pass the information along the whole chain? which is then inline with with Alex and Lukas also mentioned?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functions are more flexible, because you can default the arguments, whereas for signals, you must spell all of them out. Otherwise connecting a signal to another signal is fine for sure

@micieslak
Copy link
Member

Thank you @Khushboo-dev-cpp @alexjba and @caybro for this in-depth discussion.

I'd like to summarize what we concluded so far and go bit deeper into details from perspective of compliance with SOLID.

We have two approaches on the table:

  1. Emitting signals notifying about the intent in form of doSomethingRequested(...), with parameters carrying only info that is not available outside, like key of clicked item when the component maintains a list. On the upper level the signal can be re-emitted as it is, re-emitted in different form (e.g. with additional parameter to differentiate requests from multiple children), handled or just ignored if not relevant.
  2. Instead of emitting signal with the intent, we call Global.doSomething(...) with the same parameters as in the first case. Those functions are intended to be specialized to take only parameters that are necessary to be passed by the caller. We want to avoid multi-purpose functions to limit number of parameters and to avoid forcing caller to pass arguments not relevant from the caller point of view. In this approach the request don't have to be propagated, reducing number of potentially repetitive signals in multiple components.

The latter one, at least at first glance seems to be concise and elegant. However there are some caveats that we should keep in mind, especially if we want to introduce app-wise mechanism reusable in various scenarios, respecting all SOLID rules.

Let's consider very simple scenario:

// A.qml

Item {
    Button {
        onClicked: Global.doSomething(someArg)
    }
}

Assume that we have that component A used somewhere in the app, and the action is also already handled (e.g. doSomething emits some function with additional params and it's handled in component like our Popups).

And now, design for new functionality arrived and it turns out that we need to reuse our component A but in different part of the app, in different context. And on click we want different action now. Instead e.g. modal invoked by call to Global.doSomething in this new usage we want to trigger a very simple model with info that it's not supported here from this place (or whatever else).

How we can do that?

Probably the only option is to do it as follows:

// A.qml

Item {
    property bool isDoSomethingSupported
    Button {
        onClicked: Global.doSomething(someArg, isDoSomethingSupported)
    }
}

So in order to change behavior external for component A, we need to change A itself. Moreover we need to change signature and implementation of Global.doSomething because we are already forced to handle it via this method, there is no choice as it's already used in A's internals (ok, we could leave it as it is and handle that special case directly in A making more changes there, but it's not cleaner anyways). The next place we need to modify are existing usages of A to add proper value of isDoSomethingSupported. In simple case it could be avoided by defining neutral default value, but in more complex cases it may be not possible and than we are force to modify multiple usages across the app.

From SOLID PoV it's clear violation of OCP - we cannot extend (implementing new views) without modifying existing code (A, Global).
Also SRP is violated because A to some degree decides how the action, intended to be handled externally, is handled by calling specific global method without possibility of replacement. For A it's additional responsibility that shouldn't be on it.

So if our target is to decompose the app into set of isolated, independent and reusable component, this example is contradiction of all 3 (and OCP and OCR, first 2 points of solid).

In comparison, none of these problems occur in the first approach (emitting signals notifying intent).


Let's go back to the moment where we need to use our exemplary component A in new place. Let's assume that A is not written by us. How we can learn about what that component offer/do? We have several options:

  1. Read the docs
  2. Read the API (when well designed is kind of self documenting code and good source to learn about the component)
  3. Read the entire code of the component and sub-components (for components with well designed api it shouldn't be necessary)

Ideally 1 and 2 should be enough to learn everything is needed to use that component (example: we don't need to read source code of ListView to successfully use it).
To make it true, the fact that A calls Global.doSomething should be documented, no matter if the call is done by A directly or by some sub-component somewhere in the hierarchy. But... it's easy to imagine how cumbersome would be maintaining such documentation and how easily it could become outdated. So, as a result, if it's not in docs we could rely on, there is no other way than read the implementation to discover those hidden interactions. It's easier for developers with experience with that codebase, we know what to look for, but may be nightmare for developers starting work with status-desktop.

In comparison in the first approach the user of A can rely on the API, because all interactions with other parts of the app are visible in the API (sth like signal doSomethingRequested(...). The API of A is longer but it precisely informs the user what actions should be expected and handled. The code may seem more "boring" because same signal may be in A and repeated e.g. in C which is using A but not handling (and not intentionally ignoring) the signal from A, just re-emitting it. But in fact that action is part of API of those two components and hiding it brings more drawbacks than benefits.

So once again, if the target is to have well-defined components, first approach supports it much better because enforces APIs with all interactions listed explicitly.

I'm not sure about the argument that this solution (1st one) is more error-prone because of signals propagation. By reducing number of parameters (the handling side is responsible to fetch whatever is needed from proper models) we already reduce the need of frequent changes. If the signal in low-level component needs to be changed, we need to refactor the whole chain up, to the location where it's handled. But those components are actually really affected and that change should be explicitly visible in their API. In the case in this PR the chain is long indeed because it goes to the AppMain, but in many cases it should be shorter, especially when we decompose Popups into smaller components controlling instantiation specific modals or groups of related modals. Then for e.g. popups used only in settings we can keep instance of sth like SettingsPopups in e.g. SettingsLayout. And ofc it's not limited to modals, it's intended to be general mechanism for notifying intent. The intent may be handles by modal one day but after design updated it may e.g. trigger changing tab in the current view. And the emitter of the intent should care about that (should be no need to modify).

Yeah, in the current codebase, where many top-level components are super messy, adding new signals in order to re-emit sth from sub-components in order to improve architecture may seem counter-intuitive. But I think explicit APIs are always better than some shortcuts done somewhere in internals, and in long term it will result in much better code structure.

@alexjba @caybro @Khushboo-dev-cpp please consider those arguments. My choice is clear, I'm voting for solution in spirit of SOLID because it usually pays off quickly. I provided example against solution 2 which is possible in practice, but this is not exhaustive, I could describe several other problems. The verbosity of propagation is not a problem for me because it's needed for well-formed api. On the other hand I may be missing something similarly problematic but in solution 1. If you see sth please share!

@alexjba
Copy link
Contributor

alexjba commented Nov 29, 2024

@micieslak @caybro @Khushboo-dev-cpp WDYT if we move the discussion in a meeting? Would nice be for the discussion to result in a clear path forward, with some tasks and an action plan. We all agree there are some issues to address, but I think it's no longer in the PR scope!

My suggestion is to unblock @Khushboo-dev-cpp with this PR until we'll get an agreement. We can always revisit if needed!

Copy link
Contributor

@alexjba alexjba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the potential crash mentioned below needs to be addressed regardless of the popup launch approach.


signal cancelClicked
signal sendRequested(var sendType, var senderAddress, var tokenId, var tokenType)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
signal sendRequested(var sendType, var senderAddress, var tokenId, var tokenType)
signal sendRequested(int sendType, string senderAddress, string tokenId, int tokenType)

property EnsUsernamesStore ensUsernamesStore
property string username: ""

signal backBtnClicked()
signal registerUsername()

QtObject {
id: d
readonly property var sntToken: ModelUtils.getByKey(root.accountAssetsModel, "tokensKey", root.ensUsernamesStore.getStatusTokenKey())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is dangerous. In case of a model reset the app could crash.
ModelEntry would help here. Same in ui/app/AppLayouts/Profile/ProfileLayout.qml

@Khushboo-dev-cpp Khushboo-dev-cpp force-pushed the feat/UpdateLaunchSendModalMechanism branch from 0638cf0 to 061393c Compare November 29, 2024 20:50
@Khushboo-dev-cpp Khushboo-dev-cpp requested a review from a team as a code owner November 29, 2024 20:50
… architecture guidelines.

After this change there is not need to pass sendModal instance from AppMain to other parts of app.
Then SendModal should be launched simply by calling Global.openSendModal(....)
@Khushboo-dev-cpp Khushboo-dev-cpp force-pushed the feat/UpdateLaunchSendModalMechanism branch from 061393c to 34ae4ae Compare November 29, 2024 21:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants