diff --git a/Calendr.xcodeproj/project.pbxproj b/Calendr.xcodeproj/project.pbxproj index 5d694830..b2b9c16e 100644 --- a/Calendr.xcodeproj/project.pbxproj +++ b/Calendr.xcodeproj/project.pbxproj @@ -43,6 +43,7 @@ 344A59DB29AEBBBF004F0452 /* EventUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344A59DA29AEBBBF004F0452 /* EventUtils.swift */; }; 3453E6FD28386A84002DCC3C /* Bool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3453E6FC28386A84002DCC3C /* Bool.swift */; }; 3453E6FF28393943002DCC3C /* ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3453E6FE28393943002DCC3C /* ContextMenu.swift */; }; + 345A34972C4DADD000AF0002 /* NSMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345A34962C4DADD000AF0002 /* NSMenu.swift */; }; 345DD97326920D1B00294D90 /* CalendarViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345DD97226920D1B00294D90 /* CalendarViewPreview.swift */; }; 345FBD99293EA476005DD6DF /* Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345FBD98293EA476005DD6DF /* Keyboard.swift */; }; 34651F1525E1BB8400518C5A /* Strings.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34651F1425E1BB8400518C5A /* Strings.generated.swift */; }; @@ -139,6 +140,8 @@ 34D25E4A292F9E2100557E70 /* ImageButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D25E49292F9E2100557E70 /* ImageButton.swift */; }; 34D3655829402D5700849457 /* NSColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D3655729402D5700849457 /* NSColor.swift */; }; 34D55FE325F06669007F5C81 /* Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D55FE225F06669007F5C81 /* Sequence.swift */; }; + 34D88DC82C4C66F80047D636 /* KeyboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D88DC72C4C66F80047D636 /* KeyboardViewController.swift */; }; + 34D88DCA2C4C687B0047D636 /* SettingsUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D88DC92C4C687B0047D636 /* SettingsUI.swift */; }; 34E004A725B61D5200241419 /* StatusItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E004A625B61D5200241419 /* StatusItemViewModel.swift */; }; 34E1902325B76CFE00E9491B /* CalendarPickerViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E1902225B76CFE00E9491B /* CalendarPickerViewModelTests.swift */; }; 34E259832B659B5600074D3A /* KeyboardShortcuts in Frameworks */ = {isa = PBXBuildFile; productRef = 34E259822B659B5600074D3A /* KeyboardShortcuts */; }; @@ -214,6 +217,7 @@ 344A59DA29AEBBBF004F0452 /* EventUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventUtils.swift; sourceTree = ""; }; 3453E6FC28386A84002DCC3C /* Bool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bool.swift; sourceTree = ""; }; 3453E6FE28393943002DCC3C /* ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenu.swift; sourceTree = ""; }; + 345A34962C4DADD000AF0002 /* NSMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSMenu.swift; sourceTree = ""; }; 345DD97226920D1B00294D90 /* CalendarViewPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarViewPreview.swift; sourceTree = ""; }; 345FBD98293EA476005DD6DF /* Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keyboard.swift; sourceTree = ""; }; 34651EFB25E1B26800518C5A /* swiftgen.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = swiftgen.yml; sourceTree = ""; }; @@ -321,6 +325,8 @@ 34D3655729402D5700849457 /* NSColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSColor.swift; sourceTree = ""; }; 34D55FE225F06669007F5C81 /* Sequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sequence.swift; sourceTree = ""; }; 34D88DC62C4C3D1A0047D636 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; + 34D88DC72C4C66F80047D636 /* KeyboardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardViewController.swift; sourceTree = ""; }; + 34D88DC92C4C687B0047D636 /* SettingsUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsUI.swift; sourceTree = ""; }; 34E004A625B61D5200241419 /* StatusItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemViewModel.swift; sourceTree = ""; }; 34E1902225B76CFE00E9491B /* CalendarPickerViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarPickerViewModelTests.swift; sourceTree = ""; }; 34E60D6026A0D6D6004DA082 /* NSAccessibilityProtocol+Rx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAccessibilityProtocol+Rx.swift"; sourceTree = ""; }; @@ -596,6 +602,7 @@ 34D55FE225F06669007F5C81 /* Sequence.swift */, 34651F5925E2F08500518C5A /* String.swift */, 34F2F7352BB739A500666507 /* Range.swift */, + 345A34962C4DADD000AF0002 /* NSMenu.swift */, ); path = Extensions; sourceTree = ""; @@ -741,9 +748,11 @@ 3449403125C348C70020E664 /* CalendarPickerViewController.swift */, 34B5A09C25B118EC00F7F7ED /* CalendarPickerViewModel.swift */, 3449402D25C348B20020E664 /* GeneralSettingsViewController.swift */, + 34D88DC72C4C66F80047D636 /* KeyboardViewController.swift */, 34934CD528E69520009635D4 /* Prefs+UserDefaults.swift */, 34B5A09225B0CE6F00F7F7ED /* SettingsViewController.swift */, 348B8D0325B2925100E518FE /* SettingsViewModel.swift */, + 34D88DC92C4C687B0047D636 /* SettingsUI.swift */, ); path = Settings; sourceTree = ""; @@ -937,6 +946,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 345A34972C4DADD000AF0002 /* NSMenu.swift in Sources */, 347D0FDF259533C3002451EC /* CalendarView.swift in Sources */, 3421DA1F2693F12400056837 /* CalendarModel+Factory.swift in Sources */, 34B5A09D25B118EC00F7F7ED /* CalendarPickerViewModel.swift in Sources */, @@ -1036,8 +1046,10 @@ 34D3655829402D5700849457 /* NSColor.swift in Sources */, 34A591E42C175BBD00DE419D /* AppEditShortcuts.swift in Sources */, 3453E6FF28393943002DCC3C /* ContextMenu.swift in Sources */, + 34D88DCA2C4C687B0047D636 /* SettingsUI.swift in Sources */, 34924CEE259FD064009C3450 /* DateFormatter.swift in Sources */, 3468E651284BD3D600B21EC8 /* EventLink.swift in Sources */, + 34D88DC82C4C66F80047D636 /* KeyboardViewController.swift in Sources */, 34A591E62C175C0F00DE419D /* AppResignFocus.swift in Sources */, 3470214A259DFF3000827AE7 /* CalendarServiceProvider.swift in Sources */, 3408D13E25982DB400CF1425 /* DateSelector.swift in Sources */, @@ -1296,7 +1308,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 1.11.3; + MARKETING_VERSION = 1.11.4; PRODUCT_BUNDLE_IDENTIFIER = br.paker.Calendr; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Calendr/Config/Calendr-Bridging-Header.h"; @@ -1322,7 +1334,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 1.11.3; + MARKETING_VERSION = 1.11.4; PRODUCT_BUNDLE_IDENTIFIER = br.paker.Calendr; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Calendr/Config/Calendr-Bridging-Header.h"; diff --git a/Calendr.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Calendr.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 32cdc40d..09057ea0 100644 --- a/Calendr.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Calendr.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "7977df563fd34a0f681f62cd21147d02105dfc02903205b35488d05c20b8a357", "pins" : [ { "identity" : "keyboardshortcuts", @@ -14,10 +15,10 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/ReactiveX/RxSwift", "state" : { - "revision" : "9dcaa4b333db437b0fbfaf453fad29069044a8b4", - "version" : "6.6.0" + "revision" : "b06a8c8596e4c3e8e7788e08e720e3248563ce6a", + "version" : "6.7.1" } } ], - "version" : 2 + "version" : 3 } diff --git a/Calendr/Assets/Generated/Strings.generated.swift b/Calendr/Assets/Generated/Strings.generated.swift index 21f68f50..7810182f 100644 --- a/Calendr/Assets/Generated/Strings.generated.swift +++ b/Calendr/Assets/Generated/Strings.generated.swift @@ -120,6 +120,44 @@ internal enum Strings { internal static let hide = Strings.tr("Localizable", "settings.events.finished.hide", fallback: "Hide") } } + internal enum Keyboard { + internal enum GlobalShortcuts { + /// Open calendar + internal static let openCalendar = Strings.tr("Localizable", "settings.keyboard.global_shortcuts.open_calendar", fallback: "Open calendar") + /// Open next event + internal static let openNextEvent = Strings.tr("Localizable", "settings.keyboard.global_shortcuts.open_next_event", fallback: "Open next event") + /// Next event options + internal static let openNextEventOptions = Strings.tr("Localizable", "settings.keyboard.global_shortcuts.open_next_event_options", fallback: "Next event options") + /// Open next reminder + internal static let openNextReminder = Strings.tr("Localizable", "settings.keyboard.global_shortcuts.open_next_reminder", fallback: "Open next reminder") + /// Next reminder options + internal static let openNextReminderOptions = Strings.tr("Localizable", "settings.keyboard.global_shortcuts.open_next_reminder_options", fallback: "Next reminder options") + /// Global shortcuts + internal static let title = Strings.tr("Localizable", "settings.keyboard.global_shortcuts.title", fallback: "Global shortcuts") + } + internal enum LocalShortcuts { + /// Current date + internal static let currDate = Strings.tr("Localizable", "settings.keyboard.local_shortcuts.curr_date", fallback: "Current date") + /// Next date + internal static let nextDate = Strings.tr("Localizable", "settings.keyboard.local_shortcuts.next_date", fallback: "Next date") + /// Next month + internal static let nextMonth = Strings.tr("Localizable", "settings.keyboard.local_shortcuts.next_month", fallback: "Next month") + /// Next week + internal static let nextWeek = Strings.tr("Localizable", "settings.keyboard.local_shortcuts.next_week", fallback: "Next week") + /// Open selected date + internal static let openDate = Strings.tr("Localizable", "settings.keyboard.local_shortcuts.open_date", fallback: "Open selected date") + /// Pin calendar + internal static let pinCalendar = Strings.tr("Localizable", "settings.keyboard.local_shortcuts.pin_calendar", fallback: "Pin calendar") + /// Previous date + internal static let prevDate = Strings.tr("Localizable", "settings.keyboard.local_shortcuts.prev_date", fallback: "Previous date") + /// Previous month + internal static let prevMonth = Strings.tr("Localizable", "settings.keyboard.local_shortcuts.prev_month", fallback: "Previous month") + /// Previous week + internal static let prevWeek = Strings.tr("Localizable", "settings.keyboard.local_shortcuts.prev_week", fallback: "Previous week") + /// Local shortcuts + internal static let title = Strings.tr("Localizable", "settings.keyboard.local_shortcuts.title", fallback: "Local shortcuts") + } + } internal enum MenuBar { /// Launch at login internal static let autoLaunch = Strings.tr("Localizable", "settings.menu_bar.auto_launch", fallback: "Launch at login") @@ -147,6 +185,8 @@ internal enum Strings { internal static let calendars = Strings.tr("Localizable", "settings.tab.calendars", fallback: "Calendars") /// General internal static let general = Strings.tr("Localizable", "settings.tab.general", fallback: "General") + /// Shortcuts + internal static let keyboard = Strings.tr("Localizable", "settings.tab.keyboard", fallback: "Shortcuts") } } } diff --git a/Calendr/Assets/Icons.swift b/Calendr/Assets/Icons.swift index b5a96b39..0e9d90fe 100644 --- a/Calendr/Assets/Icons.swift +++ b/Calendr/Assets/Icons.swift @@ -32,6 +32,7 @@ enum Icons { enum Settings { static let general = NSImage(systemName: "gear") static let calendars = NSImage(systemName: "calendar.badge.plus") + static let keyboard = NSImage(systemName: "keyboard") static let about = NSImage(systemName: "book") static let tooltip = NSImage(systemName: "info.circle") static let ruler = NSImage(systemName: "ruler") diff --git a/Calendr/Assets/cs.lproj/Localizable.strings b/Calendr/Assets/cs.lproj/Localizable.strings index a4212200..493fbc36 100644 --- a/Calendr/Assets/cs.lproj/Localizable.strings +++ b/Calendr/Assets/cs.lproj/Localizable.strings @@ -12,6 +12,7 @@ "settings.title" = "Předvolby"; "settings.tab.general" = "Obecné"; "settings.tab.calendars" = "Kalendáře"; +"settings.tab.keyboard" = "Zkratky"; "settings.tab.about" = "O aplikaci"; "settings.menu_bar" = "Řádek nabídek"; @@ -39,6 +40,24 @@ "settings.transparency" = "Průhlednost"; +"settings.keyboard.local_shortcuts.title" = "Místní zkratky"; +"settings.keyboard.local_shortcuts.prev_date" = "Předchozí datum"; +"settings.keyboard.local_shortcuts.next_date" = "Následující datum"; +"settings.keyboard.local_shortcuts.prev_week" = "Předchozí týden"; +"settings.keyboard.local_shortcuts.next_week" = "Následující týden"; +"settings.keyboard.local_shortcuts.prev_month" = "Předchozí měsíc"; +"settings.keyboard.local_shortcuts.next_month" = "Následující měsíc"; +"settings.keyboard.local_shortcuts.curr_date" = "Aktuální datum"; +"settings.keyboard.local_shortcuts.open_date" = "Otevřít vybrané datum"; +"settings.keyboard.local_shortcuts.pin_calendar" = "Připnout kalendář"; + +"settings.keyboard.global_shortcuts.title" = "Globální zkratky"; +"settings.keyboard.global_shortcuts.open_calendar" = "Otevřít kalendář"; +"settings.keyboard.global_shortcuts.open_next_event" = "Otevřít další událost"; +"settings.keyboard.global_shortcuts.open_next_event_options" = "Možnosti další události"; +"settings.keyboard.global_shortcuts.open_next_reminder" = "Otevřít další připomínku"; +"settings.keyboard.global_shortcuts.open_next_reminder_options" = "Možnosti další připomínky"; + "formatter.date.all_day" = "Celý den"; "formatter.date.today" = "Dnes"; "formatter.date.relative.in" = "za %@"; diff --git a/Calendr/Assets/de.lproj/Localizable.strings b/Calendr/Assets/de.lproj/Localizable.strings index 1fbc30d2..632d7d9f 100644 --- a/Calendr/Assets/de.lproj/Localizable.strings +++ b/Calendr/Assets/de.lproj/Localizable.strings @@ -12,6 +12,7 @@ "settings.title" = "Einstellungen"; "settings.tab.general" = "Allgemein"; "settings.tab.calendars" = "Kalender"; +"settings.tab.keyboard" = "Verknüpfungen"; "settings.tab.about" = "Über"; "settings.menu_bar" = "Menüleiste"; @@ -39,6 +40,24 @@ "settings.transparency" = "Transparent"; +"settings.keyboard.local_shortcuts.title" = "Lokale Tastenkombinationen"; +"settings.keyboard.local_shortcuts.prev_date" = "Vorheriges Datum"; +"settings.keyboard.local_shortcuts.next_date" = "Nächstes Datum"; +"settings.keyboard.local_shortcuts.prev_week" = "Vorherige Woche"; +"settings.keyboard.local_shortcuts.next_week" = "Nächste Woche"; +"settings.keyboard.local_shortcuts.prev_month" = "Vorheriger Monat"; +"settings.keyboard.local_shortcuts.next_month" = "Nächster Monat"; +"settings.keyboard.local_shortcuts.curr_date" = "Aktuelles Datum"; +"settings.keyboard.local_shortcuts.open_date" = "Ausgewähltes Datum öffnen"; +"settings.keyboard.local_shortcuts.pin_calendar" = "Kalender anheften"; + +"settings.keyboard.global_shortcuts.title" = "Globale Tastenkombinationen"; +"settings.keyboard.global_shortcuts.open_calendar" = "Kalender öffnen"; +"settings.keyboard.global_shortcuts.open_next_event" = "Nächstes Ereignis öffnen"; +"settings.keyboard.global_shortcuts.open_next_event_options" = "Optionen für nächstes Ereignis"; +"settings.keyboard.global_shortcuts.open_next_reminder" = "Nächste Erinnerung öffnen"; +"settings.keyboard.global_shortcuts.open_next_reminder_options" = "Optionen für nächste Erinnerung"; + "formatter.date.all_day" = "Ganztägige Ereignisse"; "formatter.date.today" = "Heute"; "formatter.date.relative.in" = "in %@"; diff --git a/Calendr/Assets/en.lproj/Localizable.strings b/Calendr/Assets/en.lproj/Localizable.strings index 9be302a5..c74e0adf 100644 --- a/Calendr/Assets/en.lproj/Localizable.strings +++ b/Calendr/Assets/en.lproj/Localizable.strings @@ -12,6 +12,7 @@ "settings.title" = "Preferences"; "settings.tab.general" = "General"; "settings.tab.calendars" = "Calendars"; +"settings.tab.keyboard" = "Shortcuts"; "settings.tab.about" = "About"; "settings.menu_bar" = "Menu Bar"; @@ -39,6 +40,24 @@ "settings.transparency" = "Transparency"; +"settings.keyboard.local_shortcuts.title" = "Local shortcuts"; +"settings.keyboard.local_shortcuts.prev_date" = "Previous date"; +"settings.keyboard.local_shortcuts.next_date" = "Next date"; +"settings.keyboard.local_shortcuts.prev_week" = "Previous week"; +"settings.keyboard.local_shortcuts.next_week" = "Next week"; +"settings.keyboard.local_shortcuts.prev_month" = "Previous month"; +"settings.keyboard.local_shortcuts.next_month" = "Next month"; +"settings.keyboard.local_shortcuts.curr_date" = "Current date"; +"settings.keyboard.local_shortcuts.open_date" = "Open selected date"; +"settings.keyboard.local_shortcuts.pin_calendar" = "Pin calendar"; + +"settings.keyboard.global_shortcuts.title" = "Global shortcuts"; +"settings.keyboard.global_shortcuts.open_calendar" = "Open calendar"; +"settings.keyboard.global_shortcuts.open_next_event" = "Open next event"; +"settings.keyboard.global_shortcuts.open_next_event_options" = "Next event options"; +"settings.keyboard.global_shortcuts.open_next_reminder" = "Open next reminder"; +"settings.keyboard.global_shortcuts.open_next_reminder_options" = "Next reminder options"; + "formatter.date.all_day" = "All day"; "formatter.date.today" = "Today"; "formatter.date.relative.in" = "in %@"; diff --git a/Calendr/Assets/es.lproj/Localizable.strings b/Calendr/Assets/es.lproj/Localizable.strings index 03ea0796..0af9a40c 100644 --- a/Calendr/Assets/es.lproj/Localizable.strings +++ b/Calendr/Assets/es.lproj/Localizable.strings @@ -12,6 +12,7 @@ "settings.title" = "Preferencias"; "settings.tab.general" = "General"; "settings.tab.calendars" = "Calendarios"; +"settings.tab.keyboard" = "Atajos"; "settings.tab.about" = "Acerca de"; "settings.menu_bar" = "Barra de menús"; @@ -39,6 +40,24 @@ "settings.transparency" = "Transparencia"; +"settings.keyboard.local_shortcuts.title" = "Atajos locales"; +"settings.keyboard.local_shortcuts.prev_date" = "Fecha anterior"; +"settings.keyboard.local_shortcuts.next_date" = "Próxima fecha"; +"settings.keyboard.local_shortcuts.prev_week" = "Semana anterior"; +"settings.keyboard.local_shortcuts.next_week" = "Próxima semana"; +"settings.keyboard.local_shortcuts.prev_month" = "Mes anterior"; +"settings.keyboard.local_shortcuts.next_month" = "Próximo mes"; +"settings.keyboard.local_shortcuts.curr_date" = "Fecha actual"; +"settings.keyboard.local_shortcuts.open_date" = "Abrir fecha seleccionada"; +"settings.keyboard.local_shortcuts.pin_calendar" = "Fijar calendario"; + +"settings.keyboard.global_shortcuts.title" = "Atajos globales"; +"settings.keyboard.global_shortcuts.open_calendar" = "Abrir calendario"; +"settings.keyboard.global_shortcuts.open_next_event" = "Abrir próximo evento"; +"settings.keyboard.global_shortcuts.open_next_event_options" = "Opciones del próximo evento"; +"settings.keyboard.global_shortcuts.open_next_reminder" = "Abrir próximo recordatorio"; +"settings.keyboard.global_shortcuts.open_next_reminder_options" = "Opciones del próximo recordatorio"; + "formatter.date.all_day" = "Todo el día"; "formatter.date.today" = "Hoy"; "formatter.date.relative.in" = "en %@"; diff --git a/Calendr/Assets/fr.lproj/Localizable.strings b/Calendr/Assets/fr.lproj/Localizable.strings index e8b66088..9e3419c7 100644 --- a/Calendr/Assets/fr.lproj/Localizable.strings +++ b/Calendr/Assets/fr.lproj/Localizable.strings @@ -12,6 +12,7 @@ "settings.title" = "Préférences"; "settings.tab.general" = "Général"; "settings.tab.calendars" = "Calendriers"; +"settings.tab.keyboard" = "Raccourcis"; "settings.tab.about" = "À propos"; "settings.menu_bar" = "Barre de Menus"; @@ -39,6 +40,24 @@ "settings.transparency" = "Transparence"; +"settings.keyboard.local_shortcuts.title" = "Raccourcis locaux"; +"settings.keyboard.local_shortcuts.prev_date" = "Date précédente"; +"settings.keyboard.local_shortcuts.next_date" = "Date suivante"; +"settings.keyboard.local_shortcuts.prev_week" = "Semaine précédente"; +"settings.keyboard.local_shortcuts.next_week" = "Semaine prochaine"; +"settings.keyboard.local_shortcuts.prev_month" = "Mois précédent"; +"settings.keyboard.local_shortcuts.next_month" = "Mois prochain"; +"settings.keyboard.local_shortcuts.curr_date" = "Date actuelle"; +"settings.keyboard.local_shortcuts.open_date" = "Ouvrir la date sélectionnée"; +"settings.keyboard.local_shortcuts.pin_calendar" = "Épingler le calendrier"; + +"settings.keyboard.global_shortcuts.title" = "Raccourcis globaux"; +"settings.keyboard.global_shortcuts.open_calendar" = "Ouvrir le calendrier"; +"settings.keyboard.global_shortcuts.open_next_event" = "Ouvrir le prochain événement"; +"settings.keyboard.global_shortcuts.open_next_event_options" = "Options du prochain événement"; +"settings.keyboard.global_shortcuts.open_next_reminder" = "Ouvrir le prochain rappel"; +"settings.keyboard.global_shortcuts.open_next_reminder_options" = "Options du prochain rappel"; + "formatter.date.all_day" = "Toute la journée"; "formatter.date.today" = "Aujourd'hui"; "formatter.date.relative.in" = "dans %@"; diff --git a/Calendr/Assets/it.lproj/Localizable.strings b/Calendr/Assets/it.lproj/Localizable.strings index d2f3a5ee..8ea4d7c2 100644 --- a/Calendr/Assets/it.lproj/Localizable.strings +++ b/Calendr/Assets/it.lproj/Localizable.strings @@ -12,6 +12,7 @@ "settings.title" = "Preferenze"; "settings.tab.general" = "Generali"; "settings.tab.calendars" = "Calendari"; +"settings.tab.keyboard" = "Scorciatoie"; "settings.tab.about" = "Informazioni"; "settings.menu_bar" = "Barra dei menu"; @@ -39,6 +40,24 @@ "settings.transparency" = "Transparenza"; +"settings.keyboard.local_shortcuts.title" = "Scorciatoie locali"; +"settings.keyboard.local_shortcuts.prev_date" = "Data precedente"; +"settings.keyboard.local_shortcuts.next_date" = "Data successiva"; +"settings.keyboard.local_shortcuts.prev_week" = "Settimana precedente"; +"settings.keyboard.local_shortcuts.next_week" = "Prossima settimana"; +"settings.keyboard.local_shortcuts.prev_month" = "Mese precedente"; +"settings.keyboard.local_shortcuts.next_month" = "Mese prossimo"; +"settings.keyboard.local_shortcuts.curr_date" = "Data corrente"; +"settings.keyboard.local_shortcuts.open_date" = "Aprire la data selezionata"; +"settings.keyboard.local_shortcuts.pin_calendar" = "Aggiungi calendario"; + +"settings.keyboard.global_shortcuts.title" = "Scorciatoie globali"; +"settings.keyboard.global_shortcuts.open_calendar" = "Aprire il calendario"; +"settings.keyboard.global_shortcuts.open_next_event" = "Aprire il prossimo evento"; +"settings.keyboard.global_shortcuts.open_next_event_options" = "Opzioni del prossimo evento"; +"settings.keyboard.global_shortcuts.open_next_reminder" = "Aprire il prossimo promemoria"; +"settings.keyboard.global_shortcuts.open_next_reminder_options" = "Opzioni del prossimo promemoria"; + "formatter.date.all_day" = "Tutto il giorno"; "formatter.date.today" = "Oggi"; "formatter.date.relative.in" = "in %@"; diff --git a/Calendr/Assets/pt.lproj/Localizable.strings b/Calendr/Assets/pt.lproj/Localizable.strings index 63fbb3b1..913d37b5 100644 --- a/Calendr/Assets/pt.lproj/Localizable.strings +++ b/Calendr/Assets/pt.lproj/Localizable.strings @@ -12,6 +12,7 @@ "settings.title" = "Preferências"; "settings.tab.general" = "Geral"; "settings.tab.calendars" = "Calendários"; +"settings.tab.keyboard" = "Atalhos"; "settings.tab.about" = "Sobre"; "settings.menu_bar" = "Barra de Menus"; @@ -39,6 +40,24 @@ "settings.transparency" = "Transparência"; +"settings.keyboard.local_shortcuts.title" = "Atalhos locais"; +"settings.keyboard.local_shortcuts.prev_date" = "Data anterior"; +"settings.keyboard.local_shortcuts.next_date" = "Próxima data"; +"settings.keyboard.local_shortcuts.prev_week" = "Semana anterior"; +"settings.keyboard.local_shortcuts.next_week" = "Próxima semana"; +"settings.keyboard.local_shortcuts.prev_month" = "Mês anterior"; +"settings.keyboard.local_shortcuts.next_month" = "Próximo mês"; +"settings.keyboard.local_shortcuts.curr_date" = "Data atual"; +"settings.keyboard.local_shortcuts.open_date" = "Abrir data selecionada"; +"settings.keyboard.local_shortcuts.pin_calendar" = "Fixar calendário"; + +"settings.keyboard.global_shortcuts.title" = "Atalhos globais"; +"settings.keyboard.global_shortcuts.open_calendar" = "Abrir calendário"; +"settings.keyboard.global_shortcuts.open_next_event" = "Abrir próximo evento"; +"settings.keyboard.global_shortcuts.open_next_event_options" = "Opções do próximo evento"; +"settings.keyboard.global_shortcuts.open_next_reminder" = "Abrir próximo lembrete"; +"settings.keyboard.global_shortcuts.open_next_reminder_options" = "Opções do próximo lembrete"; + "formatter.date.all_day" = "Dia inteiro"; "formatter.date.today" = "Hoje"; "formatter.date.relative.in" = "em %@"; diff --git a/Calendr/Assets/sk.lproj/Localizable.strings b/Calendr/Assets/sk.lproj/Localizable.strings index cb4bbb35..0ad77bbb 100644 --- a/Calendr/Assets/sk.lproj/Localizable.strings +++ b/Calendr/Assets/sk.lproj/Localizable.strings @@ -12,6 +12,7 @@ "settings.title" = "Nastavenia"; "settings.tab.general" = "Všeobecné"; "settings.tab.calendars" = "Kalendáre"; +"settings.tab.keyboard" = "Skratky"; "settings.tab.about" = "O aplikácii"; "settings.menu_bar" = "Lišta"; @@ -39,6 +40,24 @@ "settings.transparency" = "Priehľadnosť"; +"settings.keyboard.local_shortcuts.title" = "Lokálne skratky"; +"settings.keyboard.local_shortcuts.prev_date" = "Predchádzajúci dátum"; +"settings.keyboard.local_shortcuts.next_date" = "Nasledujúci dátum"; +"settings.keyboard.local_shortcuts.prev_week" = "Predchádzajúci týždeň"; +"settings.keyboard.local_shortcuts.next_week" = "Nasledujúci týždeň"; +"settings.keyboard.local_shortcuts.prev_month" = "Predchádzajúci mesiac"; +"settings.keyboard.local_shortcuts.next_month" = "Nasledujúci mesiac"; +"settings.keyboard.local_shortcuts.curr_date" = "Aktuálny dátum"; +"settings.keyboard.local_shortcuts.open_date" = "Otvoriť vybraný dátum"; +"settings.keyboard.local_shortcuts.pin_calendar" = "Pripnúť kalendár"; + +"settings.keyboard.global_shortcuts.title" = "Globálne skratky"; +"settings.keyboard.global_shortcuts.open_calendar" = "Otvoriť kalendár"; +"settings.keyboard.global_shortcuts.open_next_event" = "Otvoriť ďalšiu udalosť"; +"settings.keyboard.global_shortcuts.open_next_event_options" = "Možnosti ďalšej udalosti"; +"settings.keyboard.global_shortcuts.open_next_reminder" = "Otvoriť ďalšiu pripomienku"; +"settings.keyboard.global_shortcuts.open_next_reminder_options" = "Možnosti ďalšej pripomienky"; + "formatter.date.all_day" = "Celý deň"; "formatter.date.today" = "Dnes"; "formatter.date.relative.in" = "za %@"; diff --git a/Calendr/Assets/sv.lproj/Localizable.strings b/Calendr/Assets/sv.lproj/Localizable.strings index feabf840..29156c97 100644 --- a/Calendr/Assets/sv.lproj/Localizable.strings +++ b/Calendr/Assets/sv.lproj/Localizable.strings @@ -12,6 +12,7 @@ "settings.title" = "Inställningar"; "settings.tab.general" = "Allmänt"; "settings.tab.calendars" = "Kalendrar"; +"settings.tab.keyboard" = "Genvägar"; "settings.tab.about" = "Om"; "settings.menu_bar" = "Menyrad"; @@ -39,6 +40,24 @@ "settings.transparency" = "Transparens"; +"settings.keyboard.local_shortcuts.title" = "Lokala genvägar"; +"settings.keyboard.local_shortcuts.prev_date" = "Föregående datum"; +"settings.keyboard.local_shortcuts.next_date" = "Nästa datum"; +"settings.keyboard.local_shortcuts.prev_week" = "Föregående vecka"; +"settings.keyboard.local_shortcuts.next_week" = "Nästa vecka"; +"settings.keyboard.local_shortcuts.prev_month" = "Föregående månad"; +"settings.keyboard.local_shortcuts.next_month" = "Nästa månad"; +"settings.keyboard.local_shortcuts.curr_date" = "Aktuellt datum"; +"settings.keyboard.local_shortcuts.open_date" = "Öppna valt datum"; +"settings.keyboard.local_shortcuts.pin_calendar" = "Fäst kalender"; + +"settings.keyboard.global_shortcuts.title" = "Globala genvägar"; +"settings.keyboard.global_shortcuts.open_calendar" = "Öppna kalender"; +"settings.keyboard.global_shortcuts.open_next_event" = "Öppna nästa händelse"; +"settings.keyboard.global_shortcuts.open_next_event_options" = "Nästa händelsealternativ"; +"settings.keyboard.global_shortcuts.open_next_reminder" = "Öppna nästa påminnelse"; +"settings.keyboard.global_shortcuts.open_next_reminder_options" = "Nästa påminnelsealternativ"; + "formatter.date.all_day" = "Heldag"; "formatter.date.today" = "Idag"; "formatter.date.relative.in" = "om %@"; diff --git a/Calendr/Assets/zh-Hans.lproj/Localizable.strings b/Calendr/Assets/zh-Hans.lproj/Localizable.strings index bf6f7fbd..4c1a53ea 100644 --- a/Calendr/Assets/zh-Hans.lproj/Localizable.strings +++ b/Calendr/Assets/zh-Hans.lproj/Localizable.strings @@ -12,6 +12,7 @@ "settings.title" = "设置"; "settings.tab.general" = "通用"; "settings.tab.calendars" = "日历"; +"settings.tab.keyboard" = "快捷方式"; "settings.tab.about" = "关于"; "settings.menu_bar" = "菜单栏"; @@ -39,6 +40,24 @@ "settings.transparency" = "透明度"; +"settings.keyboard.local_shortcuts.title" = "本地快捷键"; +"settings.keyboard.local_shortcuts.prev_date" = "前一天"; +"settings.keyboard.local_shortcuts.next_date" = "后一天"; +"settings.keyboard.local_shortcuts.prev_week" = "前一周"; +"settings.keyboard.local_shortcuts.next_week" = "后一周"; +"settings.keyboard.local_shortcuts.prev_month" = "前一个月"; +"settings.keyboard.local_shortcuts.next_month" = "后一个月"; +"settings.keyboard.local_shortcuts.curr_date" = "当前日期"; +"settings.keyboard.local_shortcuts.open_date" = "打开选定日期"; +"settings.keyboard.local_shortcuts.pin_calendar" = "固定日历"; + +"settings.keyboard.global_shortcuts.title" = "全局快捷键"; +"settings.keyboard.global_shortcuts.open_calendar" = "打开日历"; +"settings.keyboard.global_shortcuts.open_next_event" = "打开下一个事件"; +"settings.keyboard.global_shortcuts.open_next_event_options" = "下一个事件选项"; +"settings.keyboard.global_shortcuts.open_next_reminder" = "打开下一个提醒"; +"settings.keyboard.global_shortcuts.open_next_reminder_options" = "下一个提醒选项"; + "formatter.date.all_day" = "全天"; "formatter.date.today" = "今日"; "formatter.date.relative.in" = "在 %@ 内"; diff --git a/Calendr/Components/Popover.swift b/Calendr/Components/Popover.swift index 8d12e0f3..0555ddf1 100644 --- a/Calendr/Components/Popover.swift +++ b/Calendr/Components/Popover.swift @@ -45,7 +45,7 @@ class Popover: NSObject, PopoverWindowDelegate { } if single { - closeAll() + Popover.closeAll() } guard let contentViewController else { return } @@ -82,7 +82,7 @@ class Popover: NSObject, PopoverWindowDelegate { return NSMouseInRect(NSEvent.mouseLocation, window.frame, false) } - private func closeAll() { + static func closeAll() { for popover in popovers { popover.window?.performClose(nil) } @@ -95,7 +95,7 @@ class Popover: NSObject, PopoverWindowDelegate { } guard NSApp.isActive else { - return closeAll() + return Popover.closeAll() } guard !isMouseInside else { diff --git a/Calendr/Extensions/NSMenu.swift b/Calendr/Extensions/NSMenu.swift new file mode 100644 index 00000000..b67fca44 --- /dev/null +++ b/Calendr/Extensions/NSMenu.swift @@ -0,0 +1,27 @@ +// +// NSMenu.swift +// Calendr +// +// Created by Paker on 21/07/24. +// + +import AppKit + +private var activeMenus: [NSMenu] = [] + +class NSMenu: AppKit.NSMenu { + + @discardableResult + override func popUp(positioning item: NSMenuItem?, at location: NSPoint, in view: NSView?) -> Bool { + activeMenus.append(self) + let result = super.popUp(positioning: item, at: location, in: view) + activeMenus.removeAll { $0 == self } + return result + } + + static func closeAll() { + for menu in activeMenus { + menu.cancelTracking() + } + } +} diff --git a/Calendr/Extensions/Rx+Helpers.swift b/Calendr/Extensions/Rx+Helpers.swift index ce47bedf..d61d454e 100644 --- a/Calendr/Extensions/Rx+Helpers.swift +++ b/Calendr/Extensions/Rx+Helpers.swift @@ -23,8 +23,8 @@ extension ObservableType { map { value -> Element? in value } } - func matching(_ value: Element) -> Observable where Element: Equatable { - filter { value ~= $0 } + func matching(_ values: Element...) -> Observable where Element: Equatable { + filter { values.contains($0) } } func skipNil() -> Observable where Element == T? { @@ -48,6 +48,8 @@ extension BehaviorSubject { static func pipe(value: Element) -> (output: Observable, input: AnyObserver) { { ($0.asObservable(), $0.asObserver()) }(Self.init(value: value)) } + + var value: Element { try! value() } } extension Bool { diff --git a/Calendr/Main/Keyboard.swift b/Calendr/Main/Keyboard.swift index 30ad1e30..981585ee 100644 --- a/Calendr/Main/Keyboard.swift +++ b/Calendr/Main/Keyboard.swift @@ -6,11 +6,10 @@ // import AppKit -import KeyboardShortcuts class Keyboard { - enum Key: Equatable { + indirect enum Key: Equatable { enum Arrow { case left @@ -18,24 +17,13 @@ class Keyboard { case down case up } + case enter case escape + case backspace case arrow(Arrow) - case command(Character) - - static func from(_ event: NSEvent) -> Self? { - switch event.keyCode { - case 53: return .escape - case 123: return .arrow(.left) - case 124: return .arrow(.right) - case 125: return .arrow(.down) - case 126: return .arrow(.up) - default: - if event.modifierFlags.contains(.command), let char = event.characters?.first { - return .command(char) - } - } - return nil - } + case char(Character) + case command(Key) + case option(Key) } private var eventMonitor: Any? @@ -58,6 +46,40 @@ class Keyboard { } } -extension KeyboardShortcuts.Name { - static let showMainPopover = Self("showMainPopover") +private extension Keyboard.Key { + + static func from(_ event: NSEvent) -> Self? { + + var key: Self? + + switch event.keyCode { + case 36: key = .enter + case 51: key = .backspace + case 53: key = .escape + case 123: key = .arrow(.left) + case 124: key = .arrow(.right) + case 125: key = .arrow(.down) + case 126: key = .arrow(.up) + default: + if let char = event.charactersIgnoringModifiers?.lowercased().first { + key = .char(char) + } + } + + guard var key else { + return nil + } + + let mods = event.modifierFlags + + if mods.contains(.option) { + key = .option(key) + } + + if mods.contains(.command) { + key = .command(key) + } + + return key + } } diff --git a/Calendr/Main/MainViewController.swift b/Calendr/Main/MainViewController.swift index cd50bb61..06aa472b 100644 --- a/Calendr/Main/MainViewController.swift +++ b/Calendr/Main/MainViewController.swift @@ -48,10 +48,10 @@ class MainViewController: NSViewController { private let dateClick = PublishSubject() private let dateDoubleClick = PublishSubject() private let refreshDate = PublishSubject() - private let selectedDate = PublishSubject() + private let selectedDate = BehaviorSubject(value: .now) private let isShowingDetails = BehaviorSubject(value: false) private let searchInputText = BehaviorSubject(value: "") - private let arrowSubject = PublishSubject() + private let navigationSubject = PublishSubject() // Properties private let keyboard = Keyboard() @@ -59,6 +59,7 @@ class MainViewController: NSViewController { private let calendarService: CalendarServiceProviding private let dateProvider: DateProviding private let screenProvider: ScreenProviding + private let userDefaults: UserDefaults private let notificationCenter: NotificationCenter private var heightConstraint: NSLayoutConstraint? @@ -78,6 +79,7 @@ class MainViewController: NSViewController { self.calendarService = calendarService self.dateProvider = dateProvider self.screenProvider = screenProvider + self.userDefaults = userDefaults self.notificationCenter = notificationCenter mainStatusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) @@ -571,33 +573,60 @@ class MainViewController: NSViewController { .disposed(by: disposeBag) clickHandler.rightClick - .compactMap { viewModel.makeContextMenuViewModel() } - .bind { makeContextMenu($0).show(in: statusBarButton) } + .flatMapFirst { _ -> Observable in + guard let vm = viewModel.makeContextMenuViewModel() else { return .void() } + let menu = makeContextMenu(vm) + menu.show(in: statusBarButton) + return menu.rx.deallocated + } + .subscribe() .disposed(by: disposeBag) statusBarButton.setUpClickHandler(clickHandler) } private func setUpKeyboard() { + setUpLocalShortcuts() + setUpGlobalShortcuts() + } + + private func setUpLocalShortcuts() { keyboard.listen(in: self) { [weak self] event, key -> NSEvent? in guard let self else { return event } switch key { - case .command("q"): + case .command(.char("q")): NSApp.terminate(nil) - case .command("f"): - showSearchInput() - - case .command(","): + case .command(.char(",")): openSettings() + case .command(.char("p")): + pinBtn.performClick(nil) + + case .command(.char("f")): + showSearchInput() + case .escape where searchInput.hasFocus: hideSearchInput() - case .arrow(let arrow) where !searchInput.hasFocus: - arrowSubject.onNext(arrow) + case _ where searchInput.hasFocus: + return event + + // ↓ Search input not focused ↓ // + + case .option(.char("w")): + userDefaults.showWeekNumbers.toggle() + + case .option(.char("d")): + userDefaults.showDeclinedEvents.toggle() + + case .arrow, .command(.arrow), .backspace: + navigationSubject.onNext(key) + + case .enter: + dateDoubleClick.onNext(selectedDate.value) default: return event @@ -605,16 +634,44 @@ class MainViewController: NSViewController { return .none } + } + + private func closeModals() { + NSMenu.closeAll() + Popover.closeAll() + } + + private func setUpGlobalShortcuts() { - // Global shortcut KeyboardShortcuts.onKeyUp(for: .showMainPopover) { [weak self] in guard let self else { return } - if let window = view.window { - window.performClose(nil) - return - } + closeModals() mainStatusItemClickHandler.leftClick.onNext(()) } + + KeyboardShortcuts.onKeyUp(for: .showNextEventPopover) { [weak self] in + guard let self else { return } + closeModals() + eventStatusItemClickHandler.leftClick.onNext(()) + } + + KeyboardShortcuts.onKeyUp(for: .showNextEventOptions) { [weak self] in + guard let self else { return } + closeModals() + eventStatusItemClickHandler.rightClick.onNext(()) + } + + KeyboardShortcuts.onKeyUp(for: .showNextReminderPopover) { [weak self] in + guard let self else { return } + closeModals() + reminderStatusItemClickHandler.leftClick.onNext(()) + } + + KeyboardShortcuts.onKeyUp(for: .showNextReminderOptions) { [weak self] in + guard let self else { return } + closeModals() + reminderStatusItemClickHandler.rightClick.onNext(()) + } } // MARK: - Factories @@ -675,22 +732,27 @@ class MainViewController: NSViewController { private func makeDateSelector() -> DateSelector { - let keyLeft = arrowSubject.matching(.left).void() - let keyRight = arrowSubject.matching(.right).void() - let keyDown = arrowSubject.matching(.down).void() - let keyUp = arrowSubject.matching(.up).void() + let backspace = navigationSubject.matching(.backspace).void() + + let keyLeft = navigationSubject.matching(.arrow(.left)).void() + let keyRight = navigationSubject.matching(.arrow(.right)).void() + let keyDown = navigationSubject.matching(.arrow(.down)).void() + let keyUp = navigationSubject.matching(.arrow(.up)).void() + + let cmdUpLeft = navigationSubject.matching(.command(.arrow(.up)), .command(.arrow(.left))).void() + let cmdDownRight = navigationSubject.matching(.command(.arrow(.down)), .command(.arrow(.right))).void() let dateSelector = DateSelector( calendar: dateProvider.calendar, initial: refreshDate.map { [dateProvider] in dateProvider.now }, selected: selectedDate, - reset: resetBtn.rx.tap.asObservable(), + reset: .merge(resetBtn.rx.tap.asObservable(), backspace), prevDay: keyLeft, nextDay: keyRight, prevWeek: keyUp, nextWeek: keyDown, - prevMonth: prevBtn.rx.tap.asObservable(), - nextMonth: nextBtn.rx.tap.asObservable() + prevMonth: .merge(prevBtn.rx.tap.asObservable(), cmdUpLeft), + nextMonth: .merge(nextBtn.rx.tap.asObservable(), cmdDownRight) ) return dateSelector @@ -708,10 +770,13 @@ private enum Constants { } } - private extension NSMenu { + func show(in view: NSView) { - popUp(positioning: nil, at: .init(x: 0, y: view.frame.height + 5), in: view) + DispatchQueue.main.async { + // this is a blocking operation + self.popUp(positioning: nil, at: .init(x: 0, y: view.frame.height + 5), in: view) + } } } diff --git a/Calendr/Settings/GeneralSettingsViewController.swift b/Calendr/Settings/GeneralSettingsViewController.swift index 0c0ef5ef..22d682b9 100644 --- a/Calendr/Settings/GeneralSettingsViewController.swift +++ b/Calendr/Settings/GeneralSettingsViewController.swift @@ -7,11 +7,10 @@ import Cocoa import RxSwift -import KeyboardShortcuts -class GeneralSettingsViewController: NSViewController { +class GeneralSettingsViewController: NSViewController, SettingsUI { - private let disposeBag = DisposeBag() + let disposeBag = DisposeBag() private let viewModel: SettingsViewModel @@ -23,7 +22,6 @@ class GeneralSettingsViewController: NSViewController { private let iconStyleDropdown = Dropdown() private let dateFormatDropdown = Dropdown() private let dateFormatTextField = NSTextField() - private let shortcutRecorder = KeyboardShortcuts.RecorderCocoa(for: .showMainPopover) // Next Event private let showNextEventCheckbox = Checkbox(title: Strings.Settings.MenuBar.showNextEvent) @@ -123,16 +121,12 @@ class GeneralSettingsViewController: NSViewController { ]) .with(orientation: .vertical) - shortcutRecorder.setContentHuggingPriority(.fittingSizeCompression, for: .horizontal) - return NSStackView(views: [ autoLaunchCheckbox, iconStyle, dateFormat, - showMenuBarBackgroundCheckbox, - shortcutRecorder + showMenuBarBackgroundCheckbox ]) - .with(spacing: Constants.contentSpacing) .with(orientation: .vertical) }() @@ -271,31 +265,6 @@ class GeneralSettingsViewController: NSViewController { NSStackView(views: [finishedLabel, .spacer, fadePastEventsRadio, hidePastEventsRadio]) }() - private func makeSection(title: String, content: NSView) -> NSView { - - let label = Label(text: title, font: .systemFont(ofSize: 13, weight: .semibold)) - - let divider: NSView = .spacer(height: 1) - divider.wantsLayer = true - - divider.rx.updateLayer - .map { NSColor.tertiaryLabelColor.effectiveCGColor } - .bind(to: divider.layer!.rx.backgroundColor) - .disposed(by: disposeBag) - - let stackView = NSStackView(views: [ - label, - divider, - NSStackView(views: [.dummy, content, .dummy]) - ]) - .with(orientation: .vertical) - .with(alignment: .left) - .with(spacing: 6) - .with(spacing: 12, after: divider) - - return stackView - } - private func setUpBindings() { setUpMenuBar() setUpNextEvent() @@ -587,8 +556,3 @@ private extension NSSlider { return slider } } - -private enum Constants { - - static let contentSpacing: CGFloat = 16 -} diff --git a/Calendr/Settings/KeyboardViewController.swift b/Calendr/Settings/KeyboardViewController.swift new file mode 100644 index 00000000..079242d8 --- /dev/null +++ b/Calendr/Settings/KeyboardViewController.swift @@ -0,0 +1,118 @@ +// +// KeyboardViewController.swift +// Calendr +// +// Created by Paker on 20/07/24. +// + +import Cocoa +import RxSwift +import KeyboardShortcuts + +class KeyboardViewController: NSViewController, SettingsUI { + + let disposeBag = DisposeBag() + + typealias LocalShortcuts = Strings.Settings.Keyboard.LocalShortcuts + typealias GlobalShortcuts = Strings.Settings.Keyboard.GlobalShortcuts + + override func loadView() { + + view = NSView() + + let stackView = NSStackView(views: [ + makeSection(title: LocalShortcuts.title, content: localContent), + makeSection(title: GlobalShortcuts.title, content: globalContent), + ]) + .with(spacing: Constants.contentSpacing) + .with(orientation: .vertical) + + view.addSubview(stackView) + + stackView.edges(to: view, insets: .init(bottom: 1)) + } + + private lazy var localContent: NSView = { + + return NSStackView(views: [ + makeLocalShortcut(text: LocalShortcuts.prevDate, keys: "←"), + makeLocalShortcut(text: LocalShortcuts.nextDate, keys: "→"), + makeLocalShortcut(text: LocalShortcuts.prevWeek, keys: "↑"), + makeLocalShortcut(text: LocalShortcuts.nextWeek, keys: "↓"), + makeLocalShortcut(text: LocalShortcuts.prevMonth, keys: "⌘ ←", "⌘ ↑"), + makeLocalShortcut(text: LocalShortcuts.nextMonth, keys: "⌘ →", "⌘ ↓"), + makeLocalShortcut(text: LocalShortcuts.currDate, keys: "⌫"), + makeLocalShortcut(text: LocalShortcuts.openDate, keys: "↵"), + makeLocalShortcut(text: LocalShortcuts.showWeekNumbers, keys: "⌥ W"), + makeLocalShortcut(text: LocalShortcuts.showDeclinedEvents, keys: "⌥ D"), + makeLocalShortcut(text: LocalShortcuts.pinCalendar, keys: "⌘ P"), + makeLocalShortcut(text: LocalShortcuts.settings, keys: "⌘ ,"), + makeLocalShortcut(text: LocalShortcuts.quit, keys: "⌘ Q"), + ]) + .with(orientation: .vertical) + }() + + private func makeLocalShortcut(text: String, keys: String...) -> NSView { + + let keysView = NSStackView(views: keys.map(makeCommand)) + keysView.setContentHuggingPriority(.required, for: .horizontal) + + return NSStackView(views: [makeLabel(text: text), keysView]) + } + + private lazy var globalContent: NSView = { + + return NSStackView(views: [ + makeGlobalShortcut(text: GlobalShortcuts.openCalendar, for: .showMainPopover), + makeGlobalShortcut(text: GlobalShortcuts.openNextEvent, for: .showNextEventPopover), + makeGlobalShortcut(text: GlobalShortcuts.openNextEventOptions, for: .showNextEventOptions), + makeGlobalShortcut(text: GlobalShortcuts.openNextReminder, for: .showNextReminderPopover), + makeGlobalShortcut(text: GlobalShortcuts.openNextReminderOptions, for: .showNextReminderOptions) + ]) + .with(orientation: .vertical) + }() + + private func makeGlobalShortcut(text: String, for shortcut: KeyboardShortcuts.Name) -> NSView { + + return NSStackView(views: [makeLabel(text: text), makeRecorder(for: shortcut)]) + } + + private func makeLabel(text: String) -> NSView { + + Label(text: text, font: .systemFont(ofSize: 13)) + } + + private func makeCommand(text: String) -> NSView { + + let charViews = text.split(separator: " ").map { + Label(text: String($0), font: .systemFont(ofSize: 13, weight: .regular), align: .center).with(width: 16) + } + + return NSStackView(views: charViews).with(spacing: 0) + } + + private func makeRecorder(for name: KeyboardShortcuts.Name) -> NSView { + + let shortcutRecorder = KeyboardShortcuts.RecorderCocoa(for: name) + + shortcutRecorder.setContentHuggingPriority(.required, for: .horizontal) + + return shortcutRecorder + } +} + +private extension Strings.Settings.Keyboard.LocalShortcuts { + + static let showWeekNumbers = Strings.Settings.Calendar.showWeekNumbers + static let showDeclinedEvents = Strings.Settings.Calendar.showDeclinedEvents + static let settings = Strings.Settings.title + static let quit = Strings.quit +} + +extension KeyboardShortcuts.Name { + static let showMainPopover = Self("showMainPopover") + static let showNextEventPopover = Self("showNextEventPopover") + static let showNextEventOptions = Self("showNextEventOptions") + static let showNextReminderPopover = Self("showNextReminderPopover") + static let showNextReminderOptions = Self("showNextReminderOptions") +} diff --git a/Calendr/Settings/SettingsUI.swift b/Calendr/Settings/SettingsUI.swift new file mode 100644 index 00000000..73424919 --- /dev/null +++ b/Calendr/Settings/SettingsUI.swift @@ -0,0 +1,48 @@ +// +// SettingsUI.swift +// Calendr +// +// Created by Paker on 20/07/24. +// + +import Cocoa +import RxSwift + +protocol SettingsUI { + typealias Constants = SettingsUIConstants + + var disposeBag: DisposeBag { get } +} + +extension SettingsUI { + + func makeSection(title: String, content: NSView) -> NSView { + + let label = Label(text: title, font: .systemFont(ofSize: 13, weight: .semibold)) + + let divider: NSView = .spacer(height: 1) + divider.wantsLayer = true + + divider.rx.updateLayer + .map { NSColor.tertiaryLabelColor.effectiveCGColor } + .bind(to: divider.layer!.rx.backgroundColor) + .disposed(by: disposeBag) + + let stackView = NSStackView(views: [ + label, + divider, + NSStackView(views: [.dummy, content, .dummy]) + ]) + .with(orientation: .vertical) + .with(alignment: .left) + .with(spacing: 6) + .with(spacing: 12, after: divider) + + return stackView + } +} + +enum SettingsUIConstants { + + static let contentSpacing: CGFloat = 24 +} diff --git a/Calendr/Settings/SettingsViewController.swift b/Calendr/Settings/SettingsViewController.swift index 10bb28ba..0358ded5 100644 --- a/Calendr/Settings/SettingsViewController.swift +++ b/Calendr/Settings/SettingsViewController.swift @@ -32,6 +32,7 @@ class SettingsViewController: NSTabViewController { let calendars = NSTabViewItem( viewController: CalendarPickerViewController(viewModel: calendarsViewModel, configuration: .settings) ) + let keyboard = NSTabViewItem(viewController: KeyboardViewController()) let about = NSTabViewItem(viewController: AboutViewController()) general.label = Strings.Settings.Tab.general @@ -40,10 +41,13 @@ class SettingsViewController: NSTabViewController { calendars.label = Strings.Settings.Tab.calendars calendars.image = Icons.Settings.calendars + keyboard.label = Strings.Settings.Tab.keyboard + keyboard.image = Icons.Settings.keyboard + about.label = Strings.Settings.Tab.about about.image = Icons.Settings.about - tabViewItems = [general, calendars, about] + tabViewItems = [general, calendars, keyboard, about] setUpAccessibility() @@ -169,7 +173,7 @@ private func sizeWithPadding(_ size: NSSize) -> NSSize { private enum Constants { static let padding: CGFloat = 24 - static let minWidth: CGFloat = 180 + static let minWidth: CGFloat = 250 } // MARK: - Accessibility