├── .gitignore ├── Irvue Screensaver.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Irvue Screensaver ├── ConfigureSheet.xib ├── IVCollection.h ├── IVCollection.m ├── IVPhoto.h ├── IVPhoto.m ├── IVPhotoExif.h ├── IVPhotoExif.m ├── IVPhotoLocation.h ├── IVPhotoLocation.m ├── IVUser.h ├── IVUser.m ├── Info.plist ├── Irvue_ScreensaverView.h ├── Irvue_ScreensaverView.m ├── LPFileDownloader.h ├── LPFileDownloader.m ├── LPImageDownloadManager.h ├── LPImageDownloadManager.m ├── LPJSONConvertable.h ├── LPUnsplashAPI.h ├── LPUnsplashAPI.m ├── LPUnsplashPhotoView.h ├── LPUnsplashPhotoView.m ├── NSArray+Utilities.h ├── NSArray+Utilities.m ├── NSBezierPath+CGPath.h ├── NSBezierPath+CGPath.m ├── NSCharacterSet+QueryParams.h ├── NSCharacterSet+QueryParams.m ├── NSDictionary+NSURL.h ├── NSDictionary+NSURL.m ├── NSImage+Effects.h ├── NSImage+Effects.m ├── NSImage+Luminance.h ├── NSImage+Luminance.m ├── NSScreen+MaxScaleFactor.h ├── NSScreen+MaxScaleFactor.m ├── NSSet+Utilities.h ├── NSSet+Utilities.m ├── NSString+MD5.h ├── NSString+MD5.m ├── Reachability.h ├── Reachability.m ├── TTHTTPRequestSerializer.h ├── TTHTTPRequestSerializer.m ├── TTOfflineChecker.h └── TTOfflineChecker.m ├── LICENSE ├── README.md ├── README_resources ├── 1.png ├── 2.png └── 3.png └── ScreenSaverRunner ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json ├── placeholder.imageset │ ├── Contents.json │ └── placeholder.jpg └── placeholderProfileImage.imageset │ ├── Contents.json │ └── defaultProfileImage.jpg ├── Base.lproj └── MainMenu.xib ├── Info.plist └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | **.DS_Store 2 | **/DerivedData/* 3 | **/xcuserdata/* 4 | **.dmg 5 | **.app 6 | **Podfile.lock 7 | **/Pods/* 8 | **/UnsplashCredentials.h 9 | -------------------------------------------------------------------------------- /Irvue Screensaver.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E37964A41DEEEC9600C27AC7 /* IVPhoto.h in Headers */ = {isa = PBXBuildFile; fileRef = E37964981DEEEC9600C27AC7 /* IVPhoto.h */; }; 11 | E37964A51DEEEC9600C27AC7 /* IVPhoto.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964991DEEEC9600C27AC7 /* IVPhoto.m */; }; 12 | E37964A61DEEEC9600C27AC7 /* IVPhotoLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = E379649A1DEEEC9600C27AC7 /* IVPhotoLocation.h */; }; 13 | E37964A71DEEEC9600C27AC7 /* IVPhotoLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = E379649B1DEEEC9600C27AC7 /* IVPhotoLocation.m */; }; 14 | E37964AA1DEEEC9600C27AC7 /* IVPhotoExif.h in Headers */ = {isa = PBXBuildFile; fileRef = E379649E1DEEEC9600C27AC7 /* IVPhotoExif.h */; }; 15 | E37964AB1DEEEC9600C27AC7 /* IVPhotoExif.m in Sources */ = {isa = PBXBuildFile; fileRef = E379649F1DEEEC9600C27AC7 /* IVPhotoExif.m */; }; 16 | E37964AC1DEEEC9600C27AC7 /* IVUser.h in Headers */ = {isa = PBXBuildFile; fileRef = E37964A01DEEEC9600C27AC7 /* IVUser.h */; }; 17 | E37964AD1DEEEC9600C27AC7 /* IVUser.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964A11DEEEC9600C27AC7 /* IVUser.m */; }; 18 | E37964AE1DEEEC9600C27AC7 /* IVCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = E37964A21DEEEC9600C27AC7 /* IVCollection.h */; }; 19 | E37964AF1DEEEC9600C27AC7 /* IVCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964A31DEEEC9600C27AC7 /* IVCollection.m */; }; 20 | E37964B31DEEECAE00C27AC7 /* LPUnsplashAPI.h in Headers */ = {isa = PBXBuildFile; fileRef = E37964B11DEEECAE00C27AC7 /* LPUnsplashAPI.h */; }; 21 | E37964B41DEEECAE00C27AC7 /* LPUnsplashAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964B21DEEECAE00C27AC7 /* LPUnsplashAPI.m */; }; 22 | E37964B71DEEED2900C27AC7 /* TTHTTPRequestSerializer.h in Headers */ = {isa = PBXBuildFile; fileRef = E37964B51DEEED2900C27AC7 /* TTHTTPRequestSerializer.h */; }; 23 | E37964B81DEEED2900C27AC7 /* TTHTTPRequestSerializer.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964B61DEEED2900C27AC7 /* TTHTTPRequestSerializer.m */; }; 24 | E37964D61DEEED3A00C27AC7 /* NSDictionary+NSURL.h in Headers */ = {isa = PBXBuildFile; fileRef = E37964BB1DEEED3A00C27AC7 /* NSDictionary+NSURL.h */; }; 25 | E37964D71DEEED3A00C27AC7 /* NSDictionary+NSURL.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964BC1DEEED3A00C27AC7 /* NSDictionary+NSURL.m */; }; 26 | E37964D81DEEED3A00C27AC7 /* NSString+MD5.h in Headers */ = {isa = PBXBuildFile; fileRef = E37964BD1DEEED3A00C27AC7 /* NSString+MD5.h */; }; 27 | E37964D91DEEED3A00C27AC7 /* NSString+MD5.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964BE1DEEED3A00C27AC7 /* NSString+MD5.m */; }; 28 | E37964DA1DEEED3A00C27AC7 /* NSArray+Utilities.h in Headers */ = {isa = PBXBuildFile; fileRef = E37964BF1DEEED3A00C27AC7 /* NSArray+Utilities.h */; }; 29 | E37964DB1DEEED3A00C27AC7 /* NSArray+Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964C01DEEED3A00C27AC7 /* NSArray+Utilities.m */; }; 30 | E37964DC1DEEED3A00C27AC7 /* NSSet+Utilities.h in Headers */ = {isa = PBXBuildFile; fileRef = E37964C11DEEED3A00C27AC7 /* NSSet+Utilities.h */; }; 31 | E37964DD1DEEED3A00C27AC7 /* NSSet+Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964C21DEEED3A00C27AC7 /* NSSet+Utilities.m */; }; 32 | E37964DE1DEEED3A00C27AC7 /* NSImage+Luminance.h in Headers */ = {isa = PBXBuildFile; fileRef = E37964C31DEEED3A00C27AC7 /* NSImage+Luminance.h */; }; 33 | E37964DF1DEEED3A00C27AC7 /* NSImage+Luminance.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964C41DEEED3A00C27AC7 /* NSImage+Luminance.m */; }; 34 | E37964E41DEEED3A00C27AC7 /* LPJSONConvertable.h in Headers */ = {isa = PBXBuildFile; fileRef = E37964C91DEEED3A00C27AC7 /* LPJSONConvertable.h */; }; 35 | E37964E51DEEED3A00C27AC7 /* LPImageDownloadManager.h in Headers */ = {isa = PBXBuildFile; fileRef = E37964CA1DEEED3A00C27AC7 /* LPImageDownloadManager.h */; }; 36 | E37964E61DEEED3A00C27AC7 /* LPImageDownloadManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964CB1DEEED3A00C27AC7 /* LPImageDownloadManager.m */; }; 37 | E37964E71DEEED3A00C27AC7 /* NSBezierPath+CGPath.h in Headers */ = {isa = PBXBuildFile; fileRef = E37964CC1DEEED3A00C27AC7 /* NSBezierPath+CGPath.h */; }; 38 | E37964E81DEEED3A00C27AC7 /* NSBezierPath+CGPath.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964CD1DEEED3A00C27AC7 /* NSBezierPath+CGPath.m */; }; 39 | E37964E91DEEED3A00C27AC7 /* NSImage+Effects.h in Headers */ = {isa = PBXBuildFile; fileRef = E37964CE1DEEED3A00C27AC7 /* NSImage+Effects.h */; }; 40 | E37964EA1DEEED3A00C27AC7 /* NSImage+Effects.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964CF1DEEED3A00C27AC7 /* NSImage+Effects.m */; }; 41 | E37964ED1DEEED3A00C27AC7 /* NSCharacterSet+QueryParams.h in Headers */ = {isa = PBXBuildFile; fileRef = E37964D21DEEED3A00C27AC7 /* NSCharacterSet+QueryParams.h */; }; 42 | E37964EE1DEEED3A00C27AC7 /* NSCharacterSet+QueryParams.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964D31DEEED3A00C27AC7 /* NSCharacterSet+QueryParams.m */; }; 43 | E37964F21DEEEDF600C27AC7 /* LPFileDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = E37964F01DEEEDF600C27AC7 /* LPFileDownloader.h */; }; 44 | E37964F31DEEEDF600C27AC7 /* LPFileDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964F11DEEEDF600C27AC7 /* LPFileDownloader.m */; }; 45 | E37964FC1DEEFDC400C27AC7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964FB1DEEFDC400C27AC7 /* AppDelegate.m */; }; 46 | E37964FF1DEEFDC400C27AC7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964FE1DEEFDC400C27AC7 /* main.m */; }; 47 | E37965011DEEFDC400C27AC7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E37965001DEEFDC400C27AC7 /* Assets.xcassets */; }; 48 | E37965041DEEFDC400C27AC7 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = E37965021DEEFDC400C27AC7 /* MainMenu.xib */; }; 49 | E37965091DEEFE1000C27AC7 /* IVPhoto.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964991DEEEC9600C27AC7 /* IVPhoto.m */; }; 50 | E379650A1DEEFE1000C27AC7 /* IVPhotoLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = E379649B1DEEEC9600C27AC7 /* IVPhotoLocation.m */; }; 51 | E379650B1DEEFE1000C27AC7 /* IVPhotoExif.m in Sources */ = {isa = PBXBuildFile; fileRef = E379649F1DEEEC9600C27AC7 /* IVPhotoExif.m */; }; 52 | E379650C1DEEFE1000C27AC7 /* IVUser.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964A11DEEEC9600C27AC7 /* IVUser.m */; }; 53 | E379650D1DEEFE1000C27AC7 /* IVCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964A31DEEEC9600C27AC7 /* IVCollection.m */; }; 54 | E379650F1DEEFE1000C27AC7 /* NSDictionary+NSURL.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964BC1DEEED3A00C27AC7 /* NSDictionary+NSURL.m */; }; 55 | E37965101DEEFE1000C27AC7 /* NSString+MD5.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964BE1DEEED3A00C27AC7 /* NSString+MD5.m */; }; 56 | E37965111DEEFE1000C27AC7 /* NSArray+Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964C01DEEED3A00C27AC7 /* NSArray+Utilities.m */; }; 57 | E37965121DEEFE1000C27AC7 /* NSSet+Utilities.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964C21DEEED3A00C27AC7 /* NSSet+Utilities.m */; }; 58 | E37965131DEEFE1000C27AC7 /* NSImage+Luminance.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964C41DEEED3A00C27AC7 /* NSImage+Luminance.m */; }; 59 | E37965151DEEFE1000C27AC7 /* LPImageDownloadManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964CB1DEEED3A00C27AC7 /* LPImageDownloadManager.m */; }; 60 | E37965161DEEFE1000C27AC7 /* NSBezierPath+CGPath.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964CD1DEEED3A00C27AC7 /* NSBezierPath+CGPath.m */; }; 61 | E37965171DEEFE1000C27AC7 /* NSImage+Effects.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964CF1DEEED3A00C27AC7 /* NSImage+Effects.m */; }; 62 | E37965191DEEFE1000C27AC7 /* NSCharacterSet+QueryParams.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964D31DEEED3A00C27AC7 /* NSCharacterSet+QueryParams.m */; }; 63 | E379651A1DEEFE1000C27AC7 /* LPFileDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964F11DEEEDF600C27AC7 /* LPFileDownloader.m */; }; 64 | E379651B1DEEFE1000C27AC7 /* LPUnsplashAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964B21DEEECAE00C27AC7 /* LPUnsplashAPI.m */; }; 65 | E379651C1DEEFE1000C27AC7 /* TTHTTPRequestSerializer.m in Sources */ = {isa = PBXBuildFile; fileRef = E37964B61DEEED2900C27AC7 /* TTHTTPRequestSerializer.m */; }; 66 | E379651D1DEEFE1000C27AC7 /* Irvue_ScreensaverView.m in Sources */ = {isa = PBXBuildFile; fileRef = E3FBA54C1DEEE9CD00CF2932 /* Irvue_ScreensaverView.m */; }; 67 | E37965201DEF098900C27AC7 /* LPUnsplashPhotoView.h in Headers */ = {isa = PBXBuildFile; fileRef = E379651E1DEF098900C27AC7 /* LPUnsplashPhotoView.h */; }; 68 | E37965211DEF098900C27AC7 /* LPUnsplashPhotoView.m in Sources */ = {isa = PBXBuildFile; fileRef = E379651F1DEF098900C27AC7 /* LPUnsplashPhotoView.m */; }; 69 | E37965221DEF098900C27AC7 /* LPUnsplashPhotoView.m in Sources */ = {isa = PBXBuildFile; fileRef = E379651F1DEF098900C27AC7 /* LPUnsplashPhotoView.m */; }; 70 | E379652B1DEF0AD900C27AC7 /* NSScreen+MaxScaleFactor.h in Headers */ = {isa = PBXBuildFile; fileRef = E37965291DEF0AD900C27AC7 /* NSScreen+MaxScaleFactor.h */; }; 71 | E379652C1DEF0AD900C27AC7 /* NSScreen+MaxScaleFactor.m in Sources */ = {isa = PBXBuildFile; fileRef = E379652A1DEF0AD900C27AC7 /* NSScreen+MaxScaleFactor.m */; }; 72 | E379652D1DEF0AD900C27AC7 /* NSScreen+MaxScaleFactor.m in Sources */ = {isa = PBXBuildFile; fileRef = E379652A1DEF0AD900C27AC7 /* NSScreen+MaxScaleFactor.m */; }; 73 | E3B4C0401DF04A2E00747C26 /* ConfigureSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = E3B4C03F1DF04A2E00747C26 /* ConfigureSheet.xib */; }; 74 | E3B4C0411DF04A2E00747C26 /* ConfigureSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = E3B4C03F1DF04A2E00747C26 /* ConfigureSheet.xib */; }; 75 | E3E940221E3E495A00094B83 /* TTOfflineChecker.h in Headers */ = {isa = PBXBuildFile; fileRef = E3E940201E3E495A00094B83 /* TTOfflineChecker.h */; }; 76 | E3E940231E3E495A00094B83 /* TTOfflineChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = E3E940211E3E495A00094B83 /* TTOfflineChecker.m */; }; 77 | E3E940241E3E495A00094B83 /* TTOfflineChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = E3E940211E3E495A00094B83 /* TTOfflineChecker.m */; }; 78 | E3E940271E3E49BA00094B83 /* Reachability.h in Headers */ = {isa = PBXBuildFile; fileRef = E3E940251E3E49BA00094B83 /* Reachability.h */; }; 79 | E3E940281E3E49BA00094B83 /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = E3E940261E3E49BA00094B83 /* Reachability.m */; }; 80 | E3E940291E3E49BA00094B83 /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = E3E940261E3E49BA00094B83 /* Reachability.m */; }; 81 | E3FBA54B1DEEE9CD00CF2932 /* Irvue_ScreensaverView.h in Headers */ = {isa = PBXBuildFile; fileRef = E3FBA54A1DEEE9CD00CF2932 /* Irvue_ScreensaverView.h */; }; 82 | E3FBA54D1DEEE9CD00CF2932 /* Irvue_ScreensaverView.m in Sources */ = {isa = PBXBuildFile; fileRef = E3FBA54C1DEEE9CD00CF2932 /* Irvue_ScreensaverView.m */; }; 83 | /* End PBXBuildFile section */ 84 | 85 | /* Begin PBXFileReference section */ 86 | E37964981DEEEC9600C27AC7 /* IVPhoto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IVPhoto.h; sourceTree = ""; }; 87 | E37964991DEEEC9600C27AC7 /* IVPhoto.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IVPhoto.m; sourceTree = ""; }; 88 | E379649A1DEEEC9600C27AC7 /* IVPhotoLocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IVPhotoLocation.h; sourceTree = ""; }; 89 | E379649B1DEEEC9600C27AC7 /* IVPhotoLocation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IVPhotoLocation.m; sourceTree = ""; }; 90 | E379649E1DEEEC9600C27AC7 /* IVPhotoExif.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IVPhotoExif.h; sourceTree = ""; }; 91 | E379649F1DEEEC9600C27AC7 /* IVPhotoExif.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IVPhotoExif.m; sourceTree = ""; }; 92 | E37964A01DEEEC9600C27AC7 /* IVUser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IVUser.h; sourceTree = ""; }; 93 | E37964A11DEEEC9600C27AC7 /* IVUser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IVUser.m; sourceTree = ""; }; 94 | E37964A21DEEEC9600C27AC7 /* IVCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IVCollection.h; sourceTree = ""; }; 95 | E37964A31DEEEC9600C27AC7 /* IVCollection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IVCollection.m; sourceTree = ""; }; 96 | E37964B11DEEECAE00C27AC7 /* LPUnsplashAPI.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LPUnsplashAPI.h; sourceTree = ""; }; 97 | E37964B21DEEECAE00C27AC7 /* LPUnsplashAPI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LPUnsplashAPI.m; sourceTree = ""; }; 98 | E37964B51DEEED2900C27AC7 /* TTHTTPRequestSerializer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TTHTTPRequestSerializer.h; sourceTree = ""; }; 99 | E37964B61DEEED2900C27AC7 /* TTHTTPRequestSerializer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TTHTTPRequestSerializer.m; sourceTree = ""; }; 100 | E37964BB1DEEED3A00C27AC7 /* NSDictionary+NSURL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+NSURL.h"; sourceTree = ""; }; 101 | E37964BC1DEEED3A00C27AC7 /* NSDictionary+NSURL.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+NSURL.m"; sourceTree = ""; }; 102 | E37964BD1DEEED3A00C27AC7 /* NSString+MD5.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+MD5.h"; sourceTree = ""; }; 103 | E37964BE1DEEED3A00C27AC7 /* NSString+MD5.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+MD5.m"; sourceTree = ""; }; 104 | E37964BF1DEEED3A00C27AC7 /* NSArray+Utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Utilities.h"; sourceTree = ""; }; 105 | E37964C01DEEED3A00C27AC7 /* NSArray+Utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Utilities.m"; sourceTree = ""; }; 106 | E37964C11DEEED3A00C27AC7 /* NSSet+Utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSSet+Utilities.h"; sourceTree = ""; }; 107 | E37964C21DEEED3A00C27AC7 /* NSSet+Utilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSSet+Utilities.m"; sourceTree = ""; }; 108 | E37964C31DEEED3A00C27AC7 /* NSImage+Luminance.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSImage+Luminance.h"; sourceTree = ""; }; 109 | E37964C41DEEED3A00C27AC7 /* NSImage+Luminance.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSImage+Luminance.m"; sourceTree = ""; }; 110 | E37964C91DEEED3A00C27AC7 /* LPJSONConvertable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LPJSONConvertable.h; sourceTree = ""; }; 111 | E37964CA1DEEED3A00C27AC7 /* LPImageDownloadManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LPImageDownloadManager.h; sourceTree = ""; }; 112 | E37964CB1DEEED3A00C27AC7 /* LPImageDownloadManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LPImageDownloadManager.m; sourceTree = ""; }; 113 | E37964CC1DEEED3A00C27AC7 /* NSBezierPath+CGPath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBezierPath+CGPath.h"; sourceTree = ""; }; 114 | E37964CD1DEEED3A00C27AC7 /* NSBezierPath+CGPath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBezierPath+CGPath.m"; sourceTree = ""; }; 115 | E37964CE1DEEED3A00C27AC7 /* NSImage+Effects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSImage+Effects.h"; sourceTree = ""; }; 116 | E37964CF1DEEED3A00C27AC7 /* NSImage+Effects.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSImage+Effects.m"; sourceTree = ""; }; 117 | E37964D21DEEED3A00C27AC7 /* NSCharacterSet+QueryParams.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSCharacterSet+QueryParams.h"; sourceTree = ""; }; 118 | E37964D31DEEED3A00C27AC7 /* NSCharacterSet+QueryParams.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSCharacterSet+QueryParams.m"; sourceTree = ""; }; 119 | E37964F01DEEEDF600C27AC7 /* LPFileDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LPFileDownloader.h; sourceTree = ""; }; 120 | E37964F11DEEEDF600C27AC7 /* LPFileDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LPFileDownloader.m; sourceTree = ""; }; 121 | E37964F81DEEFDC400C27AC7 /* ScreenSaverRunner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ScreenSaverRunner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 122 | E37964FA1DEEFDC400C27AC7 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 123 | E37964FB1DEEFDC400C27AC7 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 124 | E37964FE1DEEFDC400C27AC7 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 125 | E37965001DEEFDC400C27AC7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 126 | E37965031DEEFDC400C27AC7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 127 | E37965051DEEFDC400C27AC7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 128 | E379651E1DEF098900C27AC7 /* LPUnsplashPhotoView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LPUnsplashPhotoView.h; sourceTree = ""; }; 129 | E379651F1DEF098900C27AC7 /* LPUnsplashPhotoView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LPUnsplashPhotoView.m; sourceTree = ""; }; 130 | E37965291DEF0AD900C27AC7 /* NSScreen+MaxScaleFactor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSScreen+MaxScaleFactor.h"; sourceTree = ""; }; 131 | E379652A1DEF0AD900C27AC7 /* NSScreen+MaxScaleFactor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSScreen+MaxScaleFactor.m"; sourceTree = ""; }; 132 | E3B4C03F1DF04A2E00747C26 /* ConfigureSheet.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ConfigureSheet.xib; sourceTree = ""; }; 133 | E3E940201E3E495A00094B83 /* TTOfflineChecker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TTOfflineChecker.h; sourceTree = ""; }; 134 | E3E940211E3E495A00094B83 /* TTOfflineChecker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TTOfflineChecker.m; sourceTree = ""; }; 135 | E3E940251E3E49BA00094B83 /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = ""; }; 136 | E3E940261E3E49BA00094B83 /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = ""; }; 137 | E3FBA5471DEEE9CD00CF2932 /* Irvue Screensaver.saver */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Irvue Screensaver.saver"; sourceTree = BUILT_PRODUCTS_DIR; }; 138 | E3FBA54A1DEEE9CD00CF2932 /* Irvue_ScreensaverView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Irvue_ScreensaverView.h; sourceTree = ""; }; 139 | E3FBA54C1DEEE9CD00CF2932 /* Irvue_ScreensaverView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Irvue_ScreensaverView.m; sourceTree = ""; }; 140 | E3FBA54E1DEEE9CD00CF2932 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 141 | /* End PBXFileReference section */ 142 | 143 | /* Begin PBXFrameworksBuildPhase section */ 144 | E37964F51DEEFDC400C27AC7 /* Frameworks */ = { 145 | isa = PBXFrameworksBuildPhase; 146 | buildActionMask = 2147483647; 147 | files = ( 148 | ); 149 | runOnlyForDeploymentPostprocessing = 0; 150 | }; 151 | E3FBA5431DEEE9CD00CF2932 /* Frameworks */ = { 152 | isa = PBXFrameworksBuildPhase; 153 | buildActionMask = 2147483647; 154 | files = ( 155 | ); 156 | runOnlyForDeploymentPostprocessing = 0; 157 | }; 158 | /* End PBXFrameworksBuildPhase section */ 159 | 160 | /* Begin PBXGroup section */ 161 | E37964B01DEEEC9E00C27AC7 /* Data */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | E37964981DEEEC9600C27AC7 /* IVPhoto.h */, 165 | E37964991DEEEC9600C27AC7 /* IVPhoto.m */, 166 | E379649A1DEEEC9600C27AC7 /* IVPhotoLocation.h */, 167 | E379649B1DEEEC9600C27AC7 /* IVPhotoLocation.m */, 168 | E379649E1DEEEC9600C27AC7 /* IVPhotoExif.h */, 169 | E379649F1DEEEC9600C27AC7 /* IVPhotoExif.m */, 170 | E37964A01DEEEC9600C27AC7 /* IVUser.h */, 171 | E37964A11DEEEC9600C27AC7 /* IVUser.m */, 172 | E37964A21DEEEC9600C27AC7 /* IVCollection.h */, 173 | E37964A31DEEEC9600C27AC7 /* IVCollection.m */, 174 | ); 175 | name = Data; 176 | sourceTree = ""; 177 | }; 178 | E37964EF1DEEED3F00C27AC7 /* Commons */ = { 179 | isa = PBXGroup; 180 | children = ( 181 | E3E940251E3E49BA00094B83 /* Reachability.h */, 182 | E3E940261E3E49BA00094B83 /* Reachability.m */, 183 | E3E940201E3E495A00094B83 /* TTOfflineChecker.h */, 184 | E3E940211E3E495A00094B83 /* TTOfflineChecker.m */, 185 | E37964BB1DEEED3A00C27AC7 /* NSDictionary+NSURL.h */, 186 | E37964BC1DEEED3A00C27AC7 /* NSDictionary+NSURL.m */, 187 | E37964BD1DEEED3A00C27AC7 /* NSString+MD5.h */, 188 | E37964BE1DEEED3A00C27AC7 /* NSString+MD5.m */, 189 | E37964BF1DEEED3A00C27AC7 /* NSArray+Utilities.h */, 190 | E37964C01DEEED3A00C27AC7 /* NSArray+Utilities.m */, 191 | E37964C11DEEED3A00C27AC7 /* NSSet+Utilities.h */, 192 | E37964C21DEEED3A00C27AC7 /* NSSet+Utilities.m */, 193 | E37964C31DEEED3A00C27AC7 /* NSImage+Luminance.h */, 194 | E37964C41DEEED3A00C27AC7 /* NSImage+Luminance.m */, 195 | E37964C91DEEED3A00C27AC7 /* LPJSONConvertable.h */, 196 | E37964CA1DEEED3A00C27AC7 /* LPImageDownloadManager.h */, 197 | E37964CB1DEEED3A00C27AC7 /* LPImageDownloadManager.m */, 198 | E37964CC1DEEED3A00C27AC7 /* NSBezierPath+CGPath.h */, 199 | E37964CD1DEEED3A00C27AC7 /* NSBezierPath+CGPath.m */, 200 | E37964CE1DEEED3A00C27AC7 /* NSImage+Effects.h */, 201 | E37964CF1DEEED3A00C27AC7 /* NSImage+Effects.m */, 202 | E37964D21DEEED3A00C27AC7 /* NSCharacterSet+QueryParams.h */, 203 | E37964D31DEEED3A00C27AC7 /* NSCharacterSet+QueryParams.m */, 204 | E37964F01DEEEDF600C27AC7 /* LPFileDownloader.h */, 205 | E37964F11DEEEDF600C27AC7 /* LPFileDownloader.m */, 206 | E37965291DEF0AD900C27AC7 /* NSScreen+MaxScaleFactor.h */, 207 | E379652A1DEF0AD900C27AC7 /* NSScreen+MaxScaleFactor.m */, 208 | ); 209 | name = Commons; 210 | sourceTree = ""; 211 | }; 212 | E37964F91DEEFDC400C27AC7 /* ScreenSaverRunner */ = { 213 | isa = PBXGroup; 214 | children = ( 215 | E37964FA1DEEFDC400C27AC7 /* AppDelegate.h */, 216 | E37964FB1DEEFDC400C27AC7 /* AppDelegate.m */, 217 | E37965001DEEFDC400C27AC7 /* Assets.xcassets */, 218 | E37965021DEEFDC400C27AC7 /* MainMenu.xib */, 219 | E37965051DEEFDC400C27AC7 /* Info.plist */, 220 | E37964FD1DEEFDC400C27AC7 /* Supporting Files */, 221 | ); 222 | path = ScreenSaverRunner; 223 | sourceTree = ""; 224 | }; 225 | E37964FD1DEEFDC400C27AC7 /* Supporting Files */ = { 226 | isa = PBXGroup; 227 | children = ( 228 | E37964FE1DEEFDC400C27AC7 /* main.m */, 229 | ); 230 | name = "Supporting Files"; 231 | sourceTree = ""; 232 | }; 233 | E3FBA53D1DEEE9CD00CF2932 = { 234 | isa = PBXGroup; 235 | children = ( 236 | E3FBA5491DEEE9CD00CF2932 /* Irvue Screensaver */, 237 | E37964F91DEEFDC400C27AC7 /* ScreenSaverRunner */, 238 | E3FBA5481DEEE9CD00CF2932 /* Products */, 239 | ); 240 | sourceTree = ""; 241 | }; 242 | E3FBA5481DEEE9CD00CF2932 /* Products */ = { 243 | isa = PBXGroup; 244 | children = ( 245 | E3FBA5471DEEE9CD00CF2932 /* Irvue Screensaver.saver */, 246 | E37964F81DEEFDC400C27AC7 /* ScreenSaverRunner.app */, 247 | ); 248 | name = Products; 249 | sourceTree = ""; 250 | }; 251 | E3FBA5491DEEE9CD00CF2932 /* Irvue Screensaver */ = { 252 | isa = PBXGroup; 253 | children = ( 254 | E37964B01DEEEC9E00C27AC7 /* Data */, 255 | E37964EF1DEEED3F00C27AC7 /* Commons */, 256 | E379651E1DEF098900C27AC7 /* LPUnsplashPhotoView.h */, 257 | E379651F1DEF098900C27AC7 /* LPUnsplashPhotoView.m */, 258 | E37964B11DEEECAE00C27AC7 /* LPUnsplashAPI.h */, 259 | E37964B21DEEECAE00C27AC7 /* LPUnsplashAPI.m */, 260 | E37964B51DEEED2900C27AC7 /* TTHTTPRequestSerializer.h */, 261 | E37964B61DEEED2900C27AC7 /* TTHTTPRequestSerializer.m */, 262 | E3FBA54A1DEEE9CD00CF2932 /* Irvue_ScreensaverView.h */, 263 | E3FBA54C1DEEE9CD00CF2932 /* Irvue_ScreensaverView.m */, 264 | E3FBA54E1DEEE9CD00CF2932 /* Info.plist */, 265 | E3B4C03F1DF04A2E00747C26 /* ConfigureSheet.xib */, 266 | ); 267 | path = "Irvue Screensaver"; 268 | sourceTree = ""; 269 | }; 270 | /* End PBXGroup section */ 271 | 272 | /* Begin PBXHeadersBuildPhase section */ 273 | E3FBA5441DEEE9CD00CF2932 /* Headers */ = { 274 | isa = PBXHeadersBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | E379652B1DEF0AD900C27AC7 /* NSScreen+MaxScaleFactor.h in Headers */, 278 | E37964D61DEEED3A00C27AC7 /* NSDictionary+NSURL.h in Headers */, 279 | E37964DC1DEEED3A00C27AC7 /* NSSet+Utilities.h in Headers */, 280 | E37964F21DEEEDF600C27AC7 /* LPFileDownloader.h in Headers */, 281 | E37964AC1DEEEC9600C27AC7 /* IVUser.h in Headers */, 282 | E37964E41DEEED3A00C27AC7 /* LPJSONConvertable.h in Headers */, 283 | E37964B31DEEECAE00C27AC7 /* LPUnsplashAPI.h in Headers */, 284 | E37964AE1DEEEC9600C27AC7 /* IVCollection.h in Headers */, 285 | E37964B71DEEED2900C27AC7 /* TTHTTPRequestSerializer.h in Headers */, 286 | E37964E71DEEED3A00C27AC7 /* NSBezierPath+CGPath.h in Headers */, 287 | E37964D81DEEED3A00C27AC7 /* NSString+MD5.h in Headers */, 288 | E37964AA1DEEEC9600C27AC7 /* IVPhotoExif.h in Headers */, 289 | E37964DA1DEEED3A00C27AC7 /* NSArray+Utilities.h in Headers */, 290 | E37964E91DEEED3A00C27AC7 /* NSImage+Effects.h in Headers */, 291 | E37964E51DEEED3A00C27AC7 /* LPImageDownloadManager.h in Headers */, 292 | E37964A41DEEEC9600C27AC7 /* IVPhoto.h in Headers */, 293 | E3E940221E3E495A00094B83 /* TTOfflineChecker.h in Headers */, 294 | E37964DE1DEEED3A00C27AC7 /* NSImage+Luminance.h in Headers */, 295 | E3E940271E3E49BA00094B83 /* Reachability.h in Headers */, 296 | E37964A61DEEEC9600C27AC7 /* IVPhotoLocation.h in Headers */, 297 | E37964ED1DEEED3A00C27AC7 /* NSCharacterSet+QueryParams.h in Headers */, 298 | E3FBA54B1DEEE9CD00CF2932 /* Irvue_ScreensaverView.h in Headers */, 299 | E37965201DEF098900C27AC7 /* LPUnsplashPhotoView.h in Headers */, 300 | ); 301 | runOnlyForDeploymentPostprocessing = 0; 302 | }; 303 | /* End PBXHeadersBuildPhase section */ 304 | 305 | /* Begin PBXNativeTarget section */ 306 | E37964F71DEEFDC400C27AC7 /* ScreenSaverRunner */ = { 307 | isa = PBXNativeTarget; 308 | buildConfigurationList = E37965061DEEFDC400C27AC7 /* Build configuration list for PBXNativeTarget "ScreenSaverRunner" */; 309 | buildPhases = ( 310 | E37964F41DEEFDC400C27AC7 /* Sources */, 311 | E37964F51DEEFDC400C27AC7 /* Frameworks */, 312 | E37964F61DEEFDC400C27AC7 /* Resources */, 313 | ); 314 | buildRules = ( 315 | ); 316 | dependencies = ( 317 | ); 318 | name = ScreenSaverRunner; 319 | productName = ScreenSaverRunner; 320 | productReference = E37964F81DEEFDC400C27AC7 /* ScreenSaverRunner.app */; 321 | productType = "com.apple.product-type.application"; 322 | }; 323 | E3FBA5461DEEE9CD00CF2932 /* Irvue Screensaver */ = { 324 | isa = PBXNativeTarget; 325 | buildConfigurationList = E3FBA5511DEEE9CD00CF2932 /* Build configuration list for PBXNativeTarget "Irvue Screensaver" */; 326 | buildPhases = ( 327 | E3FBA5421DEEE9CD00CF2932 /* Sources */, 328 | E3FBA5431DEEE9CD00CF2932 /* Frameworks */, 329 | E3FBA5441DEEE9CD00CF2932 /* Headers */, 330 | E3FBA5451DEEE9CD00CF2932 /* Resources */, 331 | ); 332 | buildRules = ( 333 | ); 334 | dependencies = ( 335 | ); 336 | name = "Irvue Screensaver"; 337 | productName = "Irvue Screensaver"; 338 | productReference = E3FBA5471DEEE9CD00CF2932 /* Irvue Screensaver.saver */; 339 | productType = "com.apple.product-type.bundle"; 340 | }; 341 | /* End PBXNativeTarget section */ 342 | 343 | /* Begin PBXProject section */ 344 | E3FBA53E1DEEE9CD00CF2932 /* Project object */ = { 345 | isa = PBXProject; 346 | attributes = { 347 | LastUpgradeCheck = 0810; 348 | ORGANIZATIONNAME = Leonspok; 349 | TargetAttributes = { 350 | E37964F71DEEFDC400C27AC7 = { 351 | CreatedOnToolsVersion = 8.1; 352 | DevelopmentTeam = 6KXZX3AW22; 353 | ProvisioningStyle = Automatic; 354 | }; 355 | E3FBA5461DEEE9CD00CF2932 = { 356 | CreatedOnToolsVersion = 8.1; 357 | DevelopmentTeam = 6KXZX3AW22; 358 | ProvisioningStyle = Automatic; 359 | }; 360 | }; 361 | }; 362 | buildConfigurationList = E3FBA5411DEEE9CD00CF2932 /* Build configuration list for PBXProject "Irvue Screensaver" */; 363 | compatibilityVersion = "Xcode 3.2"; 364 | developmentRegion = English; 365 | hasScannedForEncodings = 0; 366 | knownRegions = ( 367 | en, 368 | Base, 369 | ); 370 | mainGroup = E3FBA53D1DEEE9CD00CF2932; 371 | productRefGroup = E3FBA5481DEEE9CD00CF2932 /* Products */; 372 | projectDirPath = ""; 373 | projectRoot = ""; 374 | targets = ( 375 | E3FBA5461DEEE9CD00CF2932 /* Irvue Screensaver */, 376 | E37964F71DEEFDC400C27AC7 /* ScreenSaverRunner */, 377 | ); 378 | }; 379 | /* End PBXProject section */ 380 | 381 | /* Begin PBXResourcesBuildPhase section */ 382 | E37964F61DEEFDC400C27AC7 /* Resources */ = { 383 | isa = PBXResourcesBuildPhase; 384 | buildActionMask = 2147483647; 385 | files = ( 386 | E37965011DEEFDC400C27AC7 /* Assets.xcassets in Resources */, 387 | E37965041DEEFDC400C27AC7 /* MainMenu.xib in Resources */, 388 | E3B4C0411DF04A2E00747C26 /* ConfigureSheet.xib in Resources */, 389 | ); 390 | runOnlyForDeploymentPostprocessing = 0; 391 | }; 392 | E3FBA5451DEEE9CD00CF2932 /* Resources */ = { 393 | isa = PBXResourcesBuildPhase; 394 | buildActionMask = 2147483647; 395 | files = ( 396 | E3B4C0401DF04A2E00747C26 /* ConfigureSheet.xib in Resources */, 397 | ); 398 | runOnlyForDeploymentPostprocessing = 0; 399 | }; 400 | /* End PBXResourcesBuildPhase section */ 401 | 402 | /* Begin PBXSourcesBuildPhase section */ 403 | E37964F41DEEFDC400C27AC7 /* Sources */ = { 404 | isa = PBXSourcesBuildPhase; 405 | buildActionMask = 2147483647; 406 | files = ( 407 | E37965091DEEFE1000C27AC7 /* IVPhoto.m in Sources */, 408 | E379652D1DEF0AD900C27AC7 /* NSScreen+MaxScaleFactor.m in Sources */, 409 | E379650A1DEEFE1000C27AC7 /* IVPhotoLocation.m in Sources */, 410 | E379650B1DEEFE1000C27AC7 /* IVPhotoExif.m in Sources */, 411 | E379650C1DEEFE1000C27AC7 /* IVUser.m in Sources */, 412 | E379650D1DEEFE1000C27AC7 /* IVCollection.m in Sources */, 413 | E379650F1DEEFE1000C27AC7 /* NSDictionary+NSURL.m in Sources */, 414 | E37965221DEF098900C27AC7 /* LPUnsplashPhotoView.m in Sources */, 415 | E37965101DEEFE1000C27AC7 /* NSString+MD5.m in Sources */, 416 | E37965111DEEFE1000C27AC7 /* NSArray+Utilities.m in Sources */, 417 | E3E940291E3E49BA00094B83 /* Reachability.m in Sources */, 418 | E37965121DEEFE1000C27AC7 /* NSSet+Utilities.m in Sources */, 419 | E37965131DEEFE1000C27AC7 /* NSImage+Luminance.m in Sources */, 420 | E3E940241E3E495A00094B83 /* TTOfflineChecker.m in Sources */, 421 | E37965151DEEFE1000C27AC7 /* LPImageDownloadManager.m in Sources */, 422 | E37965161DEEFE1000C27AC7 /* NSBezierPath+CGPath.m in Sources */, 423 | E37965171DEEFE1000C27AC7 /* NSImage+Effects.m in Sources */, 424 | E37965191DEEFE1000C27AC7 /* NSCharacterSet+QueryParams.m in Sources */, 425 | E379651A1DEEFE1000C27AC7 /* LPFileDownloader.m in Sources */, 426 | E379651B1DEEFE1000C27AC7 /* LPUnsplashAPI.m in Sources */, 427 | E379651C1DEEFE1000C27AC7 /* TTHTTPRequestSerializer.m in Sources */, 428 | E379651D1DEEFE1000C27AC7 /* Irvue_ScreensaverView.m in Sources */, 429 | E37964FF1DEEFDC400C27AC7 /* main.m in Sources */, 430 | E37964FC1DEEFDC400C27AC7 /* AppDelegate.m in Sources */, 431 | ); 432 | runOnlyForDeploymentPostprocessing = 0; 433 | }; 434 | E3FBA5421DEEE9CD00CF2932 /* Sources */ = { 435 | isa = PBXSourcesBuildPhase; 436 | buildActionMask = 2147483647; 437 | files = ( 438 | E37964B81DEEED2900C27AC7 /* TTHTTPRequestSerializer.m in Sources */, 439 | E37964AB1DEEEC9600C27AC7 /* IVPhotoExif.m in Sources */, 440 | E37964DB1DEEED3A00C27AC7 /* NSArray+Utilities.m in Sources */, 441 | E37964AD1DEEEC9600C27AC7 /* IVUser.m in Sources */, 442 | E37964EE1DEEED3A00C27AC7 /* NSCharacterSet+QueryParams.m in Sources */, 443 | E3FBA54D1DEEE9CD00CF2932 /* Irvue_ScreensaverView.m in Sources */, 444 | E37964A71DEEEC9600C27AC7 /* IVPhotoLocation.m in Sources */, 445 | E37964EA1DEEED3A00C27AC7 /* NSImage+Effects.m in Sources */, 446 | E379652C1DEF0AD900C27AC7 /* NSScreen+MaxScaleFactor.m in Sources */, 447 | E37964E61DEEED3A00C27AC7 /* LPImageDownloadManager.m in Sources */, 448 | E37964D91DEEED3A00C27AC7 /* NSString+MD5.m in Sources */, 449 | E37964A51DEEEC9600C27AC7 /* IVPhoto.m in Sources */, 450 | E3E940231E3E495A00094B83 /* TTOfflineChecker.m in Sources */, 451 | E37965211DEF098900C27AC7 /* LPUnsplashPhotoView.m in Sources */, 452 | E3E940281E3E49BA00094B83 /* Reachability.m in Sources */, 453 | E37964AF1DEEEC9600C27AC7 /* IVCollection.m in Sources */, 454 | E37964DF1DEEED3A00C27AC7 /* NSImage+Luminance.m in Sources */, 455 | E37964B41DEEECAE00C27AC7 /* LPUnsplashAPI.m in Sources */, 456 | E37964F31DEEEDF600C27AC7 /* LPFileDownloader.m in Sources */, 457 | E37964E81DEEED3A00C27AC7 /* NSBezierPath+CGPath.m in Sources */, 458 | E37964DD1DEEED3A00C27AC7 /* NSSet+Utilities.m in Sources */, 459 | E37964D71DEEED3A00C27AC7 /* NSDictionary+NSURL.m in Sources */, 460 | ); 461 | runOnlyForDeploymentPostprocessing = 0; 462 | }; 463 | /* End PBXSourcesBuildPhase section */ 464 | 465 | /* Begin PBXVariantGroup section */ 466 | E37965021DEEFDC400C27AC7 /* MainMenu.xib */ = { 467 | isa = PBXVariantGroup; 468 | children = ( 469 | E37965031DEEFDC400C27AC7 /* Base */, 470 | ); 471 | name = MainMenu.xib; 472 | sourceTree = ""; 473 | }; 474 | /* End PBXVariantGroup section */ 475 | 476 | /* Begin XCBuildConfiguration section */ 477 | E37965071DEEFDC400C27AC7 /* Debug */ = { 478 | isa = XCBuildConfiguration; 479 | buildSettings = { 480 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 481 | COMBINE_HIDPI_IMAGES = YES; 482 | DEVELOPMENT_TEAM = 6KXZX3AW22; 483 | INFOPLIST_FILE = ScreenSaverRunner/Info.plist; 484 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 485 | PRODUCT_BUNDLE_IDENTIFIER = com.leonspok.osx.ScreenSaverRunner; 486 | PRODUCT_NAME = "$(TARGET_NAME)"; 487 | }; 488 | name = Debug; 489 | }; 490 | E37965081DEEFDC400C27AC7 /* Release */ = { 491 | isa = XCBuildConfiguration; 492 | buildSettings = { 493 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 494 | COMBINE_HIDPI_IMAGES = YES; 495 | DEVELOPMENT_TEAM = 6KXZX3AW22; 496 | INFOPLIST_FILE = ScreenSaverRunner/Info.plist; 497 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 498 | PRODUCT_BUNDLE_IDENTIFIER = com.leonspok.osx.ScreenSaverRunner; 499 | PRODUCT_NAME = "$(TARGET_NAME)"; 500 | }; 501 | name = Release; 502 | }; 503 | E3FBA54F1DEEE9CD00CF2932 /* Debug */ = { 504 | isa = XCBuildConfiguration; 505 | buildSettings = { 506 | ALWAYS_SEARCH_USER_PATHS = NO; 507 | CLANG_ANALYZER_NONNULL = YES; 508 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 509 | CLANG_CXX_LIBRARY = "libc++"; 510 | CLANG_ENABLE_MODULES = YES; 511 | CLANG_ENABLE_OBJC_ARC = YES; 512 | CLANG_WARN_BOOL_CONVERSION = YES; 513 | CLANG_WARN_CONSTANT_CONVERSION = YES; 514 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 515 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 516 | CLANG_WARN_EMPTY_BODY = YES; 517 | CLANG_WARN_ENUM_CONVERSION = YES; 518 | CLANG_WARN_INFINITE_RECURSION = YES; 519 | CLANG_WARN_INT_CONVERSION = YES; 520 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 521 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 522 | CLANG_WARN_UNREACHABLE_CODE = YES; 523 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 524 | CODE_SIGN_IDENTITY = "-"; 525 | COPY_PHASE_STRIP = NO; 526 | DEBUG_INFORMATION_FORMAT = dwarf; 527 | ENABLE_STRICT_OBJC_MSGSEND = YES; 528 | ENABLE_TESTABILITY = YES; 529 | GCC_C_LANGUAGE_STANDARD = gnu99; 530 | GCC_DYNAMIC_NO_PIC = NO; 531 | GCC_NO_COMMON_BLOCKS = YES; 532 | GCC_OPTIMIZATION_LEVEL = 0; 533 | GCC_PREPROCESSOR_DEFINITIONS = ( 534 | "DEBUG=1", 535 | "$(inherited)", 536 | ); 537 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 538 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 539 | GCC_WARN_UNDECLARED_SELECTOR = YES; 540 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 541 | GCC_WARN_UNUSED_FUNCTION = YES; 542 | GCC_WARN_UNUSED_VARIABLE = YES; 543 | MACOSX_DEPLOYMENT_TARGET = 10.12; 544 | MTL_ENABLE_DEBUG_INFO = YES; 545 | ONLY_ACTIVE_ARCH = YES; 546 | SDKROOT = macosx; 547 | }; 548 | name = Debug; 549 | }; 550 | E3FBA5501DEEE9CD00CF2932 /* Release */ = { 551 | isa = XCBuildConfiguration; 552 | buildSettings = { 553 | ALWAYS_SEARCH_USER_PATHS = NO; 554 | CLANG_ANALYZER_NONNULL = YES; 555 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 556 | CLANG_CXX_LIBRARY = "libc++"; 557 | CLANG_ENABLE_MODULES = YES; 558 | CLANG_ENABLE_OBJC_ARC = YES; 559 | CLANG_WARN_BOOL_CONVERSION = YES; 560 | CLANG_WARN_CONSTANT_CONVERSION = YES; 561 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 562 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 563 | CLANG_WARN_EMPTY_BODY = YES; 564 | CLANG_WARN_ENUM_CONVERSION = YES; 565 | CLANG_WARN_INFINITE_RECURSION = YES; 566 | CLANG_WARN_INT_CONVERSION = YES; 567 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 568 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 569 | CLANG_WARN_UNREACHABLE_CODE = YES; 570 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 571 | CODE_SIGN_IDENTITY = "-"; 572 | COPY_PHASE_STRIP = NO; 573 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 574 | ENABLE_NS_ASSERTIONS = NO; 575 | ENABLE_STRICT_OBJC_MSGSEND = YES; 576 | GCC_C_LANGUAGE_STANDARD = gnu99; 577 | GCC_NO_COMMON_BLOCKS = YES; 578 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 579 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 580 | GCC_WARN_UNDECLARED_SELECTOR = YES; 581 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 582 | GCC_WARN_UNUSED_FUNCTION = YES; 583 | GCC_WARN_UNUSED_VARIABLE = YES; 584 | MACOSX_DEPLOYMENT_TARGET = 10.12; 585 | MTL_ENABLE_DEBUG_INFO = NO; 586 | SDKROOT = macosx; 587 | }; 588 | name = Release; 589 | }; 590 | E3FBA5521DEEE9CD00CF2932 /* Debug */ = { 591 | isa = XCBuildConfiguration; 592 | buildSettings = { 593 | CODE_SIGN_IDENTITY = "Mac Developer"; 594 | COMBINE_HIDPI_IMAGES = YES; 595 | DEVELOPMENT_TEAM = 6KXZX3AW22; 596 | INFOPLIST_FILE = "Irvue Screensaver/Info.plist"; 597 | INSTALL_PATH = "$(HOME)/Library/Screen Savers"; 598 | MACOSX_DEPLOYMENT_TARGET = 10.11; 599 | PRODUCT_BUNDLE_IDENTIFIER = "com.leonspok.osx.Irvue-Screensaver"; 600 | PRODUCT_NAME = "$(TARGET_NAME)"; 601 | WRAPPER_EXTENSION = saver; 602 | }; 603 | name = Debug; 604 | }; 605 | E3FBA5531DEEE9CD00CF2932 /* Release */ = { 606 | isa = XCBuildConfiguration; 607 | buildSettings = { 608 | CODE_SIGN_IDENTITY = "Mac Developer"; 609 | COMBINE_HIDPI_IMAGES = YES; 610 | DEVELOPMENT_TEAM = 6KXZX3AW22; 611 | INFOPLIST_FILE = "Irvue Screensaver/Info.plist"; 612 | INSTALL_PATH = "$(HOME)/Library/Screen Savers"; 613 | MACOSX_DEPLOYMENT_TARGET = 10.11; 614 | PRODUCT_BUNDLE_IDENTIFIER = "com.leonspok.osx.Irvue-Screensaver"; 615 | PRODUCT_NAME = "$(TARGET_NAME)"; 616 | WRAPPER_EXTENSION = saver; 617 | }; 618 | name = Release; 619 | }; 620 | /* End XCBuildConfiguration section */ 621 | 622 | /* Begin XCConfigurationList section */ 623 | E37965061DEEFDC400C27AC7 /* Build configuration list for PBXNativeTarget "ScreenSaverRunner" */ = { 624 | isa = XCConfigurationList; 625 | buildConfigurations = ( 626 | E37965071DEEFDC400C27AC7 /* Debug */, 627 | E37965081DEEFDC400C27AC7 /* Release */, 628 | ); 629 | defaultConfigurationIsVisible = 0; 630 | defaultConfigurationName = Release; 631 | }; 632 | E3FBA5411DEEE9CD00CF2932 /* Build configuration list for PBXProject "Irvue Screensaver" */ = { 633 | isa = XCConfigurationList; 634 | buildConfigurations = ( 635 | E3FBA54F1DEEE9CD00CF2932 /* Debug */, 636 | E3FBA5501DEEE9CD00CF2932 /* Release */, 637 | ); 638 | defaultConfigurationIsVisible = 0; 639 | defaultConfigurationName = Release; 640 | }; 641 | E3FBA5511DEEE9CD00CF2932 /* Build configuration list for PBXNativeTarget "Irvue Screensaver" */ = { 642 | isa = XCConfigurationList; 643 | buildConfigurations = ( 644 | E3FBA5521DEEE9CD00CF2932 /* Debug */, 645 | E3FBA5531DEEE9CD00CF2932 /* Release */, 646 | ); 647 | defaultConfigurationIsVisible = 0; 648 | defaultConfigurationName = Release; 649 | }; 650 | /* End XCConfigurationList section */ 651 | }; 652 | rootObject = E3FBA53E1DEEE9CD00CF2932 /* Project object */; 653 | } 654 | -------------------------------------------------------------------------------- /Irvue Screensaver.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Irvue Screensaver/ConfigureSheet.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 50 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | -------------------------------------------------------------------------------- /Irvue Screensaver/IVCollection.h: -------------------------------------------------------------------------------- 1 | // 2 | // IVCollection.h 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 12/07/16. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LPJSONConvertable.h" 11 | 12 | @class IVPhoto, IVUser; 13 | 14 | @interface IVCollection : NSObject 15 | 16 | @property (nonatomic, strong) NSNumber *uid; 17 | @property (nonatomic, strong) NSString *title; 18 | @property (nonatomic, strong) NSString *collectionDescription; 19 | @property (nonatomic, strong) NSDate *publishedAt; 20 | @property (nonatomic, strong) NSNumber *totalPhotos; 21 | @property (nonatomic, strong) IVPhoto *coverPhoto; 22 | @property (nonatomic, strong) IVUser *user; 23 | @property (nonatomic) BOOL curated; 24 | 25 | @property (nonatomic, strong, readonly) NSURL *webpageURL; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /Irvue Screensaver/IVCollection.m: -------------------------------------------------------------------------------- 1 | // 2 | // IVCollection.m 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 12/07/16. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import "IVCollection.h" 10 | #import "NSArray+Utilities.h" 11 | #import "IVPhoto.h" 12 | #import "IVUser.h" 13 | #import "LPUnsplashAPI.h" 14 | 15 | @implementation IVCollection 16 | 17 | - (id)initWithJSON:(NSDictionary *)json { 18 | self = [super init]; 19 | if (self) { 20 | [self updateWithJSON:json]; 21 | } 22 | return self; 23 | } 24 | 25 | - (void)updateWithJSON:(NSDictionary *)json { 26 | if ([[json objectForKey:@"id"] isKindOfClass:NSNumber.class]) { 27 | self.uid = [json objectForKey:@"id"]; 28 | } 29 | if ([[json objectForKey:@"title"] isKindOfClass:NSString.class]) { 30 | self.title = [json objectForKey:@"title"]; 31 | } 32 | if ([[json objectForKey:@"description"] isKindOfClass:NSString.class]) { 33 | self.collectionDescription = [json objectForKey:@"description"]; 34 | } 35 | if ([[json objectForKey:@"published_at"] isKindOfClass:NSString.class]) { 36 | NSString *publishedAtString = [json objectForKey:@"published_at"]; 37 | NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 38 | [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssXXX"]; 39 | self.publishedAt = [formatter dateFromString:publishedAtString]; 40 | } 41 | if ([[json objectForKey:@"total_photos"] isKindOfClass:NSNumber.class]) { 42 | self.totalPhotos = [json objectForKey:@"total_photos"]; 43 | } 44 | if ([[json objectForKey:@"curated"] isKindOfClass:NSNumber.class]) { 45 | self.curated = [[json objectForKey:@"curated"] boolValue]; 46 | } 47 | if ([[json objectForKey:@"cover_photo"] isKindOfClass:NSDictionary.class]) { 48 | self.coverPhoto = [[IVPhoto alloc] initWithJSON:[json objectForKey:@"cover_photo"]]; 49 | } 50 | if ([[json objectForKey:@"user"] isKindOfClass:NSDictionary.class]) { 51 | self.user = [[IVUser alloc] initWithJSON:[json objectForKey:@"user"]]; 52 | } 53 | } 54 | 55 | + (NSArray *)createObjectsFromJSON:(NSArray *)jsonObjects { 56 | return [jsonObjects mapWithBlock:^id(id obj) { 57 | return [[IVCollection alloc] initWithJSON:obj]; 58 | }]; 59 | } 60 | 61 | - (NSUInteger)hash { 62 | return self.uid.hash; 63 | } 64 | 65 | - (BOOL)isEqual:(id)object { 66 | if (!object) { 67 | return NO; 68 | } 69 | if (object == self) { 70 | return YES; 71 | } 72 | if ([object class] == self.class) { 73 | IVUser *user = (IVUser *)object; 74 | return [user.uid isEqual:self.uid]; 75 | } 76 | return NO; 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /Irvue Screensaver/IVPhoto.h: -------------------------------------------------------------------------------- 1 | // 2 | // IVPhoto.h 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 09/02/16. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LPJSONConvertable.h" 11 | 12 | @class IVPhotoCategory, IVPhotoExif, IVPhotoLocation, IVUser, IVCollection; 13 | 14 | @interface IVPhoto : NSObject 15 | 16 | @property (nonatomic, strong) NSString *uid; 17 | @property (nonatomic) NSSize size; 18 | @property (nonatomic, strong) NSString *color; 19 | @property (nonatomic, strong) NSURL *fullImageURL; 20 | @property (nonatomic, strong) NSURL *regularImageURL; 21 | @property (nonatomic, strong) NSURL *smallImageURL; 22 | @property (nonatomic, strong) NSURL *thumbImageURL; 23 | @property (nonatomic, strong) NSNumber *downloads; 24 | @property (nonatomic, strong) NSNumber *likes; 25 | @property (nonatomic, strong) IVPhotoExif *exif; 26 | @property (nonatomic, strong) IVPhotoLocation *location; 27 | @property (nonatomic, strong) IVUser *author; 28 | 29 | @property (nonatomic, strong, readonly) NSURL *webpageURL; 30 | @property (nonatomic, strong, readonly) NSURL *imageURLForDownload; 31 | 32 | - (NSURL *)imageURLForSize:(NSSize)size; 33 | 34 | 35 | @property (nonatomic, readonly, getter=isPlaceholder) BOOL placeholder; 36 | + (instancetype)placeholderPhoto; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /Irvue Screensaver/IVPhoto.m: -------------------------------------------------------------------------------- 1 | // 2 | // IVPhoto.m 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 09/02/16. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import "IVPhoto.h" 10 | #import "IVPhotoExif.h" 11 | #import "IVPhotoLocation.h" 12 | #import "IVUser.h" 13 | #import "NSArray+Utilities.h" 14 | #import "NSDictionary+NSURL.h" 15 | #import "LPUnsplashAPI.h" 16 | 17 | @interface IVPhoto() 18 | @property (nonatomic, readwrite) BOOL placeholder; 19 | @end 20 | 21 | @implementation IVPhoto 22 | 23 | - (id)initWithCoder:(NSCoder *)aDecoder { 24 | self = [self init]; 25 | if (self) { 26 | self.uid = [aDecoder decodeObjectForKey:@"uid"]; 27 | self.fullImageURL = [aDecoder decodeObjectForKey:@"fullImageURL"]; 28 | self.regularImageURL = [aDecoder decodeObjectForKey:@"regularImageURL"]; 29 | self.smallImageURL = [aDecoder decodeObjectForKey:@"smalleImageURL"]; 30 | self.thumbImageURL = [aDecoder decodeObjectForKey:@"thumbImageURL"]; 31 | self.exif = [aDecoder decodeObjectForKey:@"exif"]; 32 | self.location = [aDecoder decodeObjectForKey:@"location"]; 33 | self.author = [aDecoder decodeObjectForKey:@"author"]; 34 | } 35 | return self; 36 | } 37 | 38 | - (void)encodeWithCoder:(NSCoder *)aCoder { 39 | [aCoder encodeObject:self.uid forKey:@"uid"]; 40 | [aCoder encodeObject:self.fullImageURL forKey:@"fullImageURL"]; 41 | [aCoder encodeObject:self.regularImageURL forKey:@"regularImageURL"]; 42 | [aCoder encodeObject:self.smallImageURL forKey:@"smallImageURL"]; 43 | [aCoder encodeObject:self.thumbImageURL forKey:@"thumbImageURL"]; 44 | [aCoder encodeObject:self.exif forKey:@"exif"]; 45 | [aCoder encodeObject:self.location forKey:@"location"]; 46 | [aCoder encodeObject:self.author forKey:@"author"]; 47 | } 48 | 49 | - (id)initWithJSON:(NSDictionary *)json { 50 | self = [super init]; 51 | if (self) { 52 | [self updateWithJSON:json]; 53 | } 54 | return self; 55 | } 56 | 57 | - (void)updateWithJSON:(NSDictionary *)json { 58 | if ([[json objectForKey:@"id"] isKindOfClass:NSString.class]) { 59 | self.uid = [json objectForKey:@"id"]; 60 | } 61 | if ([[json objectForKey:@"width"] isKindOfClass:NSNumber.class] && [[json objectForKey:@"height"] isKindOfClass:NSNumber.class]) { 62 | NSNumber *width = [json objectForKey:@"width"]; 63 | NSNumber *height = [json objectForKey:@"height"]; 64 | self.size = NSMakeSize(width.floatValue, height.floatValue); 65 | } 66 | if ([[json objectForKey:@"color"] isKindOfClass:NSString.class]) { 67 | self.color = [json objectForKey:@"color"]; 68 | } 69 | if ([[json objectForKey:@"urls"] isKindOfClass:NSDictionary.class]) { 70 | NSDictionary *imageURLs = [json objectForKey:@"urls"]; 71 | if ([imageURLs objectForKey:@"full"]) { 72 | self.fullImageURL = [NSURL URLWithString:[imageURLs objectForKey:@"full"]]; 73 | } 74 | if ([imageURLs objectForKey:@"regular"]) { 75 | self.regularImageURL = [NSURL URLWithString:[imageURLs objectForKey:@"regular"]]; 76 | } 77 | if ([imageURLs objectForKey:@"small"]) { 78 | self.smallImageURL = [NSURL URLWithString:[imageURLs objectForKey:@"small"]]; 79 | } 80 | if ([imageURLs objectForKey:@"thumb"]) { 81 | self.thumbImageURL = [NSURL URLWithString:[imageURLs objectForKey:@"thumb"]]; 82 | } 83 | } 84 | if ([[json objectForKey:@"downloads"] isKindOfClass:NSNumber.class]) { 85 | self.downloads = [json objectForKey:@"downloads"]; 86 | } 87 | if ([[json objectForKey:@"likes"] isKindOfClass:NSNumber.class]) { 88 | self.likes = [json objectForKey:@"likes"]; 89 | } 90 | if ([[json objectForKey:@"exif"] isKindOfClass:NSDictionary.class]) { 91 | self.exif = [[IVPhotoExif alloc] initWithJSON:[json objectForKey:@"exif"]]; 92 | } 93 | if ([[json objectForKey:@"location"] isKindOfClass:NSDictionary.class]) { 94 | self.location = [[IVPhotoLocation alloc] initWithJSON:[json objectForKey:@"location"]]; 95 | } 96 | if ([[json objectForKey:@"user"] isKindOfClass:NSDictionary.class]) { 97 | self.author = [[IVUser alloc] initWithJSON:[json objectForKey:@"user"]]; 98 | } 99 | } 100 | 101 | + (NSArray *)createObjectsFromJSON:(NSArray *)jsonObjects { 102 | return [jsonObjects mapWithBlock:^id(id obj) { 103 | return [[IVPhoto alloc] initWithJSON:obj]; 104 | }]; 105 | } 106 | 107 | - (NSUInteger)hash { 108 | return self.uid.hash; 109 | } 110 | 111 | - (BOOL)isEqual:(id)object { 112 | if (!object) { 113 | return NO; 114 | } 115 | if (object == self) { 116 | return YES; 117 | } 118 | if ([object class] == self.class) { 119 | IVPhoto *photo = (IVPhoto *)object; 120 | return [photo.uid isEqual:self.uid]; 121 | } 122 | return NO; 123 | } 124 | 125 | - (NSURL *)imageURLForSize:(NSSize)size { 126 | NSString *urlString = [self.fullImageURL absoluteString]; 127 | NSMutableDictionary *params = [[NSDictionary dictionaryWithURL:self.fullImageURL] mutableCopy]; 128 | if (!NSEqualSizes(self.size, NSZeroSize)) { 129 | CGFloat sourceRatio = self.size.width/self.size.height; 130 | CGFloat targetRatio = size.width/size.height; 131 | if (sourceRatio > targetRatio) { 132 | NSString *height = [NSString stringWithFormat:@"%ld", (long)size.height]; 133 | [params setObject:height forKey:@"h"]; 134 | } else { 135 | NSString *width = [NSString stringWithFormat:@"%ld", (long)size.width]; 136 | [params setObject:width forKey:@"w"]; 137 | } 138 | } else { 139 | NSString *width = [NSString stringWithFormat:@"%ld", (long)size.width]; 140 | [params setObject:width forKey:@"w"]; 141 | } 142 | [params setObject:@"jpg" forKey:@"fm"]; 143 | [params setObject:@"90" forKey:@"q"]; 144 | [params setObject:@"entropy" forKey:@"crop"]; 145 | [params setObject:@"max" forKey:@"fit"]; 146 | NSURL *url = [params urlWithBase:[urlString substringWithRange:NSMakeRange(0, [urlString rangeOfString:@"?"].location != NSNotFound? [urlString rangeOfString:@"?"].location : urlString.length)]]; 147 | return url; 148 | } 149 | 150 | #pragma mark Placeholder 151 | 152 | + (instancetype)placeholderPhoto { 153 | IVPhoto *photo = [IVPhoto new]; 154 | photo.uid = @"YadCgbsLHcE"; 155 | photo.placeholder = YES; 156 | IVUser *user = [IVUser new]; 157 | user.username = @"willpower"; 158 | user.name = @"William Stitt"; 159 | photo.author = user; 160 | return photo; 161 | } 162 | 163 | @end 164 | -------------------------------------------------------------------------------- /Irvue Screensaver/IVPhotoExif.h: -------------------------------------------------------------------------------- 1 | // 2 | // IVPhotoExif.h 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 09/02/16. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LPJSONConvertable.h" 11 | 12 | @class IVPhoto; 13 | 14 | @interface IVPhotoExif : NSObject 15 | 16 | @property (nonatomic, strong) NSString *make; 17 | @property (nonatomic, strong) NSString *model; 18 | @property (nonatomic) NSTimeInterval exposureTime; 19 | @property (nonatomic, strong, readonly) NSString *exposureTimeString; 20 | @property (nonatomic) double aperture; 21 | @property (nonatomic) double focalLength; 22 | @property (nonatomic) NSUInteger iso; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Irvue Screensaver/IVPhotoExif.m: -------------------------------------------------------------------------------- 1 | // 2 | // IVPhotoExif.m 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 09/02/16. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import "IVPhotoExif.h" 10 | #import "NSArray+Utilities.h" 11 | 12 | @implementation IVPhotoExif 13 | 14 | - (id)initWithCoder:(NSCoder *)aDecoder { 15 | self = [self init]; 16 | if (self) { 17 | self.make = [aDecoder decodeObjectForKey:@"make"]; 18 | self.model = [aDecoder decodeObjectForKey:@"model"]; 19 | self.exposureTime = [[aDecoder decodeObjectForKey:@"exposureTime"] doubleValue]; 20 | self.aperture = [[aDecoder decodeObjectForKey:@"aperture"] doubleValue]; 21 | self.focalLength = [[aDecoder decodeObjectForKey:@"focalLength"] doubleValue]; 22 | self.iso = [[aDecoder decodeObjectForKey:@"iso"] unsignedIntegerValue]; 23 | } 24 | return self; 25 | } 26 | 27 | - (void)encodeWithCoder:(NSCoder *)aCoder { 28 | [aCoder encodeObject:self.make forKey:@"make"]; 29 | [aCoder encodeObject:self.model forKey:@"model"]; 30 | [aCoder encodeObject:@(self.exposureTime) forKey:@"exposureTime"]; 31 | [aCoder encodeObject:@(self.aperture) forKey:@"aperture"]; 32 | [aCoder encodeObject:@(self.focalLength) forKey:@"focalLength"]; 33 | [aCoder encodeObject:@(self.iso) forKey:@"iso"]; 34 | } 35 | 36 | - (id)initWithJSON:(NSDictionary *)json { 37 | self = [super init]; 38 | if (self) { 39 | [self updateWithJSON:json]; 40 | } 41 | return self; 42 | } 43 | 44 | - (void)updateWithJSON:(NSDictionary *)json { 45 | if ([[json objectForKey:@"make"] isKindOfClass:NSString.class]) { 46 | self.make = [json objectForKey:@"make"]; 47 | } 48 | if ([[json objectForKey:@"model"] isKindOfClass:NSString.class]) { 49 | self.model = [json objectForKey:@"model"]; 50 | } 51 | if ([[json objectForKey:@"exposure_time"] isKindOfClass:NSString.class]) { 52 | NSString *exposureTime = [json objectForKey:@"exposure_time"]; 53 | NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; 54 | numberFormatter.numberStyle = NSNumberFormatterDecimalStyle; 55 | self.exposureTime = [[numberFormatter numberFromString:exposureTime] floatValue]; 56 | } else if ([[json objectForKey:@"exposure_time"] isKindOfClass:NSNumber.class]) { 57 | self.exposureTime = [[json objectForKey:@"exposure_time"] floatValue]; 58 | } 59 | if ([[json objectForKey:@"aperture"] isKindOfClass:NSString.class]) { 60 | NSString *aperture = [json objectForKey:@"aperture"]; 61 | NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; 62 | numberFormatter.numberStyle = NSNumberFormatterDecimalStyle; 63 | self.aperture = [[numberFormatter numberFromString:aperture] doubleValue]; 64 | } else if ([[json objectForKey:@"aperture"] isKindOfClass:NSNumber.class]) { 65 | self.aperture = [[json objectForKey:@"aperture"] doubleValue]; 66 | } 67 | if ([[json objectForKey:@"focal_length"] isKindOfClass:NSString.class]) { 68 | NSString *focalLength = [json objectForKey:@"focal_length"]; 69 | NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; 70 | numberFormatter.numberStyle = NSNumberFormatterDecimalStyle; 71 | self.focalLength = [[numberFormatter numberFromString:focalLength] doubleValue]; 72 | } else if ([[json objectForKey:@"focal_length"] isKindOfClass:NSNumber.class]) { 73 | self.focalLength = [[json objectForKey:@"focal_length"] doubleValue]; 74 | } 75 | if ([[json objectForKey:@"iso"] isKindOfClass:NSNumber.class]) { 76 | self.iso = [[json objectForKey:@"iso"] unsignedIntegerValue]; 77 | } 78 | } 79 | 80 | + (NSArray *)createObjectsFromJSON:(NSArray *)jsonObjects { 81 | return [jsonObjects mapWithBlock:^id(id obj) { 82 | return [[IVPhotoExif alloc] initWithJSON:obj]; 83 | }]; 84 | } 85 | 86 | - (NSString *)exposureTimeString { 87 | if (1.0f/self.exposureTime > 1) { 88 | return [NSString stringWithFormat:@"1/%lds", (long)round(1.0f/self.exposureTime)]; 89 | } else { 90 | return [NSString stringWithFormat:@"%.2fs", self.exposureTime]; 91 | } 92 | } 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /Irvue Screensaver/IVPhotoLocation.h: -------------------------------------------------------------------------------- 1 | // 2 | // IVPhotoLocation.h 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 09/02/16. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LPJSONConvertable.h" 11 | 12 | @import CoreLocation; 13 | 14 | @class IVPhoto; 15 | 16 | @interface IVPhotoLocation : NSObject 17 | 18 | @property (nonatomic, strong) NSString *city; 19 | @property (nonatomic, strong) NSString *country; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Irvue Screensaver/IVPhotoLocation.m: -------------------------------------------------------------------------------- 1 | // 2 | // IVPhotoLocation.m 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 09/02/16. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import "IVPhotoLocation.h" 10 | #import "NSArray+Utilities.h" 11 | 12 | @implementation IVPhotoLocation 13 | 14 | - (id)initWithCoder:(NSCoder *)aDecoder { 15 | self = [self init]; 16 | if (self) { 17 | self.city = [aDecoder decodeObjectForKey:@"city"]; 18 | self.country = [aDecoder decodeObjectForKey:@"country"]; 19 | } 20 | return self; 21 | } 22 | 23 | - (void)encodeWithCoder:(NSCoder *)aCoder { 24 | [aCoder encodeObject:self.city forKey:@"city"]; 25 | [aCoder encodeObject:self.country forKey:@"country"]; 26 | } 27 | 28 | - (id)initWithJSON:(NSDictionary *)json { 29 | self = [super init]; 30 | if (self) { 31 | [self updateWithJSON:json]; 32 | } 33 | return self; 34 | } 35 | 36 | - (void)updateWithJSON:(NSDictionary *)json { 37 | if ([[json objectForKey:@"city"] isKindOfClass:NSString.class]) { 38 | self.city = [json objectForKey:@"city"]; 39 | } 40 | if ([[json objectForKey:@"country"] isKindOfClass:NSString.class]) { 41 | self.country = [json objectForKey:@"country"]; 42 | } 43 | } 44 | 45 | + (NSArray *)createObjectsFromJSON:(NSArray *)jsonObjects { 46 | return [jsonObjects mapWithBlock:^id(id obj) { 47 | return [[IVPhotoLocation alloc] initWithJSON:obj]; 48 | }]; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Irvue Screensaver/IVUser.h: -------------------------------------------------------------------------------- 1 | // 2 | // IVUser.h 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 09/02/16. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "LPJSONConvertable.h" 11 | 12 | @class IVPhoto, IVCollection; 13 | 14 | @interface IVUser : NSObject 15 | 16 | @property (nonatomic, strong) NSString *uid; 17 | @property (nonatomic, strong) NSString *username; 18 | @property (nonatomic, strong) NSString *name; 19 | @property (nonatomic, strong) NSString *firstName; 20 | @property (nonatomic, strong) NSString *lastName; 21 | @property (nonatomic, strong) NSString *bio; 22 | @property (nonatomic, strong) NSURL *portfolioURL; 23 | @property (nonatomic, strong) NSNumber *downloads; 24 | @property (nonatomic, strong) NSURL *largeProfileImage; 25 | @property (nonatomic, strong) NSURL *mediumProfileImage; 26 | @property (nonatomic, strong) NSURL *smallProfileImage; 27 | 28 | @property (nonatomic, strong, readonly) NSURL *webpageURL; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /Irvue Screensaver/IVUser.m: -------------------------------------------------------------------------------- 1 | // 2 | // IVUser.m 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 09/02/16. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import "IVUser.h" 10 | #import "NSArray+Utilities.h" 11 | #import "LPUnsplashAPI.h" 12 | 13 | @implementation IVUser 14 | 15 | - (id)initWithCoder:(NSCoder *)aDecoder { 16 | self = [self init]; 17 | if (self) { 18 | self.uid = [aDecoder decodeObjectForKey:@"uid"]; 19 | self.username = [aDecoder decodeObjectForKey:@"username"]; 20 | self.name = [aDecoder decodeObjectForKey:@"name"]; 21 | self.firstName = [aDecoder decodeObjectForKey:@"firstName"]; 22 | self.lastName = [aDecoder decodeObjectForKey:@"lastName"]; 23 | self.portfolioURL = [aDecoder decodeObjectForKey:@"portfolioURL"]; 24 | self.smallProfileImage = [aDecoder decodeObjectForKey:@"smallProfileImage"]; 25 | self.mediumProfileImage = [aDecoder decodeObjectForKey:@"mediumProfileImage"]; 26 | self.largeProfileImage = [aDecoder decodeObjectForKey:@"largeProfileImage"]; 27 | } 28 | return self; 29 | } 30 | 31 | - (void)encodeWithCoder:(NSCoder *)aCoder { 32 | [aCoder encodeObject:self.uid forKey:@"uid"]; 33 | [aCoder encodeObject:self.username forKey:@"username"]; 34 | [aCoder encodeObject:self.name forKey:@"name"]; 35 | [aCoder encodeObject:self.firstName forKey:@"firstName"]; 36 | [aCoder encodeObject:self.lastName forKey:@"lastName"]; 37 | [aCoder encodeObject:self.portfolioURL forKey:@"portfolioURL"]; 38 | [aCoder encodeObject:self.smallProfileImage forKey:@"smallProfileImage"]; 39 | [aCoder encodeObject:self.mediumProfileImage forKey:@"mediumProfileImage"]; 40 | [aCoder encodeObject:self.largeProfileImage forKey:@"largetProfileImage"]; 41 | } 42 | 43 | - (id)initWithJSON:(NSDictionary *)json { 44 | self = [super init]; 45 | if (self) { 46 | [self updateWithJSON:json]; 47 | } 48 | return self; 49 | } 50 | 51 | - (void)updateWithJSON:(NSDictionary *)json { 52 | if ([[json objectForKey:@"id"] isKindOfClass:NSNumber.class]) { 53 | self.uid = [json objectForKey:@"id"]; 54 | } 55 | if ([[json objectForKey:@"username"] isKindOfClass:NSString.class]) { 56 | self.username = [json objectForKey:@"username"]; 57 | } 58 | if ([[json objectForKey:@"name"] isKindOfClass:NSString.class]) { 59 | self.name = [json objectForKey:@"name"]; 60 | } 61 | if ([[json objectForKey:@"first_name"] isKindOfClass:NSString.class]) { 62 | self.firstName = [json objectForKey:@"first_name"]; 63 | } 64 | if ([[json objectForKey:@"last_name"] isKindOfClass:NSString.class]) { 65 | self.lastName = [json objectForKey:@"last_name"]; 66 | } 67 | if ([[json objectForKey:@"portfolio_url"] isKindOfClass:NSString.class]) { 68 | self.portfolioURL = [NSURL URLWithString:[json objectForKey:@"portfolio_url"]]; 69 | } 70 | if ([[json objectForKey:@"downloads"] isKindOfClass:NSNumber.class]) { 71 | self.downloads = [json objectForKey:@"downloads"]; 72 | } 73 | if ([[json objectForKey:@"profile_image"] isKindOfClass:NSDictionary.class]) { 74 | NSDictionary *images = [json objectForKey:@"profile_image"]; 75 | if ([[images objectForKey:@"small"] isKindOfClass:NSString.class]) { 76 | self.smallProfileImage = [NSURL URLWithString:[images objectForKey:@"small"]]; 77 | } 78 | if ([[images objectForKey:@"medium"] isKindOfClass:NSString.class]) { 79 | self.mediumProfileImage = [NSURL URLWithString:[images objectForKey:@"medium"]]; 80 | } 81 | if ([[images objectForKey:@"large"] isKindOfClass:NSString.class]) { 82 | self.largeProfileImage = [NSURL URLWithString:[images objectForKey:@"large"]]; 83 | } 84 | self.username = [json objectForKey:@"username"]; 85 | } 86 | } 87 | 88 | + (NSArray *)createObjectsFromJSON:(NSArray *)jsonObjects { 89 | return [jsonObjects mapWithBlock:^id(id obj) { 90 | return [[IVUser alloc] initWithJSON:obj]; 91 | }]; 92 | } 93 | 94 | - (NSUInteger)hash { 95 | return self.uid.hash; 96 | } 97 | 98 | - (BOOL)isEqual:(id)object { 99 | if (!object) { 100 | return NO; 101 | } 102 | if (object == self) { 103 | return YES; 104 | } 105 | if ([object class] == self.class) { 106 | IVUser *user = (IVUser *)object; 107 | return [user.uid isEqual:self.uid]; 108 | } 109 | return NO; 110 | } 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /Irvue Screensaver/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0.9 19 | CFBundleVersion 20 | 1090 21 | NSHumanReadableCopyright 22 | Copyright © 2016 Leonspok. All rights reserved. 23 | NSPrincipalClass 24 | Irvue_ScreensaverView 25 | 26 | 27 | -------------------------------------------------------------------------------- /Irvue Screensaver/Irvue_ScreensaverView.h: -------------------------------------------------------------------------------- 1 | // 2 | // Irvue_ScreensaverView.h 3 | // Irvue Screensaver 4 | // 5 | // Created by Игорь Савельев on 30/11/2016. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef enum { 12 | PhotosSourceFeatured = 0, 13 | PhotosSourceCollection = 1, 14 | PhotosSourceUser = 2, 15 | PhotosSourceSearch = 3 16 | } PhotosSource; 17 | 18 | @interface Irvue_ScreensaverView : ScreenSaverView 19 | 20 | @property (nonatomic, strong) ScreenSaverDefaults *defaults; 21 | @property (nonatomic) NSTimeInterval updateInterval; 22 | @property (nonatomic) PhotosSource source; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Irvue Screensaver/Irvue_ScreensaverView.m: -------------------------------------------------------------------------------- 1 | // 2 | // Irvue_ScreensaverView.m 3 | // Irvue Screensaver 4 | // 5 | // Created by Игорь Савельев on 30/11/2016. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import "Irvue_ScreensaverView.h" 10 | #import "LPUnsplashAPI.h" 11 | #import "LPImageDownloadManager.h" 12 | #import "NSImage+Effects.h" 13 | #import "LPUnsplashPhotoView.h" 14 | #import "NSArray+Utilities.h" 15 | #import "NSString+MD5.h" 16 | #import "TTOfflineChecker.h" 17 | 18 | typedef enum { 19 | SwitchDirectionLeft = 0, 20 | SwitchDirectionRight = 1, 21 | SwitchDirectionTop = 2, 22 | SwitchDirectionBottom = 3 23 | } SwitchDirection; 24 | 25 | @import QuartzCore; 26 | 27 | static NSString *const kPhotosCacheKey = @"photos_cached"; 28 | static NSString *const kUpdateIntervalKey = @"update_interval"; 29 | static NSString *const kPhotosSourceKey = @"photos_source"; 30 | static NSString *const kCollectionURLKey = @"collection_url"; 31 | static NSString *const kCollectionUIDKey = @"collection_uid"; 32 | static NSString *const kUsernameKey = @"username"; 33 | static NSString *const kSearchQueryKey = @"search_query"; 34 | static NSString *const kAppIdKey = @"app_id"; 35 | 36 | @interface Irvue_ScreensaverView() 37 | @property (nonatomic, strong) NSView *containerView; 38 | @property (nonatomic, strong) LPUnsplashPhotoView *photoView; 39 | 40 | @property (weak) IBOutlet NSButton *featuredRadioButton; 41 | @property (weak) IBOutlet NSButton *collectionRadioButton; 42 | @property (weak) IBOutlet NSTextField *collectionURLTextField; 43 | @property (weak) IBOutlet NSButton *userRadioButton; 44 | @property (weak) IBOutlet NSTextField *usernameTextField; 45 | @property (weak) IBOutlet NSButton *searchRadioButton; 46 | @property (weak) IBOutlet NSTextField *searchQueryTextField; 47 | @property (weak) IBOutlet NSTextField *appIdTextField; 48 | 49 | @property (weak) IBOutlet NSSlider *updateIntervalSlider; 50 | @property (weak) IBOutlet NSTextField *updateIntervalLabel; 51 | 52 | @property (strong) IBOutlet NSWindow *configSheet; 53 | 54 | @end 55 | 56 | @implementation Irvue_ScreensaverView { 57 | IVPhoto *testPhoto; 58 | NSTimer *changeImageTimer; 59 | SwitchDirection previousDirection; 60 | } 61 | 62 | - (instancetype)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview { 63 | self = [super initWithFrame:frame isPreview:isPreview]; 64 | if (self) { 65 | [[LPImageDownloadManager defaultManager] clearOldCache]; 66 | self.defaults = [ScreenSaverDefaults defaultsForModuleWithName:@"com.leonspok.osx.Irvue-Screensaver.defaults"]; 67 | 68 | [[LPUnsplashAPI sharedInstance] setUnsplashAppId:[self.defaults objectForKey:kAppIdKey]]; 69 | 70 | self.containerView = [[NSView alloc] initWithFrame:[self bounds]]; 71 | [self.containerView setWantsLayer:YES]; 72 | [self.containerView.layer setBackgroundColor:[NSColor blackColor].CGColor]; 73 | [self addSubview:self.containerView]; 74 | 75 | if ([self cachedPhotosFromSource:self.source].count == 0) { 76 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 77 | [self replaceCurrentPhotoViewWith:[[LPUnsplashPhotoView alloc] initWithPhoto:[IVPhoto placeholderPhoto]] animated:YES]; 78 | [self changeImage]; 79 | }); 80 | } else { 81 | [self changeImage]; 82 | } 83 | 84 | [self setAnimationTimeInterval:1/30.0]; 85 | } 86 | return self; 87 | } 88 | 89 | #pragma mark Getters And Setters 90 | 91 | - (void)setUpdateInterval:(NSTimeInterval)updateInterval { 92 | if (updateInterval < 5.0f) { 93 | return; 94 | } 95 | [self.defaults setObject:@(updateInterval) forKey:kUpdateIntervalKey]; 96 | [self.defaults synchronize]; 97 | } 98 | 99 | - (NSTimeInterval)updateInterval { 100 | NSNumber *number = [self.defaults objectForKey:kUpdateIntervalKey]; 101 | if (!number) { 102 | [self setUpdateInterval:60.0f]; 103 | return [self updateInterval]; 104 | } 105 | return [number doubleValue]; 106 | } 107 | 108 | - (void)setSource:(PhotosSource)source { 109 | [self.defaults setObject:@(source) forKey:kPhotosSourceKey]; 110 | [self.defaults synchronize]; 111 | } 112 | 113 | - (PhotosSource)source { 114 | NSNumber *number = [self.defaults objectForKey:kPhotosSourceKey]; 115 | if (!number) { 116 | [self setSource:PhotosSourceFeatured]; 117 | return [self source]; 118 | } 119 | return [number doubleValue]; 120 | } 121 | 122 | #pragma mark Data 123 | 124 | - (void)loadImagesCount:(NSUInteger)count success:(void (^)(NSArray *photos))success failure:(void (^)(NSError *error))failure { 125 | switch(self.source) { 126 | case PhotosSourceFeatured: 127 | [[LPUnsplashAPI sharedInstance] getRandomPhotosFromFeaturedCount:count success:success failure:failure]; 128 | break; 129 | case PhotosSourceCollection: { 130 | NSNumber *collectionUID = [self.defaults objectForKey:kCollectionUIDKey]; 131 | if (collectionUID) { 132 | [[LPUnsplashAPI sharedInstance] getRandomPhotosFromCollectionWithID:collectionUID count:count success:success failure:failure]; 133 | } else { 134 | if (failure) { 135 | failure([NSError errorWithDomain:NSStringFromClass(self.class) code:1 userInfo:@{@"message": @"no collection id"}]); 136 | } 137 | } 138 | break; 139 | } 140 | case PhotosSourceUser: { 141 | NSString *username = [self.defaults objectForKey:kUsernameKey]; 142 | if (username.length > 0) { 143 | [[LPUnsplashAPI sharedInstance] getRandomPhotosFromUserWithName:username count:count success:success failure:failure]; 144 | } else { 145 | if (failure) { 146 | failure([NSError errorWithDomain:NSStringFromClass(self.class) code:1 userInfo:@{@"message": @"no username"}]); 147 | } 148 | } 149 | break; 150 | } 151 | case PhotosSourceSearch: { 152 | NSString *query = [self.defaults objectForKey:kSearchQueryKey]; 153 | if (query.length > 0) { 154 | [[LPUnsplashAPI sharedInstance] getRandomPhotosFromSearchQuery:query count:count success:success failure:failure]; 155 | } else { 156 | if (failure) { 157 | failure([NSError errorWithDomain:NSStringFromClass(self.class) code:1 userInfo:@{@"message": @"no query"}]); 158 | } 159 | } 160 | break; 161 | } 162 | } 163 | } 164 | 165 | - (void)getNextImageSuccess:(void (^)(IVPhoto *photo))success failure:(void (^)(NSError *))failure { 166 | PhotosSource source = self.source; 167 | NSArray *photos = [self cachedPhotosFromSource:source]; 168 | if (photos.count == 0) { 169 | [self loadImagesCount:30 success:^(NSArray *photos) { 170 | if (photos.count == 0) { 171 | if (failure) { 172 | failure([NSError errorWithDomain:NSStringFromClass(self.class) code:1 userInfo:@{@"message": @"no photos"}]); 173 | } 174 | } else { 175 | [self saveToCachePhotos:photos fromSource:source]; 176 | IVPhoto *nextPhoto = [photos firstObject]; 177 | if (success) { 178 | success(nextPhoto); 179 | } 180 | [self removeFromCachePhoto:nextPhoto fromSource:source]; 181 | } 182 | } failure:failure]; 183 | } else { 184 | IVPhoto *nextPhoto = [photos firstObject]; 185 | if (success) { 186 | success(nextPhoto); 187 | } 188 | [self removeFromCachePhoto:nextPhoto fromSource:source]; 189 | } 190 | } 191 | 192 | - (NSArray *)cachedPhotosFromSource:(PhotosSource)source { 193 | NSData *data = [self.defaults objectForKey:kPhotosCacheKey]; 194 | NSDictionary *cache = [NSKeyedUnarchiver unarchiveObjectWithData:data]; 195 | NSString *key = [@(source) stringValue]; 196 | if (source == PhotosSourceCollection) { 197 | if (![self.defaults objectForKey:kCollectionUIDKey]) { 198 | return @[]; 199 | } 200 | key = [NSString stringWithFormat:@"%@_%@", key, [[[self.defaults objectForKey:kCollectionUIDKey] stringValue] MD5String]]; 201 | } else if (source == PhotosSourceUser) { 202 | if (![self.defaults objectForKey:kUsernameKey]) { 203 | return @[]; 204 | } 205 | key = [NSString stringWithFormat:@"%@_%@", key, [[self.defaults objectForKey:kUsernameKey] MD5String]]; 206 | } else if (source == PhotosSourceSearch) { 207 | if (![self.defaults objectForKey:kSearchQueryKey]) { 208 | return @[]; 209 | } 210 | key = [NSString stringWithFormat:@"%@_%@", key, [[self.defaults objectForKey:kSearchQueryKey] MD5String]]; 211 | } 212 | return [cache objectForKey:key]; 213 | } 214 | 215 | - (void)removeFromCachePhoto:(IVPhoto *)photo fromSource:(PhotosSource)source { 216 | NSArray *cached = [self cachedPhotosFromSource:source]; 217 | cached = [cached filterWithBlock:^BOOL(id obj) { 218 | return ![obj isEqual:photo]; 219 | }]; 220 | [self saveToCachePhotos:cached fromSource:source]; 221 | } 222 | 223 | - (void)saveToCachePhotos:(NSArray *)photos fromSource:(PhotosSource)source { 224 | NSData *data = [self.defaults objectForKey:kPhotosCacheKey]; 225 | NSDictionary *cache = [NSKeyedUnarchiver unarchiveObjectWithData:data]; 226 | if (cache) { 227 | cache = [NSDictionary dictionary]; 228 | } 229 | 230 | NSString *key = [@(source) stringValue]; 231 | if (source == PhotosSourceCollection) { 232 | if (![self.defaults objectForKey:kCollectionUIDKey]) { 233 | return; 234 | } 235 | key = [NSString stringWithFormat:@"%@_%@", key, [[[self.defaults objectForKey:kCollectionUIDKey] stringValue] MD5String]]; 236 | } else if (source == PhotosSourceUser) { 237 | if (![self.defaults objectForKey:kUsernameKey]) { 238 | return; 239 | } 240 | key = [NSString stringWithFormat:@"%@_%@", key, [[self.defaults objectForKey:kUsernameKey] MD5String]]; 241 | } else if (source == PhotosSourceSearch) { 242 | if (![self.defaults objectForKey:kSearchQueryKey]) { 243 | return; 244 | } 245 | key = [NSString stringWithFormat:@"%@_%@", key, [[self.defaults objectForKey:kSearchQueryKey] MD5String]]; 246 | } 247 | 248 | NSMutableDictionary *mutableCache = [cache mutableCopy]; 249 | if (photos) { 250 | [mutableCache setObject:photos forKey:key]; 251 | } else { 252 | [mutableCache removeObjectForKey:key]; 253 | } 254 | if (source == PhotosSourceCollection || 255 | source == PhotosSourceUser || 256 | source == PhotosSourceSearch) { 257 | for (NSString *k in [mutableCache allKeys]) { 258 | if ([k hasPrefix:[@(source) stringValue]] && ![k isEqualToString:key]) { 259 | [mutableCache removeObjectForKey:k]; 260 | } 261 | } 262 | } 263 | 264 | cache = [NSDictionary dictionaryWithDictionary:mutableCache]; 265 | data = [NSKeyedArchiver archivedDataWithRootObject:cache]; 266 | [self.defaults setObject:data forKey:kPhotosCacheKey]; 267 | [self.defaults synchronize]; 268 | } 269 | 270 | #pragma mark Animations 271 | 272 | - (void)changeImage { 273 | if ([[TTOfflineChecker defaultChecker] isOffline]) { 274 | dispatch_async(dispatch_get_main_queue(), ^{ 275 | changeImageTimer = [NSTimer scheduledTimerWithTimeInterval:self.updateInterval target:self selector:@selector(changeImage) userInfo:nil repeats:YES]; 276 | }); 277 | return; 278 | } 279 | 280 | [changeImageTimer invalidate]; 281 | changeImageTimer = nil; 282 | [self getNextImageSuccess:^(IVPhoto *photo) { 283 | dispatch_async(dispatch_get_main_queue(), ^{ 284 | [self replaceCurrentPhotoViewWith:[[LPUnsplashPhotoView alloc] initWithPhoto:photo] animated:YES]; 285 | }); 286 | } failure:^(NSError *error) { 287 | NSLog(@"%@", error); 288 | dispatch_async(dispatch_get_main_queue(), ^{ 289 | changeImageTimer = [NSTimer scheduledTimerWithTimeInterval:self.updateInterval target:self selector:@selector(changeImage) userInfo:nil repeats:YES]; 290 | }); 291 | }]; 292 | } 293 | 294 | - (NSArray *)transformsForDirection:(SwitchDirection)direction { 295 | switch(direction) { 296 | case SwitchDirectionTop: 297 | return @[[NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, [self bounds].size.height, 0)], 298 | [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, -[self bounds].size.height, 0)]]; 299 | case SwitchDirectionBottom: 300 | return @[[NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, -[self bounds].size.height, 0)], 301 | [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, [self bounds].size.height, 0)]]; 302 | case SwitchDirectionLeft: 303 | return @[[NSValue valueWithCATransform3D:CATransform3DMakeTranslation(-[self bounds].size.width, 0, 0)], 304 | [NSValue valueWithCATransform3D:CATransform3DMakeTranslation([self bounds].size.width, 0, 0)]]; 305 | case SwitchDirectionRight: 306 | return @[[NSValue valueWithCATransform3D:CATransform3DMakeTranslation([self bounds].size.width, 0, 0)], 307 | [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(-[self bounds].size.width, 0, 0)]]; 308 | } 309 | } 310 | 311 | - (void)replaceCurrentPhotoViewWith:(LPUnsplashPhotoView *)photoView animated:(BOOL)animated { 312 | [photoView setupCompletion:^(BOOL success) { 313 | if (success) { 314 | 315 | [photoView setFrame:[self bounds]]; 316 | [photoView setNeedsLayout:YES]; 317 | [photoView layoutSubtreeIfNeeded]; 318 | if (animated) { 319 | SwitchDirection direction; 320 | do { 321 | direction = arc4random()%4; 322 | } while(direction == previousDirection || 323 | (direction == SwitchDirectionLeft && previousDirection == SwitchDirectionRight) || 324 | (direction == SwitchDirectionRight && previousDirection == SwitchDirectionLeft) || 325 | (direction == SwitchDirectionTop && previousDirection == SwitchDirectionBottom) || 326 | (direction == SwitchDirectionBottom && previousDirection == SwitchDirectionTop)); 327 | previousDirection = direction; 328 | 329 | NSArray *transforms = [self transformsForDirection:direction]; 330 | 331 | [photoView.layer setTransform:[[transforms firstObject] CATransform3DValue]]; 332 | [self addSubview:photoView positioned:NSWindowAbove relativeTo:nil]; 333 | 334 | CABasicAnimation *newPhotoAnimation = [CABasicAnimation animationWithKeyPath:@"transform"]; 335 | newPhotoAnimation.fromValue = [transforms firstObject]; 336 | newPhotoAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; 337 | newPhotoAnimation.duration = 1.0f; 338 | newPhotoAnimation.fillMode = kCAFillModeForwards; 339 | newPhotoAnimation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.6 :0 :0.4 :1.0]; 340 | newPhotoAnimation.removedOnCompletion = NO; 341 | [photoView.layer addAnimation:newPhotoAnimation forKey:@"animation"]; 342 | 343 | CABasicAnimation *oldPhotoAnimation = [CABasicAnimation animationWithKeyPath:@"transform"]; 344 | oldPhotoAnimation.toValue = [transforms lastObject]; 345 | oldPhotoAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; 346 | oldPhotoAnimation.duration = 1.0f; 347 | oldPhotoAnimation.fillMode = kCAFillModeForwards; 348 | oldPhotoAnimation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.6 :0 :0.4 :1.0]; 349 | oldPhotoAnimation.removedOnCompletion = NO; 350 | [self.photoView.layer addAnimation:oldPhotoAnimation forKey:@"animation"]; 351 | 352 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(newPhotoAnimation.duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 353 | [self.photoView removeFromSuperview]; 354 | self.photoView = photoView; 355 | changeImageTimer = [NSTimer scheduledTimerWithTimeInterval:self.updateInterval target:self selector:@selector(changeImage) userInfo:nil repeats:YES]; 356 | }); 357 | } else { 358 | [self addSubview:photoView positioned:NSWindowAbove relativeTo:nil]; 359 | [self.photoView removeFromSuperview]; 360 | self.photoView = photoView; 361 | changeImageTimer = [NSTimer scheduledTimerWithTimeInterval:self.updateInterval target:self selector:@selector(changeImage) userInfo:nil repeats:YES]; 362 | } 363 | } else { 364 | changeImageTimer = [NSTimer scheduledTimerWithTimeInterval:self.updateInterval target:self selector:@selector(changeImage) userInfo:nil repeats:YES]; 365 | } 366 | }]; 367 | } 368 | 369 | - (void)layoutSubtreeIfNeeded { 370 | [super layoutSubtreeIfNeeded]; 371 | [self.containerView setFrame:[self bounds]]; 372 | [self.photoView setFrame:[self.containerView bounds]]; 373 | [self.photoView setNeedsLayout:YES]; 374 | [self.photoView layoutSubtreeIfNeeded]; 375 | } 376 | 377 | - (void)viewWillStartLiveResize { 378 | [super viewWillStartLiveResize]; 379 | [self setNeedsLayout:YES]; 380 | [self layoutSubtreeIfNeeded]; 381 | } 382 | 383 | - (void)viewDidEndLiveResize { 384 | [super viewDidEndLiveResize]; 385 | [self setNeedsLayout:YES]; 386 | [self layoutSubtreeIfNeeded]; 387 | } 388 | 389 | - (void)startAnimation 390 | { 391 | [super startAnimation]; 392 | } 393 | 394 | - (void)stopAnimation 395 | { 396 | [super stopAnimation]; 397 | } 398 | 399 | - (void)drawRect:(NSRect)rect 400 | { 401 | [super drawRect:rect]; 402 | } 403 | 404 | - (void)animateOneFrame 405 | { 406 | [self setNeedsLayout:YES]; 407 | [self.containerView setNeedsDisplay:YES]; 408 | [self.containerView displayIfNeeded]; 409 | [self.photoView setNeedsDisplay:YES]; 410 | [self.photoView displayIfNeeded]; 411 | } 412 | 413 | #pragma mark - Configure Sheet 414 | 415 | - (BOOL)hasConfigureSheet 416 | { 417 | return YES; 418 | } 419 | 420 | - (NSWindow *)configureSheet { 421 | if (!self.configSheet) { 422 | if (![NSBundle loadNibNamed:@"ConfigureSheet" owner:self]) { 423 | NSLog( @"Failed to load configure sheet." ); 424 | NSBeep(); 425 | } 426 | } 427 | 428 | [self.featuredRadioButton setState:(self.source == PhotosSourceFeatured)? NSOnState: NSOffState]; 429 | [self.collectionRadioButton setState:(self.source == PhotosSourceCollection)? NSOnState: NSOffState]; 430 | [self.userRadioButton setState:(self.source == PhotosSourceUser)? NSOnState: NSOffState]; 431 | [self.searchRadioButton setState:(self.source == PhotosSourceSearch)? NSOnState: NSOffState]; 432 | self.collectionURLTextField.stringValue = [self.defaults stringForKey:kCollectionURLKey]? : @""; 433 | self.usernameTextField.stringValue = [self.defaults stringForKey:kUsernameKey]? : @""; 434 | self.searchQueryTextField.stringValue = [self.defaults stringForKey:kSearchQueryKey]? : @""; 435 | self.updateIntervalSlider.doubleValue = self.updateInterval/60.0; 436 | [self.appIdTextField setStringValue:[LPUnsplashAPI sharedInstance].unsplashAppId? : @""]; 437 | [self updateIntervalChanged:nil]; 438 | 439 | return self.configSheet; 440 | } 441 | 442 | - (IBAction)selectedSource:(id)sender { 443 | if (sender == self.featuredRadioButton) { 444 | [self selectedFeatured:sender]; 445 | } else if (sender == self.collectionRadioButton) { 446 | [self selectedCollection:sender]; 447 | } else if (sender == self.userRadioButton) { 448 | [self selectedUser:sender]; 449 | } else if (sender == self.searchRadioButton) { 450 | [self selectedSearch:sender]; 451 | } 452 | } 453 | 454 | - (IBAction)selectedFeatured:(id)sender { 455 | self.source = PhotosSourceFeatured; 456 | } 457 | 458 | - (NSNumber *)collectionUIDFromTextField { 459 | NSString *str = self.collectionURLTextField.stringValue; 460 | NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil]; 461 | NSArray *urlMatches = [linkDetector matchesInString:str options:0 range:NSMakeRange(0, [str length])]; 462 | NSArray *urls = [urlMatches mapWithBlock:^id(NSTextCheckingResult *obj) { 463 | return [obj URL]; 464 | }]; 465 | if (urls.count == 0) { 466 | return nil; 467 | } 468 | NSURL *url = [urls firstObject]; 469 | if ([[url host] isEqualToString:@"unsplash.com"]) { 470 | NSArray *pathComponents = url.pathComponents; 471 | if (pathComponents.count >= 2) { 472 | if ([[pathComponents objectAtIndex:1] isEqualToString:@"collections"]) { 473 | if (pathComponents.count >= 3) { 474 | if ([[pathComponents objectAtIndex:2] isEqualToString:@"curated"]) { 475 | if (pathComponents.count >= 4) { 476 | NSString *collectionUID = [pathComponents objectAtIndex:3]; 477 | NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; 478 | numberFormatter.numberStyle = NSNumberFormatterDecimalStyle; 479 | NSNumber *uid = [numberFormatter numberFromString:collectionUID]; 480 | return uid; 481 | } 482 | } else { 483 | NSString *collectionUID = [pathComponents objectAtIndex:2]; 484 | NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; 485 | numberFormatter.numberStyle = NSNumberFormatterDecimalStyle; 486 | NSNumber *uid = [numberFormatter numberFromString:collectionUID]; 487 | return uid; 488 | } 489 | } 490 | } 491 | } 492 | } 493 | return nil; 494 | } 495 | 496 | - (NSString *)collectionURLFromTextField { 497 | NSString *str = self.collectionURLTextField.stringValue; 498 | NSDataDetector *linkDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:nil]; 499 | NSArray *urlMatches = [linkDetector matchesInString:str options:0 range:NSMakeRange(0, [str length])]; 500 | NSArray *urls = [urlMatches mapWithBlock:^id(NSTextCheckingResult *obj) { 501 | return [obj URL]; 502 | }]; 503 | if (urls.count == 0) { 504 | return nil; 505 | } 506 | NSURL *url = [urls firstObject]; 507 | return [url absoluteString]; 508 | } 509 | 510 | - (IBAction)selectedCollection:(id)sender { 511 | void (^abortBlock)() = ^{ 512 | self.source = PhotosSourceFeatured; 513 | }; 514 | 515 | if (self.collectionURLTextField.stringValue.length > 0) { 516 | NSNumber *uid = [self collectionUIDFromTextField]; 517 | NSString *url = [self collectionURLFromTextField]; 518 | if (!uid || url.length == 0) { 519 | abortBlock(); 520 | return; 521 | } 522 | [self.defaults setObject:uid forKey:kCollectionUIDKey]; 523 | [self.defaults setObject:url forKey:kCollectionURLKey]; 524 | [self.defaults synchronize]; 525 | self.source = PhotosSourceCollection; 526 | } else { 527 | abortBlock(); 528 | } 529 | } 530 | 531 | - (IBAction)selectedUser:(id)sender { 532 | if (self.usernameTextField.stringValue.length > 0) { 533 | [self.defaults setObject:self.usernameTextField.stringValue forKey:kUsernameKey]; 534 | [self.defaults synchronize]; 535 | self.source = PhotosSourceUser; 536 | } else { 537 | self.source = PhotosSourceFeatured; 538 | } 539 | } 540 | 541 | - (IBAction)selectedSearch:(id)sender { 542 | if (self.searchQueryTextField.stringValue.length > 0) { 543 | [self.defaults setObject:self.searchQueryTextField.stringValue forKey:kSearchQueryKey]; 544 | [self.defaults synchronize]; 545 | self.source = PhotosSourceSearch; 546 | } else { 547 | self.source = PhotosSourceFeatured; 548 | } 549 | } 550 | 551 | - (IBAction)updateIntervalChanged:(id)sender { 552 | self.updateInterval = self.updateIntervalSlider.doubleValue; 553 | NSInteger minutes = ((NSInteger)round(self.updateInterval))/60; 554 | NSInteger seconds = ((NSInteger)self.updateInterval)%60; 555 | NSString *timeStr = [NSString stringWithFormat:@"%02ld:%02ld", (long)minutes, (long)seconds]; 556 | self.updateIntervalLabel.stringValue = [NSString stringWithFormat:@"Photos will update every %@", timeStr]; 557 | } 558 | 559 | - (IBAction)close:(id)sender { 560 | [self.configSheet.sheetParent endSheet:self.configSheet]; 561 | NSNumber *uid = [self collectionUIDFromTextField]; 562 | NSString *url = [self collectionURLFromTextField]; 563 | if (uid && url.length > 0) { 564 | [self.defaults setObject:uid forKey:kCollectionUIDKey]; 565 | [self.defaults setObject:url forKey:kCollectionURLKey]; 566 | } else if (self.source == PhotosSourceCollection) { 567 | [self.defaults removeObjectForKey:kCollectionUIDKey]; 568 | [self.defaults removeObjectForKey:kCollectionURLKey]; 569 | if (self.source == PhotosSourceCollection) { 570 | self.source = PhotosSourceFeatured; 571 | } 572 | } 573 | if (self.usernameTextField.stringValue.length > 0) { 574 | [self.defaults setObject:self.usernameTextField.stringValue forKey:kUsernameKey]; 575 | } else { 576 | [self.defaults removeObjectForKey:kUsernameKey]; 577 | if (self.source == PhotosSourceUser) { 578 | self.source = PhotosSourceFeatured; 579 | } 580 | } 581 | if (self.searchQueryTextField.stringValue.length > 0) { 582 | [self.defaults setObject:self.searchQueryTextField.stringValue forKey:kSearchQueryKey]; 583 | } else { 584 | [self.defaults removeObjectForKey:kSearchQueryKey]; 585 | if (self.source == PhotosSourceSearch) { 586 | self.source = PhotosSourceFeatured; 587 | } 588 | } 589 | [self.defaults synchronize]; 590 | self.updateInterval = self.updateIntervalSlider.doubleValue; 591 | 592 | NSString *appId = self.appIdTextField.stringValue; 593 | [self.defaults setObject:appId forKey:kAppIdKey]; 594 | [[LPUnsplashAPI sharedInstance] setUnsplashAppId:appId]; 595 | } 596 | 597 | @end 598 | -------------------------------------------------------------------------------- /Irvue Screensaver/LPFileDownloader.h: -------------------------------------------------------------------------------- 1 | // 2 | // LPFileDownloader.h 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 26/10/15. 6 | // Copyright © 2015 Leonspok. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface LPFileDownloader : NSObject 12 | 13 | + (instancetype)sharedDownloader; 14 | 15 | - (void)downloadFileFromURL:(NSURL *)url 16 | destinationPath:(NSString *)destinationPath 17 | progressBlock:(void (^)(double totalBytesDownloaded, double totalBytesExpectedToDownload))progressBlock 18 | success:(void (^)())success 19 | failure:(void (^)(NSError *error))failure; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /Irvue Screensaver/LPFileDownloader.m: -------------------------------------------------------------------------------- 1 | // 2 | // LPFileDownloader.m 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 26/10/15. 6 | // Copyright © 2015 Leonspok. All rights reserved. 7 | // 8 | 9 | #import "LPFileDownloader.h" 10 | 11 | @interface LPFileDownloader() 12 | @property(nonatomic, strong) NSMutableDictionary *successBlocks; 13 | @property(nonatomic, strong) NSMutableDictionary *failureBlocks; 14 | @property(nonatomic, strong) NSMutableDictionary *progressBlocks; 15 | @property(nonatomic, strong) NSMutableDictionary *destinationPaths; 16 | @end 17 | 18 | @implementation LPFileDownloader { 19 | NSOperationQueue *sessionQueue; 20 | NSURLSession *session; 21 | } 22 | 23 | + (instancetype)sharedDownloader { 24 | static LPFileDownloader *__sharedDownloader = nil; 25 | static dispatch_once_t onceToken; 26 | dispatch_once(&onceToken, ^{ 27 | __sharedDownloader = [[LPFileDownloader alloc] init]; 28 | }); 29 | return __sharedDownloader; 30 | } 31 | 32 | - (id)init { 33 | self = [super init]; 34 | if (self) { 35 | sessionQueue = [[NSOperationQueue alloc] init]; 36 | if ([sessionQueue respondsToSelector:@selector(qualityOfService)]) { 37 | sessionQueue.qualityOfService = NSQualityOfServiceUserInitiated; 38 | } 39 | sessionQueue.name = NSStringFromClass(self.class); 40 | 41 | self.successBlocks = [NSMutableDictionary dictionary]; 42 | self.failureBlocks = [NSMutableDictionary dictionary]; 43 | self.progressBlocks = [NSMutableDictionary dictionary]; 44 | self.destinationPaths = [NSMutableDictionary dictionary]; 45 | session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:sessionQueue]; 46 | } 47 | return self; 48 | } 49 | 50 | - (void)downloadFileFromURL:(NSURL *)url 51 | destinationPath:(NSString *)destinationPath 52 | progressBlock:(void (^)(double totalBytesDownloaded, double totalBytesExpectedToDownload))progress 53 | success:(void (^)())success 54 | failure:(void (^)(NSError *error))failure { 55 | if (!url) { 56 | if (failure) { 57 | failure([NSError errorWithDomain:NSStringFromClass(self.class) code:1 userInfo:@{@"message": @"no url"}]); 58 | } 59 | return; 60 | } 61 | 62 | if (!destinationPath || destinationPath.length == 0) { 63 | if (failure) { 64 | failure([NSError errorWithDomain:NSStringFromClass(self.class) code:1 userInfo:@{@"message": @"no destinationPath"}]); 65 | } 66 | return; 67 | } 68 | 69 | void (^successBlock)() = ^{ 70 | if (success) { 71 | success(); 72 | } 73 | }; 74 | 75 | void (^failureBlock)(NSError *error) = ^(NSError *error) { 76 | if (failure) { 77 | failure(error); 78 | } 79 | }; 80 | 81 | void (^progressBlock)(double, double) = ^(double totalBytesDownloaded, double totalBytesExpectedToDownload) { 82 | if (progress) { 83 | progress(totalBytesDownloaded, totalBytesExpectedToDownload); 84 | } 85 | }; 86 | 87 | BOOL shouldDownload = NO; 88 | 89 | @synchronized(self) { 90 | if ([self.successBlocks objectForKey:url] && 91 | [self.failureBlocks objectForKey:url] && 92 | [self.progressBlocks objectForKey:url] && 93 | [self.destinationPaths objectForKey:url]) { 94 | NSMutableArray *successBlocksForURL = [self.successBlocks objectForKey:url]; 95 | [successBlocksForURL addObject:successBlock]; 96 | 97 | NSMutableArray *failureBlocksForURL = [self.failureBlocks objectForKey:url]; 98 | [failureBlocksForURL addObject:failureBlock]; 99 | 100 | NSMutableArray *progressBlocksForURL = [self.progressBlocks objectForKey:url]; 101 | [progressBlocksForURL addObject:progressBlock]; 102 | 103 | NSMutableArray *destinationPathsForURL = [self.destinationPaths objectForKey:url]; 104 | [destinationPathsForURL addObject:destinationPath]; 105 | } else { 106 | NSMutableArray *successBlocksForURL = [NSMutableArray array]; 107 | [successBlocksForURL addObject:successBlock]; 108 | [self.successBlocks setObject:successBlocksForURL forKey:url]; 109 | 110 | NSMutableArray *failureBlocksForURL = [NSMutableArray array]; 111 | [failureBlocksForURL addObject:failureBlock]; 112 | [self.failureBlocks setObject:failureBlocksForURL forKey:url]; 113 | 114 | NSMutableArray *progressBlocksForURL = [NSMutableArray array]; 115 | [progressBlocksForURL addObject:progressBlock]; 116 | [self.progressBlocks setObject:progressBlocksForURL forKey:url]; 117 | 118 | NSMutableArray *destinationPathsForURL = [NSMutableArray array]; 119 | [destinationPathsForURL addObject:destinationPath]; 120 | [self.destinationPaths setObject:destinationPathsForURL forKey:url]; 121 | 122 | shouldDownload = YES; 123 | } 124 | } 125 | 126 | if (shouldDownload) { 127 | [[session downloadTaskWithURL:url] resume]; 128 | } 129 | } 130 | 131 | #pragma mark NSURLSessionDownloadDelegate 132 | 133 | - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { 134 | NSError *error = downloadTask.error; 135 | NSURL *url = downloadTask.originalRequest.URL; 136 | 137 | NSArray *destinationPathsForURL; 138 | NSArray *failureBlocks; 139 | NSArray *successBlocks; 140 | @synchronized(self) { 141 | destinationPathsForURL = [NSArray arrayWithArray:[self.destinationPaths objectForKey:url]]; 142 | failureBlocks = [NSArray arrayWithArray:[self.failureBlocks objectForKey:url]]; 143 | successBlocks = [NSArray arrayWithArray:[self.successBlocks objectForKey:url]]; 144 | 145 | [self.successBlocks removeObjectForKey:url]; 146 | [self.failureBlocks removeObjectForKey:url]; 147 | [self.progressBlocks removeObjectForKey:url]; 148 | [self.destinationPaths removeObjectForKey:url]; 149 | } 150 | 151 | if (error) { 152 | for (void (^block)(NSError *error) in failureBlocks) { 153 | block(error); 154 | } 155 | } else { 156 | NSError *error; 157 | for (NSString *path in destinationPathsForURL) { 158 | if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { 159 | [[NSFileManager defaultManager] removeItemAtPath:path error:nil]; 160 | } 161 | [[NSFileManager defaultManager] copyItemAtURL:location toURL:[NSURL fileURLWithPath:path] error:&error]; 162 | } 163 | [[NSFileManager defaultManager] removeItemAtURL:location error:nil]; 164 | if (error) { 165 | for (void (^block)(NSError *error) in failureBlocks) { 166 | block(error); 167 | } 168 | } else { 169 | for (void (^block)() in successBlocks) { 170 | block(); 171 | } 172 | } 173 | } 174 | } 175 | 176 | - (void)URLSession:(NSURLSession *)session 177 | downloadTask:(NSURLSessionDownloadTask *)downloadTask 178 | didWriteData:(int64_t)bytesWritten 179 | totalBytesWritten:(int64_t)totalBytesWritten 180 | totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { 181 | NSArray *blocks; 182 | @synchronized(self) { 183 | blocks = [NSArray arrayWithArray:[self.progressBlocks objectForKey:downloadTask.originalRequest.URL]]; 184 | } 185 | 186 | for (void (^block)(double totalBytesDownloaded, double totalBytesExpectedToDownload) in blocks) { 187 | block((double)totalBytesWritten, (double)totalBytesExpectedToWrite); 188 | } 189 | } 190 | 191 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { 192 | NSURL *url = task.originalRequest.URL; 193 | 194 | NSArray *failureBlocks; 195 | @synchronized(self) { 196 | failureBlocks = [NSArray arrayWithArray:[self.failureBlocks objectForKey:url]]; 197 | [self.successBlocks removeObjectForKey:url]; 198 | [self.failureBlocks removeObjectForKey:url]; 199 | [self.progressBlocks removeObjectForKey:url]; 200 | [self.destinationPaths removeObjectForKey:url]; 201 | } 202 | 203 | if (error) { 204 | for (void (^block)(NSError *error) in failureBlocks) { 205 | block(error); 206 | } 207 | } 208 | } 209 | 210 | @end 211 | -------------------------------------------------------------------------------- /Irvue Screensaver/LPImageDownloadManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // LPImageDownloadManager.h 3 | // Leonspok 4 | // 5 | // Created by Игорь Савельев on 28/01/14. 6 | // Copyright (c) 2014 10tracks. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @import AppKit; 12 | 13 | typedef enum { 14 | TTImageSizeOriginal, 15 | TTImageSize800px, 16 | TTImageSize500px, 17 | TTImageSize300px, 18 | TTImageSize100px, 19 | TTImageSize50px 20 | } TTImageSize; 21 | 22 | @interface LPImageDownloadManager : NSObject 23 | 24 | @property (nonatomic, strong) NSString *pathToCacheFolder; 25 | 26 | + (instancetype)defaultManager; 27 | 28 | - (void)clearCache; 29 | - (void)clearOldCache; 30 | 31 | - (NSURL *)urlToDownloadedImageFromURL:(NSString *)url 32 | size:(TTImageSize)size 33 | rounded:(BOOL)rounded; 34 | 35 | - (NSImage *)getImageForURL:(NSString *)url 36 | size:(TTImageSize)size 37 | rounded:(BOOL)rounded; 38 | - (void)getImageForURL:(NSString *)url 39 | size:(TTImageSize)size 40 | rounded:(BOOL)rounded 41 | completion:(void (^)(NSImage *image))completion; 42 | - (BOOL)hasImageForURL:(NSString *)url 43 | size:(TTImageSize)size 44 | rounded:(BOOL)rounded; 45 | 46 | - (NSImage *)getImageForURL:(NSString *)url 47 | size:(TTImageSize)size; 48 | - (void)getImageForURL:(NSString *)url 49 | size:(TTImageSize)size 50 | completion:(void (^)(NSImage *image))completion; 51 | - (BOOL)hasImageForURL:(NSString *)url 52 | size:(TTImageSize)size; 53 | 54 | - (NSImage *)getImageForURL:(NSString *)url; 55 | - (void)getImageForURL:(NSString *)url 56 | completion:(void (^)(NSImage *image))completion; 57 | - (BOOL)hasImageForURL:(NSString *)url; 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /Irvue Screensaver/LPImageDownloadManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // LPImageDownloadManager.m 3 | // Leonspok 4 | // 5 | // Created by Игорь Савельев on 28/01/14. 6 | // Copyright (c) 2014 10tracks. All rights reserved. 7 | // 8 | 9 | #import "LPImageDownloadManager.h" 10 | #import "NSString+MD5.h" 11 | #import "LPFileDownloader.h" 12 | #import "NSBezierPath+CGPath.h" 13 | #import "NSImage+Effects.h" 14 | #import "NSArray+Utilities.h" 15 | 16 | @implementation LPImageDownloadManager { 17 | NSCache *imageCache; 18 | NSOperationQueue *renderOperationQueue; 19 | } 20 | 21 | + (instancetype)defaultManager { 22 | static LPImageDownloadManager *manager = nil; 23 | static dispatch_once_t oncePredicate; 24 | dispatch_once(&oncePredicate, ^{ 25 | manager = [[LPImageDownloadManager alloc] init]; 26 | }); 27 | return manager; 28 | } 29 | 30 | - (id)init { 31 | self = [super init]; 32 | if(self) { 33 | renderOperationQueue = [[NSOperationQueue alloc] init]; 34 | if ([renderOperationQueue respondsToSelector:@selector(qualityOfService)]) { 35 | renderOperationQueue.qualityOfService = NSQualityOfServiceUserInitiated; 36 | } 37 | renderOperationQueue.name = @"render images operation queue"; 38 | 39 | imageCache = [[NSCache alloc] init]; 40 | [imageCache setName:@"images"]; 41 | [imageCache setTotalCostLimit:400]; 42 | [imageCache setCountLimit:50]; 43 | 44 | [self createFolderIfNeeded]; 45 | } 46 | return self; 47 | } 48 | 49 | - (void)clearCache { 50 | [imageCache removeAllObjects]; 51 | [[NSFileManager defaultManager] removeItemAtPath:self.pathToCacheFolder error:nil]; 52 | [self createFolderIfNeeded]; 53 | } 54 | 55 | - (void)createFolderIfNeeded { 56 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); 57 | self.pathToCacheFolder = [[paths objectAtIndex:0] stringByAppendingPathComponent:NSStringFromClass(self.class)]; 58 | BOOL isDirectory; 59 | NSError *error; 60 | if (![[NSFileManager defaultManager] fileExistsAtPath:self.pathToCacheFolder isDirectory:&isDirectory]) { 61 | error = nil; 62 | [[NSFileManager defaultManager] createDirectoryAtPath:self.pathToCacheFolder 63 | withIntermediateDirectories:NO 64 | attributes:nil 65 | error:&error]; 66 | if (error) { 67 | NSLog(@"%@", [error localizedDescription]); 68 | abort(); 69 | } 70 | } else if (!isDirectory) { 71 | [[NSFileManager defaultManager] removeItemAtPath:self.pathToCacheFolder error:&error]; 72 | if (error) { 73 | NSLog(@"%@", [error localizedDescription]); 74 | } 75 | 76 | error = nil; 77 | [[NSFileManager defaultManager] createDirectoryAtPath:self.pathToCacheFolder 78 | withIntermediateDirectories:NO 79 | attributes:nil 80 | error:&error]; 81 | if (error) { 82 | NSLog(@"%@", [error localizedDescription]); 83 | abort(); 84 | } 85 | } 86 | } 87 | 88 | - (void)clearOldCache { 89 | NSError *error = nil; 90 | NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:[NSURL fileURLWithPath:self.pathToCacheFolder] includingPropertiesForKeys:@[NSURLContentAccessDateKey,NSURLFileSizeKey] options:0 error:&error]; 91 | contents = [contents sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { 92 | NSDate *date1, *date2; 93 | [obj1 getResourceValue:&date1 forKey:NSURLContentAccessDateKey error:nil]; 94 | [obj2 getResourceValue:&date2 forKey:NSURLContentAccessDateKey error:nil]; 95 | if (!date1) { 96 | [obj1 getResourceValue:&date1 forKey:NSURLContentModificationDateKey error:nil]; 97 | } 98 | if (!date2) { 99 | [obj2 getResourceValue:&date2 forKey:NSURLContentModificationDateKey error:nil]; 100 | } 101 | return [date1 compare:date2]; 102 | }]; 103 | contents = [contents filterWithBlock:^BOOL(id obj) { 104 | NSDate *date; 105 | [obj getResourceValue:&date forKey:NSURLContentAccessDateKey error:nil]; 106 | return [date compare:[[NSDate date] dateByAddingTimeInterval:-2*60*60]] == NSOrderedAscending; 107 | }]; 108 | 109 | for (NSURL *url in contents) { 110 | [[NSFileManager defaultManager] removeItemAtURL:url error:nil]; 111 | } 112 | } 113 | 114 | - (NSString *)nameForURL:(NSString *)url size:(TTImageSize)size rounded:(BOOL)rounded { 115 | NSString *postFix = @""; 116 | switch (size) { 117 | case TTImageSizeOriginal: 118 | postFix = @""; 119 | break; 120 | case TTImageSize50px: 121 | postFix = @"50px"; 122 | break; 123 | case TTImageSize100px: 124 | postFix = @"100px"; 125 | break; 126 | case TTImageSize300px: 127 | postFix = @"300px"; 128 | break; 129 | case TTImageSize500px: 130 | postFix = @"500px"; 131 | break; 132 | case TTImageSize800px: 133 | postFix = @"800px"; 134 | break; 135 | 136 | default: 137 | break; 138 | } 139 | 140 | NSString *name = [NSString stringWithFormat:@"%@%@%@.png", [url MD5String], postFix, rounded? @"rounded":@""]; 141 | return name; 142 | } 143 | 144 | - (NSURL *)urlToDownloadedImageFromURL:(NSString *)url 145 | size:(TTImageSize)size 146 | rounded:(BOOL)rounded { 147 | NSString *fileName = [self nameForURL:url size:size rounded:rounded]; 148 | NSString *imagePath = [self.pathToCacheFolder stringByAppendingPathComponent:fileName]; 149 | 150 | if (![[NSFileManager defaultManager] fileExistsAtPath:imagePath]) { 151 | return nil; 152 | } 153 | 154 | return [[NSURL alloc] initFileURLWithPath:imagePath]; 155 | } 156 | 157 | - (NSImage *)renderImage:(NSImage *)image toSize:(NSSize)size rounded:(BOOL)rounded { 158 | return [image resizeWithAspectFillToSize:size rounded:rounded]; 159 | } 160 | 161 | - (NSImage *)getImageForURL:(NSString *)url 162 | size:(TTImageSize)size 163 | rounded:(BOOL)rounded { 164 | NSString *fileName = [self nameForURL:url size:size rounded:rounded]; 165 | NSString *imagePath = [self.pathToCacheFolder stringByAppendingPathComponent:fileName]; 166 | 167 | NSImage *image; 168 | if ([imageCache objectForKey:fileName]) { 169 | image = [imageCache objectForKey:fileName]; 170 | } else if ([[NSFileManager defaultManager] fileExistsAtPath:imagePath]) { 171 | image = [[NSImage alloc] initWithContentsOfFile:imagePath]; 172 | if (image) { 173 | [imageCache setObject:image forKey:fileName]; 174 | } 175 | } 176 | if (!image) { 177 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ 178 | if ([imageCache objectForKey:fileName]) { 179 | [imageCache removeObjectForKey:fileName]; 180 | } else if ([[NSFileManager defaultManager] fileExistsAtPath:imagePath]) { 181 | [[NSFileManager defaultManager] removeItemAtPath:imagePath error:nil]; 182 | } 183 | }); 184 | return nil; 185 | } 186 | return image; 187 | } 188 | 189 | - (NSImage *)processImage:(NSImage *)source size:(TTImageSize)size rounded:(BOOL)rounded cost:(NSUInteger *)c { 190 | NSImage *renderedImage; 191 | NSUInteger cost = 0; 192 | switch (size) { 193 | case TTImageSize50px: 194 | renderedImage = [self renderImage:source toSize:NSMakeSize(50, 50) rounded:rounded]; 195 | cost = 1; 196 | break; 197 | case TTImageSize100px: 198 | renderedImage = [self renderImage:source toSize:NSMakeSize(100, 100) rounded:rounded]; 199 | cost = 1; 200 | break; 201 | case TTImageSize300px: 202 | renderedImage = [self renderImage:source toSize:NSMakeSize(300, 300) rounded:rounded]; 203 | cost = 9; 204 | break; 205 | case TTImageSize500px: 206 | renderedImage = [self renderImage:source toSize:NSMakeSize(500, 500) rounded:rounded]; 207 | cost = 25; 208 | break; 209 | case TTImageSize800px: 210 | renderedImage = [self renderImage:source toSize:NSMakeSize(800, 800) rounded:rounded]; 211 | cost = 64; 212 | break; 213 | case TTImageSizeOriginal: 214 | if (rounded) { 215 | renderedImage = [self renderImage:source toSize:source.size rounded:YES]; 216 | cost = 100; 217 | } 218 | break; 219 | 220 | default: 221 | break; 222 | } 223 | *c = cost; 224 | return renderedImage; 225 | } 226 | 227 | - (void)getImageForURL:(NSString *)url 228 | size:(TTImageSize)size 229 | rounded:(BOOL)rounded 230 | completion:(void (^)(NSImage *image))completion { 231 | if (!url || url.length == 0) { 232 | dispatch_async(dispatch_get_main_queue(), ^{ 233 | if (completion) { 234 | completion(nil); 235 | } 236 | }); 237 | return; 238 | } 239 | 240 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 241 | NSString *fileName = [self nameForURL:url size:size rounded:rounded]; 242 | NSString *imagePath = [self.pathToCacheFolder stringByAppendingPathComponent:fileName]; 243 | 244 | if ([imageCache objectForKey:fileName]) { 245 | NSImage *image = [imageCache objectForKey:fileName]; 246 | dispatch_async(dispatch_get_main_queue(), ^{ 247 | if (completion) { 248 | completion(image); 249 | } 250 | }); 251 | } else if ([[NSFileManager defaultManager] fileExistsAtPath:imagePath]) { 252 | NSImage *image = [[NSImage alloc] initWithContentsOfFile:imagePath]; 253 | dispatch_async(dispatch_get_main_queue(), ^{ 254 | if (completion) { 255 | completion(image); 256 | } 257 | }); 258 | } else { 259 | NSImage *image; 260 | NSUInteger cost = 0; 261 | if ([self hasImageForURL:url size:TTImageSizeOriginal]) { 262 | image = [self getImageForURL:url size:TTImageSizeOriginal]; 263 | cost = 100; 264 | } 265 | for (TTImageSize s = size; s <= TTImageSize800px; s++) { 266 | if ([self hasImageForURL:url size:s]) { 267 | image = [self getImageForURL:url size:s]; 268 | cost = 64; 269 | break; 270 | } 271 | } 272 | 273 | if (image && (size != TTImageSizeOriginal || rounded)) { 274 | [renderOperationQueue addOperationWithBlock:^{ 275 | NSUInteger cost; 276 | NSImage *renderedImage = [self processImage:image size:size rounded:rounded cost:&cost]; 277 | 278 | if (renderedImage) { 279 | if (fileName) { 280 | [imageCache setObject:renderedImage forKey:fileName cost:cost]; 281 | } 282 | NSData *imageData = [[renderedImage bitmapImageRepresentation] representationUsingType:NSPNGFileType properties:@{}]; 283 | [imageData writeToFile:imagePath atomically:YES]; 284 | } 285 | dispatch_async(dispatch_get_main_queue(), ^{ 286 | if (completion) { 287 | completion(renderedImage); 288 | } 289 | }); 290 | }]; 291 | return; 292 | } 293 | 294 | NSString *originalFileName = [self nameForURL:url size:TTImageSizeOriginal rounded:NO]; 295 | NSString *originalImagePath = [self.pathToCacheFolder stringByAppendingPathComponent:originalFileName]; 296 | [[LPFileDownloader sharedDownloader] downloadFileFromURL:[NSURL URLWithString:url] destinationPath:originalImagePath progressBlock:nil success:^{ 297 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 298 | NSImage *image = [[NSImage alloc] initWithContentsOfFile:originalImagePath]; 299 | if (image) { 300 | [imageCache setObject:image forKey:originalFileName cost:100]; 301 | } else { 302 | dispatch_async(dispatch_get_main_queue(), ^{ 303 | if (completion) { 304 | completion(nil); 305 | } 306 | }); 307 | return; 308 | } 309 | 310 | if (size != TTImageSizeOriginal || rounded) { 311 | [renderOperationQueue addOperationWithBlock:^{ 312 | NSUInteger cost; 313 | NSImage *renderedImage = [self processImage:image size:size rounded:rounded cost:&cost]; 314 | 315 | if (renderedImage) { 316 | if (fileName) { 317 | [imageCache setObject:renderedImage forKey:fileName cost:cost]; 318 | } 319 | NSData *imageData = [[renderedImage bitmapImageRepresentation] representationUsingType:NSPNGFileType properties:@{}]; 320 | [imageData writeToFile:imagePath atomically:YES]; 321 | } 322 | dispatch_async(dispatch_get_main_queue(), ^{ 323 | if (completion) { 324 | completion(renderedImage); 325 | } 326 | }); 327 | }]; 328 | } else { 329 | dispatch_async(dispatch_get_main_queue(), ^{ 330 | if (completion) { 331 | completion(image); 332 | } 333 | }); 334 | } 335 | }); 336 | } failure:^(NSError *error) { 337 | NSLog(@"ERROR DOWNLOADING IMAGE: %@", [error localizedDescription]); 338 | dispatch_async(dispatch_get_main_queue(), ^{ 339 | if (completion) { 340 | completion(nil); 341 | } 342 | }); 343 | }]; 344 | } 345 | }); 346 | } 347 | 348 | - (BOOL)hasImageForURL:(NSString *)url 349 | size:(TTImageSize)size 350 | rounded:(BOOL)rounded { 351 | 352 | if (!url || url == (id)[NSNull null]) { 353 | return NO; 354 | } 355 | 356 | NSString *fileName = [self nameForURL:url size:size rounded:rounded]; 357 | NSString *imagePath = [self.pathToCacheFolder stringByAppendingPathComponent:fileName]; 358 | 359 | if (![[NSFileManager defaultManager] fileExistsAtPath:imagePath]) { 360 | return NO; 361 | } 362 | 363 | return YES; 364 | } 365 | 366 | - (NSImage *)getImageForURL:(NSString *)url size:(TTImageSize)size { 367 | return [self getImageForURL:url size:size rounded:NO]; 368 | } 369 | 370 | - (void)getImageForURL:(NSString *)url size:(TTImageSize)size completion:(void (^)(NSImage *))completion { 371 | [self getImageForURL:url size:size rounded:NO completion:completion]; 372 | } 373 | 374 | - (BOOL)hasImageForURL:(NSString *)url size:(TTImageSize)size { 375 | return [self hasImageForURL:url size:size rounded:NO]; 376 | } 377 | 378 | - (NSImage *)getImageForURL:(NSString *)url { 379 | return [self getImageForURL:url size:TTImageSizeOriginal]; 380 | } 381 | 382 | - (void)getImageForURL:(NSString *)url completion:(void (^)(NSImage *image))completion { 383 | [self getImageForURL:url size:TTImageSizeOriginal completion:completion]; 384 | } 385 | 386 | - (BOOL)hasImageForURL:(NSString *)url { 387 | return [self hasImageForURL:url size:TTImageSizeOriginal]; 388 | } 389 | 390 | @end 391 | 392 | -------------------------------------------------------------------------------- /Irvue Screensaver/LPJSONConvertable.h: -------------------------------------------------------------------------------- 1 | // 2 | // LPJSONConvertable.h 3 | // Leonspok 4 | // 5 | // Created by Игорь Савельев on 01/10/15. 6 | // Copyright © 2015 10tracks. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol LPJSONConvertable 12 | 13 | - (id)initWithJSON:(NSDictionary *)json; 14 | - (void)updateWithJSON:(NSDictionary *)json; 15 | 16 | + (NSArray *)createObjectsFromJSON:(NSArray *)jsonObjects; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Irvue Screensaver/LPUnsplashAPI.h: -------------------------------------------------------------------------------- 1 | // 2 | // LPUnsplashAPI.h 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 22/10/15. 6 | // Copyright © 2015 Leonspok. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "IVPhoto.h" 11 | #import "IVUser.h" 12 | #import "IVCollection.h" 13 | 14 | @interface LPUnsplashAPI : NSObject 15 | 16 | @property (nonatomic, strong) NSString *unsplashAppId; 17 | 18 | + (instancetype)sharedInstance; 19 | 20 | #pragma mark Photos 21 | 22 | #pragma mark Photos / Random / Single 23 | 24 | - (void)getRandomPhotoFromFeaturedSuccess:(void (^)(IVPhoto *photo))success 25 | failure:(void (^)(NSError *error))failure; 26 | 27 | - (void)getRandomPhotoFromCollectionWithID:(NSNumber *)collectionUID 28 | success:(void (^)(IVPhoto *photo))success 29 | failure:(void (^)(NSError *error))failure; 30 | 31 | - (void)getRandomPhotoFromUserWithName:(NSString *)username 32 | success:(void (^)(IVPhoto *photo))success 33 | failure:(void (^)(NSError *error))failure; 34 | 35 | - (void)getRandomPhotoFromSearchQuery:(NSString *)query 36 | success:(void (^)(IVPhoto *photo))success 37 | failure:(void (^)(NSError *error))failure; 38 | 39 | #pragma mark Photos / Random / Multiple 40 | 41 | - (void)getRandomPhotosFromFeaturedCount:(NSUInteger)count 42 | success:(void (^)(NSArray *photos))success 43 | failure:(void (^)(NSError *error))failure; 44 | 45 | - (void)getRandomPhotosFromCollectionWithID:(NSNumber *)collectionUID 46 | count:(NSUInteger)count 47 | success:(void (^)(NSArray *photos))success 48 | failure:(void (^)(NSError *error))failure; 49 | 50 | - (void)getRandomPhotosFromUserWithName:(NSString *)username 51 | count:(NSUInteger)count 52 | success:(void (^)(NSArray *photos))success 53 | failure:(void (^)(NSError *error))failure; 54 | 55 | - (void)getRandomPhotosFromSearchQuery:(NSString *)query 56 | count:(NSUInteger)count 57 | success:(void (^)(NSArray *photos))success 58 | failure:(void (^)(NSError *error))failure; 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /Irvue Screensaver/LPUnsplashAPI.m: -------------------------------------------------------------------------------- 1 | // 2 | // LPUnsplashAPI.m 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 22/10/15. 6 | // Copyright © 2015 Leonspok. All rights reserved. 7 | // 8 | 9 | #import "LPUnsplashAPI.h" 10 | #import "NSArray+Utilities.h" 11 | #import "IVCollection.h" 12 | #import "IVPhoto.h" 13 | #import "IVUser.h" 14 | #import "NSDictionary+NSURL.h" 15 | #import "TTHTTPRequestSerializer.h" 16 | 17 | #define REQUESTS_LIMIT 100 18 | 19 | static NSString *const kUnsplashAPIBaseURL = @"https://api.unsplash.com"; 20 | 21 | @interface LPUnsplashAPI() 22 | @property (nonatomic) NSUInteger requestsCount; 23 | @property (nonatomic, strong) NSDate *lastCheckDate; 24 | @end 25 | 26 | @implementation LPUnsplashAPI { 27 | NSURLSession *session; 28 | } 29 | 30 | + (instancetype)sharedInstance { 31 | static LPUnsplashAPI *__sharedInstance = nil; 32 | static dispatch_once_t onceToken; 33 | dispatch_once(&onceToken, ^{ 34 | __sharedInstance = [[LPUnsplashAPI alloc] init]; 35 | }); 36 | return __sharedInstance; 37 | } 38 | 39 | - (id)init { 40 | self = [super init]; 41 | if (self) { 42 | session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; 43 | } 44 | return self; 45 | } 46 | 47 | - (NSMutableURLRequest *)createRequestWithURL:(NSURL *)url method:(NSString *)method params:(NSDictionary *)params { 48 | NSMutableURLRequest *request = [TTHTTPRequestSerializer requestWithMethod:method url:url params:params]; 49 | [request setValue:[NSString stringWithFormat:@"Client-ID %@", [self unsplashAppId]] forHTTPHeaderField:@"Authorization"]; 50 | [request setValue:@"v1" forHTTPHeaderField:@"Accept-Version"]; 51 | return request; 52 | } 53 | 54 | #pragma mark Photos 55 | 56 | #pragma mark Photos / Random / Single 57 | 58 | - (void)getRandomPhotoWithParams:(NSDictionary *)params 59 | success:(void (^)(IVPhoto *photo))success 60 | failure:(void (^)(NSError *error))failure { 61 | 62 | if (self.lastCheckDate && [[NSDate date] timeIntervalSinceDate:self.lastCheckDate] >= 3600) { 63 | self.requestsCount = 0; 64 | } 65 | 66 | if (self.requestsCount >= REQUESTS_LIMIT) { 67 | if (failure) { 68 | failure([NSError errorWithDomain:NSStringFromClass(self.class) code:3 userInfo:nil]); 69 | } 70 | return; 71 | } 72 | 73 | self.requestsCount += 1; 74 | self.lastCheckDate = [NSDate date]; 75 | 76 | NSMutableDictionary *fullParams = [params mutableCopy]; 77 | [fullParams addEntriesFromDictionary:@{@"orientation": @"landscape", 78 | @"count": @1}]; 79 | 80 | NSURLRequest *photoRequest = [self createRequestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/photos/random", kUnsplashAPIBaseURL]] method:@"GET" params:fullParams]; 81 | [[session dataTaskWithRequest:photoRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 82 | if (error) { 83 | if (failure) { 84 | failure(error); 85 | } 86 | return; 87 | } 88 | 89 | NSError *jsonError; 90 | id json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&jsonError]; 91 | if (jsonError) { 92 | if (failure) { 93 | failure(jsonError); 94 | } 95 | return; 96 | } 97 | NSDictionary *photoJSON; 98 | if ([json isKindOfClass:(NSDictionary.class)]) { 99 | NSDictionary *errorJSON = json; 100 | if (failure) { 101 | failure([NSError errorWithDomain:NSStringFromClass(self.class) code:2 userInfo:@{@"json": errorJSON}]); 102 | } 103 | return; 104 | } else if ([json isKindOfClass:NSArray.class]) { 105 | photoJSON = [json firstObject]; 106 | } 107 | 108 | IVPhoto *photo = [[IVPhoto alloc] initWithJSON:photoJSON]; 109 | if (success) { 110 | success(photo); 111 | } 112 | }] resume]; 113 | } 114 | 115 | - (void)getRandomPhotoFromFeaturedSuccess:(void (^)(IVPhoto *photo))success 116 | failure:(void (^)(NSError *error))failure { 117 | [self getRandomPhotoWithParams:@{@"featured": @YES} success:success failure:failure]; 118 | } 119 | 120 | - (void)getRandomPhotoFromCollectionWithID:(NSNumber *)collectionUID 121 | success:(void (^)(IVPhoto *photo))success 122 | failure:(void (^)(NSError *error))failure { 123 | [self getRandomPhotoWithParams:@{@"collections": collectionUID} success:success failure:failure]; 124 | } 125 | 126 | - (void)getRandomPhotoFromUserWithName:(NSString *)username 127 | success:(void (^)(IVPhoto *photo))success 128 | failure:(void (^)(NSError *error))failure { 129 | [self getRandomPhotoWithParams:@{@"username": username} success:success failure:failure]; 130 | } 131 | 132 | - (void)getRandomPhotoFromSearchQuery:(NSString *)query 133 | success:(void (^)(IVPhoto *photo))success 134 | failure:(void (^)(NSError *error))failure { 135 | [self getRandomPhotoWithParams:@{@"query": query} success:success failure:failure]; 136 | } 137 | 138 | #pragma mark Photos / Random / Multiple 139 | 140 | - (void)loadRandomPhotosCount:(NSUInteger)count params:(NSDictionary *)params 141 | success:(void (^)(NSArray *photos))success 142 | failure:(void (^)(NSError *error))failure { 143 | if (count == 0) { 144 | if (failure) { 145 | failure([NSError errorWithDomain:NSStringFromClass(self.class) code:1 userInfo:@{@"message": @"count parameter is zero"}]); 146 | } 147 | return; 148 | } 149 | 150 | if (self.lastCheckDate && [[NSDate date] timeIntervalSinceDate:self.lastCheckDate] >= 3600) { 151 | self.requestsCount = 0; 152 | } 153 | 154 | if (self.requestsCount >= REQUESTS_LIMIT) { 155 | if (failure) { 156 | failure([NSError errorWithDomain:NSStringFromClass(self.class) code:3 userInfo:nil]); 157 | } 158 | return; 159 | } 160 | 161 | self.requestsCount += 1; 162 | self.lastCheckDate = [NSDate date]; 163 | 164 | NSMutableDictionary *fullParams = [params mutableCopy]; 165 | [fullParams addEntriesFromDictionary:@{@"orientation": @"landscape", 166 | @"count": @(count)}]; 167 | 168 | NSURLRequest *photosRequest = [self createRequestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/photos/random", kUnsplashAPIBaseURL]] method:@"GET" params:fullParams]; 169 | [[session dataTaskWithRequest:photosRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 170 | if (error) { 171 | if (failure) { 172 | failure(error); 173 | } 174 | return; 175 | } 176 | 177 | NSError *jsonError; 178 | id json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&jsonError]; 179 | if (jsonError) { 180 | if (failure) { 181 | failure(jsonError); 182 | } 183 | return; 184 | } 185 | if (![json isKindOfClass:NSArray.class]) { 186 | if (failure) { 187 | failure([NSError errorWithDomain:NSStringFromClass(self.class) code:2 userInfo:@{@"json": json}]); 188 | } 189 | return; 190 | } 191 | NSArray *photosJSON = json; 192 | if (photosJSON.count == 0) { 193 | if (failure) { 194 | failure([NSError errorWithDomain:NSStringFromClass(self.class) code:1 userInfo:@{@"message": @"no photos"}]); 195 | } 196 | return; 197 | } 198 | 199 | NSArray *__block photosToReturn = [photosJSON mapWithBlock:^id(id obj) { 200 | return [[IVPhoto alloc] initWithJSON:obj]; 201 | }]; 202 | 203 | if (success) { 204 | success(photosToReturn); 205 | } 206 | }] resume]; 207 | } 208 | 209 | - (void)getRandomPhotosFromFeaturedCount:(NSUInteger)count 210 | success:(void (^)(NSArray *photos))success 211 | failure:(void (^)(NSError *error))failure { 212 | [self loadRandomPhotosCount:count params:@{@"featured": @YES} success:success failure:failure]; 213 | } 214 | 215 | - (void)getRandomPhotosFromCollectionWithID:(NSNumber *)collectionUID 216 | count:(NSUInteger)count 217 | success:(void (^)(NSArray *photos))success 218 | failure:(void (^)(NSError *error))failure { 219 | [self loadRandomPhotosCount:count params:@{@"collections": collectionUID} success:success failure:failure]; 220 | } 221 | 222 | - (void)getRandomPhotosFromUserWithName:(NSString *)username 223 | count:(NSUInteger)count 224 | success:(void (^)(NSArray *photos))success 225 | failure:(void (^)(NSError *error))failure { 226 | [self loadRandomPhotosCount:count params:@{@"username": username} success:success failure:failure]; 227 | } 228 | 229 | - (void)getRandomPhotosFromSearchQuery:(NSString *)query 230 | count:(NSUInteger)count 231 | success:(void (^)(NSArray *photos))success 232 | failure:(void (^)(NSError *error))failure { 233 | [self loadRandomPhotosCount:count params:@{@"query": query} success:success failure:failure]; 234 | } 235 | 236 | @end 237 | -------------------------------------------------------------------------------- /Irvue Screensaver/LPUnsplashPhotoView.h: -------------------------------------------------------------------------------- 1 | // 2 | // LPUnsplashPhotoView.h 3 | // Irvue Screensaver 4 | // 5 | // Created by Игорь Савельев on 30/11/2016. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "IVPhoto.h" 11 | 12 | @interface LPUnsplashPhotoView : NSView 13 | 14 | @property (nonatomic, strong) NSImageView *photoImageView; 15 | @property (nonatomic, strong) NSImageView *bottomGradient; 16 | @property (nonatomic, strong) NSImageView *avatarImageView; 17 | @property (nonatomic, strong) NSTextField *authorNameLabel; 18 | @property (nonatomic, strong) NSTextField *authorProfilePage; 19 | 20 | @property (nonatomic, strong) IVPhoto *photo; 21 | 22 | - (id)initWithPhoto:(IVPhoto *)photo; 23 | 24 | - (void)setupCompletion:(void (^)(BOOL success))completion; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Irvue Screensaver/LPUnsplashPhotoView.m: -------------------------------------------------------------------------------- 1 | // 2 | // LPUnsplashPhotoView.m 3 | // Irvue Screensaver 4 | // 5 | // Created by Игорь Савельев on 30/11/2016. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import "LPUnsplashPhotoView.h" 10 | #import "LPImageDownloadManager.h" 11 | #import "NSImage+Effects.h" 12 | #import "IVUser.h" 13 | #import "IVPhotoLocation.h" 14 | #import "NSScreen+MaxScaleFactor.h" 15 | #import "NSImage+Luminance.h" 16 | 17 | @import QuartzCore; 18 | 19 | @implementation LPUnsplashPhotoView { 20 | NSImage *photoImage; 21 | } 22 | 23 | - (id)initWithPhoto:(IVPhoto *)photo { 24 | self = [self initWithFrame:NSMakeRect(0, 0, 1280, 800)]; 25 | if (self) { 26 | self.photo = photo; 27 | [self setWantsLayer:YES]; 28 | [self.layer setBackgroundColor:[NSColor blackColor].CGColor]; 29 | 30 | self.photoImageView = [[NSImageView alloc] initWithFrame:[self bounds]]; 31 | [self.photoImageView setImageScaling:NSImageScaleProportionallyUpOrDown]; 32 | [self.photoImageView setImageAlignment:NSImageAlignCenter]; 33 | [self.photoImageView setImageFrameStyle:NSImageFrameNone]; 34 | [self addSubview:self.photoImageView]; 35 | 36 | self.bottomGradient = [[NSImageView alloc] initWithFrame:CGRectMake(0, 0, self.photoImageView.frame.size.width, 100.0f)]; 37 | [self.bottomGradient setImageScaling:NSImageScaleAxesIndependently]; 38 | [self.bottomGradient setImageFrameStyle:NSImageFrameNone]; 39 | [self addSubview:self.bottomGradient]; 40 | 41 | self.avatarImageView = [[NSImageView alloc] initWithFrame:NSMakeRect(20, 20, 70, 70)]; 42 | [self.avatarImageView setImageScaling:NSImageScaleProportionallyUpOrDown]; 43 | [self.avatarImageView setImageAlignment:NSImageAlignCenter]; 44 | [self.avatarImageView setImageFrameStyle:NSImageFrameNone]; 45 | [self addSubview:self.avatarImageView]; 46 | 47 | self.authorNameLabel = [[NSTextField alloc] initWithFrame:NSMakeRect(105, 48, [self bounds].size.width-110, 30)]; 48 | [self.authorNameLabel setEditable:NO]; 49 | [self.authorNameLabel setBordered:NO]; 50 | [self.authorNameLabel setFont:[NSFont systemFontOfSize:22.0f]]; 51 | [self.authorNameLabel setTextColor:[NSColor colorWithWhite:0.9f alpha:1.0f]]; 52 | [self.authorNameLabel setBackgroundColor:[NSColor clearColor]]; 53 | [self addSubview:self.authorNameLabel]; 54 | 55 | self.authorProfilePage = [[NSTextField alloc] initWithFrame:NSMakeRect(105, 32, [self bounds].size.width-110, 20)]; 56 | [self.authorProfilePage setEditable:NO]; 57 | [self.authorProfilePage setBordered:NO]; 58 | [self.authorProfilePage setFont:[NSFont systemFontOfSize:14.0f]]; 59 | [self.authorProfilePage setTextColor:[NSColor colorWithWhite:0.8f alpha:1.0f]]; 60 | [self.authorProfilePage setBackgroundColor:[NSColor clearColor]]; 61 | [self addSubview:self.authorProfilePage]; 62 | 63 | NSImage *image = [NSImage imageWithSize:NSMakeSize(1, 120) flipped:NO drawingHandler:^BOOL(NSRect dstRect) { 64 | NSGradient *gradient = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithWhite:0 alpha:0.6f] 65 | endingColor:[NSColor colorWithWhite:0 alpha:0]]; 66 | [gradient drawInRect:dstRect angle:90]; 67 | return YES; 68 | }]; 69 | [image setResizingMode:NSImageResizingModeStretch]; 70 | [self.bottomGradient setImage:image]; 71 | } 72 | return self; 73 | } 74 | 75 | - (void)drawRect:(NSRect)dirtyRect { 76 | [super drawRect:dirtyRect]; 77 | } 78 | 79 | - (void)layoutSubtreeIfNeeded { 80 | [super layoutSubtreeIfNeeded]; 81 | [self.photoImageView setFrame:[self bounds]]; 82 | [self.bottomGradient setFrame:CGRectMake(0, 0, self.photoImageView.frame.size.width, 100.0f)]; 83 | [self.avatarImageView setFrame:NSMakeRect(20, 20, 70, 70)]; 84 | if (self.authorProfilePage.stringValue.length > 0) { 85 | [self.authorProfilePage setHidden:NO]; 86 | [self.authorNameLabel setFrame:NSMakeRect(105, 48, [self bounds].size.width-110, 30)]; 87 | [self.authorProfilePage setFrame:NSMakeRect(105, 32, [self bounds].size.width-110, 20)]; 88 | } else { 89 | [self.authorProfilePage setHidden:YES]; 90 | [self.authorNameLabel setFrame:NSMakeRect(105, 40, [self bounds].size.width-110, 30)]; 91 | } 92 | if (photoImage) { 93 | NSImage *newImage = [photoImage resizeWithAspectFillToSize:NSMakeSize(self.photoImageView.frame.size.width * [NSScreen maxScaleFactor], 94 | self.photoImageView.frame.size.height * [NSScreen maxScaleFactor])]; 95 | [self.photoImageView setImage:newImage]; 96 | } 97 | } 98 | 99 | - (void)setupCompletion:(void (^)(BOOL))completion { 100 | NSString *author; 101 | if (self.photo.author.firstName.length > 0 && self.photo.author.lastName.length > 0) { 102 | author = [NSString stringWithFormat:@"%@ %@", self.photo.author.firstName, self.photo.author.lastName]; 103 | } else if (self.photo.author.name.length > 0) { 104 | author = self.photo.author.name; 105 | } 106 | [self.authorNameLabel setStringValue:author]; 107 | 108 | self.authorProfilePage.stringValue = [NSString stringWithFormat:@"unsplash.com/@%@", self.photo.author.username]; 109 | 110 | __block NSInteger imageLoaded = 0; 111 | __block NSInteger avatarLoaded = 0; 112 | 113 | void (^completionBlock)() = ^{ 114 | if (imageLoaded == 1 && avatarLoaded == 1) { 115 | if (completion) { 116 | completion(YES); 117 | } 118 | return; 119 | } else if (imageLoaded != 0 && avatarLoaded != 0) { 120 | if (completion) { 121 | completion(NO); 122 | } 123 | return; 124 | } 125 | }; 126 | 127 | if (![self.photo isPlaceholder]) { 128 | NSURL *imageURL = [self.photo imageURLForSize:[NSScreen maxScreenResolution]]; 129 | if ([[LPImageDownloadManager defaultManager] hasImageForURL:[imageURL absoluteString]]) { 130 | photoImage = [[LPImageDownloadManager defaultManager] getImageForURL:[imageURL absoluteString]]; 131 | if (photoImage) { 132 | [self.photoImageView setImage:photoImage]; 133 | [self setNeedsLayout:YES]; 134 | imageLoaded = 1; 135 | } else { 136 | imageLoaded = -1; 137 | } 138 | completionBlock(); 139 | } else { 140 | [[LPImageDownloadManager defaultManager] getImageForURL:[imageURL absoluteString] completion:^(NSImage *image) { 141 | photoImage = image; 142 | if (photoImage) { 143 | [self.photoImageView setImage:photoImage]; 144 | [self setNeedsLayout:YES]; 145 | imageLoaded = 1; 146 | } else { 147 | imageLoaded = -1; 148 | } 149 | completionBlock(); 150 | }]; 151 | } 152 | } else { 153 | photoImage = [NSImage imageNamed:@"placeholder"]; 154 | if (photoImage) { 155 | [self.photoImageView setImage:photoImage]; 156 | [self setNeedsLayout:YES]; 157 | imageLoaded = 1; 158 | } else { 159 | imageLoaded = -1; 160 | } 161 | completionBlock(); 162 | } 163 | 164 | NSURL *authorImageURL = self.photo.author.largeProfileImage; 165 | if (!authorImageURL) { 166 | authorImageURL = self.photo.author.mediumProfileImage; 167 | } 168 | if (!authorImageURL) { 169 | authorImageURL = self.photo.author.smallProfileImage; 170 | } 171 | 172 | if (authorImageURL) { 173 | if ([[LPImageDownloadManager defaultManager] hasImageForURL:[authorImageURL absoluteString] size:TTImageSize300px rounded:YES]) { 174 | [self.avatarImageView setImage:[[LPImageDownloadManager defaultManager] getImageForURL:[authorImageURL absoluteString] size:TTImageSize300px rounded:YES]]; 175 | avatarLoaded = 1; 176 | completionBlock(); 177 | } else { 178 | [[LPImageDownloadManager defaultManager] getImageForURL:[authorImageURL absoluteString] size:TTImageSize300px rounded:YES completion:^(NSImage *image) { 179 | if (image) { 180 | [self.avatarImageView setImage:image]; 181 | avatarLoaded = 1; 182 | } else { 183 | avatarLoaded = -1; 184 | } 185 | completionBlock(); 186 | }]; 187 | } 188 | } else { 189 | if ([self.photo isPlaceholder]) { 190 | [self.avatarImageView setImage:[[NSImage imageNamed:@"placeholderProfileImage"] resizeWithAspectFillToSize:NSMakeSize(300, 300) rounded:YES]]; 191 | } 192 | avatarLoaded = 1; 193 | completionBlock(); 194 | } 195 | } 196 | 197 | @end 198 | -------------------------------------------------------------------------------- /Irvue Screensaver/NSArray+Utilities.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+Utilities.h 3 | // Music Sense 4 | // 5 | // Created by Игорь Савельев on 01/10/15. 6 | // Copyright © 2015 10tracks. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSArray (Utilities) 12 | 13 | - (NSArray *)mapWithBlock:(id (^)(id obj))mapBlock; 14 | - (NSArray *)filterWithBlock:(BOOL (^)(id obj))filterBlock; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Irvue Screensaver/NSArray+Utilities.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+Utilities.m 3 | // Music Sense 4 | // 5 | // Created by Игорь Савельев on 01/10/15. 6 | // Copyright © 2015 10tracks. All rights reserved. 7 | // 8 | 9 | #import "NSArray+Utilities.h" 10 | 11 | @implementation NSArray (Utilities) 12 | 13 | - (NSArray *)mapWithBlock:(id (^)(id))mapBlock { 14 | NSMutableArray *newArray = [NSMutableArray array]; 15 | for (id obj in self) { 16 | id newObj; 17 | if (mapBlock) { 18 | newObj = mapBlock(obj); 19 | } 20 | if (newObj) { 21 | [newArray addObject:newObj]; 22 | } 23 | } 24 | return [NSArray arrayWithArray:newArray]; 25 | } 26 | 27 | - (NSArray *)filterWithBlock:(BOOL (^)(id))filterBlock { 28 | NSMutableArray *newArray = [NSMutableArray array]; 29 | for (id obj in self) { 30 | if (filterBlock && filterBlock(obj)) { 31 | [newArray addObject:obj]; 32 | } 33 | } 34 | return [NSArray arrayWithArray:newArray]; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /Irvue Screensaver/NSBezierPath+CGPath.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSBezierPath+CGPath.h 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 21/10/2016. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSBezierPath (CGPath) 12 | 13 | - (CGPathRef)CGPath; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Irvue Screensaver/NSBezierPath+CGPath.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSBezierPath+CGPath.m 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 21/10/2016. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import "NSBezierPath+CGPath.h" 10 | 11 | @implementation NSBezierPath (CGPath) 12 | 13 | - (CGPathRef)CGPath { 14 | NSUInteger i, elementCount; 15 | CGPathRef immutablePath = NULL; 16 | elementCount = [self elementCount]; 17 | if (elementCount > 0) { 18 | CGMutablePathRef path = CGPathCreateMutable(); 19 | NSPoint points[3]; 20 | BOOL didClosePath = YES; 21 | 22 | for (i = 0; i < elementCount; i++) { 23 | switch ([self elementAtIndex:i associatedPoints:points]) { 24 | case NSMoveToBezierPathElement: 25 | CGPathMoveToPoint(path, NULL, points[0].x, points[0].y); 26 | break; 27 | case NSLineToBezierPathElement: 28 | CGPathAddLineToPoint(path, NULL, points[0].x, points[0].y); 29 | didClosePath = NO; 30 | break; 31 | case NSCurveToBezierPathElement: 32 | CGPathAddCurveToPoint(path, NULL, points[0].x, points[0].y, 33 | points[1].x, points[1].y, 34 | points[2].x, points[2].y); 35 | didClosePath = NO; 36 | break; 37 | case NSClosePathBezierPathElement: 38 | CGPathCloseSubpath(path); 39 | didClosePath = YES; 40 | break; 41 | } 42 | } 43 | 44 | if (!didClosePath) { 45 | CGPathCloseSubpath(path); 46 | } 47 | immutablePath = CGPathCreateCopy(path); 48 | CGPathRelease(path); 49 | } 50 | 51 | return immutablePath; 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /Irvue Screensaver/NSCharacterSet+QueryParams.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSCharacterSet+QueryParams.h 3 | // Volume 4 | // 5 | // Created by Игорь Савельев on 29/08/16. 6 | // Copyright © 2016 MusicSense. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSCharacterSet (QueryParams) 12 | 13 | + (instancetype)URLQueryParameterValueAllowedCharacterSet; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Irvue Screensaver/NSCharacterSet+QueryParams.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSCharacterSet+QueryParams.m 3 | // Volume 4 | // 5 | // Created by Игорь Савельев on 29/08/16. 6 | // Copyright © 2016 MusicSense. All rights reserved. 7 | // 8 | 9 | #import "NSCharacterSet+QueryParams.h" 10 | 11 | @implementation NSCharacterSet (QueryParams) 12 | 13 | + (instancetype)URLQueryParameterValueAllowedCharacterSet { 14 | static NSString * const kGeneralDelimitersToEncode = @":#[]@"; 15 | static NSString * const kSubDelimitersToEncode = @"!$&'()*+,;="; 16 | 17 | NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy]; 18 | [allowedCharacterSet removeCharactersInString:[kGeneralDelimitersToEncode stringByAppendingString:kSubDelimitersToEncode]]; 19 | 20 | return allowedCharacterSet; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Irvue Screensaver/NSDictionary+NSURL.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDictionary+NSURL.h 3 | // tentracks-ios 4 | // 5 | // Created by Игорь Савельев on 20/03/14. 6 | // Copyright (c) 2014 Music Sense. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSDictionary (NSURL) 12 | 13 | + (instancetype)dictionaryWithURL:(NSURL *)url; 14 | - (NSURL *)urlWithBase:(NSString *)base; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Irvue Screensaver/NSDictionary+NSURL.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDictionary+NSURL.m 3 | // tentracks-ios 4 | // 5 | // Created by Игорь Савельев on 20/03/14. 6 | // Copyright (c) 2014 Music Sense. All rights reserved. 7 | // 8 | 9 | #import "NSDictionary+NSURL.h" 10 | 11 | @implementation NSDictionary (NSURL) 12 | 13 | + (NSDictionary *)dictionaryWithURL:(NSURL *)URL { 14 | NSString *queryString = [URL query]; 15 | NSMutableDictionary *result = [NSMutableDictionary dictionary]; 16 | NSArray *parameters = [queryString componentsSeparatedByString:@"&"]; 17 | for (NSString *parameter in parameters) { 18 | NSArray *parts = [parameter componentsSeparatedByString:@"="]; 19 | NSString *key = [[parts objectAtIndex:0] stringByRemovingPercentEncoding]; 20 | if ([parts count] > 1) { 21 | id value = [[parts objectAtIndex:1] stringByRemovingPercentEncoding]; 22 | if (key) { 23 | [result setObject:value forKey:key]; 24 | } 25 | } 26 | } 27 | return result; 28 | } 29 | 30 | - (NSURL *)urlWithBase:(NSString *)base { 31 | NSMutableString *urlString = [NSMutableString stringWithString:base]; 32 | [urlString appendString:@"?"]; 33 | BOOL first = YES; 34 | for (NSString *key in self.allKeys) { 35 | if (!first) { 36 | [urlString appendString:@"&"]; 37 | } 38 | [urlString appendFormat:@"%@=%@", key, [[self objectForKey:key] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]]; 39 | first = NO; 40 | } 41 | return [NSURL URLWithString:urlString]; 42 | } 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /Irvue Screensaver/NSImage+Effects.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSImage+Effects.h 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 26/10/2016. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSImage (Effects) 12 | 13 | - (NSImage *)resizeWithAspectFillToSize:(NSSize)size; 14 | - (NSImage *)resizeWithAspectFillToSize:(NSSize)size rounded:(BOOL)rounded; 15 | - (NSBitmapImageRep *)bitmapImageRepresentation; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Irvue Screensaver/NSImage+Effects.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSImage+Effects.m 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 26/10/2016. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import "NSImage+Effects.h" 10 | 11 | @implementation NSImage (Effects) 12 | 13 | - (NSImage *)resizeWithAspectFillToSize:(NSSize)size { 14 | return [self resizeWithAspectFillToSize:size rounded:NO]; 15 | } 16 | 17 | - (NSImage *)resizeWithAspectFillToSize:(NSSize)size rounded:(BOOL)rounded { 18 | NSRect targetFrame = NSMakeRect(0, 0, size.width, size.height); 19 | NSImage *targetImage = [[NSImage alloc] initWithSize:size]; 20 | 21 | NSSize sourceSize = [self size]; 22 | 23 | float ratioH = size.height/sourceSize.height; 24 | float ratioW = size.width/sourceSize.width; 25 | 26 | NSRect cropRect = NSZeroRect; 27 | if (ratioH >= ratioW) { 28 | cropRect.size.width = floor (size.width / ratioH); 29 | cropRect.size.height = sourceSize.height; 30 | } else { 31 | cropRect.size.width = sourceSize.width; 32 | cropRect.size.height = floor(size.height / ratioW); 33 | } 34 | 35 | cropRect.origin.x = floor((sourceSize.width - cropRect.size.width)/2); 36 | cropRect.origin.y = floor((sourceSize.height - cropRect.size.height)/2); 37 | 38 | [targetImage lockFocus]; 39 | if (rounded) { 40 | [[NSBezierPath bezierPathWithRoundedRect:NSMakeRect(0, 0, size.width, size.height) xRadius:size.width/2.0f yRadius:size.height/2.0f] addClip]; 41 | } 42 | [self drawInRect:targetFrame 43 | fromRect:cropRect 44 | operation:NSCompositingOperationCopy 45 | fraction:1.0 46 | respectFlipped:YES 47 | hints:@{NSImageHintInterpolation: [NSNumber numberWithInt:NSImageInterpolationLow]}]; 48 | [targetImage unlockFocus]; 49 | 50 | return targetImage; 51 | } 52 | 53 | - (NSBitmapImageRep *)bitmapImageRepresentation { 54 | int width = [self size].width; 55 | int height = [self size].height; 56 | 57 | if (width < 1 || height < 1) { 58 | return nil; 59 | } 60 | 61 | NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] 62 | initWithBitmapDataPlanes:NULL 63 | pixelsWide:width 64 | pixelsHigh:height 65 | bitsPerSample:8 66 | samplesPerPixel:4 67 | hasAlpha:YES 68 | isPlanar:NO 69 | colorSpaceName:NSDeviceRGBColorSpace 70 | bytesPerRow:width * 4 71 | bitsPerPixel:32]; 72 | 73 | NSGraphicsContext *ctx = [NSGraphicsContext graphicsContextWithBitmapImageRep: rep]; 74 | [NSGraphicsContext saveGraphicsState]; 75 | [NSGraphicsContext setCurrentContext: ctx]; 76 | [self drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositingOperationCopy fraction:1.0]; 77 | [ctx flushGraphics]; 78 | [NSGraphicsContext restoreGraphicsState]; 79 | 80 | return rep; 81 | } 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /Irvue Screensaver/NSImage+Luminance.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSImage+Luminance.h 3 | // Unsplash Wallpaper 4 | // 5 | // Created by Игорь Савельев on 06/06/15. 6 | // Copyright (c) 2015 Leonspok. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSImage (Luminance) 12 | 13 | @property (nonatomic, readonly) CGFloat luminance; 14 | 15 | - (CGFloat)luminanceInRect:(NSRect)rect; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Irvue Screensaver/NSImage+Luminance.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSImage+Luminance.m 3 | // Unsplash Wallpaper 4 | // 5 | // Created by Игорь Савельев on 06/06/15. 6 | // Copyright (c) 2015 Leonspok. All rights reserved. 7 | // 8 | 9 | #import "NSImage+Luminance.h" 10 | #import "NSImage+Effects.h" 11 | #import 12 | 13 | static NSString *const kCalculatedLuminanceKey = @"CALCULATED LUMINANCE"; 14 | 15 | @implementation NSImage (Luminance) 16 | 17 | - (CGFloat)luminanceInRect:(NSRect)rect { 18 | NSNumber *lum = objc_getAssociatedObject(self, &kCalculatedLuminanceKey); 19 | if (lum) { 20 | return [lum doubleValue]; 21 | } 22 | 23 | CGFloat averageLuminance = 0.5f; 24 | @autoreleasepool { 25 | NSBitmapImageRep *rep = [self bitmapImageRepresentation]; 26 | 27 | int width = [self size].width; 28 | int height = [self size].height; 29 | 30 | CGFloat totalLuminance = 0; 31 | CGFloat r; 32 | CGFloat g; 33 | CGFloat b; 34 | NSColor *color; 35 | NSInteger n = 0; 36 | for (NSInteger i = MIN(MAX(0, rect.origin.y), height); i < MIN(rect.origin.y+rect.size.height, height); i+=10) { 37 | for (NSInteger j = MIN(MAX(0, rect.origin.x), width); j < MIN(rect.origin.x+rect.size.width, width); j+=10) { 38 | color = [rep colorAtX:j y:i]; 39 | [color getRed:&r green:&g blue:&b alpha:NULL]; 40 | CGFloat luminance = (0.299*r + 0.587*g + 0.114*b); 41 | totalLuminance += luminance; 42 | n++; 43 | } 44 | } 45 | averageLuminance = totalLuminance/n; 46 | } 47 | 48 | objc_setAssociatedObject(self, &kCalculatedLuminanceKey, @(averageLuminance), OBJC_ASSOCIATION_RETAIN); 49 | return averageLuminance; 50 | } 51 | 52 | - (CGFloat)luminance { 53 | return [self luminanceInRect:NSMakeRect(0, 0, self.size.width, self.size.height)]; 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /Irvue Screensaver/NSScreen+MaxScaleFactor.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSScreen+MaxScaleFactor.h 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 30/11/2016. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSScreen (MaxScaleFactor) 12 | 13 | + (CGFloat)maxScaleFactor; 14 | + (CGSize)maxScreenResolution; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Irvue Screensaver/NSScreen+MaxScaleFactor.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSScreen+MaxScaleFactor.m 3 | // Irvue 4 | // 5 | // Created by Игорь Савельев on 30/11/2016. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import "NSScreen+MaxScaleFactor.h" 10 | 11 | @implementation NSScreen (MaxScaleFactor) 12 | 13 | + (CGFloat)maxScaleFactor { 14 | CGFloat factor = 0.0f; 15 | for (NSScreen *screen in [NSScreen screens]) { 16 | factor = MAX(factor, [screen backingScaleFactor]); 17 | } 18 | return factor; 19 | } 20 | 21 | + (CGSize)maxScreenResolution { 22 | CGFloat width = 0.0f; 23 | CGFloat height = 0.0f; 24 | for (NSScreen *screen in [NSScreen screens]) { 25 | width = MAX(width, screen.frame.size.width*[screen backingScaleFactor]); 26 | height = MAX(height, screen.frame.size.height*[screen backingScaleFactor]); 27 | } 28 | return NSMakeSize(width, height); 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /Irvue Screensaver/NSSet+Utilities.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSSet+Utilities.h 3 | // Music Sense 4 | // 5 | // Created by Игорь Савельев on 01/10/15. 6 | // Copyright © 2015 10tracks. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSSet (Utilities) 12 | 13 | - (NSSet *)mapWithBlock:(id (^)(id obj))mapBlock; 14 | - (NSSet *)filterWithBlock:(BOOL (^)(id obj))filterBlock; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Irvue Screensaver/NSSet+Utilities.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSSet+Utilities.m 3 | // Music Sense 4 | // 5 | // Created by Игорь Савельев on 01/10/15. 6 | // Copyright © 2015 10tracks. All rights reserved. 7 | // 8 | 9 | #import "NSSet+Utilities.h" 10 | 11 | @implementation NSSet (Utilities) 12 | 13 | - (NSSet *)mapWithBlock:(id (^)(id))mapBlock { 14 | NSMutableSet *newSet = [NSMutableSet set]; 15 | for (id obj in self) { 16 | id newObj; 17 | if (mapBlock) { 18 | newObj = mapBlock(obj); 19 | } 20 | if (newObj) { 21 | [newSet addObject:newObj]; 22 | } 23 | } 24 | return [NSMutableSet setWithSet:newSet]; 25 | } 26 | 27 | - (NSSet *)filterWithBlock:(BOOL (^)(id))filterBlock { 28 | NSMutableSet *newSet = [NSMutableSet set]; 29 | for (id obj in self) { 30 | if (filterBlock && filterBlock(obj)) { 31 | [newSet addObject:obj]; 32 | } 33 | } 34 | return [NSSet setWithSet:newSet]; 35 | } 36 | 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /Irvue Screensaver/NSString+MD5.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+MD5.h 3 | // Music Sense 4 | // 5 | // Created by Игорь Савельев on 06/08/14. 6 | // Copyright (c) 2014 Music Sense. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSString (MD5) 12 | 13 | - (NSString *)MD5String; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Irvue Screensaver/NSString+MD5.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+MD5.m 3 | // Music Sense 4 | // 5 | // Created by Игорь Савельев on 06/08/14. 6 | // Copyright (c) 2014 10tracks. All rights reserved. 7 | // 8 | 9 | #import "NSString+MD5.h" 10 | #import 11 | 12 | @implementation NSString (MD5) 13 | 14 | - (NSString *)MD5String { 15 | const char *cstr = [self UTF8String]; 16 | unsigned char result[16]; 17 | CC_MD5(cstr, (CC_LONG)strlen(cstr), result); 18 | 19 | return [NSString stringWithFormat: 20 | @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", 21 | result[0], result[1], result[2], result[3], 22 | result[4], result[5], result[6], result[7], 23 | result[8], result[9], result[10], result[11], 24 | result[12], result[13], result[14], result[15] 25 | ]; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /Irvue Screensaver/Reachability.h: -------------------------------------------------------------------------------- 1 | /* 2 | File: Reachability.h 3 | Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. 4 | Version: 3.5 5 | 6 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 7 | Inc. ("Apple") in consideration of your agreement to the following 8 | terms, and your use, installation, modification or redistribution of 9 | this Apple software constitutes acceptance of these terms. If you do 10 | not agree with these terms, please do not use, install, modify or 11 | redistribute this Apple software. 12 | 13 | In consideration of your agreement to abide by the following terms, and 14 | subject to these terms, Apple grants you a personal, non-exclusive 15 | license, under Apple's copyrights in this original Apple software (the 16 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 17 | Software, with or without modifications, in source and/or binary forms; 18 | provided that if you redistribute the Apple Software in its entirety and 19 | without modifications, you must retain this notice and the following 20 | text and disclaimers in all such redistributions of the Apple Software. 21 | Neither the name, trademarks, service marks or logos of Apple Inc. may 22 | be used to endorse or promote products derived from the Apple Software 23 | without specific prior written permission from Apple. Except as 24 | expressly stated in this notice, no other rights or licenses, express or 25 | implied, are granted by Apple herein, including but not limited to any 26 | patent rights that may be infringed by your derivative works or by other 27 | works in which the Apple Software may be incorporated. 28 | 29 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 30 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 31 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 32 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 33 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 34 | 35 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 36 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 37 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 38 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 39 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 40 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 41 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 42 | POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2014 Apple Inc. All Rights Reserved. 45 | 46 | */ 47 | 48 | #import 49 | #import 50 | #import 51 | 52 | 53 | typedef enum : NSInteger { 54 | NotReachable = 0, 55 | ReachableViaWiFi, 56 | ReachableViaWWAN 57 | } NetworkStatus; 58 | 59 | 60 | extern NSString *kReachabilityChangedNotification; 61 | 62 | 63 | @interface Reachability : NSObject 64 | 65 | /*! 66 | * Use to check the reachability of a given host name. 67 | */ 68 | + (instancetype)reachabilityWithHostName:(NSString *)hostName; 69 | 70 | /*! 71 | * Use to check the reachability of a given IP address. 72 | */ 73 | + (instancetype)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress; 74 | 75 | /*! 76 | * Checks whether the default route is available. Should be used by applications that do not connect to a particular host. 77 | */ 78 | + (instancetype)reachabilityForInternetConnection; 79 | 80 | /*! 81 | * Checks whether a local WiFi connection is available. 82 | */ 83 | + (instancetype)reachabilityForLocalWiFi; 84 | 85 | /*! 86 | * Start listening for reachability notifications on the current run loop. 87 | */ 88 | - (BOOL)startNotifier; 89 | - (void)stopNotifier; 90 | 91 | - (NetworkStatus)currentReachabilityStatus; 92 | 93 | /*! 94 | * WWAN may be available, but not active until a connection has been established. WiFi may require a connection for VPN on Demand. 95 | */ 96 | - (BOOL)connectionRequired; 97 | 98 | @end 99 | 100 | 101 | -------------------------------------------------------------------------------- /Irvue Screensaver/Reachability.m: -------------------------------------------------------------------------------- 1 | /* 2 | File: Reachability.m 3 | Abstract: Basic demonstration of how to use the SystemConfiguration Reachablity APIs. 4 | Version: 3.5 5 | 6 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 7 | Inc. ("Apple") in consideration of your agreement to the following 8 | terms, and your use, installation, modification or redistribution of 9 | this Apple software constitutes acceptance of these terms. If you do 10 | not agree with these terms, please do not use, install, modify or 11 | redistribute this Apple software. 12 | 13 | In consideration of your agreement to abide by the following terms, and 14 | subject to these terms, Apple grants you a personal, non-exclusive 15 | license, under Apple's copyrights in this original Apple software (the 16 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 17 | Software, with or without modifications, in source and/or binary forms; 18 | provided that if you redistribute the Apple Software in its entirety and 19 | without modifications, you must retain this notice and the following 20 | text and disclaimers in all such redistributions of the Apple Software. 21 | Neither the name, trademarks, service marks or logos of Apple Inc. may 22 | be used to endorse or promote products derived from the Apple Software 23 | without specific prior written permission from Apple. Except as 24 | expressly stated in this notice, no other rights or licenses, express or 25 | implied, are granted by Apple herein, including but not limited to any 26 | patent rights that may be infringed by your derivative works or by other 27 | works in which the Apple Software may be incorporated. 28 | 29 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 30 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 31 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 32 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 33 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 34 | 35 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 36 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 37 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 38 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 39 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 40 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 41 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 42 | POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2014 Apple Inc. All Rights Reserved. 45 | 46 | */ 47 | 48 | #import 49 | #import 50 | #import 51 | #import 52 | 53 | #import 54 | 55 | #import "Reachability.h" 56 | 57 | 58 | NSString *kReachabilityChangedNotification = @"kNetworkReachabilityChangedNotification"; 59 | 60 | 61 | #pragma mark - Supporting functions 62 | 63 | static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) 64 | { 65 | #pragma unused (target, flags) 66 | NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback"); 67 | NSCAssert([(__bridge NSObject*) info isKindOfClass: [Reachability class]], @"info was wrong class in ReachabilityCallback"); 68 | 69 | Reachability* noteObject = (__bridge Reachability *)info; 70 | // Post a notification to notify the client that the network reachability changed. 71 | [[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification object: noteObject]; 72 | } 73 | 74 | 75 | #pragma mark - Reachability implementation 76 | 77 | @implementation Reachability 78 | { 79 | BOOL _alwaysReturnLocalWiFiStatus; //default is NO 80 | SCNetworkReachabilityRef _reachabilityRef; 81 | } 82 | 83 | + (instancetype)reachabilityWithHostName:(NSString *)hostName 84 | { 85 | Reachability* returnValue = NULL; 86 | SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]); 87 | if (reachability != NULL) 88 | { 89 | returnValue= [[self alloc] init]; 90 | if (returnValue != NULL) 91 | { 92 | returnValue->_reachabilityRef = reachability; 93 | returnValue->_alwaysReturnLocalWiFiStatus = NO; 94 | } 95 | } 96 | return returnValue; 97 | } 98 | 99 | 100 | + (instancetype)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress 101 | { 102 | SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)hostAddress); 103 | 104 | Reachability* returnValue = NULL; 105 | 106 | if (reachability != NULL) 107 | { 108 | returnValue = [[self alloc] init]; 109 | if (returnValue != NULL) 110 | { 111 | returnValue->_reachabilityRef = reachability; 112 | returnValue->_alwaysReturnLocalWiFiStatus = NO; 113 | } 114 | } 115 | return returnValue; 116 | } 117 | 118 | 119 | 120 | + (instancetype)reachabilityForInternetConnection 121 | { 122 | struct sockaddr_in zeroAddress; 123 | bzero(&zeroAddress, sizeof(zeroAddress)); 124 | zeroAddress.sin_len = sizeof(zeroAddress); 125 | zeroAddress.sin_family = AF_INET; 126 | 127 | return [self reachabilityWithAddress:&zeroAddress]; 128 | } 129 | 130 | 131 | + (instancetype)reachabilityForLocalWiFi 132 | { 133 | struct sockaddr_in localWifiAddress; 134 | bzero(&localWifiAddress, sizeof(localWifiAddress)); 135 | localWifiAddress.sin_len = sizeof(localWifiAddress); 136 | localWifiAddress.sin_family = AF_INET; 137 | 138 | // IN_LINKLOCALNETNUM is defined in as 169.254.0.0. 139 | localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); 140 | 141 | Reachability* returnValue = [self reachabilityWithAddress: &localWifiAddress]; 142 | if (returnValue != NULL) 143 | { 144 | returnValue->_alwaysReturnLocalWiFiStatus = YES; 145 | } 146 | 147 | return returnValue; 148 | } 149 | 150 | 151 | #pragma mark - Start and stop notifier 152 | 153 | - (BOOL)startNotifier 154 | { 155 | BOOL returnValue = NO; 156 | SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL}; 157 | 158 | if (SCNetworkReachabilitySetCallback(_reachabilityRef, ReachabilityCallback, &context)) 159 | { 160 | if (SCNetworkReachabilityScheduleWithRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)) 161 | { 162 | returnValue = YES; 163 | } 164 | } 165 | 166 | return returnValue; 167 | } 168 | 169 | 170 | - (void)stopNotifier 171 | { 172 | if (_reachabilityRef != NULL) 173 | { 174 | SCNetworkReachabilityUnscheduleFromRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); 175 | } 176 | } 177 | 178 | 179 | - (void)dealloc 180 | { 181 | [self stopNotifier]; 182 | if (_reachabilityRef != NULL) 183 | { 184 | CFRelease(_reachabilityRef); 185 | } 186 | } 187 | 188 | 189 | #pragma mark - Network Flag Handling 190 | 191 | - (NetworkStatus)localWiFiStatusForFlags:(SCNetworkReachabilityFlags)flags 192 | { 193 | //PrintReachabilityFlags(flags, "localWiFiStatusForFlags"); 194 | NetworkStatus returnValue = NotReachable; 195 | 196 | if ((flags & kSCNetworkReachabilityFlagsReachable) && (flags & kSCNetworkReachabilityFlagsIsDirect)) 197 | { 198 | returnValue = ReachableViaWiFi; 199 | } 200 | 201 | return returnValue; 202 | } 203 | 204 | 205 | - (NetworkStatus)networkStatusForFlags:(SCNetworkReachabilityFlags)flags 206 | { 207 | //PrintReachabilityFlags(flags, "networkStatusForFlags"); 208 | if ((flags & kSCNetworkReachabilityFlagsReachable) == 0) 209 | { 210 | // The target host is not reachable. 211 | return NotReachable; 212 | } 213 | 214 | NetworkStatus returnValue = NotReachable; 215 | 216 | if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0) 217 | { 218 | /* 219 | If the target host is reachable and no connection is required then we'll assume (for now) that you're on Wi-Fi... 220 | */ 221 | returnValue = ReachableViaWiFi; 222 | } 223 | 224 | if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || 225 | (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0)) 226 | { 227 | /* 228 | ... and the connection is on-demand (or on-traffic) if the calling application is using the CFSocketStream or higher APIs... 229 | */ 230 | 231 | if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0) 232 | { 233 | /* 234 | ... and no [user] intervention is needed... 235 | */ 236 | returnValue = ReachableViaWiFi; 237 | } 238 | } 239 | 240 | if ((flags & kSCNetworkReachabilityFlagsIsDirect) == kSCNetworkReachabilityFlagsIsDirect) 241 | { 242 | /* 243 | ... but WWAN connections are OK if the calling application is using the CFNetwork APIs. 244 | */ 245 | returnValue = ReachableViaWWAN; 246 | } 247 | 248 | return returnValue; 249 | } 250 | 251 | 252 | - (BOOL)connectionRequired 253 | { 254 | NSAssert(_reachabilityRef != NULL, @"connectionRequired called with NULL reachabilityRef"); 255 | SCNetworkReachabilityFlags flags; 256 | 257 | if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags)) 258 | { 259 | return (flags & kSCNetworkReachabilityFlagsConnectionRequired); 260 | } 261 | 262 | return NO; 263 | } 264 | 265 | 266 | - (NetworkStatus)currentReachabilityStatus 267 | { 268 | NSAssert(_reachabilityRef != NULL, @"currentNetworkStatus called with NULL SCNetworkReachabilityRef"); 269 | NetworkStatus returnValue = NotReachable; 270 | SCNetworkReachabilityFlags flags; 271 | 272 | if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags)) 273 | { 274 | if (_alwaysReturnLocalWiFiStatus) 275 | { 276 | returnValue = [self localWiFiStatusForFlags:flags]; 277 | } 278 | else 279 | { 280 | returnValue = [self networkStatusForFlags:flags]; 281 | } 282 | } 283 | 284 | return returnValue; 285 | } 286 | 287 | 288 | @end 289 | -------------------------------------------------------------------------------- /Irvue Screensaver/TTHTTPRequestSerializer.h: -------------------------------------------------------------------------------- 1 | // 2 | // TTHTTPRequestSerializer.h 3 | // Music Sense 4 | // 5 | // Created by Игорь Савельев on 24/06/16. 6 | // Copyright © 2016 10tracks. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TTHTTPRequestSerializer : NSObject 12 | 13 | + (NSMutableURLRequest *)requestWithMethod:(NSString *)method url:(NSURL *)url params:(NSDictionary *)params; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Irvue Screensaver/TTHTTPRequestSerializer.m: -------------------------------------------------------------------------------- 1 | // 2 | // TTHTTPRequestSerializer.m 3 | // Music Sense 4 | // 5 | // Created by Игорь Савельев on 24/06/16. 6 | // Copyright © 2016 10tracks. All rights reserved. 7 | // 8 | 9 | #import "TTHTTPRequestSerializer.h" 10 | #import "NSCharacterSet+QueryParams.h" 11 | 12 | @implementation TTHTTPRequestSerializer 13 | 14 | + (NSMutableURLRequest *)requestWithMethod:(NSString *)method url:(NSURL *)url params:(NSDictionary *)params { 15 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 16 | [request setCachePolicy:NSURLRequestReloadIgnoringCacheData]; 17 | [request setHTTPMethod:[method uppercaseString]]; 18 | if (params.count > 0) { 19 | NSMutableString *queryString = [NSMutableString string]; 20 | NSUInteger i = 0; 21 | for (NSString *key in params.allKeys) { 22 | NSString *valueString = nil; 23 | id value = [params objectForKey:key]; 24 | if ([value isKindOfClass:NSString.class]) { 25 | valueString = value; 26 | } else if ([value isKindOfClass:NSNumber.class]) { 27 | NSNumber *number = value; 28 | if (strcmp([number objCType], @encode(BOOL)) == 0) { 29 | valueString = [number boolValue]? @"true" : @"false"; 30 | } else { 31 | valueString = [number stringValue]; 32 | } 33 | } else { 34 | valueString = [value description]; 35 | } 36 | 37 | [queryString appendFormat:@"%@=%@", [key stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]], [valueString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryParameterValueAllowedCharacterSet]]]; 38 | if (i < params.count-1) { 39 | [queryString appendString:@"&"]; 40 | } 41 | i++; 42 | } 43 | 44 | if ([[method uppercaseString] isEqualToString:@"GET"]) { 45 | request.URL = [NSURL URLWithString:[[request.URL absoluteString] stringByAppendingFormat:request.URL.query ? @"&%@" : @"?%@", queryString]]; 46 | } else { 47 | [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; 48 | [request setHTTPBody:[queryString dataUsingEncoding:NSUTF8StringEncoding]]; 49 | } 50 | } 51 | return request; 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /Irvue Screensaver/TTOfflineChecker.h: -------------------------------------------------------------------------------- 1 | // 2 | // TTOfflineChecker.h 3 | // tentracks-ios 4 | // 5 | // Created by Игорь Савельев on 15/01/14. 6 | // Copyright (c) 2014 10tracks. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #define kTTDomain @"unsplash.com" 12 | #define kOfflineStatusChangedNotification @"kOfflineStatusChanged" 13 | 14 | enum { 15 | TTNetworkConnectionWIFI, 16 | TTNetworkConnectionCellular, 17 | TTNetworkConnectionNone 18 | } typedef TTNetworkConnection; 19 | 20 | @interface TTOfflineChecker : NSObject 21 | @property (nonatomic, readonly) NSNotificationCenter *notificationCenter; 22 | 23 | @property (nonatomic) BOOL enabled; 24 | @property (atomic, readonly, getter=isOffline) BOOL offline; 25 | @property (atomic, readonly) TTNetworkConnection networkConnection; 26 | 27 | + (instancetype)defaultChecker; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Irvue Screensaver/TTOfflineChecker.m: -------------------------------------------------------------------------------- 1 | // 2 | // TTOfflineChecker.m 3 | // tentracks-ios 4 | // 5 | // Created by Игорь Савельев on 15/01/14. 6 | // Copyright (c) 2014 10tracks. All rights reserved. 7 | // 8 | 9 | #import "TTOfflineChecker.h" 10 | #import "Reachability.h" 11 | 12 | @interface TTOfflineChecker() 13 | @property (atomic, readwrite) BOOL offline; 14 | @property (atomic, readwrite) TTNetworkConnection networkConnection; 15 | @end 16 | 17 | @implementation TTOfflineChecker { 18 | Reachability *reachibility; 19 | } 20 | 21 | + (instancetype)defaultChecker { 22 | static TTOfflineChecker* _checker = nil; 23 | static dispatch_once_t oncePresicate; 24 | dispatch_once(&oncePresicate, ^{ 25 | _checker = [[TTOfflineChecker alloc] init]; 26 | }); 27 | return _checker; 28 | } 29 | 30 | - (id)init { 31 | self = [super init]; 32 | if (self) { 33 | _notificationCenter = [[NSNotificationCenter alloc] init]; 34 | 35 | reachibility = [Reachability reachabilityForInternetConnection]; 36 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachibilityChanged) name:kReachabilityChangedNotification object:nil]; 37 | [self setEnabled:YES]; 38 | } 39 | return self; 40 | } 41 | 42 | - (void)reachibilityChanged { 43 | switch (reachibility.currentReachabilityStatus) { 44 | case NotReachable: { 45 | self.offline = YES; 46 | self.networkConnection = TTNetworkConnectionNone; 47 | } 48 | break; 49 | case ReachableViaWiFi: { 50 | self.networkConnection = TTNetworkConnectionWIFI; 51 | self.offline = NO; 52 | } 53 | break; 54 | case ReachableViaWWAN: { 55 | self.networkConnection = TTNetworkConnectionCellular; 56 | self.offline = NO; 57 | } 58 | break; 59 | } 60 | [self.notificationCenter postNotificationName:kOfflineStatusChangedNotification object:nil]; 61 | } 62 | 63 | - (void)setEnabled:(BOOL)enabled { 64 | _enabled = enabled; 65 | if (enabled) { 66 | [reachibility startNotifier]; 67 | } else { 68 | [reachibility stopNotifier]; 69 | } 70 | } 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Igor Savelev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Irvue Screensaver 2 | 3 | Screensaver with images from [Unsplash](https://unsplash.com). 4 | 5 | ![](README_resources/1.png) 6 | 7 | ## How to install 8 | 9 | 1. Download [Irvue Screensaver.saver](https://github.com/leonspok/Irvue-Screensaver/releases) 10 | 2. Double click it and it will appear in System Preferences:![](README_resources/2.png) 11 | 3. Register your Unsplash app [here](https://unsplash.com/oauth/applications) 12 | 4. Set your Unsplash App Id, choose source and update duration:![](README_resources/3.png) 13 | -------------------------------------------------------------------------------- /README_resources/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leonspok/Irvue-Screensaver/d535eb3f519cc0d8530a07d5996d4252d258d729/README_resources/1.png -------------------------------------------------------------------------------- /README_resources/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leonspok/Irvue-Screensaver/d535eb3f519cc0d8530a07d5996d4252d258d729/README_resources/2.png -------------------------------------------------------------------------------- /README_resources/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leonspok/Irvue-Screensaver/d535eb3f519cc0d8530a07d5996d4252d258d729/README_resources/3.png -------------------------------------------------------------------------------- /ScreenSaverRunner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // ScreenSaverRunner 4 | // 5 | // Created by Игорь Савельев on 30/11/2016. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : NSObject 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /ScreenSaverRunner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // ScreenSaverRunner 4 | // 5 | // Created by Игорь Савельев on 30/11/2016. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "Irvue_ScreensaverView.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @property (weak) IBOutlet NSWindow *window; 15 | @property (nonatomic, strong) Irvue_ScreensaverView *screenSaver; 16 | @end 17 | 18 | @implementation AppDelegate 19 | 20 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 21 | self.screenSaver = [[Irvue_ScreensaverView alloc] initWithFrame:self.window.contentView.bounds isPreview:YES]; 22 | [self.screenSaver setAutoresizingMask:NSViewHeightSizable|NSViewWidthSizable]; 23 | // [self.screenSaver setSource:PhotosSourceSearch]; 24 | // [self.screenSaver.defaults setObject:@"city" forKey:@"search_query"]; 25 | // [self.screenSaver.defaults synchronize]; 26 | [self.screenSaver setUpdateInterval:5.0f]; 27 | [self.window.contentView addSubview:self.screenSaver]; 28 | } 29 | 30 | 31 | - (void)applicationWillTerminate:(NSNotification *)aNotification { 32 | // Insert code here to tear down your application 33 | } 34 | 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /ScreenSaverRunner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /ScreenSaverRunner/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ScreenSaverRunner/Assets.xcassets/placeholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "placeholder.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "placeholder-1.jpg", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "placeholder-2.jpg", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | }, 23 | "properties" : { 24 | "template-rendering-intent" : "original" 25 | } 26 | } -------------------------------------------------------------------------------- /ScreenSaverRunner/Assets.xcassets/placeholder.imageset/placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leonspok/Irvue-Screensaver/d535eb3f519cc0d8530a07d5996d4252d258d729/ScreenSaverRunner/Assets.xcassets/placeholder.imageset/placeholder.jpg -------------------------------------------------------------------------------- /ScreenSaverRunner/Assets.xcassets/placeholderProfileImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "defaultProfileImage.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /ScreenSaverRunner/Assets.xcassets/placeholderProfileImage.imageset/defaultProfileImage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leonspok/Irvue-Screensaver/d535eb3f519cc0d8530a07d5996d4252d258d729/ScreenSaverRunner/Assets.xcassets/placeholderProfileImage.imageset/defaultProfileImage.jpg -------------------------------------------------------------------------------- /ScreenSaverRunner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2016 Leonspok. All rights reserved. 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /ScreenSaverRunner/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ScreenSaverRunner 4 | // 5 | // Created by Игорь Савельев on 30/11/2016. 6 | // Copyright © 2016 Leonspok. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, const char * argv[]) { 12 | return NSApplicationMain(argc, argv); 13 | } 14 | --------------------------------------------------------------------------------