Restrict features with purchase screen
- Providers - Trusted networks
This commit is contained in:
parent
d613c17ac9
commit
394762f5d6
|
@ -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 = (
|
||||
|
|
|
@ -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="<title>" 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="<description>" 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="<price>" 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="<footer>" 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="<restore>" 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="<purchase>" 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="<restore>" 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>
|
|
@ -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">
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = ""
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue