diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..56d0804f --- /dev/null +++ b/Makefile @@ -0,0 +1,87 @@ +APP = Stats +BUNDLE_ID = eu.exelban.Stats + +ITC_USERNAME = $(AC_USERNAME) +ITC_PASSWORD = @keychain:AC_PASSWORD +ITC_PROVIDER = $(AC_PROVIDER) + +RequestUUID = e6c7b954-d9fa-4c74-8927-ba2172c9526e + +BUILD_PATH = $(PWD)/build +ARCHIVE_PATH = $(BUILD_PATH)/$(APP).xcarchive +APP_PATH = "$(BUILD_PATH)/$(APP).app" +ZIP_PATH = "$(BUILD_PATH)/$(APP).zip" +DMG_PATH = $(PWD)/$(APP).dmg + +all: clean archive notarize sign build clean + +clean: + rm -rf $(BUILD_PATH) + +.PHONY: archive +archive: clean + xcodebuild \ + -scheme $(APP) \ + -destination 'platform=OS X,arch=x86_64' \ + -configuration AppStoreDistribution archive \ + -archivePath $(ARCHIVE_PATH) + + xcodebuild \ + -exportArchive \ + -exportOptionsPlist "$(PWD)/exportOptions.plist" \ + -archivePath $(ARCHIVE_PATH) \ + -exportPath $(BUILD_PATH) + + ditto -c -k --keepParent $(APP_PATH) $(ZIP_PATH) + +.PHONY: notarize +notarize: + xcrun altool \ + --notarize-app \ + --primary-bundle-id $(BUNDLE_ID)\ + -itc_provider $(ITC_PROVIDER) \ + -u $(ITC_USERNAME) \ + -p $(ITC_PASSWORD) \ + --file $(ZIP_PATH) + + sleep 380 + +.PHONY: sign +sign: + xcrun stapler staple $(APP_PATH) + spctl -a -t exec -vvv $(APP_PATH) + +.PHONY: build +build: sign + if [ ! -d $(PWD)/create-dmg ]; then \ + git clone https://github.com/andreyvit/create-dmg; \ + fi + + ./create-dmg/create-dmg \ + --volname $(APP) \ + --background "./resources/background.png" \ + --window-pos 200 120 \ + --window-size 500 320 \ + --icon-size 80 \ + --icon "Stats.app" 125 175 \ + --hide-extension "Stats.app" \ + --app-drop-link 375 175 \ + $(DMG_PATH) \ + $(APP_PATH) + + rm -rf ./create-dmg + open $(PWD) + +check: + xcrun altool \ + --notarization-info $(RequestUUID) \ + -itc_provider $(ITC_PROVIDER) \ + -u $(ITC_USERNAME) \ + -p $(ITC_PASSWORD) + +history: + xcrun altool \ + --notarization-history 0 \ + -itc_provider $(ITC_PROVIDER) \ + -u $(ITC_USERNAME) \ + -p $(ITC_PASSWORD) \ No newline at end of file diff --git a/README.md b/README.md index 37bd4df5..73a7928e 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,16 @@ You can download latest version [here](https://github.com/exelban/stats/releases ## What's new +### v1.2.2 + - fully automated build and sign app process + - fixed update and about visibility window in dark mode + - added name of the indicators in the Chart/Chart with value + - added check for new version on start + - removed charts and charts with value to Disk module + - now module submenu is disabled if module is disabled + - fixed bug when network module stop working after turn on/of + - fixed few bugs + ### v1.2.1 - added charts and charts with value to Disk module - fixed bug when Chart with value does not shows diff --git a/Stats/AppDelegate.swift b/Stats/AppDelegate.swift index 85c61af1..29c74897 100755 --- a/Stats/AppDelegate.swift +++ b/Stats/AppDelegate.swift @@ -15,6 +15,9 @@ extension Notification.Name { let modules: Observable<[Module]> = Observable([CPU(), Memory(), Disk(), Battery(), Network()]) let colors: Observable = Observable(true) +let labelForChart: Observable = Observable(true) + +let updater = macAppUpdater(user: "exelban", repo: "stats") @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { @@ -27,7 +30,28 @@ class AppDelegate: NSObject, NSApplicationDelegate { return } + updater.check() { result, error in + if error != nil && error as! String == "No internet connection" { + return + } + + guard error == nil, let version: version = result else { + print("Error: \(error ?? "check error")") + return + } + + if version.newest { + DispatchQueue.main.async(execute: { + let updatesVC: NSWindowController? = NSStoryboard(name: "Updates", bundle: nil).instantiateController(withIdentifier: "UpdatesVC") as? NSWindowController + updatesVC?.window?.center() + updatesVC?.window?.level = .floating + updatesVC!.showWindow(self) + }) + } + } + colors << (defaults.object(forKey: "colors") != nil ? defaults.bool(forKey: "colors") : false) + labelForChart << (defaults.object(forKey: "labelForChart") != nil ? defaults.bool(forKey: "labelForChart") : false) _ = MenuBar(menuBarItem, menuBarButton: menuBarButton) let launcherAppId = "eu.exelban.StatsLauncher" @@ -41,6 +65,11 @@ class AppDelegate: NSObject, NSApplicationDelegate { self.defaults.set(true, forKey: "runAtLogin") } + if defaults.object(forKey: "labelForChart") == nil { + self.defaults.set(true, forKey: "labelForChart") + labelForChart << true + } + if isRunning { DistributedNotificationCenter.default().post(name: .killLauncher, object: Bundle.main.bundleIdentifier!) } @@ -73,9 +102,7 @@ class AboutVC: NSViewController { override func awakeFromNib() { if self.view.layer != nil { - self.view.window?.backgroundColor = .white - self.view.layer?.backgroundColor = .white - + self.view.window?.backgroundColor = .windowBackgroundColor let versionNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String versionLabel.stringValue = "Version \(versionNumber)" } @@ -92,7 +119,6 @@ class UpdatesVC: NSViewController { @IBOutlet weak var downloadButton: NSButton! @IBOutlet weak var spinner: NSProgressIndicator! - let updater = macAppUpdater(user: "exelban", repo: "stats") var url: String? override func viewDidLoad() { @@ -133,8 +159,7 @@ class UpdatesVC: NSViewController { override func awakeFromNib() { if self.view.layer != nil { - self.view.window?.backgroundColor = .white - self.view.layer?.backgroundColor = .white + self.view.window?.backgroundColor = .windowBackgroundColor } } diff --git a/Stats/MenuBar.swift b/Stats/MenuBar.swift index ad72edff..27abbc0f 100644 --- a/Stats/MenuBar.swift +++ b/Stats/MenuBar.swift @@ -65,6 +65,11 @@ class MenuBar { colorStatus.target = self preferencesMenu.addItem(colorStatus) + let chartLabels = NSMenuItem(title: "Label in chart", action: #selector(toggleMenu), keyEquivalent: "") + chartLabels.state = defaults.bool(forKey: "labelForChart") || defaults.object(forKey: "labelForChart") == nil ? NSControl.StateValue.on : NSControl.StateValue.off + chartLabels.target = self + preferencesMenu.addItem(chartLabels) + let runAtLogin = NSMenuItem(title: "Run at login", action: #selector(toggleMenu), keyEquivalent: "") runAtLogin.state = defaults.bool(forKey: "runAtLogin") || defaults.object(forKey: "runAtLogin") == nil ? NSControl.StateValue.on : NSControl.StateValue.off runAtLogin.target = self @@ -115,6 +120,10 @@ class MenuBar { self.defaults.set(status, forKey: "colors") colors << status return + case "Label in chart": + self.defaults.set(status, forKey: "labelForChart") + labelForChart << status + return default: break } } diff --git a/Stats/Modules/CPU/CPU.swift b/Stats/Modules/CPU/CPU.swift index 24a996c7..211fe05d 100644 --- a/Stats/Modules/CPU/CPU.swift +++ b/Stats/Modules/CPU/CPU.swift @@ -27,6 +27,15 @@ class CPU: Module { self.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini initMenu() initWidget() + + labelForChart.subscribe(observer: self) { (value, _) in + guard let chartView: Chart = self.view as? Chart else { + return + } + self.active << false + chartView.toggleLabel(value: value) + self.active << true + } } func initMenu() { diff --git a/Stats/Modules/Disk/Disk.swift b/Stats/Modules/Disk/Disk.swift index 986b9906..aba607c6 100644 --- a/Stats/Modules/Disk/Disk.swift +++ b/Stats/Modules/Disk/Disk.swift @@ -29,37 +29,21 @@ class Disk: Module { self.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini self.initMenu() - initWidget() + + let widget = Mini(frame: NSMakeRect(0, 0, MODULE_WIDTH, MODULE_HEIGHT)) + widget.label = self.shortName + self.view = widget } func initMenu() { menu = NSMenuItem(title: name, action: #selector(toggle), keyEquivalent: "") - submenu = NSMenu() - if defaults.object(forKey: name) != nil { menu.state = defaults.bool(forKey: name) ? NSControl.StateValue.on : NSControl.StateValue.off } else { menu.state = NSControl.StateValue.on } menu.target = self - - let mini = NSMenuItem(title: "Mini", action: #selector(toggleWidget), keyEquivalent: "") - mini.state = self.widgetType == Widgets.Mini ? NSControl.StateValue.on : NSControl.StateValue.off - mini.target = self - - let chart = NSMenuItem(title: "Chart", action: #selector(toggleWidget), keyEquivalent: "") - chart.state = self.widgetType == Widgets.Chart ? NSControl.StateValue.on : NSControl.StateValue.off - chart.target = self - - let chartWithValue = NSMenuItem(title: "Chart with value", action: #selector(toggleWidget), keyEquivalent: "") - chartWithValue.state = self.widgetType == Widgets.ChartWithValue ? NSControl.StateValue.on : NSControl.StateValue.off - chartWithValue.target = self - - submenu.addItem(mini) - submenu.addItem(chart) - submenu.addItem(chartWithValue) - - menu.submenu = submenu + menu.isEnabled = true } @objc func toggle(_ sender: NSMenuItem) { @@ -75,36 +59,4 @@ class Disk: Module { self.start() } } - - @objc func toggleWidget(_ sender: NSMenuItem) { - var widgetCode: Float = 0.0 - - switch sender.title { - case "Mini": - widgetCode = Widgets.Mini - case "Chart": - widgetCode = Widgets.Chart - case "Chart with value": - widgetCode = Widgets.ChartWithValue - default: - break - } - - if self.widgetType == widgetCode { - return - } - - for item in self.submenu.items { - if item.title == "Mini" || item.title == "Chart" || item.title == "Chart with value" { - item.state = NSControl.StateValue.off - } - } - - sender.state = sender.state == NSControl.StateValue.on ? NSControl.StateValue.off : NSControl.StateValue.on - self.defaults.set(widgetCode, forKey: "\(name)_widget") - self.widgetType = widgetCode - self.active << false - initWidget() - self.active << true - } } diff --git a/Stats/Modules/Memory/Memory.swift b/Stats/Modules/Memory/Memory.swift index 28e11d3a..dd4fb1af 100644 --- a/Stats/Modules/Memory/Memory.swift +++ b/Stats/Modules/Memory/Memory.swift @@ -29,6 +29,15 @@ class Memory: Module { self.widgetType = defaults.object(forKey: "\(name)_widget") != nil ? defaults.float(forKey: "\(name)_widget") : Widgets.Mini initMenu() initWidget() + + labelForChart.subscribe(observer: self) { (value, _) in + guard let chartView: Chart = self.view as? Chart else { + return + } + self.active << false + chartView.toggleLabel(value: value) + self.active << true + } } func initMenu() { @@ -69,8 +78,10 @@ class Memory: Module { self.active << state if !state { + menu.submenu = nil self.stop() } else { + menu.submenu = submenu self.start() } } diff --git a/Stats/Modules/Network/Network.swift b/Stats/Modules/Network/Network.swift index 7670ed72..9924c19e 100644 --- a/Stats/Modules/Network/Network.swift +++ b/Stats/Modules/Network/Network.swift @@ -87,8 +87,10 @@ class Network: Module { self.active << state if !state { + menu.submenu = nil self.stop() } else { + menu.submenu = submenu self.start() } } diff --git a/Stats/Modules/Network/NetworkReader.swift b/Stats/Modules/Network/NetworkReader.swift index 1e10b42b..f40cd05d 100644 --- a/Stats/Modules/Network/NetworkReader.swift +++ b/Stats/Modules/Network/NetworkReader.swift @@ -33,33 +33,37 @@ class NetworkReader: Reader { defer { self.pipe.fileHandleForReading.waitForDataInBackgroundAndNotify() } - + let output = self.pipe.fileHandleForReading.availableData if output.isEmpty { return } - + let outputString = String(data: output, encoding: String.Encoding.utf8) ?? "" let arr = outputString.condenseWhitespace().split(separator: " ") - + if !arr.isEmpty && Int64(arr[0]) != nil { guard let download = Int64(arr[2]), let upload = Int64(arr[5]) else { return } - + guard let value: Double = Double("\(download).\(upload)") else { return } - + self.usage << value } } - netProcess.launch() + do { + try netProcess.run() + } catch let error { + print(error) + } } func stop() { - netProcess.interrupt() + NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSFileHandleDataAvailable, object: nil) } func read() {} diff --git a/Stats/Supporting Files/About.storyboard b/Stats/Supporting Files/About.storyboard index 4f811814..077412f3 100644 --- a/Stats/Supporting Files/About.storyboard +++ b/Stats/Supporting Files/About.storyboard @@ -1,8 +1,8 @@ - + - + @@ -85,7 +85,7 @@ - + diff --git a/Stats/Supporting Files/Info.plist b/Stats/Supporting Files/Info.plist index 49ba35eb..03189f4c 100755 --- a/Stats/Supporting Files/Info.plist +++ b/Stats/Supporting Files/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.2.1 + 1.2.2 CFBundleVersion - 1 + 7 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/Stats/Widgets/Chart.swift b/Stats/Widgets/Chart.swift index 96e5e441..9857c784 100644 --- a/Stats/Widgets/Chart.swift +++ b/Stats/Widgets/Chart.swift @@ -9,6 +9,10 @@ import Cocoa class Chart: NSView, Widget { + var labelPadding: CGFloat = 10.0 + var labelEnabled: Bool = false + var label: String = "" + var height: CGFloat = 0.0 var points: [Double] { didSet { @@ -21,6 +25,11 @@ class Chart: NSView, Widget { super.init(frame: frame) self.wantsLayer = true self.addSubview(NSView()) + self.labelEnabled = labelForChart.value + + if self.labelEnabled { + self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width + labelPadding, height: self.frame.size.height) + } } required init?(coder decoder: NSCoder) { @@ -34,12 +43,19 @@ class Chart: NSView, Widget { let gradientColor: NSColor = NSColor(red: (26/255.0), green: (126/255.0), blue: (252/255.0), alpha: 0.5) let context = NSGraphicsContext.current!.cgContext - let xOffset: CGFloat = 4.0 + var xOffset: CGFloat = 4.0 + if labelEnabled { + xOffset = xOffset + labelPadding + } let yOffset: CGFloat = 3.0 if height == 0 { height = self.frame.size.height - CGFloat((yOffset * 2)) } - let xRatio = Double(self.frame.size.width - (xOffset * 2)) / (Double(self.points.count) - 1) + + var xRatio = Double(self.frame.size.width - (xOffset * 2)) / (Double(self.points.count) - 1) + if labelEnabled { + xRatio = Double(self.frame.size.width - (xOffset * 2) + labelPadding) / (Double(self.points.count) - 1) + } let columnXPoint = { (point: Int) -> CGFloat in return CGFloat((Double(point) * xRatio)) + xOffset @@ -76,6 +92,30 @@ class Chart: NSView, Widget { graphPath.lineWidth = 0.5 graphPath.stroke() + + if !self.labelEnabled { + return + } + + let style = NSMutableParagraphStyle() + style.alignment = .center + let stringAttributes = [ + NSAttributedString.Key.font: NSFont.systemFont(ofSize: 7.2, weight: .bold), + NSAttributedString.Key.foregroundColor: NSColor.labelColor, + NSAttributedString.Key.paragraphStyle: style + ] + + let letterHeight = (self.frame.size.height - (MODULE_MARGIN*2)) / 3 + let letterWidth: CGFloat = 10.0 + + var yMargin = MODULE_MARGIN + for char in self.label.reversed() { + let rect = CGRect(x: MODULE_MARGIN, y: yMargin, width: letterWidth, height: letterHeight) + let str = NSAttributedString.init(string: "\(char)", attributes: stringAttributes) + str.draw(with: rect) + + yMargin += letterHeight + } } func redraw() { @@ -97,6 +137,15 @@ class Chart: NSView, Widget { } } } + + func toggleLabel(value: Bool) { + labelEnabled = value + if value { + self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width + labelPadding, height: self.frame.size.height) + } else { + self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width - labelPadding, height: self.frame.size.height) + } + } } class ChartWithValue: Chart { @@ -104,10 +153,12 @@ class ChartWithValue: Chart { override init(frame: NSRect) { super.init(frame: frame) - self.wantsLayer = true valueLabel = NSTextField(frame: NSMakeRect(2, MODULE_HEIGHT - 11, self.frame.size.width, 10)) + if labelEnabled { + valueLabel = NSTextField(frame: NSMakeRect(labelPadding + 2, MODULE_HEIGHT - 11, self.frame.size.width, 10)) + } valueLabel.textColor = NSColor.red valueLabel.isEditable = false valueLabel.isSelectable = false @@ -146,4 +197,15 @@ class ChartWithValue: Chart { } } } + + override func toggleLabel(value: Bool) { + labelEnabled = value + if value { + valueLabel.frame = NSMakeRect(labelPadding + 2, MODULE_HEIGHT - 11, self.frame.size.width, 10) + self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width + labelPadding, height: self.frame.size.height) + } else { + valueLabel.frame = NSMakeRect(2, MODULE_HEIGHT - 11, self.frame.size.width, 10) + self.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: self.frame.size.width - labelPadding, height: self.frame.size.height) + } + } } diff --git a/Stats/libs/Extensions.swift b/Stats/libs/Extensions.swift index e6cc8819..16eb29c8 100755 --- a/Stats/libs/Extensions.swift +++ b/Stats/libs/Extensions.swift @@ -141,16 +141,3 @@ extension NSBezierPath { self.line(to: arrowLine2) } } - -//extension NSView { -// var backgroundColor: NSColor? { -// get { -// guard let color = layer?.backgroundColor else { return nil } -// return NSColor(cgColor: color) -// } -// set { -// wantsLayer = true -// layer?.backgroundColor = newValue?.cgColor -// } -// } -//} diff --git a/Stats/libs/Module.swift b/Stats/libs/Module.swift index 60d23e2c..3df2257a 100644 --- a/Stats/libs/Module.swift +++ b/Stats/libs/Module.swift @@ -13,6 +13,7 @@ protocol Module: class { var shortName: String { get } var view: NSView { get set } var menu: NSMenuItem { get } + var submenu: NSMenu { get } var active: Observable { get } var available: Observable { get } var reader: Reader { get } @@ -31,10 +32,14 @@ extension Module { self.view = widget break case Widgets.Chart: - self.view = Chart(frame: NSMakeRect(0, 0, MODULE_WIDTH + 7, MODULE_HEIGHT)) + let widget = Chart(frame: NSMakeRect(0, 0, MODULE_WIDTH + 7, MODULE_HEIGHT)) + widget.label = self.shortName + self.view = widget break case Widgets.ChartWithValue: - self.view = ChartWithValue(frame: NSMakeRect(0, 0, MODULE_WIDTH + 7, MODULE_HEIGHT)) + let widget = ChartWithValue(frame: NSMakeRect(0, 0, MODULE_WIDTH + 7, MODULE_HEIGHT)) + widget.label = self.shortName + self.view = widget break case Widgets.Dots: self.view = NetworkDotsView(frame: NSMakeRect(0, 0, MODULE_WIDTH, MODULE_HEIGHT)) diff --git a/build.sh b/build.sh deleted file mode 100644 index 79b4eab2..00000000 --- a/build.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh - -if [ ! -d "./create-dmg" ]; then - git clone https://github.com/andreyvit/create-dmg -fi - -# xcodebuild -configuration Distribution clean build - -# cp -rf $PWD/build/Release/Stats.app ./ -# rm -rf echo $PWD/build - -./create-dmg/create-dmg \ - --volname "Stats" \ - --background "./resources/background.png" \ - --window-pos 200 120 \ - --window-size 500 320 \ - --icon-size 80 \ - --icon "Stats.app" 125 175 \ - --hide-extension "Stats.app" \ - --app-drop-link 375 175 \ - "Stats.dmg" \ - "Stats.app" - -rm -rf ./create-dmg -rm -rf Stats.app diff --git a/exportOptions.plist b/exportOptions.plist new file mode 100644 index 00000000..376008e7 --- /dev/null +++ b/exportOptions.plist @@ -0,0 +1,10 @@ + + + + + method + developer-id + signingStyle + automatic + + \ No newline at end of file