diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f6e847 --- /dev/null +++ b/.gitignore @@ -0,0 +1,131 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/osx,swift,xcode +# Edit at https://www.toptal.com/developers/gitignore?templates=osx,swift,xcode + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Swift ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +### Xcode ### + +## Xcode 8 and earlier + +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcodeproj/project.xcworkspace/ +!*.xcworkspace/contents.xcworkspacedata +/*.gcno +**/xcshareddata/WorkspaceSettings.xcsettings + +# End of https://www.toptal.com/developers/gitignore/api/osx,swift,xcode diff --git a/Screaming Reminder.xcodeproj/project.pbxproj b/Screaming Reminder.xcodeproj/project.pbxproj new file mode 100644 index 0000000..615baf5 --- /dev/null +++ b/Screaming Reminder.xcodeproj/project.pbxproj @@ -0,0 +1,406 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 67CF41D22C3FED90002906E5 /* Screaming_ReminderApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CF41D12C3FED90002906E5 /* Screaming_ReminderApp.swift */; }; + 67CF41D42C3FED90002906E5 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CF41D32C3FED90002906E5 /* ContentView.swift */; }; + 67CF41D62C3FED90002906E5 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CF41D52C3FED90002906E5 /* Notification.swift */; }; + 67CF41D82C3FED91002906E5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 67CF41D72C3FED91002906E5 /* Assets.xcassets */; }; + 67CF41DB2C3FED91002906E5 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 67CF41DA2C3FED91002906E5 /* Preview Assets.xcassets */; }; + 67CF41E52C4080DE002906E5 /* Reminder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CF41E42C4080DE002906E5 /* Reminder.swift */; }; + 67CF41E72C408E73002906E5 /* NotificationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CF41E62C408E73002906E5 /* NotificationDelegate.swift */; }; + 67CF41EB2C4157DA002906E5 /* NotificationsUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67CF41EA2C4157DA002906E5 /* NotificationsUtils.swift */; }; + 67E5F0382C4BED68005DDD7A /* SheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E5F0372C4BED68005DDD7A /* SheetView.swift */; }; + 67E5F03A2C504501005DDD7A /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67E5F0392C504501005DDD7A /* Extensions.swift */; }; + 67E5F0402C53D898005DDD7A /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = 67E5F03F2C53D898005DDD7A /* .gitignore */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 67CF41CE2C3FED90002906E5 /* Screaming Reminder.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Screaming Reminder.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 67CF41D12C3FED90002906E5 /* Screaming_ReminderApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Screaming_ReminderApp.swift; sourceTree = ""; }; + 67CF41D32C3FED90002906E5 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 67CF41D52C3FED90002906E5 /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = ""; }; + 67CF41D72C3FED91002906E5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 67CF41DA2C3FED91002906E5 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 67CF41E42C4080DE002906E5 /* Reminder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reminder.swift; sourceTree = ""; }; + 67CF41E62C408E73002906E5 /* NotificationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationDelegate.swift; sourceTree = ""; }; + 67CF41EA2C4157DA002906E5 /* NotificationsUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsUtils.swift; sourceTree = ""; }; + 67CF41EC2C41C5DF002906E5 /* TODO.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = TODO.md; sourceTree = ""; }; + 67E5F0372C4BED68005DDD7A /* SheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetView.swift; sourceTree = ""; }; + 67E5F0392C504501005DDD7A /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; + 67E5F03F2C53D898005DDD7A /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 67CF41CB2C3FED90002906E5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 67CF41C52C3FED90002906E5 = { + isa = PBXGroup; + children = ( + 67E5F03F2C53D898005DDD7A /* .gitignore */, + 67CF41D02C3FED90002906E5 /* Screaming Reminder */, + 67CF41CF2C3FED90002906E5 /* Products */, + ); + sourceTree = ""; + }; + 67CF41CF2C3FED90002906E5 /* Products */ = { + isa = PBXGroup; + children = ( + 67CF41CE2C3FED90002906E5 /* Screaming Reminder.app */, + ); + name = Products; + sourceTree = ""; + }; + 67CF41D02C3FED90002906E5 /* Screaming Reminder */ = { + isa = PBXGroup; + children = ( + 67E5F03C2C53CDD7005DDD7A /* Views */, + 67E5F03B2C53CDC3005DDD7A /* Models */, + 67CF41E12C406895002906E5 /* Utils */, + 67CF41D12C3FED90002906E5 /* Screaming_ReminderApp.swift */, + 67CF41D72C3FED91002906E5 /* Assets.xcassets */, + 67CF41D92C3FED91002906E5 /* Preview Content */, + 67CF41EC2C41C5DF002906E5 /* TODO.md */, + ); + path = "Screaming Reminder"; + sourceTree = ""; + }; + 67CF41D92C3FED91002906E5 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 67CF41DA2C3FED91002906E5 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 67CF41E12C406895002906E5 /* Utils */ = { + isa = PBXGroup; + children = ( + 67CF41E62C408E73002906E5 /* NotificationDelegate.swift */, + 67CF41EA2C4157DA002906E5 /* NotificationsUtils.swift */, + ); + path = Utils; + sourceTree = ""; + }; + 67E5F03B2C53CDC3005DDD7A /* Models */ = { + isa = PBXGroup; + children = ( + 67E5F0392C504501005DDD7A /* Extensions.swift */, + 67CF41D52C3FED90002906E5 /* Notification.swift */, + 67CF41E42C4080DE002906E5 /* Reminder.swift */, + ); + path = Models; + sourceTree = ""; + }; + 67E5F03C2C53CDD7005DDD7A /* Views */ = { + isa = PBXGroup; + children = ( + 67CF41D32C3FED90002906E5 /* ContentView.swift */, + 67E5F0372C4BED68005DDD7A /* SheetView.swift */, + ); + path = Views; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 67CF41CD2C3FED90002906E5 /* Screaming Reminder */ = { + isa = PBXNativeTarget; + buildConfigurationList = 67CF41DE2C3FED91002906E5 /* Build configuration list for PBXNativeTarget "Screaming Reminder" */; + buildPhases = ( + 67CF41CA2C3FED90002906E5 /* Sources */, + 67CF41CB2C3FED90002906E5 /* Frameworks */, + 67CF41CC2C3FED90002906E5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Screaming Reminder"; + productName = "Screaming Reminder"; + productReference = 67CF41CE2C3FED90002906E5 /* Screaming Reminder.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 67CF41C62C3FED90002906E5 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1540; + LastUpgradeCheck = 1540; + TargetAttributes = { + 67CF41CD2C3FED90002906E5 = { + CreatedOnToolsVersion = 15.4; + }; + }; + }; + buildConfigurationList = 67CF41C92C3FED90002906E5 /* Build configuration list for PBXProject "Screaming Reminder" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = fr; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + fr, + ); + mainGroup = 67CF41C52C3FED90002906E5; + productRefGroup = 67CF41CF2C3FED90002906E5 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 67CF41CD2C3FED90002906E5 /* Screaming Reminder */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 67CF41CC2C3FED90002906E5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 67CF41DB2C3FED91002906E5 /* Preview Assets.xcassets in Resources */, + 67E5F0402C53D898005DDD7A /* .gitignore in Resources */, + 67CF41D82C3FED91002906E5 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 67CF41CA2C3FED90002906E5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 67CF41E72C408E73002906E5 /* NotificationDelegate.swift in Sources */, + 67E5F03A2C504501005DDD7A /* Extensions.swift in Sources */, + 67CF41D42C3FED90002906E5 /* ContentView.swift in Sources */, + 67CF41EB2C4157DA002906E5 /* NotificationsUtils.swift in Sources */, + 67CF41D62C3FED90002906E5 /* Notification.swift in Sources */, + 67CF41D22C3FED90002906E5 /* Screaming_ReminderApp.swift in Sources */, + 67E5F0382C4BED68005DDD7A /* SheetView.swift in Sources */, + 67CF41E52C4080DE002906E5 /* Reminder.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 67CF41DC2C3FED91002906E5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 67CF41DD2C3FED91002906E5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 67CF41DF2C3FED91002906E5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Screaming Reminder/Preview Content\""; + DEVELOPMENT_TEAM = SH3XZXB7R8; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = "REMINDER!"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "apps.amine.bou.Screaming-Reminder"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 67CF41E02C3FED91002906E5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Screaming Reminder/Preview Content\""; + DEVELOPMENT_TEAM = SH3XZXB7R8; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = "REMINDER!"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "apps.amine.bou.Screaming-Reminder"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 67CF41C92C3FED90002906E5 /* Build configuration list for PBXProject "Screaming Reminder" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 67CF41DC2C3FED91002906E5 /* Debug */, + 67CF41DD2C3FED91002906E5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 67CF41DE2C3FED91002906E5 /* Build configuration list for PBXNativeTarget "Screaming Reminder" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 67CF41DF2C3FED91002906E5 /* Debug */, + 67CF41E02C3FED91002906E5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 67CF41C62C3FED90002906E5 /* Project object */; +} diff --git a/Screaming Reminder.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Screaming Reminder.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/Screaming Reminder.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Screaming Reminder.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Screaming Reminder.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Screaming Reminder.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Screaming Reminder.xcodeproj/xcshareddata/xcschemes/Screaming Reminder.xcscheme b/Screaming Reminder.xcodeproj/xcshareddata/xcschemes/Screaming Reminder.xcscheme new file mode 100644 index 0000000..0cb2931 --- /dev/null +++ b/Screaming Reminder.xcodeproj/xcshareddata/xcschemes/Screaming Reminder.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Screaming Reminder.xcodeproj/xcuserdata/amine.xcuserdatad/xcschemes/xcschememanagement.plist b/Screaming Reminder.xcodeproj/xcuserdata/amine.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..d3dc0e0 --- /dev/null +++ b/Screaming Reminder.xcodeproj/xcuserdata/amine.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,22 @@ + + + + + SchemeUserState + + Screaming Reminder.xcscheme_^#shared#^_ + + orderHint + 0 + + + SuppressBuildableAutocreation + + 67CF41CD2C3FED90002906E5 + + primary + + + + + diff --git a/Screaming Reminder/Assets.xcassets/AccentColor.colorset/Contents.json b/Screaming Reminder/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/Screaming Reminder/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Screaming Reminder/Assets.xcassets/AppIcon.appiconset/Contents.json b/Screaming Reminder/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..8582a6c --- /dev/null +++ b/Screaming Reminder/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "Frame 1(1).png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Screaming Reminder/Assets.xcassets/AppIcon.appiconset/Frame 1(1).png b/Screaming Reminder/Assets.xcassets/AppIcon.appiconset/Frame 1(1).png new file mode 100644 index 0000000..4f4ce06 Binary files /dev/null and b/Screaming Reminder/Assets.xcassets/AppIcon.appiconset/Frame 1(1).png differ diff --git a/Screaming Reminder/Assets.xcassets/Contents.json b/Screaming Reminder/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Screaming Reminder/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Screaming Reminder/Models/Extensions.swift b/Screaming Reminder/Models/Extensions.swift new file mode 100644 index 0000000..6aa8b31 --- /dev/null +++ b/Screaming Reminder/Models/Extensions.swift @@ -0,0 +1,33 @@ +// +// Extensions.swift +// Screaming Reminder +// +// Created by Amine Bou on 23/07/2024. +// + +import Foundation + +extension Date { + func withSpecificHour(hour: Int) -> Date { + var dateComponents = Calendar.current.dateComponents(in: TimeZone.current, from: self) + dateComponents.hour = hour + dateComponents.minute = 0 + return Calendar.current.date(from: dateComponents).unsafelyUnwrapped + } + + func atZeroMinutes() -> Date { + var dateComponents = Calendar.current.dateComponents(in: TimeZone.current, from: self) + dateComponents.minute = 0 + return Calendar.current.date(from: dateComponents).unsafelyUnwrapped + } + +} + +extension Set { + mutating func insertAll(_ newMembers: [Set.Element]) { + newMembers.forEach { (member) in + self.insert(member) + } + } + +} diff --git a/Screaming Reminder/Models/Notification.swift b/Screaming Reminder/Models/Notification.swift new file mode 100644 index 0000000..f24c966 --- /dev/null +++ b/Screaming Reminder/Models/Notification.swift @@ -0,0 +1,35 @@ +// +// Item.swift +// Screaming Reminder +// +// Created by Amine Bou on 11/07/2024. +// + +import Foundation +import SwiftUI + +final class Notification: CustomStringConvertible { + var reminder: Reminder + var triggers: [UNCalendarNotificationTrigger] + + + init(reminder: Reminder) { + self.reminder = reminder + self.triggers = getTriggersForReminder(reminder: reminder) + } + + public var description: String { return "\(reminder) + \(triggers)" } + + +} + +func getTriggersForReminder(reminder: Reminder) -> [UNCalendarNotificationTrigger] { + return reminder.when.map { + let components = NSCalendar.current.dateComponents([.second, .minute, .hour], from: $0) + return UNCalendarNotificationTrigger(dateMatching: components, repeats: true) + } +} + +func getIdentifier(reminder: Reminder, trigger: UNCalendarNotificationTrigger) -> String { + return "\(reminder.label)-\(trigger.dateComponents.hour ?? -1)-\(trigger.dateComponents.minute ?? -1)" +} diff --git a/Screaming Reminder/Models/Reminder.swift b/Screaming Reminder/Models/Reminder.swift new file mode 100644 index 0000000..3200b25 --- /dev/null +++ b/Screaming Reminder/Models/Reminder.swift @@ -0,0 +1,33 @@ +// +// Reminder.swift +// Screaming Reminder +// +// Created by Amine Bou on 11/07/2024. +// +import SwiftUI +import SwiftData + + +@Model +final class Reminder: CustomStringConvertible { + + var label: String + var when: [Date] + + + required init(label: String, when: [Date]) { + self.label = label + self.when = when + + } + + public var description: String { + let dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.timeStyle = .short + return formatter + }() + let smallestDate = self.when.sorted()[0] + return "\(label) - Tous les jours, à partir de \(dateFormatter.string(from: smallestDate))" + } +} diff --git a/Screaming Reminder/Preview Content/Preview Assets.xcassets/Contents.json b/Screaming Reminder/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Screaming Reminder/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Screaming Reminder/Screaming_ReminderApp.swift b/Screaming Reminder/Screaming_ReminderApp.swift new file mode 100644 index 0000000..db053b0 --- /dev/null +++ b/Screaming Reminder/Screaming_ReminderApp.swift @@ -0,0 +1,64 @@ +// +// Screaming_ReminderApp.swift +// Screaming Reminder +// +// Created by Amine Bou on 11/07/2024. +// + +import SwiftUI +import SwiftData + +@main +struct Screaming_ReminderApp: App { + private var notificDelegate : NotificationDelegate = NotificationDelegate() + let modelContainer: ModelContainer + + var body: some Scene { + WindowGroup { + ContentView() + } + .modelContainer(modelContainer) + } + + init() { + do { + let schema = Schema([ + Reminder.self, + ]) + let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) + + modelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration]) + } catch { + fatalError("Could not create ModelContainer: \(error)") + } + registerForNotification() + + // Define the custom actions. + let doneAction = UNNotificationAction(identifier: "DONE_ACTION", + title: "Fait !", + options: []) + + let notificationCategory = + UNNotificationCategory(identifier: "CAT", + actions: [doneAction], + intentIdentifiers: [], + hiddenPreviewsBodyPlaceholder: "", + options: .customDismissAction) + // Register the reminder type. + let notificationCenter = UNUserNotificationCenter.current() + notificationCenter.setNotificationCategories([notificationCategory]) + + UNUserNotificationCenter.current().delegate = notificDelegate + + } + + func registerForNotification() { + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in + if success { + } else { + exit(0) + } + } + } + +} diff --git a/Screaming Reminder/TODO.md b/Screaming Reminder/TODO.md new file mode 100644 index 0000000..0121b46 --- /dev/null +++ b/Screaming Reminder/TODO.md @@ -0,0 +1,8 @@ +# TODO + +- Au lieu de virer toutes les notifs, ignorer les suivantes aujourd'hui + +# Later + +- TimeDiffs manuelles + diff --git a/Screaming Reminder/Utils/NotificationDelegate.swift b/Screaming Reminder/Utils/NotificationDelegate.swift new file mode 100644 index 0000000..a9f942c --- /dev/null +++ b/Screaming Reminder/Utils/NotificationDelegate.swift @@ -0,0 +1,44 @@ +// +// NotificationDelegate.swift +// Screaming Reminder +// +// Created by Amine Bou on 12/07/2024. +// + +import Foundation +import SwiftUI +import SwiftData + +class NotificationDelegate: NSObject , UNUserNotificationCenterDelegate{ + + func userNotificationCenter(_ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: + @escaping () -> Void) { + + let title = response.notification.request.content.title + switch response.actionIdentifier { + case "DONE_ACTION": + let content = UNMutableNotificationContent() + content.title = "Bravo !" + content.subtitle = "Tu as fais ce que tu devais faire ! (Il faudra ignorer les autres notifications 😅)" + content.sound = UNNotificationSound.default + + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3, repeats: false) + + // choose a random identifier + let request = UNNotificationRequest(identifier: "\(title)-done", content: content, trigger: trigger) + + // add our reminder request + UNUserNotificationCenter.current().add(request) + + break + default: + exit(0) + break + } + + // Always call the completion handler when done. + completionHandler() + } +} diff --git a/Screaming Reminder/Utils/NotificationsUtils.swift b/Screaming Reminder/Utils/NotificationsUtils.swift new file mode 100644 index 0000000..091969a --- /dev/null +++ b/Screaming Reminder/Utils/NotificationsUtils.swift @@ -0,0 +1,35 @@ +// +// NotificationsUtils.swift +// Screaming Reminder +// +// Created by Amine Bou on 12/07/2024. +// + +import Foundation +import SwiftUI +import SwiftData + + +func scheduleNotifications(reminder: Reminder) { + let notification = Notification(reminder: reminder) + notification.triggers.forEach { + let content = UNMutableNotificationContent() + content.title = notification.reminder.label + content.sound = UNNotificationSound.defaultCritical + content.categoryIdentifier = "CAT" + + let request = UNNotificationRequest(identifier: getIdentifier(reminder: reminder, trigger: $0), content: content, trigger: $0) + + // add our reminder request + UNUserNotificationCenter.current().add(request) + } +} + +func cancelNotifications(reminder: Reminder) { + let identifiers = getTriggersForReminder(reminder: reminder).map { + getIdentifier(reminder: reminder, trigger: $0) + } + + UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: identifiers) + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: identifiers) +} diff --git a/Screaming Reminder/Views/ContentView.swift b/Screaming Reminder/Views/ContentView.swift new file mode 100644 index 0000000..fb6a96e --- /dev/null +++ b/Screaming Reminder/Views/ContentView.swift @@ -0,0 +1,90 @@ +// +// ContentView.swift +// Screaming Reminder +// +// Created by Amine Bou on 11/07/2024. +// + +import SwiftUI +import SwiftData + +struct ContentView: View { + @Query private var reminders: [Reminder] + + @State var shouldPresentSheet = false + @State var selectedLabel: String = "" + @State var selectedReminder: Reminder? + @State var selectedDate = [Date()] + @State var showingAlert: Bool = false + + var body: some View { + NavigationSplitView { + List { + ForEach(reminders) { reminder in + Button { + showModal(reminder: reminder) + } label: { + Text("\(reminder)") + } + } + } + .toolbar { + ToolbarItem { + Button { + showModal() + } label : { + Label("Add Item", systemImage: "plus") + } + } + #if targetEnvironment(simulator) + ToolbarItem { + Button(action: testNotifs) { + Label("Test notifs", systemImage: "volume") + } + } + ToolbarItem { + Button(action: { + UNUserNotificationCenter.current().removeAllPendingNotificationRequests() + }) { + Label("Delete", systemImage: "speaker.slash") + } + } +#endif + }.sheet(isPresented: $shouldPresentSheet) { + selectedDate = [Date()] + selectedReminder = nil + selectedLabel = "" + } content: { + SheetView(selectedLabel: $selectedLabel, selectedReminder: $selectedReminder, selectedDate: $selectedDate) + } + } detail: { + Text("Select a reminder") + } + } + + private func testNotifs() { + UNUserNotificationCenter.current().getPendingNotificationRequests(completionHandler: { + for notification in $0 { + print("\(notification.identifier) - \(notification.trigger!) \((notification.trigger! as! UNCalendarNotificationTrigger).nextTriggerDate())") + } + + }) + } + + private func showModal(reminder: Reminder? = nil) { + withAnimation { + if (reminder != nil) { + selectedReminder = reminder + selectedLabel = reminder!.label + selectedDate = reminder!.when + } + shouldPresentSheet = true + } + } + +} + +#Preview { + ContentView() + .modelContainer(for: Reminder.self, inMemory: true) +} diff --git a/Screaming Reminder/Views/SheetView.swift b/Screaming Reminder/Views/SheetView.swift new file mode 100644 index 0000000..7c98357 --- /dev/null +++ b/Screaming Reminder/Views/SheetView.swift @@ -0,0 +1,96 @@ +// +// SheetView.swift +// Screaming Reminder +// +// Created by Amine Bou on 20/07/2024. +// + +import SwiftUI +import SwiftData + +struct SheetView: View { + @Environment(\.dismiss) private var dismiss + @Environment(\.modelContext) private var modelContext + + + @Binding var selectedLabel: String + @Binding var selectedReminder: Reminder? + @Binding var selectedDate: [Date] + + var body: some View { + + Form { + LabeledContent { + TextField("", text: $selectedLabel) + } label: { + Text("Nom") + } + + VStack { + ForEach($selectedDate.indices, id: \.self) { i in + HStack { + DatePicker("Quand", selection: $selectedDate[i], displayedComponents: .hourAndMinute) + Button(action: { + selectedDate.remove(at: i) + }) { + Label("", systemImage: "minus.circle.fill") + }.buttonStyle(.borderless) + .disabled(selectedDate.count == 1) + } + + } + } + + Button(action: { + selectedDate.append(selectedDate.last!) + }) { + Label("Ajouter une notification", systemImage: "plus") + }.buttonStyle(.borderless) + + HStack { + Button("Cancel") { + dismiss() + } + if ($selectedReminder.wrappedValue != nil) { + Spacer() + Button("Supprimer") { + deleteReminder() + dismiss() + }.buttonStyle(.bordered) + } + Spacer() + Button("Confirm") { + upsertReminder() + dismiss() + }.buttonStyle(.borderedProminent) + } + }.formStyle(.grouped) + .textFieldStyle(.roundedBorder) + + } + + private func deleteReminder() { + withAnimation { + cancelNotifications(reminder: $selectedReminder.wrappedValue!) + modelContext.delete($selectedReminder.wrappedValue!) + } + } + private func upsertReminder() { + withAnimation { + let reminder: Reminder + if (selectedReminder != nil) { + cancelNotifications(reminder: $selectedReminder.wrappedValue!) + // Should i Wait ? + + selectedReminder!.label = $selectedLabel.wrappedValue + selectedReminder!.when = $selectedDate.wrappedValue + reminder = selectedReminder! + } else { + reminder = Reminder(label: $selectedLabel.wrappedValue, when: $selectedDate.wrappedValue) + modelContext.insert(reminder) + } + scheduleNotifications(reminder: reminder) + } + } + +}