Commit Graph

45 Commits

Author SHA1 Message Date
Davide
c93a43702c
Improve migrate design (#885)
- Mention iCloud in informational message
- Redesign to always show message despite no migratable profiles
- Refine the looks
2024-11-17 14:02:40 +01:00
Davide
d3e344670b
Resolve excessive profile reloads (#883)
Optimize ProfileManager in several ways:

- Refine control over objectWillChange
- Observe search separately
- Store subscriptions separately (local, remote, search)
- Fix multiple local updates on save/remove/foreground (updating
allProfiles manually)
- Update the library with more optimized NE reloads
- Cancel pending remote import before a new one
- Yield 100ms between imports
- Reorganize code

Extras:

- Only use background context in provider repositories
- Externalize tunnel receipt URL, do not hardcode BundleConfiguration
- Improve some logging

Self-reminder: NEVER use a Core Data background context to observe
changes in CloudKit containers. They just won't be notified (e.g. in
NSFetchedResultsController).

Fixes #857
2024-11-17 11:34:43 +01:00
Davide
9e5beff23a
Perform migrate + import in one step (#882)
- Drop the .importing / .imported steps
- Animate rows re-sorting during process
- Rephrase some strings better
- Test fake migration with launch argument
2024-11-16 21:16:25 +01:00
Davide
3737560851
Add the option to migrate old profiles (#879)
Finalize migration flow:

- Add entry to "Add" menu
- Suggest to migrate old profiles when there are no profiles
- Add informational message
- Keep included profiles on top
- Allow deletion of migratable profiles
- Fix duplicated Form in preview
- Rename views and models

Improve some Theme modifiers:

- Empty message with full screen option
- Progress modifier with custom view
- Confirmation dialog with custom message
2024-11-16 12:29:03 +01:00
Davide
9ca103e949
Refactor modal size to be a modifer parameter (#877)
Way more flexible.
2024-11-15 23:32:54 +01:00
Davide
b08243949c
Nested ZStack modifiers in progress/empty modifiers (#876)
Merge into a single modifier because they are used together.
2024-11-15 16:33:26 +01:00
Davide
b52e3c779b
Clean up and rename beta receipt variables 2024-11-14 22:52:52 +01:00
Davide
5818b36cda
Copy release receipt to tunnel (#871)
Not just the provided one, because it could be the sandbox receipt.

Fixes #870 further.
2024-11-14 19:42:38 +01:00
Davide
114e1abe12
Add initial migration UI (#866)
Repurpose LegacyManager as MigrationManager. Present initial migration
UI from "+" menu in app home.

Different styles:

- iOS → Section / ForEach
- macOS → Table
2024-11-14 11:02:26 +01:00
Davide
6495f51dc1
Unify macOS toggles in menu/settings (#864)
Show in both:

- Launch on login
- Keep in menu bar
2024-11-13 22:04:34 +01:00
Davide
2d698cabfe
Reuse modifier for navigation stacks with close button (#861)
Refactored:

- iOS
  - VPNProviderServerView
- macOS
  - AboutView
  - ProfileSplitView
- Both
  - ProfileCoordinator
  - VPNProviderServerCoordinator
2024-11-13 19:17:00 +01:00
Davide
008b78cc7c
Upgrade to Xcode 16 (#858)
- Address further restrictions on actor-isolation by using `nonisolated`
on:
  - Combine subjects
  - Core Data context/controller
  - Blocks
- In previews using inline `@State`, create a custom view instead
- Use `@retroactive` in l10n extensions
- Fix compile error in WireGuardKit
2024-11-13 12:07:30 +01:00
Davide
1dfb013115
Implement some CustomDebugStringConvertible
- AppFeature
- AppProduct
- ProviderID
2024-11-11 19:45:56 +01:00
Davide
0686650ccf
React on providers eligibility (#848)
Check .providers eligibility in tunnel to prevent from starting if
profile has an active provider module. Do not alter original profile.
2024-11-11 12:50:26 +01:00
Davide
21340e9f56
Rewrite AppContext event handlers (#839)
Loading remote profiles before local profiles may cause duplicated NE
managers. This happened because if local profiles are empty, any remote
profile is imported regardless of their former existence in the local
store. The importer just doesn't know.

Therefore, revisit the sequence of AppContext registrations:

- First off
- Skip Tunnel prepare() because NEProfileRepository.fetch() does it
already
- NE is both Tunnel and ProfileRepository, so calling tunnel.prepare()
loads local NE profiles twice
- onLaunch() - **run this once and before anything else**
  - Read local profiles
  - Reload in-app receipt
  - Observe in-app eligibility → Triggers onEligibleFeatures()
  - Observe profile save → Triggers onSaveProfile()
  - Fetch providers index
- onForeground()
  - Read local profiles
  - Read remote profiles, and toggle CloudKit sync based on eligibility
- onEligibleFeatures()
  - Read remote profiles, and toggle CloudKit sync based on eligibility
- onSaveProfile()
  - Reconnect if necessary
2024-11-10 17:51:28 +01:00
Davide
3a5e3889d3
Add more view modifiers (#838)
1. ThemeProgressViewModifier to replace content with a progress view
while a condition is active
2. ThemeEmptyContentModifier to replace content with a message if an
empty condition is met
3. Replace .opacity(bool ? 1.0 : 0.0) with .opaque(bool)

Reuse:

- 1 in PaywallView and DonateView
- 2 in ProfileContainerView
2024-11-10 12:00:07 +01:00
Davide
e07833b2a4
Revisit in-app eligibility for iCloud sharing (#837)
Restore .sharing feature:

- Merge "Apple TV" into "iCloud" section
  - "Enabled", disabled if ineligible for .sharing
  - "Apple TV", disabled if ineligible for .appleTV || !isShared
- Footer about TV restrictions

Paywalls:

- "Share on iCloud" if ineligible for .sharing
- "Drop TV restriction" if eligible for .sharing but not for .appleTV
  - Applies to full version products (user level 2)
  - Suggest Apple TV product

Restrictions:

- Toggle CloudKit sync on remote repository based on .sharing
eligibility
- Do not start tunnel on Apple TV if ineligible for .appleTV

Fixes:

- Incorrect zip() publishers in remote repository
- Resolve duplicates in Core Data, first profile wins sorted by
lastUpdate descending
- Reload receipt on OOB IAPManager events
2024-11-09 15:20:59 +01:00
Davide
7c27125dd7
Decouple library from PassepartoutKit implementations (#834)
Move the following dependencies:

- OpenVPN/OpenSSL
- WireGuard/Go

up the chain until the main App/Tunnel targets, so that UILibrary and
CommonLibrary can abstract from these unnecessary details. Instead, give
module views access to generic implementations via Registry.

Incidentally, this fixes an issue preventing TV previews from working
due to OpenSSL linkage.
2024-11-08 12:37:09 +01:00
Davide
8fbccc6d80
Add donations UI and in-app error handling (#833)
- Reuse same product views from paywall
- Handle errors in fetch products
- Hide views on fetch products error
- Disable views during purchase

Closes #830
2024-11-07 23:02:10 +01:00
Davide
2c1ccbcbfd
Finalize paywall UI (#831)
- Use StoreKit views when available
- Offer one-time purchase
- Recurring subscriptions for all features
- Restore purchases

Remove .siri (Shortcuts), now free.

Closes #819
Closes #469
2024-11-07 18:27:36 +01:00
Davide
8ef1e7fbe9
Refactor theme section and styles (#829)
Provide shortcut for section with single row. Redesign Settings
accordingly.
2024-11-07 15:50:19 +01:00
Davide
22a5cb9af2
Update FIXMEs 2024-11-07 12:48:10 +01:00
Davide
abe4c779b8
Improve startup time (#822)
Profiles were loaded after reading receipt, which took at least a second
on iOS.

Potential regression from #821
2024-11-07 01:48:39 +01:00
Davide
68df6066ba
Improve configuration on app launch/active (#821)
- Centralize context initialization/refresh in platform-specific app
delegates
- Prevent multiple calls to .onApplicationActive()
- Simplify local/remote profile fingerprint comparison
- Revert to always replacing Core Data entities
- The remote store somehow ended up having duplicates, which caused
repeated imports of remote profiles due to randomly different
fingerprints
- Optimize reload of in-app receipt
2024-11-06 18:42:42 +01:00
Davide
d8c4e87239
Refactor in-app entities for StoreKit/Kvitto integration (#820)
Refactoring:

- Get receipts from StoreKit Transaction.currentEntitlements
- Search for the originally purchased build in the local receipt anyway
(Kvitto)
- Fall back to release receipt (Kvitto), if any, for feature eligibility
in TestFlight builds
- Parse and verify expiration date in subscriptions
- Decouple in-app identifier composition from BundleConfiguration
- Fix user level features only applied when a receipt was not found

Testing:

- Add StoreKit configuration
- Fake purchases with PP_FAKE_IAP
- Fake user level with PP_USER_LEVEL

Then for reactive receipt reload, detect app activation differently:

- iOS/tvOS on .scenePhase
- macOS on launch and NSWorkspace.didActivateApplicationNotification

As to features:

- Credit former "Full version" purchasers with all current AND future
features, except the Apple TV
2024-11-06 13:20:12 +01:00
Davide
d5ac785bb8
Simulate in-app purchases (#818)
Integrate in-app helper into IAPManager and simulate purchases with an
in-memory receipt.
2024-11-05 18:55:57 +01:00
Davide
9286ead348
Improve logging on ineligible features 2024-11-05 14:10:17 +01:00
Davide
320b92591e
Address some issues with animations (#816)
- Missing animation in OpenVPN provider sections
- Hardcoded animation in TV profile, theme modifiers were not available
2024-11-05 13:43:23 +01:00
Davide
735d3b2fbe
Improve footers in macOS form sections (#814)
Revisit the use of informational footers in forms because:

- iOS uses Section footers
- macOS uses a secondary label below the main row label

Therefore:

- Add .themeRow() modifier to accomplish macOS behavior
- iOS: leave .themeSection() as is, and add a dummy .themeRow() that
does nothing
- macOS: make footer ineffective in .themeSection(), but add .themeRow()
modifiers to move footers to rows
2024-11-05 13:32:09 +01:00
Davide
346aaec441
Reuse PurchaseButtonModifier in restricted areas (#815)
Fixes #687
2024-11-05 13:27:05 +01:00
Davide
1cb46e066c
Encapsulate behavior on app active (#812)
Implement inside AppContext.
2024-11-05 10:41:02 +01:00
Davide
bba661f104
Implement TV profile expiration (#811)
Based on in-app eligibility, expire TV profiles after 10 minutes.
Refactor/redesign general sections and offer .sharing feature for free,
it makes it simpler to focus on Apple TV product.
2024-11-05 10:03:54 +01:00
Davide
f3d13d0cdf
Refactor AppContext creation and profile processing (#810)
Streamline initialization of AppContext objects without singletons,
especially because some are interconnected.

Rethink ProfileProcessor to be the only gateway of profile processing
for:

- Include
- Save
- Connect

Provide closures with access to the IAPManager for eligibility checks.

Finally, take a ProfileProcessor parameter in:

- ProfileManager (for isIncluded and willSave)
- ExtendedTunnel (for willConnect)

so that it's used implicitly without having to put it into the SwiftUI
environment.

Other than that:

- Move AppError to CommonLibrary
- Skip decoding of attributes from Core Data because they are already
part of the profile
2024-11-04 23:34:22 +01:00
Davide
0c66050726
Review ProfileManager observation logic (#809)
- Perform profiles removal in a single publisher, in
reloadRemoteProfiles() after importing remote profiles
- Only force a new lastUpdate/fingerprint if profile is saved locally,
DO NOT alter them if imported from remote repository because this would
cause a re-save on iCloud
- Profiles were purged twice on launch in the main macOS app
2024-11-04 10:10:17 +01:00
Davide
5119cc20d5
Implement TV profile sharing (#808)
Add profile attribute `isAvailableForTV` and set specific behavior to:

- Observe shared profiles and delete locally when unshared
- Only keep locally those profiles with the TV attribute enabled
- Add toggle in UI
2024-11-03 23:42:17 +01:00
Davide
a22584c630
Fine-tune profile management with additional attributes (#807)
Additions to the domain:

- Update rather than replace existing Core Data profile
- Attach ProfileAttributes to Profile.userInfo
- Store one-off `fingerprint` UUID on each save

With the above in place, fix and improve ProfileManager to:

- Use `fingerprint` to compare local/remote profiles in history and thus
avoid local re-import of shared profiles
- Use `deletingRemotely` to delete local profiles when removed from the
remote repository (default false)
- Use `isIncluded` filter to exclude certain profiles from the local
repository (default nil)
2024-11-03 23:35:45 +01:00
Davide
2790a2aac2
Merge AppLibrary into CommonLibrary (#806)
CommonLibrary barely used as standalone, almost always together with
AppLibrary.
2024-11-03 13:16:13 +01:00
Davide
1d2de39d49
Use profile name as interactive view title 2024-11-03 11:44:14 +01:00
Davide
fbe2d84113
Submit on OpenVPN fields (#805)
Submit on password or OTP depending on the selected method.
2024-11-03 11:27:12 +01:00
Davide
15959d2422
Resolve some focus issues (#802)
- [x] tvOS: When profile selector appears, if it's closed without
selecting any profile, it instantly reopens
- [x] Set initial focus in OpenVPN credentials
2024-11-03 08:17:19 +01:00
Davide
8aff3bedbc
Fix missing country flags 2024-11-02 16:15:19 +01:00
Davide
248f1e7257
Fix compile error on tvOS 2024-11-02 15:27:52 +01:00
Davide
aba5081450
Refactor and improve interactive login (#801)
Define two styles for interactive login:

- Modal (iOS/macOS) - Form inside NavigationStack
- Inline (tvOS) - VStack

Requires OpenVPN credentials view to be container-agnostic.

Play with focus to improve the overall TV experience.
2024-11-02 15:24:41 +01:00
Davide
454efb8e50
Refactor ModuleType to be a single source of truth (#800)
Rather than defining a new enum, tie ModuleType to ModuleHandler names
from PassepartoutKit.

Also a way to reuse ModuleType.localizedDescription on both Module and
ModuleBuilder implementations.
2024-11-02 15:23:36 +01:00
Davide
070b661c43
Refactor library targets (#799)
Renames:

- AppUI → UILibrary
- AppUIPlatform → AppUI (conditional umbrella) + AppUI[Main|TV]
- APILibrary/CommonLibrary/UtilsLibrary → Common[API|Library|Utils]

Dependencies:

- AppUI → UILibrary
- UILibrary → AppLibrary, CommonAPI
- AppLibrary → CommonLibrary, CommonUtils
2024-11-02 10:11:59 +01:00