Skip to content

Commit

Permalink
Add reminders support
Browse files Browse the repository at this point in the history
  • Loading branch information
pakerwreah committed Apr 11, 2021
1 parent 533dd48 commit 8974733
Show file tree
Hide file tree
Showing 31 changed files with 581 additions and 190 deletions.
16 changes: 14 additions & 2 deletions Calendr.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@
34C72F9C25E9847300FD20FA /* EventListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C72F9B25E9847300FD20FA /* EventListViewModel.swift */; };
34C72FA225E987F000FD20FA /* EventListViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34C72FA125E987F000FD20FA /* EventListViewModelTests.swift */; };
34CC382D25B636A6006CBD99 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34CC382C25B636A6006CBD99 /* Checkbox.swift */; };
34D19FC0262299D300B2732C /* NSEdgeInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D19FBF262299D300B2732C /* NSEdgeInsets.swift */; };
34D19FC62622A02C00B2732C /* EventDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D19FC52622A02C00B2732C /* EventDetailsViewModelTests.swift */; };
34D19FCC2622B9CD00B2732C /* EventMeta.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D19FCB2622B9CC00B2732C /* EventMeta.swift */; };
34D55FE325F06669007F5C81 /* Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34D55FE225F06669007F5C81 /* Sequence.swift */; };
34E004A725B61D5200241419 /* StatusItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E004A625B61D5200241419 /* StatusItemViewModel.swift */; };
34E1902325B76CFE00E9491B /* CalendarPickerViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34E1902225B76CFE00E9491B /* CalendarPickerViewModelTests.swift */; };
Expand Down Expand Up @@ -199,6 +202,9 @@
34C72F9B25E9847300FD20FA /* EventListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventListViewModel.swift; sourceTree = "<group>"; };
34C72FA125E987F000FD20FA /* EventListViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventListViewModelTests.swift; sourceTree = "<group>"; };
34CC382C25B636A6006CBD99 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = "<group>"; };
34D19FBF262299D300B2732C /* NSEdgeInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEdgeInsets.swift; sourceTree = "<group>"; };
34D19FC52622A02C00B2732C /* EventDetailsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDetailsViewModelTests.swift; sourceTree = "<group>"; };
34D19FCB2622B9CC00B2732C /* EventMeta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventMeta.swift; sourceTree = "<group>"; };
34D55FE225F06669007F5C81 /* Sequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sequence.swift; sourceTree = "<group>"; };
34E004A625B61D5200241419 /* StatusItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemViewModel.swift; sourceTree = "<group>"; };
34E1902225B76CFE00E9491B /* CalendarPickerViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarPickerViewModelTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -370,6 +376,7 @@
34E1902225B76CFE00E9491B /* CalendarPickerViewModelTests.swift */,
347E6D3D25AA2811009A6716 /* CalendarViewModelTests.swift */,
34924CE3259FCB36009C3450 /* DateSelectorTests.swift */,
34D19FC52622A02C00B2732C /* EventDetailsViewModelTests.swift */,
34C72FA125E987F000FD20FA /* EventListViewModelTests.swift */,
348E702C25C6161900B3B160 /* EventViewModelFadeTests.swift */,
34651F6D25E3171D00518C5A /* EventViewModelLinkTests.swift */,
Expand Down Expand Up @@ -418,6 +425,7 @@
34651F5325E22A3900518C5A /* DateIntervalFormatter.swift */,
34B5A09625B0F8A500F7F7ED /* NSButton.swift */,
34B23E9B25C0C94000DADCD6 /* NSButton+Rx.swift */,
34D19FBF262299D300B2732C /* NSEdgeInsets.swift */,
34A4C3E825963FB200D0F949 /* NSStackView.swift */,
3430ED02259634E00045DA53 /* NSStackView+Rx.swift */,
341B2B4725D0C9A600336342 /* NSView.swift */,
Expand All @@ -435,6 +443,7 @@
isa = PBXGroup;
children = (
3470214F259E163000827AE7 /* CalendarModel.swift */,
34D19FCB2622B9CC00B2732C /* EventMeta.swift */,
347D0FF125954920002451EC /* EventModel.swift */,
348E701E25C43C5200B3B160 /* WeekDay.swift */,
);
Expand Down Expand Up @@ -761,6 +770,7 @@
3449402E25C348B20020E664 /* GeneralSettingsViewController.swift in Sources */,
349355A625BCA8D400957945 /* EventListView.swift in Sources */,
341B2B4825D0C9A600336342 /* NSView.swift in Sources */,
34D19FCC2622B9CD00B2732C /* EventMeta.swift in Sources */,
34F96CEE25B523ED0020DEEA /* main.swift in Sources */,
348B8D0425B2925100E518FE /* SettingsViewModel.swift in Sources */,
34F128D125968044007DF31C /* Label.swift in Sources */,
Expand All @@ -778,6 +788,7 @@
34CC382D25B636A6006CBD99 /* Checkbox.swift in Sources */,
3430ED03259634E00045DA53 /* NSStackView+Rx.swift in Sources */,
34E004A725B61D5200241419 /* StatusItemViewModel.swift in Sources */,
34D19FC0262299D300B2732C /* NSEdgeInsets.swift in Sources */,
34702168259E761100827AE7 /* Calendar.swift in Sources */,
34C4599C25DEFFDA00561C29 /* BuildConfig.m in Sources */,
34651F1525E1BB8400518C5A /* Strings.generated.swift in Sources */,
Expand Down Expand Up @@ -823,6 +834,7 @@
3477F3D425FAF079008EA888 /* MockCalendarServiceProvider.swift in Sources */,
347E6D3E25AA2811009A6716 /* CalendarViewModelTests.swift in Sources */,
348E702D25C6161900B3B160 /* EventViewModelFadeTests.swift in Sources */,
34D19FC62622A02C00B2732C /* EventDetailsViewModelTests.swift in Sources */,
34C72FA225E987F000FD20FA /* EventListViewModelTests.swift in Sources */,
34C72F9625E8A05700FD20FA /* MockNextEventSettings.swift in Sources */,
341B2B4225D0B19500336342 /* StatusItemViewModelTests.swift in Sources */,
Expand Down Expand Up @@ -987,7 +999,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.1.1;
MARKETING_VERSION = 1.2.0;
PRODUCT_BUNDLE_IDENTIFIER = br.paker.Calendr;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Calendr/Config/Calendr-Bridging-Header.h";
Expand All @@ -1012,7 +1024,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.1.1;
MARKETING_VERSION = 1.2.0;
PRODUCT_BUNDLE_IDENTIFIER = br.paker.Calendr;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Calendr/Config/Calendr-Bridging-Header.h";
Expand Down
14 changes: 12 additions & 2 deletions Calendr/Assets/Strings.generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,18 @@ internal enum Strings {
/// Today
internal static var today: String { return Strings.tr("Localizable", "formatter.date.today") }
internal enum Relative {
/// in
internal static var `in`: String { return Strings.tr("Localizable", "formatter.date.relative.in") }
/// %@ ago
internal static func ago(_ p1: Any) -> String {
return Strings.tr("Localizable", "formatter.date.relative.ago", String(describing: p1))
}
/// in %@
internal static func `in`(_ p1: Any) -> String {
return Strings.tr("Localizable", "formatter.date.relative.in", String(describing: p1))
}
/// %@ left
internal static func `left`(_ p1: Any) -> String {
return Strings.tr("Localizable", "formatter.date.relative.left", String(describing: p1))
}
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion Calendr/Assets/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@

"formatter.date.all_day" = "All day";
"formatter.date.today" = "Today";
"formatter.date.relative.in" = "in";
"formatter.date.relative.in" = "in %@";
"formatter.date.relative.ago" = "%@ ago";
"formatter.date.relative.left" = "%@ left";
4 changes: 3 additions & 1 deletion Calendr/Assets/es.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@

"formatter.date.all_day" = "Todo el día";
"formatter.date.today" = "Hoy";
"formatter.date.relative.in" = "en";
"formatter.date.relative.in" = "en %@";
"formatter.date.relative.ago" = "hace %@";
"formatter.date.relative.left" = "%@ rest.";
4 changes: 3 additions & 1 deletion Calendr/Assets/fr.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@

"formatter.date.all_day" = "Toute la journée";
"formatter.date.today" = "Aujourd'hui";
"formatter.date.relative.in" = "dans";
"formatter.date.relative.in" = "dans %@";
"formatter.date.relative.ago" = "il y a %@";
"formatter.date.relative.left" = "%@ rest.";
4 changes: 3 additions & 1 deletion Calendr/Assets/pt.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@

"formatter.date.all_day" = "Dia inteiro";
"formatter.date.today" = "Hoje";
"formatter.date.relative.in" = "em";
"formatter.date.relative.in" = "em %@";
"formatter.date.relative.ago" = "%@ atrás";
"formatter.date.relative.left" = "%@ rest.";
4 changes: 3 additions & 1 deletion Calendr/Config/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@
<key>LSUIElement</key>
<true/>
<key>NSCalendarsUsageDescription</key>
<string>Calendr needs to display events from your calendars.</string>
<string>Calendr needs access to display events from your calendars.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSRemindersUsageDescription</key>
<string>Calendr needs access to display your reminders.</string>
</dict>
</plist>
2 changes: 1 addition & 1 deletion Calendr/Events/EventDetailsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class EventDetailsViewController: NSViewController, NSPopoverDelegate {
override func loadView() {
view = NSView()

view.widthAnchor.constraint(lessThanOrEqualToConstant: 300).activate()
view.widthAnchor.constraint(lessThanOrEqualToConstant: 310).activate()

let contentStackView = NSStackView(
views: fields
Expand Down
27 changes: 20 additions & 7 deletions Calendr/Events/EventDetailsViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,15 @@ class EventDetailsViewModel {

let popoverMaterial: Observable<PopoverMaterial>

init?(
identifier: String,
init(
event: EventModel,
dateProvider: DateProviding,
calendarService: CalendarServiceProviding,
settings: EventSettings
) {

guard let event = calendarService.event(identifier) else { return nil }

title = event.title
url = event.url?.absoluteString ?? ""
url = (event.type.isBirthday ? nil : event.url?.absoluteString) ?? ""
location = event.location ?? ""
notes = event.notes ?? ""

Expand All @@ -37,10 +35,25 @@ class EventDetailsViewModel {

if event.isAllDay {
formatter.timeStyle = .none
duration = formatter.string(from: event.startDate, to: event.startDate)
duration = formatter.string(from: event.start, to: event.start)
} else {
formatter.timeStyle = .short
duration = formatter.string(from: event.startDate, to: event.endDate)

let meta = EventMeta(event: event, dateProvider: dateProvider)

let end: Date

if event.type.isReminder {
end = event.start
}
else if meta.isSingleDay && meta.endsMidnight {
end = dateProvider.calendar.startOfDay(for: event.start)
}
else {
end = event.end
}

duration = formatter.string(from: event.start, to: end)
}

popoverMaterial = settings.popoverMaterial
Expand Down
16 changes: 2 additions & 14 deletions Calendr/Events/EventListViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,6 @@ class EventListViewModel {
)
}

func endsToday(_ event: EventModel) -> Bool {
// fix range ending at 00:00 of the next day
let fixedEnd = dateProvider.calendar.date(byAdding: .second, value: -1, to: event.end)!
return dateProvider.calendar.isDate(fixedEnd, inSameDayAs: dateProvider.now)
}

func isPast(_ event: EventModel) -> Bool {
dateProvider.calendar.isDate(
event.end, lessThan: dateProvider.now, granularity: .second
)
}

viewModels = Observable.combineLatest(
eventsObservable, dateObservable, settings.showPastEvents
)
Expand All @@ -74,7 +62,7 @@ class EventListViewModel {
return Observable.merge(
events
.filter {
!$0.isAllDay && endsToday($0)
!$0.isAllDay && $0.meta(using: dateProvider).endsToday
}
.map {
Int(dateProvider.now.distance(to: $0.end).rounded(.up)) + 1
Expand All @@ -88,7 +76,7 @@ class EventListViewModel {
.startWith(())
.map {
events.filter {
$0.isAllDay || !isPast($0)
$0.isAllDay || !$0.meta(using: dateProvider).isPast
}
}
.map { ($0, date, isToday) }
Expand Down
96 changes: 55 additions & 41 deletions Calendr/Events/EventView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,22 @@ class EventView: NSView {

private func setData() {

icon.stringValue = "🎁"
icon.isHidden = !viewModel.isBirthday
switch viewModel.type {

case .birthday:
icon.stringValue = "🎁"
icon.textColor = .systemRed

case .reminder:
icon.stringValue = "🔔"

case .event:
icon.isHidden = true
}

title.stringValue = viewModel.title

subtitle.stringValue = viewModel.subtitle.replacingOccurrences(of: "https://", with: "")
subtitle.stringValue = viewModel.subtitle
subtitle.isHidden = subtitle.isEmpty

duration.stringValue = viewModel.duration
Expand All @@ -71,7 +81,6 @@ class EventView: NSView {
layer?.addSublayer(hoverLayer)

icon.forceVibrancy = false
icon.textColor = .systemRed
icon.font = Fonts.SegoeUISymbol.regular.font(size: 10)
icon.setContentHuggingPriority(.required, for: .horizontal)
icon.setContentCompressionResistancePriority(.required, for: .horizontal)
Expand Down Expand Up @@ -114,39 +123,19 @@ class EventView: NSView {
let eventStackView = NSStackView(views: [titleStackView, subtitleStackView, duration])
.with(orientation: .vertical)
.with(spacing: 2)
.with(insets: .init(vertical: 1))

let contentStackView = NSStackView(views: [colorBar, eventStackView])
addSubview(contentStackView)
contentStackView.edges(to: self)

addSubview(progress, positioned: .below, relativeTo: nil)

progress.isHidden = true
progress.wantsLayer = true
progress.layer?.backgroundColor = NSColor.red.cgColor.copy(alpha: 0.7)
progress.height(equalTo: 1)
progress.width(equalTo: self)

rx.leftClickGesture { gesture, _ in
// NSClickGestureRecognizer overrides other events by default when buttonMask is 0x1 🤦🏻‍♂️
gesture.delaysPrimaryMouseButtonEvents = false
}
.when(.recognized)
.toVoid()
.compactMap(viewModel.makeDetails)
.withUnretained(self)
.flatMapLatest { view, viewModel -> Observable<Void> in
let vc = EventDetailsViewController(viewModel: viewModel)
let popover = NSPopover()
popover.behavior = .transient
popover.contentViewController = vc
popover.delegate = vc
popover.show(relativeTo: .zero, of: view, preferredEdge: .minX)
return popover.rx.deallocated
}
.bind(with: self) { view, _ in
view.window?.makeKey()
}
.disposed(by: disposeBag)
}

private func setUpBindings() {
Expand All @@ -171,29 +160,54 @@ class EventView: NSView {
.disposed(by: disposeBag)
}

viewModel.isFaded
.map { $0 ? 0.5 : 1 }
.bind(to: rx.alpha)
.disposed(by: disposeBag)
if viewModel.type.isEvent {

Observable.combineLatest(
viewModel.progress, rx.observe(\.frame)
)
.compactMap { progress, frame in
progress.map { max(1, $0 * frame.height - 0.5) }
}
.bind(to: progressTop.rx.constant)
.disposed(by: disposeBag)
viewModel.isFaded
.map { $0 ? 0.5 : 1 }
.bind(to: rx.alpha)
.disposed(by: disposeBag)

viewModel.isInProgress
.map(!)
.bind(to: progress.rx.isHidden)
Observable.combineLatest(
viewModel.progress, rx.observe(\.frame)
)
.compactMap { progress, frame in
progress.map { max(1, $0 * frame.height - 0.5) }
}
.bind(to: progressTop.rx.constant)
.disposed(by: disposeBag)

viewModel.isInProgress
.map(!)
.bind(to: progress.rx.isHidden)
.disposed(by: disposeBag)
}

viewModel.backgroundColor
.map(\.cgColor)
.bind(to: layer!.rx.backgroundColor)
.disposed(by: disposeBag)

rx.leftClickGesture { gesture, _ in
// NSClickGestureRecognizer overrides other events by default when buttonMask is 0x1 🤦🏻‍♂️
gesture.delaysPrimaryMouseButtonEvents = false
}
.when(.recognized)
.toVoid()
.map(viewModel.makeDetails)
.withUnretained(self)
.flatMapLatest { view, viewModel -> Observable<Void> in
let vc = EventDetailsViewController(viewModel: viewModel)
let popover = NSPopover()
popover.behavior = .transient
popover.contentViewController = vc
popover.delegate = vc
popover.show(relativeTo: .zero, of: view, preferredEdge: .minX)
return popover.rx.deallocated
}
.bind(with: self) { view, _ in
view.window?.makeKey()
}
.disposed(by: disposeBag)
}

override func updateLayer() {
Expand Down
Loading

0 comments on commit 8974733

Please sign in to comment.