-
Notifications
You must be signed in to change notification settings - Fork 121
[Woo POS][Local Catalog] Loading screen for missing catalog when launching POS #16272
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
[Woo POS][Local Catalog] Loading screen for missing catalog when launching POS #16272
Conversation
|
|
joshheald
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It also made me think of creating a task for caching the eligibility check, and if the POS becomes ineligible, just showing a full-screen overlay. It's an unlikely scenario, and it's better to show POS immediately to give a fast execution impression.
Eligibility for POS, or for local catalog? For the latter, see my latest PR, it moves towards that and has an element of caching. We fall back to the network based approach if they're ineligible. #16276
I've had some initial issues when testing this:
- Catalogs over 1000 items attempt to sync, and never leave the syncing state when it fails. For these sites, we shouldn't even attempt the sync, but this seems to be solved if you merge the PR I mentioned above.
- Syncing catalog can complete, but not transition to the item list, it just spins forever. This happened when I tried to sync an eligible store after opening POS for a store with too many products for local catalog, so I think there may be some state leaked?
joshheald
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works well, at least when merged with #16276, but I've left some questions/thoughts in line about another way we could approach it which might be easier
| Spacer() | ||
| ProgressView() | ||
| .progressViewStyle(POSProgressViewStyle()) | ||
| .matchedGeometryEffect(id: animation.progressTransitionId, in: animation.namespace, properties: .position) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about using this existing LoadingView for local catalog syncs as well, rather than making a new view? That way we could set text fields to show, but keep using the same loading indicator and avoid visual glitches...
I think it would be a case of adding associated values to the containerState's loading enum case, perhaps?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that wouldn;t help with visual glitches.
We transition from PointOfSaleLoadingView within EntryPoint to PointOfSaleLoadingView within Dashboard, and when we add the text, the progress view moves up since, according to the designs, the whole container is centered, not the progress view. I tried centering only the progress view, but that doesn't look right.
But yes, we could keep one view that does more. We would still need to pass matched geometry from the EntryPoint to the Dashboard.
| Task { @MainActor [weak self] in | ||
| guard let self else { return } | ||
|
|
||
| catalogSyncState = await coordinator.lastFullSyncState(for: siteID) | ||
|
|
||
| for await state in coordinator.fullSyncStateStream { | ||
| catalogSyncState = state | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this task might never end, and therefore be a memory leak. It doesn't capture self, so no retain cycle, but we'll have the task hanging out for each time we leave and enter POS. Perhaps we should keep hold of it, and cancel it when we deinit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it doesn't end since we want an endless stream. Good point on a memory leak. Need to be careful with this 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AsyncStream may not be what I want as a pattern or at least not the way I implemented it. Canceling a task terminates the stream automatically, which has to be recreated. Otherwise, it doesn't emit values anymore. I need a pattern that Observation or Combine implements for continuous value streaming to multiple listeners.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be fine to just keep hold of the task in a property, and cancel/finish when we deinit the model, wouldn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, canceling the Task terminates the stream. I think I found a better way just to use Observation. I will push the changes soon.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does that matter, on deinit? We want it to end then, surely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We won't use AsyncStream anymore but that mattered because when AsyncStream is terminated, there was no mechanism to recreate it. It wouldn't emit any new results after opening POS for the second time.
| case .syncStarted(let id, _): | ||
| id | ||
| case .syncCompleted(let id): | ||
| id | ||
| case .syncFailed(let id, _): | ||
| id | ||
| case .syncNeverDone(let id): | ||
| id |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| case .syncStarted(let id, _): | |
| id | |
| case .syncCompleted(let id): | |
| id | |
| case .syncFailed(let id, _): | |
| id | |
| case .syncNeverDone(let id): | |
| id | |
| case .syncStarted(let id, _), .syncCompleted(let id), .syncFailed(let id, _), .syncNeverDone(let id): | |
| id |
| return lhsSiteID == rhsSiteID && lhsInitial == rhsInitial | ||
| case (.syncCompleted(let lhsSiteID), .syncCompleted(let rhsSiteID)): | ||
| return lhsSiteID == rhsSiteID | ||
| case (.syncFailed(let lhsSiteID, _), .syncFailed(let rhsSiteID, _)): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we OK with two failures that have different errors being considered equal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. We shouldn't have to error transition, since we would have a loading state in between, but I guess it would be more correct to check error equality as well 🤔
| if let syncState = catalogSyncState { | ||
| switch syncState { | ||
| case .syncStarted(_, true): | ||
| return .catalogSyncing | ||
| case .syncNeverDone: | ||
| return .catalogSyncing | ||
| case .syncStarted(_, false): | ||
| // Non-initial sync, continue to other checks | ||
| break | ||
| case .syncCompleted: | ||
| // Continue to other checks | ||
| break | ||
| case .syncFailed: | ||
| // TODO: WOOMOB-1565 | ||
| return .error(PointOfSaleErrorState.errorOnLoadingOrders()) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we'd avoid the need for this (and some of the async stream stuff) if the GRDBItemsController handled this and set the container state to .loading(.catalogSyncing)? I know it's a big change, and I could be missing some fundamental reason why it won't work or would be a bad way to go...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair point. I mean, we would still need changes within the coordinator, since it's the one that does the syncing. Syncing can start at any time, so we need a mechanism that keeps and streams the syncing state.
In this case, I imagine the difference would be replacing aggregateModel+viewHelper with grdbItemsController, but other things would be mostly the same. Correct me if I'm wrong.
and some of the async stream stuff
Do you see how it can be avoided if we implemented the logic within the controller?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you see how it can be avoided if we implemented the logic within the controller?
No, thinking about it more we'd still need something like the stream, which should definitely still be in the coordinator, so that we can show the current sync if there's one already ongoing.
We'd be reading that stream in the GRDBItemsController, and setting the container state there too. I didn't really know that we used this helper, to be honest... it's probably fine in either place.
|
@joshheald Thanks for testing! Yes, I admit I found these tasks with combining sync states, GRDB observation, and SwiftUI more complicated and time-consuming than I anticipated. There are a lot of little details and even after thorough work and testing there are issues that I don't like to see...
Eligibility for POS. I would imagine that with a loaded POS Catalog, we could launch straight into the POS without any loading screen. But that's a topic for future improvements.
Yes, I haven't addressed these limitations in this PR.
When developing, I tested multiple times with various stores, but I haven't noticed this case. I will try to understand what could be happening here. |
58e12ce to
45955b3
Compare
…inator Introduces a fullSyncStateStream and state cache to POSCatalogSyncCoordinator, allowing consumers to observe full sync state changes. Adds a POSCatalogSyncState enum to represent sync progress, completion, failure, and never-done states. Refactors sync logic to emit state updates and provides a method to query the last known sync state for a site.
Generic loading view appears briefly why POS model is loading. It's hard to get away from that. Synchronizing animations between both loading views allows to transition between both loading states without any noticeable transition issues.
45955b3 to
644c9ee
Compare
|
@joshheald thanks for the comments. I think I managed to address them with quite substantial changes:
catalog.syncing.movAssigning to you, to merge after woomob-1098-woo-poslocal-catalog-add-incremental-sync-triggers-pull-to Thank you again for all the help! 🙇 |

WOOMOB-1101
Description
POSCatalogSyncCoordinator:fullSyncStateStreamto observe ongoing sync state: notStarted, started, completed, error...lastFullSyncStateto check a cached last state if it was completed beforeCreated
POSCatalogLoadingViewto be displayed when the catalog loads. Added matched geometry between a loading view and a catalog view, since it's impossible not to show a normal loading view, at least for a split second. And the progress view position doesn't fall between the two views since the progress view is centered together with the text in the catalog view.Expanded
PointOfSaleDashboardViewHelperandPointOfSaleDashboardView.ViewStateto showcatalogSyncstateRemoved an unnecessary initial delay for syncing the inventory.
Error handling will be done separately: WOOMOB-1565
It also made me think of creating a task for caching the eligibility check, and if the POS becomes ineligible, just showing a full-screen overlay. It's an unlikely scenario, and it's better to show POS immediately to give a fast execution impression.
Steps to reproduce
Fresh login
Switch sites
Testing information
Tested on iPad Air 26 simulator.
Screenshots
catalog.sync.full.mov
RELEASE-NOTES.txtif necessary.