Skip to content

Commit

Permalink
Merge pull request #48 from othyn/17-as-a-user-i-would-like-to-be-abl…
Browse files Browse the repository at this point in the history
…e-to-minimise-the-app-to-a-menubar-icon

As a user i would like to be able to minimise the app to a menubar icon
  • Loading branch information
othyn authored Jul 18, 2022
2 parents 6bbc137 + 63801e3 commit 0c92cab
Show file tree
Hide file tree
Showing 21 changed files with 547 additions and 69 deletions.
3 changes: 2 additions & 1 deletion .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ only_rules:
- empty_xctest_method
- enum_case_associated_values_count
- explicit_init
- explicit_self
- fallthrough
- fatal_error_message
- first_where
Expand Down Expand Up @@ -155,7 +156,7 @@ identifier_name:
error: 2
validates_start_with_lowercase: false
deployment_target:
macOS_deployment_target: '11.0'
macOS_deployment_target: "11.0"
custom_rules:
swiftui_state_private:
regex: '@(State|StateObject|ObservedObject|EnvironmentObject)\s+var'
Expand Down
20 changes: 20 additions & 0 deletions auto-clicker.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
B5BCE80A287C343900B739AD /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B5BCE80C287C343900B739AD /* Localizable.strings */; };
B5BCE80D287C344200B739AD /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = B5BCE80F287C344200B739AD /* Localizable.stringsdict */; };
B5BCE811287C38ED00B739AD /* CGVector+DefaultsSerializable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BCE810287C38ED00B739AD /* CGVector+DefaultsSerializable.swift */; };
B5BF09402883230D008092D9 /* MenuBarService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF093F2883230D008092D9 /* MenuBarService.swift */; };
B5BF094328832A4E008092D9 /* MenuBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF094228832A4E008092D9 /* MenuBarView.swift */; };
B5BF094528847346008092D9 /* KeyboardShortcuts+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BF094428847346008092D9 /* KeyboardShortcuts+Extensions.swift */; };
B5D603EF2883027D00655D2C /* SettingsTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D603EE2883027D00655D2C /* SettingsTabView.swift */; };
B5D603F12883035E00655D2C /* ContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D603F02883035E00655D2C /* ContainerView.swift */; };
B5D603F528830A3600655D2C /* SettingsTabItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D603F428830A3600655D2C /* SettingsTabItemView.swift */; };
Expand Down Expand Up @@ -106,6 +109,9 @@
B5BCE80B287C343900B739AD /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/Localizable.strings"; sourceTree = "<group>"; };
B5BCE80E287C344200B739AD /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "en-GB"; path = "en-GB.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
B5BCE810287C38ED00B739AD /* CGVector+DefaultsSerializable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGVector+DefaultsSerializable.swift"; sourceTree = "<group>"; };
B5BF093F2883230D008092D9 /* MenuBarService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarService.swift; sourceTree = "<group>"; };
B5BF094228832A4E008092D9 /* MenuBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarView.swift; sourceTree = "<group>"; };
B5BF094428847346008092D9 /* KeyboardShortcuts+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyboardShortcuts+Extensions.swift"; sourceTree = "<group>"; };
B5D603EE2883027D00655D2C /* SettingsTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTabView.swift; sourceTree = "<group>"; };
B5D603F02883035E00655D2C /* ContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerView.swift; sourceTree = "<group>"; };
B5D603F428830A3600655D2C /* SettingsTabItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTabItemView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -162,6 +168,7 @@
B5E92B1B27F1BA5E00A7FC63 /* ThemeService.swift */,
B5B6B46428033A0F00C779FD /* PermissionsService.swift */,
B5E4213628057BA900C2CA2D /* LoggerService.swift */,
B5BF093F2883230D008092D9 /* MenuBarService.swift */,
);
path = Services;
sourceTree = "<group>";
Expand Down Expand Up @@ -300,6 +307,7 @@
isa = PBXGroup;
children = (
B510761227F4A34500BB1CDA /* Main */,
B5BF094128832A3F008092D9 /* Menu Bar */,
B510761327F4A35400BB1CDA /* Settings */,
B510763327FF7ECD00BB1CDA /* Libs */,
);
Expand Down Expand Up @@ -333,6 +341,14 @@
path = Init;
sourceTree = "<group>";
};
B5BF094128832A3F008092D9 /* Menu Bar */ = {
isa = PBXGroup;
children = (
B5BF094228832A4E008092D9 /* MenuBarView.swift */,
);
path = "Menu Bar";
sourceTree = "<group>";
};
B5E6394E27CA4CB1008B111A /* Constants */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -376,6 +392,7 @@
C4345BB42846056000365CF9 /* ProcessInfo+Extensions.swift */,
B5BCE804287BFB5A00B739AD /* Color+ExpressibleByStringLiteral.swift */,
B5BCE810287C38ED00B739AD /* CGVector+DefaultsSerializable.swift */,
B5BF094428847346008092D9 /* KeyboardShortcuts+Extensions.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -521,6 +538,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B5BF094528847346008092D9 /* KeyboardShortcuts+Extensions.swift in Sources */,
B5E6395027CA4CDD008B111A /* FieldConstants.swift in Sources */,
B5F6A00F27F37440003CD730 /* MainView.swift in Sources */,
B510762527F4BB7B00BB1CDA /* WindowSettingsTabView.swift in Sources */,
Expand Down Expand Up @@ -567,13 +585,15 @@
B5E92B0E27F1036B00A7FC63 /* DurationModal.swift in Sources */,
B5B0B2AF2882EE6E00462F11 /* ACWindow.swift in Sources */,
B5BCE803287BF59900B739AD /* Colour.swift in Sources */,
B5BF09402883230D008092D9 /* MenuBarService.swift in Sources */,
B5F5C60B28039AA40049B04D /* FormState.swift in Sources */,
B510760F27F4A25400BB1CDA /* WindowStateService.swift in Sources */,
B5E92B1C27F1BA5E00A7FC63 /* ThemeService.swift in Sources */,
B5B0B2AD2882DCCC00462F11 /* Zoop.swift in Sources */,
C4345BB52846056000365CF9 /* ProcessInfo+Extensions.swift in Sources */,
B5E6395327CA62EB008B111A /* ThemedButtonStyle.swift in Sources */,
B510762127F4BB4900BB1CDA /* AppearanceSettingsTabView.swift in Sources */,
B5BF094328832A4E008092D9 /* MenuBarView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
15 changes: 14 additions & 1 deletion auto-clicker/Build Assets/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,21 @@
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>auto-clicker</string>
<key>CFBundleURLSchemes</key>
<array>
<string>auto-clicker</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>cc1646c</string>
<string>f910f6f</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
Expand Down
5 changes: 4 additions & 1 deletion auto-clicker/Constants/Defaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ import Cocoa
import Defaults

extension Defaults.Keys {
static let appShouldQuitOnClose = Key<Bool>("app_should_quit_on_close", default: true)

static let windowShouldKeepOnTop = Key<Bool>("window_should_keep_on_top", default: false)

static let appShouldQuitOnClose = Key<Bool>("app_should_quit_on_close", default: true)
static let menuBarShowIcon = Key<Bool>("menu_bar_show_icon", default: true)
static let menuBarHideDock = Key<Bool>("menu_bar_hide_dock", default: false)

static let appearanceSelectedTheme = Key<ThemeService>("appearance_selected_theme", default: ThemeService())

Expand Down
29 changes: 29 additions & 0 deletions auto-clicker/Extensions/KeyboardShortcuts+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// KeyboardShortcuts+Extensions.swift
// auto-clicker
//
// Created by Ben Tindall on 17/07/2022.
//

import Foundation
import KeyboardShortcuts

extension KeyboardShortcuts.Shortcut {
/**
The string representation of the keyboard shortcut key only.

```
print(Shortcut(.a, modifiers: [.command]))
//=> "A"
```
*/
public var descriptionKeyOnly: String {
// 'keyToCharacter' is inaccessible due to 'fileprivate' protection level
// :(
// modifiers.description + (keyToCharacter()?.uppercased() ?? "�")

// Hacky due to the above fileprivate protection level of keyToCharacter()
// So just strip the modifier from the string to gain access to a string representation of just the key alone
self.description.replacingOccurrences(of: modifiers.description, with: "")
}
}
46 changes: 39 additions & 7 deletions auto-clicker/Init/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,21 @@

import Foundation
import Cocoa
import Defaults

final class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
func applicationWillFinishLaunching(_ notification: Notification) {
WindowStateService.refreshDockIconState()
}

final class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
if let window = NSApplication.shared.mainWindow {
window.titlebarAppearsTransparent = true
NSApp.activate(ignoringOtherApps: true)

let customToolbar = NSToolbar()
customToolbar.showsBaselineSeparator = false
window.toolbar = customToolbar
}
// Hacky workaround in SwiftUI in order to have macOS persist the window size state
// https://stackoverflow.com/a/72558375/4494375
NSApp.windows[0].delegate = self

MenuBarService.refreshState()

PermissionsService.acquireAccessibilityPrivileges()
}
Expand All @@ -28,4 +33,31 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
func applicationWillBecomeActive(_ notification: Notification) {
WindowStateService.refreshKeepWindowOnTop()
}

func applicationDidHide(_ notification: Notification) {
if let hideOrShowMenuItem = MenuBarService.hideOrShowMenuItem {
hideOrShowMenuItem.title = NSLocalizedString("menu_bar_item_hide_show_show", comment: "Menu bar item show option")
+ " "
+ NSLocalizedString("menu_bar_item_hide_show_suffix", comment: "Menu bar item show/hide option suffix")
}
}

func applicationDidUnhide(_ notification: Notification) {
if let hideOrShowMenuItem = MenuBarService.hideOrShowMenuItem {
hideOrShowMenuItem.title = NSLocalizedString("menu_bar_item_hide_show_hide", comment: "Menu bar item hide option")
+ " "
+ NSLocalizedString("menu_bar_item_hide_show_suffix", comment: "Menu bar item show/hide option suffix")
}
}

// Hacky workaround in SwiftUI in order to have macOS persist the window size state
// https://stackoverflow.com/a/72558375/4494375
func windowShouldClose(_ sender: NSWindow) -> Bool {
if !Defaults[.appShouldQuitOnClose] {
NSApp.hide(nil)
return false
}

return true
}
}
8 changes: 4 additions & 4 deletions auto-clicker/Init/AutoClickerApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ struct AutoClickerApp: App {

WindowGroup {
ACWindow()
.frame(minWidth: WindowStateService.minWidth,
maxWidth: WindowStateService.minWidth * WindowStateService.maxDimensionMultiplier,
minHeight: WindowStateService.minHeight,
maxHeight: WindowStateService.minHeight)
.frame(minWidth: WindowStateService.mainWindowMinWidth,
maxWidth: WindowStateService.mainWindowMinWidth * WindowStateService.mainWindowMaxDimensionMultiplier,
minHeight: WindowStateService.mainWindowMinHeight,
maxHeight: WindowStateService.mainWindowMinHeight)
}
.windowStyle(.hiddenTitleBar)
.commands {
Expand Down
20 changes: 18 additions & 2 deletions auto-clicker/Localisation/en-GB.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,38 @@
"settings_window" = "Window";
"settings_appearance" = "Appearance";

"settings_general_app_should_quit_on_close_title" = "State";
"settings_general_app_should_quit_on_close_title" = "Lifecycle";
"settings_general_app_should_quit_on_close" = "Quit app on main window close";
"settings_general_app_should_quit_on_close_help" = "When the main window of the application is closed, instead of keeping the app running in the background (macOS default behaviour), quit the app.";

"settings_general_menu_bar_show_icon_title" = "Menu Bar";
"settings_general_menu_bar_show_icon" = "Show menu bar icon";
"settings_general_menu_bar_show_icon_help" = "Always show an icon in the macOS menu bar where the app and quick access functionality can be accessed.";

"settings_general_menu_bar_hide_dock" = "Hide dock icon";
"settings_general_menu_bar_hide_dock_help" = "Instead of the app running from the dock, the app will instead run from the menu bar.";

"settings_keyboard_shortcuts_title" = "Global";
"settings_keyboard_shortcuts_start" = "Start auto clicker";
"settings_keyboard_shortcuts_stop" = "Stop auto clicker";
"settings_keyboard_shortcuts_help" = "Global shortcuts to start and stop the auto click functionality.";

"settings_window_stay_ontop_title" = "Visibility";
"settings_window_stay_ontop" = "Keep the main window ontop";
"settings_window_stay_ontop" = "Always keep the main window on top";
"settings_window_stay_ontop_help" = "When you click on another window, this defines whether the window should dissapear behind the other windows (macOS default behaviour), or stay on top of all other windows.";

"help_commands_request_a_feature" = "Request a feature...";
"help_commands_report_a_bug" = "Report a problem...";

"menu_bar_item_start" = "Start now";
"menu_bar_item_stop" = "Stop now";
"menu_bar_item_hide_show_show" = "Show";
"menu_bar_item_hide_show_hide" = "Hide";
"menu_bar_item_hide_show_suffix" = "app";
"menu_bar_item_preferences" = "Preferences...";
"menu_bar_item_about" = "About...";
"menu_bar_item_quit" = "Quit";

"colour_black" = "Black";
"colour_blue" = "Blue";
"colour_brown" = "Brown";
Expand Down
19 changes: 19 additions & 0 deletions auto-clicker/Observable Objects/AutoClickSimulator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import SwiftUI
import Defaults

final class AutoClickSimulator: ObservableObject {
static var shared: AutoClickSimulator = .init()
private init() {}

@Published var isAutoClicking = false

@Published var remainingInterations: Int = 0
Expand All @@ -31,6 +34,14 @@ final class AutoClickSimulator: ObservableObject {
func start() {
self.isAutoClicking = true

if let startMenuItem = MenuBarService.startMenuItem {
startMenuItem.isEnabled = false
}

if let stopMenuItem = MenuBarService.stopMenuItem {
stopMenuItem.isEnabled = true
}

self.activity = ProcessInfo.processInfo.beginActivity(.autoClicking)

self.duration = Defaults[.autoClickerState].pressIntervalDuration
Expand All @@ -53,6 +64,14 @@ final class AutoClickSimulator: ObservableObject {
func stop() {
self.isAutoClicking = false

if let startMenuItem = MenuBarService.startMenuItem {
startMenuItem.isEnabled = true
}

if let stopMenuItem = MenuBarService.stopMenuItem {
stopMenuItem.isEnabled = false
}

self.activity?.cancel()
self.activity = nil

Expand Down
11 changes: 11 additions & 0 deletions auto-clicker/Observable Objects/DelayTimer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import Defaults
import Combine

final class DelayTimer: ObservableObject {
static var shared: DelayTimer = .init()
private init() {}

private static let defaultCountdownText: String = "-"

@Published var isCountingDown = false
Expand All @@ -25,6 +28,14 @@ final class DelayTimer: ObservableObject {

self.onFinish = onFinish

if let startMenuItem = MenuBarService.startMenuItem {
startMenuItem.isEnabled = false
}

if let stopMenuItem = MenuBarService.stopMenuItem {
stopMenuItem.isEnabled = true
}

if delayInSeconds > 0 {
self.remainingDelaySeconds = delayInSeconds
self.isCountingDown = true
Expand Down
8 changes: 2 additions & 6 deletions auto-clicker/Services/LoggerService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,11 @@ import SwiftUI
final class LoggerService {
private static func log(file: String, function: String, _ lines: [String]) {
#if DEBUG
NSLog("---")

NSLog("Caller: \(file) ~ \(function)")
NSLog(">~ Who: \(file) ~ \(function)")

for line in lines {
NSLog(line)
NSLog(">~ What: \(line)")
}

NSLog("---")
#endif
}

Expand Down
Loading

0 comments on commit 0c92cab

Please sign in to comment.