Skip to content

Commit

Permalink
Merge pull request #44 from sanghyeok-kim/feature/#8-alarm-sound
Browse files Browse the repository at this point in the history
알람 사운드를 설정할 수 있는 기능을 구현합니다.
  • Loading branch information
sanghyeok-kim authored Aug 3, 2023
2 parents 85ecfe9 + 1d20064 commit b3e0c74
Show file tree
Hide file tree
Showing 59 changed files with 1,031 additions and 88 deletions.
192 changes: 182 additions & 10 deletions Multimer/Multimer.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ extension CoreDataStorage {
startDate: Date? = nil,
notificationIdentifier: String? = nil,
type: TimerType? = nil,
ringtone: Ringtone? = nil,
index: Int? = nil
) {
backgroundContext.perform { [weak self] in
Expand All @@ -34,6 +35,7 @@ extension CoreDataStorage {
startDate: startDate,
notificationIdentifier: notificationIdentifier,
type: type,
ringtone: ringtone,
context: self.backgroundContext
)
self.saveContext()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ extension TimerMO: ModelConvertible {
expireDate: expireDate,
startDate: startDate,
type: type,
ringtone: ringtone,
index: Int(index)
)
}
Expand All @@ -44,6 +45,7 @@ extension TimerMO {
startDate: Date? = nil,
notificationIdentifier: String? = nil,
type: TimerType? = nil,
ringtone: Ringtone? = nil,
index: Int? = nil,
context: NSManagedObjectContext
) {
Expand All @@ -56,6 +58,7 @@ extension TimerMO {
self.startDate = startDate ?? self.startDate
self.notificationIdentifier = notificationIdentifier ?? self.notificationIdentifier
self.type = type ?? self.type
self.ringtone = ringtone ?? self.ringtone
self.index = Int16(index ?? Int(self.index))
}
}
Expand All @@ -82,6 +85,16 @@ extension TimerMO {
self.typeValue = Int16(newValue.rawValue)
}
}

var ringtone: Ringtone? {
get {
guard let ringtoneValue = self.ringtoneValue else { return nil }
return Ringtone(rawValue: ringtoneValue)
}
set {
self.ringtoneValue = newValue?.rawValue
}
}
}

@frozen
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="20086" systemVersion="21D62" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21512" systemVersion="22E261" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Tag" representedClassName="TagMO" syncable="YES" codeGenerationType="category">
<attribute name="isSelected" attributeType="Boolean" usesScalarValueType="YES"/>
<relationship name="color" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="TagColor"/>
Expand All @@ -19,17 +19,11 @@
<attribute name="name" attributeType="String"/>
<attribute name="notificationIdentifier" optional="YES" attributeType="String"/>
<attribute name="repeatCount" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="soundValue" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="ringtoneValue" optional="YES" attributeType="String"/>
<attribute name="startDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="stateValue" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="typeValue" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<relationship name="tag" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Tag"/>
<relationship name="time" maxCount="1" deletionRule="Nullify" destinationEntity="Time"/>
</entity>
<elements>
<element name="Tag" positionX="-518.3515625" positionY="-274.8671875" width="128" height="59"/>
<element name="TagColor" positionX="-396.00390625" positionY="-144.3125" width="128" height="44"/>
<element name="Time" positionX="-821.26953125" positionY="-276.1484375" width="128" height="59"/>
<element name="Timer" positionX="-638.5390625" positionY="-406.37890625" width="128" height="224"/>
</elements>
</model>
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ final class CoreDataTimerRepository: TimerPersistentRepository {
state: TimerState? = nil,
expireDate: Date? = nil,
startDate: Date? = nil,
type: TimerType? = nil
type: TimerType? = nil,
ringtone: Ringtone? = nil
) -> Completable {
return Completable.create { [weak self] completable in
guard let self = self else { return Disposables.create { } }
Expand All @@ -66,7 +67,8 @@ final class CoreDataTimerRepository: TimerPersistentRepository {
state: state,
expireDate: expireDate,
startDate: startDate,
type: type
type: type,
ringtone: ringtone
)
completable(.completed)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ protocol TimerPersistentRepository {
state: TimerState?,
expireDate: Date?,
startDate: Date?,
type: TimerType?
type: TimerType?,
ringtone: Ringtone?
) -> Completable
func saveTimeOfTimer(
target identifier: UUID,
Expand All @@ -42,16 +43,19 @@ extension TimerPersistentRepository {
state: TimerState? = nil,
expireDate: Date? = nil,
startDate: Date? = nil,
type: TimerType? = nil
type: TimerType? = nil,
ringtone: Ringtone? = nil
) -> Completable {
updateTimer(target: identifier,
name: name,
tag: tag,
time: time,
state: state,
expireDate: expireDate,
startDate: startDate,
type: type
updateTimer(
target: identifier,
name: name,
tag: tag,
time: time,
state: state,
expireDate: expireDate,
startDate: startDate,
type: type,
ringtone: ringtone
)
}

Expand Down
39 changes: 39 additions & 0 deletions Multimer/Multimer/Data/Service/UserNotificationCenterService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// UserNotificationCenterService.swift
// Multimer
//
// Created by 김상혁 on 2023/08/02.
//

import Foundation
import UserNotifications

final class UserNotificationCenterService {
static func registerNotification(
ringtone: Ringtone?,
remainingSeconds: TimeInterval,
timerName: String,
notificationIdentifier: String?
) {
let content = UNMutableNotificationContent()
content.title = LocalizableString.appTitle.localized
content.body = LocalizableString.timerExpired(timerName: timerName).localized
content.sound = .default

if let ringtoneFileName = ringtone?.name, ringtone != .default1 {
content.sound = UNNotificationSound(
named: UNNotificationSoundName(rawValue: "\(ringtoneFileName).\(Constant.Ringtone.extension)")
)
}

if remainingSeconds <= .zero { return }
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: remainingSeconds, repeats: false)
guard let notificationIdentifier = notificationIdentifier else { return }
let request = UNNotificationRequest(identifier: notificationIdentifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request)
}

static func removeNotification(withIdentifiers identifiers: [String]) {
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: identifiers)
}
}
10 changes: 9 additions & 1 deletion Multimer/Multimer/Domain/Model/Timer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct Timer {
var startDate: Date?
var notificationIdentifier: String?
var type: TimerType
var ringtone: Ringtone?
var index: Int

// TODO: Sound, RepeatCount
Expand All @@ -38,6 +39,7 @@ struct Timer {
expireDate: Date? = nil,
startDate: Date? = nil,
type: TimerType = .countDown,
ringtone: Ringtone? = .default1,
index: Int = .zero) {
self.identifier = identifier
self.name = name
Expand All @@ -48,6 +50,7 @@ struct Timer {
self.startDate = startDate
self.notificationIdentifier = identifier.uuidString
self.type = type
self.ringtone = ringtone
self.index = index
}

Expand All @@ -61,6 +64,7 @@ struct Timer {
self.startDate = timer.startDate
self.notificationIdentifier = timer.identifier.uuidString
self.type = timer.type
self.ringtone = timer.ringtone
self.index = timer.index
}

Expand All @@ -74,7 +78,10 @@ struct Timer {

extension Timer: Equatable {
static func == (lhs: Timer, rhs: Timer) -> Bool {
return (lhs.name == rhs.name) && (lhs.tag == rhs.tag) && (lhs.time == rhs.time)
return (lhs.name == rhs.name)
&& (lhs.tag == rhs.tag)
&& (lhs.time == rhs.time)
&& (lhs.ringtone == rhs.ringtone)
}
}

Expand All @@ -91,6 +98,7 @@ extension Timer: ManagedObjectConvertible {
startDate: startDate,
notificationIdentifier: notificationIdentifier,
type: type,
ringtone: ringtone,
index: index,
context: context
)
Expand Down
50 changes: 27 additions & 23 deletions Multimer/Multimer/Domain/UseCase/CountDownTimerUseCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,12 @@ final class CountDownTimerUseCase: TimerUseCase {
resetTimer()
}

registerNotification()
UserNotificationCenterService.registerNotification(
ringtone: timer.value.ringtone,
remainingSeconds: currentTimer.remainingSeconds,
timerName: currentTimer.name,
notificationIdentifier: currentTimer.notificationIdentifier
)

let expireDate = Date(timeInterval: currentTimer.remainingSeconds, since: .now)
timerPersistentRepository
Expand Down Expand Up @@ -112,7 +117,13 @@ final class CountDownTimerUseCase: TimerUseCase {
}

timerPersistentRepository
.updateTimer(target: newTimer.identifier, name: newTimer.name, tag: newTimer.tag, time: newTimer.time)
.updateTimer(
target: newTimer.identifier,
name: newTimer.name,
tag: newTimer.tag,
time: newTimer.time,
ringtone: newTimer.ringtone
)
.subscribe(onCompleted: { [weak self] in
guard let self = self else { return }
self.timer.accept(newTimer)
Expand All @@ -129,24 +140,18 @@ final class CountDownTimerUseCase: TimerUseCase {
dispatchSourceTimer = nil

guard let notificationIdentifier = currentTimer.notificationIdentifier else { return }
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [notificationIdentifier])
UserNotificationCenterService.removeNotification(withIdentifiers: [notificationIdentifier])
}

// TODO: Notification Manager 구현
private func registerNotification() {
let content = UNMutableNotificationContent()
content.title = LocalizableString.appTitle.localized
content.body = LocalizableString.timerExpired(timerName: currentTimer.name).localized
content.sound = .default

if currentTimer.remainingSeconds <= .zero { return }
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(currentTimer.remainingSeconds), repeats: false)
guard let notificationIdentifier = currentTimer.notificationIdentifier else { return }
let request = UNNotificationRequest(identifier: notificationIdentifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request)
deinit {
stopTimer()
}

private func runTimer(by expirationDate: Date) {
}

// MARK: - Supporting Methods

private extension CountDownTimerUseCase {
func runTimer(by expirationDate: Date) {
if dispatchSourceTimer == nil {
dispatchSourceTimer = DispatchSource.makeTimerSource(queue: .global())
}
Expand All @@ -163,19 +168,18 @@ final class CountDownTimerUseCase: TimerUseCase {
self.expireTimer()
self.stopTimer()
} else {
let remainingTime = Time(totalSeconds: self.currentTimer.totalSeconds, remainingSeconds: remainingTimeInterval)
let remainingTime = Time(
totalSeconds: self.currentTimer.totalSeconds,
remainingSeconds: remainingTimeInterval
)
self.timer.accept(Timer(timer: self.currentTimer, time: remainingTime))
}
}
}

private func expireTimer() {
func expireTimer() {
let expiredTime = TimeFactory.createExpiredTime(of: currentTimer.time)
timer.accept(Timer(timer: currentTimer, time: expiredTime))
timerPersistentRepository.saveTimeOfTimer(target: timerIdentifier, time: expiredTime)
}

deinit {
stopTimer()
}
}
10 changes: 10 additions & 0 deletions Multimer/Multimer/Localizing/LocalizableString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ enum LocalizableString {
case checkNotificationPermissions
case allowNotificationAuthorizationAlert
case goToSettings
case alertTones
case ringtones
case ringtoneName(ringtone: Ringtone)


var localized: String {
switch self {
Expand Down Expand Up @@ -104,6 +108,12 @@ enum LocalizableString {
return String(format: NSLocalizedString("allowNotificationAuthorizationAlert", comment: ""))
case .goToSettings:
return String(format: NSLocalizedString("goToSettings", comment: ""))
case .alertTones:
return String(format: NSLocalizedString("alertTones", comment: ""))
case .ringtones:
return String(format: NSLocalizedString("ringtones", comment: ""))
case .ringtoneName(let ringtone):
return String(format: NSLocalizedString("\(ringtone.name)", comment: ""))
}
}
}
35 changes: 34 additions & 1 deletion Multimer/Multimer/Localizing/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,44 @@
"noTimerActivatedMessage" = "No Timer Activated";
"onlyActiveTimersAppearMessage" = "Timers in the run, pause, and complete states appear.";
"settingTimer" = "Editing %@";
"timerExpired" = "Timer Expired - %@";
"timerExpired" = "[Timer Expired] %@";
"appTitle" = "Multi+Timer";
"defaultName" = "Default Name";
"swipeRightToStop" = "Swipe right to stop the timer.";
"resetAll" = "Reset All";
"checkNotificationPermissions" = "Check Notification Permissions";
"allowNotificationAuthorizationAlert" = "The alarm is not sounding because you have not allowed notification permissions. Please allow notification permissions in Settings.";
"goToSettings" = "Go to Settings";

// MARK: - Ringtones

"alertTones" = "ALERT TONES";
"ringtones" = "RINGTONES";

"default1" = "Alert Sound 1";
"default2" = "Alert Sound 2";
"default3" = "Alert Sound 3";
"default4" = "Alert Sound 4";
"default5" = "Alert Sound 5";
"default6" = "Alert Sound 6";
"default7" = "Alert Sound 7";
"default8" = "Alert Sound 8";
"alarm" = "Alarm";
"bark" = "Bark";
"beacon" = "Beacon";
"bulletin" = "Bulletin";
"duck" = "Duck";
"illuminate" = "Illuminate";
"marimba" = "Marimba";
"oldPhone" = "Old Phone";
"pianoRiff" = "Piano Riff";
"pinball" = "Pinball";
"presto" = "Presto";
"radar" = "Radar";
"reflection" = "Reflection";
"sencha" = "Sencha";
"signal" = "Signal";
"stargaze" = "Stargaze";
"strum" = "Strum";
"trill" = "Trill";
"xylophone" = "Xylophone";
Loading

0 comments on commit b3e0c74

Please sign in to comment.