Skip to content

Commit

Permalink
Add ability to skip next event
Browse files Browse the repository at this point in the history
  • Loading branch information
pakerwreah committed Nov 11, 2023
1 parent 2b13e00 commit 8ea5134
Show file tree
Hide file tree
Showing 22 changed files with 364 additions and 153 deletions.
4 changes: 2 additions & 2 deletions Calendr.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1263,7 +1263,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.10.6;
MARKETING_VERSION = 1.10.7;
PRODUCT_BUNDLE_IDENTIFIER = br.paker.Calendr;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Calendr/Config/Calendr-Bridging-Header.h";
Expand All @@ -1289,7 +1289,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.10.6;
MARKETING_VERSION = 1.10.7;
PRODUCT_BUNDLE_IDENTIFIER = br.paker.Calendr;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Calendr/Config/Calendr-Bridging-Header.h";
Expand Down
2 changes: 2 additions & 0 deletions Calendr/Assets/Generated/Strings.generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ internal enum Strings {
internal static let maybe = Strings.tr("Localizable", "event_action.maybe", fallback: "Maybe")
/// Open
internal static let `open` = Strings.tr("Localizable", "event_action.open", fallback: "Open")
/// Skip
internal static let skip = Strings.tr("Localizable", "event_action.skip", fallback: "Skip")
}
internal enum EventDetails {
internal enum Participant {
Expand Down
1 change: 1 addition & 0 deletions Calendr/Assets/Icons.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ enum Icons {
static let video = NSImage(systemName: "video")
static let video_fill = NSImage(systemName: "video.fill")
static let open = NSImage(systemName: "square.and.arrow.up")
static let skip = NSImage(systemName: "forward")
}

enum Reminder {
Expand Down
1 change: 1 addition & 0 deletions Calendr/Assets/de.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"event_status.pending" = "Ausstehend";

"event_action.open" = "Öffnen";
"event_action.skip" = "Überspringen";
"event_action.accept" = "Akzeptieren";
"event_action.maybe" = "Vielleicht";
"event_action.decline" = "Ablehnen";
1 change: 1 addition & 0 deletions Calendr/Assets/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"event_status.pending" = "Pending";

"event_action.open" = "Open";
"event_action.skip" = "Skip";
"event_action.accept" = "Accept";
"event_action.maybe" = "Maybe";
"event_action.decline" = "Decline";
1 change: 1 addition & 0 deletions Calendr/Assets/es.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"event_status.pending" = "Pendiente";

"event_action.open" = "Abrir";
"event_action.skip" = "Saltar";
"event_action.accept" = "Aceptar";
"event_action.maybe" = "Quizá";
"event_action.decline" = "Rechazar";
1 change: 1 addition & 0 deletions Calendr/Assets/fr.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"event_status.pending" = "En attente";

"event_action.open" = "Ouvrir";
"event_action.skip" = "Sauter";
"event_action.accept" = "Accepter";
"event_action.maybe" = "Peut-être";
"event_action.decline" = "Refuser";
1 change: 1 addition & 0 deletions Calendr/Assets/it.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"event_status.pending" = "In attesa";

"event_action.open" = "Aprire";
"event_action.skip" = "Saltare";
"event_action.accept" = "Accetta";
"event_action.maybe" = "Forse";
"event_action.decline" = "Rifiuta";
1 change: 1 addition & 0 deletions Calendr/Assets/pt.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"event_status.pending" = "Pendente";

"event_action.open" = "Abrir";
"event_action.skip" = "Pular";
"event_action.accept" = "Aceitar";
"event_action.maybe" = "Talvez";
"event_action.decline" = "Recusar";
1 change: 1 addition & 0 deletions Calendr/Assets/zh-Hans.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"event_status.pending" = "等待中";

"event_action.open" = "打开";
"event_action.skip" = "跳过";
"event_action.accept" = "接受";
"event_action.maybe" = "也许";
"event_action.decline" = "拒绝";
55 changes: 49 additions & 6 deletions Calendr/Events/ContextMenu/ContextMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import AppKit
import RxSwift

protocol ContextMenuAction {
protocol ContextMenuAction: Equatable {
var icon: NSImage? { get }
var title: String { get }
}
Expand All @@ -17,23 +17,66 @@ extension ContextMenuAction {
var icon: NSImage? { nil }
}

enum ContextMenuViewModelItem<Action: ContextMenuAction> {
enum ContextMenuViewModelItem<Action: ContextMenuAction>: Equatable {
case separator
case action(Action)
}

extension ContextMenuViewModelItem: Equatable where Action: Equatable { }

protocol ContextMenuViewModel {
protocol ContextMenuViewModel<Action> {
associatedtype Action: ContextMenuAction
typealias ActionItem = ContextMenuViewModelItem<Action>

var items: [ActionItem] { get }
var actionCallback: Observable<Void> { get }
var actionCallback: Observable<Action> { get }

func triggerAction(_ action: Action)
}

class BaseContextMenuViewModel<Action: ContextMenuAction>: ContextMenuViewModel {

typealias ActionItem = ContextMenuViewModelItem<Action>

private(set) var items: [ActionItem] = []

private let actionCallbackObserver: AnyObserver<Action>
let actionCallback: Observable<Action>

private let disposeBag = DisposeBag()

init() {
(actionCallback, actionCallbackObserver) = PublishSubject.pipe()
}

func addSeparator() {
if !items.isEmpty {
items.append(.separator)
}
}

func addItem(_ action: Action) {
items.append(.action(action))
}

func addItems(_ actions: Action...) {
actions.forEach(addItem)
}

func triggerAction( _ action: Action) -> Observable<Void> {
fatalError("Not implemented")
}

func triggerAction(_ action: Action) {
let callback = actionCallbackObserver

triggerAction(action)
.subscribe(
onNext: { callback.onNext(action) },
onError: callback.onError
)
.disposed(by: disposeBag)
}
}

private class ContextMenuItem<Action: ContextMenuAction>: NSMenuItem {

private let value: Action
Expand Down
12 changes: 9 additions & 3 deletions Calendr/Events/ContextMenu/ContextMenuFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@

import Foundation

enum ContextMenuSource {
case list
case details
case menubar
}

enum ContextMenuFactory {

static func makeViewModel(
event: EventModel,
dateProvider: DateProviding,
calendarService: CalendarServiceProviding,
workspace: WorkspaceServiceProviding,
canOpen: Bool
source: ContextMenuSource
) -> (any ContextMenuViewModel)? {

switch event.type {
Expand All @@ -24,7 +30,7 @@ enum ContextMenuFactory {
dateProvider: dateProvider,
calendarService: calendarService,
workspace: workspace,
canOpen: canOpen
source: source
)

case .reminder:
Expand All @@ -33,7 +39,7 @@ enum ContextMenuFactory {
dateProvider: dateProvider,
calendarService: calendarService,
workspace: workspace,
canOpen: canOpen
source: source
)
}
}
Expand Down
88 changes: 46 additions & 42 deletions Calendr/Events/ContextMenu/EventOptionsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,58 +8,59 @@
import AppKit
import RxSwift

enum EventAction {
enum EventAction: Equatable {
case open
case skip
case status(EventStatusAction)
}

enum EventStatusAction: Equatable {
case accept
case maybe
case decline
}

class EventOptionsViewModel: ContextMenuViewModel {
typealias Action = EventAction

private let actionCallbackObserver: AnyObserver<Void>
let actionCallback: Observable<Void>

private(set) var items: [ActionItem] = []
class EventOptionsViewModel: BaseContextMenuViewModel<EventAction> {

private let event: EventModel
private let dateProvider: DateProviding
private let calendarService: CalendarServiceProviding
private let workspace: WorkspaceServiceProviding

private let disposeBag = DisposeBag()

init?(
event: EventModel,
dateProvider: DateProviding,
calendarService: CalendarServiceProviding,
workspace: WorkspaceServiceProviding,
canOpen: Bool
source: ContextMenuSource
) {
self.event = event
self.dateProvider = dateProvider
self.calendarService = calendarService
self.workspace = workspace

(actionCallback, actionCallbackObserver) = PublishSubject.pipe()
super.init()

if canOpen {
items.append(.action(.open))
if [.list, .menubar].contains(source) {
addItem(.open)
}

if source ~= .menubar {
addSeparator()
addItem(.skip)
}

if event.status != .unknown {
if !items.isEmpty {
items.append(.separator)
}
addSeparator()

if event.status != .accepted {
items.append(.action(.accept))
addItem(.status(.accept))
}
if event.status != .maybe {
items.append(.action(.maybe))
addItem(.status(.maybe))
}
if event.status != .declined {
items.append(.action(.decline))
addItem(.status(.decline))
}
}

Expand All @@ -81,35 +82,34 @@ class EventOptionsViewModel: ContextMenuViewModel {
workspace.open(URL(string: "ical://ekevent\(date)/\(event.id)?method=show&options=more")!)
}

func triggerAction(_ action: Action) {

if action ~= .open {
return openEvent()
override func triggerAction( _ action: Action) -> Observable<Void> {

switch action {
case .open:
openEvent()
case .skip:
break
case .status(let action):
return changeEventStatus(to: action.status)
}
return .just(())
}

guard let newStatus = action.status else { return }

calendarService.changeEventStatus(id: event.id, date: event.start, to: newStatus)
.subscribe(
onNext: actionCallbackObserver.onNext,
onError: actionCallbackObserver.onError
)
.disposed(by: disposeBag)
private func changeEventStatus(to status: EventStatus) -> Observable<Void> {
calendarService.changeEventStatus(id: event.id, date: event.start, to: status)
}
}

private extension EventAction {
private extension EventStatusAction {

var status: EventStatus? {
var status: EventStatus {
switch self {
case .accept:
return .accepted
case .maybe:
return .maybe
case .decline:
return .declined
default:
return nil
}
}
}
Expand All @@ -120,11 +120,13 @@ extension EventAction: ContextMenuAction {
switch self {
case .open:
return Icons.Event.open
case .accept:
case .skip:
return Icons.Event.skip
case .status(.accept):
return Icons.EventStatus.accepted.with(color: .systemGreen)
case .maybe:
case .status(.maybe):
return Icons.EventStatus.maybe.with(color: .systemOrange)
case .decline:
case .status(.decline):
return Icons.EventStatus.declined.with(color: .systemRed)
}
}
Expand All @@ -133,11 +135,13 @@ extension EventAction: ContextMenuAction {
switch self {
case .open:
return Strings.EventAction.open
case .accept:
case .skip:
return Strings.EventAction.skip
case .status(.accept):
return Strings.EventAction.accept
case .maybe:
case .status(.maybe):
return Strings.EventAction.maybe
case .decline:
case .status(.decline):
return Strings.EventAction.decline
}
}
Expand Down
Loading

0 comments on commit 8ea5134

Please sign in to comment.