mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
Merge branch 'updates' of https://github.com/exelban/stats into dev
This commit is contained in:
@@ -81,3 +81,65 @@ class AboutVC: NSViewController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UpdatesVC: NSViewController {
|
||||
@IBOutlet weak var mainView: NSStackView!
|
||||
@IBOutlet weak var spinnerView: NSView!
|
||||
@IBOutlet weak var noInternetView: NSView!
|
||||
@IBOutlet weak var mainTextLabel: NSTextFieldCell!
|
||||
@IBOutlet weak var currentVersionLabel: NSTextField!
|
||||
@IBOutlet weak var latestVersionLabel: NSTextField!
|
||||
@IBOutlet weak var downloadButton: NSButton!
|
||||
@IBOutlet weak var spinner: NSProgressIndicator!
|
||||
|
||||
let updater = macAppUpdater(user: "exelban", repo: "stats")
|
||||
var url: String?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
self.view.wantsLayer = true
|
||||
|
||||
self.spinner.startAnimation(self)
|
||||
|
||||
updater.check() { result, error in
|
||||
if error != nil && error as! String == "No internet connection" {
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.spinnerView.isHidden = true
|
||||
self.noInternetView.isHidden = false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
guard error == nil, let version: version = result else {
|
||||
print("Error: \(error ?? "check error")")
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.spinner.stopAnimation(self)
|
||||
self.spinnerView.isHidden = true
|
||||
self.mainView.isHidden = false
|
||||
self.currentVersionLabel.stringValue = version.current
|
||||
self.latestVersionLabel.stringValue = version.latest
|
||||
self.url = version.url
|
||||
|
||||
if !version.newest {
|
||||
self.mainTextLabel.stringValue = "No new version available"
|
||||
self.downloadButton.isEnabled = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func download(_ sender: Any) {
|
||||
guard let urlString = self.url, let url = URL(string: urlString) else {
|
||||
return
|
||||
}
|
||||
NSWorkspace.shared.open(url)
|
||||
self.view.window?.close()
|
||||
}
|
||||
|
||||
@IBAction func exit(_ sender: Any) {
|
||||
self.view.window?.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,14 +74,27 @@ class MenuBar {
|
||||
menu.addItem(preferences)
|
||||
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
let updateMenu = NSMenuItem(title: "Check for updates", action: #selector(checkUpdate), keyEquivalent: "")
|
||||
updateMenu.target = self
|
||||
|
||||
let aboutMenu = NSMenuItem(title: "About Stats", action: #selector(openAbout), keyEquivalent: "")
|
||||
aboutMenu.target = self
|
||||
|
||||
menu.addItem(updateMenu)
|
||||
menu.addItem(aboutMenu)
|
||||
menu.addItem(NSMenuItem(title: "Quit Stats", action: #selector(NSApplication.terminate(_:)), keyEquivalent: ""))
|
||||
|
||||
return menu
|
||||
}
|
||||
|
||||
@objc func checkUpdate(_ sender : NSMenuItem) {
|
||||
let updatesVC: NSWindowController? = NSStoryboard(name: "Updates", bundle: nil).instantiateController(withIdentifier: "UpdatesVC") as? NSWindowController
|
||||
updatesVC?.window?.center()
|
||||
updatesVC?.window?.level = .floating
|
||||
updatesVC!.showWindow(self)
|
||||
}
|
||||
|
||||
@objc func openAbout(_ sender : NSMenuItem) {
|
||||
let aboutVC: NSWindowController? = NSStoryboard(name: "About", bundle: nil).instantiateController(withIdentifier: "AboutVC") as? NSWindowController
|
||||
aboutVC?.window?.center()
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
287
Stats/Supporting Files/Updates.storyboard
Normal file
287
Stats/Supporting Files/Updates.storyboard
Normal file
@@ -0,0 +1,287 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Window Controller-->
|
||||
<scene sceneID="Qxa-eF-fGn">
|
||||
<objects>
|
||||
<windowController storyboardIdentifier="UpdatesVC" id="eTp-5e-KuD" sceneMemberID="viewController">
|
||||
<window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" titleVisibility="hidden" id="MdT-C2-Xn6">
|
||||
<windowStyleMask key="styleMask" titled="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="425" y="313" width="440" height="140"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1057"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="eTp-5e-KuD" id="Kaj-pD-86v"/>
|
||||
</connections>
|
||||
</window>
|
||||
<connections>
|
||||
<segue destination="Jvz-IB-V0r" kind="relationship" relationship="window.shadowedContentViewController" id="xGi-4Z-y1y"/>
|
||||
</connections>
|
||||
</windowController>
|
||||
<customObject id="cb1-RP-9mi" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="4" y="48"/>
|
||||
</scene>
|
||||
<!--UpdatesVC-->
|
||||
<scene sceneID="dHW-OY-NO5">
|
||||
<objects>
|
||||
<viewController id="Jvz-IB-V0r" customClass="UpdatesVC" customModule="Stats" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" id="c5r-hq-Fd6">
|
||||
<rect key="frame" x="0.0" y="0.0" width="440" height="140"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<stackView hidden="YES" distribution="fill" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="gbc-x4-ULo">
|
||||
<rect key="frame" x="20" y="20" width="400" height="100"/>
|
||||
<subviews>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="lil-l7-SfL">
|
||||
<rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
|
||||
<subviews>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="dW0-Eg-dcV">
|
||||
<rect key="frame" x="5" y="20" width="80" height="80"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="80" id="M09-H6-mdV"/>
|
||||
<constraint firstAttribute="width" constant="80" id="OXL-r3-XPM"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="AppIcon" id="qay-dJ-vRD"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="100" id="426-Mn-c7c"/>
|
||||
<constraint firstAttribute="width" constant="100" id="J6X-3U-Tq1"/>
|
||||
<constraint firstItem="dW0-Eg-dcV" firstAttribute="top" secondItem="lil-l7-SfL" secondAttribute="top" id="faU-OY-TeM"/>
|
||||
<constraint firstItem="dW0-Eg-dcV" firstAttribute="leading" secondItem="lil-l7-SfL" secondAttribute="leading" constant="5" id="hQN-bI-dc4"/>
|
||||
</constraints>
|
||||
</customView>
|
||||
<stackView distribution="fill" orientation="vertical" alignment="trailing" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="a2t-Ca-JuB">
|
||||
<rect key="frame" x="108" y="0.0" width="292" height="100"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ouZ-jq-tRu">
|
||||
<rect key="frame" x="-2" y="82" width="296" height="18"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="New Version Available" id="ifE-Um-dTa">
|
||||
<font key="font" metaFont="systemBold" size="14"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<stackView distribution="fill" orientation="vertical" alignment="leading" spacing="4" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ePi-7H-tuZ">
|
||||
<rect key="frame" x="0.0" y="36" width="292" height="38"/>
|
||||
<subviews>
|
||||
<stackView distribution="fillEqually" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ekt-ux-mcR">
|
||||
<rect key="frame" x="0.0" y="21" width="208" height="17"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fOg-zl-1M0">
|
||||
<rect key="frame" x="-2" y="0.0" width="104" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="100" id="xw9-2q-BDa"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Latest version:" id="ETQ-A6-cne">
|
||||
<font key="font" metaFont="systemLight" size="13"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="zPp-kk-s1Y">
|
||||
<rect key="frame" x="106" y="0.0" width="104" height="17"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="0.0.0" id="beD-Uf-T9l">
|
||||
<font key="font" metaFont="systemMedium" size="13"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<visibilityPriorities>
|
||||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
</visibilityPriorities>
|
||||
<customSpacing>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
</customSpacing>
|
||||
</stackView>
|
||||
<stackView distribution="fillEqually" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wYM-Rg-iB8">
|
||||
<rect key="frame" x="0.0" y="0.0" width="208" height="17"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gxk-NW-8MC">
|
||||
<rect key="frame" x="-2" y="0.0" width="104" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="100" id="3hv-Qv-72l"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Current version:" id="pkM-KV-LXp">
|
||||
<font key="font" metaFont="systemLight" size="13"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gob-ke-eHN">
|
||||
<rect key="frame" x="106" y="0.0" width="104" height="17"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="0.0.0" id="w43-pL-4IU">
|
||||
<font key="font" metaFont="systemMedium" size="13"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<visibilityPriorities>
|
||||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
</visibilityPriorities>
|
||||
<customSpacing>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
</customSpacing>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="38" id="hTG-ET-Q9C"/>
|
||||
</constraints>
|
||||
<visibilityPriorities>
|
||||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
</visibilityPriorities>
|
||||
<customSpacing>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
</customSpacing>
|
||||
</stackView>
|
||||
<stackView distribution="fill" orientation="horizontal" alignment="bottom" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="sXv-6U-z9c">
|
||||
<rect key="frame" x="132" y="0.0" width="160" height="28"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="4Ft-uA-xyo">
|
||||
<rect key="frame" x="-6" y="-7" width="101" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Download" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="lYz-7d-p0I">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="download:" target="Jvz-IB-V0r" id="CaY-3w-sQl"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="9JO-Ob-lhu">
|
||||
<rect key="frame" x="91" y="-7" width="75" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Close" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="kc0-rN-ti2">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="exit:" target="Jvz-IB-V0r" id="8ae-Af-YpV"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<visibilityPriorities>
|
||||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
</visibilityPriorities>
|
||||
<customSpacing>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
</customSpacing>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="sXv-6U-z9c" secondAttribute="bottom" id="0wF-S8-fdh"/>
|
||||
<constraint firstItem="ouZ-jq-tRu" firstAttribute="leading" secondItem="a2t-Ca-JuB" secondAttribute="leading" id="EHv-7M-gMc"/>
|
||||
<constraint firstItem="ePi-7H-tuZ" firstAttribute="leading" secondItem="a2t-Ca-JuB" secondAttribute="leading" id="OwE-N0-YYv"/>
|
||||
<constraint firstAttribute="trailing" secondItem="ouZ-jq-tRu" secondAttribute="trailing" id="rVg-xV-PZ3"/>
|
||||
<constraint firstAttribute="trailing" secondItem="ePi-7H-tuZ" secondAttribute="trailing" id="sxv-7o-0SH"/>
|
||||
</constraints>
|
||||
<visibilityPriorities>
|
||||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
</visibilityPriorities>
|
||||
<customSpacing>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
</customSpacing>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="400" id="Acb-hj-G5c"/>
|
||||
<constraint firstAttribute="bottom" secondItem="a2t-Ca-JuB" secondAttribute="bottom" id="Ari-ig-V9M"/>
|
||||
<constraint firstAttribute="trailing" secondItem="a2t-Ca-JuB" secondAttribute="trailing" id="M7p-NR-yqx"/>
|
||||
<constraint firstItem="a2t-Ca-JuB" firstAttribute="top" secondItem="gbc-x4-ULo" secondAttribute="top" id="dyF-7z-8sN"/>
|
||||
<constraint firstAttribute="height" constant="100" id="f6V-S5-nxa"/>
|
||||
<constraint firstItem="a2t-Ca-JuB" firstAttribute="leading" secondItem="lil-l7-SfL" secondAttribute="trailing" constant="8" id="xeZ-6k-w1Q"/>
|
||||
</constraints>
|
||||
<visibilityPriorities>
|
||||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
</visibilityPriorities>
|
||||
<customSpacing>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
</customSpacing>
|
||||
</stackView>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="TaT-Bx-z9f">
|
||||
<rect key="frame" x="0.0" y="0.0" width="440" height="140"/>
|
||||
<subviews>
|
||||
<progressIndicator wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" bezeled="NO" indeterminate="YES" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="4lV-OW-XAy">
|
||||
<rect key="frame" x="204" y="54" width="32" height="32"/>
|
||||
</progressIndicator>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="4lV-OW-XAy" firstAttribute="centerX" secondItem="TaT-Bx-z9f" secondAttribute="centerX" id="RSU-P4-ZMI"/>
|
||||
<constraint firstItem="4lV-OW-XAy" firstAttribute="centerY" secondItem="TaT-Bx-z9f" secondAttribute="centerY" id="yJ2-V9-z1C"/>
|
||||
</constraints>
|
||||
</customView>
|
||||
<customView hidden="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VuR-f2-eNB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="440" height="140"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jbE-84-Rxv">
|
||||
<rect key="frame" x="120" y="61" width="200" height="17"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Internet connection not available" id="zbB-5V-Hny">
|
||||
<font key="font" metaFont="systemLight" size="13"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="jbE-84-Rxv" firstAttribute="centerX" secondItem="VuR-f2-eNB" secondAttribute="centerX" id="8Qe-nE-B8p"/>
|
||||
<constraint firstItem="jbE-84-Rxv" firstAttribute="centerY" secondItem="VuR-f2-eNB" secondAttribute="centerY" id="QGV-Gz-g7v"/>
|
||||
</constraints>
|
||||
</customView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="VuR-f2-eNB" firstAttribute="leading" secondItem="c5r-hq-Fd6" secondAttribute="leading" id="84G-yg-hMa"/>
|
||||
<constraint firstItem="gbc-x4-ULo" firstAttribute="centerX" secondItem="c5r-hq-Fd6" secondAttribute="centerX" id="Dpi-rK-AzM"/>
|
||||
<constraint firstItem="TaT-Bx-z9f" firstAttribute="leading" secondItem="c5r-hq-Fd6" secondAttribute="leading" id="E51-Oc-2V4"/>
|
||||
<constraint firstAttribute="bottom" secondItem="VuR-f2-eNB" secondAttribute="bottom" id="F2o-tO-1Cw"/>
|
||||
<constraint firstAttribute="trailing" secondItem="VuR-f2-eNB" secondAttribute="trailing" id="IpB-2O-18u"/>
|
||||
<constraint firstAttribute="bottom" secondItem="gbc-x4-ULo" secondAttribute="bottom" constant="20" id="LMa-In-o74"/>
|
||||
<constraint firstItem="gbc-x4-ULo" firstAttribute="top" secondItem="c5r-hq-Fd6" secondAttribute="top" constant="20" id="LRc-d3-GD7"/>
|
||||
<constraint firstItem="TaT-Bx-z9f" firstAttribute="top" secondItem="c5r-hq-Fd6" secondAttribute="top" id="R4W-Se-evk"/>
|
||||
<constraint firstAttribute="trailing" secondItem="TaT-Bx-z9f" secondAttribute="trailing" id="WwE-PV-CRA"/>
|
||||
<constraint firstItem="gbc-x4-ULo" firstAttribute="leading" secondItem="c5r-hq-Fd6" secondAttribute="leading" constant="20" id="dFH-7N-tFv"/>
|
||||
<constraint firstItem="VuR-f2-eNB" firstAttribute="top" secondItem="c5r-hq-Fd6" secondAttribute="top" id="dMy-6f-EzA"/>
|
||||
<constraint firstAttribute="bottom" secondItem="TaT-Bx-z9f" secondAttribute="bottom" id="eSI-Yp-nsb"/>
|
||||
<constraint firstAttribute="trailing" secondItem="gbc-x4-ULo" secondAttribute="trailing" constant="20" id="fVu-MD-LPi"/>
|
||||
<constraint firstItem="gbc-x4-ULo" firstAttribute="centerY" secondItem="c5r-hq-Fd6" secondAttribute="centerY" id="m0O-DZ-N1Z"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="currentVersionLabel" destination="gob-ke-eHN" id="CEW-EL-VoZ"/>
|
||||
<outlet property="downloadButton" destination="4Ft-uA-xyo" id="gIo-OP-jHo"/>
|
||||
<outlet property="latestVersionLabel" destination="zPp-kk-s1Y" id="Rw8-5R-dgM"/>
|
||||
<outlet property="mainTextLabel" destination="ifE-Um-dTa" id="srK-S1-Icw"/>
|
||||
<outlet property="mainView" destination="gbc-x4-ULo" id="8kc-ng-29G"/>
|
||||
<outlet property="noInternetView" destination="VuR-f2-eNB" id="LrM-sz-2Ej"/>
|
||||
<outlet property="spinner" destination="4lV-OW-XAy" id="a0Q-hW-TxG"/>
|
||||
<outlet property="spinnerView" destination="TaT-Bx-z9f" id="bsd-xe-n0O"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<customObject id="bP2-Gx-7ZJ" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="4" y="386"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="AppIcon" width="128" height="128"/>
|
||||
</resources>
|
||||
</document>
|
||||
136
Stats/libs/macAppUpdater.swift
Normal file
136
Stats/libs/macAppUpdater.swift
Normal file
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// macAppUpdater.swift
|
||||
// Stats
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 25.06.2019.
|
||||
// Copyright © 2019 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SystemConfiguration
|
||||
|
||||
extension String: Error {}
|
||||
|
||||
struct version {
|
||||
let current: String
|
||||
let latest: String
|
||||
let newest: Bool
|
||||
let url: String
|
||||
}
|
||||
|
||||
public class macAppUpdater {
|
||||
let user: String
|
||||
let repo: String
|
||||
|
||||
let appName: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as! String
|
||||
let currentVersion: String = "v\(Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String)"
|
||||
|
||||
var url: String {
|
||||
return "https://api.github.com/repos/\(user)/\(repo)/releases/latest"
|
||||
}
|
||||
|
||||
init(user: String, repo: String) {
|
||||
self.user = user
|
||||
self.repo = repo
|
||||
}
|
||||
|
||||
func fetchLastVersion(completionHandler: @escaping (_ result: [String]?, _ error: Error?) -> Void) {
|
||||
let task = URLSession.shared.dataTask(with: URL(string: self.url)!) { data, response, error in
|
||||
guard let data = data, error == nil else { return }
|
||||
|
||||
do {
|
||||
let jsonResponse = try JSONSerialization.jsonObject(with: data, options: [])
|
||||
guard let jsonArray = jsonResponse as? [String: Any] else {
|
||||
completionHandler(nil, "parse json")
|
||||
return
|
||||
}
|
||||
let lastVersion = jsonArray["tag_name"] as? String
|
||||
|
||||
guard let assets = jsonArray["assets"] as? [[String: Any]] else {
|
||||
completionHandler(nil, "parse assets")
|
||||
return
|
||||
}
|
||||
if let asset = assets.first(where: {$0["name"] as! String == "\(self.appName).dmg"}) {
|
||||
let downloadURL = asset["browser_download_url"] as? String
|
||||
completionHandler([lastVersion!, downloadURL!], nil)
|
||||
}
|
||||
} catch let parsingError {
|
||||
completionHandler(nil, parsingError)
|
||||
}
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
|
||||
func checkIfNewer(current: String, latest: String) -> Bool {
|
||||
guard let currentNumber: Int64 = Int64(current.replacingOccurrences(of: "[v.]", with: "", options: [.regularExpression])) else {
|
||||
print("Error: wrong version tag \(current)")
|
||||
return false
|
||||
}
|
||||
guard let latestNumber: Int64 = Int64(latest.replacingOccurrences(of: "[v.]", with: "", options: [.regularExpression])) else {
|
||||
print("Error: wrong version tag \(latest)")
|
||||
return false
|
||||
}
|
||||
return latestNumber>currentNumber
|
||||
}
|
||||
|
||||
func check(completionHandler: @escaping (_ result: version?, _ error: Error?) -> Void) {
|
||||
if !Reachability.isConnectedToNetwork() {
|
||||
completionHandler(nil, "No internet connection")
|
||||
return
|
||||
}
|
||||
|
||||
fetchLastVersion() { result, error in
|
||||
guard error == nil else {
|
||||
completionHandler(nil, error)
|
||||
return
|
||||
}
|
||||
|
||||
guard let results = result, results.count > 1 else {
|
||||
completionHandler(nil, "wrong results")
|
||||
return
|
||||
}
|
||||
|
||||
let downloadURL: String = result![1]
|
||||
let lastVersion: String = result![0]
|
||||
let newVersion: Bool = self.checkIfNewer(current: self.currentVersion, latest: lastVersion)
|
||||
|
||||
completionHandler(version(current: self.currentVersion, latest: lastVersion, newest: newVersion, url: downloadURL), nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// https://stackoverflow.com/questions/30743408/check-for-internet-connection-with-swift
|
||||
public class Reachability {
|
||||
class func isConnectedToNetwork() -> Bool {
|
||||
var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
|
||||
zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
|
||||
zeroAddress.sin_family = sa_family_t(AF_INET)
|
||||
|
||||
let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {
|
||||
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in
|
||||
SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
|
||||
}
|
||||
}
|
||||
|
||||
var flags: SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0)
|
||||
if SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) == false {
|
||||
return false
|
||||
}
|
||||
|
||||
/* Only Working for WIFI
|
||||
let isReachable = flags == .reachable
|
||||
let needsConnection = flags == .connectionRequired
|
||||
|
||||
return isReachable && !needsConnection
|
||||
*/
|
||||
|
||||
// Working for Cellular and WIFI
|
||||
let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
|
||||
let needsConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
|
||||
let ret = (isReachable && !needsConnection)
|
||||
|
||||
return ret
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user