├── .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 |
6 |
7 |
8 |
9 |
10 |
11 |
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 |
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 |
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
65 | var isDraft: Bool
66 | var isReadByViewer: Bool
67 |
68 | enum CodingKeys: String, CodingKey {
69 | case url
70 | case updatedAt
71 | case createdAt
72 | case title
73 | case number
74 | case deletions
75 | case additions
76 | case reviews
77 | case author
78 | case repository
79 | case commits
80 | case labels
81 | case isDraft
82 | case isReadByViewer
83 | }
84 | }
85 |
86 |
87 | struct Nodes: Codable, Hashable {
88 | var nodes: [T]
89 |
90 | enum CodingKeys: String, CodingKey {
91 | case nodes
92 | }
93 | }
94 |
95 | struct Review: Codable {
96 | var totalCount: Int
97 | var edges: [UserEdge]
98 |
99 | enum CodingKeys: String, CodingKey {
100 | case totalCount
101 | case edges
102 | }
103 | }
104 |
105 | struct UserEdge: Codable {
106 | var node: UserNode
107 |
108 | enum CondigKeys: String, CodingKey {
109 | case node
110 | }
111 | }
112 |
113 | struct UserNode: Codable {
114 | var author: User?
115 |
116 | enum CodingKeys: String, CodingKey {
117 | case author
118 | }
119 | }
120 |
121 | struct User: Codable {
122 | var login: String
123 | var avatarUrl: URL?
124 |
125 | enum CodingKeys: String, CodingKey {
126 | case login
127 | case avatarUrl
128 | }
129 |
130 | static var ghost: User {
131 | return User(login: "ghost", avatarUrl: nil)
132 | }
133 | }
134 |
135 | struct Repository: Codable {
136 | var name: String
137 |
138 | enum CodingKeys: String, CodingKey {
139 | case name
140 | }
141 | }
142 |
143 | struct CommitsNodes: Codable {
144 | var nodes: [Commit]
145 |
146 | enum CodingKeys: String, CodingKey {
147 | case nodes
148 | }
149 | }
150 |
151 | struct Commit: Codable, Hashable {
152 | var commit: CheckSuites
153 |
154 | enum CodingKeys: String, CodingKey {
155 | case commit
156 | }
157 | }
158 |
159 | struct CheckSuites: Codable, Hashable {
160 | var checkSuites: CheckSuitsNodes?
161 | var statusCheckRollup: StatusCheckRollup?
162 |
163 | enum CodingKeys: String, CodingKey {
164 | case checkSuites
165 | case statusCheckRollup
166 | }
167 | }
168 |
169 |
170 | struct CheckSuitsNodes: Codable, Hashable {
171 | var nodes: [CheckSuit]
172 |
173 | enum CodingKeys: String, CodingKey {
174 | case nodes
175 | }
176 | }
177 |
178 | struct App: Codable, Hashable {
179 | var name: String?
180 | enum CodingKeys: String, CodingKey {
181 | case name
182 | }
183 | }
184 |
185 | struct CheckSuit: Codable, Hashable {
186 | var app: App?
187 | var checkRuns: CheckRun
188 |
189 | enum CodingKeys: String, CodingKey {
190 | case checkRuns
191 | case app
192 | }
193 | }
194 |
195 | struct CheckRun: Codable, Hashable {
196 | var totalCount: Int
197 | var nodes: [Check]
198 |
199 | enum CodingKeys: String, CodingKey {
200 | case totalCount
201 | case nodes
202 | }
203 | }
204 |
205 | struct Check: Codable, Hashable {
206 | var name: String
207 | var conclusion: String?
208 | var detailsUrl: URL
209 |
210 | enum CodingKeys: String, CodingKey {
211 | case name
212 | case conclusion
213 | case detailsUrl
214 | }
215 | }
216 |
217 | struct LatestRelease: Codable {
218 |
219 | var name: String
220 | var assets: [Asset]
221 |
222 | enum CodingKeys: String, CodingKey {
223 | case name
224 | case assets
225 | }
226 | }
227 |
228 | struct Asset: Codable {
229 | var name: String
230 | var browserDownloadUrl: String
231 |
232 | enum CodingKeys: String, CodingKey {
233 | case name
234 | case browserDownloadUrl = "browser_download_url"
235 | }
236 | }
237 |
238 | struct Label: Codable, Hashable {
239 | var name: String
240 | var color: String
241 |
242 | enum CodingKeys: String, CodingKey {
243 | case name
244 | case color
245 | }
246 | }
247 |
248 | struct StatusCheckRollup: Codable, Hashable {
249 | var state: String
250 | var contexts: ContextNodes
251 |
252 | enum CodingKeys: String, CodingKey {
253 | case state
254 | case contexts
255 | }
256 | }
257 |
258 | struct ContextNodes: Codable, Hashable {
259 | var nodes: [ContextNode]
260 |
261 | enum CodingKeys: String, CodingKey {
262 | case nodes
263 | }
264 | }
265 |
266 | struct ContextNode: Codable, Hashable {
267 | var name: String?
268 | var context: String?
269 | var conclusion: String?
270 | var state: String?
271 | var title: String?
272 | var description: String?
273 | var detailsUrl: URL?
274 | var targetUrl: String?
275 |
276 | enum CodingKeys: String, CodingKey {
277 | case name
278 | case context
279 | case conclusion
280 | case state
281 | case title
282 | case description
283 | case detailsUrl
284 | case targetUrl
285 | }
286 | }
287 |
288 |
--------------------------------------------------------------------------------
/pullBar/Views/PreferencesView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GeneralTab.swift
3 | // issueBar
4 | //
5 | // Created by Pavel Makhov on 2021-11-14.
6 | //
7 |
8 | import SwiftUI
9 | import Defaults
10 | import KeychainAccess
11 | import LaunchAtLogin
12 |
13 | struct PreferencesView: View {
14 |
15 | @Default(.githubApiBaseUrl) var githubApiBaseUrl
16 | @Default(.githubUsername) var githubUsername
17 | @Default(.githubAdditionalQuery) var githubAdditionalQuery
18 | @FromKeychain(.githubToken) var githubToken
19 |
20 | @Default(.showAssigned) var showAssigned
21 | @Default(.showCreated) var showCreated
22 | @Default(.showRequested) var showRequested
23 |
24 | @Default(.showAvatar) var showAvatar
25 | @Default(.showLabels) var showLabels
26 |
27 | @Default(.refreshRate) var refreshRate
28 | @Default(.buildType) var builtType
29 | @Default(.counterType) var counterType
30 |
31 | @State private var showGhAlert = false
32 |
33 | @StateObject private var githubTokenValidator = GithubTokenValidator()
34 | // @ObservedObject private var launchAtLogin = LaunchAtLogin.observable
35 |
36 | @State private var isExpanded: Bool = false
37 |
38 | var body: some View {
39 |
40 | TabView {
41 | Form {
42 | HStack(alignment: .center) {
43 | Text("Pull Requests:").frame(width: 120, alignment: .trailing)
44 | VStack(alignment: .leading){
45 | Toggle("assigned", isOn: $showAssigned)
46 | Toggle("created", isOn: $showCreated)
47 | Toggle("review requested", isOn: $showRequested)
48 | }
49 | }
50 |
51 | HStack(alignment: .center) {
52 | Text("Build Information:").frame(width: 120, alignment: .trailing)
53 | Picker("", selection: $builtType, content: {
54 | ForEach(BuildType.allCases) { bt in
55 | Text(bt.description)
56 | }
57 | })
58 | .labelsHidden()
59 | .pickerStyle(RadioGroupPickerStyle())
60 | .frame(width: 120)
61 | }
62 |
63 | HStack(alignment: .center) {
64 | Text("Show Avatar:").frame(width: 120, alignment: .trailing)
65 | Toggle("", isOn: $showAvatar)
66 | }
67 |
68 | HStack(alignment: .center) {
69 | Text("Show Labels:").frame(width: 120, alignment: .trailing)
70 | Toggle("", isOn: $showLabels)
71 | }
72 |
73 | HStack(alignment: .center) {
74 | Text("Refresh Rate:").frame(width: 120, alignment: .trailing)
75 | Picker("", selection: $refreshRate, content: {
76 | Text("1 minute").tag(1)
77 | Text("5 minutes").tag(5)
78 | Text("10 minutes").tag(10)
79 | Text("15 minutes").tag(15)
80 | Text("30 minutes").tag(30)
81 | }).labelsHidden()
82 | .pickerStyle(MenuPickerStyle())
83 | .frame(width: 100)
84 | }
85 |
86 | HStack(alignment: .center) {
87 | Text("Launch at login:").frame(width: 120, alignment: .trailing)
88 | LaunchAtLogin.Toggle {
89 | Text("")
90 | }
91 | }
92 |
93 | }
94 | .padding(8)
95 | .frame(maxWidth: .infinity)
96 | .tabItem{Text("General")}
97 |
98 | Form {
99 | HStack(alignment: .center) {
100 | Text("API Base URL:").frame(width: 120, alignment: .trailing)
101 | TextField("", text: $githubApiBaseUrl)
102 | .textFieldStyle(RoundedBorderTextFieldStyle())
103 | .disableAutocorrection(true)
104 | .textContentType(.password)
105 | .frame(width: 200)
106 | }
107 | HStack(alignment: .center) {
108 | Text("Username:").frame(width: 120, alignment: .trailing)
109 | TextField("", text: $githubUsername)
110 | .textFieldStyle(RoundedBorderTextFieldStyle())
111 | .disableAutocorrection(true)
112 | .textContentType(.password)
113 | .frame(width: 200)
114 | }
115 |
116 | HStack(alignment: .center) {
117 | Text("Token:").frame(width: 120, alignment: .trailing)
118 | VStack(alignment: .leading) {
119 | HStack() {
120 | SecureField("", text: $githubToken)
121 | .textFieldStyle(RoundedBorderTextFieldStyle())
122 | .overlay(
123 | Image(systemName: githubTokenValidator.iconName).foregroundColor(githubTokenValidator.iconColor)
124 | .frame(maxWidth: .infinity, alignment: .trailing)
125 | .padding(.trailing, 8)
126 | )
127 | .frame(width: 380)
128 | .onChange(of: githubToken) { _ in
129 | githubTokenValidator.validate()
130 | }
131 | Button {
132 | githubTokenValidator.validate()
133 | } label: {
134 | Image(systemName: "repeat")
135 | }
136 | .help("Retry")
137 | }
138 | Text("[Generate](https://github.com/settings/tokens/new?scopes=repo) a personal access token, make sure to select **repo** scope")
139 | .font(.footnote)
140 | .padding(.leading, 8)
141 | .foregroundColor(.secondary)
142 | }
143 | }
144 | }
145 | .padding()
146 | .frame(maxWidth: .infinity)
147 | .onAppear() {
148 | githubTokenValidator.validate()
149 | }
150 | .tabItem{Text("Authentication")}
151 |
152 | Form {
153 | HStack(alignment: .center) {
154 | VStack(alignment: .leading) {
155 | Text("Counter:")
156 | Text("Number of pull requests next to the icon")
157 | .font(.subheadline)
158 | .foregroundColor(.secondary)
159 | }
160 | Picker("", selection: $counterType, content: {
161 | ForEach(CounterType.allCases) { bt in
162 | Text(bt.description)
163 | }
164 | })
165 | .labelsHidden()
166 | .pickerStyle(RadioGroupPickerStyle())
167 | }
168 | }
169 | .padding(8)
170 | .frame(maxWidth: .infinity)
171 | .tabItem{Text("Menubar icon")}
172 |
173 | Form {
174 | HStack(alignment: .top) {
175 | Text("Additional Query:").frame(width: 120, alignment: .trailing)
176 | TextField("", text: $githubAdditionalQuery)
177 | .textFieldStyle(RoundedBorderTextFieldStyle())
178 | .disableAutocorrection(true)
179 | .textContentType(.password)
180 | .frame(width: 380)
181 |
182 | }
183 | Text("See the GitHub [search documentation](https://docs.github.com/en/search-github/getting-started-with-searching-on-github/understanding-the-search-syntax) for more information on advanced queries")
184 | .font(.footnote)
185 | .padding(.leading, 8)
186 | .foregroundColor(.secondary)
187 | }.padding(8)
188 | .frame(maxWidth: .infinity)
189 | .tabItem{Text("Advanced")}
190 |
191 | }
192 | .frame(width: 600)
193 | .padding()
194 |
195 | }
196 | }
197 |
198 | struct PreferencesView_Previews: PreviewProvider {
199 | static var previews: some View {
200 | PreferencesView()
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/pullBar/GitHub/GitHubClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GitHubClient.swift
3 | // issueBar
4 | //
5 | // Created by Pavel Makhov on 2021-11-09.
6 | //
7 |
8 | import Foundation
9 | import Defaults
10 | import Alamofire
11 | import KeychainAccess
12 |
13 | public class GitHubClient {
14 |
15 | @FromKeychain(.githubToken) var githubToken
16 |
17 | func getAssignedPulls(completion:@escaping (([Edge]) -> Void)) -> Void {
18 |
19 | if (Defaults[.githubUsername] == "" || githubToken == "") {
20 | completion([Edge]())
21 | }
22 |
23 | let headers: HTTPHeaders = [
24 | .authorization(bearerToken: githubToken),
25 | .accept("application/json")
26 | ]
27 |
28 | let graphQlQuery = buildGraphQlQuery(queryString: "is:open is:pr assignee:\(Defaults[.githubUsername]) archived:false \(Defaults[.githubAdditionalQuery])")
29 |
30 | let parameters = [
31 | "query": graphQlQuery,
32 | "variables":[]
33 | ] as [String: Any]
34 |
35 | AF.request(Defaults[.githubApiBaseUrl] + "/graphql", method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers)
36 | .validate(statusCode: 200..<300)
37 | .responseDecodable(of: GraphQlSearchResp.self, decoder: GithubDecoder()) { response in
38 | switch response.result {
39 | case .success(let prs):
40 | completion(prs.data.search.edges)
41 | case .failure(let error):
42 | sendNotification(body: error.localizedDescription)
43 | completion([Edge]())
44 | print(error)
45 | }
46 | }
47 | }
48 |
49 | func getCreatedPulls(completion:@escaping (([Edge]) -> Void)) -> Void {
50 |
51 | if (Defaults[.githubUsername] == "" || githubToken == "") {
52 | completion([Edge]())
53 | }
54 |
55 | let headers: HTTPHeaders = [
56 | .authorization(bearerToken: githubToken),
57 | .accept("application/json")
58 | ]
59 | let graphQlQuery = buildGraphQlQuery(queryString: "is:open is:pr author:\(Defaults[.githubUsername]) archived:false \(Defaults[.githubAdditionalQuery])")
60 |
61 | let parameters = [
62 | "query": graphQlQuery,
63 | "variables":[]
64 | ] as [String: Any]
65 |
66 | AF.request(Defaults[.githubApiBaseUrl] + "/graphql", method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers)
67 | .validate(statusCode: 200..<300)
68 | .responseDecodable(of: GraphQlSearchResp.self, decoder: GithubDecoder()) { response in
69 | switch response.result {
70 | case .success(let prs):
71 | completion(prs.data.search.edges)
72 | case .failure(let error):
73 | sendNotification(body: error.localizedDescription)
74 | print(error)
75 | completion([Edge]())
76 | }
77 | }
78 | }
79 |
80 | func getReviewRequestedPulls(completion:@escaping (([Edge]) -> Void)) -> Void {
81 | if (Defaults[.githubUsername] == "" || githubToken == "") {
82 | completion([Edge]())
83 | }
84 |
85 | let headers: HTTPHeaders = [
86 | .authorization(bearerToken: githubToken),
87 | .accept("application/json")
88 | ]
89 | let graphQlQuery = buildGraphQlQuery(queryString: "is:open is:pr review-requested:\(Defaults[.githubUsername]) archived:false \(Defaults[.githubAdditionalQuery])")
90 |
91 | let parameters = [
92 | "query": graphQlQuery,
93 | "variables":[]
94 | ] as [String: Any]
95 |
96 | AF.request(Defaults[.githubApiBaseUrl] + "/graphql", method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers)
97 | .validate(statusCode: 200..<300)
98 | .responseDecodable(of: GraphQlSearchResp.self, decoder: GithubDecoder()) { response in
99 | switch response.result {
100 | case .success(let prs):
101 | completion(prs.data.search.edges)
102 | case .failure(let error):
103 | sendNotification(body: error.localizedDescription)
104 | completion([Edge]())
105 | }
106 | }
107 | }
108 |
109 | private func buildGraphQlQuery(queryString: String) -> String {
110 |
111 | var build = ""
112 |
113 | switch Defaults[.buildType] {
114 | case .checks:
115 | build = """
116 | commits(last: 1) {
117 | nodes {
118 | commit {
119 | checkSuites(first: 10) {
120 | nodes {
121 | app {
122 | name
123 | }
124 | checkRuns(first: 10) {
125 | totalCount
126 | nodes {
127 | name
128 | conclusion
129 | detailsUrl
130 | }
131 | }
132 | }
133 | }
134 | }
135 | }
136 | }
137 | """
138 | case .commitStatus:
139 | build = """
140 | commits(last: 1) {
141 | nodes {
142 | commit {
143 | statusCheckRollup {
144 | state
145 | contexts (first: 20) {
146 | nodes {
147 | ... on StatusContext {
148 | context
149 | description
150 | state
151 | targetUrl
152 | description
153 | }
154 | ... on CheckRun {
155 | name
156 | conclusion
157 | detailsUrl
158 | title
159 | }
160 | }
161 | }
162 | }
163 | }
164 | }
165 | }
166 | """
167 | default:
168 | build = ""
169 | }
170 |
171 |
172 |
173 | return """
174 | {
175 | search(query: "\(queryString)", type: ISSUE, first: 30) {
176 | issueCount
177 | edges {
178 | node {
179 | ... on PullRequest {
180 | number
181 | createdAt
182 | updatedAt
183 | title
184 | headRefName
185 | url
186 | deletions
187 | additions
188 | isDraft
189 | isReadByViewer
190 | author {
191 | login
192 | avatarUrl
193 | }
194 | repository {
195 | name
196 | }
197 | labels(first: 5) {
198 | nodes {
199 | name
200 | color
201 | }
202 | }
203 | reviews(states: APPROVED, first: 10) {
204 | totalCount
205 | edges {
206 | node {
207 | author {
208 | login
209 | }
210 | }
211 | }
212 | }
213 | \(build)
214 | }
215 | }
216 | }
217 | }
218 | }
219 |
220 |
221 | """
222 | }
223 |
224 | func getUser(completion: @escaping (User?) -> Void) {
225 | let headers: HTTPHeaders = [
226 | .authorization(bearerToken: githubToken),
227 | .contentType("application/json"),
228 | .accept("application/json")
229 | ]
230 |
231 | AF.request(Defaults[.githubApiBaseUrl] + "/user",
232 | method: .get,
233 | headers: headers)
234 | .validate(statusCode: 200..<300)
235 | .cacheResponse(using: ResponseCacher(behavior: .doNotCache))
236 | .responseDecodable(of: User.self) { response in
237 | switch response.result {
238 | case .success(let repo):
239 | completion(repo)
240 | case .failure(let error):
241 | completion(nil)
242 | print(error)
243 | }
244 | }
245 | }
246 |
247 | func getLatestRelease(completion:@escaping (((LatestRelease?) -> Void))) -> Void {
248 | let headers: HTTPHeaders = [
249 | .authorization(username: Defaults[.githubUsername], password: githubToken),
250 | .contentType("application/json"),
251 | .accept("application/json")
252 | ]
253 | AF.request("https://api.github.com/repos/menubar-apps/PullBar/releases/latest",
254 | method: .get,
255 | encoding: JSONEncoding.default,
256 | headers: headers)
257 | .validate(statusCode: 200..<300)
258 | .responseDecodable(of: LatestRelease.self) { response in
259 | switch response.result {
260 | case .success(let latestRelease):
261 | completion(latestRelease)
262 | case .failure(let error):
263 | completion(nil)
264 | if let data = response.data {
265 | let json = String(data: data, encoding: String.Encoding.utf8)
266 | // print("Failure Response: \(json)")
267 | }
268 | sendNotification(body: error.localizedDescription)
269 | }
270 | }
271 | }
272 | }
273 |
274 | class GithubDecoder: JSONDecoder {
275 | let dateFormatter = DateFormatter()
276 |
277 | override init() {
278 | super.init()
279 | dateDecodingStrategy = .iso8601
280 | }
281 | }
282 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/pullBar/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // pullBar
4 | //
5 | // Created by Pavel Makhov on 2021-11-15.
6 | //
7 |
8 | import Cocoa
9 | import Defaults
10 | import SwiftUI
11 | import Foundation
12 | import KeychainAccess
13 |
14 | @main
15 | class AppDelegate: NSObject, NSApplicationDelegate {
16 |
17 | @FromKeychain(.githubToken) var githubToken
18 |
19 | let ghClient = GitHubClient()
20 | var statusBarItem: NSStatusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
21 | let menu: NSMenu = NSMenu()
22 |
23 | var preferencesWindow: NSWindow!
24 | var aboutWindow: NSWindow!
25 |
26 | var timer: Timer? = nil
27 |
28 | func applicationDidFinishLaunching(_ aNotification: Notification) {
29 | NotificationCenter.default.addObserver(self, selector: #selector(AppDelegate.windowClosed), name: NSWindow.willCloseNotification, object: nil)
30 |
31 | guard let statusButton = statusBarItem.button else { return }
32 | let icon = NSImage(named: "git-pull-request")
33 | let size = NSSize(width: 16, height: 16)
34 | icon?.isTemplate = true
35 | icon?.size = size
36 | statusButton.image = icon
37 | statusButton.imagePosition = NSControl.ImagePosition.imageLeft
38 |
39 | statusBarItem.menu = menu
40 |
41 | timer = Timer.scheduledTimer(
42 | timeInterval: Double(Defaults[.refreshRate] * 60),
43 | target: self,
44 | selector: #selector(refreshMenu),
45 | userInfo: nil,
46 | repeats: true
47 | )
48 | timer?.fire()
49 | RunLoop.main.add(timer!, forMode: .common)
50 | NSApp.setActivationPolicy(.accessory)
51 |
52 |
53 | // Insert code here to initialize your application
54 | }
55 |
56 | func applicationWillTerminate(_ aNotification: Notification) {
57 | // Insert code here to tear down your application
58 | }
59 |
60 | func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
61 | return true
62 | }
63 |
64 | @objc
65 | func openLink(_ sender: NSMenuItem) {
66 | NSWorkspace.shared.open(sender.representedObject as! URL)
67 | }
68 |
69 | }
70 |
71 | extension AppDelegate {
72 | @objc
73 | func refreshMenu() {
74 | NSLog("Refreshing menu")
75 | self.menu.removeAllItems()
76 |
77 | if (Defaults[.githubUsername] == "" || githubToken == "") {
78 | addMenuFooterItems()
79 | return
80 | }
81 |
82 |
83 | var assignedPulls: [Edge]? = []
84 | var createdPulls: [Edge]? = []
85 | var reviewRequestedPulls: [Edge]? = []
86 |
87 |
88 | let group = DispatchGroup()
89 |
90 | if Defaults[.showAssigned] {
91 | group.enter()
92 | ghClient.getAssignedPulls() { pulls in
93 | assignedPulls?.append(contentsOf: pulls)
94 | group.leave()
95 | }
96 | }
97 |
98 | if Defaults[.showCreated] {
99 | group.enter()
100 | ghClient.getCreatedPulls() { pulls in
101 | createdPulls?.append(contentsOf: pulls)
102 | group.leave()
103 | }
104 | }
105 |
106 | if Defaults[.showRequested] {
107 | group.enter()
108 | ghClient.getReviewRequestedPulls() { pulls in
109 | reviewRequestedPulls?.append(contentsOf: pulls)
110 | group.leave()
111 | }
112 | }
113 |
114 | group.notify(queue: .main) {
115 |
116 | if let assignedPulls = assignedPulls, let createdPulls = createdPulls, let reviewRequestedPulls = reviewRequestedPulls {
117 | self.statusBarItem.button?.title = ""
118 |
119 | if Defaults[.showAssigned] && !assignedPulls.isEmpty {
120 | if Defaults[.counterType] == .assigned {
121 | self.statusBarItem.button?.title = String(assignedPulls.count)
122 | }
123 |
124 | self.menu.addItem(NSMenuItem(title: "Assigned (\(assignedPulls.count))", action: nil, keyEquivalent: ""))
125 | for pull in assignedPulls {
126 | self.menu.addItem(self.createMenuItem(pull: pull))
127 | }
128 | self.menu.addItem(.separator())
129 | }
130 |
131 | if Defaults[.showCreated] && !createdPulls.isEmpty {
132 | if Defaults[.counterType] == .created {
133 | self.statusBarItem.button?.title = String(createdPulls.count)
134 | }
135 |
136 | self.menu.addItem(NSMenuItem(title: "Created (\(createdPulls.count))", action: nil, keyEquivalent: ""))
137 | for pull in createdPulls {
138 | self.menu.addItem(self.createMenuItem(pull: pull))
139 | }
140 | self.menu.addItem(.separator())
141 | }
142 |
143 | if Defaults[.showRequested] && !reviewRequestedPulls.isEmpty {
144 | if Defaults[.counterType] == .reviewRequested {
145 | self.statusBarItem.button?.title = String(reviewRequestedPulls.count)
146 | }
147 |
148 | self.menu.addItem(NSMenuItem(title: "Review Requested (\(reviewRequestedPulls.count))", action: nil, keyEquivalent: ""))
149 | for pull in reviewRequestedPulls {
150 | self.menu.addItem(self.createMenuItem(pull: pull))
151 | }
152 | self.menu.addItem(.separator())
153 | }
154 |
155 |
156 | self.addMenuFooterItems()
157 | }
158 | }
159 | }
160 |
161 | func createMenuItem(pull: Edge) -> NSMenuItem {
162 | let issueItem = NSMenuItem(title: "", action: #selector(self.openLink), keyEquivalent: "")
163 |
164 | let issueItemTitle = NSMutableAttributedString(string: "")
165 | .appendString(string: pull.node.isReadByViewer ? "" : "⏺ ", color: .systemBlue)
166 |
167 | if (pull.node.isDraft) {
168 | issueItemTitle
169 | .appendIcon(iconName: "git-draft-pull-request", color: NSColor.secondaryLabelColor)
170 | }
171 |
172 | issueItemTitle
173 | .appendString(string: pull.node.title.trunc(length: 50), color: NSColor(.primary))
174 | .appendString(string: " #" + String(pull.node.number))
175 | .appendSeparator()
176 |
177 | issueItemTitle.appendNewLine()
178 |
179 | issueItemTitle
180 | .appendIcon(iconName: "repo")
181 | .appendString(string: pull.node.repository.name)
182 | .appendSeparator()
183 | .appendIcon(iconName: "person")
184 | .appendString(string: pull.node.author?.login ?? User.ghost.login)
185 |
186 | if !pull.node.labels.nodes.isEmpty && Defaults[.showLabels] {
187 | issueItemTitle
188 | .appendNewLine()
189 | .appendIcon(iconName: "tag", color: NSColor(.secondary))
190 | for label in pull.node.labels.nodes {
191 | issueItemTitle
192 | .appendString(string: label.name, color: hexColor(hex: label.color), fontSize: NSFont.smallSystemFontSize)
193 | .appendSeparator()
194 | }
195 | }
196 |
197 | issueItemTitle.appendNewLine()
198 |
199 | let approvedByMe = pull.node.reviews.edges.contains{ $0.node.author?.login == Defaults[.githubUsername] }
200 | issueItemTitle
201 | .appendIcon(iconName: "check-circle", color: approvedByMe ? NSColor(named: "green")! : NSColor.secondaryLabelColor)
202 | .appendString(string: " " + String(pull.node.reviews.totalCount))
203 | .appendSeparator()
204 | .appendString(string: "+" + String(pull.node.additions ?? 0), color: NSColor(named: "green")!)
205 | .appendString(string: " -" + String(pull.node.deletions ?? 0), color: NSColor(named: "red")!)
206 | .appendSeparator()
207 | .appendIcon(iconName: "calendar")
208 | .appendString(string: pull.node.createdAt.getElapsedInterval())
209 |
210 | if Defaults[.showAvatar] {
211 | // Set default image initially
212 | let defaultImage = NSImage(named: "person")!
213 | let resizedDefaultImage = resizeImage(image: defaultImage, size: NSSize(width: 36.0, height: 36.0))
214 | issueItem.image = resizedDefaultImage
215 |
216 | // Load avatar asynchronously if available
217 | if let author = pull.node.author, let imageURL = author.avatarUrl {
218 | NSImage.loadImageAsync(fromURL: imageURL) { loadedImage in
219 | guard let loadedImage = loadedImage else { return }
220 |
221 | loadedImage.cacheMode = NSImage.CacheMode.always
222 | let resizedImage = self.resizeImage(image: loadedImage, size: NSSize(width: 36.0, height: 36.0))
223 | issueItem.image = resizedImage
224 | }
225 | }
226 | }
227 |
228 |
229 | if let commits = pull.node.commits {
230 |
231 | if let checkSuites = commits.nodes[0].commit.checkSuites {
232 |
233 | if checkSuites.nodes.count > 0 {
234 | issueItem.submenu = NSMenu()
235 | issueItemTitle
236 | .appendSeparator()
237 | .appendIcon(iconName: "checklist", color: NSColor.secondaryLabelColor)
238 | }
239 | for checkSuite in checkSuites.nodes {
240 |
241 | if checkSuite.checkRuns.nodes.count > 0 {
242 | issueItem.submenu?.addItem(withTitle: checkSuite.app?.name ?? "empty", action: nil, keyEquivalent: "")
243 | }
244 | for check in checkSuite.checkRuns.nodes {
245 |
246 | let buildItem = NSMenuItem(title: check.name, action: #selector(self.openLink), keyEquivalent: "")
247 | buildItem.representedObject = check.detailsUrl
248 | buildItem.toolTip = check.conclusion
249 | if check.conclusion == "SUCCESS" {
250 | buildItem.image = NSImage(named: "check-circle-fill")!.tint(color: NSColor(named: "green")!)
251 | issueItemTitle.appendIcon(iconName: "dot-fill", color: NSColor(named: "green")!)
252 | } else if check.conclusion == "FAILURE" {
253 | buildItem.image = NSImage(named: "x-circle-fill")!.tint(color: NSColor(named: "red")!)
254 | issueItemTitle.appendIcon(iconName: "dot-fill", color: NSColor(named: "red")!)
255 | } else if check.conclusion == "ACTION_REQUIRED" {
256 | buildItem.image = NSImage(named: "issue-draft")!.tint(color: NSColor(named: "yellow")!)
257 | issueItemTitle.appendIcon(iconName: "dot-fill", color: NSColor(named: "yellow")!)
258 | } else {
259 | buildItem.image = NSImage(named: "question")!.tint(color: NSColor.gray)
260 | issueItemTitle.appendIcon(iconName: "dot-fill", color: NSColor.gray)
261 | }
262 |
263 | issueItem.submenu?.addItem(buildItem)
264 | }
265 | }
266 | }
267 |
268 | else if let statusCheckRollup = commits.nodes[0].commit.statusCheckRollup {
269 |
270 | if statusCheckRollup.contexts.nodes.count > 0 {
271 | issueItem.submenu = NSMenu()
272 | issueItemTitle
273 | .appendSeparator()
274 | .appendIcon(iconName: "checklist", color: NSColor.secondaryLabelColor)
275 | }
276 |
277 | for check in statusCheckRollup.contexts.nodes {
278 | let itemTitle = NSMutableAttributedString()
279 | itemTitle.appendString(string: check.name ?? check.context ?? "", color: NSColor(.primary))
280 | itemTitle.appendNewLine()
281 | .appendString(string: check.description ?? check.title ?? "", color: NSColor(.secondary))
282 |
283 | let buildItem = NSMenuItem(title: "", action: #selector(AppDelegate.openLink), keyEquivalent: "")
284 | buildItem.attributedTitle = itemTitle
285 |
286 | buildItem.representedObject = check.detailsUrl ?? URL.init(string:check.targetUrl ?? "")
287 |
288 | buildItem.toolTip = check.conclusion ?? check.state ?? ""
289 |
290 | let status = check.conclusion ?? check.state ?? ""
291 | switch status {
292 | case "SUCCESS":
293 | buildItem.image = NSImage(named: "check-circle-fill")!.tint(color: NSColor(named: "green")!)
294 | issueItemTitle.appendIcon(iconName: "dot-fill", color: NSColor(named: "green")!)
295 | case "FAILURE":
296 | buildItem.image = NSImage(named: "x-circle-fill")!.tint(color: NSColor(named: "red")!)
297 | issueItemTitle.appendIcon(iconName: "dot-fill", color: NSColor(named: "red")!)
298 | case "PENDING":
299 | buildItem.image = NSImage(named: "issue-draft")!.tint(color: NSColor(named: "yellow")!)
300 | issueItemTitle.appendIcon(iconName: "dot-fill", color: NSColor(named: "yellow")!)
301 | default:
302 | buildItem.image = NSImage(named: "question")!.tint(color: NSColor.gray)
303 | issueItemTitle.appendIcon(iconName: "dot-fill", color: NSColor.gray)
304 |
305 | }
306 | issueItem.submenu?.addItem(buildItem)
307 | }
308 | }
309 | }
310 |
311 | issueItem.attributedTitle = issueItemTitle
312 | if pull.node.title.count > 50 {
313 | issueItem.toolTip = pull.node.title
314 | }
315 | issueItem.representedObject = pull.node.url
316 |
317 | return issueItem
318 | }
319 |
320 | func addMenuFooterItems() {
321 | self.menu.addItem(withTitle: "Refresh", action: #selector(self.refreshMenu), keyEquivalent: "")
322 | self.menu.addItem(.separator())
323 | self.menu.addItem(withTitle: "Preferences...", action: #selector(self.openPrefecencesWindow), keyEquivalent: "")
324 | // Remove for app store release
325 | // self.menu.addItem(withTitle: "Check for updates...", action: #selector(self.checkForUpdates), keyEquivalent: "")
326 | self.menu.addItem(withTitle: "About PullBar", action: #selector(self.openAboutWindow), keyEquivalent: "")
327 | self.menu.addItem(withTitle: "Quit", action: #selector(self.quit), keyEquivalent: "")
328 | }
329 |
330 | @objc
331 | func openPrefecencesWindow(_: NSStatusBarButton?) {
332 | NSLog("Open preferences window")
333 | let contentView = PreferencesView()
334 | if preferencesWindow != nil {
335 | preferencesWindow.close()
336 | }
337 | preferencesWindow = NSWindow(
338 | contentRect: NSRect(x: 0, y: 0, width: 0, height: 0),
339 | styleMask: [.closable, .titled],
340 | backing: .buffered,
341 | defer: false
342 | )
343 |
344 | preferencesWindow.title = "Preferences"
345 | preferencesWindow.contentView = NSHostingView(rootView: contentView)
346 | preferencesWindow.makeKeyAndOrderFront(nil)
347 | preferencesWindow.styleMask.remove(.resizable)
348 |
349 | // allow the preference window can be focused automatically when opened
350 | NSApplication.shared.activate(ignoringOtherApps: true)
351 |
352 | let controller = NSWindowController(window: preferencesWindow)
353 | controller.showWindow(self)
354 |
355 | preferencesWindow.center()
356 | preferencesWindow.orderFrontRegardless()
357 | }
358 |
359 | @objc
360 | func openAboutWindow(_: NSStatusBarButton?) {
361 | NSLog("Open about window")
362 | let contentView = AboutView()
363 | if aboutWindow != nil {
364 | aboutWindow.close()
365 | }
366 | aboutWindow = NSWindow(
367 | contentRect: NSRect(x: 0, y: 0, width: 240, height: 500),
368 | styleMask: [.closable, .titled],
369 | backing: .buffered,
370 | defer: false
371 | )
372 |
373 | aboutWindow.title = "About"
374 | aboutWindow.contentView = NSHostingView(rootView: contentView)
375 | aboutWindow.makeKeyAndOrderFront(nil)
376 | aboutWindow.styleMask.remove(.resizable)
377 |
378 | // allow the preference window can be focused automatically when opened
379 | NSApplication.shared.activate(ignoringOtherApps: true)
380 |
381 | let controller = NSWindowController(window: aboutWindow)
382 | controller.showWindow(self)
383 |
384 | aboutWindow.center()
385 | aboutWindow.orderFrontRegardless()
386 | }
387 |
388 | @objc
389 | func windowClosed(notification: NSNotification) {
390 | let window = notification.object as? NSWindow
391 | if let windowTitle = window?.title {
392 | if (windowTitle == "Preferences") {
393 | timer?.invalidate()
394 | timer = Timer.scheduledTimer(
395 | timeInterval: Double(Defaults[.refreshRate] * 60),
396 | target: self,
397 | selector: #selector(refreshMenu),
398 | userInfo: nil,
399 | repeats: true
400 | )
401 | timer?.fire()
402 | }
403 | }
404 | }
405 |
406 | @objc
407 | func quit() {
408 | NSLog("User click Quit")
409 | NSApplication.shared.terminate(self)
410 | }
411 |
412 | @objc
413 | func checkForUpdates(_: NSStatusBarButton?) {
414 | let currentVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String
415 | ghClient.getLatestRelease { latestRelease in
416 | if let latestRelease = latestRelease {
417 | let versionComparison = currentVersion.compare(latestRelease.name.replacingOccurrences(of: "v", with: ""), options: .numeric)
418 | if versionComparison == .orderedAscending {
419 | self.downloadNewVersionDialog(link: latestRelease.assets[0].browserDownloadUrl)
420 | } else {
421 | self.dialogWithText(text: "You have the latest version installed!")
422 | }
423 | }
424 | }
425 | }
426 |
427 | func dialogWithText(text: String) -> Void {
428 | let alert = NSAlert()
429 | alert.messageText = text
430 | alert.alertStyle = .informational
431 | alert.addButton(withTitle: "OK")
432 | alert.runModal()
433 | }
434 | func downloadNewVersionDialog(link: String) -> Void {
435 | let alert = NSAlert()
436 | alert.messageText = "New version is available!"
437 | alert.alertStyle = .informational
438 | alert.addButton(withTitle: "Download")
439 | alert.addButton(withTitle: "Cancel")
440 | let pressedButton = alert.runModal()
441 | if (pressedButton == .alertFirstButtonReturn) {
442 | NSWorkspace.shared.open(URL(string: link)!)
443 | }
444 | }
445 |
446 | /// Resizes an NSImage to the specified size
447 | func resizeImage(image: NSImage, size: NSSize) -> NSImage {
448 | if image.size.height == size.height && image.size.width == size.width {
449 | return image
450 | }
451 |
452 | let newImage = NSImage(size: size)
453 | newImage.lockFocus()
454 | image.draw(in: NSRect(origin: .zero, size: size),
455 | from: NSRect(origin: .zero, size: image.size),
456 | operation: .sourceOver,
457 | fraction: 1.0)
458 | newImage.unlockFocus()
459 | return newImage
460 | }
461 | }
462 |
--------------------------------------------------------------------------------
/pullBar.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 55;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 76181DD52D7CC83000D4D7CB /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76181DD42D7CC83000D4D7CB /* AppView.swift */; };
11 | 76181DD72D7CC84800D4D7CB /* AppPromotionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76181DD62D7CC84800D4D7CB /* AppPromotionView.swift */; };
12 | 761B97072D7CCDA70000378A /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = 761B97062D7CCDA70000378A /* LaunchAtLogin */; };
13 | 768A4C5528AF253C004E557E /* BottomItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 768A4C5428AF253C004E557E /* BottomItemView.swift */; };
14 | 769F4E1B2765A2B900594911 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 769F4E1A2765A2B900594911 /* AboutView.swift */; };
15 | 769F4E7B277B97B300594911 /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 769F4E7A277B97B300594911 /* Notifications.swift */; };
16 | 769F4E7D277CAFAD00594911 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 769F4E7C277CAFAD00594911 /* StringExtensions.swift */; };
17 | 76C6AA5B2C98E809003472DA /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = 76C6AA5A2C98E809003472DA /* Defaults */; };
18 | 76D5F9E428D28958009EBD80 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 76D5F9E328D28958009EBD80 /* KeychainAccess */; };
19 | 76D5F9E628D28A29009EBD80 /* Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76D5F9E528D28A29009EBD80 /* Keychain.swift */; };
20 | 76F7DAD028A88CAB0086A3B0 /* GithubTokenValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76F7DACF28A88CAB0086A3B0 /* GithubTokenValidator.swift */; };
21 | 8C55444B274AD7B20079CA42 /* DateExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C55444A274AD7B20079CA42 /* DateExtensions.swift */; };
22 | 8C55444D274AD9E00079CA42 /* NSImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C55444C274AD9E00079CA42 /* NSImageExtensions.swift */; };
23 | 8CECBBE32742AAF900A2802D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CECBBE22742AAF900A2802D /* AppDelegate.swift */; };
24 | 8CECBBE72742AAFB00A2802D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8CECBBE62742AAFB00A2802D /* Assets.xcassets */; };
25 | 8CECBBEA2742AAFB00A2802D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8CECBBE82742AAFB00A2802D /* Main.storyboard */; };
26 | 8CECBBF42742AB3F00A2802D /* GitHubClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CECBBF22742AB3F00A2802D /* GitHubClient.swift */; };
27 | 8CECBBF52742AB3F00A2802D /* GitHubDtos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CECBBF32742AB3F00A2802D /* GitHubDtos.swift */; };
28 | 8CECBBFB2742ABE600A2802D /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 8CECBBFA2742ABE600A2802D /* Alamofire */; };
29 | 8CECBBFE2742AC0800A2802D /* DefaultsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CECBBFD2742AC0800A2802D /* DefaultsExtensions.swift */; };
30 | 8CECBC022742B3A000A2802D /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CECBC002742B3A000A2802D /* PreferencesView.swift */; };
31 | 8CECBC0927436C2200A2802D /* NSMutableAttributedStringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CECBC0827436C2200A2802D /* NSMutableAttributedStringExtensions.swift */; };
32 | /* End PBXBuildFile section */
33 |
34 | /* Begin PBXFileReference section */
35 | 76181DD42D7CC83000D4D7CB /* AppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppView.swift; sourceTree = ""; };
36 | 76181DD62D7CC84800D4D7CB /* AppPromotionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPromotionView.swift; sourceTree = ""; };
37 | 768A4C5428AF253C004E557E /* BottomItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomItemView.swift; sourceTree = ""; };
38 | 769F4E1A2765A2B900594911 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; };
39 | 769F4E7A277B97B300594911 /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = ""; };
40 | 769F4E7C277CAFAD00594911 /* StringExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; };
41 | 76D5F9E528D28A29009EBD80 /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = ""; };
42 | 76F7DACF28A88CAB0086A3B0 /* GithubTokenValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GithubTokenValidator.swift; sourceTree = ""; };
43 | 8C55444A274AD7B20079CA42 /* DateExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtensions.swift; sourceTree = ""; };
44 | 8C55444C274AD9E00079CA42 /* NSImageExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImageExtensions.swift; sourceTree = ""; };
45 | 8CECBBDF2742AAF800A2802D /* pullBar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = pullBar.app; sourceTree = BUILT_PRODUCTS_DIR; };
46 | 8CECBBE22742AAF900A2802D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
47 | 8CECBBE62742AAFB00A2802D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
48 | 8CECBBE92742AAFB00A2802D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
49 | 8CECBBEB2742AAFB00A2802D /* pullBar.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = pullBar.entitlements; sourceTree = ""; };
50 | 8CECBBF22742AB3F00A2802D /* GitHubClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubClient.swift; sourceTree = ""; };
51 | 8CECBBF32742AB3F00A2802D /* GitHubDtos.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitHubDtos.swift; sourceTree = ""; };
52 | 8CECBBFD2742AC0800A2802D /* DefaultsExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultsExtensions.swift; sourceTree = ""; };
53 | 8CECBC002742B3A000A2802D /* PreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = ""; };
54 | 8CECBC0827436C2200A2802D /* NSMutableAttributedStringExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSMutableAttributedStringExtensions.swift; sourceTree = ""; };
55 | /* End PBXFileReference section */
56 |
57 | /* Begin PBXFrameworksBuildPhase section */
58 | 8CECBBDC2742AAF800A2802D /* Frameworks */ = {
59 | isa = PBXFrameworksBuildPhase;
60 | buildActionMask = 2147483647;
61 | files = (
62 | 76C6AA5B2C98E809003472DA /* Defaults in Frameworks */,
63 | 8CECBBFB2742ABE600A2802D /* Alamofire in Frameworks */,
64 | 761B97072D7CCDA70000378A /* LaunchAtLogin in Frameworks */,
65 | 76D5F9E428D28958009EBD80 /* KeychainAccess in Frameworks */,
66 | );
67 | runOnlyForDeploymentPostprocessing = 0;
68 | };
69 | /* End PBXFrameworksBuildPhase section */
70 |
71 | /* Begin PBXGroup section */
72 | 76C6AA592C98E809003472DA /* Frameworks */ = {
73 | isa = PBXGroup;
74 | children = (
75 | );
76 | name = Frameworks;
77 | sourceTree = "";
78 | };
79 | 8CECBBD62742AAF800A2802D = {
80 | isa = PBXGroup;
81 | children = (
82 | 8CECBBE12742AAF800A2802D /* pullBar */,
83 | 8CECBBE02742AAF800A2802D /* Products */,
84 | 76C6AA592C98E809003472DA /* Frameworks */,
85 | );
86 | sourceTree = "";
87 | };
88 | 8CECBBE02742AAF800A2802D /* Products */ = {
89 | isa = PBXGroup;
90 | children = (
91 | 8CECBBDF2742AAF800A2802D /* pullBar.app */,
92 | );
93 | name = Products;
94 | sourceTree = "";
95 | };
96 | 8CECBBE12742AAF800A2802D /* pullBar */ = {
97 | isa = PBXGroup;
98 | children = (
99 | 8CECBBFF2742B39600A2802D /* Views */,
100 | 8CECBBF12742AB2900A2802D /* GitHub */,
101 | 8CECBBFC2742ABFD00A2802D /* Extensions */,
102 | 8CECBBE22742AAF900A2802D /* AppDelegate.swift */,
103 | 8CECBBE62742AAFB00A2802D /* Assets.xcassets */,
104 | 8CECBBE82742AAFB00A2802D /* Main.storyboard */,
105 | 8CECBBEB2742AAFB00A2802D /* pullBar.entitlements */,
106 | 769F4E7A277B97B300594911 /* Notifications.swift */,
107 | 76D5F9E528D28A29009EBD80 /* Keychain.swift */,
108 | );
109 | path = pullBar;
110 | sourceTree = "";
111 | };
112 | 8CECBBF12742AB2900A2802D /* GitHub */ = {
113 | isa = PBXGroup;
114 | children = (
115 | 8CECBBF22742AB3F00A2802D /* GitHubClient.swift */,
116 | 8CECBBF32742AB3F00A2802D /* GitHubDtos.swift */,
117 | 76F7DACF28A88CAB0086A3B0 /* GithubTokenValidator.swift */,
118 | );
119 | path = GitHub;
120 | sourceTree = "";
121 | };
122 | 8CECBBFC2742ABFD00A2802D /* Extensions */ = {
123 | isa = PBXGroup;
124 | children = (
125 | 8CECBC0827436C2200A2802D /* NSMutableAttributedStringExtensions.swift */,
126 | 8CECBBFD2742AC0800A2802D /* DefaultsExtensions.swift */,
127 | 8C55444A274AD7B20079CA42 /* DateExtensions.swift */,
128 | 8C55444C274AD9E00079CA42 /* NSImageExtensions.swift */,
129 | 769F4E7C277CAFAD00594911 /* StringExtensions.swift */,
130 | );
131 | path = Extensions;
132 | sourceTree = "";
133 | };
134 | 8CECBBFF2742B39600A2802D /* Views */ = {
135 | isa = PBXGroup;
136 | children = (
137 | 8CECBC002742B3A000A2802D /* PreferencesView.swift */,
138 | 769F4E1A2765A2B900594911 /* AboutView.swift */,
139 | 768A4C5428AF253C004E557E /* BottomItemView.swift */,
140 | 76181DD42D7CC83000D4D7CB /* AppView.swift */,
141 | 76181DD62D7CC84800D4D7CB /* AppPromotionView.swift */,
142 | );
143 | path = Views;
144 | sourceTree = "";
145 | };
146 | /* End PBXGroup section */
147 |
148 | /* Begin PBXNativeTarget section */
149 | 8CECBBDE2742AAF800A2802D /* pullBar */ = {
150 | isa = PBXNativeTarget;
151 | buildConfigurationList = 8CECBBEE2742AAFB00A2802D /* Build configuration list for PBXNativeTarget "pullBar" */;
152 | buildPhases = (
153 | 8CECBBDB2742AAF800A2802D /* Sources */,
154 | 8CECBBDC2742AAF800A2802D /* Frameworks */,
155 | 8CECBBDD2742AAF800A2802D /* Resources */,
156 | );
157 | buildRules = (
158 | );
159 | dependencies = (
160 | );
161 | name = pullBar;
162 | packageProductDependencies = (
163 | 8CECBBFA2742ABE600A2802D /* Alamofire */,
164 | 76D5F9E328D28958009EBD80 /* KeychainAccess */,
165 | 76C6AA5A2C98E809003472DA /* Defaults */,
166 | 761B97062D7CCDA70000378A /* LaunchAtLogin */,
167 | );
168 | productName = pullBar;
169 | productReference = 8CECBBDF2742AAF800A2802D /* pullBar.app */;
170 | productType = "com.apple.product-type.application";
171 | };
172 | /* End PBXNativeTarget section */
173 |
174 | /* Begin PBXProject section */
175 | 8CECBBD72742AAF800A2802D /* Project object */ = {
176 | isa = PBXProject;
177 | attributes = {
178 | BuildIndependentTargetsInParallel = 1;
179 | LastSwiftUpdateCheck = 1300;
180 | LastUpgradeCheck = 1530;
181 | TargetAttributes = {
182 | 8CECBBDE2742AAF800A2802D = {
183 | CreatedOnToolsVersion = 13.0;
184 | };
185 | };
186 | };
187 | buildConfigurationList = 8CECBBDA2742AAF800A2802D /* Build configuration list for PBXProject "pullBar" */;
188 | compatibilityVersion = "Xcode 13.0";
189 | developmentRegion = en;
190 | hasScannedForEncodings = 0;
191 | knownRegions = (
192 | en,
193 | Base,
194 | );
195 | mainGroup = 8CECBBD62742AAF800A2802D;
196 | packageReferences = (
197 | 8CECBBF62742ABDA00A2802D /* XCRemoteSwiftPackageReference "Defaults" */,
198 | 8CECBBF92742ABE600A2802D /* XCRemoteSwiftPackageReference "Alamofire" */,
199 | 76D5F9E228D28958009EBD80 /* XCRemoteSwiftPackageReference "KeychainAccess" */,
200 | 761B97052D7CCDA70000378A /* XCRemoteSwiftPackageReference "LaunchAtLogin-Modern" */,
201 | );
202 | productRefGroup = 8CECBBE02742AAF800A2802D /* Products */;
203 | projectDirPath = "";
204 | projectRoot = "";
205 | targets = (
206 | 8CECBBDE2742AAF800A2802D /* pullBar */,
207 | );
208 | };
209 | /* End PBXProject section */
210 |
211 | /* Begin PBXResourcesBuildPhase section */
212 | 8CECBBDD2742AAF800A2802D /* Resources */ = {
213 | isa = PBXResourcesBuildPhase;
214 | buildActionMask = 2147483647;
215 | files = (
216 | 8CECBBE72742AAFB00A2802D /* Assets.xcassets in Resources */,
217 | 8CECBBEA2742AAFB00A2802D /* Main.storyboard in Resources */,
218 | );
219 | runOnlyForDeploymentPostprocessing = 0;
220 | };
221 | /* End PBXResourcesBuildPhase section */
222 |
223 | /* Begin PBXSourcesBuildPhase section */
224 | 8CECBBDB2742AAF800A2802D /* Sources */ = {
225 | isa = PBXSourcesBuildPhase;
226 | buildActionMask = 2147483647;
227 | files = (
228 | 769F4E7D277CAFAD00594911 /* StringExtensions.swift in Sources */,
229 | 8C55444B274AD7B20079CA42 /* DateExtensions.swift in Sources */,
230 | 8CECBC022742B3A000A2802D /* PreferencesView.swift in Sources */,
231 | 769F4E7B277B97B300594911 /* Notifications.swift in Sources */,
232 | 76181DD72D7CC84800D4D7CB /* AppPromotionView.swift in Sources */,
233 | 8CECBBE32742AAF900A2802D /* AppDelegate.swift in Sources */,
234 | 76F7DAD028A88CAB0086A3B0 /* GithubTokenValidator.swift in Sources */,
235 | 8CECBBF52742AB3F00A2802D /* GitHubDtos.swift in Sources */,
236 | 768A4C5528AF253C004E557E /* BottomItemView.swift in Sources */,
237 | 76181DD52D7CC83000D4D7CB /* AppView.swift in Sources */,
238 | 769F4E1B2765A2B900594911 /* AboutView.swift in Sources */,
239 | 8CECBC0927436C2200A2802D /* NSMutableAttributedStringExtensions.swift in Sources */,
240 | 8CECBBF42742AB3F00A2802D /* GitHubClient.swift in Sources */,
241 | 8C55444D274AD9E00079CA42 /* NSImageExtensions.swift in Sources */,
242 | 8CECBBFE2742AC0800A2802D /* DefaultsExtensions.swift in Sources */,
243 | 76D5F9E628D28A29009EBD80 /* Keychain.swift in Sources */,
244 | );
245 | runOnlyForDeploymentPostprocessing = 0;
246 | };
247 | /* End PBXSourcesBuildPhase section */
248 |
249 | /* Begin PBXVariantGroup section */
250 | 8CECBBE82742AAFB00A2802D /* Main.storyboard */ = {
251 | isa = PBXVariantGroup;
252 | children = (
253 | 8CECBBE92742AAFB00A2802D /* Base */,
254 | );
255 | name = Main.storyboard;
256 | sourceTree = "";
257 | };
258 | /* End PBXVariantGroup section */
259 |
260 | /* Begin XCBuildConfiguration section */
261 | 8CECBBEC2742AAFB00A2802D /* Debug */ = {
262 | isa = XCBuildConfiguration;
263 | buildSettings = {
264 | ALWAYS_SEARCH_USER_PATHS = NO;
265 | CLANG_ANALYZER_NONNULL = YES;
266 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
267 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
268 | CLANG_CXX_LIBRARY = "libc++";
269 | CLANG_ENABLE_MODULES = YES;
270 | CLANG_ENABLE_OBJC_ARC = YES;
271 | CLANG_ENABLE_OBJC_WEAK = YES;
272 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
273 | CLANG_WARN_BOOL_CONVERSION = YES;
274 | CLANG_WARN_COMMA = YES;
275 | CLANG_WARN_CONSTANT_CONVERSION = YES;
276 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
277 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
278 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
279 | CLANG_WARN_EMPTY_BODY = YES;
280 | CLANG_WARN_ENUM_CONVERSION = YES;
281 | CLANG_WARN_INFINITE_RECURSION = YES;
282 | CLANG_WARN_INT_CONVERSION = YES;
283 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
284 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
285 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
286 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
287 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
288 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
289 | CLANG_WARN_STRICT_PROTOTYPES = YES;
290 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
291 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
292 | CLANG_WARN_UNREACHABLE_CODE = YES;
293 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
294 | COPY_PHASE_STRIP = NO;
295 | DEAD_CODE_STRIPPING = YES;
296 | DEBUG_INFORMATION_FORMAT = dwarf;
297 | ENABLE_STRICT_OBJC_MSGSEND = YES;
298 | ENABLE_TESTABILITY = YES;
299 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
300 | GCC_C_LANGUAGE_STANDARD = gnu11;
301 | GCC_DYNAMIC_NO_PIC = NO;
302 | GCC_NO_COMMON_BLOCKS = YES;
303 | GCC_OPTIMIZATION_LEVEL = 0;
304 | GCC_PREPROCESSOR_DEFINITIONS = (
305 | "DEBUG=1",
306 | "$(inherited)",
307 | );
308 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
309 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
310 | GCC_WARN_UNDECLARED_SELECTOR = YES;
311 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
312 | GCC_WARN_UNUSED_FUNCTION = YES;
313 | GCC_WARN_UNUSED_VARIABLE = YES;
314 | MACOSX_DEPLOYMENT_TARGET = 11.3;
315 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
316 | MTL_FAST_MATH = YES;
317 | ONLY_ACTIVE_ARCH = YES;
318 | SDKROOT = macosx;
319 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
320 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
321 | };
322 | name = Debug;
323 | };
324 | 8CECBBED2742AAFB00A2802D /* Release */ = {
325 | isa = XCBuildConfiguration;
326 | buildSettings = {
327 | ALWAYS_SEARCH_USER_PATHS = NO;
328 | CLANG_ANALYZER_NONNULL = YES;
329 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
330 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
331 | CLANG_CXX_LIBRARY = "libc++";
332 | CLANG_ENABLE_MODULES = YES;
333 | CLANG_ENABLE_OBJC_ARC = YES;
334 | CLANG_ENABLE_OBJC_WEAK = YES;
335 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
336 | CLANG_WARN_BOOL_CONVERSION = YES;
337 | CLANG_WARN_COMMA = YES;
338 | CLANG_WARN_CONSTANT_CONVERSION = YES;
339 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
340 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
341 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
342 | CLANG_WARN_EMPTY_BODY = YES;
343 | CLANG_WARN_ENUM_CONVERSION = YES;
344 | CLANG_WARN_INFINITE_RECURSION = YES;
345 | CLANG_WARN_INT_CONVERSION = YES;
346 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
347 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
348 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
349 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
350 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
351 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
352 | CLANG_WARN_STRICT_PROTOTYPES = YES;
353 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
354 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
355 | CLANG_WARN_UNREACHABLE_CODE = YES;
356 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
357 | COPY_PHASE_STRIP = NO;
358 | DEAD_CODE_STRIPPING = YES;
359 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
360 | ENABLE_NS_ASSERTIONS = NO;
361 | ENABLE_STRICT_OBJC_MSGSEND = YES;
362 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
363 | GCC_C_LANGUAGE_STANDARD = gnu11;
364 | GCC_NO_COMMON_BLOCKS = YES;
365 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
366 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
367 | GCC_WARN_UNDECLARED_SELECTOR = YES;
368 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
369 | GCC_WARN_UNUSED_FUNCTION = YES;
370 | GCC_WARN_UNUSED_VARIABLE = YES;
371 | MACOSX_DEPLOYMENT_TARGET = 11.3;
372 | MTL_ENABLE_DEBUG_INFO = NO;
373 | MTL_FAST_MATH = YES;
374 | SDKROOT = macosx;
375 | SWIFT_COMPILATION_MODE = wholemodule;
376 | SWIFT_OPTIMIZATION_LEVEL = "-O";
377 | };
378 | name = Release;
379 | };
380 | 8CECBBEF2742AAFB00A2802D /* Debug */ = {
381 | isa = XCBuildConfiguration;
382 | buildSettings = {
383 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
384 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
385 | CODE_SIGN_ENTITLEMENTS = pullBar/pullBar.entitlements;
386 | CODE_SIGN_IDENTITY = "-";
387 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
388 | CODE_SIGN_STYLE = Automatic;
389 | COMBINE_HIDPI_IMAGES = YES;
390 | CURRENT_PROJECT_VERSION = "16(gh)";
391 | DEAD_CODE_STRIPPING = YES;
392 | DEVELOPMENT_TEAM = UV3HUS49VJ;
393 | ENABLE_HARDENED_RUNTIME = YES;
394 | ENABLE_USER_SCRIPT_SANDBOXING = NO;
395 | GENERATE_INFOPLIST_FILE = YES;
396 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
397 | INFOPLIST_KEY_NSHumanReadableCopyright = "";
398 | INFOPLIST_KEY_NSMainStoryboardFile = Main;
399 | INFOPLIST_KEY_NSPrincipalClass = NSApplication;
400 | LD_RUNPATH_SEARCH_PATHS = (
401 | "$(inherited)",
402 | "@executable_path/../Frameworks",
403 | );
404 | MACOSX_DEPLOYMENT_TARGET = 13;
405 | MARKETING_VERSION = 1.12;
406 | PRODUCT_BUNDLE_IDENTIFIER = debug.com.pavelmakhov.pullBar;
407 | PRODUCT_NAME = "$(TARGET_NAME)";
408 | SWIFT_EMIT_LOC_STRINGS = YES;
409 | SWIFT_VERSION = 5.0;
410 | };
411 | name = Debug;
412 | };
413 | 8CECBBF02742AAFB00A2802D /* Release */ = {
414 | isa = XCBuildConfiguration;
415 | buildSettings = {
416 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
417 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
418 | CODE_SIGN_ENTITLEMENTS = pullBar/pullBar.entitlements;
419 | CODE_SIGN_IDENTITY = "-";
420 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
421 | CODE_SIGN_STYLE = Automatic;
422 | COMBINE_HIDPI_IMAGES = YES;
423 | CURRENT_PROJECT_VERSION = "16(gh)";
424 | DEAD_CODE_STRIPPING = YES;
425 | DEVELOPMENT_TEAM = UV3HUS49VJ;
426 | ENABLE_HARDENED_RUNTIME = YES;
427 | ENABLE_USER_SCRIPT_SANDBOXING = NO;
428 | GENERATE_INFOPLIST_FILE = YES;
429 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
430 | INFOPLIST_KEY_NSHumanReadableCopyright = "";
431 | INFOPLIST_KEY_NSMainStoryboardFile = Main;
432 | INFOPLIST_KEY_NSPrincipalClass = NSApplication;
433 | LD_RUNPATH_SEARCH_PATHS = (
434 | "$(inherited)",
435 | "@executable_path/../Frameworks",
436 | );
437 | MACOSX_DEPLOYMENT_TARGET = 13;
438 | MARKETING_VERSION = 1.12;
439 | PRODUCT_BUNDLE_IDENTIFIER = com.pavelmakhov.pullBar;
440 | PRODUCT_NAME = "$(TARGET_NAME)";
441 | SWIFT_EMIT_LOC_STRINGS = YES;
442 | SWIFT_VERSION = 5.0;
443 | };
444 | name = Release;
445 | };
446 | /* End XCBuildConfiguration section */
447 |
448 | /* Begin XCConfigurationList section */
449 | 8CECBBDA2742AAF800A2802D /* Build configuration list for PBXProject "pullBar" */ = {
450 | isa = XCConfigurationList;
451 | buildConfigurations = (
452 | 8CECBBEC2742AAFB00A2802D /* Debug */,
453 | 8CECBBED2742AAFB00A2802D /* Release */,
454 | );
455 | defaultConfigurationIsVisible = 0;
456 | defaultConfigurationName = Release;
457 | };
458 | 8CECBBEE2742AAFB00A2802D /* Build configuration list for PBXNativeTarget "pullBar" */ = {
459 | isa = XCConfigurationList;
460 | buildConfigurations = (
461 | 8CECBBEF2742AAFB00A2802D /* Debug */,
462 | 8CECBBF02742AAFB00A2802D /* Release */,
463 | );
464 | defaultConfigurationIsVisible = 0;
465 | defaultConfigurationName = Release;
466 | };
467 | /* End XCConfigurationList section */
468 |
469 | /* Begin XCRemoteSwiftPackageReference section */
470 | 761B97052D7CCDA70000378A /* XCRemoteSwiftPackageReference "LaunchAtLogin-Modern" */ = {
471 | isa = XCRemoteSwiftPackageReference;
472 | repositoryURL = "https://github.com/sindresorhus/LaunchAtLogin-Modern";
473 | requirement = {
474 | kind = exactVersion;
475 | version = 1.1.0;
476 | };
477 | };
478 | 76D5F9E228D28958009EBD80 /* XCRemoteSwiftPackageReference "KeychainAccess" */ = {
479 | isa = XCRemoteSwiftPackageReference;
480 | repositoryURL = "https://github.com/kishikawakatsumi/KeychainAccess";
481 | requirement = {
482 | kind = upToNextMajorVersion;
483 | minimumVersion = 4.0.0;
484 | };
485 | };
486 | 8CECBBF62742ABDA00A2802D /* XCRemoteSwiftPackageReference "Defaults" */ = {
487 | isa = XCRemoteSwiftPackageReference;
488 | repositoryURL = "https://github.com/sindresorhus/Defaults";
489 | requirement = {
490 | kind = exactVersion;
491 | version = 8.2.0;
492 | };
493 | };
494 | 8CECBBF92742ABE600A2802D /* XCRemoteSwiftPackageReference "Alamofire" */ = {
495 | isa = XCRemoteSwiftPackageReference;
496 | repositoryURL = "https://github.com/Alamofire/Alamofire";
497 | requirement = {
498 | branch = master;
499 | kind = branch;
500 | };
501 | };
502 | /* End XCRemoteSwiftPackageReference section */
503 |
504 | /* Begin XCSwiftPackageProductDependency section */
505 | 761B97062D7CCDA70000378A /* LaunchAtLogin */ = {
506 | isa = XCSwiftPackageProductDependency;
507 | package = 761B97052D7CCDA70000378A /* XCRemoteSwiftPackageReference "LaunchAtLogin-Modern" */;
508 | productName = LaunchAtLogin;
509 | };
510 | 76C6AA5A2C98E809003472DA /* Defaults */ = {
511 | isa = XCSwiftPackageProductDependency;
512 | package = 8CECBBF62742ABDA00A2802D /* XCRemoteSwiftPackageReference "Defaults" */;
513 | productName = Defaults;
514 | };
515 | 76D5F9E328D28958009EBD80 /* KeychainAccess */ = {
516 | isa = XCSwiftPackageProductDependency;
517 | package = 76D5F9E228D28958009EBD80 /* XCRemoteSwiftPackageReference "KeychainAccess" */;
518 | productName = KeychainAccess;
519 | };
520 | 8CECBBFA2742ABE600A2802D /* Alamofire */ = {
521 | isa = XCSwiftPackageProductDependency;
522 | package = 8CECBBF92742ABE600A2802D /* XCRemoteSwiftPackageReference "Alamofire" */;
523 | productName = Alamofire;
524 | };
525 | /* End XCSwiftPackageProductDependency section */
526 | };
527 | rootObject = 8CECBBD72742AAF800A2802D /* Project object */;
528 | }
529 |
--------------------------------------------------------------------------------
/pullBar/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
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 | Default
531 |
532 |
533 |
534 |
535 |
536 |
537 | Left to Right
538 |
539 |
540 |
541 |
542 |
543 |
544 | Right to Left
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 | Default
556 |
557 |
558 |
559 |
560 |
561 |
562 | Left to Right
563 |
564 |
565 |
566 |
567 |
568 |
569 | Right to Left
570 |
571 |
572 |
573 |
574 |
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 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 |
--------------------------------------------------------------------------------