Skip to content

Commit

Permalink
Merged dev into master
Browse files Browse the repository at this point in the history
  • Loading branch information
masich committed Mar 15, 2020
2 parents fc98b5c + 065b558 commit 414ad79
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 29 deletions.
8 changes: 4 additions & 4 deletions SaveMyEyes.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 12;
CURRENT_PROJECT_VERSION = 15;
DEVELOPMENT_ASSET_PATHS = "\"SaveMyEyes/Preview Content\"";
DEVELOPMENT_TEAM = 5PYHR2J538;
ENABLE_HARDENED_RUNTIME = YES;
Expand All @@ -369,7 +369,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 0.2;
MARKETING_VERSION = 0.25;
PRODUCT_BUNDLE_IDENTIFIER = com.masich.SaveMyEyes;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
Expand All @@ -384,7 +384,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 12;
CURRENT_PROJECT_VERSION = 15;
DEVELOPMENT_ASSET_PATHS = "\"SaveMyEyes/Preview Content\"";
DEVELOPMENT_TEAM = 5PYHR2J538;
ENABLE_HARDENED_RUNTIME = YES;
Expand All @@ -395,7 +395,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 0.2;
MARKETING_VERSION = 0.25;
PRODUCT_BUNDLE_IDENTIFIER = com.masich.SaveMyEyes;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
Expand Down
41 changes: 32 additions & 9 deletions SaveMyEyes/Source/Common/AppNotification.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ class AppNotification {
private let title: String
private let subtitle: String

struct Action {
static let pause = "pause.action"
}

struct Category {
static let reminder = "sme.reminder"
}

init(title: String, subtitle: String) {
self.title = title
self.subtitle = subtitle
Expand All @@ -23,45 +31,60 @@ class AppNotification {
content.title = title
content.subtitle = subtitle
content.sound = UNNotificationSound.default
content.categoryIdentifier = Category.reminder

return content
}
}

class AppNotificationManager {
private static let notificationCenter = UNUserNotificationCenter.current()
public static let defaultTimeToShow: TimeInterval = 5

static func requestAuthorization() {
notificationCenter.requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
if success {
print("Notifications are allowed")
print("Notifications. Permission granted")
} else if let error = error {
print(error.localizedDescription)
} else {
print("Notifications. Permission not granted")
}
}
}

static func send(_ notification: AppNotification,_ timeToShow: TimeInterval = defaultTimeToShow) {
// Show this notification five seconds from now
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeToShow, repeats: false)
static func send(_ notification: AppNotification) {
let content = notification.getNotificationContent()
// Choose a random identifier
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)

// Add a request for this notification
UNUserNotificationCenter.current().add(request)
notificationCenter.add(request)
}

/**
Removes all AppNotifications from the NotificationCenter and sends a new one
*/
static func sendSingle(_ notification: AppNotification,_ timeToShow: TimeInterval = defaultTimeToShow) {
static func sendSingle(_ notification: AppNotification) {
removeAllNotifications()
send(notification, timeToShow)
send(notification)
}

static func removeAllNotifications() {
notificationCenter.removeAllDeliveredNotifications()
}

static func registerCategories() {
let pause = UNNotificationAction(identifier: AppNotification.Action.pause, title: "Pause".localized, options: [])
let category = UNNotificationCategory(identifier: AppNotification.Category.reminder, actions: [pause], intentIdentifiers: [])

notificationCenter.setNotificationCategories([category])
}

static func registerDelegate(_ delegate: UNUserNotificationCenterDelegate) {
notificationCenter.delegate = delegate
}

static func removeDelegate() {
notificationCenter.delegate = nil
}
}
8 changes: 4 additions & 4 deletions SaveMyEyes/Source/Common/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ struct Constants {
public static let workIntervals = [15, 20, 30, 40, 60, 90, 120]
public static let breakIntervals = [1, 2, 5, 10, 15, 20, 30]

// Timer will tick every 1 minute
public static let timerInterval: TimeInterval = 1 * 60
public static let minute: TimeInterval = 60 // seconds

// If user is inactive for more than `allowedUserInactivityInterval` seconds -> internal timer will be paused
public static let allowedUserInactivityInterval: TimeInterval = 5 * 60
// If user is inactive for more than `allowedUserInactivityMinutes` minutes -> internal timer will be paused
public static let allowedUserInactivityMinutes: Int = 5
public static let allowedUserInactivityInterval: TimeInterval = TimeInterval(allowedUserInactivityMinutes) * minute
}
4 changes: 4 additions & 0 deletions SaveMyEyes/Source/Common/Extentions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ extension String {
var localized: String {
return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
}

var localizedUserNotification: String {
return NSString.localizedUserNotificationString(forKey: self, arguments: nil)
}
}
4 changes: 4 additions & 0 deletions SaveMyEyes/Source/Common/Preferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,8 @@ class System {
public static func isUserInactive(forTimeInterval: TimeInterval) -> Bool {
return (getUserInactiveSeconds() ?? 0) >= forTimeInterval
}

public static func isUserInactive(forMinutes: Int) -> Bool {
return isUserInactive(forTimeInterval: TimeInterval(forMinutes) * Constants.minute)
}
}
17 changes: 16 additions & 1 deletion SaveMyEyes/Source/Main/MainViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class MainViewModel: ObservableObject {

private var timerWorker: TimerWorker!
private var cancellables = [AnyCancellable]()
private var isUserInactive = false

private let allowedUserInactivityInterval: TimeInterval
private let timerInterval: TimeInterval
Expand Down Expand Up @@ -113,7 +114,13 @@ class MainViewModel: ObservableObject {
Performs time management only if user was active for at least last `allowedUserInactivityInterval` seconds
*/
public func timerHandler(timer: Timer) {
if !System.isUserInactive(forTimeInterval: allowedUserInactivityInterval) {
let isUserIncativeNew = System.isUserInactive(forMinutes: Constants.allowedUserInactivityMinutes)
if !isUserInactive && isUserIncativeNew {
remainingMins += Constants.allowedUserInactivityMinutes
}
isUserInactive = isUserIncativeNew

if isBreakTimeNow || !isUserInactive {
remainingMins -= 1
if remainingMins <= 0 {
if isBreakTimeNow {
Expand Down Expand Up @@ -141,4 +148,12 @@ class MainViewModel: ObservableObject {
}
AppNotificationManager.sendSingle(notification)
}

public func pauseTimer() {
shouldTimerRun.value = false

// A temporary crutch to update view content:)
// TODO: Remove it
remainingMins -= 0
}
}
49 changes: 38 additions & 11 deletions SaveMyEyes/Source/Startup/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@

import Cocoa
import SwiftUI
import UserNotifications

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDelegate {
var popover: NSPopover!
var statusBarItem: NSStatusItem!
var mainViewModel: MainViewModel?

func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let viewModel = MainViewModel(workIntervals: Constants.workIntervals, breakIntervals: Constants.breakIntervals, timerInterval: Constants.timerInterval, allowedUserInactivityInterval: Constants.allowedUserInactivityInterval, terminateApp: AppDelegate.terminateApp)
let view = MainView(mainViewModel: viewModel)
mainViewModel = MainViewModel(workIntervals: Constants.workIntervals, breakIntervals: Constants.breakIntervals, timerInterval: Constants.minute, allowedUserInactivityInterval: Constants.allowedUserInactivityInterval, terminateApp: AppDelegate.terminateApp)
let view = MainView(mainViewModel: mainViewModel!)

// Create the popover
let popover = NSPopover()
Expand All @@ -35,22 +37,47 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}

NSApp.activate(ignoringOtherApps: true)
AppDelegate.setupNotifications()
setupNotifications()
}

@objc func togglePopover(_ sender: AnyObject?) {
@objc private func togglePopover(_ sender: AnyObject?) {
if self.popover.isShown {
closePopover()
} else {
showPopover()
}
}

private func showPopover() {
if let button = self.statusBarItem.button {
if self.popover.isShown {
self.popover.performClose(sender)
} else {
self.popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
self.popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}

private func closePopover() {
self.popover.performClose(nil)
}

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
let category = response.notification.request.content.categoryIdentifier

if category == AppNotification.Category.reminder {
switch response.actionIdentifier {
case AppNotification.Action.pause:
mainViewModel?.pauseTimer()
break
default:
break
}
}
completionHandler()
}

static func setupNotifications() {
private func setupNotifications() {
AppNotificationManager.requestAuthorization()

AppNotificationManager.removeAllNotifications()
AppNotificationManager.registerDelegate(self)
AppNotificationManager.registerCategories()
}

static func terminateApp() {
Expand Down
1 change: 1 addition & 0 deletions SaveMyEyes/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
"It's time to work" = "It's time for work";
"Relax from your computer for %d minutes." = "Relax from your computer for %d minutes.";
"Let's continue to do amazing things!" = "Let's continue to do amazing things!";
"Pause" = "Pause";
1 change: 1 addition & 0 deletions SaveMyEyes/uk.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
"It's time to work" = "Час для роботи";
"Relax from your computer for %d minutes." = "Відпочинь від компьютера на %d хвилин.";
"Let's continue to do amazing things!" = "Давай продовжимо робити круті речі!";
"Pause" = "Пауза";

0 comments on commit 414ad79

Please sign in to comment.