├── .github ├── CODEOWNERS ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── homebrew-cask-updates.yml ├── pullBar ├── git-pull-request (6).png ├── git-pull-request (7).png ├── Assets.xcassets │ ├── Contents.json │ ├── apps │ │ ├── Contents.json │ │ ├── gojibar.imageset │ │ │ ├── 256.png │ │ │ └── Contents.json │ │ ├── todobar.imageset │ │ │ ├── 256.png │ │ │ └── Contents.json │ │ ├── pullbarpro.imageset │ │ │ ├── 256.png │ │ │ └── Contents.json │ │ └── streakbar.imageset │ │ │ ├── 256.png │ │ │ └── Contents.json │ ├── x.imageset │ │ ├── x.pdf │ │ └── Contents.json │ ├── repo.imageset │ │ ├── repo.pdf │ │ └── Contents.json │ ├── sync.imageset │ │ ├── sync.pdf │ │ └── Contents.json │ ├── tag.imageset │ │ ├── tag.pdf │ │ └── Contents.json │ ├── alert.imageset │ │ ├── alert.pdf │ │ └── Contents.json │ ├── check.imageset │ │ ├── check.pdf │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── 1024.png │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 256.png │ │ ├── 32-1.png │ │ ├── 32.png │ │ ├── 512.png │ │ ├── 64.png │ │ ├── 256-1.png │ │ ├── 512-1.png │ │ └── Contents.json │ ├── person.imageset │ │ ├── person.pdf │ │ └── Contents.json │ ├── calendar.imageset │ │ ├── calendar.pdf │ │ └── Contents.json │ ├── checklist.imageset │ │ ├── checklist.pdf │ │ └── Contents.json │ ├── dot-fill.imageset │ │ ├── dot-fill.pdf │ │ └── Contents.json │ ├── question.imageset │ │ ├── question.pdf │ │ └── Contents.json │ ├── issue-draft.imageset │ │ ├── issue-draft.pdf │ │ └── Contents.json │ ├── check-circle.imageset │ │ ├── check-circle.pdf │ │ └── Contents.json │ ├── x-circle-fill.imageset │ │ ├── x-circle-fill.pdf │ │ └── Contents.json │ ├── git-pull-request.imageset │ │ ├── git-pull-request.pdf │ │ └── Contents.json │ ├── check-circle-fill.imageset │ │ ├── check-circle-fill.pdf │ │ └── Contents.json │ ├── bmc-logo-no-background.imageset │ │ ├── bmc-logo-no-background.png │ │ └── Contents.json │ ├── git-draft-pull-request.imageset │ │ ├── Contents.json │ │ └── git-draft-pull-request.svg │ ├── red.colorset │ │ └── Contents.json │ ├── green.colorset │ │ └── Contents.json │ └── yellow.colorset │ │ └── Contents.json ├── pullBar.entitlements ├── ViewController.swift ├── Notifications.swift ├── Extensions │ ├── StringExtensions.swift │ ├── DateExtensions.swift │ ├── NSImageExtensions.swift │ ├── DefaultsExtensions.swift │ └── NSMutableAttributedStringExtensions.swift ├── GitHub │ ├── GithubTokenValidator.swift │ ├── GitHubDtos.swift │ └── GitHubClient.swift ├── Views │ ├── AppPromotionView.swift │ ├── AppView.swift │ ├── AboutView.swift │ ├── BottomItemView.swift │ └── PreferencesView.swift ├── Keychain.swift ├── AppDelegate.swift └── Base.lproj │ └── Main.storyboard ├── pullBar.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved ├── xcshareddata │ └── xcschemes │ │ └── pullBar.xcscheme └── project.pbxproj ├── .gitignore ├── README.md └── LICENSE /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * streetturtle 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: streetturtle 2 | 3 | custom: https://www.buymeacoffee.com/streetturtle 4 | -------------------------------------------------------------------------------- /pullBar/git-pull-request (6).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/git-pull-request (6).png -------------------------------------------------------------------------------- /pullBar/git-pull-request (7).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/git-pull-request (7).png -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/apps/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/x.imageset/x.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/x.imageset/x.pdf -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/repo.imageset/repo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/repo.imageset/repo.pdf -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/sync.imageset/sync.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/sync.imageset/sync.pdf -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/tag.imageset/tag.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/tag.imageset/tag.pdf -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/alert.imageset/alert.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/alert.imageset/alert.pdf -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/check.imageset/check.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/check.imageset/check.pdf -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/AppIcon.appiconset/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/AppIcon.appiconset/128.png -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/AppIcon.appiconset/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/AppIcon.appiconset/16.png -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/AppIcon.appiconset/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/AppIcon.appiconset/256.png -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/AppIcon.appiconset/32-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/AppIcon.appiconset/32-1.png -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/AppIcon.appiconset/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/AppIcon.appiconset/32.png -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/AppIcon.appiconset/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/AppIcon.appiconset/512.png -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/AppIcon.appiconset/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/AppIcon.appiconset/64.png -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/person.imageset/person.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/person.imageset/person.pdf -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/AppIcon.appiconset/256-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/AppIcon.appiconset/256-1.png -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/AppIcon.appiconset/512-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/AppIcon.appiconset/512-1.png -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/apps/gojibar.imageset/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/apps/gojibar.imageset/256.png -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/apps/todobar.imageset/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/apps/todobar.imageset/256.png -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/apps/pullbarpro.imageset/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/apps/pullbarpro.imageset/256.png -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/apps/streakbar.imageset/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/apps/streakbar.imageset/256.png -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/calendar.imageset/calendar.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/calendar.imageset/calendar.pdf -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/checklist.imageset/checklist.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/checklist.imageset/checklist.pdf -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/dot-fill.imageset/dot-fill.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/dot-fill.imageset/dot-fill.pdf -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/question.imageset/question.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/question.imageset/question.pdf -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/issue-draft.imageset/issue-draft.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/issue-draft.imageset/issue-draft.pdf -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/check-circle.imageset/check-circle.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/check-circle.imageset/check-circle.pdf -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/x-circle-fill.imageset/x-circle-fill.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/x-circle-fill.imageset/x-circle-fill.pdf -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/git-pull-request.imageset/git-pull-request.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/git-pull-request.imageset/git-pull-request.pdf -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/check-circle-fill.imageset/check-circle-fill.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/check-circle-fill.imageset/check-circle-fill.pdf -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/bmc-logo-no-background.imageset/bmc-logo-no-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/menubar-apps/PullBar/HEAD/pullBar/Assets.xcassets/bmc-logo-no-background.imageset/bmc-logo-no-background.png -------------------------------------------------------------------------------- /pullBar.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/apps/gojibar.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "256.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/apps/todobar.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "256.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/apps/pullbarpro.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "256.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/apps/streakbar.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "256.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pullBar.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/tag.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "tag.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/x.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "x.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/alert.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "alert.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/check.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "check.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/repo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "repo.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/sync.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "sync.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/calendar.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "calendar.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/dot-fill.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "dot-fill.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/checklist.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "checklist.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/check-circle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "check-circle.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/x-circle-fill.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "x-circle-fill.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/check-circle-fill.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "check-circle-fill.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/git-pull-request.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "git-pull-request.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/git-draft-pull-request.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "git-draft-pull-request.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pullBar/pullBar.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/question.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "question.pdf", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/red.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x6A", 9 | "green" : "0x61", 10 | "red" : "0xBF" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/green.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x8C", 9 | "green" : "0xBE", 10 | "red" : "0xA3" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/yellow.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x8B", 9 | "green" : "0xCB", 10 | "red" : "0xEB" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/bmc-logo-no-background.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "bmc-logo-no-background.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Problem 11 | Description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | ## Proposed solution 14 | Description of the proposed solution, if any 15 | 16 | ## Alternatives 17 | Have you considered any alternatives? 18 | 19 | ## Notes 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/person.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "person.pdf", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/issue-draft.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "issue-draft.pdf", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pullBar/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // pullBar 4 | // 5 | // Created by Pavel Makhov on 2021-11-15. 6 | // 7 | 8 | import Cocoa 9 | 10 | class ViewController: NSViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | // Do any additional setup after loading the view. 16 | } 17 | 18 | override var representedObject: Any? { 19 | didSet { 20 | // Update the view, if already loaded. 21 | } 22 | } 23 | 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Describe the bug 11 | A clear and concise description of what the bug is. 12 | 13 | # How To Reproduce 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | # Expected behavior 21 | Description of what you expected to happen. 22 | 23 | # Screenshots 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | # Additional context 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/workflows/homebrew-cask-updates.yml: -------------------------------------------------------------------------------- 1 | name: Homebrew cask updates 2 | 3 | on: 4 | release: 5 | types: [published] 6 | # Manual trigger from the UI 7 | workflow_dispatch: 8 | 9 | jobs: 10 | bump-casks: 11 | runs-on: macos-latest 12 | steps: 13 | - name: Publish release 14 | uses: macauley/action-homebrew-bump-cask@v1 15 | with: 16 | # Required, custom GitHub access token with only the 'public_repo' scope enabled 17 | token: ${{ secrets.HOMEBREW_PULLBAR_PUBLISH_TOKEN }} 18 | # Bump all outdated casks in this tap 19 | tap: menubar-apps/homebrew-menubar-apps 20 | # Bump only these casks if outdated 21 | cask: pullbar 22 | livecheck: true 23 | dryrun: false -------------------------------------------------------------------------------- /pullBar/Notifications.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Notifications.swift 3 | // pullBar 4 | // 5 | // Created by Casey Jones on 2021-12-28. 6 | // 7 | 8 | import Foundation 9 | import UserNotifications 10 | 11 | func sendNotification(body: String = "") { 12 | let content = UNMutableNotificationContent() 13 | content.title = "PullBar" 14 | 15 | if body.count > 0 { 16 | content.body = body 17 | } 18 | 19 | let uuidString = UUID().uuidString 20 | let request = UNNotificationRequest( 21 | identifier: uuidString, 22 | content: content, trigger: nil) 23 | 24 | let notificationCenter = UNUserNotificationCenter.current() 25 | notificationCenter.requestAuthorization(options: [.alert, .sound]) { _, _ in } 26 | notificationCenter.add(request) 27 | } 28 | -------------------------------------------------------------------------------- /pullBar/Extensions/StringExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringExtensions.swift 3 | // pullBar 4 | // 5 | // Created by Casey Jones on 2021-12-29. 6 | // 7 | 8 | import Foundation 9 | 10 | extension String { 11 | /* 12 | Truncates the string to the specified length number of characters and appends an optional trailing string if longer. 13 | - Parameter length: Desired maximum lengths of a string 14 | - Parameter trailing: A 'String' that will be appended after the truncation. 15 | 16 | - Returns: 'String' object. 17 | */ 18 | func trunc(length: Int, trailing: String = "…") -> String { 19 | return (self.count > length) ? self.prefix(length) + trailing : self 20 | } 21 | } 22 | 23 | extension Bool { 24 | var intValue: Int { 25 | return self ? 1 : 0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /pullBar/GitHub/GithubTokenValidator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TokenStatus.swift 3 | // pullBar 4 | // 5 | // Created by Pavel Makhov on 2022-08-13. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | class GithubTokenValidator: ObservableObject { 12 | 13 | @Published var iconName: String!; 14 | @Published var iconColor: Color!; 15 | 16 | init() { 17 | setLoading() 18 | } 19 | 20 | func setLoading() { 21 | 22 | self.iconName = "clock.fill" 23 | self.iconColor = Color(.systemGray) 24 | } 25 | 26 | func setInvalid() { 27 | self.iconName = "exclamationmark.circle.fill" 28 | self.iconColor = Color(.systemRed) 29 | } 30 | 31 | func setValid() { 32 | self.iconName = "checkmark.circle.fill" 33 | self.iconColor = Color(.systemGreen) 34 | 35 | } 36 | 37 | func validate() { 38 | self.setLoading() 39 | 40 | GitHubClient().getUser() { user in 41 | if user != nil { 42 | self.setValid() 43 | } else { 44 | self.setInvalid() 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /pullBar/Extensions/DateExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateExtensions.swift 3 | // pullBar 4 | // 5 | // Created by Pavel Makhov on 2021-11-21. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Date { 11 | 12 | func getElapsedInterval() -> String { 13 | 14 | let interval = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], from: self, to: Date()) 15 | 16 | if let year = interval.year, year > 0 { 17 | return "\(year) year\(year == 1 ? "" : "s") ago" 18 | } else if let month = interval.month, month > 0 { 19 | return "\(month) month\(month == 1 ? "" : "s") ago" 20 | } else if let day = interval.day, day > 0 { 21 | return "\(day) day\(day == 1 ? "" : "s") ago" 22 | } else if let hour = interval.hour, hour > 0 { 23 | return "\(hour) hour\(hour == 1 ? "" : "s") ago" 24 | } else if let minute = interval.minute, minute > 0 { 25 | return "\(minute) minute\(minute == 1 ? "" : "s") ago" 26 | } else if let second = interval.second, second > 0 { 27 | return "\(second) second\(second == 1 ? "" : "s") ago" 28 | } else { 29 | return "a moment ago" 30 | } 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pullBar/Views/AppPromotionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppPromotionView.swift 3 | // pullBar 4 | // 5 | // Created by Pavel Makhov on 2025-03-08. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct AppPromotionView: View { 11 | let apps: [MyApp] = [ 12 | MyApp(name: "ToDoBar", 13 | description: "Description for App One", 14 | iconName: "todobar", 15 | appStoreURL: "https://apps.apple.com/app/id1641624925"), 16 | MyApp(name: "GojiBar", 17 | description: "Description for App Two", 18 | iconName: "gojibar", 19 | appStoreURL: "https://apps.apple.com/app/id6471348025"), 20 | MyApp(name: "PullBar Pro", 21 | description: "Description for App Three", 22 | iconName: "pullbarpro", 23 | appStoreURL: "https://apps.apple.com/app/id6462591649"), 24 | MyApp(name: "StreakBar", 25 | description: "Description for App Four", 26 | iconName: "streakbar", 27 | appStoreURL: "https://apps.apple.com/app/id6464448808"), 28 | ] 29 | 30 | var body: some View { 31 | Text("More apps") 32 | .font(.headline) 33 | 34 | HStack { 35 | ForEach(apps, id:\.id) { app in 36 | AppView(app: app) 37 | } 38 | 39 | } 40 | } 41 | } 42 | 43 | #Preview { 44 | AppPromotionView() 45 | } 46 | -------------------------------------------------------------------------------- /pullBar.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "22c866ea327f6db938d479f8e16cd618a46a1ba6c0503558514381243c714979", 3 | "pins" : [ 4 | { 5 | "identity" : "alamofire", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/Alamofire/Alamofire", 8 | "state" : { 9 | "branch" : "master", 10 | "revision" : "26b8c9a41aa03f0356f957ab62e9ca6b1068afe0" 11 | } 12 | }, 13 | { 14 | "identity" : "defaults", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/sindresorhus/Defaults", 17 | "state" : { 18 | "revision" : "38925e3cfacf3fb89a81a35b1cd44fd5a5b7e0fa", 19 | "version" : "8.2.0" 20 | } 21 | }, 22 | { 23 | "identity" : "keychainaccess", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/kishikawakatsumi/KeychainAccess", 26 | "state" : { 27 | "revision" : "84e546727d66f1adc5439debad16270d0fdd04e7", 28 | "version" : "4.2.2" 29 | } 30 | }, 31 | { 32 | "identity" : "launchatlogin-modern", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/sindresorhus/LaunchAtLogin-Modern", 35 | "state" : { 36 | "revision" : "a04ec1c363be3627734f6dad757d82f5d4fa8fcc", 37 | "version" : "1.1.0" 38 | } 39 | } 40 | ], 41 | "version" : 3 42 | } 43 | -------------------------------------------------------------------------------- /pullBar/Views/AppView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppView.swift 3 | // pullBar 4 | // 5 | // Created by Pavel Makhov on 2025-03-08. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct AppView: View { 11 | @Environment(\.openURL) var openURL 12 | 13 | let app: MyApp 14 | @State private var isHovered = false 15 | 16 | var body: some View { 17 | VStack(spacing: 0) { 18 | Image(app.iconName) 19 | .resizable() 20 | .frame(width: 64, height: 64) 21 | 22 | Text(app.name) 23 | .font(.caption) 24 | } 25 | .padding(8) 26 | .background(isHovered ? Color.gray.opacity(0.1) : Color.clear) 27 | .cornerRadius(8) 28 | .scaleEffect(isHovered ? 1.02 : 1.0) 29 | .onHover { hovering in 30 | withAnimation(.easeInOut(duration: 0.2)) { 31 | isHovered = hovering 32 | } 33 | } 34 | .onTapGesture { 35 | openURL(URL(string: app.appStoreURL)!) 36 | } 37 | } 38 | } 39 | 40 | struct MyApp: Identifiable { 41 | let id = UUID() 42 | let name: String 43 | let description: String 44 | let iconName: String 45 | let appStoreURL: String 46 | } 47 | 48 | #Preview { 49 | AppView(app: MyApp(name: "GojiBar", 50 | description: "Descriptxion for App One", 51 | iconName: "gojibar", 52 | appStoreURL: "https://apps.apple.com/app/id6471348025")) 53 | } 54 | 55 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "32-1.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "64.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "256-1.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "512-1.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "1024.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /pullBar/Extensions/NSImageExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSImageExtensions.swift 3 | // pullBar 4 | // 5 | // Created by Pavel Makhov on 2021-11-21. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension NSImage { 12 | 13 | convenience init?(named: String, color: NSColor) { 14 | 15 | let img = NSImage.init(named: named)! 16 | let newImg = img.tint(color: color) 17 | self.init(data: newImg.tiffRepresentation!) 18 | } 19 | 20 | static func loadImageAsync(fromURL url: URL, completion: @escaping (NSImage?) -> Void) { 21 | URLSession.shared.dataTask(with: url) { data, response, error in 22 | guard let data = data, error == nil, 23 | let image = NSImage(data: data) else { 24 | DispatchQueue.main.async { 25 | completion(nil) 26 | } 27 | return 28 | } 29 | 30 | DispatchQueue.main.async { 31 | completion(image) 32 | } 33 | }.resume() 34 | } 35 | 36 | func tint(color: NSColor) -> NSImage { 37 | let newImage = NSImage(size: self.size) 38 | newImage.lockFocus() 39 | 40 | // Draw with specified transparency 41 | let imageRect = NSRect(origin: .zero, size: self.size) 42 | self.draw(in: imageRect, from: imageRect, operation: .sourceOver, fraction: color.alphaComponent) 43 | 44 | // Tint with color 45 | color.withAlphaComponent(1).set() 46 | imageRect.fill(using: .sourceAtop) 47 | 48 | newImage.unlockFocus() 49 | return newImage 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /pullBar/Views/AboutView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AboutView.swift 3 | // pullBar 4 | // 5 | // Created by Casey Jones on 2021-12-11. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct AboutView: View { 11 | @Environment(\.openURL) var openURL 12 | 13 | let currentVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String 14 | 15 | var body: some View { 16 | VStack { 17 | Image(nsImage: NSImage(named: "AppIcon")!) 18 | Text("PullBar").font(.title) 19 | Text("by Pavel Makhov").font(.caption) 20 | Text("version " + currentVersion).font(.footnote) 21 | Divider() 22 | 23 | Button(action: { 24 | openURL(URL(string:"https://github.com/menubar-apps/PullBar/issues/new?assignees=&labels=enhancement&projects=&template=feature_request.md&title=")!) 25 | }) { 26 | HStack { 27 | Image(systemName: "star.fill") 28 | Text("Feature Request") 29 | } 30 | } 31 | Button(action: { 32 | openURL(URL(string:"https://github.com/menubar-apps/PullBar/issues/new?assignees=&labels=bug&projects=&template=bug_report.md&title=")!) 33 | }) { 34 | HStack { 35 | Image(systemName: "ladybug.fill") 36 | Text("Bug Report") 37 | } 38 | } 39 | // Divider() 40 | Button(action: { 41 | openURL(URL(string: "https://www.buymeacoffee.com/streetturtle")!) 42 | }) { 43 | HStack { 44 | Image("bmc-logo-no-background") 45 | .resizable() 46 | .scaledToFit() 47 | .padding(.top, 2) 48 | Text("Buy me a coffee") 49 | } 50 | } 51 | 52 | Divider() 53 | AppPromotionView() 54 | }.padding() 55 | } 56 | } 57 | 58 | struct AboutTab_Previews: PreviewProvider { 59 | static var previews: some View { 60 | AboutView() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /pullBar/Extensions/DefaultsExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultsExtensions.swift 3 | // issueBar 4 | // 5 | // Created by Pavel Makhov on 2021-11-10. 6 | // 7 | 8 | import Foundation 9 | import Defaults 10 | 11 | extension Defaults.Keys { 12 | static let githubApiBaseUrl = Key("githubApiBaseUrl", default: "https://api.github.com") 13 | static let githubUsername = Key("githubUsername", default: "") 14 | static let githubAdditionalQuery = Key("githubAdditionalQuery", default:"") 15 | 16 | static let showAssigned = Key("showAssigned", default: false) 17 | static let showCreated = Key("showCreated", default: false) 18 | static let showRequested = Key("showRequested", default: true) 19 | 20 | static let showAvatar = Key("showAvatar", default: false) 21 | static let showLabels = Key("showLabels", default: true) 22 | 23 | static let refreshRate = Key("refreshRate", default: 5) 24 | static let buildType = Key("buildType", default: .none) 25 | static let counterType = Key("counterType", default: .reviewRequested) 26 | } 27 | 28 | extension KeychainKeys { 29 | static let githubToken: KeychainAccessKey = KeychainAccessKey(key: "githubToken") 30 | } 31 | 32 | enum BuildType: String, Defaults.Serializable, CaseIterable, Identifiable { 33 | case checks 34 | case commitStatus 35 | case none 36 | 37 | var id: Self { self } 38 | 39 | var description: String { 40 | 41 | switch self { 42 | case .checks: 43 | return "checks" 44 | case .commitStatus: 45 | return "commit statuses" 46 | case .none: 47 | return "none" 48 | } 49 | } 50 | } 51 | 52 | enum CounterType: String, Defaults.Serializable, CaseIterable, Identifiable { 53 | case assigned 54 | case created 55 | case reviewRequested 56 | case none 57 | 58 | var id: Self { self } 59 | 60 | var description: String { 61 | 62 | switch self { 63 | case .assigned: 64 | return "assigned" 65 | case .created: 66 | return "created" 67 | case .reviewRequested: 68 | return "review requested" 69 | case .none: 70 | return "none" 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | 92 | .DS_Store 93 | -------------------------------------------------------------------------------- /pullBar/Keychain.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Keychain.swift 3 | // pullBar 4 | // 5 | // Created by Pavel Makhov on 2022-09-14. 6 | // 7 | 8 | import SwiftUI 9 | import Defaults 10 | import KeychainAccess 11 | import Foundation 12 | 13 | typealias FromKeychain = KeychainStorage 14 | typealias KeychainKeys = KeychainAccessKey 15 | 16 | @propertyWrapper 17 | struct KeychainStorage: DynamicProperty { 18 | 19 | private let key: KeychainAccessKey 20 | @ObservedObject 21 | private var observable: ObservableString 22 | 23 | init(wrappedValue: String = "", _ key: KeychainAccessKey) { 24 | self.key = key 25 | 26 | let presentObservable: ObservableString? = ObservablesStore.store[key] 27 | 28 | if presentObservable != nil { 29 | self.observable = presentObservable! 30 | } else { 31 | self.observable = ObservableString(key) 32 | ObservablesStore.store[key] = self.observable 33 | } 34 | } 35 | 36 | var wrappedValue: String { 37 | get { observable.value } 38 | 39 | nonmutating set { 40 | observable.value = newValue 41 | } 42 | } 43 | 44 | var projectedValue: Binding { $observable.value } 45 | } 46 | 47 | private class ObservableString: ObservableObject { 48 | 49 | let key: KeychainAccessKey 50 | var currentValue: String? = nil 51 | 52 | init(_ key: KeychainAccessKey) { 53 | self.key = key 54 | } 55 | 56 | var value: String { 57 | get { 58 | if currentValue == nil { 59 | currentValue = try? Keychain().get(key.keyName) ?? "" 60 | } 61 | 62 | return currentValue! 63 | } 64 | 65 | set { 66 | objectWillChange.send() 67 | 68 | do { 69 | currentValue = newValue 70 | 71 | if currentValue!.isEmpty { 72 | // Keychain does not want to save empty value 73 | try Keychain().remove(key.keyName) 74 | } else { 75 | try Keychain().set(currentValue!, key: key.keyName) 76 | } 77 | } catch let error { 78 | fatalError("\(error)") 79 | } 80 | } 81 | } 82 | } 83 | 84 | struct KeychainAccessKey: Hashable { 85 | let keyName: String 86 | 87 | init(key: String) { 88 | self.keyName = key 89 | } 90 | } 91 | 92 | private struct ObservablesStore { 93 | static var store: [KeychainAccessKey: ObservableString] = [:] 94 | } 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PullBar 2 | 3 |

4 | 5 | GitHub all releases 6 | GitHub top language 7 | GitHub release (with filter) 8 |

9 | 10 |

11 | Download on the Mac App Store 12 |

13 | 14 | Native MacOS menubar application to show GitHub Pull Requests in your menu bar! Keep track of created, assigned and review requested Pull Requests: 15 | 16 |

17 | Screen Shot 2022-07-17 at 9 06 36 PM 18 |

19 | 20 | If you liked the PullBar, check the [PullBar Pro](https://menubar-apps.github.io/#pullbar-pro) - an improved version with more features! 21 | 22 | # Features 23 | 24 | - shows assigned, created and/or review requested pull requests; 25 | - for each pull request shows title, number, project, author, number of approvals, number of added/deleted lines and how long ago this PR was created; 26 | - show check suites information. 27 | 28 | # Installation 29 | 30 | There are 3 ways of installing the application: 31 | 32 | - [Mac App Store](https://apps.apple.com/ca/app/pullbar/id1601913905) 33 | - Homebrew: 34 | ```shell 35 | brew tap menubar-apps/menubar-apps 36 | brew install pullbar 37 | ``` 38 | - [Download](https://github.com/menubar-apps/PullBar/releases) from github releases 39 | 40 | Then [generate](https://github.com/settings/tokens/new?scopes=repo) a github access token (you'll need to have a **repo** scope selected) and paste it in the application preferences with your github username: 41 | 42 | 43 | 44 |

45 | Screenshot 2023-07-09 at 11 08 51 AM 46 |

47 | -------------------------------------------------------------------------------- /pullBar/Assets.xcassets/git-draft-pull-request.imageset/git-draft-pull-request.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /pullBar/Extensions/NSMutableAttributedStringExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableAttributedString.swift 3 | // FleetBar 4 | // 5 | // Created by Pavel Makhov on 2021-11-04. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension NSMutableAttributedString { 12 | 13 | @discardableResult 14 | func appendString(string: String, color: NSColor = NSColor.secondaryLabelColor) -> NSMutableAttributedString { 15 | var attributes = [NSAttributedString.Key: AnyObject]() 16 | attributes[.foregroundColor] = color 17 | self.append(NSMutableAttributedString(string: string, attributes: attributes)) 18 | 19 | return self 20 | } 21 | 22 | 23 | @discardableResult 24 | func appendString(string: String, color: NSColor, fontSize: CGFloat = NSFont.systemFontSize) -> NSMutableAttributedString { 25 | 26 | var attributes = [NSAttributedString.Key: AnyObject]() 27 | attributes[.foregroundColor] = color 28 | attributes[.font] = NSFont.systemFont(ofSize: fontSize) 29 | self.append(NSMutableAttributedString(string: string, attributes: attributes)) 30 | 31 | return self 32 | } 33 | 34 | @discardableResult 35 | func appendIcon(iconName: String, color: NSColor = NSColor.secondaryLabelColor) -> NSMutableAttributedString { 36 | let image = NSImage(named: iconName)?.tint(color: color) 37 | image?.size = NSSize(width: 12, height: 12) 38 | let image1Attachment = NSTextAttachment() 39 | image1Attachment.attachmentCell = NSTextAttachmentCell(imageCell: image) 40 | image1Attachment.image = image 41 | let image1String = NSMutableAttributedString(attachment: image1Attachment) 42 | let range = NSMakeRange(0,image1String.length) 43 | image1String.addAttribute(NSAttributedString.Key.baselineOffset, value: -1.0, range: range) 44 | self.append(image1String) 45 | self.appendString(string: " ") 46 | 47 | return self 48 | } 49 | 50 | @discardableResult 51 | func appendSeparator() -> NSMutableAttributedString { 52 | self.append(NSMutableAttributedString(string: " ")) 53 | return self 54 | } 55 | 56 | @discardableResult 57 | func appendNewLine() -> NSMutableAttributedString { 58 | self.append(NSMutableAttributedString(string: "\n")) 59 | return self 60 | } 61 | } 62 | 63 | func hexColor (hex: String) -> NSColor { 64 | var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() 65 | 66 | if (cString.hasPrefix("#")) { 67 | cString.remove(at: cString.startIndex) 68 | } 69 | 70 | if ((cString.count) != 6) { 71 | return NSColor.gray 72 | } 73 | 74 | var rgbValue:UInt64 = 0 75 | Scanner(string: cString).scanHexInt64(&rgbValue) 76 | 77 | return NSColor( 78 | red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, 79 | green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, 80 | blue: CGFloat(rgbValue & 0x0000FF) / 255.0, 81 | alpha: CGFloat(1.0) 82 | ) 83 | } 84 | -------------------------------------------------------------------------------- /pullBar.xcodeproj/xcshareddata/xcschemes/pullBar.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /pullBar/Views/BottomItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BottomItemView.swift 3 | // pullBar 4 | // 5 | // Created by Casey Jones on 2022-08-18. 6 | // 7 | 8 | import SwiftUI 9 | 10 | 11 | struct BottomItemView: View { 12 | @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate 13 | 14 | var body: some View { 15 | 16 | HStack { 17 | 18 | // VStack(alignment: .leading) { 19 | 20 | Button{ 21 | print("pressed") 22 | appDelegate.refreshMenu() 23 | } label: { 24 | ZStack { 25 | Circle() 26 | .fill(Color.secondary) 27 | 28 | Image(systemName: "repeat") 29 | .font(.system(size: 15, weight: .bold, design: .rounded)) 30 | .foregroundColor(.secondary) 31 | } 32 | .padding(8) 33 | .contentShape(Circle()) 34 | } 35 | .buttonStyle(PlainButtonStyle()) 36 | .accessibilityLabel(Text("Close")) 37 | // } 38 | Spacer() 39 | HStack(alignment: .bottom){ 40 | Button{ 41 | print("pressed") 42 | appDelegate.openPrefecencesWindow(nil) 43 | 44 | } label: { 45 | ZStack { 46 | Circle() 47 | .fill(Color.secondary) 48 | 49 | Image(systemName: "xmark") 50 | .font(.system(size: 15, weight: .bold, design: .rounded)) 51 | .foregroundColor(.secondary) 52 | } 53 | .padding(8) 54 | .contentShape(Circle()) 55 | } 56 | .buttonStyle(PlainButtonStyle()) 57 | .accessibilityLabel(Text("Close")) 58 | 59 | 60 | Button{ 61 | print("pressed") 62 | appDelegate.openAboutWindow(nil) 63 | } label: { 64 | ZStack { 65 | Circle() 66 | .fill(Color.secondary) 67 | 68 | Image(systemName: "info.circle.fill") 69 | .font(.system(size: 15, weight: .bold, design: .rounded)) 70 | .foregroundColor(.secondary) 71 | } 72 | .padding(8) 73 | .contentShape(Circle()) 74 | } 75 | .buttonStyle(PlainButtonStyle()) 76 | .accessibilityLabel(Text("Close")) 77 | 78 | 79 | Button{ 80 | print("pressed") 81 | appDelegate.quit() 82 | } label: { 83 | ZStack { 84 | Circle() 85 | .fill(Color.secondary) 86 | 87 | Image(systemName: "power.circle.fill") 88 | .font(.system(size: 15, weight: .bold, design: .rounded)) 89 | .foregroundColor(.secondary) 90 | } 91 | .padding(8) 92 | .contentShape(Circle()) 93 | } 94 | .buttonStyle(PlainButtonStyle()) 95 | .accessibilityLabel(Text("Close")) 96 | } 97 | 98 | }.border(Color.pink) 99 | .frame(maxWidth: .infinity, alignment: .trailing) 100 | 101 | 102 | } 103 | } 104 | 105 | struct BottomItemView_Previews: PreviewProvider { 106 | static var previews: some View { 107 | BottomItemView() 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /pullBar/GitHub/GitHubDtos.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubDtos.swift 3 | // issueBar 4 | // 5 | // Created by Pavel Makhov on 2021-11-10. 6 | // 7 | 8 | import Foundation 9 | 10 | struct GraphQlSearchResp: Codable { 11 | var data: Data 12 | 13 | enum CodingKeys: String, CodingKey { 14 | case data 15 | } 16 | } 17 | 18 | struct Data: Codable { 19 | var search: Search 20 | 21 | enum CodingKeys: String, CodingKey { 22 | case search 23 | } 24 | } 25 | 26 | struct Search: Codable { 27 | var edges: [Edge] 28 | var issueCount: Int 29 | 30 | enum CodingKeys: String, CodingKey { 31 | case edges 32 | case issueCount 33 | } 34 | } 35 | 36 | struct Edges: Codable { 37 | var edge: [Edge] 38 | 39 | enum CodingKeys: String, CodingKey { 40 | case edge 41 | } 42 | } 43 | 44 | struct Edge: Codable { 45 | var node: Pull 46 | 47 | enum CodingKeys: String, CodingKey { 48 | case node 49 | } 50 | } 51 | 52 | struct Pull: Codable { 53 | var url: URL 54 | var updatedAt: Date 55 | var createdAt: Date 56 | var title: String 57 | var number: Int 58 | var deletions: Int? 59 | var additions: Int? 60 | var reviews: Review 61 | var author: User? 62 | var repository: Repository 63 | var commits: CommitsNodes? 64 | var labels: Nodes