From f89df29f4c6135bad8053552e280364bdf538273 Mon Sep 17 00:00:00 2001 From: ahmermughal Date: Fri, 1 Aug 2025 22:27:53 +0200 Subject: [PATCH 1/2] multiple notifications functionality --- app/dime.xcodeproj/project.pbxproj | 8 + .../Utilities/UserDefaultsExtension.swift | 26 ++++ .../SettingsNotificationCustomTimeView.swift | 143 ++++++++++++++++++ .../SettingsNotificationView.swift | 141 ++++++++--------- 4 files changed, 240 insertions(+), 78 deletions(-) create mode 100644 app/dime/Utilities/UserDefaultsExtension.swift create mode 100644 app/dime/Views/Settings/Settings Subviews/SettingsNotificationCustomTimeView.swift diff --git a/app/dime.xcodeproj/project.pbxproj b/app/dime.xcodeproj/project.pbxproj index 4598761..7d305e6 100644 --- a/app/dime.xcodeproj/project.pbxproj +++ b/app/dime.xcodeproj/project.pbxproj @@ -154,6 +154,8 @@ AEADEF742AE94DC3006EB614 /* ToolbarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEADEF722AE94DC3006EB614 /* ToolbarButton.swift */; }; AEADEF752AE94DC3006EB614 /* Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEADEF732AE94DC3006EB614 /* Toolbar.swift */; }; AECE7F9E2AED1A6800B57267 /* SuggestedTransactions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AECE7F9D2AED1A6800B57267 /* SuggestedTransactions.swift */; }; + C5C6FA032E3D5A4800C6AED7 /* SettingsNotificationCustomTimeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5C6FA022E3D5A3600C6AED7 /* SettingsNotificationCustomTimeView.swift */; }; + C5C6FA052E3D5A6F00C6AED7 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5C6FA042E3D5A6E00C6AED7 /* UserDefaultsExtension.swift */; }; D63B204A2D8728C300CAFB20 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D63B20462D8728C300CAFB20 /* Localizable.strings */; }; D63B204B2D8728C300CAFB20 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = D63B20482D8728C300CAFB20 /* Localizable.stringsdict */; }; D6A4DE202D61744400F7F751 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = D6A4DE1F2D61744400F7F751 /* SwiftUIIntrospect */; }; @@ -331,6 +333,8 @@ AEADEF722AE94DC3006EB614 /* ToolbarButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToolbarButton.swift; sourceTree = ""; }; AEADEF732AE94DC3006EB614 /* Toolbar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Toolbar.swift; sourceTree = ""; }; AECE7F9D2AED1A6800B57267 /* SuggestedTransactions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestedTransactions.swift; sourceTree = ""; }; + C5C6FA022E3D5A3600C6AED7 /* SettingsNotificationCustomTimeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsNotificationCustomTimeView.swift; sourceTree = ""; }; + C5C6FA042E3D5A6E00C6AED7 /* UserDefaultsExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtension.swift; sourceTree = ""; }; D63B20472D8728C300CAFB20 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = Localizations/en.lproj/Localizable.strings; sourceTree = ""; }; D63B20492D8728C300CAFB20 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = Localizations/en.lproj/Localizable.stringsdict; sourceTree = ""; }; /* End PBXFileReference section */ @@ -472,6 +476,7 @@ 5327D296287C69A400F76ADF /* Utilities */ = { isa = PBXGroup; children = ( + C5C6FA042E3D5A6E00C6AED7 /* UserDefaultsExtension.swift */, 5358E9182AC885E3003E8CA9 /* AlertToast.swift */, 53A839C028D732E1006F227C /* TabBarHiding.swift */, 53A839B928D33840006F227C /* UnlockManager.swift */, @@ -621,6 +626,7 @@ 53D6BDDB2AF73E1900F5728E /* Settings Subviews */ = { isa = PBXGroup; children = ( + C5C6FA022E3D5A3600C6AED7 /* SettingsNotificationCustomTimeView.swift */, 53D6BDD92AF73DE900F5728E /* SettingsCloudView.swift */, 53D6BDD72AF73CFC00F5728E /* SettingsEraseView.swift */, 53D6BDCB2AF73BC800F5728E /* SettingsCurrencyView.swift */, @@ -924,6 +930,7 @@ 5327D2C8287C6B2500F76ADF /* ChartTimeFrame.swift in Sources */, 536FA6C12A7E05C600C52490 /* GetInsightsIntent.swift in Sources */, 533D1C3E2AE8023B00894764 /* PencilShape.swift in Sources */, + C5C6FA032E3D5A4800C6AED7 /* SettingsNotificationCustomTimeView.swift in Sources */, 532C58BC2A629C3900DA2C81 /* NewBudgetView.swift in Sources */, AE5B3D792AEE99E000AB364E /* NumberPad.swift in Sources */, 53D5F02328A8E0E4004573C8 /* InsightsWidget.swift in Sources */, @@ -972,6 +979,7 @@ 53D40B652AB1B2DB001D3866 /* UpdateSheet.swift in Sources */, 5327D2BA287C6AFB00F76ADF /* ViewExtension.swift in Sources */, 5327D2DB287C6B5F00F76ADF /* LogView.swift in Sources */, + C5C6FA052E3D5A6F00C6AED7 /* UserDefaultsExtension.swift in Sources */, 5362CF322A92750800244744 /* ImportDataView.swift in Sources */, 5327D287287C697400F76ADF /* dimeApp.swift in Sources */, 53A4147728B3B559008C30E7 /* WelcomeSheetView.swift in Sources */, diff --git a/app/dime/Utilities/UserDefaultsExtension.swift b/app/dime/Utilities/UserDefaultsExtension.swift new file mode 100644 index 0000000..b8ade32 --- /dev/null +++ b/app/dime/Utilities/UserDefaultsExtension.swift @@ -0,0 +1,26 @@ +// +// UserDefaultsExtension.swift +// dime +// +// Created by Ahmer Mughal on 01.08.25. +// + +import Foundation + +extension UserDefaults { + private static let customTimerKey = "customTimerList" + + func saveCustomTimerList(_ dates: [Date]) { + if let encoded = try? JSONEncoder().encode(dates) { + set(encoded, forKey: UserDefaults.customTimerKey) + } + } + + func loadCustomTimerList() -> [Date] { + if let savedData = data(forKey: UserDefaults.customTimerKey), + let decoded = try? JSONDecoder().decode([Date].self, from: savedData) { + return decoded + } + return [] + } +} diff --git a/app/dime/Views/Settings/Settings Subviews/SettingsNotificationCustomTimeView.swift b/app/dime/Views/Settings/Settings Subviews/SettingsNotificationCustomTimeView.swift new file mode 100644 index 0000000..e4baac3 --- /dev/null +++ b/app/dime/Views/Settings/Settings Subviews/SettingsNotificationCustomTimeView.swift @@ -0,0 +1,143 @@ +// +// SettingsNotificationCustomTimeView.swift +// dime +// +// Created by Ahmer Mughal on 31.07.25. +// + +import SwiftUI + +struct SettingsNotificationCustomTimeView: View { + @Environment(\.presentationMode) var presentationMode: Binding + + @Binding var customTimerList: [Date] + + var onDismiss: (() -> Void) + + var body: some View { + VStack { + Text("Notifications") + .font(.system(.title3, design: .rounded).weight(.semibold)) + .foregroundColor(Color.PrimaryText) + .frame(maxWidth: .infinity) + .overlay(alignment: .leading) { + Button { + self.presentationMode.wrappedValue.dismiss() + self.onDismiss() + } label: { + SettingsBackButton() + } + } + .padding(.bottom, 20) + .padding(.horizontal, 20) + + if #available(iOS 16.0, *) { + List { + ForEach(customTimerList.indices, id: \.self) { index in + HStack{ + Text("Custom Time") + .font(.system(.body, design: .rounded)) + .foregroundColor(Color.PrimaryText) + + Spacer() + + DatePicker( + "Select Time", + selection: Binding( + get: { customTimerList[index] }, + set: { customTimerList[index] = $0 } + ), + displayedComponents: .hourAndMinute + ) + .labelsHidden() + } + .listRowBackground(Color.SettingsBackground) + .padding(.vertical, 1) + + } + .onDelete(perform: delete) + + Button(action: { + customTimerList.append(Date()) // Add new date picker + }) { + HStack { + Image(systemName: "plus.circle.fill") + .font(.title2) + .foregroundColor(Color.PrimaryText) + Text("Add Time") + .font(.system(.body, design: .rounded)) + .foregroundColor(Color.PrimaryText) + } + .foregroundColor(.blue) + .frame(maxWidth: .infinity, alignment: .center) + .padding(.vertical, 4) + } + .listRowBackground(Color.SettingsBackground) + + } + .scrollContentBackground(.hidden) + + } else { + List { + ForEach(customTimerList.indices, id: \.self) { index in + HStack{ + Text("Custom Time") + .font(.system(.body, design: .rounded)) + .foregroundColor(Color.PrimaryText) + + Spacer() + + DatePicker( + "Select Time", + selection: Binding( + get: { customTimerList[index] }, + set: { customTimerList[index] = $0 } + ), + displayedComponents: .hourAndMinute + ) + .labelsHidden() + } + .listRowBackground(Color.SettingsBackground) + .padding(.vertical, 1) + + } + + Button(action: { + customTimerList.append(Date()) + }) { + HStack { + Image(systemName: "plus.circle.fill") + .font(.title2) + .foregroundColor(Color.PrimaryText) + Text("Add Time") + .font(.system(.body, design: .rounded)) + .foregroundColor(Color.PrimaryText) + } + .foregroundColor(.blue) + .frame(maxWidth: .infinity, alignment: .center) + .padding(.vertical, 9) + .padding(.horizontal, 0) + } + .listRowBackground(Color.SettingsBackground) + } + .background(Color.clear) + } + + } + .navigationBarBackButtonHidden(true) + .navigationBarTitle("") + .navigationBarHidden(true) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + .background(Color.PrimaryBackground) + .dynamicTypeSize(...DynamicTypeSize.xxxLarge) + .padding(.vertical, 20) + } + + func delete(at offsets: IndexSet) { + customTimerList.remove(atOffsets: offsets) + } +} + +#Preview { + SettingsNotificationCustomTimeView(customTimerList: .constant([Date.now, Date.now]), onDismiss: {}) +} diff --git a/app/dime/Views/Settings/Settings Subviews/SettingsNotificationView.swift b/app/dime/Views/Settings/Settings Subviews/SettingsNotificationView.swift index 3d85240..974b26d 100644 --- a/app/dime/Views/Settings/Settings Subviews/SettingsNotificationView.swift +++ b/app/dime/Views/Settings/Settings Subviews/SettingsNotificationView.swift @@ -16,7 +16,7 @@ struct SettingsNotificationsView: View { @AppStorage("notificationsEnabled", store: UserDefaults(suiteName: "group.com.rafaelsoh.dime")) var notificationsEnabled: Bool = true @State var option = 1 - @State var customTime = Date.now + @State private var customTimerList: [Date] = [] var center = UNUserNotificationCenter.current() @@ -157,35 +157,33 @@ struct SettingsNotificationsView: View { .overlay(alignment: .bottom) { Divider() } + + NavigationLink(destination: SettingsNotificationCustomTimeView(customTimerList: $customTimerList, onDismiss: onDismiss)) { + + HStack { + Text("Custom Time") + .font(.system(.body, design: .rounded)) + .foregroundColor(Color.PrimaryText) + + Spacer() + + if option == 3 { + Image(systemName: "checkmark") + .font(.system(.subheadline, design: .rounded)) + .foregroundColor(.DarkIcon.opacity(0.6)) + .matchedGeometryEffect(id: "tick", in: animation) + } + + Image(systemName: "chevron.forward") + .font(.system(.subheadline, design: .rounded)) + .foregroundColor(.DarkIcon.opacity(0.6)) - HStack { - Text("Custom Time") - .font(.system(.body, design: .rounded)) - .foregroundColor(Color.PrimaryText) - - Spacer() - - if option == 3 { - DatePicker( - "Custom notification time", selection: $customTime, - displayedComponents: .hourAndMinute - ) - .labelsHidden() - - Image(systemName: "checkmark") - .font(.system(.subheadline, design: .rounded)) - .foregroundColor(.DarkIcon.opacity(0.6)) - .matchedGeometryEffect(id: "tick", in: animation) - } - } - .frame(maxWidth: .infinity) - .contentShape(Rectangle()) - .onTapGesture { - withAnimation(.easeIn(duration: 0.15)) { - option = 3 + } + .frame(maxWidth: .infinity) + .contentShape(Rectangle()) + .padding(.vertical, 9) } - } - .padding(.vertical, 9) + } .padding(.horizontal, 15) .background(Color.SettingsBackground, in: RoundedRectangle(cornerRadius: 9)) @@ -193,25 +191,10 @@ struct SettingsNotificationsView: View { UserDefaults(suiteName: "group.com.rafaelsoh.dime")!.set( option, forKey: "notificationOption") - if newValue == 3 { - let components = Calendar.current.dateComponents([.hour, .minute], from: customTime) - - UserDefaults(suiteName: "group.com.rafaelsoh.dime")!.set( - components.hour!, forKey: "customHour") - UserDefaults(suiteName: "group.com.rafaelsoh.dime")!.set( - components.minute!, forKey: "customMinute") - } - - newNotification() + newNotification() } - .onChange(of: customTime) { _ in - let components = Calendar.current.dateComponents([.hour, .minute], from: customTime) - - UserDefaults(suiteName: "group.com.rafaelsoh.dime")!.set( - components.hour!, forKey: "customHour") - UserDefaults(suiteName: "group.com.rafaelsoh.dime")!.set( - components.minute!, forKey: "customMinute") - + .onChange(of: customTimerList) { _ in + UserDefaults(suiteName: "group.com.rafaelsoh.dime")!.saveCustomTimerList(customTimerList) newNotification() } .onAppear { @@ -221,22 +204,16 @@ struct SettingsNotificationsView: View { forKey: "notificationOption") } - if UserDefaults(suiteName: "group.com.rafaelsoh.dime")!.object(forKey: "customHour") - != nil - && UserDefaults(suiteName: "group.com.rafaelsoh.dime")!.object(forKey: "customMinute") - != nil { - var components = DateComponents() - components.hour = UserDefaults(suiteName: "group.com.rafaelsoh.dime")!.integer( - forKey: "customHour") - components.minute = UserDefaults(suiteName: "group.com.rafaelsoh.dime")!.integer( - forKey: "customMinute") - customTime = Calendar.current.date(from: components)! - } + self.customTimerList = UserDefaults(suiteName: "group.com.rafaelsoh.dime")!.loadCustomTimerList() } } } .modifier(SettingsSubviewModifier()) } + + private func onDismiss(){ + self.option = 3 + } } func newNotification() { @@ -248,7 +225,7 @@ func newNotification() { content.sound = UNNotificationSound.default // show this notification five seconds from now - var components = DateComponents() + var dateComponentsList : [DateComponents] = [] var option = 1 if UserDefaults(suiteName: "group.com.rafaelsoh.dime")!.object(forKey: "notificationOption") @@ -258,30 +235,38 @@ func newNotification() { } if option == 1 { - components.hour = 8 - components.minute = 0 + var dateComponents = DateComponents() + dateComponents.hour = 8 + dateComponents.minute = 0 + dateComponentsList.append(dateComponents) } else if option == 2 { - components.hour = 20 - components.minute = 0 + var dateComponents = DateComponents() + dateComponents.hour = 20 + dateComponents.minute = 0 + dateComponentsList.append(dateComponents) } else { - if UserDefaults(suiteName: "group.com.rafaelsoh.dime")!.object(forKey: "customHour") != nil, - UserDefaults(suiteName: "group.com.rafaelsoh.dime")!.object(forKey: "customMinute") != nil { - components.hour = UserDefaults(suiteName: "group.com.rafaelsoh.dime")!.integer( - forKey: "customHour") - components.minute = UserDefaults(suiteName: "group.com.rafaelsoh.dime")!.integer( - forKey: "customMinute") - } else { - components.hour = 8 - components.minute = 0 - } + let customTimes = UserDefaults(suiteName: "group.com.rafaelsoh.dime")!.loadCustomTimerList() + if !customTimes.isEmpty { + for item in customTimes { + let dateComponents = Calendar.current.dateComponents([.hour, .minute], from: item) + dateComponentsList.append(dateComponents) + } + } else { + var dateComponent = DateComponents() + dateComponent.hour = 8 + dateComponent.minute = 0 + dateComponentsList.append(dateComponent) + } } - let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: true) + for components in dateComponentsList { + let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: true) + // choose a random identifier + let request = UNNotificationRequest( + identifier: UUID().uuidString, content: content, trigger: trigger) + // add our notification request + UNUserNotificationCenter.current().add(request) + } - // choose a random identifier - let request = UNNotificationRequest( - identifier: UUID().uuidString, content: content, trigger: trigger) - // add our notification request - UNUserNotificationCenter.current().add(request) } From 827e851ccc7585dd069951b71b43c43ad5e68d8e Mon Sep 17 00:00:00 2001 From: ahmermughal Date: Fri, 1 Aug 2025 22:37:32 +0200 Subject: [PATCH 2/2] title fix --- .../Settings Subviews/SettingsNotificationCustomTimeView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/dime/Views/Settings/Settings Subviews/SettingsNotificationCustomTimeView.swift b/app/dime/Views/Settings/Settings Subviews/SettingsNotificationCustomTimeView.swift index e4baac3..3e1e820 100644 --- a/app/dime/Views/Settings/Settings Subviews/SettingsNotificationCustomTimeView.swift +++ b/app/dime/Views/Settings/Settings Subviews/SettingsNotificationCustomTimeView.swift @@ -16,7 +16,7 @@ struct SettingsNotificationCustomTimeView: View { var body: some View { VStack { - Text("Notifications") + Text("Custom Times") .font(.system(.title3, design: .rounded).weight(.semibold)) .foregroundColor(Color.PrimaryText) .frame(maxWidth: .infinity)