Merge pull request #2259 from stuartbreckenridge/feature/mac-preferences
Mac Preferences
This commit is contained in:
commit
cf233f4825
@ -1,8 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17132" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="mPU-HG-I4u">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="16097" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="mPU-HG-I4u">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17132"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16097"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@ -32,14 +31,14 @@
|
||||
<objects>
|
||||
<viewController title="General" storyboardIdentifier="General" id="iuH-lz-18x" customClass="GeneralPreferencesViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" id="WnV-px-wCT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="501" height="217"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="501" height="213"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<customView horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="Ut3-yd-q6G">
|
||||
<rect key="frame" x="80" y="16" width="340" height="185"/>
|
||||
<rect key="frame" x="83" y="16" width="334" height="181"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ucw-vG-yLt">
|
||||
<rect key="frame" x="29" y="162" width="92" height="16"/>
|
||||
<rect key="frame" x="29" y="159" width="92" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Refresh feeds:" id="F7c-lm-g97">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -47,13 +46,13 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="SFF-mL-yc8">
|
||||
<rect key="frame" x="125" y="157" width="218" height="25"/>
|
||||
<rect key="frame" x="125" y="153" width="212" height="25"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="200" id="N1a-qV-4Os"/>
|
||||
</constraints>
|
||||
<popUpButtonCell key="cell" type="push" title="Every 30 minutes" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="3" imageScaling="proportionallyDown" inset="2" selectedItem="rZU-Tg-xwo" id="Jwn-HD-eP6">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<menu key="menu" id="v0m-Z7-oG3">
|
||||
<items>
|
||||
<menuItem title="Manually only" tag="1" id="doa-Wq-4Uq"/>
|
||||
@ -82,7 +81,7 @@
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rbK-cS-VQl">
|
||||
<rect key="frame" x="-2" y="131" width="123" height="16"/>
|
||||
<rect key="frame" x="-2" y="128" width="123" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Default RSS reader:" id="bUb-r3-SmS">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -90,10 +89,10 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="cSu-T2-Jby">
|
||||
<rect key="frame" x="125" y="126" width="218" height="25"/>
|
||||
<rect key="frame" x="125" y="122" width="212" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="NetNewsWire" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="bEy-Qx-Grl" id="Dyk-WN-XOo" userLabel="Popup">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<menu key="menu" id="8PF-kj-u99">
|
||||
<items>
|
||||
<menuItem title="NetNewsWire" state="on" id="bEy-Qx-Grl"/>
|
||||
@ -105,7 +104,7 @@
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<button verticalHuggingPriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="Ubm-Pk-l7x">
|
||||
<rect key="frame" x="125" y="67" width="215" height="18"/>
|
||||
<rect key="frame" x="125" y="64" width="211" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Open in background in browser" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="t0a-LN-rCv">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -125,7 +124,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jVd-Ie-CGX">
|
||||
<rect key="frame" x="53" y="4" width="68" height="16"/>
|
||||
<rect key="frame" x="53" y="3" width="68" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Dock icon:" id="vFc-Nz-RFp">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -133,7 +132,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mwT-nY-TrX">
|
||||
<rect key="frame" x="125" y="3" width="143" height="18"/>
|
||||
<rect key="frame" x="125" y="2" width="139" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Show unread count" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="lh0-G6-9v4">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -154,18 +153,18 @@
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="j0t-Wa-UTL">
|
||||
<rect key="frame" x="145" y="32" width="197" height="28"/>
|
||||
<rect key="frame" x="145" y="30" width="191" height="28"/>
|
||||
<textFieldCell key="cell" controlSize="small" title="Hold the Shift key to invert this preference." id="EMq-9M-zTJ">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<font key="font" metaFont="toolTip"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ci4-fW-KjU">
|
||||
<rect key="frame" x="125" y="95" width="218" height="25"/>
|
||||
<rect key="frame" x="125" y="91" width="212" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Safari" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="ObP-qK-qDJ" id="hrm-aT-Rc2" userLabel="Popup">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<menu key="menu" id="j1i-Ev-7rI">
|
||||
<items>
|
||||
<menuItem title="Safari" state="on" id="ObP-qK-qDJ"/>
|
||||
@ -177,7 +176,7 @@
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Wsb-Lr-8Q7">
|
||||
<rect key="frame" x="45" y="100" width="76" height="16"/>
|
||||
<rect key="frame" x="45" y="97" width="76" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Web pages:" id="CgU-dE-Qtb">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -243,14 +242,14 @@
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="Advanced" id="GNh-Wp-giO" customClass="AdvancedPreferencesViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" id="Hij-7D-6Pw">
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="278"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="277"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="uJD-OF-YVY">
|
||||
<rect key="frame" x="60" y="20" width="330" height="238"/>
|
||||
<rect key="frame" x="60" y="20" width="330" height="237"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="EH5-aS-E55">
|
||||
<rect key="frame" x="-2" y="222" width="87" height="16"/>
|
||||
<rect key="frame" x="-2" y="221" width="87" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="App Updates:" id="zqG-X2-E9b">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -258,7 +257,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="T4A-0o-p2w">
|
||||
<rect key="frame" x="89" y="221" width="149" height="18"/>
|
||||
<rect key="frame" x="89" y="220" width="145" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Check automatically" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="dm8-Xy-0Ba">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -276,7 +275,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="QCu-J4-0yV">
|
||||
<rect key="frame" x="89" y="193" width="114" height="18"/>
|
||||
<rect key="frame" x="90" y="193" width="110" height="18"/>
|
||||
<buttonCell key="cell" type="radio" title="Release builds" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="F8M-rS-und">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -286,7 +285,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="CeE-AE-hRG">
|
||||
<rect key="frame" x="89" y="171" width="92" height="18"/>
|
||||
<rect key="frame" x="90" y="171" width="88" height="18"/>
|
||||
<buttonCell key="cell" type="radio" title="Test builds" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="Fuf-rU-D6M">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -307,7 +306,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="TKI-a9-bRX">
|
||||
<rect key="frame" x="84" y="81" width="160" height="32"/>
|
||||
<rect key="frame" x="85" y="80" width="154" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Check for Updates" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="AaA-Rr-UYD">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -325,7 +324,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="UHg-1l-FlD">
|
||||
<rect key="frame" x="89" y="39" width="142" height="18"/>
|
||||
<rect key="frame" x="89" y="39" width="138" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Send automatically" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="jnc-C5-4oI">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -345,7 +344,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="uuc-f2-OFX">
|
||||
<rect key="frame" x="84" y="-7" width="160" height="32"/>
|
||||
<rect key="frame" x="85" y="-7" width="154" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Privacy Policy" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="kSv-Wu-NYx">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -416,16 +415,16 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="7UM-iq-OLB" customClass="PreferencesTableViewBackgroundView" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="44" width="160" height="223"/>
|
||||
<rect key="frame" x="20" y="44" width="160" height="213"/>
|
||||
<subviews>
|
||||
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="26" horizontalPageScroll="10" verticalLineScroll="26" verticalPageScroll="10" hasHorizontalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="PaF-du-r3c">
|
||||
<rect key="frame" x="1" y="0.0" width="158" height="222"/>
|
||||
<rect key="frame" x="1" y="0.0" width="158" height="212"/>
|
||||
<clipView key="contentView" id="cil-Gq-akO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="158" height="222"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="158" height="212"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnSelection="YES" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="24" viewBased="YES" id="aTp-KR-y6b">
|
||||
<rect key="frame" x="0.0" y="0.0" width="188" height="222"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="159" height="212"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -444,7 +443,7 @@
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView identifier="Cell" id="h2e-5a-qNO">
|
||||
<rect key="frame" x="11" y="1" width="165" height="17"/>
|
||||
<rect key="frame" x="1" y="1" width="156" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="27f-p8-Wnt">
|
||||
@ -456,7 +455,7 @@
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSActionTemplate" id="lKA-xK-bHU"/>
|
||||
</imageView>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" allowsExpansionToolTips="YES" translatesAutoresizingMaskIntoConstraints="NO" id="hR2-bm-0wE">
|
||||
<rect key="frame" x="26" y="1" width="135" height="16"/>
|
||||
<rect key="frame" x="26" y="1" width="126" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="CcS-BO-sdv">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -532,7 +531,7 @@
|
||||
<rect key="frame" x="83" y="20" width="97" height="24"/>
|
||||
</customView>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="Y7D-xQ-wep">
|
||||
<rect key="frame" x="188" y="20" width="242" height="247"/>
|
||||
<rect key="frame" x="188" y="20" width="242" height="237"/>
|
||||
</customView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
@ -587,16 +586,16 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="pjs-G4-byk" customClass="PreferencesTableViewBackgroundView" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="20" y="44" width="160" height="216"/>
|
||||
<rect key="frame" x="20" y="44" width="160" height="206"/>
|
||||
<subviews>
|
||||
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="26" horizontalPageScroll="10" verticalLineScroll="26" verticalPageScroll="10" hasHorizontalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="29T-r2-ckC">
|
||||
<rect key="frame" x="1" y="0.0" width="158" height="215"/>
|
||||
<rect key="frame" x="1" y="0.0" width="158" height="205"/>
|
||||
<clipView key="contentView" id="dXw-GY-TP8">
|
||||
<rect key="frame" x="0.0" y="0.0" width="158" height="215"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="158" height="205"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnSelection="YES" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="24" viewBased="YES" id="dfn-Vn-oDp">
|
||||
<rect key="frame" x="0.0" y="0.0" width="188" height="215"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="159" height="205"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -615,7 +614,7 @@
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView identifier="Cell" id="xQs-6E-Kpy">
|
||||
<rect key="frame" x="11" y="1" width="165" height="17"/>
|
||||
<rect key="frame" x="1" y="1" width="156" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kmG-vw-CbN">
|
||||
@ -627,7 +626,7 @@
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSActionTemplate" id="OVD-Jo-TXU"/>
|
||||
</imageView>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" allowsExpansionToolTips="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6cr-cB-qAN">
|
||||
<rect key="frame" x="26" y="1" width="135" height="16"/>
|
||||
<rect key="frame" x="26" y="1" width="126" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="goO-QG-kk7">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@ -699,7 +698,7 @@
|
||||
<rect key="frame" x="83" y="20" width="97" height="24"/>
|
||||
</customView>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="N1N-pE-gBL">
|
||||
<rect key="frame" x="188" y="20" width="242" height="240"/>
|
||||
<rect key="frame" x="188" y="20" width="242" height="230"/>
|
||||
</customView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
@ -734,8 +733,8 @@
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="NSActionTemplate" width="17" height="15"/>
|
||||
<image name="NSAddTemplate" width="15" height="11"/>
|
||||
<image name="NSRemoveTemplate" width="15" height="2"/>
|
||||
<image name="NSActionTemplate" width="14" height="14"/>
|
||||
<image name="NSAddTemplate" width="11" height="11"/>
|
||||
<image name="NSRemoveTemplate" width="11" height="11"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
@ -138,6 +138,17 @@ final class AppDefaults: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
static var userInterfaceColorScheme: ColorScheme? {
|
||||
switch AppDefaults.shared.userInterfaceColorPalette {
|
||||
case .light:
|
||||
return ColorScheme.light
|
||||
case .dark:
|
||||
return ColorScheme.dark
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Feeds & Folders
|
||||
@AppStorage(Key.addWebFeedAccountID, store: store) var addWebFeedAccountID: String?
|
||||
|
||||
|
@ -21,6 +21,7 @@ struct MainApp: App {
|
||||
@StateObject private var refreshProgress = RefreshProgressModel()
|
||||
@StateObject private var defaults = AppDefaults.shared
|
||||
|
||||
|
||||
@SceneBuilder var body: some Scene {
|
||||
#if os(macOS)
|
||||
WindowGroup {
|
||||
@ -29,6 +30,7 @@ struct MainApp: App {
|
||||
.onAppear { refreshProgress.startup() }
|
||||
.environmentObject(refreshProgress)
|
||||
.environmentObject(defaults)
|
||||
.preferredColorScheme(AppDefaults.userInterfaceColorScheme)
|
||||
}
|
||||
.windowToolbarStyle(UnifiedWindowToolbarStyle())
|
||||
.commands {
|
||||
@ -72,6 +74,7 @@ struct MainApp: App {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Mac Preferences
|
||||
Settings {
|
||||
MacPreferencesView()
|
||||
@ -79,8 +82,8 @@ struct MainApp: App {
|
||||
.frame(width: 500)
|
||||
.navigationTitle("Preferences")
|
||||
.environmentObject(defaults)
|
||||
.preferredColorScheme(AppDefaults.userInterfaceColorScheme)
|
||||
}
|
||||
.windowToolbarStyle(UnifiedWindowToolbarStyle())
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -65,7 +65,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
||||
private let appNewsURLString = "https://nnw.ranchero.com/feed.json"
|
||||
private let appMovementMonitor = RSAppMovementMonitor()
|
||||
#if !MAC_APP_STORE && !TEST
|
||||
private var softwareUpdater: SPUUpdater!
|
||||
var softwareUpdater: SPUUpdater!
|
||||
#endif
|
||||
|
||||
override init() {
|
||||
|
@ -35,5 +35,23 @@
|
||||
<string>$(ORGANIZATION_IDENTIFIER)</string>
|
||||
<key>DeveloperEntitlements</key>
|
||||
<string>$(DEVELOPER_ENTITLEMENTS)</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>RSS Feed</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>feed</string>
|
||||
<string>feeds</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>SUFeedURL</key>
|
||||
<string>https://ranchero.com/downloads/netnewswire-5.1-beta.xml</string>
|
||||
<key>FeedURLForTestBuilds</key>
|
||||
<string>https://ranchero.com/downloads/netnewswire-5.1-beta.xml</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -7,83 +7,90 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MacPreferenceViewModel {
|
||||
|
||||
enum PreferencePane: Int, CaseIterable {
|
||||
case general = 0
|
||||
case accounts = 1
|
||||
case advanced = 2
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .general:
|
||||
return "General"
|
||||
case .accounts:
|
||||
return "Accounts"
|
||||
case .advanced:
|
||||
return "Advanced"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var currentPreferencePane: PreferencePane = PreferencePane.general
|
||||
|
||||
enum PreferencePane: Int, CaseIterable {
|
||||
case general = 0
|
||||
case accounts = 1
|
||||
case advanced = 2
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .general:
|
||||
return "General"
|
||||
case .accounts:
|
||||
return "Accounts"
|
||||
case .advanced:
|
||||
return "Advanced"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MacPreferencesView: View {
|
||||
|
||||
@EnvironmentObject var defaults: AppDefaults
|
||||
@State private var viewModel = MacPreferenceViewModel()
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
if viewModel.currentPreferencePane == .general {
|
||||
AnyView(GeneralPreferencesView().environmentObject(defaults))
|
||||
}
|
||||
else if viewModel.currentPreferencePane == .accounts {
|
||||
AnyView(AccountsPreferencesView().environmentObject(defaults))
|
||||
}
|
||||
else {
|
||||
AnyView(AdvancedPreferencesView().environmentObject(defaults))
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem {
|
||||
Button(action: {
|
||||
viewModel.currentPreferencePane = .general
|
||||
}, label: {
|
||||
Image(systemName: "checkmark.rectangle")
|
||||
Text("General")
|
||||
})
|
||||
}
|
||||
ToolbarItem {
|
||||
Button(action: {
|
||||
viewModel.currentPreferencePane = .accounts
|
||||
}, label: {
|
||||
Image(systemName: "network")
|
||||
Text("Accounts")
|
||||
})
|
||||
}
|
||||
ToolbarItem {
|
||||
Button(action: {
|
||||
viewModel.currentPreferencePane = .advanced
|
||||
}, label: {
|
||||
Image(systemName: "gearshape.fill")
|
||||
Text("Advanced")
|
||||
})
|
||||
}
|
||||
}
|
||||
.presentedWindowToolbarStyle(UnifiedCompactWindowToolbarStyle())
|
||||
.presentedWindowStyle(TitleBarWindowStyle())
|
||||
.navigationTitle(Text(viewModel.currentPreferencePane.description))
|
||||
}
|
||||
|
||||
@EnvironmentObject var defaults: AppDefaults
|
||||
@State private var preferencePane: PreferencePane = .general
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
switch preferencePane {
|
||||
case .general:
|
||||
GeneralPreferencesView()
|
||||
.environmentObject(defaults)
|
||||
case .accounts:
|
||||
AccountsPreferencesView()
|
||||
.environmentObject(defaults)
|
||||
case .advanced:
|
||||
AdvancedPreferencesView()
|
||||
.environmentObject(defaults)
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem {
|
||||
HStack {
|
||||
Button(action: {
|
||||
preferencePane = .general
|
||||
}, label: {
|
||||
VStack {
|
||||
Image(systemName: "gearshape")
|
||||
.font(.title2)
|
||||
Text("General")
|
||||
}.foregroundColor(
|
||||
preferencePane == .general ? Color("AccentColor") : Color.gray
|
||||
)
|
||||
}).frame(width: 70)
|
||||
Button(action: {
|
||||
preferencePane = .accounts
|
||||
}, label: {
|
||||
VStack {
|
||||
Image(systemName: "at")
|
||||
.font(.title2)
|
||||
Text("Accounts")
|
||||
}.foregroundColor(
|
||||
preferencePane == .accounts ? Color("AccentColor") : Color.gray
|
||||
)
|
||||
}).frame(width: 70)
|
||||
Button(action: {
|
||||
preferencePane = .advanced
|
||||
}, label: {
|
||||
VStack {
|
||||
Image(systemName: "scale.3d")
|
||||
.font(.title2)
|
||||
Text("Advanced")
|
||||
}.foregroundColor(
|
||||
preferencePane == .advanced ? Color("AccentColor") : Color.gray
|
||||
)
|
||||
}).frame(width: 70)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
struct MacPreferencesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
MacPreferencesView()
|
||||
}
|
||||
static var previews: some View {
|
||||
MacPreferencesView()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,93 @@
|
||||
//
|
||||
// AccountsPreferencesModel.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 13/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Account
|
||||
import Combine
|
||||
|
||||
class AccountsPreferencesModel: ObservableObject {
|
||||
|
||||
enum AccountConfigurationSheets {
|
||||
case add, credentials, none
|
||||
}
|
||||
|
||||
// Selected Account
|
||||
public private(set) var account: Account?
|
||||
|
||||
// All Accounts
|
||||
@Published var sortedAccounts: [Account] = []
|
||||
@Published var selectedConfiguredAccountID: String? = AccountManager.shared.defaultAccount.accountID {
|
||||
didSet {
|
||||
if let accountID = selectedConfiguredAccountID {
|
||||
account = sortedAccounts.first(where: { $0.accountID == accountID })
|
||||
accountIsActive = account?.isActive ?? false
|
||||
accountName = account?.name ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@Published var showAddAccountView: Bool = false
|
||||
var selectedAccountIsDefault: Bool {
|
||||
guard let selected = selectedConfiguredAccountID else {
|
||||
return true
|
||||
}
|
||||
if selected == AccountManager.shared.defaultAccount.accountID {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Edit Account
|
||||
@Published var accountIsActive: Bool = false {
|
||||
didSet {
|
||||
account?.isActive = accountIsActive
|
||||
}
|
||||
}
|
||||
@Published var accountName: String = "" {
|
||||
didSet {
|
||||
account?.name = accountName
|
||||
}
|
||||
}
|
||||
|
||||
// Sheets
|
||||
@Published var showSheet: Bool = false
|
||||
@Published var sheetToShow: AccountConfigurationSheets = .none {
|
||||
didSet {
|
||||
showSheet = sheetToShow != .none
|
||||
}
|
||||
}
|
||||
@Published var showDeleteConfirmation: Bool = false
|
||||
|
||||
// Subscriptions
|
||||
var notificationSubscriptions = Set<AnyCancellable>()
|
||||
|
||||
init() {
|
||||
sortedAccounts = AccountManager.shared.sortedAccounts
|
||||
|
||||
NotificationCenter.default.publisher(for: .UserDidAddAccount).sink(receiveValue: { [weak self] _ in
|
||||
self?.sortedAccounts = AccountManager.shared.sortedAccounts
|
||||
}).store(in: ¬ificationSubscriptions)
|
||||
|
||||
NotificationCenter.default.publisher(for: .UserDidDeleteAccount).sink(receiveValue: { [weak self] _ in
|
||||
self?.selectedConfiguredAccountID = nil
|
||||
self?.sortedAccounts = AccountManager.shared.sortedAccounts
|
||||
self?.selectedConfiguredAccountID = AccountManager.shared.defaultAccount.accountID
|
||||
}).store(in: ¬ificationSubscriptions)
|
||||
|
||||
NotificationCenter.default.publisher(for: .AccountStateDidChange).sink(receiveValue: { [weak self] notification in
|
||||
guard let account = notification.object as? Account else {
|
||||
return
|
||||
}
|
||||
if account.accountID == self?.account?.accountID {
|
||||
self?.account = account
|
||||
self?.accountIsActive = account.isActive
|
||||
self?.accountName = account.name ?? ""
|
||||
}
|
||||
}).store(in: ¬ificationSubscriptions)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
//
|
||||
// AccountsPreferencesView.swift
|
||||
// macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 27/6/20.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Account
|
||||
|
||||
|
||||
struct AccountsPreferencesView: View {
|
||||
|
||||
@StateObject var viewModel = AccountsPreferencesModel()
|
||||
@State private var hoverOnAdd: Bool = false
|
||||
@State private var hoverOnRemove: Bool = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack(alignment: .top, spacing: 10) {
|
||||
listOfAccounts
|
||||
|
||||
AccountDetailView(viewModel: viewModel)
|
||||
.frame(height: 300, alignment: .leading)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.sheet(isPresented: $viewModel.showSheet,
|
||||
onDismiss: { viewModel.sheetToShow = .none },
|
||||
content: {
|
||||
switch viewModel.sheetToShow {
|
||||
case .add:
|
||||
AddAccountView(preferencesModel: viewModel)
|
||||
case .credentials:
|
||||
EditAccountCredentialsView(viewModel: viewModel)
|
||||
case .none:
|
||||
EmptyView()
|
||||
}
|
||||
})
|
||||
.alert(isPresented: $viewModel.showDeleteConfirmation, content: {
|
||||
Alert(title: Text("Delete \(viewModel.account!.nameForDisplay)?"),
|
||||
message: Text("Are you sure you want to delete the account \"\(viewModel.account!.nameForDisplay)\"? This can not be undone."),
|
||||
primaryButton: .destructive(Text("Delete"), action: {
|
||||
AccountManager.shared.deleteAccount(viewModel.account!)
|
||||
viewModel.showDeleteConfirmation = false
|
||||
}),
|
||||
secondaryButton: .cancel({
|
||||
viewModel.showDeleteConfirmation = false
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
var listOfAccounts: some View {
|
||||
VStack(alignment: .leading) {
|
||||
List(viewModel.sortedAccounts, id: \.accountID, selection: $viewModel.selectedConfiguredAccountID) {
|
||||
ConfiguredAccountRow(account: $0)
|
||||
.id($0.accountID)
|
||||
}.overlay(
|
||||
Group {
|
||||
bottomButtonStack
|
||||
}, alignment: .bottom)
|
||||
}
|
||||
.frame(width: 160, height: 300, alignment: .leading)
|
||||
.border(Color.gray, width: 1)
|
||||
}
|
||||
|
||||
var bottomButtonStack: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Divider()
|
||||
HStack(alignment: .center, spacing: 4) {
|
||||
Button(action: {
|
||||
viewModel.sheetToShow = .add
|
||||
}, label: {
|
||||
Image(systemName: "plus")
|
||||
.font(.title)
|
||||
.frame(width: 30, height: 30)
|
||||
.overlay(RoundedRectangle(cornerRadius: 4, style: .continuous)
|
||||
.foregroundColor(hoverOnAdd ? Color.gray.opacity(0.1) : Color.clear))
|
||||
.padding(4)
|
||||
})
|
||||
.buttonStyle(BorderlessButtonStyle())
|
||||
.onHover { hovering in
|
||||
hoverOnAdd = hovering
|
||||
}
|
||||
.help("Add Account")
|
||||
|
||||
Button(action: {
|
||||
viewModel.showDeleteConfirmation = true
|
||||
}, label: {
|
||||
Image(systemName: "minus")
|
||||
.font(.title)
|
||||
.frame(width: 30, height: 30)
|
||||
.overlay(RoundedRectangle(cornerRadius: 4, style: .continuous)
|
||||
.foregroundColor(hoverOnRemove ? Color.gray.opacity(0.1) : Color.clear))
|
||||
.padding(4)
|
||||
})
|
||||
.buttonStyle(BorderlessButtonStyle())
|
||||
.onHover { hovering in
|
||||
hoverOnRemove = hovering
|
||||
}
|
||||
.disabled(viewModel.selectedAccountIsDefault)
|
||||
.help("Delete Account")
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.background(Color.init(.windowBackgroundColor))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,296 @@
|
||||
//
|
||||
// AddAccountModel.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 13/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Account
|
||||
import RSWeb
|
||||
import Secrets
|
||||
import RSCore
|
||||
|
||||
class AddAccountModel: ObservableObject {
|
||||
|
||||
#if DEBUG
|
||||
let addableAccountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly, .feedWrangler, .freshRSS, .cloudKit, .newsBlur]
|
||||
#else
|
||||
let addableAccountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly]
|
||||
#endif
|
||||
|
||||
// Add Accounts
|
||||
@Published var selectedAddAccount: AccountType = .onMyMac
|
||||
@Published var userName: String = ""
|
||||
@Published var password: String = ""
|
||||
@Published var apiUrl: String = ""
|
||||
@Published var newLocalAccountName: String = ""
|
||||
@Published var accountIsAuthenticating: Bool = false
|
||||
@Published var addAccountError: AccountUpdateErrors = .none {
|
||||
didSet {
|
||||
if addAccountError == .none {
|
||||
showError = false
|
||||
} else {
|
||||
showError = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@Published var showError: Bool = false
|
||||
@Published var accountAdded: Bool = false
|
||||
|
||||
func resetUserEntries() {
|
||||
userName = ""
|
||||
password = ""
|
||||
newLocalAccountName = ""
|
||||
apiUrl = ""
|
||||
}
|
||||
|
||||
func authenticateAccount() {
|
||||
switch selectedAddAccount {
|
||||
case .onMyMac:
|
||||
addLocalAccount()
|
||||
case .cloudKit:
|
||||
authenticateCloudKit()
|
||||
case .feedbin:
|
||||
authenticateFeedbin()
|
||||
case .feedWrangler:
|
||||
authenticateFeedWrangler()
|
||||
case .freshRSS:
|
||||
authenticateFreshRSS()
|
||||
case .feedly:
|
||||
authenticateFeedly()
|
||||
case .newsBlur:
|
||||
authenticateNewsBlur()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK:- Authentication APIs
|
||||
|
||||
extension AddAccountModel {
|
||||
|
||||
private func addLocalAccount() {
|
||||
let account = AccountManager.shared.createAccount(type: .onMyMac)
|
||||
account.name = newLocalAccountName
|
||||
accountAdded = true
|
||||
}
|
||||
|
||||
private func authenticateFeedbin() {
|
||||
accountIsAuthenticating = true
|
||||
let credentials = Credentials(type: .basic, username: userName, secret: password)
|
||||
|
||||
Account.validateCredentials(type: .feedbin, credentials: credentials) { [weak self] result in
|
||||
|
||||
guard let self = self else { return }
|
||||
|
||||
self.accountIsAuthenticating = false
|
||||
|
||||
switch result {
|
||||
case .success(let validatedCredentials):
|
||||
|
||||
guard let validatedCredentials = validatedCredentials else {
|
||||
self.addAccountError = .invalidUsernamePassword
|
||||
return
|
||||
}
|
||||
|
||||
let account = AccountManager.shared.createAccount(type: .feedbin)
|
||||
|
||||
do {
|
||||
try account.removeCredentials(type: .basic)
|
||||
try account.storeCredentials(validatedCredentials)
|
||||
self.accountAdded = true
|
||||
account.refreshAll(completion: { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
self.addAccountError = .other(error: error)
|
||||
}
|
||||
})
|
||||
|
||||
} catch {
|
||||
self.addAccountError = .keyChainError
|
||||
}
|
||||
|
||||
case .failure:
|
||||
self.addAccountError = .networkError
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func authenticateFeedWrangler() {
|
||||
|
||||
accountIsAuthenticating = true
|
||||
let credentials = Credentials(type: .feedWranglerBasic, username: userName, secret: password)
|
||||
|
||||
Account.validateCredentials(type: .feedWrangler, credentials: credentials) { [weak self] result in
|
||||
|
||||
guard let self = self else { return }
|
||||
|
||||
self.accountIsAuthenticating = false
|
||||
|
||||
switch result {
|
||||
case .success(let validatedCredentials):
|
||||
|
||||
guard let validatedCredentials = validatedCredentials else {
|
||||
self.addAccountError = .invalidUsernamePassword
|
||||
return
|
||||
}
|
||||
|
||||
let account = AccountManager.shared.createAccount(type: .feedWrangler)
|
||||
|
||||
do {
|
||||
try account.removeCredentials(type: .feedWranglerBasic)
|
||||
try account.removeCredentials(type: .feedWranglerToken)
|
||||
try account.storeCredentials(credentials)
|
||||
try account.storeCredentials(validatedCredentials)
|
||||
self.accountAdded = true
|
||||
account.refreshAll(completion: { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
self.addAccountError = .other(error: error)
|
||||
}
|
||||
})
|
||||
|
||||
} catch {
|
||||
self.addAccountError = .keyChainError
|
||||
}
|
||||
|
||||
case .failure:
|
||||
self.addAccountError = .networkError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func authenticateNewsBlur() {
|
||||
accountIsAuthenticating = true
|
||||
let credentials = Credentials(type: .newsBlurBasic, username: userName, secret: password)
|
||||
|
||||
Account.validateCredentials(type: .newsBlur, credentials: credentials) { [weak self] result in
|
||||
|
||||
guard let self = self else { return }
|
||||
|
||||
self.accountIsAuthenticating = false
|
||||
|
||||
switch result {
|
||||
case .success(let validatedCredentials):
|
||||
|
||||
guard let validatedCredentials = validatedCredentials else {
|
||||
self.addAccountError = .invalidUsernamePassword
|
||||
return
|
||||
}
|
||||
|
||||
let account = AccountManager.shared.createAccount(type: .newsBlur)
|
||||
|
||||
do {
|
||||
try account.removeCredentials(type: .newsBlurBasic)
|
||||
try account.removeCredentials(type: .newsBlurSessionId)
|
||||
try account.storeCredentials(credentials)
|
||||
try account.storeCredentials(validatedCredentials)
|
||||
self.accountAdded = true
|
||||
account.refreshAll(completion: { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
self.addAccountError = .other(error: error)
|
||||
}
|
||||
})
|
||||
|
||||
} catch {
|
||||
self.addAccountError = .keyChainError
|
||||
}
|
||||
|
||||
case .failure:
|
||||
self.addAccountError = .networkError
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func authenticateFreshRSS() {
|
||||
accountIsAuthenticating = true
|
||||
let credentials = Credentials(type: .readerBasic, username: userName, secret: password)
|
||||
|
||||
Account.validateCredentials(type: .freshRSS, credentials: credentials, endpoint: URL(string: apiUrl)!) { [weak self] result in
|
||||
|
||||
guard let self = self else { return }
|
||||
|
||||
self.accountIsAuthenticating = false
|
||||
|
||||
switch result {
|
||||
case .success(let validatedCredentials):
|
||||
|
||||
guard let validatedCredentials = validatedCredentials else {
|
||||
self.addAccountError = .invalidUsernamePassword
|
||||
return
|
||||
}
|
||||
|
||||
let account = AccountManager.shared.createAccount(type: .freshRSS)
|
||||
|
||||
do {
|
||||
try account.removeCredentials(type: .readerBasic)
|
||||
try account.removeCredentials(type: .readerAPIKey)
|
||||
try account.storeCredentials(credentials)
|
||||
try account.storeCredentials(validatedCredentials)
|
||||
self.accountAdded = true
|
||||
account.refreshAll(completion: { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
self.addAccountError = .other(error: error)
|
||||
}
|
||||
})
|
||||
|
||||
} catch {
|
||||
self.addAccountError = .keyChainError
|
||||
}
|
||||
|
||||
case .failure:
|
||||
self.addAccountError = .networkError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func authenticateCloudKit() {
|
||||
let _ = AccountManager.shared.createAccount(type: .cloudKit)
|
||||
self.accountAdded = true
|
||||
}
|
||||
|
||||
private func authenticateFeedly() {
|
||||
accountIsAuthenticating = true
|
||||
let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly)
|
||||
addAccount.delegate = self
|
||||
addAccount.presentationAnchor = NSApplication.shared.windows.last
|
||||
MainThreadOperationQueue.shared.add(addAccount)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK:- OAuthAccountAuthorizationOperationDelegate
|
||||
extension AddAccountModel: OAuthAccountAuthorizationOperationDelegate {
|
||||
func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account) {
|
||||
accountIsAuthenticating = false
|
||||
accountAdded = true
|
||||
account.refreshAll { [weak self] result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
self?.addAccountError = .other(error: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) {
|
||||
accountIsAuthenticating = false
|
||||
addAccountError = .other(error: error)
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
//
|
||||
// AddAccountPickerRow.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 13/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Account
|
||||
|
||||
struct AddAccountPickerRow: View {
|
||||
|
||||
var accountType: AccountType
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
if let img = AppAssets.image(for: accountType) {
|
||||
Image(rsImage: img)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 15, height: 15)
|
||||
}
|
||||
|
||||
switch accountType {
|
||||
case .onMyMac:
|
||||
Text(Account.defaultLocalAccountName)
|
||||
case .cloudKit:
|
||||
Text("iCloud")
|
||||
case .feedbin:
|
||||
Text("Feedbin")
|
||||
case .feedWrangler:
|
||||
Text("FeedWrangler")
|
||||
case .freshRSS:
|
||||
Text("FreshRSS")
|
||||
case .feedly:
|
||||
Text("Feedly")
|
||||
case .newsBlur:
|
||||
Text("NewsBlur")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AddAccountPickerRow_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AddAccountPickerRow(accountType: .onMyMac)
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
//
|
||||
// AddAccountView.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 13/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Account
|
||||
|
||||
struct AddAccountView: View {
|
||||
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
@ObservedObject var preferencesModel: AccountsPreferencesModel
|
||||
@StateObject private var viewModel = AddAccountModel()
|
||||
|
||||
var body: some View {
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("Add an Account").font(.headline)
|
||||
Form {
|
||||
Picker("Account Type",
|
||||
selection: $viewModel.selectedAddAccount,
|
||||
content: {
|
||||
ForEach(0..<viewModel.addableAccountTypes.count, content: { i in
|
||||
AddAccountPickerRow(accountType: viewModel.addableAccountTypes[i]).tag(viewModel.addableAccountTypes[i])
|
||||
})
|
||||
})
|
||||
|
||||
switch viewModel.selectedAddAccount {
|
||||
case .onMyMac:
|
||||
addLocalAccountView
|
||||
case .cloudKit:
|
||||
iCloudAccountView
|
||||
case .feedbin:
|
||||
userNameAndPasswordView
|
||||
case .feedWrangler:
|
||||
userNameAndPasswordView
|
||||
case .freshRSS:
|
||||
userNamePasswordAndAPIUrlView
|
||||
case .feedly:
|
||||
oAuthView
|
||||
case .newsBlur:
|
||||
userNameAndPasswordView
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
HStack {
|
||||
if viewModel.accountIsAuthenticating {
|
||||
ProgressView("Adding Account")
|
||||
}
|
||||
Spacer()
|
||||
Button(action: { presentationMode.wrappedValue.dismiss() }, label: {
|
||||
Text("Cancel")
|
||||
})
|
||||
|
||||
switch viewModel.selectedAddAccount {
|
||||
case .onMyMac:
|
||||
Button("Add Account", action: {
|
||||
viewModel.authenticateAccount()
|
||||
})
|
||||
case .feedly:
|
||||
Button("Authenticate", action: {
|
||||
viewModel.authenticateAccount()
|
||||
})
|
||||
case .cloudKit:
|
||||
Button("Add Account", action: {
|
||||
viewModel.authenticateAccount()
|
||||
})
|
||||
case .freshRSS:
|
||||
Button("Add Account", action: {
|
||||
viewModel.authenticateAccount()
|
||||
})
|
||||
.disabled(viewModel.userName.count == 0 || viewModel.password.count == 0 || viewModel.apiUrl.count == 0)
|
||||
default:
|
||||
Button("Add Account", action: {
|
||||
viewModel.authenticateAccount()
|
||||
})
|
||||
.disabled(viewModel.userName.count == 0 || viewModel.password.count == 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(width: 300, height: 200, alignment: .top)
|
||||
.padding()
|
||||
.onChange(of: viewModel.selectedAddAccount) { _ in
|
||||
viewModel.resetUserEntries()
|
||||
}
|
||||
.onChange(of: viewModel.accountAdded) { value in
|
||||
if value == true {
|
||||
preferencesModel.showAddAccountView = false
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $viewModel.showError) {
|
||||
Alert(title: Text("Error Adding Account"),
|
||||
message: Text(viewModel.addAccountError.description),
|
||||
dismissButton: .default(Text("Dismiss"),
|
||||
action: {
|
||||
viewModel.addAccountError = .none
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var addLocalAccountView: some View {
|
||||
Group {
|
||||
TextField("Account Name", text: $viewModel.newLocalAccountName)
|
||||
Text("This account stores all of its data on your device. It does not sync.")
|
||||
.foregroundColor(.secondary)
|
||||
.multilineTextAlignment(.leading)
|
||||
}.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
}
|
||||
|
||||
var iCloudAccountView: some View {
|
||||
Group {
|
||||
Text("This account syncs across your devices using your iCloud account.")
|
||||
.foregroundColor(.secondary)
|
||||
.multilineTextAlignment(.leading)
|
||||
}.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
}
|
||||
|
||||
var userNameAndPasswordView: some View {
|
||||
Group {
|
||||
TextField("Email", text: $viewModel.userName)
|
||||
SecureField("Password", text: $viewModel.password)
|
||||
}.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
}
|
||||
|
||||
var userNamePasswordAndAPIUrlView: some View {
|
||||
Group {
|
||||
TextField("Email", text: $viewModel.userName)
|
||||
SecureField("Password", text: $viewModel.password)
|
||||
TextField("API URL", text: $viewModel.apiUrl)
|
||||
}.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
}
|
||||
|
||||
var oAuthView: some View {
|
||||
Group {
|
||||
Text("Click Authenticate")
|
||||
}.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
struct AddAccountView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AddAccountView(preferencesModel: AccountsPreferencesModel())
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
//
|
||||
// ConfiguredAccountRow.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 13/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Account
|
||||
|
||||
struct ConfiguredAccountRow: View {
|
||||
|
||||
var account: Account
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .center) {
|
||||
if let img = account.smallIcon?.image {
|
||||
Image(rsImage: img)
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
.aspectRatio(contentMode: .fit)
|
||||
}
|
||||
Text(account.nameForDisplay)
|
||||
}.padding(.vertical, 4)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,83 @@
|
||||
//
|
||||
// AccountDetailView.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 14/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Account
|
||||
import Combine
|
||||
|
||||
struct AccountDetailView: View {
|
||||
|
||||
@ObservedObject var viewModel: AccountsPreferencesModel
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 8, style: .circular)
|
||||
.foregroundColor(Color.secondary.opacity(0.1))
|
||||
.padding(.top, 8)
|
||||
|
||||
VStack {
|
||||
editAccountHeader
|
||||
if viewModel.account != nil {
|
||||
editAccountForm
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var editAccountHeader: some View {
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Account Information", action: {})
|
||||
Spacer()
|
||||
}
|
||||
.padding([.leading, .trailing, .bottom], 4)
|
||||
}
|
||||
|
||||
var editAccountForm: some View {
|
||||
Form(content: {
|
||||
HStack(alignment: .top) {
|
||||
Text("Type: ")
|
||||
.frame(width: 50)
|
||||
VStack(alignment: .leading) {
|
||||
Text(viewModel.account!.defaultName)
|
||||
Toggle("Active", isOn: $viewModel.accountIsActive)
|
||||
}
|
||||
}
|
||||
HStack(alignment: .top) {
|
||||
Text("Name: ")
|
||||
.frame(width: 50)
|
||||
VStack(alignment: .leading) {
|
||||
TextField(viewModel.account!.name ?? "", text: $viewModel.accountName)
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
Text("The name appears in the sidebar. It can be anything you want. You can even use emoji. 🎸")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
if viewModel.account?.type != .onMyMac {
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Credentials", action: {
|
||||
viewModel.sheetToShow = .credentials
|
||||
})
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
})
|
||||
.padding()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct AccountDetailView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AccountDetailView(viewModel: AccountsPreferencesModel())
|
||||
}
|
||||
}
|
@ -0,0 +1,276 @@
|
||||
//
|
||||
// EditAccountCredentialsModel.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 14/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Account
|
||||
import Secrets
|
||||
import RSCore
|
||||
|
||||
class EditAccountCredentialsModel: ObservableObject {
|
||||
|
||||
@Published var userName: String = ""
|
||||
@Published var password: String = ""
|
||||
@Published var apiUrl: String = ""
|
||||
@Published var accountIsUpdatingCredentials: Bool = false
|
||||
@Published var accountCredentialsWereUpdated: Bool = false
|
||||
@Published var error: AccountUpdateErrors = .none {
|
||||
didSet {
|
||||
if error == .none {
|
||||
showError = false
|
||||
} else {
|
||||
showError = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@Published var showError: Bool = false
|
||||
|
||||
func updateAccountCredentials(_ account: Account) {
|
||||
switch account.type {
|
||||
case .onMyMac:
|
||||
return
|
||||
case .feedbin:
|
||||
updateFeedbin(account)
|
||||
case .cloudKit:
|
||||
return
|
||||
case .feedWrangler:
|
||||
updateFeedWrangler(account)
|
||||
case .feedly:
|
||||
updateFeedly(account)
|
||||
case .freshRSS:
|
||||
updateFreshRSS(account)
|
||||
case .newsBlur:
|
||||
updateNewsblur(account)
|
||||
}
|
||||
}
|
||||
|
||||
func retrieveCredentials(_ account: Account) {
|
||||
switch account.type {
|
||||
case .feedbin:
|
||||
let credentials = try? account.retrieveCredentials(type: .basic)
|
||||
userName = credentials?.username ?? ""
|
||||
case .feedWrangler:
|
||||
let credentials = try? account.retrieveCredentials(type: .feedWranglerBasic)
|
||||
userName = credentials?.username ?? ""
|
||||
case .feedly:
|
||||
return
|
||||
case .freshRSS:
|
||||
let credentials = try? account.retrieveCredentials(type: .readerBasic)
|
||||
userName = credentials?.username ?? ""
|
||||
case .newsBlur:
|
||||
let credentials = try? account.retrieveCredentials(type: .newsBlurBasic)
|
||||
userName = credentials?.username ?? ""
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK:- Update API
|
||||
extension EditAccountCredentialsModel {
|
||||
|
||||
func updateFeedbin(_ account: Account) {
|
||||
accountIsUpdatingCredentials = true
|
||||
let credentials = Credentials(type: .basic, username: userName, secret: password)
|
||||
|
||||
Account.validateCredentials(type: .feedbin, credentials: credentials) { [weak self] result in
|
||||
|
||||
guard let self = self else { return }
|
||||
|
||||
self.accountIsUpdatingCredentials = false
|
||||
|
||||
switch result {
|
||||
case .success(let validatedCredentials):
|
||||
|
||||
guard let validatedCredentials = validatedCredentials else {
|
||||
self.error = .invalidUsernamePassword
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try account.removeCredentials(type: .basic)
|
||||
try account.storeCredentials(validatedCredentials)
|
||||
self.accountCredentialsWereUpdated = true
|
||||
account.refreshAll(completion: { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
self.error = .other(error: error)
|
||||
}
|
||||
})
|
||||
|
||||
} catch {
|
||||
self.error = .keyChainError
|
||||
}
|
||||
|
||||
case .failure:
|
||||
self.error = .networkError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateFeedWrangler(_ account: Account) {
|
||||
accountIsUpdatingCredentials = true
|
||||
let credentials = Credentials(type: .feedWranglerBasic, username: userName, secret: password)
|
||||
|
||||
Account.validateCredentials(type: .feedWrangler, credentials: credentials) { [weak self] result in
|
||||
|
||||
guard let self = self else { return }
|
||||
|
||||
self.accountIsUpdatingCredentials = false
|
||||
|
||||
switch result {
|
||||
case .success(let validatedCredentials):
|
||||
|
||||
guard let validatedCredentials = validatedCredentials else {
|
||||
self.error = .invalidUsernamePassword
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try account.removeCredentials(type: .feedWranglerBasic)
|
||||
try account.removeCredentials(type: .feedWranglerToken)
|
||||
try account.storeCredentials(credentials)
|
||||
try account.storeCredentials(validatedCredentials)
|
||||
self.accountCredentialsWereUpdated = true
|
||||
account.refreshAll(completion: { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
self.error = .other(error: error)
|
||||
}
|
||||
})
|
||||
|
||||
} catch {
|
||||
self.error = .keyChainError
|
||||
}
|
||||
|
||||
case .failure:
|
||||
self.error = .networkError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateFeedly(_ account: Account) {
|
||||
accountIsUpdatingCredentials = true
|
||||
let updateAccount = OAuthAccountAuthorizationOperation(accountType: .feedly)
|
||||
updateAccount.delegate = self
|
||||
updateAccount.presentationAnchor = NSApplication.shared.windows.last
|
||||
MainThreadOperationQueue.shared.add(updateAccount)
|
||||
}
|
||||
|
||||
func updateFreshRSS(_ account: Account) {
|
||||
accountIsUpdatingCredentials = true
|
||||
let credentials = Credentials(type: .readerBasic, username: userName, secret: password)
|
||||
|
||||
Account.validateCredentials(type: .freshRSS, credentials: credentials) { [weak self] result in
|
||||
|
||||
guard let self = self else { return }
|
||||
|
||||
self.accountIsUpdatingCredentials = false
|
||||
|
||||
switch result {
|
||||
case .success(let validatedCredentials):
|
||||
|
||||
guard let validatedCredentials = validatedCredentials else {
|
||||
self.error = .invalidUsernamePassword
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try account.removeCredentials(type: .readerBasic)
|
||||
try account.removeCredentials(type: .readerAPIKey)
|
||||
try account.storeCredentials(credentials)
|
||||
try account.storeCredentials(validatedCredentials)
|
||||
self.accountCredentialsWereUpdated = true
|
||||
account.refreshAll(completion: { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
self.error = .other(error: error)
|
||||
}
|
||||
})
|
||||
|
||||
} catch {
|
||||
self.error = .keyChainError
|
||||
}
|
||||
|
||||
case .failure:
|
||||
self.error = .networkError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNewsblur(_ account: Account) {
|
||||
accountIsUpdatingCredentials = true
|
||||
let credentials = Credentials(type: .newsBlurBasic, username: userName, secret: password)
|
||||
|
||||
Account.validateCredentials(type: .newsBlur, credentials: credentials) { [weak self] result in
|
||||
|
||||
guard let self = self else { return }
|
||||
|
||||
self.accountIsUpdatingCredentials = false
|
||||
|
||||
switch result {
|
||||
case .success(let validatedCredentials):
|
||||
|
||||
guard let validatedCredentials = validatedCredentials else {
|
||||
self.error = .invalidUsernamePassword
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try account.removeCredentials(type: .newsBlurBasic)
|
||||
try account.removeCredentials(type: .newsBlurSessionId)
|
||||
try account.storeCredentials(credentials)
|
||||
try account.storeCredentials(validatedCredentials)
|
||||
self.accountCredentialsWereUpdated = true
|
||||
account.refreshAll(completion: { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
self.error = .other(error: error)
|
||||
}
|
||||
})
|
||||
|
||||
} catch {
|
||||
self.error = .keyChainError
|
||||
}
|
||||
|
||||
case .failure:
|
||||
self.error = .networkError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK:- OAuthAccountAuthorizationOperationDelegate
|
||||
extension EditAccountCredentialsModel: OAuthAccountAuthorizationOperationDelegate {
|
||||
func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account) {
|
||||
accountIsUpdatingCredentials = false
|
||||
accountCredentialsWereUpdated = true
|
||||
account.refreshAll { [weak self] result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
case .failure(let error):
|
||||
self?.error = .other(error: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) {
|
||||
accountIsUpdatingCredentials = false
|
||||
self.error = .other(error: error)
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
//
|
||||
// EditAccountCredentialsView.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 14/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Secrets
|
||||
|
||||
struct EditAccountCredentialsView: View {
|
||||
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
@StateObject private var editModel = EditAccountCredentialsModel()
|
||||
@ObservedObject var viewModel: AccountsPreferencesModel
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
HStack {
|
||||
Spacer()
|
||||
Image(rsImage: viewModel.account!.smallIcon!.image)
|
||||
.resizable()
|
||||
.frame(width: 30, height: 30)
|
||||
Text(viewModel.account?.nameForDisplay ?? "")
|
||||
Spacer()
|
||||
}.padding()
|
||||
|
||||
HStack(alignment: .center) {
|
||||
VStack(alignment: .trailing, spacing: 12) {
|
||||
Text("Username: ")
|
||||
Text("Password: ")
|
||||
if viewModel.account?.type == .freshRSS {
|
||||
Text("API URL: ")
|
||||
}
|
||||
}.frame(width: 75)
|
||||
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
TextField("Username", text: $editModel.userName)
|
||||
SecureField("Password", text: $editModel.password)
|
||||
if viewModel.account?.type == .freshRSS {
|
||||
TextField("API URL", text: $editModel.apiUrl)
|
||||
}
|
||||
}
|
||||
}.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
|
||||
Spacer()
|
||||
HStack{
|
||||
if editModel.accountIsUpdatingCredentials {
|
||||
ProgressView("Updating")
|
||||
}
|
||||
Spacer()
|
||||
Button("Cancel", action: {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
})
|
||||
if viewModel.account?.type != .freshRSS {
|
||||
Button("Update", action: {
|
||||
editModel.updateAccountCredentials(viewModel.account!)
|
||||
}).disabled(editModel.userName.count == 0 || editModel.password.count == 0)
|
||||
} else {
|
||||
Button("Update", action: {
|
||||
editModel.updateAccountCredentials(viewModel.account!)
|
||||
}).disabled(editModel.userName.count == 0 || editModel.password.count == 0 || editModel.apiUrl.count == 0)
|
||||
}
|
||||
|
||||
}
|
||||
}.onAppear {
|
||||
editModel.retrieveCredentials(viewModel.account!)
|
||||
}
|
||||
.onChange(of: editModel.accountCredentialsWereUpdated) { value in
|
||||
if value == true {
|
||||
viewModel.sheetToShow = .none
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $editModel.showError) {
|
||||
Alert(title: Text("Error Adding Account"),
|
||||
message: Text(editModel.error.description),
|
||||
dismissButton: .default(Text("Dismiss"),
|
||||
action: {
|
||||
editModel.error = .none
|
||||
}))
|
||||
}
|
||||
.frame(idealWidth: 300, idealHeight: 200, alignment: .top)
|
||||
.padding()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct EditAccountCredentials_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
EditAccountCredentialsView(viewModel: AccountsPreferencesModel())
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
//
|
||||
// AccountManagement.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 14/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol AccountManagement {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
//
|
||||
// AccountUpdateErrors.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 14/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum AccountUpdateErrors: CustomStringConvertible {
|
||||
case invalidUsernamePassword, invalidUsernamePasswordAPI, networkError, keyChainError, other(error: Error) , none
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .invalidUsernamePassword:
|
||||
return NSLocalizedString("Invalid email or password combination.", comment: "Invalid email/password combination.")
|
||||
case .invalidUsernamePasswordAPI:
|
||||
return NSLocalizedString("Invalid email, password, or API URL combination.", comment: "Invalid email/password/API combination.")
|
||||
case .networkError:
|
||||
return NSLocalizedString("Network Error. Please try later.", comment: "Network Error. Please try later.")
|
||||
case .keyChainError:
|
||||
return NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error")
|
||||
case .other(let error):
|
||||
return NSLocalizedString(error.localizedDescription, comment: "Other add account error")
|
||||
default:
|
||||
return NSLocalizedString("N/A", comment: "N/A")
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: AccountUpdateErrors, rhs: AccountUpdateErrors) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.other(let lhsError), .other(let rhsError)):
|
||||
return lhsError.localizedDescription == rhsError.localizedDescription
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
//
|
||||
// AdvancedPreferencesModel.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 16/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class AdvancedPreferencesModel: ObservableObject {
|
||||
|
||||
let releaseBuildsURL = Bundle.main.infoDictionary!["SUFeedURL"]! as! String
|
||||
let testBuildsURL = Bundle.main.infoDictionary!["FeedURLForTestBuilds"]! as! String
|
||||
let appcastDefaultsKey = "SUFeedURL"
|
||||
|
||||
init() {
|
||||
if AppDefaults.shared.downloadTestBuilds == false {
|
||||
AppDefaults.store.setValue(releaseBuildsURL, forKey: appcastDefaultsKey)
|
||||
} else {
|
||||
AppDefaults.store.setValue(testBuildsURL, forKey: appcastDefaultsKey)
|
||||
}
|
||||
}
|
||||
|
||||
func updateAppcast() {
|
||||
if AppDefaults.shared.downloadTestBuilds == false {
|
||||
AppDefaults.store.setValue(releaseBuildsURL, forKey: appcastDefaultsKey)
|
||||
} else {
|
||||
AppDefaults.store.setValue(testBuildsURL, forKey: appcastDefaultsKey)
|
||||
}
|
||||
}
|
||||
}
|
@ -10,39 +10,36 @@ import SwiftUI
|
||||
struct AdvancedPreferencesView: View {
|
||||
|
||||
@EnvironmentObject private var preferences: AppDefaults
|
||||
@StateObject private var viewModel = AdvancedPreferencesModel()
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Form {
|
||||
Toggle("Check for app updates automatically", isOn: $preferences.checkForUpdatesAutomatically)
|
||||
|
||||
Toggle("Download Test Builds", isOn: $preferences.downloadTestBuilds)
|
||||
Text("If you’re not sure, don't enable test builds. Test builds may have bugs, which may include crashing bugs and data loss.")
|
||||
.foregroundColor(.secondary)
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("If you’re not sure, don't enable test builds. Test builds may have bugs, which may include crashing bugs and data loss.").foregroundColor(.secondary)
|
||||
Button("Check for Updates") {
|
||||
appDelegate.softwareUpdater.checkForUpdates()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Check for Updates", action: {})
|
||||
Spacer()
|
||||
}.padding(.vertical, 12)
|
||||
|
||||
|
||||
Toggle("Send Crash Logs Automatically", isOn: $preferences.sendCrashLogs)
|
||||
|
||||
Spacer()
|
||||
Divider()
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Privacy Policy", action: {})
|
||||
Button("Privacy Policy", action: {
|
||||
NSWorkspace.shared.open(URL(string: "https://ranchero.com/netnewswire/privacypolicy")!)
|
||||
})
|
||||
Spacer()
|
||||
}.padding(.top, 12)
|
||||
|
||||
|
||||
}
|
||||
Spacer()
|
||||
}.frame(width: 300, alignment: .center)
|
||||
}
|
||||
}
|
||||
.onChange(of: preferences.downloadTestBuilds, perform: { _ in
|
||||
viewModel.updateAppcast()
|
||||
})
|
||||
.frame(width: 400, alignment: .center)
|
||||
.lineLimit(3)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
//
|
||||
// GeneralPreferencesModel.swift
|
||||
// Multiplatform macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 12/7/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
|
||||
class GeneralPreferencesModel: ObservableObject {
|
||||
|
||||
@Published var rssReaders = [RSSReader]()
|
||||
@Published var readerSelection: Int = 0 {
|
||||
willSet {
|
||||
if newValue != readerSelection {
|
||||
registerAppWithBundleID(rssReaders[newValue].bundleID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let readerInfo = RSSReaderInfo()
|
||||
|
||||
init() {
|
||||
prepareRSSReaders()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK:- RSS Readers
|
||||
|
||||
private extension GeneralPreferencesModel {
|
||||
|
||||
func prepareRSSReaders() {
|
||||
|
||||
// Populate rssReaders
|
||||
var thisApp = RSSReader(bundleID: Bundle.main.bundleIdentifier!)
|
||||
thisApp?.nameMinusAppSuffix.append(" (this app—multiplatform)")
|
||||
|
||||
let otherRSSReaders = readerInfo.rssReaders.filter { $0.bundleID != Bundle.main.bundleIdentifier! }.sorted(by: { $0.nameMinusAppSuffix < $1.nameMinusAppSuffix })
|
||||
rssReaders.append(thisApp!)
|
||||
rssReaders.append(contentsOf: otherRSSReaders)
|
||||
|
||||
if readerInfo.defaultRSSReaderBundleID != nil {
|
||||
let defaultReader = rssReaders.filter({ $0.bundleID == readerInfo.defaultRSSReaderBundleID })
|
||||
if defaultReader.count == 1 {
|
||||
let reader = defaultReader[0]
|
||||
readerSelection = rssReaders.firstIndex(of: reader)!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func registerAppWithBundleID(_ bundleID: String) {
|
||||
NSWorkspace.shared.setDefaultAppBundleID(forURLScheme: "feed", to: bundleID)
|
||||
NSWorkspace.shared.setDefaultAppBundleID(forURLScheme: "feeds", to: bundleID)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// MARK: - RSSReaderInfo
|
||||
|
||||
struct RSSReaderInfo {
|
||||
|
||||
var defaultRSSReaderBundleID: String? {
|
||||
NSWorkspace.shared.defaultAppBundleID(forURLScheme: RSSReaderInfo.feedURLScheme)
|
||||
}
|
||||
let rssReaders: Set<RSSReader>
|
||||
static let feedURLScheme = "feed:"
|
||||
|
||||
init() {
|
||||
self.rssReaders = RSSReaderInfo.fetchRSSReaders()
|
||||
}
|
||||
|
||||
static func fetchRSSReaders() -> Set<RSSReader> {
|
||||
let rssReaderBundleIDs = NSWorkspace.shared.bundleIDsForApps(forURLScheme: feedURLScheme)
|
||||
|
||||
var rssReaders = Set<RSSReader>()
|
||||
rssReaderBundleIDs.forEach { (bundleID) in
|
||||
if let reader = RSSReader(bundleID: bundleID) {
|
||||
rssReaders.insert(reader)
|
||||
}
|
||||
}
|
||||
return rssReaders
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - RSSReader
|
||||
|
||||
struct RSSReader: Hashable {
|
||||
|
||||
let bundleID: String
|
||||
let name: String
|
||||
var nameMinusAppSuffix: String
|
||||
let path: String
|
||||
|
||||
init?(bundleID: String) {
|
||||
guard let path = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleID) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.path = path.path
|
||||
self.bundleID = bundleID
|
||||
|
||||
let name = (self.path as NSString).lastPathComponent
|
||||
self.name = name
|
||||
if name.hasSuffix(".app") {
|
||||
self.nameMinusAppSuffix = name.stripping(suffix: ".app")
|
||||
}
|
||||
else {
|
||||
self.nameMinusAppSuffix = name
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Hashable
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(bundleID)
|
||||
}
|
||||
|
||||
// MARK: - Equatable
|
||||
|
||||
static func ==(lhs: RSSReader, rhs: RSSReader) -> Bool {
|
||||
return lhs.bundleID == rhs.bundleID
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
//
|
||||
// GeneralPreferencesView.swift
|
||||
// macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 27/6/20.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct GeneralPreferencesView: View {
|
||||
|
||||
@EnvironmentObject private var defaults: AppDefaults
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@StateObject private var preferences = GeneralPreferencesModel()
|
||||
private let colorPalettes = UserInterfaceColorPalette.allCases
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Picker("Refresh feeds",
|
||||
selection: $defaults.interval,
|
||||
content: {
|
||||
ForEach(RefreshInterval.allCases, content: { interval in
|
||||
Text(interval.description())
|
||||
.tag(interval.rawValue)
|
||||
})
|
||||
})
|
||||
|
||||
Picker("Default RSS reader", selection: $preferences.readerSelection, content: {
|
||||
ForEach(0..<preferences.rssReaders.count, content: { index in
|
||||
if index > 0 && preferences.rssReaders[index].nameMinusAppSuffix.contains("NetNewsWire") {
|
||||
Text(preferences.rssReaders[index].nameMinusAppSuffix.appending(" (old version)"))
|
||||
|
||||
} else {
|
||||
Text(preferences.rssReaders[index].nameMinusAppSuffix)
|
||||
.tag(index)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Toggle("Open webpages in background in browser", isOn: $defaults.openInBrowserInBackground)
|
||||
|
||||
Toggle("Hide Unread Count in Dock", isOn: $defaults.hideDockUnreadCount)
|
||||
|
||||
Divider()
|
||||
|
||||
Picker("Appearance", selection: $defaults.userInterfaceColorPalette, content: {
|
||||
ForEach(colorPalettes, id: \.self, content: {
|
||||
Text($0.description)
|
||||
})
|
||||
}).pickerStyle(RadioGroupPickerStyle())
|
||||
|
||||
|
||||
}
|
||||
.frame(width: 400, alignment: .center)
|
||||
.lineLimit(2)
|
||||
}
|
||||
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
//
|
||||
// AccountsPreferencesView.swift
|
||||
// macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 27/6/20.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AccountPreferencesViewModel {
|
||||
let accountTypes = ["On My Mac", "FeedBin"]
|
||||
var selectedAccount = Int?.none
|
||||
}
|
||||
|
||||
struct AccountsPreferencesView: View {
|
||||
|
||||
@State private var viewModel = AccountPreferencesViewModel()
|
||||
@State private var addAccountViewModel = AccountPreferencesViewModel()
|
||||
@State private var showAddAccountView: Bool = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack(alignment: .top, spacing: 10) {
|
||||
VStack(alignment: .leading) {
|
||||
List(selection: $viewModel.selectedAccount, content: {
|
||||
ForEach(0..<viewModel.accountTypes.count, content: { i in
|
||||
AccountDetailRow(imageName: "desktopcomputer", accountName: viewModel.accountTypes[i]).padding(.vertical, 8)
|
||||
})
|
||||
})
|
||||
HStack {
|
||||
Button("+", action: {
|
||||
showAddAccountView.toggle()
|
||||
})
|
||||
Button("-", action: {})
|
||||
.disabled(viewModel.selectedAccount == nil)
|
||||
Spacer()
|
||||
}
|
||||
}.frame(width: 225, height: 300, alignment: .leading)
|
||||
VStack(alignment: .leading) {
|
||||
viewModel.selectedAccount == nil ? Text("Select Account") : Text(viewModel.accountTypes[viewModel.selectedAccount!])
|
||||
Spacer()
|
||||
}.frame(width: 225, height: 300, alignment: .leading)
|
||||
}
|
||||
Spacer()
|
||||
}.sheet(isPresented: $showAddAccountView,
|
||||
onDismiss: { showAddAccountView.toggle() },
|
||||
content: {
|
||||
AddAccountView()
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct AccountDetailRow: View {
|
||||
|
||||
var imageName: String
|
||||
var accountName: String
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Image(systemName: imageName).font(.headline)
|
||||
Text(accountName).font(.headline)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct AddAccountView: View {
|
||||
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
let accountTypes = ["On My Mac", "FeedBin"]
|
||||
@State var selectedAccount: Int = 0
|
||||
@State private var userName: String = ""
|
||||
@State private var password: String = ""
|
||||
|
||||
var body: some View {
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text("Add an Account").font(.headline)
|
||||
Form {
|
||||
Picker("Account Type",
|
||||
selection: $selectedAccount,
|
||||
content: {
|
||||
ForEach(0..<accountTypes.count, content: {
|
||||
AccountDetailRow(imageName: "desktopcomputer", accountName: accountTypes[$0])
|
||||
})
|
||||
})
|
||||
|
||||
if selectedAccount == 1 {
|
||||
TextField("Email", text: $userName)
|
||||
SecureField("Password", text: $password)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
|
||||
Button(action: { presentationMode.wrappedValue.dismiss() }, label: {
|
||||
Text("Cancel")
|
||||
})
|
||||
|
||||
if selectedAccount == 0 {
|
||||
Button("Add", action: {})
|
||||
}
|
||||
|
||||
if selectedAccount != 0 {
|
||||
Button("Create", action: {})
|
||||
.disabled(userName.count == 0 || password.count == 0)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}.frame(width: 300, alignment: .top).padding()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class AddAccountModel: ObservableObject {
|
||||
let accountTypes = ["On My Mac", "FeedBin"]
|
||||
@Published var selectedAccount = Int?.none
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,33 +0,0 @@
|
||||
//
|
||||
// GeneralPreferencesView.swift
|
||||
// macOS
|
||||
//
|
||||
// Created by Stuart Breckenridge on 27/6/20.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct GeneralPreferencesView: View {
|
||||
|
||||
@EnvironmentObject private var defaults: AppDefaults
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Form {
|
||||
Picker("Refresh Feeds",
|
||||
selection: $defaults.interval,
|
||||
content: {
|
||||
ForEach(RefreshInterval.allCases, content: { interval in
|
||||
Text(interval.description()).tag(interval.rawValue)
|
||||
})
|
||||
}).frame(width: 300, alignment: .center)
|
||||
|
||||
Toggle("Open webpages in background in browser", isOn: $defaults.openInBrowserInBackground)
|
||||
|
||||
Toggle("Hide Unread Count in Dock", isOn: $defaults.hideDockUnreadCount)
|
||||
}
|
||||
Spacer()
|
||||
}.frame(width: 300, alignment: .center)
|
||||
}
|
||||
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1717535624BADF33004498C6 /* GeneralPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1717535524BADF33004498C6 /* GeneralPreferencesModel.swift */; };
|
||||
172199C924AB228900A31D04 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199C824AB228900A31D04 /* SettingsView.swift */; };
|
||||
172199ED24AB2E0100A31D04 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199EC24AB2E0100A31D04 /* SafariView.swift */; };
|
||||
172199F124AB716900A31D04 /* SidebarToolbarModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199F024AB716900A31D04 /* SidebarToolbarModifier.swift */; };
|
||||
@ -17,6 +18,15 @@
|
||||
1729529B24AA1FD200D65E66 /* MacSearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1729529A24AA1FD200D65E66 /* MacSearchField.swift */; };
|
||||
175942AA24AD533200585066 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; };
|
||||
175942AB24AD533200585066 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; };
|
||||
1769E32224BC5925000E1E8E /* AccountsPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32124BC5925000E1E8E /* AccountsPreferencesModel.swift */; };
|
||||
1769E32524BC5A65000E1E8E /* AddAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32424BC5A65000E1E8E /* AddAccountView.swift */; };
|
||||
1769E32724BC5B6C000E1E8E /* AddAccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32624BC5B6C000E1E8E /* AddAccountModel.swift */; };
|
||||
1769E32924BCAFC7000E1E8E /* AddAccountPickerRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32824BCAFC7000E1E8E /* AddAccountPickerRow.swift */; };
|
||||
1769E32B24BCB030000E1E8E /* ConfiguredAccountRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32A24BCB030000E1E8E /* ConfiguredAccountRow.swift */; };
|
||||
1769E32D24BD20A0000E1E8E /* AccountDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32C24BD20A0000E1E8E /* AccountDetailView.swift */; };
|
||||
1769E33024BD6271000E1E8E /* EditAccountCredentialsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E32F24BD6271000E1E8E /* EditAccountCredentialsView.swift */; };
|
||||
1769E33624BD9621000E1E8E /* EditAccountCredentialsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E33524BD9621000E1E8E /* EditAccountCredentialsModel.swift */; };
|
||||
1769E33824BD97CB000E1E8E /* AccountUpdateErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1769E33724BD97CB000E1E8E /* AccountUpdateErrors.swift */; };
|
||||
1776E88E24AC5F8A00E78166 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */; };
|
||||
1776E88F24AC5F8A00E78166 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1776E88D24AC5F8A00E78166 /* AppDefaults.swift */; };
|
||||
17930ED424AF10EE00A9BA52 /* AddWebFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */; };
|
||||
@ -28,6 +38,7 @@
|
||||
17D5F17124B0BC6700375168 /* SidebarToolbarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D5F17024B0BC6700375168 /* SidebarToolbarModel.swift */; };
|
||||
17D5F17224B0BC6700375168 /* SidebarToolbarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D5F17024B0BC6700375168 /* SidebarToolbarModel.swift */; };
|
||||
17D5F19524B0C1DD00375168 /* SidebarToolbarModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172199F024AB716900A31D04 /* SidebarToolbarModifier.swift */; };
|
||||
17E4DBD624BFC53E00FE462A /* AdvancedPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E4DBD524BFC53E00FE462A /* AdvancedPreferencesModel.swift */; };
|
||||
3B3A32A5238B820900314204 /* FeedWranglerAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B3A328B238B820900314204 /* FeedWranglerAccountViewController.swift */; };
|
||||
3B826DCB2385C84800FC1ADB /* AccountsFeedWrangler.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */; };
|
||||
3B826DCC2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */; };
|
||||
@ -1787,6 +1798,7 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1717535524BADF33004498C6 /* GeneralPreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPreferencesModel.swift; sourceTree = "<group>"; };
|
||||
172199C824AB228900A31D04 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||
172199EC24AB2E0100A31D04 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
|
||||
172199F024AB716900A31D04 /* SidebarToolbarModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarToolbarModifier.swift; sourceTree = "<group>"; };
|
||||
@ -1795,12 +1807,22 @@
|
||||
1729529224AA1CAA00D65E66 /* GeneralPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralPreferencesView.swift; sourceTree = "<group>"; };
|
||||
1729529624AA1CD000D65E66 /* MacPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacPreferencesView.swift; sourceTree = "<group>"; };
|
||||
1729529A24AA1FD200D65E66 /* MacSearchField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacSearchField.swift; sourceTree = "<group>"; };
|
||||
1769E32124BC5925000E1E8E /* AccountsPreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsPreferencesModel.swift; sourceTree = "<group>"; };
|
||||
1769E32424BC5A65000E1E8E /* AddAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountView.swift; sourceTree = "<group>"; };
|
||||
1769E32624BC5B6C000E1E8E /* AddAccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountModel.swift; sourceTree = "<group>"; };
|
||||
1769E32824BCAFC7000E1E8E /* AddAccountPickerRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccountPickerRow.swift; sourceTree = "<group>"; };
|
||||
1769E32A24BCB030000E1E8E /* ConfiguredAccountRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfiguredAccountRow.swift; sourceTree = "<group>"; };
|
||||
1769E32C24BD20A0000E1E8E /* AccountDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDetailView.swift; sourceTree = "<group>"; };
|
||||
1769E32F24BD6271000E1E8E /* EditAccountCredentialsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccountCredentialsView.swift; sourceTree = "<group>"; };
|
||||
1769E33524BD9621000E1E8E /* EditAccountCredentialsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditAccountCredentialsModel.swift; sourceTree = "<group>"; };
|
||||
1769E33724BD97CB000E1E8E /* AccountUpdateErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountUpdateErrors.swift; sourceTree = "<group>"; };
|
||||
1776E88D24AC5F8A00E78166 /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDefaults.swift; sourceTree = "<group>"; };
|
||||
17930ED324AF10EE00A9BA52 /* AddWebFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedView.swift; sourceTree = "<group>"; };
|
||||
179DBBA2B22A659F81EED6F9 /* AccountsNewsBlurWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsNewsBlurWindowController.swift; sourceTree = "<group>"; };
|
||||
17B223DB24AC24D2001E4592 /* TimelineLayoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineLayoutView.swift; sourceTree = "<group>"; };
|
||||
17D232A724AFF10A0005F075 /* AddWebFeedModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedModel.swift; sourceTree = "<group>"; };
|
||||
17D5F17024B0BC6700375168 /* SidebarToolbarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarToolbarModel.swift; sourceTree = "<group>"; };
|
||||
17E4DBD524BFC53E00FE462A /* AdvancedPreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedPreferencesModel.swift; sourceTree = "<group>"; };
|
||||
3B3A328B238B820900314204 /* FeedWranglerAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedWranglerAccountViewController.swift; sourceTree = "<group>"; };
|
||||
3B826DB02385C84800FC1ADB /* AccountsFeedWrangler.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsFeedWrangler.xib; sourceTree = "<group>"; };
|
||||
3B826DCA2385C84800FC1ADB /* AccountsFeedWranglerWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsFeedWranglerWindowController.swift; sourceTree = "<group>"; };
|
||||
@ -2523,19 +2545,78 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1729529624AA1CD000D65E66 /* MacPreferencesView.swift */,
|
||||
1729529924AA1CE100D65E66 /* View */,
|
||||
1729529924AA1CE100D65E66 /* Preference Panes */,
|
||||
);
|
||||
path = Preferences;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1729529924AA1CE100D65E66 /* View */ = {
|
||||
1729529924AA1CE100D65E66 /* Preference Panes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1729529024AA1CAA00D65E66 /* AccountsPreferencesView.swift */,
|
||||
1729529124AA1CAA00D65E66 /* AdvancedPreferencesView.swift */,
|
||||
1769E2FD24BC589E000E1E8E /* General */,
|
||||
1769E31F24BC58A4000E1E8E /* Accounts */,
|
||||
1769E32024BC58AD000E1E8E /* Advanced */,
|
||||
);
|
||||
path = "Preference Panes";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1769E2FD24BC589E000E1E8E /* General */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1717535524BADF33004498C6 /* GeneralPreferencesModel.swift */,
|
||||
1729529224AA1CAA00D65E66 /* GeneralPreferencesView.swift */,
|
||||
);
|
||||
path = View;
|
||||
path = General;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1769E31F24BC58A4000E1E8E /* Accounts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1769E33724BD97CB000E1E8E /* AccountUpdateErrors.swift */,
|
||||
1769E33924BD97E5000E1E8E /* Account Preferences */,
|
||||
);
|
||||
path = Accounts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1769E32024BC58AD000E1E8E /* Advanced */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
17E4DBD524BFC53E00FE462A /* AdvancedPreferencesModel.swift */,
|
||||
1729529124AA1CAA00D65E66 /* AdvancedPreferencesView.swift */,
|
||||
);
|
||||
path = Advanced;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1769E32324BC5A50000E1E8E /* Add Account */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1769E32624BC5B6C000E1E8E /* AddAccountModel.swift */,
|
||||
1769E32424BC5A65000E1E8E /* AddAccountView.swift */,
|
||||
1769E32824BCAFC7000E1E8E /* AddAccountPickerRow.swift */,
|
||||
);
|
||||
path = "Add Account";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1769E32E24BD5F22000E1E8E /* Edit Account */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1769E32C24BD20A0000E1E8E /* AccountDetailView.swift */,
|
||||
1769E32F24BD6271000E1E8E /* EditAccountCredentialsView.swift */,
|
||||
1769E33524BD9621000E1E8E /* EditAccountCredentialsModel.swift */,
|
||||
);
|
||||
path = "Edit Account";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1769E33924BD97E5000E1E8E /* Account Preferences */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1769E32124BC5925000E1E8E /* AccountsPreferencesModel.swift */,
|
||||
1729529024AA1CAA00D65E66 /* AccountsPreferencesView.swift */,
|
||||
1769E32A24BCB030000E1E8E /* ConfiguredAccountRow.swift */,
|
||||
1769E32324BC5A50000E1E8E /* Add Account */,
|
||||
1769E32E24BD5F22000E1E8E /* Edit Account */,
|
||||
);
|
||||
path = "Account Preferences";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
17930ED224AF10CD00A9BA52 /* Add */ = {
|
||||
@ -4174,46 +4255,46 @@
|
||||
TargetAttributes = {
|
||||
51314636235A7BBE00387FDC = {
|
||||
CreatedOnToolsVersion = 11.2;
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
DevelopmentTeam = FQLBNX3GP7;
|
||||
LastSwiftMigration = 1120;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
513C5CE5232571C2003D4054 = {
|
||||
CreatedOnToolsVersion = 11.0;
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
DevelopmentTeam = FQLBNX3GP7;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
518B2ED12351B3DD00400001 = {
|
||||
CreatedOnToolsVersion = 11.2;
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
DevelopmentTeam = FQLBNX3GP7;
|
||||
ProvisioningStyle = Automatic;
|
||||
TestTargetID = 840D617B2029031C009BC708;
|
||||
};
|
||||
51C0513C24A77DF800194D5E = {
|
||||
CreatedOnToolsVersion = 12.0;
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
DevelopmentTeam = FQLBNX3GP7;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
51C0514324A77DF800194D5E = {
|
||||
CreatedOnToolsVersion = 12.0;
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
DevelopmentTeam = FQLBNX3GP7;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
6581C73220CED60000F4AD34 = {
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
DevelopmentTeam = FQLBNX3GP7;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
65ED3FA2235DEF6C0081F399 = {
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
DevelopmentTeam = FQLBNX3GP7;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
65ED4090235DEF770081F399 = {
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
DevelopmentTeam = FQLBNX3GP7;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
840D617B2029031C009BC708 = {
|
||||
CreatedOnToolsVersion = 9.3;
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
DevelopmentTeam = FQLBNX3GP7;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.BackgroundModes = {
|
||||
@ -4223,7 +4304,7 @@
|
||||
};
|
||||
849C645F1ED37A5D003D8FC0 = {
|
||||
CreatedOnToolsVersion = 8.2.1;
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
DevelopmentTeam = FQLBNX3GP7;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.HardenedRuntime = {
|
||||
@ -4233,7 +4314,7 @@
|
||||
};
|
||||
849C64701ED37A5D003D8FC0 = {
|
||||
CreatedOnToolsVersion = 8.2.1;
|
||||
DevelopmentTeam = SHJK2V3AJG;
|
||||
DevelopmentTeam = FQLBNX3GP7;
|
||||
ProvisioningStyle = Automatic;
|
||||
TestTargetID = 849C645F1ED37A5D003D8FC0;
|
||||
};
|
||||
@ -5165,14 +5246,17 @@
|
||||
51E4996F24A8764C00B667CB /* ActivityType.swift in Sources */,
|
||||
51E4994E24A8734C00B667CB /* SendToMarsEditCommand.swift in Sources */,
|
||||
51919FB024AA8EFA00541E64 /* SidebarItemView.swift in Sources */,
|
||||
1769E33624BD9621000E1E8E /* EditAccountCredentialsModel.swift in Sources */,
|
||||
51919FEF24AB85E400541E64 /* TimelineContainerView.swift in Sources */,
|
||||
51E4996624A8760B00B667CB /* ArticleStyle.swift in Sources */,
|
||||
5171B4D424B7BABA00FB8D3B /* MarkStatusCommand.swift in Sources */,
|
||||
17E4DBD624BFC53E00FE462A /* AdvancedPreferencesModel.swift in Sources */,
|
||||
5177470724B2910300EB0F74 /* ArticleToolbarModifier.swift in Sources */,
|
||||
FA80C11824B0728000974098 /* AddFolderView.swift in Sources */,
|
||||
51E4996C24A8762D00B667CB /* ExtractedArticle.swift in Sources */,
|
||||
51E4990824A808C300B667CB /* RSHTMLMetadata+Extension.swift in Sources */,
|
||||
51919FF824AB8B7700541E64 /* TimelineView.swift in Sources */,
|
||||
1717535624BADF33004498C6 /* GeneralPreferencesModel.swift in Sources */,
|
||||
51E4992B24A8676300B667CB /* ArticleArray.swift in Sources */,
|
||||
51B54AB324B5AC830014348B /* ArticleExtractorButtonState.swift in Sources */,
|
||||
17D5F17224B0BC6700375168 /* SidebarToolbarModel.swift in Sources */,
|
||||
@ -5190,8 +5274,11 @@
|
||||
51E4994F24A8734C00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */,
|
||||
51E498CA24A8085D00B667CB /* SmartFeedDelegate.swift in Sources */,
|
||||
5177470A24B2F87600EB0F74 /* SidebarListStyleModifier.swift in Sources */,
|
||||
1769E33824BD97CB000E1E8E /* AccountUpdateErrors.swift in Sources */,
|
||||
51E4990524A808C300B667CB /* FeaturedImageDownloader.swift in Sources */,
|
||||
5181C5AE24AF89B1002E0F70 /* PreferredColorSchemeModifier.swift in Sources */,
|
||||
1769E32924BCAFC7000E1E8E /* AddAccountPickerRow.swift in Sources */,
|
||||
1769E32224BC5925000E1E8E /* AccountsPreferencesModel.swift in Sources */,
|
||||
51E4991624A8090300B667CB /* ArticleUtilities.swift in Sources */,
|
||||
51919FF224AB864A00541E64 /* TimelineModel.swift in Sources */,
|
||||
51E4991A24A8090F00B667CB /* IconImage.swift in Sources */,
|
||||
@ -5203,13 +5290,16 @@
|
||||
1729529724AA1CD000D65E66 /* MacPreferencesView.swift in Sources */,
|
||||
51E4994C24A8734C00B667CB /* RedditFeedProvider-Extensions.swift in Sources */,
|
||||
1729529324AA1CAA00D65E66 /* AccountsPreferencesView.swift in Sources */,
|
||||
1769E32D24BD20A0000E1E8E /* AccountDetailView.swift in Sources */,
|
||||
51919FAD24AA8CCA00541E64 /* UnreadCountView.swift in Sources */,
|
||||
51E498C924A8085D00B667CB /* PseudoFeed.swift in Sources */,
|
||||
51E498FC24A808BA00B667CB /* FaviconURLFinder.swift in Sources */,
|
||||
51E4991C24A8092000B667CB /* NSAttributedString+NetNewsWire.swift in Sources */,
|
||||
1769E32B24BCB030000E1E8E /* ConfiguredAccountRow.swift in Sources */,
|
||||
FF64D0E824AF53EE0084080A /* RefreshProgressModel.swift in Sources */,
|
||||
51E499D924A912C200B667CB /* SceneModel.swift in Sources */,
|
||||
51919FB424AAB97900541E64 /* FeedIconImageLoader.swift in Sources */,
|
||||
1769E32524BC5A65000E1E8E /* AddAccountView.swift in Sources */,
|
||||
51E4994A24A8734C00B667CB /* ExtensionPointManager.swift in Sources */,
|
||||
514E6C0324AD29A300AC6F6E /* TimelineItemStatusView.swift in Sources */,
|
||||
51B54A6524B549B20014348B /* WrapperScriptMessageHandler.swift in Sources */,
|
||||
@ -5231,6 +5321,7 @@
|
||||
51408B7F24A9EC6F0073CF4E /* SidebarItem.swift in Sources */,
|
||||
514E6BDB24ACEA0400AC6F6E /* TimelineItemView.swift in Sources */,
|
||||
51E4996E24A8764C00B667CB /* ActivityManager.swift in Sources */,
|
||||
1769E33024BD6271000E1E8E /* EditAccountCredentialsView.swift in Sources */,
|
||||
51E4995A24A873F900B667CB /* ErrorHandler.swift in Sources */,
|
||||
5194737124BBCAF4001A2939 /* TimelineSortOrderView.swift in Sources */,
|
||||
51E4991F24A8094300B667CB /* RSImage-AppIcons.swift in Sources */,
|
||||
@ -5258,6 +5349,7 @@
|
||||
51B54A4324B5499B0014348B /* WebViewProvider.swift in Sources */,
|
||||
514E6C0024AD255D00AC6F6E /* PreviewArticles.swift in Sources */,
|
||||
1729529524AA1CAA00D65E66 /* GeneralPreferencesView.swift in Sources */,
|
||||
1769E32724BC5B6C000E1E8E /* AddAccountModel.swift in Sources */,
|
||||
1729529424AA1CAA00D65E66 /* AdvancedPreferencesView.swift in Sources */,
|
||||
5177470424B2657F00EB0F74 /* TimelineToolbarModifier.swift in Sources */,
|
||||
51E4992D24A8676300B667CB /* FetchRequestOperation.swift in Sources */,
|
||||
|
Loading…
x
Reference in New Issue
Block a user