Restrict features with purchase screen

- Providers
- Trusted networks
This commit is contained in:
Davide De Rosa 2021-02-02 20:28:29 +01:00
parent d613c17ac9
commit 394762f5d6
11 changed files with 549 additions and 2 deletions

View File

@ -175,9 +175,12 @@
0E57F64620C83FC7008323CF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E57F64420C83FC7008323CF /* LaunchScreen.storyboard */; };
0E6268942369AD0600355F75 /* PurchaseTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6268932369AD0600355F75 /* PurchaseTableViewCell.swift */; };
0E66A270225FE25800F9C779 /* PoolCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E66A26F225FE25800F9C779 /* PoolCategory.swift */; };
0E6BA54B25C9EE3A000CDFAC /* Purchase.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E6BA54D25C9EE3A000CDFAC /* Purchase.storyboard */; };
0E6BE13F20CFBAB300A6DD36 /* DebugLogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6BE13E20CFBAB300A6DD36 /* DebugLogViewController.swift */; };
0E773BF8224BF37600CDDC8E /* ShortcutsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E773BF7224BF37600CDDC8E /* ShortcutsViewController.swift */; };
0E776642229D0DAE0023FA76 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CAFAD229AAE760008E5C8 /* Intents.intentdefinition */; settings = {ATTRIBUTES = (no_codegen, ); }; };
0E79D2C825C9F1B300D12964 /* PurchaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E79D2C725C9F1B300D12964 /* PurchaseViewController.swift */; };
0E79D31E25CC0CF600D12964 /* PurchaseProductView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E79D31D25CC0CF600D12964 /* PurchaseProductView.swift */; };
0E89DFCE213EEDFA00741BA1 /* WizardProviderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E89DFCD213EEDFA00741BA1 /* WizardProviderViewController.swift */; };
0E9AA978259F756A003FAFF1 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9AA977259F756A003FAFF1 /* PacketTunnelProvider.swift */; };
0E9AA979259F756A003FAFF1 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9AA977259F756A003FAFF1 /* PacketTunnelProvider.swift */; };
@ -485,6 +488,7 @@
0E66A26F225FE25800F9C779 /* PoolCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PoolCategory.swift; path = ../Model/Profiles/PoolCategory.swift; sourceTree = "<group>"; };
0E6ACB7722B1A57C001B3C99 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Intents.strings; sourceTree = "<group>"; };
0E6ACB7822B1A5BB001B3C99 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Core.strings; sourceTree = "<group>"; };
0E6BA54C25C9EE3A000CDFAC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Purchase.storyboard; sourceTree = "<group>"; };
0E6BE13920CFB76800A6DD36 /* ApplicationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationError.swift; sourceTree = "<group>"; };
0E6BE13E20CFBAB300A6DD36 /* DebugLogViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugLogViewController.swift; sourceTree = "<group>"; };
0E773BF7224BF37600CDDC8E /* ShortcutsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsViewController.swift; sourceTree = "<group>"; };
@ -500,6 +504,8 @@
0E776640229D0DA80023FA76 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Intents.strings; sourceTree = "<group>"; };
0E79D13E21919EC900BB5FB2 /* PlaceholderConnectionProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderConnectionProfile.swift; sourceTree = "<group>"; };
0E79D14021919F5600BB5FB2 /* ProfileKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileKey.swift; sourceTree = "<group>"; };
0E79D2C725C9F1B300D12964 /* PurchaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseViewController.swift; sourceTree = "<group>"; };
0E79D31D25CC0CF600D12964 /* PurchaseProductView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseProductView.swift; sourceTree = "<group>"; };
0E89DFC4213DF7AE00741BA1 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
0E89DFC7213E8FC500741BA1 /* SessionProxy+Communication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionProxy+Communication.swift"; sourceTree = "<group>"; };
0E89DFCD213EEDFA00741BA1 /* WizardProviderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardProviderViewController.swift; sourceTree = "<group>"; };
@ -771,6 +777,7 @@
0E569F7D259F41690022DFB8 /* Providers.xcassets */,
0E569F85259F41690022DFB8 /* Main.storyboard */,
0E569F81259F41690022DFB8 /* Preferences.storyboard */,
0E6BA54D25C9EE3A000CDFAC /* Purchase.storyboard */,
0E569F83259F41690022DFB8 /* Service.storyboard */,
);
path = macOS;
@ -801,6 +808,7 @@
isa = PBXGroup;
children = (
0E569F65259F41690022DFB8 /* Preferences */,
0E6BA54125C9ED91000CDFAC /* Purchase */,
0E569F6C259F41690022DFB8 /* Service */,
0E569F69259F41690022DFB8 /* OrganizerProfileTableView.swift */,
0E569F6A259F41690022DFB8 /* OrganizerViewController.swift */,
@ -932,6 +940,15 @@
path = iOS;
sourceTree = "<group>";
};
0E6BA54125C9ED91000CDFAC /* Purchase */ = {
isa = PBXGroup;
children = (
0E79D31D25CC0CF600D12964 /* PurchaseProductView.swift */,
0E79D2C725C9F1B300D12964 /* PurchaseViewController.swift */,
);
path = Purchase;
sourceTree = "<group>";
};
0E89DFCC213EEDE700741BA1 /* Organizer */ = {
isa = PBXGroup;
children = (
@ -1486,6 +1503,7 @@
0E52031D259F58BF00CBAB56 /* Providers.xcassets in Resources */,
0E52047D259F642600CBAB56 /* Preferences.storyboard in Resources */,
0E52038F259F593F00CBAB56 /* App.strings in Resources */,
0E6BA54B25C9EE3A000CDFAC /* Purchase.storyboard in Resources */,
0E520385259F593B00CBAB56 /* Credits.html in Resources */,
0E52047C259F642600CBAB56 /* Service.storyboard in Resources */,
0E52032B259F58DD00CBAB56 /* TextTableView.xib in Resources */,
@ -1882,6 +1900,7 @@
0E294AA225AE2B0B00CB4908 /* Descriptible.swift in Sources */,
0E520356259F590600CBAB56 /* PreferencesGeneralViewController.swift in Sources */,
0E520348259F58FE00CBAB56 /* MTUViewController.swift in Sources */,
0E79D31E25CC0CF600D12964 /* PurchaseProductView.swift in Sources */,
0E52037E259F593B00CBAB56 /* SwiftGen+Segues.swift in Sources */,
0E52037C259F593B00CBAB56 /* Theme.swift in Sources */,
0E52035E259F591300CBAB56 /* StatusMenu.swift in Sources */,
@ -1895,6 +1914,7 @@
0E520354259F590600CBAB56 /* PreferencesViewController.swift in Sources */,
0E520343259F58FE00CBAB56 /* ProfileCustomizationViewController.swift in Sources */,
0E520346259F58FE00CBAB56 /* TrustedNetworksViewController.swift in Sources */,
0E79D2C825C9F1B300D12964 /* PurchaseViewController.swift in Sources */,
0E520338259F58F500CBAB56 /* ProviderServiceView.swift in Sources */,
0E520347259F58FE00CBAB56 /* ProxyViewController.swift in Sources */,
0E52037B259F593B00CBAB56 /* IssueReporter.swift in Sources */,
@ -2247,6 +2267,14 @@
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
0E6BA54D25C9EE3A000CDFAC /* Purchase.storyboard */ = {
isa = PBXVariantGroup;
children = (
0E6BA54C25C9EE3A000CDFAC /* Base */,
);
name = Purchase.storyboard;
sourceTree = "<group>";
};
0ED38ADC213F44D00004D387 /* Organizer.storyboard */ = {
isa = PBXVariantGroup;
children = (

View File

@ -0,0 +1,187 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="Rv5-Zx-TH3">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Purchase View Controller-->
<scene sceneID="9TJ-0E-yCE">
<objects>
<viewController id="Rv5-Zx-TH3" customClass="PurchaseViewController" customModule="Passepartout" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="8QZ-37-H7U">
<rect key="frame" x="0.0" y="0.0" width="442" height="500"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<scrollView autohidesScrollers="YES" horizontalLineScroll="80" horizontalPageScroll="10" verticalLineScroll="80" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WTb-Wq-BLo">
<rect key="frame" x="20" y="120" width="402" height="360"/>
<clipView key="contentView" id="dfi-7t-ces">
<rect key="frame" x="1" y="1" width="400" height="358"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnSelection="YES" multipleSelection="NO" autosaveColumns="NO" rowHeight="80" rowSizeStyle="automatic" viewBased="YES" id="9w7-b6-jXh">
<rect key="frame" x="0.0" y="0.0" width="400" height="358"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="17" height="0.0"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn width="388" minWidth="40" maxWidth="1000" id="GRZ-an-Pd1">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" title="Text" id="gwL-5E-ehb">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<customView identifier="ProductCellIdentifier" misplaced="YES" id="pnP-i1-QiM" customClass="PurchaseProductView" customModule="Passepartout" customModuleProvider="target">
<rect key="frame" x="8" y="0.0" width="383" height="80"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q6k-22-i5F">
<rect key="frame" x="18" y="44" width="278" height="20"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="&lt;title&gt;" id="3zZ-WV-GIN">
<font key="font" metaFont="systemMedium" size="17"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xYH-wg-hUh">
<rect key="frame" x="18" y="20" width="347" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="&lt;description&gt;" id="bqY-NM-eTC">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="kRO-hG-JPd">
<rect key="frame" x="300" y="44" width="65" height="20"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="&lt;price&gt;" id="YYL-xk-bbo">
<font key="font" metaFont="systemMedium" size="17"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="xYH-wg-hUh" secondAttribute="bottom" constant="20" symbolic="YES" id="1EP-V1-gsU"/>
<constraint firstAttribute="trailing" secondItem="xYH-wg-hUh" secondAttribute="trailing" constant="20" symbolic="YES" id="Des-Zp-REX"/>
<constraint firstAttribute="trailing" secondItem="kRO-hG-JPd" secondAttribute="trailing" constant="20" symbolic="YES" id="OY4-cV-sdC"/>
<constraint firstItem="kRO-hG-JPd" firstAttribute="leading" secondItem="q6k-22-i5F" secondAttribute="trailing" constant="8" symbolic="YES" id="Olt-aL-NpR"/>
<constraint firstItem="xYH-wg-hUh" firstAttribute="top" secondItem="q6k-22-i5F" secondAttribute="bottom" constant="8" symbolic="YES" id="R2x-ie-ECU"/>
<constraint firstItem="q6k-22-i5F" firstAttribute="leading" secondItem="pnP-i1-QiM" secondAttribute="leading" constant="20" symbolic="YES" id="TQm-gM-KGy"/>
<constraint firstItem="q6k-22-i5F" firstAttribute="top" secondItem="pnP-i1-QiM" secondAttribute="top" constant="20" symbolic="YES" id="dO0-4G-6TX"/>
<constraint firstItem="kRO-hG-JPd" firstAttribute="centerY" secondItem="q6k-22-i5F" secondAttribute="centerY" id="f9p-8K-1bF"/>
<constraint firstItem="xYH-wg-hUh" firstAttribute="leading" secondItem="pnP-i1-QiM" secondAttribute="leading" constant="20" symbolic="YES" id="nZu-CX-k7N"/>
</constraints>
<connections>
<outlet property="labelDescription" destination="xYH-wg-hUh" id="TSs-OW-1hh"/>
<outlet property="labelPrice" destination="kRO-hG-JPd" id="QE3-Yk-pxQ"/>
<outlet property="labelTitle" destination="q6k-22-i5F" id="0IP-Gv-Wh0"/>
</connections>
</customView>
</prototypeCellViews>
</tableColumn>
</tableColumns>
<connections>
<outlet property="dataSource" destination="Rv5-Zx-TH3" id="QpY-8y-snp"/>
<outlet property="delegate" destination="Rv5-Zx-TH3" id="vc2-tf-15J"/>
</connections>
</tableView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="CeE-dS-NyP">
<rect key="frame" x="1" y="329" width="400" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="2Ip-OW-X0v">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="TKV-I9-3N8">
<rect key="frame" x="19" y="96" width="404" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="400" id="uyO-So-B3k"/>
</constraints>
<textFieldCell key="cell" title="&lt;footer&gt;" id="thS-No-Evy">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<progressIndicator hidden="YES" maxValue="100" displayedWhenStopped="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="2hB-Jy-QEw">
<rect key="frame" x="213" y="22" width="16" height="16"/>
</progressIndicator>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="XMI-x0-24k">
<rect key="frame" x="230" y="13" width="94" height="32"/>
<buttonCell key="cell" type="push" title="&lt;restore&gt;" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Tce-5t-Hj1">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="doRestorePurchases:" target="Rv5-Zx-TH3" id="5Nz-PR-cvY"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lBO-a6-qnj">
<rect key="frame" x="322" y="13" width="107" height="32"/>
<buttonCell key="cell" type="push" title="&lt;purchase&gt;" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="uxN-5f-y4i">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="doPurchase:" target="Rv5-Zx-TH3" id="e5O-mz-tRo"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="72F-hB-DPP">
<rect key="frame" x="19" y="60" width="404" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="400" id="ejH-6r-pkl"/>
</constraints>
<textFieldCell key="cell" title="&lt;restore&gt;" id="VuC-Wl-HpD">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="72F-hB-DPP" firstAttribute="top" secondItem="TKV-I9-3N8" secondAttribute="bottom" constant="20" id="8kK-yQ-UGc"/>
<constraint firstAttribute="bottom" secondItem="lBO-a6-qnj" secondAttribute="bottom" constant="20" symbolic="YES" id="ENp-WT-Z1C"/>
<constraint firstAttribute="trailing" secondItem="WTb-Wq-BLo" secondAttribute="trailing" constant="20" symbolic="YES" id="Ed7-QK-8qI"/>
<constraint firstItem="lBO-a6-qnj" firstAttribute="leading" secondItem="XMI-x0-24k" secondAttribute="trailing" constant="12" symbolic="YES" id="HGl-z7-xCS"/>
<constraint firstItem="XMI-x0-24k" firstAttribute="centerY" secondItem="lBO-a6-qnj" secondAttribute="centerY" id="Lkd-1j-rt9"/>
<constraint firstItem="lBO-a6-qnj" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="9w7-b6-jXh" secondAttribute="leading" id="PAx-YO-dub"/>
<constraint firstItem="TKV-I9-3N8" firstAttribute="trailing" secondItem="9w7-b6-jXh" secondAttribute="trailing" id="QMH-Dc-Vbo"/>
<constraint firstItem="XMI-x0-24k" firstAttribute="leading" secondItem="2hB-Jy-QEw" secondAttribute="trailing" constant="8" symbolic="YES" id="Rlz-6Y-GTq"/>
<constraint firstItem="WTb-Wq-BLo" firstAttribute="leading" secondItem="8QZ-37-H7U" secondAttribute="leading" constant="20" symbolic="YES" id="UZT-z0-YQI"/>
<constraint firstItem="72F-hB-DPP" firstAttribute="leading" secondItem="TKV-I9-3N8" secondAttribute="leading" id="ZI9-mi-dTu"/>
<constraint firstAttribute="trailing" secondItem="lBO-a6-qnj" secondAttribute="trailing" constant="20" symbolic="YES" id="Ztx-Qh-mHp"/>
<constraint firstItem="lBO-a6-qnj" firstAttribute="top" secondItem="72F-hB-DPP" secondAttribute="bottom" constant="20" id="agT-yo-xZF"/>
<constraint firstItem="TKV-I9-3N8" firstAttribute="top" secondItem="WTb-Wq-BLo" secondAttribute="bottom" constant="8" symbolic="YES" id="kzd-et-rve"/>
<constraint firstItem="72F-hB-DPP" firstAttribute="trailing" secondItem="TKV-I9-3N8" secondAttribute="trailing" id="m9x-V7-HTG"/>
<constraint firstItem="2hB-Jy-QEw" firstAttribute="centerY" secondItem="lBO-a6-qnj" secondAttribute="centerY" id="mX6-db-4Tp"/>
<constraint firstItem="TKV-I9-3N8" firstAttribute="leading" secondItem="9w7-b6-jXh" secondAttribute="leading" id="pNd-TR-JzE"/>
<constraint firstItem="WTb-Wq-BLo" firstAttribute="top" secondItem="8QZ-37-H7U" secondAttribute="top" constant="20" symbolic="YES" id="q88-zV-efL"/>
</constraints>
</view>
<connections>
<outlet property="activityPurchase" destination="2hB-Jy-QEw" id="WcK-o8-BZZ"/>
<outlet property="buttonPurchase" destination="lBO-a6-qnj" id="m5t-5u-goQ"/>
<outlet property="buttonRestore" destination="XMI-x0-24k" id="Xhd-Ph-uUY"/>
<outlet property="labelFooter" destination="TKV-I9-3N8" id="DTN-XY-TSD"/>
<outlet property="labelRestore" destination="72F-hB-DPP" id="rnt-Zy-gEz"/>
<outlet property="tableView" destination="9w7-b6-jXh" id="0Ju-MH-sbN"/>
</connections>
</viewController>
<customObject id="GVf-vI-DWL" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="428" y="-9"/>
</scene>
</scenes>
</document>

View File

@ -1220,7 +1220,7 @@ DQ
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<segue destination="KOf-Ss-PtI" kind="sheet" id="131-KX-EPr"/>
<segue destination="KOf-Ss-PtI" kind="sheet" identifier="TrustedNetworkAddSegueIdentifier" id="131-KX-EPr"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="C9Y-vu-Z9f">

View File

@ -24,6 +24,7 @@
//
import Cocoa
import PassepartoutCore
class Macros {
static func warning(_ title: String, _ message: String) -> NSAlert {
@ -89,6 +90,15 @@ extension NSAlert {
}
}
extension NSViewController {
func presentPurchaseScreen(forProduct product: Product, delegate: PurchaseViewControllerDelegate? = nil) {
let vc = StoryboardScene.Purchase.initialScene.instantiate()
vc.feature = product
vc.delegate = delegate
presentAsModalWindow(vc)
}
}
extension NSView {
static func get<T: NSView>() -> T {
let name = String(describing: T.self)

View File

@ -24,6 +24,11 @@ internal enum StoryboardScene {
internal static let initialScene = InitialSceneType<PreferencesViewController>(storyboard: Preferences.self)
}
internal enum Purchase: StoryboardType {
internal static let storyboardName = "Purchase"
internal static let initialScene = InitialSceneType<PurchaseViewController>(storyboard: Purchase.self)
}
internal enum Service: StoryboardType {
internal static let storyboardName = "Service"

View File

@ -19,6 +19,7 @@ internal enum StoryboardSegue {
internal enum Service: String, SegueType {
case accountSegueIdentifier = "AccountSegueIdentifier"
case customizeSegueIdentifier = "CustomizeSegueIdentifier"
case trustedNetworkAddSegueIdentifier = "TrustedNetworkAddSegueIdentifier"
}
}
// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name

View File

@ -88,6 +88,12 @@ class OrganizerViewController: NSViewController {
guard let item = sender as? NSMenuItem, let metadata = item.representedObject as? Infrastructure.Metadata else {
return
}
do {
try ProductManager.shared.verifyEligible(forProvider: metadata)
} catch {
presentPurchaseScreen(forProduct: metadata.product)
return
}
perform(segue: StoryboardSegue.Main.enterAccountSegueIdentifier, sender: metadata.name)
}

View File

@ -0,0 +1,49 @@
//
// PurchaseProductView.swift
// Passepartout
//
// Created by Davide De Rosa on 2/4/21.
// Copyright (c) 2021 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import Cocoa
import StoreKit
class PurchaseProductView: NSView {
@IBOutlet private weak var labelTitle: NSTextField?
@IBOutlet private weak var labelPrice: NSTextField?
@IBOutlet private weak var labelDescription: NSTextField?
func fill(product: SKProduct, customDescription: String? = nil) {
fill(
title: product.localizedTitle,
description: customDescription ?? "\(product.localizedDescription)."
)
labelPrice?.stringValue = product.localizedPrice ?? ""
}
func fill(title: String, description: String) {
labelTitle?.stringValue = title
labelDescription?.stringValue = description
labelPrice?.stringValue = ""
}
}

View File

@ -0,0 +1,242 @@
//
// PurchaseViewController.swift
// Passepartout
//
// Created by Davide De Rosa on 2/2/21.
// Copyright (c) 2021 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import Cocoa
import StoreKit
import PassepartoutCore
import SwiftyBeaver
import Convenience
private let log = SwiftyBeaver.self
protocol PurchaseViewControllerDelegate: class {
func purchaseController(_ purchaseController: PurchaseViewController, didPurchase product: Product)
}
class PurchaseViewController: NSViewController {
private struct Columns {
static let product = NSUserInterfaceItemIdentifier("ProductCellIdentifier")
}
@IBOutlet private weak var tableView: NSTableView!
@IBOutlet private weak var labelFooter: NSTextField!
@IBOutlet private weak var labelRestore: NSTextField!
@IBOutlet private weak var activityPurchase: NSProgressIndicator!
@IBOutlet private weak var buttonPurchase: NSButton!
@IBOutlet private weak var buttonRestore: NSButton!
var feature: Product!
weak var delegate: PurchaseViewControllerDelegate?
private var skFeature: SKProduct?
private var skFullVersion: SKProduct?
private var fullVersionExtra: String?
var rows: [RowType] = []
func reloadModel() {
rows = []
let pm = ProductManager.shared
if let skFullVersion = pm.product(withIdentifier: .fullVersion) {
self.skFullVersion = skFullVersion
rows.append(.fullVersion)
}
if let skFeature = pm.product(withIdentifier: feature) {
self.skFeature = skFeature
rows.append(.feature)
}
let fullBulletsList: [String] = ProductManager.shared.featureProducts(excluding: [.fullVersion, .fullVersion_macOS]).map {
return $0.localizedTitle
}.sortedCaseInsensitive()
let fullBullets = fullBulletsList.joined(separator: "\n")
fullVersionExtra = L10n.Core.Purchase.Cells.FullVersion.extraDescription(fullBullets)
}
// MARK: NSViewController
override func viewDidLoad() {
super.viewDidLoad()
title = L10n.Core.Purchase.title
labelFooter.stringValue = L10n.Core.Purchase.Sections.Products.footer
labelRestore.stringValue = L10n.Core.Purchase.Cells.Restore.description
buttonPurchase.title = L10n.Core.Purchase.title
buttonRestore.title = L10n.Core.Purchase.Cells.Restore.title
guard let _ = feature else {
fatalError("No feature set for purchase")
}
tableView.usesAutomaticRowHeights = true
tableView.reloadData()
}
override func viewWillAppear() {
super.viewWillAppear()
view.window?.styleMask = [.closable, .titled]
}
override func viewDidAppear() {
super.viewDidAppear()
startWaiting()
ProductManager.shared.listProducts { [weak self] (_, _) in
self?.reloadModel()
self?.tableView.reloadData()
self?.stopWaiting()
}
}
// MARK: Actions
@IBAction private func doPurchase(_ sender: Any) {
guard tableView.selectedRow != -1 else {
return
}
switch rows[tableView.selectedRow] {
case .feature:
purchaseFeature()
case .fullVersion:
purchaseFullVersion()
}
}
@IBAction private func doRestorePurchases(_ sender: Any) {
startWaiting()
ProductManager.shared.restorePurchases { [weak self] in
self?.stopWaiting()
guard $0 == nil else {
return
}
self?.dismiss(nil)
}
}
private func purchaseFeature() {
guard let sk = skFeature else {
return
}
purchase(sk)
}
private func purchaseFullVersion() {
guard let sk = skFullVersion else {
return
}
purchase(sk)
}
private func purchase(_ skProduct: SKProduct) {
startWaiting()
ProductManager.shared.purchase(skProduct) { [weak self] in
self?.stopWaiting()
guard $0 == .success else {
if let error = $1 {
self?.reportPurchaseError(withProduct: skProduct, error: error)
}
return
}
guard let weakSelf = self else {
return
}
let product = weakSelf.feature.matchesStoreKitProduct(skProduct) ? weakSelf.feature! : .fullVersion
weakSelf.delegate?.purchaseController(weakSelf, didPurchase: product)
self?.dismiss(nil)
}
}
private func reportPurchaseError(withProduct product: SKProduct, error: Error) {
log.error("Unable to purchase \(product): \(error)")
let alert = Macros.warning(product.localizedTitle, error.localizedDescription)
_ = alert.presentModally(withOK: L10n.Core.Global.ok, cancel: nil)
}
@objc private func close() {
dismiss(nil)
}
// MARK: Helpers
private func startWaiting() {
tableView.isEnabled = false
buttonPurchase.isEnabled = false
buttonRestore.isEnabled = false
activityPurchase.isHidden = false
activityPurchase.startAnimation(nil)
}
private func stopWaiting() {
activityPurchase.stopAnimation(nil)
tableView.isEnabled = true
buttonPurchase.isEnabled = true
buttonRestore.isEnabled = true
}
}
extension PurchaseViewController: NSTableViewDataSource, NSTableViewDelegate {
enum RowType {
case feature
case fullVersion
}
func numberOfRows(in tableView: NSTableView) -> Int {
return rows.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard let view = tableView.makeView(withIdentifier: Columns.product, owner: nil) as? PurchaseProductView else {
return nil
}
switch rows[row] {
case .feature:
guard let product = skFeature else {
fatalError("Loaded feature cell, yet no corresponding product?")
}
view.fill(product: product)
case .fullVersion:
guard let product = skFullVersion else {
fatalError("Loaded full version cell, yet no corresponding product?")
}
view.fill(product: product, customDescription: fullVersionExtra)
}
return view
}
}

View File

@ -107,6 +107,13 @@ class TrustedNetworksViewController: NSViewController, ProfileCustomization {
}
@IBAction private func toggleTrustEthernet(_ sender: Any?) {
do {
try ProductManager.shared.verifyEligibleForTrustedNetworks()
} catch {
checkTrustEthernet.state = .off
presentPurchaseScreen(forProduct: .fullVersion_macOS)
return
}
trustedNetworks.includesEthernet = (checkTrustEthernet.state == .on)
delegate?.profileCustomization(self, didUpdateTrustedNetworks: trustedNetworks)
@ -122,6 +129,18 @@ class TrustedNetworksViewController: NSViewController, ProfileCustomization {
delegate?.profileCustomization(self, didUpdateTrustedNetworks: trustedNetworks)
}
override func shouldPerformSegue(withIdentifier identifier: NSStoryboardSegue.Identifier, sender: Any?) -> Bool {
if identifier == StoryboardSegue.Service.trustedNetworkAddSegueIdentifier.rawValue {
do {
try ProductManager.shared.verifyEligibleForTrustedNetworks()
} catch {
presentPurchaseScreen(forProduct: .fullVersion_macOS)
return false
}
}
return true
}
override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
if let addVC = segue.destinationController as? TrustedNetworksAddViewController {
addVC.delegate = self

View File

@ -8,9 +8,9 @@ strings:
ib:
inputs:
#- Base.lproj/About.storyboard
- Base.lproj/Main.storyboard
- Base.lproj/Preferences.storyboard
- Base.lproj/Purchase.storyboard
- Base.lproj/Service.storyboard
#- Base.lproj/Shortcuts.storyboard
outputs: