diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..c5cb9c6 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: kimar # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.gitignore b/.gitignore index ebbef4b..9861489 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,51 @@ + +# Created by https://www.gitignore.io/api/xcode,swift,macos,carthage + +### Carthage ### +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +### macOS ### +*.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 -.DS_Store -*/build/* +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings *.pbxuser !default.pbxuser *.mode1v3 @@ -9,12 +54,62 @@ !default.mode2v3 *.perspectivev3 !default.perspectivev3 -xcuserdata -profile +xcuserdata/ + +## Other *.moved-aside -DerivedData -.idea/ +*.xccheckout +*.xcscmblueprint + +## Obj-C/Swift specific *.hmap +*.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 +.build/ + +# CocoaPods - Refactored to standalone file + +# Carthage - Refactored to standalone file + +# 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 +fastlane/test_output + +### Xcode ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated + +## Various settings + +## Other + +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcworkspace/contents.xcworkspacedata +/*.gcno -#CocoaPods -Pods +# End of https://www.gitignore.io/api/xcode,swift,macos,carthage diff --git a/DeveloperExcuses.xcodeproj/project.pbxproj b/DeveloperExcuses.xcodeproj/project.pbxproj index fe05a8f..bc5c3ad 100644 --- a/DeveloperExcuses.xcodeproj/project.pbxproj +++ b/DeveloperExcuses.xcodeproj/project.pbxproj @@ -12,20 +12,53 @@ 076642FD1EE92D9500FE8619 /* DeveloperExcusesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077BC2B01DA22D14007DE060 /* DeveloperExcusesView.swift */; }; 076643011EE92E8B00FE8619 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 076643001EE92E8B00FE8619 /* Main.storyboard */; }; 077BC2B11DA22D14007DE060 /* DeveloperExcusesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077BC2B01DA22D14007DE060 /* DeveloperExcusesView.swift */; }; + 07B5E452218645D50005E36F /* OnelinerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B5E451218645D50005E36F /* OnelinerView.swift */; }; + 07B5E459218645DF0005E36F /* String+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B5E454218645DF0005E36F /* String+Constants.swift */; }; + 07B5E45B218645DF0005E36F /* UserDefaults+LastLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B5E456218645DF0005E36F /* UserDefaults+LastLine.swift */; }; + 07B5E45C218645DF0005E36F /* Date+FetchInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B5E457218645DF0005E36F /* Date+FetchInterval.swift */; }; + 07B5E45D218645DF0005E36F /* TimeInterval+FetchInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B5E458218645DF0005E36F /* TimeInterval+FetchInterval.swift */; }; + 07F22F0623EEE67400F939BF /* OnelinerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B5E451218645D50005E36F /* OnelinerView.swift */; }; + 07F22F0723EEE68000F939BF /* String+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B5E454218645DF0005E36F /* String+Constants.swift */; }; + 07F22F0923EEE68000F939BF /* UserDefaults+LastLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B5E456218645DF0005E36F /* UserDefaults+LastLine.swift */; }; + 07F22F0A23EEE68000F939BF /* Date+FetchInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B5E457218645DF0005E36F /* Date+FetchInterval.swift */; }; + 07F22F0B23EEE68000F939BF /* TimeInterval+FetchInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07B5E458218645DF0005E36F /* TimeInterval+FetchInterval.swift */; }; 2A9D0E6817DB87B300DBFFBD /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A9D0E6717DB87B300DBFFBD /* Cocoa.framework */; }; 2A9D0E6A17DB87B300DBFFBD /* ScreenSaver.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A9D0E6917DB87B300DBFFBD /* ScreenSaver.framework */; }; 2A9D0E7417DB87B300DBFFBD /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 2A9D0E7217DB87B300DBFFBD /* InfoPlist.strings */; }; + 3C9050DD2440FD9200542954 /* DeveloperExcusesConfigureSheetWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C9050DB2440FD9100542954 /* DeveloperExcusesConfigureSheetWC.swift */; }; + 3C9050DE2440FD9200542954 /* DeveloperExcusesConfigureSheetWC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3C9050DC2440FD9200542954 /* DeveloperExcusesConfigureSheetWC.xib */; }; + 3C9050DF2440FE4B00542954 /* DeveloperExcusesConfigureSheetWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C9050DB2440FD9100542954 /* DeveloperExcusesConfigureSheetWC.swift */; }; + 3C9050E02440FE4E00542954 /* DeveloperExcusesConfigureSheetWC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3C9050DC2440FD9200542954 /* DeveloperExcusesConfigureSheetWC.xib */; }; 8117802E1D80A79F00127177 /* thumbnail@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8117802D1D80A79F00127177 /* thumbnail@2x.png */; }; 811780321D80B5DD00127177 /* thumbnail.png in Resources */ = {isa = PBXBuildFile; fileRef = 811780311D80B5DD00127177 /* thumbnail.png */; }; /* End PBXBuildFile section */ +/* Begin PBXCopyFilesBuildPhase section */ + 07A101791FE687A600F7A42F /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ + 072387FF1FE6884A00B1E0E2 /* OnelinerKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OnelinerKit.framework; path = Carthage/Build/Mac/OnelinerKit.framework; sourceTree = ""; }; 076642EE1EE92D6D00FE8619 /* DeveloperExcusesDebug.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DeveloperExcusesDebug.app; sourceTree = BUILT_PRODUCTS_DIR; }; 076642F01EE92D6D00FE8619 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 076642F41EE92D6D00FE8619 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 076642F91EE92D6D00FE8619 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 076643001EE92E8B00FE8619 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 077BC2B01DA22D14007DE060 /* DeveloperExcusesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeveloperExcusesView.swift; sourceTree = ""; }; + 07B5E451218645D50005E36F /* OnelinerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnelinerView.swift; sourceTree = ""; }; + 07B5E454218645DF0005E36F /* String+Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Constants.swift"; sourceTree = ""; }; + 07B5E456218645DF0005E36F /* UserDefaults+LastLine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+LastLine.swift"; sourceTree = ""; }; + 07B5E457218645DF0005E36F /* Date+FetchInterval.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+FetchInterval.swift"; sourceTree = ""; }; + 07B5E458218645DF0005E36F /* TimeInterval+FetchInterval.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TimeInterval+FetchInterval.swift"; sourceTree = ""; }; 2A9D0E6417DB87B300DBFFBD /* DeveloperExcuses.saver */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DeveloperExcuses.saver; sourceTree = BUILT_PRODUCTS_DIR; }; 2A9D0E6717DB87B300DBFFBD /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 2A9D0E6917DB87B300DBFFBD /* ScreenSaver.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ScreenSaver.framework; path = System/Library/Frameworks/ScreenSaver.framework; sourceTree = SDKROOT; }; @@ -35,6 +68,8 @@ 2A9D0E7117DB87B300DBFFBD /* DeveloperExcuses-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "DeveloperExcuses-Info.plist"; sourceTree = ""; }; 2A9D0E7317DB87B300DBFFBD /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 2A9D0E7517DB87B300DBFFBD /* DeveloperExcuses-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DeveloperExcuses-Prefix.pch"; sourceTree = ""; }; + 3C9050DB2440FD9100542954 /* DeveloperExcusesConfigureSheetWC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperExcusesConfigureSheetWC.swift; sourceTree = ""; }; + 3C9050DC2440FD9200542954 /* DeveloperExcusesConfigureSheetWC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DeveloperExcusesConfigureSheetWC.xib; sourceTree = ""; }; 8117802D1D80A79F00127177 /* thumbnail@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "thumbnail@2x.png"; sourceTree = ""; }; 811780311D80B5DD00127177 /* thumbnail.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = thumbnail.png; sourceTree = ""; }; /* End PBXFileReference section */ @@ -70,6 +105,25 @@ path = DeveloperExcusesDebug; sourceTree = ""; }; + 07B5E450218645D50005E36F /* View */ = { + isa = PBXGroup; + children = ( + 07B5E451218645D50005E36F /* OnelinerView.swift */, + ); + path = View; + sourceTree = ""; + }; + 07B5E453218645DF0005E36F /* Extension */ = { + isa = PBXGroup; + children = ( + 07B5E454218645DF0005E36F /* String+Constants.swift */, + 07B5E456218645DF0005E36F /* UserDefaults+LastLine.swift */, + 07B5E457218645DF0005E36F /* Date+FetchInterval.swift */, + 07B5E458218645DF0005E36F /* TimeInterval+FetchInterval.swift */, + ); + path = Extension; + sourceTree = ""; + }; 2A9D0E5917DB87B300DBFFBD = { isa = PBXGroup; children = ( @@ -92,6 +146,7 @@ 2A9D0E6617DB87B300DBFFBD /* Frameworks */ = { isa = PBXGroup; children = ( + 072387FF1FE6884A00B1E0E2 /* OnelinerKit.framework */, 2A9D0E6717DB87B300DBFFBD /* Cocoa.framework */, 2A9D0E6917DB87B300DBFFBD /* ScreenSaver.framework */, 2A9D0E6B17DB87B300DBFFBD /* Other Frameworks */, @@ -112,7 +167,11 @@ 2A9D0E6F17DB87B300DBFFBD /* DeveloperExcuses */ = { isa = PBXGroup; children = ( + 07B5E453218645DF0005E36F /* Extension */, + 07B5E450218645D50005E36F /* View */, 077BC2B01DA22D14007DE060 /* DeveloperExcusesView.swift */, + 3C9050DB2440FD9100542954 /* DeveloperExcusesConfigureSheetWC.swift */, + 3C9050DC2440FD9200542954 /* DeveloperExcusesConfigureSheetWC.xib */, 2A9D0E7017DB87B300DBFFBD /* Supporting Files */, ); path = DeveloperExcuses; @@ -150,6 +209,7 @@ 076642EA1EE92D6D00FE8619 /* Sources */, 076642EB1EE92D6D00FE8619 /* Frameworks */, 076642EC1EE92D6D00FE8619 /* Resources */, + 07A101791FE687A600F7A42F /* Embed Frameworks */, ); buildRules = ( ); @@ -186,7 +246,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0830; - LastUpgradeCheck = 0800; + LastUpgradeCheck = 1240; ORGANIZATIONNAME = "Marcus Kida"; TargetAttributes = { 076642ED1EE92D6D00FE8619 = { @@ -195,17 +255,17 @@ ProvisioningStyle = Automatic; }; 2A9D0E6317DB87B300DBFFBD = { - LastSwiftMigration = 0800; + DevelopmentTeam = 857XYUU5FE; + LastSwiftMigration = 1240; }; }; }; buildConfigurationList = 2A9D0E5D17DB87B300DBFFBD /* Build configuration list for PBXProject "DeveloperExcuses" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, - Base, ); mainGroup = 2A9D0E5917DB87B300DBFFBD; productRefGroup = 2A9D0E6517DB87B300DBFFBD /* Products */; @@ -225,6 +285,7 @@ files = ( 076642F51EE92D6D00FE8619 /* Assets.xcassets in Resources */, 076643011EE92E8B00FE8619 /* Main.storyboard in Resources */, + 3C9050E02440FE4E00542954 /* DeveloperExcusesConfigureSheetWC.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -232,6 +293,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3C9050DE2440FD9200542954 /* DeveloperExcusesConfigureSheetWC.xib in Resources */, 2A9D0E7417DB87B300DBFFBD /* InfoPlist.strings in Resources */, 8117802E1D80A79F00127177 /* thumbnail@2x.png in Resources */, 811780321D80B5DD00127177 /* thumbnail.png in Resources */, @@ -255,8 +317,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 07F22F0723EEE68000F939BF /* String+Constants.swift in Sources */, + 07F22F0B23EEE68000F939BF /* TimeInterval+FetchInterval.swift in Sources */, + 07F22F0923EEE68000F939BF /* UserDefaults+LastLine.swift in Sources */, + 07F22F0A23EEE68000F939BF /* Date+FetchInterval.swift in Sources */, + 3C9050DF2440FE4B00542954 /* DeveloperExcusesConfigureSheetWC.swift in Sources */, 076642FD1EE92D9500FE8619 /* DeveloperExcusesView.swift in Sources */, 076642F11EE92D6D00FE8619 /* AppDelegate.swift in Sources */, + 07F22F0623EEE67400F939BF /* OnelinerView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -264,7 +332,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 07B5E452218645D50005E36F /* OnelinerView.swift in Sources */, 077BC2B11DA22D14007DE060 /* DeveloperExcusesView.swift in Sources */, + 07B5E45B218645DF0005E36F /* UserDefaults+LastLine.swift in Sources */, + 07B5E45C218645DF0005E36F /* Date+FetchInterval.swift in Sources */, + 3C9050DD2440FD9200542954 /* DeveloperExcusesConfigureSheetWC.swift in Sources */, + 07B5E459218645DF0005E36F /* String+Constants.swift in Sources */, + 07B5E45D218645DF0005E36F /* TimeInterval+FetchInterval.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -300,6 +374,10 @@ DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 857XYUU5FE; ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/Mac", + ); GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = DeveloperExcusesDebug/Info.plist; @@ -310,7 +388,6 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -333,6 +410,10 @@ DEVELOPMENT_TEAM = 857XYUU5FE; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/Mac", + ); GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = DeveloperExcusesDebug/Info.plist; @@ -342,7 +423,6 @@ PRODUCT_BUNDLE_IDENTIFIER = io.kida.DeveloperExcusesDebug; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; }; name = Release; }; @@ -350,17 +430,28 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = 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_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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; @@ -377,10 +468,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = 10.11; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -388,18 +480,29 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = 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_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_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -408,9 +511,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = 10.11; SDKROOT = macosx; SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -420,17 +525,20 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; - EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + CURRENT_PROJECT_VERSION = 2.1.4; + DEVELOPMENT_TEAM = 857XYUU5FE; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "DeveloperExcuses/DeveloperExcuses-Prefix.pch"; INFOPLIST_FILE = "DeveloperExcuses/DeveloperExcuses-Info.plist"; INSTALL_PATH = "$(HOME)/Library/Screen Savers"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MARKETING_VERSION = 2.1.4; PRODUCT_BUNDLE_IDENTIFIER = "com.marcuskida.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; WRAPPER_EXTENSION = saver; }; name = Debug; @@ -441,16 +549,19 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CODE_SIGN_IDENTITY = "Mac Developer"; COMBINE_HIDPI_IMAGES = YES; - EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + CURRENT_PROJECT_VERSION = 2.1.4; + DEVELOPMENT_TEAM = 857XYUU5FE; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "DeveloperExcuses/DeveloperExcuses-Prefix.pch"; INFOPLIST_FILE = "DeveloperExcuses/DeveloperExcuses-Info.plist"; INSTALL_PATH = "$(HOME)/Library/Screen Savers"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MARKETING_VERSION = 2.1.4; PRODUCT_BUNDLE_IDENTIFIER = "com.marcuskida.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; WRAPPER_EXTENSION = saver; }; name = Release; diff --git a/DeveloperExcuses/DeveloperExcuses-Info.plist b/DeveloperExcuses/DeveloperExcuses-Info.plist index 33d86ab..68f297b 100644 --- a/DeveloperExcuses/DeveloperExcuses-Info.plist +++ b/DeveloperExcuses/DeveloperExcuses-Info.plist @@ -17,14 +17,14 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2.0.1 + $(MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion - 2.0.1 + $(CURRENT_PROJECT_VERSION) NSHumanReadableCopyright Copyright © 2016 Marcus Kida. All rights reserved. NSPrincipalClass - DeveloperExcusesView + DeveloperExcuses.DeveloperExcusesView diff --git a/DeveloperExcuses/DeveloperExcusesConfigureSheetWC.swift b/DeveloperExcuses/DeveloperExcusesConfigureSheetWC.swift new file mode 100644 index 0000000..9ec2ec8 --- /dev/null +++ b/DeveloperExcuses/DeveloperExcusesConfigureSheetWC.swift @@ -0,0 +1,21 @@ +import Cocoa +import ScreenSaver + +class DeveloperExcusesConfigureSheetWC: NSWindowController { + override func windowDidLoad() { + super.windowDidLoad() + } + + @IBAction func endSheet(_ sender: Any?) { + if let window = window { + window.sheetParent?.endSheet(window) + } + } + + @objc dynamic var fontSize: Double = UserDefaults.standard.double(forKey: .onelineFontSize) { + didSet { + UserDefaults.standard.set(fontSize, forKey: .onelineFontSize) + UserDefaults.standard.synchronize() + } + } +} diff --git a/DeveloperExcuses/DeveloperExcusesConfigureSheetWC.xib b/DeveloperExcuses/DeveloperExcusesConfigureSheetWC.xib new file mode 100644 index 0000000..570e4cb --- /dev/null +++ b/DeveloperExcuses/DeveloperExcusesConfigureSheetWC.xib @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DeveloperExcuses/DeveloperExcusesView.swift b/DeveloperExcuses/DeveloperExcusesView.swift index b9889c7..947be21 100644 --- a/DeveloperExcuses/DeveloperExcusesView.swift +++ b/DeveloperExcuses/DeveloperExcusesView.swift @@ -1,8 +1,7 @@ -import Foundation +import Cocoa import ScreenSaver private extension String { - static let lastQuote = "lastQuote" static let htmlRegex = "(.+)" } @@ -10,127 +9,30 @@ private extension URL { static let websiteUrl = URL(string: "http://developerexcuses.com")! } -private extension UserDefaults { - static var lastQuote: String? { - get { - return UserDefaults.standard.string(forKey: .lastQuote) +class DeveloperExcusesView: OnelinerView { + override func fetchOneline(_ completion: @escaping (String) -> Void) { + guard let data = try? Data(contentsOf: .websiteUrl), let string = String(data: data, encoding: .utf8) else { + return } - set { - UserDefaults.standard.set(newValue, forKey: .lastQuote) - UserDefaults.standard.synchronize() + + guard let regex = try? NSRegularExpression(pattern: .htmlRegex, options: NSRegularExpression.Options(rawValue: 0)) else { + return } - } -} - -class DeveloperExcusesView: ScreenSaverView { - let fetchQueue = DispatchQueue(label: "io.kida.DeveloperExcuses.fetchQueue") - let mainQueue = DispatchQueue.main - - var label: NSTextField! - var fetchingDue = true - - override init?(frame: NSRect, isPreview: Bool) { - super.init(frame: frame, isPreview: isPreview) - label = .label(isPreview, bounds: frame) - initialize() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - label = .label(isPreview, bounds: bounds) - initialize() - } - - override func configureSheet() -> NSWindow? { - return nil - } - - override func hasConfigureSheet() -> Bool { - return false - } - - override func animateOneFrame() { - fetchNext() - } - - override func draw(_ rect: NSRect) { - super.draw(rect) - var newFrame = label.frame - let height = (label.stringValue as NSString).size(withAttributes: [NSFontAttributeName: label.font!]).height - newFrame.size.height = height; - newFrame.origin.y = rect.size.height / 2; - label.frame = newFrame; + let quotes = regex.matches(in: string, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSRange(location: 0, length: string.lengthOfBytes(using: .utf8))).map { result in + return (string as NSString).substring(with: result.range(at: 1)) + } - NSColor.black.setFill() - NSRectFill(rect) - } - - func initialize() { - animationTimeInterval = 0.5 - addSubview(label) - restoreLast() - scheduleNext() + completion(quotes.first!) } - func restoreLast() { - fetchingDue = true - set(quote: UserDefaults.lastQuote) + override var hasConfigureSheet: Bool { + true } - func set(quote: String?) { - if let q = quote { - label.stringValue = q - UserDefaults.lastQuote = q - setNeedsDisplay(frame) - } - } + var configureSheetWC = DeveloperExcusesConfigureSheetWC(windowNibName: "DeveloperExcusesConfigureSheetWC") - func scheduleNext() { - mainQueue.asyncAfter(deadline: .now() + 10) { [weak self] in - self?.fetchingDue = true - self?.fetchNext() - } - } - - func fetchNext() { - if !fetchingDue { - return - } - fetchingDue = false - - fetchQueue.async { [weak self] in - guard let data = try? Data(contentsOf: .websiteUrl), let string = String(data: data, encoding: .utf8) else { - return - } - - guard let regex = try? NSRegularExpression(pattern: .htmlRegex, options: NSRegularExpression.Options(rawValue: 0)) else { - return - } - - let quotes = regex.matches(in: string, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSRange(location: 0, length: string.characters.count)).map { result in - return (string as NSString).substring(with: result.rangeAt(1)) - } - - self?.mainQueue.async { [weak self] in - self?.scheduleNext() - self?.set(quote: quotes.first) - } - } - } -} - -private extension NSTextField { - static func label(_ isPreview: Bool, bounds: CGRect) -> NSTextField { - let label = NSTextField(frame: bounds) - label.autoresizingMask = .viewWidthSizable - label.alignment = .center - label.stringValue = "Loading…" - label.textColor = .white - label.font = NSFont(name: "Courier", size: (isPreview ? 12.0 : 24.0)) - label.backgroundColor = .clear - label.isEditable = false - label.isBezeled = false - return label + override var configureSheet: NSWindow? { + configureSheetWC.window } } diff --git a/DeveloperExcuses/Extension/Date+FetchInterval.swift b/DeveloperExcuses/Extension/Date+FetchInterval.swift new file mode 100644 index 0000000..9ea81f9 --- /dev/null +++ b/DeveloperExcuses/Extension/Date+FetchInterval.swift @@ -0,0 +1,15 @@ +// +// NSDate+FetchInterval.swift +// OnelinerKit +// +// Created by Marcus Kida on 17.12.17. +// Copyright © 2017 Marcus Kida. All rights reserved. +// + +import Foundation + +extension Date { + func isFetchDue(since: Date) -> Bool { + return timeIntervalSinceReferenceDate > since.timeIntervalSinceReferenceDate + .minimumFetchInterval + } +} diff --git a/DeveloperExcuses/Extension/String+Constants.swift b/DeveloperExcuses/Extension/String+Constants.swift new file mode 100644 index 0000000..1761218 --- /dev/null +++ b/DeveloperExcuses/Extension/String+Constants.swift @@ -0,0 +1,15 @@ +// +// String+Constants.swift +// OnelinerKit +// +// Created by Marcus Kida on 17.12.17. +// Copyright © 2017 Marcus Kida. All rights reserved. +// + +import Foundation + +extension String { + static let onelineFontSize = "onelineFontSize" + static let lastOneline = "lastOneline" + static let fetchQueue = "io.kida.OnelinerKit.fetchQueue" +} diff --git a/DeveloperExcuses/Extension/TimeInterval+FetchInterval.swift b/DeveloperExcuses/Extension/TimeInterval+FetchInterval.swift new file mode 100644 index 0000000..35a53e1 --- /dev/null +++ b/DeveloperExcuses/Extension/TimeInterval+FetchInterval.swift @@ -0,0 +1,13 @@ +// +// TimeInterval+FetchInterval.swift +// OnelinerKit +// +// Created by Marcus Kida on 17.12.17. +// Copyright © 2017 Marcus Kida. All rights reserved. +// + +import Foundation + +extension TimeInterval { + static let minimumFetchInterval = 15.0 +} diff --git a/DeveloperExcuses/Extension/UserDefaults+LastLine.swift b/DeveloperExcuses/Extension/UserDefaults+LastLine.swift new file mode 100644 index 0000000..a7e3df7 --- /dev/null +++ b/DeveloperExcuses/Extension/UserDefaults+LastLine.swift @@ -0,0 +1,21 @@ +// +// UserDefaults+LastLine.swift +// OnelinerKit +// +// Created by Marcus Kida on 17.12.17. +// Copyright © 2017 Marcus Kida. All rights reserved. +// + +import Foundation + +extension UserDefaults { + static var lastOneline: String? { + get { + return UserDefaults.standard.string(forKey: .lastOneline) + } + set { + UserDefaults.standard.set(newValue, forKey: .lastOneline) + UserDefaults.standard.synchronize() + } + } +} diff --git a/DeveloperExcuses/View/OnelinerView.swift b/DeveloperExcuses/View/OnelinerView.swift new file mode 100644 index 0000000..4ea2dab --- /dev/null +++ b/DeveloperExcuses/View/OnelinerView.swift @@ -0,0 +1,157 @@ +// +// OnelinerView.swift +// OnelinerKit +// +// Created by Marcus Kida on 17.12.17. +// Copyright © 2017 Marcus Kida. All rights reserved. +// + +import ScreenSaver + +open class OnelinerView: ScreenSaverView { + private let fetchQueue = DispatchQueue(label: .fetchQueue) + private let mainQueue = DispatchQueue.main + + private var label: NSTextField! + private var fetchingDue = true + private var lastFetchDate: Date? + + public var backgroundColor = NSColor.black + public var textColor = NSColor.white + + convenience init() { + self.init(frame: .zero, isPreview: false) + label = makeLabel(false, bounds: frame) + initialize() + } + + override init!(frame: NSRect, isPreview: Bool) { + super.init(frame: frame, isPreview: isPreview) + label = makeLabel(isPreview, bounds: frame) + initialize() + } + + required public init?(coder: NSCoder) { + super.init(coder: coder) + label = makeLabel(isPreview, bounds: bounds) + initialize() + } + + override open var configureSheet: NSWindow? { + return nil + } + + override open var hasConfigureSheet: Bool { + return false + } + + override open func animateOneFrame() { + fetchNext() + } + + override open func draw(_ rect: NSRect) { + super.draw(rect) + + var newFrame = label.frame + newFrame.origin.x = 0 + newFrame.origin.y = rect.size.height / 2 + newFrame.size.width = rect.size.width + newFrame.size.height = (label.stringValue as NSString).size(withAttributes: [NSAttributedString.Key.font: label.font!]).height + label.frame = newFrame + label.textColor = textColor + + backgroundColor.setFill() + rect.fill() + } + + open func fetchOneline(_ completion: @escaping (String) -> Void) { + preconditionFailure("`fetchOneline` must be overridden") + } + + open var onelineFontSize: Double { + let size = UserDefaults.standard.double(forKey: .onelineFontSize) + if size == 0 { + let defaultSize = 24.0 + UserDefaults.standard.set(defaultSize, forKey: .onelineFontSize) + UserDefaults.standard.synchronize() + return defaultSize + } else { + return size + } + } + + private func makeLabel(_ isPreview: Bool, bounds: CGRect) -> NSTextField { + let fontSize = CGFloat(onelineFontSize) + let label = NSTextField(frame: bounds) + label.autoresizingMask = NSView.AutoresizingMask.width + label.alignment = .center + label.stringValue = "Loading…" + label.textColor = .white + + if #available(OSX 10.15, *) { + label.font = NSFont.monospacedSystemFont(ofSize: fontSize, weight: .medium) + } else { + label.font = NSFont(name: "Courier", size: fontSize) + } + + label.backgroundColor = .clear + label.isEditable = false + label.isBezeled = false + return label + } + + private func initialize() { + animationTimeInterval = 0.5 + addSubview(label) + restoreLast() + scheduleNext() + } + + private func restoreLast() { + fetchingDue = true + set(oneliner: UserDefaults.lastOneline) + } + + private func set(oneliner: String?) { + if let oneliner = oneliner { + label.stringValue = oneliner + UserDefaults.lastOneline = oneliner + setNeedsDisplay(frame) + } + } + + private func scheduleNext() { + mainQueue.asyncAfter(deadline: .now() + 1) { [weak self] in + guard let 🕑 = self?.lastFetchDate else { + self?.scheduleForFetch() + return + } + guard Date().isFetchDue(since: 🕑) else { + self?.scheduleNext() + return + } + self?.scheduleForFetch() + } + } + + private func scheduleForFetch() { + fetchingDue = true + fetchNext() + } + + private func fetchNext() { + if !fetchingDue { + return + } + fetchingDue = false + fetchQueue.sync { [weak self] in + self?.fetchOneline { oneline in + self?.mainQueue.async { [weak self] in + self?.lastFetchDate = Date() + self?.scheduleNext() + self?.set(oneliner: oneline) + } + } + } + } +} diff --git a/DeveloperExcusesDebug/AppDelegate.swift b/DeveloperExcusesDebug/AppDelegate.swift index 76ebfb0..2d76704 100644 --- a/DeveloperExcusesDebug/AppDelegate.swift +++ b/DeveloperExcusesDebug/AppDelegate.swift @@ -15,13 +15,13 @@ class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { guard - let window = NSApplication.shared().mainWindow, + let window = NSApplication.shared.mainWindow, let screenSaverView = screenSaverView else { preconditionFailure() } screenSaverView.frame = window.contentView!.bounds; - screenSaverView.autoresizingMask = [.viewHeightSizable, .viewWidthSizable] + screenSaverView.autoresizingMask = [.height, .width] window.contentView!.addSubview(screenSaverView); } diff --git a/DeveloperExcusesDebug/Main.storyboard b/DeveloperExcusesDebug/Main.storyboard index e3ad8f6..3500571 100644 --- a/DeveloperExcusesDebug/Main.storyboard +++ b/DeveloperExcusesDebug/Main.storyboard @@ -1,7 +1,9 @@ - - + + - + + + @@ -598,7 +600,7 @@ - + @@ -653,7 +655,7 @@ - + @@ -662,11 +664,14 @@ - + + + + @@ -676,16 +681,16 @@ - + - + + - diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..8370ff2 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013 - 2017 Marcus Kida + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index e2ba035..43976ce 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # DeveloperExcuses.saver +[![Twitter: @Kidmar](https://img.shields.io/badge/contact-@Kidmar-blue.svg?style=flat)](https://twitter.com/Kidmar) +[![License](https://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://github.com/kimar/DeveloperExcuses/blob/master/LICENSE.md) + + +## Proudly powered by [OnelinerKit](https://github.com/kimar/OnelinerKit). + ![Screenshot](Release/Screenshot.png) Dead simple Screensaver which periodically shows a quote from [http://developerexcuses.com](http://developerexcuses.com), that's it. @@ -9,3 +15,15 @@ Not officially affiliated with [http://developerexcuses.com](http://developerexc Grab the current [Release here](Release/DeveloperExcuses.saver.zip)! And feel free to fork and contribute ;-) + +## Getting started + +Open up *DeveloperExcuses.xcodeproj* using Xcode and hit Cmd+B to build it. That's it. + +## Contributing + +As this is only a side-project of mine, there's no roadmap or explicit commitment from my side to fix bugs or add features, however if you'd like to contribute to this project, please feel free to raise a [Pull Request](https://github.com/kimar/DeveloperExcuses/pulls). + +## License + +See [LICENSE.md](LICENSE.md) diff --git a/Release/DeveloperExcuses.saver.zip b/Release/DeveloperExcuses.saver.zip index cfdbeeb..dc5e4fb 100644 Binary files a/Release/DeveloperExcuses.saver.zip and b/Release/DeveloperExcuses.saver.zip differ