Clarify the use of contexts:
- **Production** (.shared)
- **Previews** (.mock → .forPreviews)
- ONLY use it in UILibrary for, well, previews
- This context has dumb profiles with UUIDs as names
- Registry is fake
- **UI Tests** (.forUITesting)
- Add new context for UI testing
- Selected based on command line arguments
- This context has mock data tuned for decent screenshots
- Registry is real
Share the same InAppProcessor in .shared and .forTesting contexts
because the app behavior was inconsistent regarding e.g. in-app
purchases.
Ready for screenshots generation, except for the tests themselves and
the TV target.
- More customizations while UI testing
- Act as full version user in IAPManager
- Override layout with default to .grid if isBigDevice
- Show module names in profile list/grid
- Improve mock Profile/ProfileManager
- Meaningful profile names
- iCloud/TV icons
- Initial modules
- Improve XCTest extensions
- Screenshot destination (attachment/temporary)
- Screenshot target (window/sheet)
- Print saved temporary URL at the end (may help with CI)
- Append device name to screenshot filename
- Tests
- Refactor actions with the [Page Object
pattern](https://swiftwithmajid.com/2021/03/24/ui-testing-using-page-object-pattern-in-swift/)
- Perform iPad screenshots in landscape
- Split simple flow tests and screenshots
- Add "Connect to" test
Closes#681
Create UITesting target with:
- AppCommandLine/AppEnvironment: strongly typed refactoring of PP_*
environment values
- AccessibilityInfo: identifies and locates elements for UI testing
Make the app behave differently when launched with `.uiTesting`, and
expose the flag to SwiftUI via `.environment(\.isUITesting)` to:
- Use the mock AppContext
- Skip onboarding
Add PassepartoutUITests target with two screenshot tests:
- Connected screen
- Profile modal
- Omit duplicate/delete from current profile
- Replace .infoButton with .installedProfile if current
- Drop dots from direct actions
- Disable trailing dots on iOS/tvOS
- Disable "Connect to" if ineligible (decouple to own view)
CommonLibrary had some undesired knowledge of UI. Split AppPreference
and UIPreference. Then move some more stuff from AppUI* to UILibrary.
WARNING: this forgets existing UI preferences (e.g. favorite servers).
Create profile with a provider module and an on-demand module (disabled)
to speed up initial provider selection and configuration. Supports
OpenVPN for now.
Fixes#899
Merge former ContainerView into main view and define platform-specific
subviews:
- ContainerView (composition of content + filters)
- ContentView
- FiltersView
- PaywallView is the paywall content
- PaywallModifier attaches paywall with optional confirmation
- PurchaseRequiredButton presents paywall explicitly
- PaywallReason is the compound input
Refactoring:
- PurchaseRequiredButton takes a custom view
- PurchaseAlertModifier was merged into PaywallModifier
- PurchaseButtonModifier was merged into PurchaseRequiredButton
- Modal options were packed into a single struct
Confirmation alert presented on:
- Connect to ineligible profile (AppCoordinator)
- Save ineligible profile (ProfileCoordinator)
Includes:
- Credits
- Donations
- Diagnostics
- Version
Had to:
- Wrap tab view into a NavigationStack for full-screen navigation
- Take out navigation titles of about subviews
- Customize donations view layout with modifier
- Fix credits and debug log to support scrolling
Closes#914
- Move about subviews to UILibrary
- Refactor about to single coordinator + platform views
- Refactor debug log to single view + content views
- Take out debug log routes from about routes
- Rename Settings* to Preferences*
- Reuse empty modifier in debug log
- Fix a visual bug in .themeTrailingValue() (extra Spacer)
Preparation for #914
To get access to modules, try to avoid full Profile objects. Instead,
replace the coupled ProfileHeader occurrences with a new intermediary
ProfilePreview everywhere.
This way, a ProfileProcessor can inject the localized modules
descriptions from above with the preview() method.
First, rename `force` parameter of ProfileManager.save() to `isLocal`,
because it's meant to be used when saving local profiles. In such
scenario, a profile that is also remotely shared _must_ be re-saved to
the remote repository.
This was not being done when selecting a provider server, and it could
be noticed because the other devices would only receive the iCloud
update after editing the profile and re-doing a manual "Save". Only at
that point would the new profile be re-shared on iCloud.
StoreKit ProductView performs the purchases internally without calling
IAPManager.purchase(), which causes the IAPManager state to be
momentarily outdated.
Leverage PaywallView.onComplete() to reload the receipt and eventually
trigger IAPManager.objectWillChange, so that the app is immediately
unlocked on a successful purchase.
Visually clarify that a profile requires a purchase to be enabled.
- Implement AppFeatureRequiring in Profile
- Refactor IAPManager.verify() accordingly
- Pre-compute required features in ProfileManager via ProfileProcessor
- Allow unrestricted save, but show PurchaseRequiredButton
- Warn however about paid features (FIXME)
- Redesign features in paywall
- Strip already eligible features from paywall
- List required features in restricted alert
- Localize feature descriptions
- Review propagation of paywall modifiers/reasons
Extra:
- Move more domain entities from UILibrary to CommonLibrary
- Default on-demand policy to .any (free feature)
- Fix modals not reappearing after closing with gesture
- Extend UILibrary start-up assertions