├── .gitignore ├── Gank.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Gank.xcworkspace └── contents.xcworkspacedata ├── Gank ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_1024x1024-1024.png │ │ ├── icon_1024x1024-128.png │ │ ├── icon_1024x1024-16.png │ │ ├── icon_1024x1024-256.png │ │ ├── icon_1024x1024-257.png │ │ ├── icon_1024x1024-32.png │ │ ├── icon_1024x1024-33.png │ │ ├── icon_1024x1024-512.png │ │ ├── icon_1024x1024-513.png │ │ └── icon_1024x1024-64.png │ ├── Contents.json │ ├── icon-android.imageset │ │ ├── Contents.json │ │ └── android@2x.png │ ├── icon-gank.imageset │ │ ├── Contents.json │ │ └── gank@2x.png │ ├── icon-html5.imageset │ │ ├── Contents.json │ │ └── html5@2x.png │ ├── icon-image.imageset │ │ ├── Contents.json │ │ └── picasa@2x.png │ ├── icon-ios.imageset │ │ ├── Contents.json │ │ └── apple@2x.png │ ├── icon-reload.imageset │ │ ├── Contents.json │ │ └── reload.png │ ├── icon-settings.imageset │ │ ├── Contents.json │ │ └── settings.png │ ├── icon-slack.imageset │ │ ├── Contents.json │ │ └── slack@2x.png │ ├── icon-statusbar.imageset │ │ ├── Contents.json │ │ └── google@2x.png │ └── icon-video.imageset │ │ ├── Contents.json │ │ └── vimeo@2x.png ├── EventMonitor.swift ├── GankAPI.swift ├── GankButton.swift ├── GankItem.swift ├── GankItemCell.swift ├── GankLoadingView.swift ├── GankScrollView.swift ├── GankSection.swift ├── GankSectionCell.swift ├── GankSeenView.swift ├── GankViewController.swift ├── GankViewController.xib ├── Info.plist ├── MainMenu.xib ├── NSColor+Gank.swift ├── NSUserNotification+Gank.swift ├── OpenUrlAction.swift ├── PopoverAction.swift ├── SettingAction.swift └── screenshot4.png ├── LICENSE.txt ├── Podfile ├── Podfile.lock ├── README-EN.md ├── README.md ├── appimage.png ├── autologin.png ├── notification.png ├── screenshot.png ├── screenshot2.png ├── screenshot3.png └── screenshot4.png /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ### Objective-C ### 3 | # Xcode 4 | # 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | *.moved-aside 17 | DerivedData 18 | *.hmap 19 | *.ipa 20 | *.xcuserstate 21 | 22 | Pods/* 23 | -------------------------------------------------------------------------------- /Gank.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 655427E61EEBC7B600708D12 /* screenshot4.png in Resources */ = {isa = PBXBuildFile; fileRef = 655427E51EEBC7B600708D12 /* screenshot4.png */; }; 11 | A0A1EB5BEA0EA66DDCFC3B7C /* Pods_Gank.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B71DE5E34C80D7F11D29D874 /* Pods_Gank.framework */; }; 12 | C83D57C91CE62E2300762FF1 /* LICENSE.txt in Resources */ = {isa = PBXBuildFile; fileRef = C83D57C81CE62E2300762FF1 /* LICENSE.txt */; }; 13 | C84B06BF1CE6F86300EA2FC7 /* SwiftyTimer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C84B06BE1CE6F86300EA2FC7 /* SwiftyTimer.framework */; settings = {ATTRIBUTES = (Required, ); }; }; 14 | C87D6BA31CE4731B00AA31B6 /* GankItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C87D6BA21CE4731B00AA31B6 /* GankItem.swift */; }; 15 | C87D6BB61CE486F800AA31B6 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C87D6BB31CE486F800AA31B6 /* Alamofire.framework */; }; 16 | C87D6BB81CE486F800AA31B6 /* SwiftyJSON.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C87D6BB51CE486F800AA31B6 /* SwiftyJSON.framework */; }; 17 | C887EF7C1CE37ADD0065DA55 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C887EF7B1CE37ADD0065DA55 /* AppDelegate.swift */; }; 18 | C887EF7E1CE37ADD0065DA55 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C887EF7D1CE37ADD0065DA55 /* Assets.xcassets */; }; 19 | C887EF8A1CE37FF10065DA55 /* EventMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C887EF891CE37FF10065DA55 /* EventMonitor.swift */; }; 20 | C887EF901CE3831E0065DA55 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = C887EF8F1CE3831E0065DA55 /* MainMenu.xib */; }; 21 | C887EF921CE384DB0065DA55 /* GankAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C887EF911CE384DB0065DA55 /* GankAPI.swift */; }; 22 | C89D55581CE59F510063763F /* SettingAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C89D55571CE59F510063763F /* SettingAction.swift */; }; 23 | C8A1DB501CE714DA00EE8CD6 /* NSUserNotification+Gank.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8A1DB4F1CE714DA00EE8CD6 /* NSUserNotification+Gank.swift */; }; 24 | C8C636BE1CE5EDC300801E00 /* GankSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C636BD1CE5EDC300801E00 /* GankSection.swift */; }; 25 | C8EB09721CE4D54100861B77 /* PopoverAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8EB09711CE4D54100861B77 /* PopoverAction.swift */; }; 26 | C8EB098E1CE57A7C00861B77 /* OpenUrlAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8EB098D1CE57A7C00861B77 /* OpenUrlAction.swift */; }; 27 | C8EB09961CE58A6A00861B77 /* GankButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8EB09901CE58A6A00861B77 /* GankButton.swift */; }; 28 | C8EB09971CE58A6A00861B77 /* GankLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8EB09911CE58A6A00861B77 /* GankLoadingView.swift */; }; 29 | C8EB09981CE58A6A00861B77 /* GankItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8EB09921CE58A6A00861B77 /* GankItemCell.swift */; }; 30 | C8EB09991CE58A6A00861B77 /* GankScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8EB09931CE58A6A00861B77 /* GankScrollView.swift */; }; 31 | C8EB099A1CE58A6A00861B77 /* GankSectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8EB09941CE58A6A00861B77 /* GankSectionCell.swift */; }; 32 | C8EB099B1CE58A6A00861B77 /* GankSeenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8EB09951CE58A6A00861B77 /* GankSeenView.swift */; }; 33 | C8EB09A21CE58ADB00861B77 /* NSColor+Gank.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8EB099E1CE58ADB00861B77 /* NSColor+Gank.swift */; }; 34 | C8EB09A71CE58BAB00861B77 /* GankViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8EB09A51CE58BAB00861B77 /* GankViewController.swift */; }; 35 | C8EB09A81CE58BAB00861B77 /* GankViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C8EB09A61CE58BAB00861B77 /* GankViewController.xib */; }; 36 | /* End PBXBuildFile section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | 4F9AAFD7867AD56F0F15DDA2 /* Pods-Gank.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Gank.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Gank/Pods-Gank.debug.xcconfig"; sourceTree = ""; }; 40 | 655427E51EEBC7B600708D12 /* screenshot4.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = screenshot4.png; sourceTree = ""; }; 41 | 794D55073DB106AE6F7076E8 /* Pods-Gank.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Gank.release.xcconfig"; path = "Pods/Target Support Files/Pods-Gank/Pods-Gank.release.xcconfig"; sourceTree = ""; }; 42 | B71DE5E34C80D7F11D29D874 /* Pods_Gank.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Gank.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | C819FF651CE71E70004D569E /* README-EN.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = "README-EN.md"; path = "../README-EN.md"; sourceTree = ""; }; 44 | C83D57C01CE6259000762FF1 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 45 | C83D57C61CE62E1A00762FF1 /* screenshot.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = screenshot.png; path = ../screenshot.png; sourceTree = ""; }; 46 | C83D57C81CE62E2300762FF1 /* LICENSE.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = LICENSE.txt; path = ../LICENSE.txt; sourceTree = ""; }; 47 | C83D57CA1CE6300400762FF1 /* screenshot2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = screenshot2.png; path = ../screenshot2.png; sourceTree = ""; }; 48 | C83D57EA1CE633F000762FF1 /* screenshot3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = screenshot3.png; path = ../screenshot3.png; sourceTree = ""; }; 49 | C84B06BE1CE6F86300EA2FC7 /* SwiftyTimer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyTimer.framework; path = "../../Library/Developer/Xcode/DerivedData/Gank-agokjyfuexvnqfhesiiklyguildc/Build/Products/Debug/SwiftyTimer/SwiftyTimer.framework"; sourceTree = ""; }; 50 | C87D6BA21CE4731B00AA31B6 /* GankItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GankItem.swift; sourceTree = ""; }; 51 | C87D6BB31CE486F800AA31B6 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = "../../Library/Developer/Xcode/DerivedData/Gank-agokjyfuexvnqfhesiiklyguildc/Build/Products/Debug/Alamofire/Alamofire.framework"; sourceTree = ""; }; 52 | C87D6BB51CE486F800AA31B6 /* SwiftyJSON.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyJSON.framework; path = "../../Library/Developer/Xcode/DerivedData/Gank-agokjyfuexvnqfhesiiklyguildc/Build/Products/Debug/SwiftyJSON/SwiftyJSON.framework"; sourceTree = ""; }; 53 | C887EF781CE37ADD0065DA55 /* Gank.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Gank.app; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | C887EF7B1CE37ADD0065DA55 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 55 | C887EF7D1CE37ADD0065DA55 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 56 | C887EF821CE37ADD0065DA55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | C887EF891CE37FF10065DA55 /* EventMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventMonitor.swift; sourceTree = ""; }; 58 | C887EF8F1CE3831E0065DA55 /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; 59 | C887EF911CE384DB0065DA55 /* GankAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GankAPI.swift; sourceTree = ""; }; 60 | C89D55571CE59F510063763F /* SettingAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingAction.swift; sourceTree = ""; }; 61 | C8A1DB4F1CE714DA00EE8CD6 /* NSUserNotification+Gank.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSUserNotification+Gank.swift"; sourceTree = ""; }; 62 | C8C636BD1CE5EDC300801E00 /* GankSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GankSection.swift; sourceTree = ""; }; 63 | C8EB09711CE4D54100861B77 /* PopoverAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PopoverAction.swift; sourceTree = ""; }; 64 | C8EB098D1CE57A7C00861B77 /* OpenUrlAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenUrlAction.swift; sourceTree = ""; }; 65 | C8EB09901CE58A6A00861B77 /* GankButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GankButton.swift; sourceTree = ""; }; 66 | C8EB09911CE58A6A00861B77 /* GankLoadingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GankLoadingView.swift; sourceTree = ""; }; 67 | C8EB09921CE58A6A00861B77 /* GankItemCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GankItemCell.swift; sourceTree = ""; }; 68 | C8EB09931CE58A6A00861B77 /* GankScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GankScrollView.swift; sourceTree = ""; }; 69 | C8EB09941CE58A6A00861B77 /* GankSectionCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GankSectionCell.swift; sourceTree = ""; }; 70 | C8EB09951CE58A6A00861B77 /* GankSeenView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GankSeenView.swift; sourceTree = ""; }; 71 | C8EB099E1CE58ADB00861B77 /* NSColor+Gank.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSColor+Gank.swift"; sourceTree = ""; }; 72 | C8EB09A51CE58BAB00861B77 /* GankViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GankViewController.swift; sourceTree = ""; }; 73 | C8EB09A61CE58BAB00861B77 /* GankViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = GankViewController.xib; sourceTree = ""; }; 74 | /* End PBXFileReference section */ 75 | 76 | /* Begin PBXFrameworksBuildPhase section */ 77 | C887EF751CE37ADD0065DA55 /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | C84B06BF1CE6F86300EA2FC7 /* SwiftyTimer.framework in Frameworks */, 82 | C87D6BB61CE486F800AA31B6 /* Alamofire.framework in Frameworks */, 83 | C87D6BB81CE486F800AA31B6 /* SwiftyJSON.framework in Frameworks */, 84 | A0A1EB5BEA0EA66DDCFC3B7C /* Pods_Gank.framework in Frameworks */, 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | /* End PBXFrameworksBuildPhase section */ 89 | 90 | /* Begin PBXGroup section */ 91 | 106B207C3B987E904DE24CF3 /* Frameworks */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | C84B06BE1CE6F86300EA2FC7 /* SwiftyTimer.framework */, 95 | C87D6BB31CE486F800AA31B6 /* Alamofire.framework */, 96 | C87D6BB51CE486F800AA31B6 /* SwiftyJSON.framework */, 97 | B71DE5E34C80D7F11D29D874 /* Pods_Gank.framework */, 98 | ); 99 | name = Frameworks; 100 | sourceTree = ""; 101 | }; 102 | A5529BBF921C9FF210100FDD /* Pods */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 4F9AAFD7867AD56F0F15DDA2 /* Pods-Gank.debug.xcconfig */, 106 | 794D55073DB106AE6F7076E8 /* Pods-Gank.release.xcconfig */, 107 | ); 108 | name = Pods; 109 | sourceTree = ""; 110 | }; 111 | C87D6B9D1CE4728E00AA31B6 /* Models */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | C887EF911CE384DB0065DA55 /* GankAPI.swift */, 115 | C87D6BA21CE4731B00AA31B6 /* GankItem.swift */, 116 | C8C636BD1CE5EDC300801E00 /* GankSection.swift */, 117 | ); 118 | name = Models; 119 | sourceTree = ""; 120 | }; 121 | C87D6BA01CE472CF00AA31B6 /* Controllers */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | C8EB09A51CE58BAB00861B77 /* GankViewController.swift */, 125 | C8EB09A61CE58BAB00861B77 /* GankViewController.xib */, 126 | ); 127 | name = Controllers; 128 | sourceTree = ""; 129 | }; 130 | C87D6BA11CE472DC00AA31B6 /* Support */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 655427E51EEBC7B600708D12 /* screenshot4.png */, 134 | C83D57EA1CE633F000762FF1 /* screenshot3.png */, 135 | C83D57CA1CE6300400762FF1 /* screenshot2.png */, 136 | C83D57C81CE62E2300762FF1 /* LICENSE.txt */, 137 | C83D57C61CE62E1A00762FF1 /* screenshot.png */, 138 | C819FF651CE71E70004D569E /* README-EN.md */, 139 | C83D57C01CE6259000762FF1 /* README.md */, 140 | C887EF7D1CE37ADD0065DA55 /* Assets.xcassets */, 141 | C887EF8F1CE3831E0065DA55 /* MainMenu.xib */, 142 | C887EF821CE37ADD0065DA55 /* Info.plist */, 143 | ); 144 | name = Support; 145 | sourceTree = ""; 146 | }; 147 | C887EF6F1CE37ADD0065DA55 = { 148 | isa = PBXGroup; 149 | children = ( 150 | C887EF7A1CE37ADD0065DA55 /* Gank */, 151 | C887EF791CE37ADD0065DA55 /* Products */, 152 | A5529BBF921C9FF210100FDD /* Pods */, 153 | 106B207C3B987E904DE24CF3 /* Frameworks */, 154 | ); 155 | sourceTree = ""; 156 | usesTabs = 0; 157 | }; 158 | C887EF791CE37ADD0065DA55 /* Products */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | C887EF781CE37ADD0065DA55 /* Gank.app */, 162 | ); 163 | name = Products; 164 | sourceTree = ""; 165 | }; 166 | C887EF7A1CE37ADD0065DA55 /* Gank */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | C87D6BA11CE472DC00AA31B6 /* Support */, 170 | C8EB09701CE4D50300861B77 /* Actions */, 171 | C87D6B9D1CE4728E00AA31B6 /* Models */, 172 | C8EB098F1CE58A1800861B77 /* Views */, 173 | C87D6BA01CE472CF00AA31B6 /* Controllers */, 174 | C887EF7B1CE37ADD0065DA55 /* AppDelegate.swift */, 175 | ); 176 | path = Gank; 177 | sourceTree = ""; 178 | }; 179 | C8EB09701CE4D50300861B77 /* Actions */ = { 180 | isa = PBXGroup; 181 | children = ( 182 | C887EF891CE37FF10065DA55 /* EventMonitor.swift */, 183 | C8EB09711CE4D54100861B77 /* PopoverAction.swift */, 184 | C8EB098D1CE57A7C00861B77 /* OpenUrlAction.swift */, 185 | C89D55571CE59F510063763F /* SettingAction.swift */, 186 | ); 187 | name = Actions; 188 | sourceTree = ""; 189 | }; 190 | C8EB098F1CE58A1800861B77 /* Views */ = { 191 | isa = PBXGroup; 192 | children = ( 193 | C8EB099E1CE58ADB00861B77 /* NSColor+Gank.swift */, 194 | C8EB09901CE58A6A00861B77 /* GankButton.swift */, 195 | C8EB09911CE58A6A00861B77 /* GankLoadingView.swift */, 196 | C8EB09921CE58A6A00861B77 /* GankItemCell.swift */, 197 | C8EB09931CE58A6A00861B77 /* GankScrollView.swift */, 198 | C8EB09941CE58A6A00861B77 /* GankSectionCell.swift */, 199 | C8EB09951CE58A6A00861B77 /* GankSeenView.swift */, 200 | C8A1DB4F1CE714DA00EE8CD6 /* NSUserNotification+Gank.swift */, 201 | ); 202 | name = Views; 203 | sourceTree = ""; 204 | }; 205 | /* End PBXGroup section */ 206 | 207 | /* Begin PBXNativeTarget section */ 208 | C887EF771CE37ADD0065DA55 /* Gank */ = { 209 | isa = PBXNativeTarget; 210 | buildConfigurationList = C887EF851CE37ADD0065DA55 /* Build configuration list for PBXNativeTarget "Gank" */; 211 | buildPhases = ( 212 | 7BC71D198D290E6BFE14D67A /* [CP] Check Pods Manifest.lock */, 213 | C887EF741CE37ADD0065DA55 /* Sources */, 214 | C887EF751CE37ADD0065DA55 /* Frameworks */, 215 | C887EF761CE37ADD0065DA55 /* Resources */, 216 | FFBA0E0253CDDD57E5FE391E /* [CP] Embed Pods Frameworks */, 217 | 70E4634D0A49A86454191B68 /* [CP] Copy Pods Resources */, 218 | ); 219 | buildRules = ( 220 | ); 221 | dependencies = ( 222 | ); 223 | name = Gank; 224 | productName = Gank; 225 | productReference = C887EF781CE37ADD0065DA55 /* Gank.app */; 226 | productType = "com.apple.product-type.application"; 227 | }; 228 | /* End PBXNativeTarget section */ 229 | 230 | /* Begin PBXProject section */ 231 | C887EF701CE37ADD0065DA55 /* Project object */ = { 232 | isa = PBXProject; 233 | attributes = { 234 | LastSwiftUpdateCheck = 0730; 235 | LastUpgradeCheck = 0730; 236 | ORGANIZATIONNAME = "Tsinghua University"; 237 | TargetAttributes = { 238 | C887EF771CE37ADD0065DA55 = { 239 | CreatedOnToolsVersion = 7.3.1; 240 | DevelopmentTeam = MHWY925EE2; 241 | LastSwiftMigration = 0830; 242 | ProvisioningStyle = Automatic; 243 | }; 244 | }; 245 | }; 246 | buildConfigurationList = C887EF731CE37ADD0065DA55 /* Build configuration list for PBXProject "Gank" */; 247 | compatibilityVersion = "Xcode 3.2"; 248 | developmentRegion = English; 249 | hasScannedForEncodings = 0; 250 | knownRegions = ( 251 | en, 252 | Base, 253 | ); 254 | mainGroup = C887EF6F1CE37ADD0065DA55; 255 | productRefGroup = C887EF791CE37ADD0065DA55 /* Products */; 256 | projectDirPath = ""; 257 | projectRoot = ""; 258 | targets = ( 259 | C887EF771CE37ADD0065DA55 /* Gank */, 260 | ); 261 | }; 262 | /* End PBXProject section */ 263 | 264 | /* Begin PBXResourcesBuildPhase section */ 265 | C887EF761CE37ADD0065DA55 /* Resources */ = { 266 | isa = PBXResourcesBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | C887EF7E1CE37ADD0065DA55 /* Assets.xcassets in Resources */, 270 | 655427E61EEBC7B600708D12 /* screenshot4.png in Resources */, 271 | C8EB09A81CE58BAB00861B77 /* GankViewController.xib in Resources */, 272 | C887EF901CE3831E0065DA55 /* MainMenu.xib in Resources */, 273 | C83D57C91CE62E2300762FF1 /* LICENSE.txt in Resources */, 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | }; 277 | /* End PBXResourcesBuildPhase section */ 278 | 279 | /* Begin PBXShellScriptBuildPhase section */ 280 | 70E4634D0A49A86454191B68 /* [CP] Copy Pods Resources */ = { 281 | isa = PBXShellScriptBuildPhase; 282 | buildActionMask = 2147483647; 283 | files = ( 284 | ); 285 | inputPaths = ( 286 | ); 287 | name = "[CP] Copy Pods Resources"; 288 | outputPaths = ( 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | shellPath = /bin/sh; 292 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Gank/Pods-Gank-resources.sh\"\n"; 293 | showEnvVarsInLog = 0; 294 | }; 295 | 7BC71D198D290E6BFE14D67A /* [CP] Check Pods Manifest.lock */ = { 296 | isa = PBXShellScriptBuildPhase; 297 | buildActionMask = 2147483647; 298 | files = ( 299 | ); 300 | inputPaths = ( 301 | ); 302 | name = "[CP] Check Pods Manifest.lock"; 303 | outputPaths = ( 304 | ); 305 | runOnlyForDeploymentPostprocessing = 0; 306 | shellPath = /bin/sh; 307 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 308 | showEnvVarsInLog = 0; 309 | }; 310 | FFBA0E0253CDDD57E5FE391E /* [CP] Embed Pods Frameworks */ = { 311 | isa = PBXShellScriptBuildPhase; 312 | buildActionMask = 2147483647; 313 | files = ( 314 | ); 315 | inputPaths = ( 316 | ); 317 | name = "[CP] Embed Pods Frameworks"; 318 | outputPaths = ( 319 | ); 320 | runOnlyForDeploymentPostprocessing = 0; 321 | shellPath = /bin/sh; 322 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Gank/Pods-Gank-frameworks.sh\"\n"; 323 | showEnvVarsInLog = 0; 324 | }; 325 | /* End PBXShellScriptBuildPhase section */ 326 | 327 | /* Begin PBXSourcesBuildPhase section */ 328 | C887EF741CE37ADD0065DA55 /* Sources */ = { 329 | isa = PBXSourcesBuildPhase; 330 | buildActionMask = 2147483647; 331 | files = ( 332 | C8EB09961CE58A6A00861B77 /* GankButton.swift in Sources */, 333 | C8EB099B1CE58A6A00861B77 /* GankSeenView.swift in Sources */, 334 | C887EF7C1CE37ADD0065DA55 /* AppDelegate.swift in Sources */, 335 | C8EB098E1CE57A7C00861B77 /* OpenUrlAction.swift in Sources */, 336 | C8EB09971CE58A6A00861B77 /* GankLoadingView.swift in Sources */, 337 | C89D55581CE59F510063763F /* SettingAction.swift in Sources */, 338 | C8EB09A71CE58BAB00861B77 /* GankViewController.swift in Sources */, 339 | C8EB09721CE4D54100861B77 /* PopoverAction.swift in Sources */, 340 | C8A1DB501CE714DA00EE8CD6 /* NSUserNotification+Gank.swift in Sources */, 341 | C8EB09981CE58A6A00861B77 /* GankItemCell.swift in Sources */, 342 | C8C636BE1CE5EDC300801E00 /* GankSection.swift in Sources */, 343 | C887EF8A1CE37FF10065DA55 /* EventMonitor.swift in Sources */, 344 | C87D6BA31CE4731B00AA31B6 /* GankItem.swift in Sources */, 345 | C887EF921CE384DB0065DA55 /* GankAPI.swift in Sources */, 346 | C8EB099A1CE58A6A00861B77 /* GankSectionCell.swift in Sources */, 347 | C8EB09991CE58A6A00861B77 /* GankScrollView.swift in Sources */, 348 | C8EB09A21CE58ADB00861B77 /* NSColor+Gank.swift in Sources */, 349 | ); 350 | runOnlyForDeploymentPostprocessing = 0; 351 | }; 352 | /* End PBXSourcesBuildPhase section */ 353 | 354 | /* Begin XCBuildConfiguration section */ 355 | C887EF831CE37ADD0065DA55 /* Debug */ = { 356 | isa = XCBuildConfiguration; 357 | buildSettings = { 358 | ALWAYS_SEARCH_USER_PATHS = NO; 359 | CLANG_ANALYZER_NONNULL = YES; 360 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 361 | CLANG_CXX_LIBRARY = "libc++"; 362 | CLANG_ENABLE_MODULES = YES; 363 | CLANG_ENABLE_OBJC_ARC = YES; 364 | CLANG_WARN_BOOL_CONVERSION = YES; 365 | CLANG_WARN_CONSTANT_CONVERSION = YES; 366 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 367 | CLANG_WARN_EMPTY_BODY = YES; 368 | CLANG_WARN_ENUM_CONVERSION = YES; 369 | CLANG_WARN_INT_CONVERSION = YES; 370 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 371 | CLANG_WARN_UNREACHABLE_CODE = YES; 372 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 373 | CODE_SIGN_IDENTITY = "-"; 374 | COPY_PHASE_STRIP = NO; 375 | DEBUG_INFORMATION_FORMAT = dwarf; 376 | ENABLE_STRICT_OBJC_MSGSEND = YES; 377 | ENABLE_TESTABILITY = YES; 378 | GCC_C_LANGUAGE_STANDARD = gnu99; 379 | GCC_DYNAMIC_NO_PIC = NO; 380 | GCC_NO_COMMON_BLOCKS = YES; 381 | GCC_OPTIMIZATION_LEVEL = 0; 382 | GCC_PREPROCESSOR_DEFINITIONS = ( 383 | "DEBUG=1", 384 | "$(inherited)", 385 | ); 386 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 387 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 388 | GCC_WARN_UNDECLARED_SELECTOR = YES; 389 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 390 | GCC_WARN_UNUSED_FUNCTION = YES; 391 | GCC_WARN_UNUSED_VARIABLE = YES; 392 | MACOSX_DEPLOYMENT_TARGET = 10.11; 393 | MTL_ENABLE_DEBUG_INFO = YES; 394 | ONLY_ACTIVE_ARCH = YES; 395 | SDKROOT = macosx; 396 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 397 | }; 398 | name = Debug; 399 | }; 400 | C887EF841CE37ADD0065DA55 /* Release */ = { 401 | isa = XCBuildConfiguration; 402 | buildSettings = { 403 | ALWAYS_SEARCH_USER_PATHS = NO; 404 | CLANG_ANALYZER_NONNULL = YES; 405 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 406 | CLANG_CXX_LIBRARY = "libc++"; 407 | CLANG_ENABLE_MODULES = YES; 408 | CLANG_ENABLE_OBJC_ARC = YES; 409 | CLANG_WARN_BOOL_CONVERSION = YES; 410 | CLANG_WARN_CONSTANT_CONVERSION = YES; 411 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 412 | CLANG_WARN_EMPTY_BODY = YES; 413 | CLANG_WARN_ENUM_CONVERSION = YES; 414 | CLANG_WARN_INT_CONVERSION = YES; 415 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 416 | CLANG_WARN_UNREACHABLE_CODE = YES; 417 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 418 | CODE_SIGN_IDENTITY = "-"; 419 | COPY_PHASE_STRIP = NO; 420 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 421 | ENABLE_NS_ASSERTIONS = NO; 422 | ENABLE_STRICT_OBJC_MSGSEND = YES; 423 | GCC_C_LANGUAGE_STANDARD = gnu99; 424 | GCC_NO_COMMON_BLOCKS = YES; 425 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 426 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 427 | GCC_WARN_UNDECLARED_SELECTOR = YES; 428 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 429 | GCC_WARN_UNUSED_FUNCTION = YES; 430 | GCC_WARN_UNUSED_VARIABLE = YES; 431 | MACOSX_DEPLOYMENT_TARGET = 10.11; 432 | MTL_ENABLE_DEBUG_INFO = NO; 433 | SDKROOT = macosx; 434 | }; 435 | name = Release; 436 | }; 437 | C887EF861CE37ADD0065DA55 /* Debug */ = { 438 | isa = XCBuildConfiguration; 439 | baseConfigurationReference = 4F9AAFD7867AD56F0F15DDA2 /* Pods-Gank.debug.xcconfig */; 440 | buildSettings = { 441 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 442 | CODE_SIGN_IDENTITY = "Mac Developer"; 443 | COMBINE_HIDPI_IMAGES = YES; 444 | DEVELOPMENT_TEAM = MHWY925EE2; 445 | INFOPLIST_FILE = Gank/Info.plist; 446 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 447 | MACOSX_DEPLOYMENT_TARGET = 10.10; 448 | PRODUCT_BUNDLE_IDENTIFIER = cn.edu.tsinghua.Gank; 449 | PRODUCT_NAME = "$(TARGET_NAME)"; 450 | PROVISIONING_PROFILE_SPECIFIER = ""; 451 | SWIFT_VERSION = 3.0; 452 | }; 453 | name = Debug; 454 | }; 455 | C887EF871CE37ADD0065DA55 /* Release */ = { 456 | isa = XCBuildConfiguration; 457 | baseConfigurationReference = 794D55073DB106AE6F7076E8 /* Pods-Gank.release.xcconfig */; 458 | buildSettings = { 459 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 460 | CODE_SIGN_IDENTITY = "Mac Developer"; 461 | COMBINE_HIDPI_IMAGES = YES; 462 | DEVELOPMENT_TEAM = MHWY925EE2; 463 | INFOPLIST_FILE = Gank/Info.plist; 464 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 465 | MACOSX_DEPLOYMENT_TARGET = 10.10; 466 | PRODUCT_BUNDLE_IDENTIFIER = cn.edu.tsinghua.Gank; 467 | PRODUCT_NAME = "$(TARGET_NAME)"; 468 | PROVISIONING_PROFILE_SPECIFIER = ""; 469 | SWIFT_VERSION = 3.0; 470 | }; 471 | name = Release; 472 | }; 473 | /* End XCBuildConfiguration section */ 474 | 475 | /* Begin XCConfigurationList section */ 476 | C887EF731CE37ADD0065DA55 /* Build configuration list for PBXProject "Gank" */ = { 477 | isa = XCConfigurationList; 478 | buildConfigurations = ( 479 | C887EF831CE37ADD0065DA55 /* Debug */, 480 | C887EF841CE37ADD0065DA55 /* Release */, 481 | ); 482 | defaultConfigurationIsVisible = 0; 483 | defaultConfigurationName = Release; 484 | }; 485 | C887EF851CE37ADD0065DA55 /* Build configuration list for PBXNativeTarget "Gank" */ = { 486 | isa = XCConfigurationList; 487 | buildConfigurations = ( 488 | C887EF861CE37ADD0065DA55 /* Debug */, 489 | C887EF871CE37ADD0065DA55 /* Release */, 490 | ); 491 | defaultConfigurationIsVisible = 0; 492 | defaultConfigurationName = Release; 493 | }; 494 | /* End XCConfigurationList section */ 495 | }; 496 | rootObject = C887EF701CE37ADD0065DA55 /* Project object */; 497 | } 498 | -------------------------------------------------------------------------------- /Gank.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Gank.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Gank/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Gank 4 | // 5 | // Created by hujiawei on 2016/5/11. 6 | // Copyright © 2016年 Tsinghua University. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | // @IBOutlet weak var window: NSWindow! //没有window了,整个应用只有状态栏中的那个弹出窗口 15 | 16 | let statusItem = NSStatusBar.system().statusItem(withLength: NSVariableStatusItemLength) // 状态栏的应用按钮,或者用 -2 17 | let popover = NSPopover() // 点击状态栏应用按钮弹出的主界面窗口 18 | var refreshTimer: Timer? // 定时器用于刷新数据 19 | 20 | // 应用启动之后 21 | func applicationDidFinishLaunching(_ aNotification: Notification) { 22 | // 设置状态栏的应用图标 23 | if let button = statusItem.button { 24 | button.image = NSImage(named: "icon-statusbar") 25 | button.action = #selector(togglePopover) // AppDelegate.togglePopover(_:) 26 | } 27 | 28 | // 设置弹出的主界面 29 | popover.contentViewController = GankViewController(nibName: "GankViewController", bundle: nil) 30 | popover.behavior = .transient 31 | popover.animates = false 32 | popover.appearance = NSAppearance(named: NSAppearanceNameAqua) // since os x 10.9 33 | 34 | // 设置定时器每隔10分钟发送数据请求 10.minutes 35 | refreshTimer = Timer.every(10.minutes) { // seconds 36 | NotificationCenter.default.post(name: NSNotification.Name(rawValue: "ReloadSections"), object: nil) 37 | } 38 | } 39 | 40 | func applicationWillTerminate(_ aNotification: Notification) { 41 | refreshTimer?.invalidate() // 关闭定时器 42 | refreshTimer = nil 43 | if let controller = popover.contentViewController { // 取消监听 44 | NotificationCenter.default.removeObserver(controller) 45 | } 46 | } 47 | 48 | // 打开或者关闭弹出窗口 49 | func togglePopover() { 50 | PopoverAction.toggle() 51 | } 52 | 53 | // 打开github进入应用源码 54 | func openGithubLink() { 55 | OpenUrlAction.perform(withPath: "https://github.com/hujiaweibujidao/Gank-for-Mac") 56 | } 57 | 58 | // 打开我的博客 59 | func openBlogLink() { 60 | OpenUrlAction.perform(withPath: "https://javayhu.me/") 61 | } 62 | 63 | // 退出应用 64 | func quit() { 65 | NSApplication.shared().terminate(self) 66 | } 67 | 68 | } 69 | 70 | -------------------------------------------------------------------------------- /Gank/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "icon_1024x1024-16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "icon_1024x1024-32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "icon_1024x1024-33.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "icon_1024x1024-64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "icon_1024x1024-128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "icon_1024x1024-256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "icon_1024x1024-257.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "icon_1024x1024-512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "icon_1024x1024-513.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "icon_1024x1024-1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-1024.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-128.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-16.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-256.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-257.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-257.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-32.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-33.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-512.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-513.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-513.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/AppIcon.appiconset/icon_1024x1024-64.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-android.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "android@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-android.imageset/android@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/icon-android.imageset/android@2x.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-gank.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "gank@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-gank.imageset/gank@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/icon-gank.imageset/gank@2x.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-html5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "html5@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-html5.imageset/html5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/icon-html5.imageset/html5@2x.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "picasa@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-image.imageset/picasa@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/icon-image.imageset/picasa@2x.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-ios.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "apple@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-ios.imageset/apple@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/icon-ios.imageset/apple@2x.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-reload.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "reload.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-reload.imageset/reload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/icon-reload.imageset/reload.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-settings.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "settings.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-settings.imageset/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/icon-settings.imageset/settings.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-slack.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "slack@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-slack.imageset/slack@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/icon-slack.imageset/slack@2x.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-statusbar.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "google@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-statusbar.imageset/google@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/icon-statusbar.imageset/google@2x.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-video.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "vimeo@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Gank/Assets.xcassets/icon-video.imageset/vimeo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/Assets.xcassets/icon-video.imageset/vimeo@2x.png -------------------------------------------------------------------------------- /Gank/EventMonitor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventMonitor.swift 3 | // Gank 4 | // 5 | // Created by hujiawei on 2016/5/11. 6 | // Copyright © 2016年 Tsinghua University. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | //监听用户的点击事件,当用户在弹出窗口外点击时要关闭窗口 12 | class EventMonitor { 13 | 14 | fileprivate var monitor: AnyObject? 15 | fileprivate let mask: NSEventMask 16 | fileprivate let handler: (NSEvent?) -> () // 回调函数 17 | 18 | init(mask: NSEventMask, handler: @escaping (NSEvent?) -> ()) { 19 | self.mask = mask 20 | self.handler = handler 21 | } 22 | 23 | deinit { 24 | stop() 25 | } 26 | 27 | func start() { 28 | monitor = NSEvent.addGlobalMonitorForEvents(matching: mask, handler: handler) as AnyObject 29 | } 30 | 31 | func stop() { 32 | if monitor != nil { 33 | NSEvent.removeMonitor(monitor!) 34 | monitor = nil 35 | } 36 | } 37 | 38 | } 39 | 40 | -------------------------------------------------------------------------------- /Gank/GankAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataFetcher.swift 3 | // Gank 4 | // 5 | // Created by hujiawei on 2016/5/11. 6 | // Copyright © 2016年 Tsinghua University. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | import SwiftyJSON 12 | 13 | //干货数据访问类 14 | class GankAPI { 15 | 16 | typealias CallBack = (AnyObject?) -> Void 17 | 18 | let API_TODAY = "https://gank.io/api/day/" // 获取指定日期干货数据的URL前缀 19 | let API_DATES = "https://gank.io/api/day/history" // 获取所有发过日报的日期列表 20 | 21 | // 加载指定日期的数据 22 | func loadDateGanks(_ section: GankSection, callback: @escaping CallBack) { 23 | print("Load Date Ganks") 24 | let url = API_TODAY + section.urlSuffix()! 25 | 26 | Alamofire.request(url, method: .get).responseJSON { response in 27 | // response.result是一个枚举,如果是Success的话有个关联值Value,如果是Failure的话有个关联值Error 28 | switch response.result { 29 | case .failure(let error): 30 | print("Load Date Ganks Failed") 31 | // print(error) 32 | callback(error as AnyObject) // 如果失败的话将错误error传给回调函数 33 | case .success: 34 | print("Load Date Ganks Success") 35 | 36 | var itemList = [GankItem]() 37 | if let value = response.result.value { 38 | let json = JSON(value), results = json["results"], categories = json["category"] 39 | for category in categories.arrayValue { 40 | if let items = results[category.stringValue].array { 41 | itemList += items.map { return GankItem.parse($0) } 42 | } 43 | } 44 | } 45 | callback(itemList as AnyObject) // 如果成功的话将数据列表传给回调函数 46 | } 47 | } // responseJSON最后一个参数是闭包 completionHandler: Response -> Void 48 | } 49 | 50 | // 加载所有发过日报的日期列表 51 | func loadAllDates(_ callback: @escaping CallBack) { 52 | print("Load All Dates") 53 | 54 | Alamofire.request(API_DATES, method: .get).responseJSON { response in 55 | switch response.result { 56 | case .failure(let error): 57 | print("Load All Dates Failed") 58 | // print(error) 59 | callback(error as AnyObject) 60 | case .success: 61 | print("Load All Dates Success") 62 | 63 | var sections = [GankSection]() 64 | if let value = response.result.value { 65 | let json = JSON(value), results = json["results"].arrayValue 66 | for date in results { 67 | sections.append(GankSection(date: date.stringValue)) 68 | } 69 | } 70 | callback(sections as AnyObject) 71 | } 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /Gank/GankButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GankButton.swift <- PHButton.swift 3 | // Gank 4 | // 5 | // Created by Vlado on 3/24/16. 6 | // Copyright © 2016 ProductHunt. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | // 按钮,鼠标进入按钮区域会显示为手指状态 12 | class GankButton: NSButton { 13 | 14 | fileprivate let cursor = NSCursor.pointingHand() // 手指鼠标 15 | fileprivate var normalStateImage: NSImage? // 正常状态下的图片 off state 16 | fileprivate var highlightedStateImage: NSImage? // 高亮状态下的图片 on state 17 | fileprivate var trackingArea: NSTrackingArea? // 在指定区域内鼠标进入的时候变成手指 18 | 19 | override func resetCursorRects() { 20 | addCursorRect(bounds, cursor: cursor) 21 | cursor.set() 22 | } 23 | 24 | override init(frame frameRect: NSRect) { 25 | super.init(frame: frameRect) 26 | commonInit() 27 | } 28 | 29 | required init?(coder: NSCoder) { 30 | super.init(coder: coder) 31 | commonInit() 32 | } 33 | 34 | override func awakeFromNib() { 35 | super.awakeFromNib() 36 | commonInit() 37 | } 38 | 39 | func commonInit() { 40 | } 41 | 42 | func setImages(_ normalImage: String, highlitedImage: String) { 43 | self.setButtonType(.momentaryChange) 44 | 45 | normalStateImage = NSImage(named: normalImage) 46 | highlightedStateImage = NSImage(named: highlitedImage) 47 | } 48 | 49 | func resetTrackingArea() { 50 | trackingArea = nil 51 | 52 | if let normalStateImage = normalStateImage { 53 | image = normalStateImage 54 | } 55 | } 56 | 57 | fileprivate func createTrackingAreaIfNeeded() { 58 | if trackingArea == nil { 59 | trackingArea = NSTrackingArea(rect: CGRect.zero, options: [.inVisibleRect, .mouseEnteredAndExited, .activeAlways], owner: self, userInfo: nil) 60 | } 61 | } 62 | 63 | override func updateTrackingAreas() { 64 | super.updateTrackingAreas() 65 | 66 | createTrackingAreaIfNeeded() 67 | 68 | if !trackingAreas.contains(trackingArea!) { 69 | addTrackingArea(trackingArea!) 70 | } 71 | } 72 | 73 | override func mouseEntered(with theEvent: NSEvent) { 74 | if let highlightedImage = highlightedStateImage { 75 | image = highlightedImage 76 | } 77 | } 78 | 79 | override func mouseExited(with theEvent: NSEvent) { 80 | if let normalStateImage = normalStateImage { 81 | image = normalStateImage 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Gank/GankItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GankItem.swift 3 | // Gank 4 | // 5 | // Created by hujiawei on 2016/5/12. 6 | // Copyright © 2016年 Tsinghua University. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyJSON 11 | 12 | //干货,只是解析了返回数据中的需要字段 13 | class GankItem { 14 | 15 | var type: String // 分类 16 | var url: String // 对应网址 17 | var desc: String // 描述 18 | var who: String // 推荐人 19 | var date: String // 发布日期 20 | 21 | init(type: String, url: String, desc: String, who: String, date: String) { 22 | self.type = type 23 | self.url = url 24 | self.desc = desc 25 | self.who = who 26 | self.date = date 27 | } 28 | 29 | // 将返回的json串中的一个item转换成GankItem,时间 2016-05-13T11:08:37.42Z -> 2016-05-13 30 | class func parse(_ item: JSON) -> GankItem { 31 | let time = item["publishedAt"].stringValue 32 | return GankItem(type: item["type"].stringValue, url: item["url"].stringValue, 33 | desc: item["desc"].stringValue, who: item["who"].stringValue, date: time.substring(to: time.index(time.startIndex, offsetBy: 10))) 34 | } 35 | 36 | } 37 | 38 | //扩展的用于界面显示的方法和属性 39 | extension GankItem { 40 | 41 | var typeImageMap: [String: String] { // 干货类型和对应的图片名称 42 | return ["iOS": "icon-ios", "Android": "icon-android", "前端": "icon-html5", 43 | "瞎推荐": "icon-slack", "福利": "icon-image", "休息视频": "icon-video"] 44 | } 45 | 46 | var textWho: String { 47 | return self.who == "" ? "who" : self.who 48 | } 49 | 50 | var textDesc: String { 51 | return self.desc 52 | } 53 | 54 | var imageType: String { 55 | return self.typeImageMap[self.type] ?? "icon-gank" 56 | } 57 | 58 | var visitUrl: String { // 点击某个item需要访问的url 59 | if self.imageType != "icon-image" && self.imageType != "icon-video" { 60 | return self.url 61 | } else { // 图片和视频特殊处理,跳转到当天的干货数据页面 62 | return "https://gank.io/" + self.urlSuffix() 63 | } 64 | } 65 | 66 | // 构造成每日数据所需的结构,将“-”替换成“/”即可 67 | func urlSuffix() -> String { 68 | return date.replacingOccurrences(of: "-", with: "/") 69 | } 70 | } 71 | 72 | //使用String(gankItem)可以得到该string,用于打印查看信息 73 | extension GankItem: CustomStringConvertible { 74 | var description: String { 75 | return "{type=\(type), desc=\(desc), who=\(who), url=\(url)}" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Gank/GankItemCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GankPostCell.swift <- PHPostCell.swift 3 | // Gank 4 | // 5 | // Created by Vlado on 3/15/16. 6 | // Copyright © 2016 ProductHunt. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | //表格的显示干货内容的单元格组件,标示符是 postCellIdentifier 12 | class GankItemCell: NSTableCellView { 13 | 14 | @IBOutlet weak var typeImageView: NSImageView! // 干货类型图片 15 | @IBOutlet weak var whoLabel: NSTextField! // 干货推荐人 16 | @IBOutlet weak var descLabel: NSTextField! // 干货描述 17 | @IBOutlet weak var seenView: GankSeenView! // 是否已经看过,暂时没用 18 | 19 | fileprivate var item: GankItem? // 干货 20 | 21 | fileprivate let cursor = NSCursor.pointingHand() 22 | fileprivate var trackingArea: NSTrackingArea? 23 | fileprivate var mouseInside = false { 24 | didSet { 25 | updateUI() 26 | } 27 | } 28 | 29 | // 创建一个table cell view 30 | class func view(_ tableView: NSTableView, owner: AnyObject?, subject: AnyObject?) -> NSView { 31 | let view = tableView.make(withIdentifier: "postCellIdentifier", owner: owner) as! GankItemCell 32 | 33 | if let item = subject as? GankItem { 34 | view.setItem(item) 35 | } 36 | 37 | return view 38 | } 39 | 40 | override func awakeFromNib() { 41 | super.awakeFromNib() 42 | commonInit() 43 | } 44 | 45 | fileprivate func commonInit() { 46 | wantsLayer = true 47 | 48 | typeImageView.wantsLayer = true 49 | typeImageView.layer?.masksToBounds = true 50 | typeImageView.layer?.cornerRadius = 3 51 | } 52 | 53 | override func prepareForReuse() { 54 | super.prepareForReuse() 55 | trackingArea = nil 56 | mouseInside = false 57 | } 58 | 59 | override func resetCursorRects() { 60 | addCursorRect(bounds, cursor: cursor) 61 | cursor.set() 62 | } 63 | 64 | fileprivate func setItem(_ item: GankItem?) { 65 | guard let item = item else { 66 | return 67 | } 68 | 69 | self.item = item 70 | 71 | updateUI() 72 | } 73 | 74 | fileprivate func updateUI() { 75 | guard let item = item else { 76 | return 77 | } 78 | 79 | layer?.backgroundColor = mouseInside ? NSColor.gankHighlightColor().cgColor : NSColor.gankWhiteColor().cgColor 80 | 81 | seenView.isHidden = true 82 | 83 | whoLabel.stringValue = item.textWho 84 | descLabel.stringValue = item.textDesc 85 | typeImageView.image = NSImage(named: item.imageType) 86 | } 87 | 88 | fileprivate func createTrackingAreaIfNeeded() { 89 | if trackingArea == nil { 90 | trackingArea = NSTrackingArea(rect: CGRect.zero, options: [NSTrackingAreaOptions.inVisibleRect, NSTrackingAreaOptions.mouseEnteredAndExited, NSTrackingAreaOptions.activeAlways], owner: self, userInfo: nil) 91 | } 92 | } 93 | 94 | override func updateTrackingAreas() { 95 | super.updateTrackingAreas() 96 | 97 | createTrackingAreaIfNeeded() 98 | 99 | if !trackingAreas.contains(trackingArea!) { 100 | addTrackingArea(trackingArea!) 101 | } 102 | } 103 | 104 | override func mouseEntered(with theEvent: NSEvent) { 105 | mouseInside = true 106 | } 107 | 108 | override func mouseExited(with theEvent: NSEvent) { 109 | mouseInside = false 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Gank/GankLoadingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GankLoadingView.swift <- PHLoadingView.swift 3 | // Gank 4 | // 5 | // Created by Vlado on 3/17/16. 6 | // Copyright © 2016 ProductHunt. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | //加载界面的状态,分别是 正在加载,出现错误,内容为空,空闲状态 12 | enum LoadingState { 13 | case loading, error, empty, idle 14 | } 15 | 16 | //加载界面 17 | class GankLoadingView: NSView { 18 | 19 | @IBOutlet weak var loadingIndicator: NSProgressIndicator! // 进度圈 20 | @IBOutlet weak var loadingLabel: NSTextField! // 提示信息 21 | @IBOutlet weak var reload: GankButton! // 重新加载按钮 22 | 23 | var currentState: LoadingState? // 当前状态 24 | 25 | override func awakeFromNib() { 26 | super.awakeFromNib() 27 | 28 | wantsLayer = true 29 | layer?.backgroundColor = NSColor.gankWhiteColor().cgColor 30 | } 31 | 32 | // 显示某个状态 33 | func showState(_ state: LoadingState) { 34 | switch state { 35 | case .loading: 36 | isHidden = false 37 | reload.isHidden = true 38 | loadingIndicator.isHidden = false 39 | loadingIndicator.startAnimation(nil) 40 | loadingLabel.stringValue = "客官,请稍等..." 41 | currentState = .loading 42 | 43 | case .error: 44 | isHidden = false 45 | reload.isHidden = false 46 | loadingIndicator.isHidden = true 47 | loadingIndicator.stopAnimation(nil) 48 | loadingLabel.stringValue = "客官,出错啦!!!" 49 | currentState = .error 50 | 51 | case .empty: 52 | isHidden = false 53 | reload.isHidden = false 54 | loadingIndicator.isHidden = true 55 | loadingIndicator.stopAnimation(nil) 56 | loadingLabel.stringValue = "客官,没货啦..." 57 | currentState = .empty 58 | 59 | case .idle: 60 | isHidden = true 61 | loadingIndicator.stopAnimation(nil) 62 | currentState = .idle 63 | } 64 | } 65 | 66 | @IBAction func toggleReloadButton(_ sender: NSView) { 67 | showState(.loading) 68 | 69 | // 发送加载数据的请求 70 | NotificationCenter.default.post(name: Notification.Name(rawValue: "Reload"), object: nil) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Gank/GankScrollView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GankScrollView.swift <- PHTableView.swift 3 | // Gank 4 | // 5 | // Created by Vlado on 3/17/16. 6 | // Copyright © 2016 ProductHunt. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | //滚动组件 12 | class GankScrollView: NSScrollView { 13 | 14 | override func draw(_ dirtyRect: NSRect) { 15 | super.draw(dirtyRect) 16 | 17 | // 下面的代码在滚动组件的底部加上了1像素的横线 18 | let layer = CALayer() 19 | layer.borderColor = NSColor.windowBackgroundColor.cgColor 20 | layer.borderWidth = 1 21 | layer.frame = NSRect(x: 0, y: dirtyRect.size.height - 1, width: dirtyRect.size.width, height: 1) 22 | self.layer?.addSublayer(layer) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Gank/GankSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GankSection.swift 3 | // Gank 4 | // 5 | // Created by hujiawei on 2016/5/13. 6 | // Copyright © 2016年 Tsinghua University. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | //干货的日报 12 | class GankSection { 13 | 14 | var date: String? // 日期,从http://gank.io/api/day/history接口获取的原始字符串,例如2016-05-13 15 | var items: [GankItem]? // 数据 16 | 17 | init(date: String?, items: [GankItem]?) { 18 | self.date = date 19 | self.items = items 20 | } 21 | 22 | convenience init(date: String?) { 23 | self.init(date: date, items: nil) 24 | } 25 | 26 | } 27 | 28 | extension GankSection { 29 | // 构造成每日数据所需的结构,将“-”替换成“/”即可 30 | func urlSuffix() -> String? { 31 | if let date = self.date { 32 | return date.replacingOccurrences(of: "-", with: "/") 33 | } 34 | return nil 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Gank/GankSectionCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GankSectionCell.swift <- PHSectionCell.swift 3 | // Gank 4 | // 5 | // Created by Vlado on 3/16/16. 6 | // Copyright © 2016 ProductHunt. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | //表格的显示时间的区域单元格组件,标示符是 sectionCellIdentifier 12 | class GankSectionCell: NSTableCellView { 13 | 14 | class func view(_ tableView: NSTableView, owner: AnyObject?, subject: AnyObject?) -> NSView? { 15 | guard let section = subject as? String else { 16 | return nil 17 | } 18 | 19 | let view = tableView.make(withIdentifier: "sectionCellIdentifier", owner: owner) as! GankSectionCell 20 | view.textField?.stringValue = section 21 | return view 22 | } 23 | 24 | override func awakeFromNib() { 25 | super.awakeFromNib() 26 | 27 | wantsLayer = true 28 | layer?.backgroundColor = NSColor.gankWhiteColor().cgColor 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Gank/GankSeenView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GankSectionCell.swift <- PHSeenView.swift 3 | // Gank 4 | // 5 | // Created by Vlado on 3/17/16. 6 | // Copyright © 2016 ProductHunt. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | //表示没看过的小圆点,暂时没有利用 12 | class GankSeenView: NSView { 13 | 14 | override func awakeFromNib() { 15 | super.awakeFromNib() 16 | 17 | wantsLayer = true 18 | layer?.backgroundColor = NSColor.gankOrangeColor().cgColor 19 | } 20 | 21 | override func draw(_ dirtyRect: NSRect) { 22 | super.draw(dirtyRect) 23 | 24 | layer?.cornerRadius = dirtyRect.size.height / 2 // 小圆点 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Gank/GankViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GankViewController.swift 3 | // Gank 4 | // 5 | // Created by Vlado on 3/14/16. 6 | // Copyright © 2016 ProductHunt. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import SwiftyTimer 11 | 12 | //主界面窗口 13 | class GankViewController: NSViewController, NSUserNotificationCenterDelegate { 14 | 15 | @IBOutlet weak var tableView: NSTableView! // 表格组件 16 | @IBOutlet weak var loadingView: GankLoadingView! // 加载界面 17 | @IBOutlet weak var lastUpdatedLabel: NSTextField! // 上次更新时间 18 | @IBOutlet weak var homeButton: GankButton! // 首页按钮 19 | @IBOutlet weak var settingsButton: GankButton! // 设置按钮 20 | 21 | var currentDate = 0 // 当前需要加载的日报的编号 22 | var sections = [GankSection]() // 干货日报 23 | var items = [AnyObject]() // 数据列表 24 | let gankAPI = GankAPI() // 干货API工具类 25 | let format = DateFormatter() // 格式化更新时间 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | 30 | view.wantsLayer = true 31 | view.layer?.backgroundColor = NSColor.gankWhiteColor().cgColor 32 | 33 | format.dateFormat = "yyyy-MM-dd HH:mm:ss" // 更新时间的显示格式 34 | // tableView.intercellSpacing = NSSize.zero // 列表单元格间隔为0 35 | lastUpdatedLabel.stringValue = "" // 初始时为空字符串 36 | loadingView.showState(.loading) // 进入之后首先显示加载界面 37 | 38 | // 关于发送系统通知 39 | NSUserNotificationCenter.default.delegate = self 40 | 41 | // 注册监听,前者是用于加载某日的数据,后者是用于加载日期列表 42 | NotificationCenter.default.addObserver(self, selector: #selector(reloadData), name: NSNotification.Name(rawValue: "Reload"), object: nil) 43 | NotificationCenter.default.addObserver(self, selector: #selector(reloadSections), name: NSNotification.Name(rawValue: "ReloadSections"), object: nil) 44 | 45 | NotificationCenter.default.post(name: Notification.Name(rawValue: "Reload"), object: nil) // 发送加载数据的请求 46 | } 47 | 48 | // 加载日期列表 49 | func reloadSections() { 50 | gankAPI.loadAllDates { 51 | [unowned self] data in 52 | 53 | if data is NSError { 54 | return self.loadingView.showState(.error) 55 | } 56 | 57 | let sectionList = data as! [GankSection] 58 | if sectionList.isEmpty { // 如果sections是空那就是没有数据,当然这不太可能发生,这里是为了避免请求的死循环 59 | self.loadingView.showState(.empty) // 这里可以不用考虑之前的sections是什么 60 | } else { // 如果sectionList不为空,那么就表示sections中有数据 61 | if self.sections.count == 0 { // sections之前为空,设置为加载到的数据列表,并重新请求加载数据(加载日期列表中第一个日期的数据) 62 | self.sections = sectionList 63 | // self.sections.removeFirst() // 测试使用 64 | NotificationCenter.default.post(name: Notification.Name(rawValue: "Reload"), object: nil) 65 | } else if self.sections.count > 0 && self.sections.count < sectionList.count { // sections之前不为空,但是却有最新的数据来了 66 | self.sections = sectionList 67 | self.currentDate = 0 68 | self.items = [] // 为了简便,最新的数据来了就清空原来的列表数据,加载当前最新的数据,此时不用更新界面,等数据加载到了自然更新 69 | NotificationCenter.default.post(name: Notification.Name(rawValue: "Reload"), object: nil) 70 | self.sendNotification() 71 | } // 另一种情况是两次请求的数据没有变化,那么就不做任何处理 72 | } 73 | } 74 | } 75 | 76 | // 加载某日的数据,每次数据加载的请求发送之后这个方法就会被触发 77 | func reloadData() { 78 | if self.sections.count > 0 { // 如果日期列表不为空就加载当前已经加载到的日期对应的数据 79 | gankAPI.loadDateGanks(sections[currentDate]) { 80 | [unowned self] data in 81 | 82 | if data is NSError { 83 | return self.loadingView.showState(.error) 84 | } 85 | 86 | let itemList = data as! [GankItem] 87 | print("Reloaded Data Ganks \(self.sections[self.currentDate].date!)") 88 | self.items.append(self.sections[self.currentDate].date! as AnyObject) 89 | itemList.forEach { self.items.append($0) } 90 | self.updateUI() 91 | self.loadingView.showState(self.items.count > 0 ? .idle : .empty) 92 | self.currentDate += 1 93 | } 94 | } else { // 如果日期列表为空就先加载日期列表sections 95 | reloadSections() 96 | } 97 | } 98 | 99 | // NSUserNotificationCenterDelegate 100 | // http://stackoverflow.com/questions/11814903/send-notification-to-mountain-lion-notification-center 101 | func userNotificationCenter(_ center: NSUserNotificationCenter, shouldPresent notification: NSUserNotification) -> Bool { 102 | return true 103 | } 104 | 105 | // 发送系统通知 106 | func sendNotification() { 107 | let notification = NSUserNotification() 108 | notification.title = "干货集中营" 109 | notification.informativeText = "客官,有新的干货啦,快点趁热吃了吧!" 110 | // notification.contentImage = NSImage(named: "AppIcon") // 并非用来设置通知的logo 111 | notification.soundName = NSUserNotificationDefaultSoundName 112 | NSUserNotificationCenter.default.deliver(notification) 113 | } 114 | 115 | // 刷新界面 116 | func updateUI() { 117 | tableView.reloadData() 118 | lastUpdatedLabel.stringValue = "更新时间: \(format.string(from: Date()))" 119 | } 120 | 121 | // MARK: Actions 122 | @IBAction func toggleGankButton(_ sender: AnyObject) { 123 | OpenUrlAction.perform(withPath: "https://gank.io/", closeAfterLaunch: true) 124 | } 125 | 126 | @IBAction func toggleSettingsButton(_ sender: NSView) { 127 | SettingAction.makeSettingMenu(sender) 128 | } 129 | } 130 | 131 | // 列表的数据源 132 | extension GankViewController: NSTableViewDataSource { 133 | 134 | func numberOfRows(in aTableView: NSTableView) -> Int { 135 | return items.count 136 | } 137 | 138 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { 139 | loadOlderIfNeeded(row) // 判断是否需要加载更早的数据 140 | return GankSectionCell.view(tableView, owner: self, subject: items[row]) ?? GankItemCell.view(tableView, owner: self, subject: items[row]) 141 | } 142 | 143 | fileprivate func loadOlderIfNeeded(_ row: Int) { 144 | if items.count - row == 1 { // ph设置的值是15,我这里设置的是下拉到底时才触发加载新数据,节省流量 145 | NotificationCenter.default.post(name: Notification.Name(rawValue: "Reload"), object: nil) 146 | } 147 | } 148 | 149 | } 150 | 151 | // 列表的动作代理 152 | extension GankViewController: NSTableViewDelegate { 153 | 154 | func tableView(_ tableView: NSTableView, isGroupRow row: Int) -> Bool { 155 | return (items[row] as? String) != nil // 显示日期的是GroupRow 156 | } 157 | 158 | func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { 159 | return (items[row] as? String) != nil ? 45 : tableView.rowHeight // GroupRow高度是45 160 | } 161 | 162 | func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool { 163 | if loadingView.currentState != LoadingState.idle { // 在出错界面中点击item无效,不执行任何操作 164 | return true 165 | } 166 | if let item = items[row] as? GankItem { 167 | OpenUrlAction.perform(withPath: item.visitUrl) 168 | } 169 | return true 170 | } 171 | 172 | } 173 | 174 | -------------------------------------------------------------------------------- /Gank/GankViewController.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 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 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 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 224 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | -------------------------------------------------------------------------------- /Gank/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 | 2.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSApplicationCategoryType 26 | public.app-category.utilities 27 | LSMinimumSystemVersion 28 | $(MACOSX_DEPLOYMENT_TARGET) 29 | LSUIElement 30 | 31 | NSHumanReadableCopyright 32 | Copyright © 2016年 Tsinghua University. All rights reserved. 33 | NSMainNibFile 34 | MainMenu 35 | NSPrincipalClass 36 | NSApplication 37 | 38 | 39 | -------------------------------------------------------------------------------- /Gank/MainMenu.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 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 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 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 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 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | Default 536 | 537 | 538 | 539 | 540 | 541 | 542 | Left to Right 543 | 544 | 545 | 546 | 547 | 548 | 549 | Right to Left 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | Default 561 | 562 | 563 | 564 | 565 | 566 | 567 | Left to Right 568 | 569 | 570 | 571 | 572 | 573 | 574 | Right to Left 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | -------------------------------------------------------------------------------- /Gank/NSColor+Gank.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSColor+Gank.swift <- NSColor+ProductHunt.swift 3 | // Gank 4 | // 5 | // Created by Vlado on 3/24/16. 6 | // Copyright © 2016 ProductHunt. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | //颜色值的扩展 12 | extension NSColor { 13 | 14 | class func gankWhiteColor() -> NSColor { 15 | return NSColor.white 16 | } 17 | 18 | class func gankHighlightColor() -> NSColor { 19 | return NSColor(calibratedRed: 249 / 255, green: 249 / 255, blue: 249 / 255, alpha: 1) 20 | } 21 | 22 | class func gankOrangeColor() -> NSColor { 23 | return NSColor(calibratedRed: 228 / 255, green: 81 / 255, blue: 39 / 255, alpha: 1) 24 | } 25 | 26 | class func gankGrayColor() -> NSColor { 27 | return NSColor(calibratedRed: 153 / 255, green: 153 / 255, blue: 153 / 255, alpha: 1) 28 | } 29 | 30 | class func gankLightGrayColor() -> NSColor { 31 | return NSColor(calibratedRed: 204 / 255, green: 204 / 255, blue: 208 / 255, alpha: 1) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Gank/NSUserNotification+Gank.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSUserNotification+Gank.swift 3 | // Gank 4 | // 5 | // Created by hujiawei on 2016/5/14. 6 | // Copyright © 2016年 Tsinghua University. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Foundation 11 | 12 | protocol NSUserNotificationImage { 13 | func set_identityImage(_ image: NSImage) 14 | } 15 | -------------------------------------------------------------------------------- /Gank/OpenUrlAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OpenUrlAction.swift 3 | // Gank 4 | // 5 | // Created by hujiawei on 2016/5/13. 6 | // Copyright © 2016年 Tsinghua University. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | //打开网址链接的操作 12 | class OpenUrlAction { 13 | 14 | class func perform(withPath path: String, closeAfterLaunch: Bool = false) { 15 | perform(withUrl: URL(string: path)!, closeAfterLaunch: closeAfterLaunch) 16 | } 17 | 18 | class func perform(withUrl url: URL, closeAfterLaunch: Bool = false) { 19 | let handle = NSWorkspace.shared().open(url) 20 | 21 | if handle && closeAfterLaunch { 22 | PopoverAction.close() 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Gank/PopoverAction.swift: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // PopoverAction.swift 4 | // Gank 5 | // 6 | // Created by hujiawei on 2016/5/12. 7 | // Copyright © 2016年 Tsinghua University. All rights reserved. 8 | // 9 | 10 | import Foundation 11 | import Cocoa 12 | 13 | //打开或者关闭窗口的操作 14 | class PopoverAction { 15 | 16 | // 静态计算型属性,监听用户的点击事件,在窗口外点击要关闭窗口 17 | fileprivate class var eventMonitor: EventMonitor? { 18 | return EventMonitor(mask: [.leftMouseDown, .rightMouseDown]) { event in 19 | close() 20 | } 21 | } 22 | 23 | // AppDelegate 24 | fileprivate class var appDelegate: AppDelegate { 25 | return NSApplication.shared().delegate as! AppDelegate 26 | } 27 | 28 | class func toggle() { 29 | if appDelegate.popover.isShown { 30 | close() 31 | } else { 32 | show() 33 | } 34 | } 35 | 36 | class func close() { 37 | if !appDelegate.popover.isShown { 38 | return 39 | } 40 | 41 | appDelegate.popover.close() 42 | eventMonitor?.stop() 43 | } 44 | 45 | class func show() { 46 | NSRunningApplication.current().activate(options: NSApplicationActivationOptions.activateIgnoringOtherApps) 47 | 48 | guard let button = appDelegate.statusItem.button else { 49 | return 50 | } 51 | 52 | appDelegate.popover.show(relativeTo: button.frame, of: button, preferredEdge: .minY) 53 | eventMonitor?.start() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Gank/SettingAction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingAction.swift 3 | // Gank 4 | // 5 | // Created by hujiawei on 2016/5/13. 6 | // Copyright © 2016年 Tsinghua University. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Foundation 11 | 12 | //点击设置按钮之后 13 | class SettingAction { 14 | 15 | class func makeSettingMenu(_ sender: NSView) { 16 | let delegate = NSApplication.shared().delegate as! AppDelegate 17 | 18 | let menu = NSMenu() 19 | menu.addItem(NSMenuItem(title: "应用源码", action: #selector(delegate.openGithubLink), keyEquivalent: "g")) 20 | menu.addItem(NSMenuItem(title: "作者博客", action: #selector(delegate.openBlogLink), keyEquivalent: "b")) 21 | menu.addItem(NSMenuItem(title: "退出应用", action: #selector(delegate.quit), keyEquivalent: "q")) 22 | 23 | NSMenu.popUpContextMenu(menu, with: NSApp.currentEvent!, for: sender) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Gank/screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/Gank/screenshot4.png -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Hujiawei 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 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.10' 2 | use_frameworks! 3 | 4 | target 'Gank' do 5 | pod 'SwiftyTimer', '~> 2.0.0' 6 | pod 'Alamofire', '~> 4.4' 7 | pod 'SwiftyJSON', '~> 3.1.4' 8 | end 9 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.4.0) 3 | - SwiftyJSON (3.1.4) 4 | - SwiftyTimer (2.0.0) 5 | 6 | DEPENDENCIES: 7 | - Alamofire (~> 4.4) 8 | - SwiftyJSON (~> 3.1.4) 9 | - SwiftyTimer (~> 2.0.0) 10 | 11 | SPEC CHECKSUMS: 12 | Alamofire: dc44b1600b800eb63da6a19039a0083d62a6a62d 13 | SwiftyJSON: c2842d878f95482ffceec5709abc3d05680c0220 14 | SwiftyTimer: 2efd74b060d69ad4f1496baf5bbedbe132125fcf 15 | 16 | PODFILE CHECKSUM: e38cdd4f32edf8182730ad90be9ce7ea2c6a3291 17 | 18 | COCOAPODS: 1.2.1 19 | -------------------------------------------------------------------------------- /README-EN.md: -------------------------------------------------------------------------------- 1 | [中文说明](README.md) [![Github All Releases](https://img.shields.io/github/downloads/hujiaweibujidao/Gank-for-Mac/total.svg)](https://github.com/hujiaweibujidao/Gank-for-Mac/releases) 2 | 3 | # Gank for Mac 4 | 5 | > The missing Mac OS X application for [gank.io](https://gank.io) 6 | 7 | ## Application Features 8 | 9 | This tiny application is used to view the `gank` posts in [gank.io](https://gank.io), and send you a notification when new post arrived. Nothing more and nothing less. 10 | It is developed with [Swift](https://swift.org/) 3!!! Excited? 😜 11 | Btw, please make sure your mac system is OS X 10.10 or later. 12 | 13 | ![img](screenshot2.png) 14 | 15 | Almost all the code is explained with its fuctions, so enjoy reading the source code. 😄 16 | Since I just learned Swift language and Mac OS X application development this week, please be nice when you read the xxxx code. 😂 Btw, other helpful features are still under development. 😘 17 | Feel free to submit any pull request to make it better or send your suggestions to [me](mailto:hujiawei090807@gmail.com). 18 | 19 | ![img](screenshot3.png) 20 | 21 | ## Get this Application 22 | 23 | You can rebuild this project to get this applicaton or you can download Gank for Mac [here](https://github.com/hujiaweibujidao/Gank/releases) 24 | 25 | ## Data and Libraries 26 | 27 | 1.[gank.io](http://gank.io/) and its [API](http://gank.io/api): Share technical `ganks` every day 28 | 2.[Alamofire](https://github.com/Alamofire/Alamofire): Elegant HTTP Networking in Swift 29 | 3.[SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON): The better way to deal with JSON data in Swift 30 | 4.[SwiftyTimer](https://github.com/radex/SwiftyTimer): Swifty API for NSTimer 31 | 5.Image resources are coming from [IconFinder](https://www.iconfinder.com/), mainly from [this iconset](https://www.iconfinder.com/iconsets/picons-social) with the license of free for commercial use. 32 | 33 | ## Projects Based on 34 | 35 | 1.[Product Hunt](https://github.com/producthunt/producthunt-osx) 36 | 2.[Toutiao for Mac](https://github.com/judi0713/TouTiao) 37 | 38 | ## The License 39 | 40 | ``` 41 | The MIT License (MIT) 42 | 43 | Copyright (c) 2016 Hujiawei 44 | 45 | Permission is hereby granted, free of charge, to any person obtaining a copy 46 | of this software and associated documentation files (the "Software"), to deal 47 | in the Software without restriction, including without limitation the rights 48 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 49 | copies of the Software, and to permit persons to whom the Software is 50 | furnished to do so, subject to the following conditions: 51 | 52 | The above copyright notice and this permission notice shall be included in all 53 | copies or substantial portions of the Software. 54 | 55 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 56 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 57 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 58 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 59 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 60 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 61 | SOFTWARE. 62 | ``` 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [README in English](README-EN.md) [![Github All Releases](https://img.shields.io/github/downloads/hujiaweibujidao/Gank-for-Mac/total.svg)](https://github.com/hujiaweibujidao/Gank-for-Mac/releases) 2 | 3 | # Gank for Mac 4 | 5 | > The missing Mac OS X application for [gank.io](https://gank.io). 6 | 7 | Gank for Mac是一个小巧的干货集中营Mac端应用,它只在你的状态栏占据一点点空间而已。 8 | 9 | [应用下载地址](https://github.com/hujiaweibujidao/Gank-for-Mac/releases) 10 | 11 | 如果你对我的开发工作感兴趣的话记得在Github上Follow我哟,或者关注[我的博客](http://javayhu.me/)。 12 | 13 | ## 应用功能 14 | 15 | Gank for Mac的功能包括查看[干货集中营](https://gank.io)今天或者以前的干货数据,同时在应用启动的情况下,当有新的干货到来的时候会推送通知给你喂食哟,麻麻为了不让你饿着真是操碎了心。当然,你再也不用担心抢不到评论区的沙发啦!😚 16 | 17 | 应用在加载数据的时候会显示菊花,加载失败的时候会显示出错并提供重载按钮。 18 | 19 | Gank for Mac使用的是 [Swift](https://swift.org/) 3 开发的,Mac系统版本要求10.10及以上,应用截图如下: 20 | 21 | ![img](screenshot2.png) 22 | 23 | 由于是第一次写Mac OS X应用,所以项目源码基本上都注释得很清楚,但是不保证代码写得非常好,所以哪块代码要是没写好请轻拍哈。当然,欢迎提交PR或者提建议,比如加上提交干货的功能。😘 24 | 25 | ![img](screenshot3.png) 26 | 27 | 2017/6/10 周末过来公司抽空将Swift升级到3.0版本 28 | 29 | ![img](screenshot4.png) 30 | 31 | ## 数据+依赖+资源 32 | 33 | 1.[gank.io](http://gank.io/) and its [API](http://gank.io/api): 每日分享妹子图和技术干货 34 | 2.[Alamofire](https://github.com/Alamofire/Alamofire): Elegant HTTP Networking in Swift 35 | 3.[SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON): The better way to deal with JSON data in Swift 36 | 4.[SwiftyTimer](https://github.com/radex/SwiftyTimer): Swifty API for NSTimer 37 | 5.图片来源于[IconFinder](https://www.iconfinder.com/),主要是[这个图标集合](https://www.iconfinder.com/iconsets/picons-social) 38 | 39 | ## 参考项目 40 | 41 | 1.[Product Hunt](https://github.com/producthunt/producthunt-osx) 42 | 2.[Toutiao for Mac](https://github.com/judi0713/TouTiao) 43 | 44 | ### FAQ 45 | 46 | #### 1.妹子图在哪里呢? 47 | 48 | 我就知道你只关心妹子!!!🙈 为了应用的简洁,点击妹子图或者休息视频会进入那天的干货日报界面。 49 | 50 | #### 2.后台自动刷新的时间间隔设置的是多久? 51 | 52 | 默认是10分钟,如果你觉得不合适可以自己改改源码重新生成一个应用哈。 53 | 54 | #### 3.为啥没有开机自启动? 55 | 56 | 应用的开机自启动可以在`System Preferences`中设置的嘛,别以为我不知道!😌 57 | 58 | 59 | 60 | ## The License 61 | 62 | ``` 63 | The MIT License (MIT) 64 | 65 | Copyright (c) 2016 Hujiawei 66 | 67 | Permission is hereby granted, free of charge, to any person obtaining a copy 68 | of this software and associated documentation files (the "Software"), to deal 69 | in the Software without restriction, including without limitation the rights 70 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 71 | copies of the Software, and to permit persons to whom the Software is 72 | furnished to do so, subject to the following conditions: 73 | 74 | The above copyright notice and this permission notice shall be included in all 75 | copies or substantial portions of the Software. 76 | 77 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 78 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 79 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 80 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 81 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 82 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 83 | SOFTWARE. 84 | ``` 85 | -------------------------------------------------------------------------------- /appimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/appimage.png -------------------------------------------------------------------------------- /autologin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/autologin.png -------------------------------------------------------------------------------- /notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/notification.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/screenshot.png -------------------------------------------------------------------------------- /screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/screenshot2.png -------------------------------------------------------------------------------- /screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/screenshot3.png -------------------------------------------------------------------------------- /screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javayhu/Gank-for-Mac/8283dba0298911ccecfdf41b4298537ffe7cddf9/screenshot4.png --------------------------------------------------------------------------------