Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
0baed76275
commit
f4f365f4c7
@ -39,7 +39,10 @@ enum NotificationUtils {
|
||||
if soundName == AppConstants.SystemSounds.defaultSound {
|
||||
content.sound = UNNotificationSound.default
|
||||
} else {
|
||||
content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "\(soundName).caf"))
|
||||
// For alarm sounds, use the default notification sound since custom sounds need to be in the app bundle
|
||||
// and properly configured. For now, use default to ensure notifications work.
|
||||
content.sound = UNNotificationSound.default
|
||||
print("🔔 Using default notification sound for alarm: \(soundName)")
|
||||
}
|
||||
|
||||
return content
|
||||
|
||||
@ -14,6 +14,7 @@ struct Alarm: Identifiable, Codable, Equatable {
|
||||
var isEnabled: Bool
|
||||
var soundName: String
|
||||
var label: String
|
||||
var notificationMessage: String // Custom notification message
|
||||
var snoozeDuration: Int // in minutes
|
||||
var isVibrationEnabled: Bool
|
||||
var isLightFlashEnabled: Bool
|
||||
@ -26,6 +27,7 @@ struct Alarm: Identifiable, Codable, Equatable {
|
||||
isEnabled: Bool = true,
|
||||
soundName: String = AppConstants.SystemSounds.defaultSound,
|
||||
label: String = "Alarm",
|
||||
notificationMessage: String = "Your alarm is ringing",
|
||||
snoozeDuration: Int = 9,
|
||||
isVibrationEnabled: Bool = true,
|
||||
isLightFlashEnabled: Bool = false,
|
||||
@ -36,6 +38,7 @@ struct Alarm: Identifiable, Codable, Equatable {
|
||||
self.isEnabled = isEnabled
|
||||
self.soundName = soundName
|
||||
self.label = label
|
||||
self.notificationMessage = notificationMessage
|
||||
self.snoozeDuration = snoozeDuration
|
||||
self.isVibrationEnabled = isVibrationEnabled
|
||||
self.isLightFlashEnabled = isLightFlashEnabled
|
||||
|
||||
@ -84,8 +84,8 @@ class AlarmService {
|
||||
// Use FocusModeService for better Focus mode compatibility
|
||||
focusModeService.scheduleAlarmNotification(
|
||||
identifier: alarm.id.uuidString,
|
||||
title: "Wake Up!",
|
||||
body: "Your alarm is ringing.",
|
||||
title: alarm.label,
|
||||
body: alarm.notificationMessage,
|
||||
date: alarm.time,
|
||||
soundName: alarm.soundName,
|
||||
repeats: false // For now, set to false since Alarm model doesn't have repeatDays
|
||||
|
||||
@ -100,7 +100,10 @@ class FocusModeService {
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = title
|
||||
content.body = body
|
||||
content.sound = UNNotificationSound(named: UNNotificationSoundName(soundName))
|
||||
// Use default notification sound for now to ensure notifications work
|
||||
// Custom sounds require proper bundle configuration
|
||||
content.sound = UNNotificationSound.default
|
||||
print("🔔 Using default notification sound for alarm: \(soundName)")
|
||||
content.categoryIdentifier = "ALARM_CATEGORY"
|
||||
content.userInfo = [
|
||||
"alarmId": identifier,
|
||||
@ -115,7 +118,10 @@ class FocusModeService {
|
||||
let components = calendar.dateComponents([.hour, .minute], from: date)
|
||||
trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: true)
|
||||
} else {
|
||||
trigger = UNTimeIntervalNotificationTrigger(timeInterval: date.timeIntervalSinceNow, repeats: false)
|
||||
// Use calendar trigger for one-time alarms to avoid time interval issues
|
||||
let calendar = Calendar.current
|
||||
let components = calendar.dateComponents([.hour, .minute], from: date)
|
||||
trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: false)
|
||||
}
|
||||
|
||||
// Create request
|
||||
|
||||
@ -40,7 +40,7 @@ class AlarmViewModel {
|
||||
await notificationService.scheduleAlarmNotification(
|
||||
id: alarm.id.uuidString,
|
||||
title: alarm.label,
|
||||
body: "Time to wake up!",
|
||||
body: alarm.notificationMessage,
|
||||
soundName: alarm.soundName,
|
||||
date: alarm.time
|
||||
)
|
||||
@ -55,7 +55,7 @@ class AlarmViewModel {
|
||||
await notificationService.scheduleAlarmNotification(
|
||||
id: alarm.id.uuidString,
|
||||
title: alarm.label,
|
||||
body: "Time to wake up!",
|
||||
body: alarm.notificationMessage,
|
||||
soundName: alarm.soundName,
|
||||
date: alarm.time
|
||||
)
|
||||
@ -83,7 +83,7 @@ class AlarmViewModel {
|
||||
await notificationService.scheduleAlarmNotification(
|
||||
id: alarm.id.uuidString,
|
||||
title: alarm.label,
|
||||
body: "Time to wake up!",
|
||||
body: alarm.notificationMessage,
|
||||
soundName: alarm.soundName,
|
||||
date: alarm.time
|
||||
)
|
||||
@ -100,6 +100,7 @@ class AlarmViewModel {
|
||||
time: Date,
|
||||
soundName: String = AppConstants.SystemSounds.defaultSound,
|
||||
label: String = "Alarm",
|
||||
notificationMessage: String = "Your alarm is ringing",
|
||||
snoozeDuration: Int = 9,
|
||||
isVibrationEnabled: Bool = true,
|
||||
isLightFlashEnabled: Bool = false,
|
||||
@ -111,6 +112,7 @@ class AlarmViewModel {
|
||||
isEnabled: true,
|
||||
soundName: soundName,
|
||||
label: label,
|
||||
notificationMessage: notificationMessage,
|
||||
snoozeDuration: snoozeDuration,
|
||||
isVibrationEnabled: isVibrationEnabled,
|
||||
isLightFlashEnabled: isLightFlashEnabled,
|
||||
|
||||
@ -17,6 +17,7 @@ struct AddAlarmView: View {
|
||||
@State private var newAlarmTime = Calendar.current.date(bySettingHour: 6, minute: 0, second: 0, of: Date()) ?? Date()
|
||||
@State private var selectedSoundName = "digital-alarm.mp3"
|
||||
@State private var alarmLabel = "Alarm"
|
||||
@State private var notificationMessage = "Your alarm is ringing"
|
||||
@State private var snoozeDuration = 9 // minutes
|
||||
@State private var isVibrationEnabled = true
|
||||
@State private var isLightFlashEnabled = false
|
||||
@ -24,53 +25,67 @@ struct AddAlarmView: View {
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
TimePickerSection(selectedTime: $newAlarmTime)
|
||||
TimeUntilAlarmSection(alarmTime: newAlarmTime)
|
||||
VStack(spacing: 0) {
|
||||
// Time picker section at top
|
||||
TimePickerSection(selectedTime: $newAlarmTime)
|
||||
TimeUntilAlarmSection(alarmTime: newAlarmTime)
|
||||
|
||||
List {
|
||||
// Label Section
|
||||
NavigationLink(destination: LabelEditView(label: $alarmLabel)) {
|
||||
HStack {
|
||||
Image(systemName: "textformat")
|
||||
.foregroundColor(UIConstants.Colors.accentColor)
|
||||
.frame(width: 24)
|
||||
Text("Label")
|
||||
Spacer()
|
||||
Text(alarmLabel)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
// Sound Section
|
||||
NavigationLink(destination: SoundSelectionView(selectedSound: $selectedSoundName)) {
|
||||
HStack {
|
||||
Image(systemName: "music.note")
|
||||
.foregroundColor(UIConstants.Colors.accentColor)
|
||||
.frame(width: 24)
|
||||
Text("Sound")
|
||||
Spacer()
|
||||
Text(getSoundDisplayName(selectedSoundName))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
// Snooze Section
|
||||
NavigationLink(destination: SnoozeSelectionView(snoozeDuration: $snoozeDuration)) {
|
||||
HStack {
|
||||
Image(systemName: "clock.arrow.circlepath")
|
||||
.foregroundColor(UIConstants.Colors.accentColor)
|
||||
.frame(width: 24)
|
||||
Text("Snooze")
|
||||
Spacer()
|
||||
Text("for \(snoozeDuration) min")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
// List for settings below
|
||||
List {
|
||||
// Label Section
|
||||
NavigationLink(destination: LabelEditView(label: $alarmLabel)) {
|
||||
HStack {
|
||||
Image(systemName: "textformat")
|
||||
.foregroundColor(UIConstants.Colors.accentColor)
|
||||
.frame(width: 24)
|
||||
Text("Label")
|
||||
Spacer()
|
||||
Text(alarmLabel)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
// Notification Message Section
|
||||
NavigationLink(destination: NotificationMessageEditView(message: $notificationMessage)) {
|
||||
HStack {
|
||||
Image(systemName: "message")
|
||||
.foregroundColor(UIConstants.Colors.accentColor)
|
||||
.frame(width: 24)
|
||||
Text("Message")
|
||||
Spacer()
|
||||
Text(notificationMessage)
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Sound Section
|
||||
NavigationLink(destination: SoundSelectionView(selectedSound: $selectedSoundName)) {
|
||||
HStack {
|
||||
Image(systemName: "music.note")
|
||||
.foregroundColor(UIConstants.Colors.accentColor)
|
||||
.frame(width: 24)
|
||||
Text("Sound")
|
||||
Spacer()
|
||||
Text(getSoundDisplayName(selectedSoundName))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
// Snooze Section
|
||||
NavigationLink(destination: SnoozeSelectionView(snoozeDuration: $snoozeDuration)) {
|
||||
HStack {
|
||||
Image(systemName: "clock.arrow.circlepath")
|
||||
.foregroundColor(UIConstants.Colors.accentColor)
|
||||
.frame(width: 24)
|
||||
Text("Snooze")
|
||||
Spacer()
|
||||
Text("for \(snoozeDuration) min")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
}
|
||||
.navigationTitle("Alarm")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
@ -90,6 +105,7 @@ struct AddAlarmView: View {
|
||||
time: newAlarmTime,
|
||||
soundName: selectedSoundName,
|
||||
label: alarmLabel,
|
||||
notificationMessage: notificationMessage,
|
||||
snoozeDuration: snoozeDuration,
|
||||
isVibrationEnabled: isVibrationEnabled,
|
||||
isLightFlashEnabled: isLightFlashEnabled,
|
||||
|
||||
@ -13,6 +13,7 @@ struct AlarmView: View {
|
||||
// MARK: - Properties
|
||||
@State private var viewModel = AlarmViewModel()
|
||||
@State private var showAddAlarm = false
|
||||
@State private var selectedAlarmForEdit: Alarm?
|
||||
|
||||
// MARK: - Body
|
||||
var body: some View {
|
||||
@ -35,7 +36,9 @@ struct AlarmView: View {
|
||||
await viewModel.toggleAlarm(id: alarm.id)
|
||||
}
|
||||
},
|
||||
onEdit: { /* TODO: Implement edit functionality */ }
|
||||
onEdit: {
|
||||
selectedAlarmForEdit = alarm
|
||||
}
|
||||
)
|
||||
}
|
||||
.onDelete(perform: deleteAlarm)
|
||||
@ -64,6 +67,12 @@ struct AlarmView: View {
|
||||
isPresented: $showAddAlarm
|
||||
)
|
||||
}
|
||||
.sheet(item: $selectedAlarmForEdit) { alarm in
|
||||
EditAlarmView(
|
||||
viewModel: viewModel,
|
||||
alarm: alarm
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private Methods
|
||||
|
||||
@ -0,0 +1,55 @@
|
||||
//
|
||||
// NotificationMessageEditView.swift
|
||||
// TheNoiseClock
|
||||
//
|
||||
// Created by Matt Bruce on 9/7/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// View for editing alarm notification message
|
||||
struct NotificationMessageEditView: View {
|
||||
@Binding var message: String
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: UIConstants.Spacing.large) {
|
||||
TextField("Notification message", text: $message)
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
.contentPadding(horizontal: UIConstants.Spacing.large)
|
||||
|
||||
// Preview section
|
||||
VStack(alignment: .leading, spacing: UIConstants.Spacing.small) {
|
||||
Text("Preview:")
|
||||
.font(.headline)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Alarm")
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
Text(message.isEmpty ? "Your alarm is ringing" : message)
|
||||
.font(.body)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemGray6))
|
||||
.cornerRadius(8)
|
||||
}
|
||||
.contentPadding(horizontal: UIConstants.Spacing.large)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.navigationTitle("Message")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.contentPadding(vertical: UIConstants.Spacing.large)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Preview
|
||||
#Preview {
|
||||
NavigationStack {
|
||||
NotificationMessageEditView(message: .constant("Your alarm is ringing"))
|
||||
}
|
||||
}
|
||||
165
TheNoiseClock/Views/Alarms/EditAlarmView.swift
Normal file
165
TheNoiseClock/Views/Alarms/EditAlarmView.swift
Normal file
@ -0,0 +1,165 @@
|
||||
//
|
||||
// EditAlarmView.swift
|
||||
// TheNoiseClock
|
||||
//
|
||||
// Created by Matt Bruce on 9/7/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// View for editing existing alarms
|
||||
struct EditAlarmView: View {
|
||||
|
||||
// MARK: - Properties
|
||||
let viewModel: AlarmViewModel
|
||||
let alarm: Alarm
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State private var alarmTime: Date
|
||||
@State private var selectedSoundName: String
|
||||
@State private var alarmLabel: String
|
||||
@State private var notificationMessage: String
|
||||
@State private var snoozeDuration: Int
|
||||
@State private var isVibrationEnabled: Bool
|
||||
@State private var isLightFlashEnabled: Bool
|
||||
@State private var volume: Float
|
||||
|
||||
// MARK: - Initialization
|
||||
init(viewModel: AlarmViewModel, alarm: Alarm) {
|
||||
self.viewModel = viewModel
|
||||
self.alarm = alarm
|
||||
|
||||
// Initialize state with current alarm values
|
||||
self._alarmTime = State(initialValue: alarm.time)
|
||||
self._selectedSoundName = State(initialValue: alarm.soundName)
|
||||
self._alarmLabel = State(initialValue: alarm.label)
|
||||
self._notificationMessage = State(initialValue: alarm.notificationMessage)
|
||||
self._snoozeDuration = State(initialValue: alarm.snoozeDuration)
|
||||
self._isVibrationEnabled = State(initialValue: alarm.isVibrationEnabled)
|
||||
self._isLightFlashEnabled = State(initialValue: alarm.isLightFlashEnabled)
|
||||
self._volume = State(initialValue: alarm.volume)
|
||||
}
|
||||
|
||||
// MARK: - Body
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack(spacing: 0) {
|
||||
// Time picker section at top
|
||||
TimePickerSection(selectedTime: $alarmTime)
|
||||
TimeUntilAlarmSection(alarmTime: alarmTime)
|
||||
|
||||
// List for settings below
|
||||
List {
|
||||
// Label Section
|
||||
NavigationLink(destination: LabelEditView(label: $alarmLabel)) {
|
||||
HStack {
|
||||
Image(systemName: "textformat")
|
||||
.foregroundColor(UIConstants.Colors.accentColor)
|
||||
.frame(width: 24)
|
||||
Text("Label")
|
||||
Spacer()
|
||||
Text(alarmLabel)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
// Notification Message Section
|
||||
NavigationLink(destination: NotificationMessageEditView(message: $notificationMessage)) {
|
||||
HStack {
|
||||
Image(systemName: "message")
|
||||
.foregroundColor(UIConstants.Colors.accentColor)
|
||||
.frame(width: 24)
|
||||
Text("Message")
|
||||
Spacer()
|
||||
Text(notificationMessage)
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Sound Section
|
||||
NavigationLink(destination: SoundSelectionView(selectedSound: $selectedSoundName)) {
|
||||
HStack {
|
||||
Image(systemName: "music.note")
|
||||
.foregroundColor(UIConstants.Colors.accentColor)
|
||||
.frame(width: 24)
|
||||
Text("Sound")
|
||||
Spacer()
|
||||
Text(getSoundDisplayName(selectedSoundName))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
// Snooze Section
|
||||
NavigationLink(destination: SnoozeSelectionView(snoozeDuration: $snoozeDuration)) {
|
||||
HStack {
|
||||
Image(systemName: "clock.arrow.circlepath")
|
||||
.foregroundColor(UIConstants.Colors.accentColor)
|
||||
.frame(width: 24)
|
||||
Text("Snooze")
|
||||
Spacer()
|
||||
Text("for \(snoozeDuration) min")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.insetGrouped)
|
||||
}
|
||||
.navigationTitle("Edit Alarm")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
}
|
||||
.foregroundColor(UIConstants.Colors.accentColor)
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Save") {
|
||||
Task {
|
||||
let updatedAlarm = Alarm(
|
||||
id: alarm.id, // Keep the same ID
|
||||
time: alarmTime,
|
||||
isEnabled: alarm.isEnabled, // Keep the same enabled state
|
||||
soundName: selectedSoundName,
|
||||
label: alarmLabel,
|
||||
notificationMessage: notificationMessage,
|
||||
snoozeDuration: snoozeDuration,
|
||||
isVibrationEnabled: isVibrationEnabled,
|
||||
isLightFlashEnabled: isLightFlashEnabled,
|
||||
volume: volume
|
||||
)
|
||||
await viewModel.updateAlarm(updatedAlarm)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
.foregroundColor(UIConstants.Colors.accentColor)
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helper Methods
|
||||
private func getSoundDisplayName(_ fileName: String) -> String {
|
||||
let alarmSounds = SoundConfigurationService.shared.getAlarmSounds()
|
||||
if let sound = alarmSounds.first(where: { $0.fileName == fileName }) {
|
||||
return sound.name
|
||||
}
|
||||
return fileName.replacingOccurrences(of: ".mp3", with: "").capitalized
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Preview
|
||||
#Preview {
|
||||
EditAlarmView(
|
||||
viewModel: AlarmViewModel(),
|
||||
alarm: Alarm(
|
||||
time: Date(),
|
||||
label: "Morning Alarm",
|
||||
notificationMessage: "Time to wake up!"
|
||||
)
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user