├── .github
├── CODEOWNERS
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── scripts
│ ├── import-certificate.sh
│ ├── import-profile.sh
│ └── set-env-from-xcodeproj.sh
└── workflows
│ ├── deploy.yml
│ └── tests.yml
├── .gitignore
├── LICENSE
├── Package.swift
├── README.md
├── SECURITY.md
├── SnippetsLibrary.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
├── xcshareddata
│ └── xcschemes
│ │ └── SnippetsLibrary.xcscheme
└── xcuserdata
│ └── krzysztoflowiec.xcuserdatad
│ ├── xcdebugger
│ └── Breakpoints_v2.xcbkptlist
│ └── xcschemes
│ └── xcschememanagement.plist
├── SnippetsLibrary
├── Application
│ ├── ActiveAppView.swift
│ ├── AppAlert.swift
│ ├── AppDelegate.swift
│ ├── AppMenu.swift
│ ├── AppSheet.swift
│ ├── AppView.swift
│ └── SnippetsLibraryApp.swift
├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── 1024.png
│ │ ├── 128.png
│ │ ├── 16.png
│ │ ├── 256-1.png
│ │ ├── 256.png
│ │ ├── 32-1.png
│ │ ├── 32.png
│ │ ├── 512-1.png
│ │ ├── 512.png
│ │ ├── 64.png
│ │ └── Contents.json
│ ├── Colors
│ │ ├── Contents.json
│ │ ├── accent.colorset
│ │ │ └── Contents.json
│ │ ├── background.colorset
│ │ │ └── Contents.json
│ │ ├── darkGrey.colorset
│ │ │ └── Contents.json
│ │ ├── defaultBackground.colorset
│ │ │ └── Contents.json
│ │ ├── secondaryBackground.colorset
│ │ │ └── Contents.json
│ │ ├── skeletonLight.colorset
│ │ │ └── Contents.json
│ │ ├── skeletonRegular.colorset
│ │ │ └── Contents.json
│ │ └── skeletonThin.colorset
│ │ │ └── Contents.json
│ ├── Contents.json
│ ├── icLogo.imageset
│ │ ├── Contents.json
│ │ └── icLogo.pdf
│ ├── icLogoStatusBar.imageset
│ │ ├── Books_front_matte.pdf
│ │ └── Contents.json
│ ├── icSnippetFileBlank.imageset
│ │ ├── Contents.json
│ │ └── icSnippetFileBlank.png
│ └── icSnippetFileWhite.imageset
│ │ ├── Contents.json
│ │ └── icSnippetFile.png
├── Dependencies
│ └── DependencyContainer.swift
├── Extensions
│ ├── AnyPublisher+SinkWithoutValue.swift
│ ├── AppDelegate+NSMenuDelegate.swift
│ ├── AppMenu+HideWindow.swift
│ ├── DatabaseReference+Timeout.swift
│ ├── NSApplication+AppVersion.swift
│ ├── NSNotification+Name.swift
│ ├── NSTableView+BackgroundColor.swift
│ ├── SnippetPlist+Dictonary.swift
│ ├── SnippetsLibraryView+Equatable.swift
│ ├── StartView+Equatable.swift
│ ├── String+Extensions.swift
│ ├── View+Displayed.swift
│ ├── View+Skeletonable.swift
│ └── View+Visibility.swift
├── Info.plist
├── Models
│ ├── Snippet.swift
│ └── SnippetPlist.swift
├── Modules
│ ├── SnippetDetails
│ │ ├── SnippetDetailsView.swift
│ │ └── SnippetDetailsViewModel.swift
│ ├── SnippetImport
│ │ ├── SnippetImportView.swift
│ │ ├── SnippetImportViewModel+DropDelegate.swift
│ │ └── SnippetImportViewModel.swift
│ ├── SnippetsLibrary
│ │ ├── SnippetLibraryList
│ │ │ ├── SnippetsLibraryListFilterView.swift
│ │ │ ├── SnippetsLibraryListSectionView.swift
│ │ │ ├── SnippetsLibraryListView.swift
│ │ │ └── SnippetsLibraryListViewModel.swift
│ │ ├── SnippetsLibraryPreview
│ │ │ └── SnippetsLibraryPreviewView.swift
│ │ ├── SnippetsLibraryView.swift
│ │ └── SnippetsLibraryViewModel.swift
│ ├── SnippetsUpload
│ │ ├── SnippetsUploadView.swift
│ │ └── SnippetsUploadViewModel.swift
│ ├── Start
│ │ ├── StartView.swift
│ │ ├── StartViewMenuItem
│ │ │ └── StartViewMenuItem.swift
│ │ ├── StartViewModel.swift
│ │ └── StartViewRecentSnippets
│ │ │ └── StartViewRecentSnippetsView.swift
│ └── Status
│ │ ├── StatusView.swift
│ │ └── StatusViewModel.swift
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Services
│ ├── Firebase
│ │ ├── Crashlytics
│ │ │ └── CrashlyticsService.swift
│ │ ├── DatabaseService
│ │ │ └── DatabaseService.swift
│ │ └── Logs
│ │ │ └── LogsService.swift
│ ├── Network
│ │ └── NetworkService.swift
│ ├── SnippetParser
│ │ └── SnippetsParserService.swift
│ ├── URLFactory
│ │ └── URLFactory.swift
│ └── UserDefaults
│ │ └── UserDefaultsService.swift
├── SnippetsLibrary.entitlements
├── SnippetsLibraryDebug.entitlements
├── Supporting Files
│ ├── CustomCodeTheme.swift
│ ├── Enums
│ │ ├── DisabledCommandGroupButtonType.swift
│ │ ├── Errors
│ │ │ ├── CrashlyticsError.swift
│ │ │ ├── DatabaseError.swift
│ │ │ ├── SnippetsParserServiceError.swift
│ │ │ └── UserDefaultsServiceError.swift
│ │ ├── FileStatusCardType.swift
│ │ ├── PlistCodingKeys.swift
│ │ ├── SnippetAvailability.swift
│ │ ├── SnippetCategory.swift
│ │ ├── SnippetDetailsViewType.swift
│ │ ├── SnippetFileCardViewState.swift
│ │ ├── SnippetPlatform.swift
│ │ ├── SnippetType.swift
│ │ ├── SnippetWriteType.swift
│ │ ├── SnippetsFetchingType.swift
│ │ ├── StartViewMenuItemType.swift
│ │ ├── URLType.swift
│ │ ├── UploadingStatus.swift
│ │ ├── UserActivityLevel.swift
│ │ └── UserActivityLogType.swift
│ ├── Layout.swift
│ ├── Mocks.swift
│ ├── NetworkObserver.swift
│ ├── System.swift
│ └── TextFieldStyleModifier.swift
└── Views
│ ├── DisabledCommandGroupButton.swift
│ ├── EmptySnippetsListView.swift
│ ├── FileStatusCard
│ ├── FileStatusCard.swift
│ └── FileStatusCardViewModel.swift
│ ├── RecentSnippetCardView.swift
│ ├── SearchBar.swift
│ ├── SnippetCreationManualView.swift
│ ├── SnippetDropCellView.swift
│ ├── SnippetFileCard
│ ├── SnippetFileCardView.swift
│ └── SnippetFileCardViewModel.swift
│ ├── SnippetListItem
│ └── SnippetListItemView.swift
│ ├── ToastView.swift
│ └── VisualEffectView.swift
├── SnippetsLibraryUITests
├── Info.plist
└── SnippetsLibraryUITests.swift
├── SnippetsLibraryUnitTests
├── Info.plist
└── SnippetsLibraryUnitTests.swift
├── SnippetsLibraryWidget
├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Contents.json
│ └── WidgetBackground.colorset
│ │ └── Contents.json
├── Info.plist
├── Models
│ └── WidgetEntry.swift
├── Provider.swift
├── SnippetsLibraryWidget.entitlements
├── SnippetsLibraryWidget.swift
└── Views
│ ├── MediumWidget
│ ├── MediumWidgetListItemView.swift
│ └── MediumWidgetView.swift
│ ├── SmallWidget
│ └── SmallWidgetView.swift
│ └── SnippetsLibraryWidgetView.swift
├── Sources
└── SnippetsLibrary
│ └── SnippetsLibrary.swift
├── Tests
└── SnippetsLibraryTests
│ └── SnippetsLibraryTests.swift
└── public
├── docs
├── privacy_policy.md
└── terms_and_conditions.md
└── images
└── app_preview.png
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Lines starting with '#' are comments.
2 | # Each line is a file pattern followed by one or more owners.
3 |
4 | # More details are here: https://help.github.com/articles/about-codeowners/
5 |
6 | # The '*' pattern is global owners.
7 |
8 | # Order is important. The last matching pattern has the most precedence.
9 | # The folders are ordered as follows:
10 |
11 | # In each subsection folders are ordered first by depth, then alphabetically.
12 | # This should make it easy to add new rules without breaking existing ones.
13 |
14 | # Global rule:
15 | * @tryboxx
16 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **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 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Environment:**
27 | - device: [e.g. MacBook Pro (13-inch, M1, 2020)]
28 | - macOS: [e.g. Big Sur, version 11.3.1]
29 | - appVersion: [e.g. 1.0]
30 |
31 | **Additional context**
32 | Add any other context about the problem here.
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/scripts/import-certificate.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -euo pipefail
4 |
5 | security create-keychain -p "" build.keychain
6 | security list-keychains -s build.keychain
7 | security default-keychain -s build.keychain
8 | security unlock-keychain -p "" build.keychain
9 | security set-keychain-settings
10 | security import <(echo $SIGNING_CERTIFICATE_P12_DATA | base64 --decode) \
11 | -f pkcs12 \
12 | -k build.keychain \
13 | -P $SIGNING_CERTIFICATE_PASSWORD \
14 | -T /usr/bin/codesign
15 | security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain
16 |
--------------------------------------------------------------------------------
/.github/scripts/import-profile.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -euo pipefail
4 |
5 | mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
6 | echo "$PROVISIONING_PROFILE_DATA" | base64 --decode > ~/Library/MobileDevice/Provisioning\ Profiles/SnippetsLibrary_Distribution.provisionprofile
7 |
--------------------------------------------------------------------------------
/.github/scripts/set-env-from-xcodeproj.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -euo pipefail
4 |
5 | SCHEME="$(xcodebuild -list -json | jq -r '.project.schemes[0]')"
6 | PRODUCT_NAME="$(xcodebuild -scheme "$SCHEME" -showBuildSettings | grep " PRODUCT_NAME " | sed "s/[ ]*PRODUCT_NAME = //")"
7 | echo "::set-env name=PRODUCT_NAME::$PRODUCT_NAME"
8 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | # name: deploy
2 |
3 | # on:
4 | # push:
5 | # branches: [ main ]
6 | # pull_request:
7 | # branches: [ main ]
8 |
9 | # workflow_dispatch:
10 |
11 | # jobs:
12 | # deploy:
13 | # name: Deploying to Testflight
14 | # runs-on: macOS-latest
15 |
16 | # steps:
17 | # - name: Checkout repository
18 | # uses: actions/checkout@v1
19 |
20 | # - name: Deploy iOS Beta to TestFlight via Fastlane
21 | # uses: maierj/fastlane-action@v1.4.0
22 | # with:
23 | # lane: closed_beta
24 | # env:
25 | # APP_STORE_CONNECT_TEAM_ID: '${{ secrets.APP_STORE_CONNECT_TEAM_ID }}'
26 | # DEVELOPER_APP_ID: '${{ secrets.DEVELOPER_APP_ID }}'
27 | # DEVELOPER_APP_IDENTIFIER: '${{ secrets.DEVELOPER_APP_IDENTIFIER }}'
28 | # DEVELOPER_PORTAL_TEAM_ID: '${{ secrets.DEVELOPER_PORTAL_TEAM_ID }}'
29 | # FASTLANE_APPLE_ID: '${{ secrets.FASTLANE_APPLE_ID }}'
30 | # FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: '${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }}'
31 | # MATCH_PASSWORD: '${{ secrets.MATCH_PASSWORD }}'
32 | # GIT_AUTHORIZATION: '${{ secrets.GIT_AUTHORIZATION }}'
33 | # PROVISIONING_PROFILE_SPECIFIER: '${{ secrets.PROVISIONING_PROFILE_SPECIFIER }}'
34 | # TEMP_KEYCHAIN_PASSWORD: '${{ secrets.TEMP_KEYCHAIN_PASSWORD }}'
35 | # TEMP_KEYCHAIN_USER: '${{ secrets.TEMP_KEYCHAIN_USER }}'
36 | # APPLE_KEY_ID: '${{ secrets.APPLE_KEY_ID }}'
37 | # APPLE_ISSUER_ID: '${{ secrets.APPLE_ISSUER_ID }}'
38 | # APPLE_KEY_CONTENT: '${{ secrets.APPLE_KEY_CONTENT }}'
39 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: macos-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: Build
17 | run: swift build -v
18 | - name: Run tests
19 | run: swift test -v
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | GoogleService-Info.plist
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "SnippetsLibrary",
8 | products: [
9 | // Products define the executables and libraries a package produces, and make them visible to other packages.
10 | .library(
11 | name: "SnippetsLibrary",
12 | targets: ["SnippetsLibrary"]),
13 | ],
14 | dependencies: [
15 | // Dependencies declare other packages that this package depends on.
16 | // .package(url: /* package url */, from: "1.0.0"),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
20 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
21 | .target(
22 | name: "SnippetsLibrary",
23 | dependencies: []),
24 | .testTarget(
25 | name: "SnippetsLibraryTests",
26 | dependencies: ["SnippetsLibrary"]),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Snippets Library for SwiftUI
5 |
6 |
7 | [](https://github.com/tryboxx/SnippetsLibrary/actions)
8 | [](https://twitter.com/swift_devtools)
9 |
10 |
11 |
12 | [**SnippetsLibrary 1.0.2 is now available!**](https://apps.apple.com/pl/app/snippets-library/id1585687204?mt=12)
13 |
14 | SnippetsLibrary is a helpful tool for SwiftUI developers to help with their daily coding life. SnippetsLibrary contains all the needed code snippets for you to view, edit, or add more and more. This will make your daily work easier and faster.
15 |
16 | 
17 |
18 | Table of Contents:
19 |
20 | - [Installation](#installation)
21 | - [App Store](#app-store)
22 | - [Test Flight](#test-flight)
23 | - [Getting Started](#getting-started)
24 | - [Availabe Functionalities](#availabe-functionalities)
25 | - [Language support](#language-support)
26 | - [Contributing](#contributing)
27 | - [Security](#security)
28 | - [Follow Us](#follow-us)
29 | - [License](#license)
30 |
31 | ## Installation
32 |
33 | The SnippetsLibrary app can be downloaded directly from the App Store or you can join the Developer Beta Testing Program via Test Flight:
34 |
35 | ### App Store
36 |
37 | Check the newest version of the Snippets Library app:
38 |
39 | ```
40 | https://apps.apple.com/pl/app/snippets-library/id1585687204?mt=12
41 | ```
42 |
43 | ### Test Flight
44 |
45 | You can join the SnippetsLibrary app beta testing by clicking on the invite link below (macOS Monterey required):
46 |
47 | ```
48 | https://testflight.apple.com/join/P1tNGffH
49 | ```
50 |
51 | ## Getting Started
52 |
53 | Getting started with the SnippetsLibrary is as simple as cloning this project with your favorite Git client, you can choose it from the recommended clients listed below:
54 |
55 | * [Sourcetree App](https://www.sourcetreeapp.com)
56 | * [GitHub Desktop](https://desktop.github.com)
57 |
58 | ### Availabe Functionalities
59 |
60 | * [**Search for snippets**](https://swiftdevtools/snippetslibrary/docs/snippet/searching) - Open and search in the snippets library.
61 | * [**Create new snippet**](https://swiftdevtools/snippetslibrary/docs/snippet/creating) - Create a new snippet file.
62 | * [**Upload existing snippet**](https://swiftdevtools/snippetslibrary/docs/snippet/uploading) - Import an existing code snippet.
63 | * [**Upload all snippets to Xcode**](https://swiftdevtools/snippetslibrary/docs/snippet/xcode-uploading) - Export all code snippets from the Snippets Library directly to the Xcode.
64 | * [**Download the code snippet**](https://swiftdevtools/snippetslibrary/docs/snippet/xcode-uploading) - Save a code snippet locally on your Mac.
65 | * [**Use status bar app quick menu**](https://swiftdevtools/snippetslibrary/docs/snippet/quick-menu) - Quick access to the featured code snippets, developer documentation and actions.
66 | * [**Add widgets to the notification center**](https://swiftdevtools/snippetslibrary/docs/snippet/widgets) - Open a snippet from the macOS widget.
67 |
68 | For the complete documentation, visit [https://swiftdevtools/snippetslibrary/docs](https://swiftdevtools/snippetslibrary/docs). For more tutorials, news and announcements check out our [blog](https://medium.com/swiftdevtools) or social media accounts.
69 |
70 | ### Language support
71 | * ✅ [SwiftUI](https://developer.apple.com/documentation/swiftui/)
72 | * ❌ Swift
73 | * ❌ Objective-C
74 |
75 | Looking for more language support? Help us by contributing a pull request with the code snippets!
76 |
77 | ## Contributing
78 |
79 | All code contributions must go through a pull request and be approved by a core developer before being merged. This is to ensure a proper review of all the code.
80 |
81 | We truly ❤️ pull requests! If you wish to help, you can email us at [contribution@swiftdevtools.com](mailto:contribution@swiftdevtools.com) or DM us on [Twitter](https://twitter.com/swift_devtools).
82 |
83 | ## Security
84 |
85 | For security issues, kindly email us at [security@swiftdevtools.com](mailto:security@swiftdevtools.com) instead of posting a public issue on GitHub.
86 |
87 | ## Follow Us
88 |
89 | Join our growing community around the world! See our official [Blog](https://medium.com/swiftdevtools). Follow us on [Twitter](https://twitter.com/swift_devtools) for more help, ideas, and discussions.
90 |
91 | ## License
92 |
93 | This repository is available under the [Apache 2.0](./LICENSE).
94 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | | Version | Supported |
6 | | ------- | ------------------ |
7 | | 1.0.x | :white_check_mark: |
8 |
9 | ## Reports
10 |
11 | For security issues, kindly email us at security@swiftdevtools.com instead of posting a public issue on GitHub.
12 |
--------------------------------------------------------------------------------
/SnippetsLibrary.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SnippetsLibrary.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SnippetsLibrary.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "abseil",
6 | "repositoryURL": "https://github.com/firebase/abseil-cpp-SwiftPM.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "fffc3c2729be5747390ad02d5100291a0d9ad26a",
10 | "version": "0.20200225.4"
11 | }
12 | },
13 | {
14 | "package": "BoringSSL-GRPC",
15 | "repositoryURL": "https://github.com/firebase/boringssl-SwiftPM.git",
16 | "state": {
17 | "branch": null,
18 | "revision": "734a8247442fde37df4364c21f6a0085b6a36728",
19 | "version": "0.7.2"
20 | }
21 | },
22 | {
23 | "package": "Firebase",
24 | "repositoryURL": "https://github.com/firebase/firebase-ios-sdk.git",
25 | "state": {
26 | "branch": "master",
27 | "revision": "08686f04881483d2bc098b2696e674c0ba135e47",
28 | "version": null
29 | }
30 | },
31 | {
32 | "package": "GoogleAppMeasurement",
33 | "repositoryURL": "https://github.com/google/GoogleAppMeasurement.git",
34 | "state": {
35 | "branch": null,
36 | "revision": "9b2f6aca5b4685c45f9f5481f19bee8e7982c538",
37 | "version": "8.9.1"
38 | }
39 | },
40 | {
41 | "package": "GoogleDataTransport",
42 | "repositoryURL": "https://github.com/google/GoogleDataTransport.git",
43 | "state": {
44 | "branch": null,
45 | "revision": "15ccdfd25ac55b9239b82809531ff26605e7556e",
46 | "version": "9.1.2"
47 | }
48 | },
49 | {
50 | "package": "GoogleUtilities",
51 | "repositoryURL": "https://github.com/google/GoogleUtilities.git",
52 | "state": {
53 | "branch": null,
54 | "revision": "b3bb0c5551fb3f80ca939829639ab5b093edd14f",
55 | "version": "7.7.0"
56 | }
57 | },
58 | {
59 | "package": "gRPC",
60 | "repositoryURL": "https://github.com/firebase/grpc-SwiftPM.git",
61 | "state": {
62 | "branch": null,
63 | "revision": "fb405dd2c7901485f7e158b24e3a0a47e4efd8b5",
64 | "version": "1.28.4"
65 | }
66 | },
67 | {
68 | "package": "GTMSessionFetcher",
69 | "repositoryURL": "https://github.com/google/gtm-session-fetcher.git",
70 | "state": {
71 | "branch": null,
72 | "revision": "bc6a19702ac76ac4e488b68148710eb815f9bc56",
73 | "version": "1.7.0"
74 | }
75 | },
76 | {
77 | "package": "leveldb",
78 | "repositoryURL": "https://github.com/firebase/leveldb.git",
79 | "state": {
80 | "branch": null,
81 | "revision": "0706abcc6b0bd9cedfbb015ba840e4a780b5159b",
82 | "version": "1.22.2"
83 | }
84 | },
85 | {
86 | "package": "nanopb",
87 | "repositoryURL": "https://github.com/firebase/nanopb.git",
88 | "state": {
89 | "branch": null,
90 | "revision": "7ee9ef9f627d85cbe1b8c4f49a3ed26eed216c77",
91 | "version": "2.30908.0"
92 | }
93 | },
94 | {
95 | "package": "PopupView",
96 | "repositoryURL": "https://github.com/exyte/PopupView.git",
97 | "state": {
98 | "branch": null,
99 | "revision": "0d37d6f94e0ec9b38ab43300213ad72d28af921f",
100 | "version": "1.0.2"
101 | }
102 | },
103 | {
104 | "package": "Promises",
105 | "repositoryURL": "https://github.com/google/promises.git",
106 | "state": {
107 | "branch": null,
108 | "revision": "611337c330350c9c1823ad6d671e7f936af5ee13",
109 | "version": "2.0.0"
110 | }
111 | },
112 | {
113 | "package": "Sourceful",
114 | "repositoryURL": "https://github.com/twostraws/Sourceful.git",
115 | "state": {
116 | "branch": "main",
117 | "revision": "04f7e85900451ebdc34345ee19bb0139f05f3a01",
118 | "version": null
119 | }
120 | },
121 | {
122 | "package": "SwiftProtobuf",
123 | "repositoryURL": "https://github.com/apple/swift-protobuf.git",
124 | "state": {
125 | "branch": null,
126 | "revision": "7e2c5f3cbbeea68e004915e3a8961e20bd11d824",
127 | "version": "1.18.0"
128 | }
129 | },
130 | {
131 | "package": "SwiftUISkeleton",
132 | "repositoryURL": "https://github.com/serbats/SwiftUISkeleton.git",
133 | "state": {
134 | "branch": "master",
135 | "revision": "fb41f67b39e9e23b9714ae41473462ca8bed4373",
136 | "version": null
137 | }
138 | }
139 | ]
140 | },
141 | "version": 1
142 | }
143 |
--------------------------------------------------------------------------------
/SnippetsLibrary.xcodeproj/xcshareddata/xcschemes/SnippetsLibrary.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/SnippetsLibrary.xcodeproj/xcuserdata/krzysztoflowiec.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
9 |
21 |
22 |
23 |
25 |
37 |
38 |
39 |
41 |
53 |
54 |
55 |
57 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/SnippetsLibrary.xcodeproj/xcuserdata/krzysztoflowiec.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Promises (Playground) 1.xcscheme
8 |
9 | isShown
10 |
11 | orderHint
12 | 24
13 |
14 | Promises (Playground) 2.xcscheme
15 |
16 | isShown
17 |
18 | orderHint
19 | 25
20 |
21 | Promises (Playground) 3.xcscheme
22 |
23 | isShown
24 |
25 | orderHint
26 | 1
27 |
28 | Promises (Playground) 4.xcscheme
29 |
30 | isShown
31 |
32 | orderHint
33 | 5
34 |
35 | Promises (Playground) 5.xcscheme
36 |
37 | isShown
38 |
39 | orderHint
40 | 6
41 |
42 | Promises (Playground).xcscheme
43 |
44 | isShown
45 |
46 | orderHint
47 | 23
48 |
49 | SnippetsLibrary.xcscheme_^#shared#^_
50 |
51 | orderHint
52 | 0
53 |
54 | SnippetsLibraryWidgetExtension.xcscheme_^#shared#^_
55 |
56 | orderHint
57 | 1
58 |
59 |
60 | SuppressBuildableAutocreation
61 |
62 | B82561AF26E81D570040A67E
63 |
64 | primary
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Application/ActiveAppView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ActiveAppView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 07/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum ActiveAppView: Identifiable, Equatable {
11 | case create
12 | case importSnippet
13 | case snippetsLibrary(_ directWithSnippetId: SnippetId?)
14 |
15 | var id: Int {
16 | switch self {
17 | case .create: return 0
18 | case .importSnippet: return 1
19 | case .snippetsLibrary: return 2
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Application/AppAlert.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppAlert.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 12/10/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum AppAlert: Identifiable {
11 | case snippetDownload
12 |
13 | var id: Int {
14 | switch self {
15 | case .snippetDownload: return 0
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Application/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 07/09/2021.
6 | //
7 |
8 | import SwiftUI
9 | import Firebase
10 |
11 | final class AppDelegate: NSObject, NSApplicationDelegate {
12 |
13 | // MARK: - Stored Properties
14 |
15 | @Environment(\.openURL) var openURL
16 |
17 | internal var statusBarItem: NSStatusItem?
18 | internal var statusView: NSView?
19 | internal var menu: AppMenu?
20 |
21 | // MARK: - Methods
22 |
23 | func applicationDidFinishLaunching(_ notification: Notification) {
24 | FirebaseApp.configure()
25 | setupMenuItems()
26 | return
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Application/AppMenu.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppMenu.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 27/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | final class AppMenu: NSMenu {
11 |
12 | // MARK: - Stored Properties
13 |
14 | private lazy var applicationName = ProcessInfo.processInfo.processName
15 |
16 | private var statusItem: NSMenuItem {
17 | NSMenuItem(
18 | title: "Status",
19 | action: nil,
20 | keyEquivalent: ""
21 | )
22 | }
23 |
24 | private var aboutItem: NSMenuItem {
25 | let item = NSMenuItem(
26 | title: "",
27 | action: #selector(NSApplication.orderFrontStandardAboutPanel(_:)),
28 | keyEquivalent: ""
29 | )
30 | item.attributedTitle = NSAttributedString(
31 | string: "About \(applicationName)",
32 | attributes: [NSAttributedString.Key.font : NSFont.systemFont(ofSize: 13.0)]
33 | )
34 | return item
35 | }
36 |
37 | private var docsItem: NSMenuItem {
38 | let item = NSMenuItem(
39 | title: "",
40 | action: nil,
41 | keyEquivalent: ""
42 | )
43 | item.attributedTitle = NSAttributedString(
44 | string: "Developer Documentation...",
45 | attributes: [NSAttributedString.Key.font : NSFont.systemFont(ofSize: 13.0)]
46 | )
47 | return item
48 | }
49 |
50 | private var quitItem: NSMenuItem {
51 | let item = NSMenuItem(
52 | title: "",
53 | action: #selector(NSApplication.terminate(_:)),
54 | keyEquivalent: "q"
55 | )
56 | item.attributedTitle = NSAttributedString(
57 | string: "Quit \(applicationName)",
58 | attributes: [NSAttributedString.Key.font : NSFont.systemFont(ofSize: 13.0)]
59 | )
60 | return item
61 | }
62 |
63 | // MARK: - Life Cycle
64 |
65 | override init(title: String) {
66 | super.init(title: title)
67 |
68 | setupMenuItems()
69 | NSApplication.shared.windows.first?.delegate = self
70 | }
71 |
72 | required init(coder: NSCoder) {
73 | super.init(coder: coder)
74 | }
75 |
76 | // MARK: - Methods
77 |
78 | private func setupMenuItems() {
79 | items = [
80 | statusItem,
81 | NSMenuItem.separator(),
82 | aboutItem,
83 | docsItem,
84 | NSMenuItem.separator(),
85 | quitItem
86 | ]
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Application/AppSheet.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppSheet.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 08/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | enum AppSheet: Identifiable {
11 | case snippetDetails(_ snippet: Snippet, _ type: SnippetDetailsViewType)
12 | case snippetsUpload
13 |
14 | var id: Int {
15 | switch self {
16 | case .snippetDetails: return 0
17 | case .snippetsUpload: return 1
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Application/AppView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 25/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AppView: View {
11 |
12 | private enum Constants {
13 | static let statusWindowClassName = "NSStatusBarWindow"
14 | static let delay = DispatchTime.now() + 0.01
15 | }
16 |
17 | // MARK: - Stored Properties
18 |
19 | @Environment(\.scenePhase) var scenePhase
20 |
21 | @Binding internal var activeAppView: ActiveAppView?
22 | @Binding internal var activeAppSheet: AppSheet?
23 | @Binding internal var shouldBeDisabled: Bool
24 |
25 | @ObservedObject private var networkObserver = NetworkObserver()
26 | @State private var shouldShowNetworkAlert = false
27 |
28 | // MARK: - Views
29 |
30 | var body: some View {
31 | showActiveAppView()
32 | .onChange(of: networkObserver.isConnected) { _ in
33 | withAnimation {
34 | shouldShowNetworkAlert.toggle()
35 | }
36 | }
37 | .onOpenURL {
38 | openSnippet(fromURL: $0)
39 | }
40 | .makeDisplayed(
41 | with: $shouldShowNetworkAlert,
42 | imageName: "network",
43 | title: networkObserver.isConnected ? "Connected" : "No connection",
44 | state: networkObserver.isConnected ? .success : .failure
45 | )
46 | }
47 |
48 | // MARK: - Methods
49 |
50 | @ViewBuilder
51 | private func showActiveAppView() -> some View {
52 | switch activeAppView {
53 | case .create:
54 | SnippetsLibraryView(
55 | viewModel: SnippetsLibraryViewModel(hasConnection: $networkObserver.isConnected),
56 | activeSheet: $activeAppSheet
57 | )
58 | .onAppear {
59 | updateSystemButtons(hidden: false)
60 | }
61 | case .importSnippet:
62 | SnippetImportView(viewModel: SnippetImportViewModel(activeAppView: $activeAppView))
63 | .onAppear {
64 | updateSystemButtons(hidden: false)
65 | }
66 | case let .snippetsLibrary(snippetId):
67 | SnippetsLibraryView(
68 | viewModel: SnippetsLibraryViewModel(
69 | activeSnippetId: snippetId,
70 | hasConnection: $networkObserver.isConnected
71 | ),
72 | activeSheet: $activeAppSheet
73 | )
74 | .onAppear {
75 | updateSystemButtons(hidden: false)
76 | }
77 | case .none:
78 | StartView(
79 | viewModel: StartViewModel(
80 | activeAppView: $activeAppView,
81 | activeAppSheet: $activeAppSheet
82 | )
83 | )
84 | .onChange(of: scenePhase) {
85 | guard $0 == .active else { return }
86 |
87 | updateSystemButtons(hidden: true)
88 | }
89 | }
90 | }
91 |
92 | private func updateSystemButtons(hidden: Bool) {
93 | for window in NSApplication.shared.windows {
94 | window.standardWindowButton(.zoomButton)?.isHidden = hidden
95 | window.standardWindowButton(.miniaturizeButton)?.isHidden = hidden
96 | window.standardWindowButton(.closeButton)?.isHidden = hidden
97 | }
98 | }
99 |
100 | private func openSnippet(fromURL url: URL) {
101 | closeWindowsIfVisible()
102 | let snippetId = url.absoluteString.replacingOccurrences(of: "widget://", with: "")
103 |
104 | DispatchQueue.main.asyncAfter(deadline: Constants.delay) {
105 | NSApplication.shared.windows.first?.orderFront(nil)
106 | activeAppView = .snippetsLibrary(snippetId)
107 | }
108 | }
109 |
110 | private func closeWindowsIfVisible() {
111 | for window in NSApplication.shared.windows where window.className != Constants.statusWindowClassName {
112 | DispatchQueue.main.async {
113 | window.close()
114 | }
115 | }
116 | }
117 |
118 | }
119 |
120 |
121 | struct AppView_Previews: PreviewProvider {
122 | static var previews: some View {
123 | AppView(activeAppView: .constant(nil), activeAppSheet: .constant(nil), shouldBeDisabled: .constant(false))
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Application/SnippetsLibraryApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetsLibraryApp.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 07/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct SnippetsLibraryApp: App {
12 |
13 | // MARK: - Stored Properties
14 |
15 | @Environment(\.openURL) var openURL
16 |
17 | @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
18 |
19 | @State private var activeAppView: ActiveAppView? = nil
20 | @State private var activeAppSheet: AppSheet? = nil
21 | @State private var shouldBeDisabled = false
22 |
23 | // MARK: - Views
24 |
25 | var body: some Scene {
26 | WindowGroup {
27 | AppView(
28 | activeAppView: $activeAppView,
29 | activeAppSheet: $activeAppSheet,
30 | shouldBeDisabled: $shouldBeDisabled
31 | )
32 | .onReceive(NotificationCenter.default.publisher(for: NSNotification.statusBarSnippetTapped)) {
33 | openSnippet(from: $0)
34 | }
35 | }
36 | .windowStyle(HiddenTitleBarWindowStyle())
37 | .onChange(of: activeAppView) {
38 | shouldBeDisabled = ($0 == .snippetsLibrary(nil))
39 | }
40 | .commands {
41 | CommandGroup(replacing: .newItem) {
42 | Button("Add New Code Snippet...") {
43 | if activeAppView != .snippetsLibrary(nil) {
44 | activeAppView = .create
45 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
46 | activeAppSheet = .snippetDetails(Snippet(), .create)
47 | }
48 | } else {
49 | activeAppSheet = .snippetDetails(Snippet(), .create)
50 | }
51 | }
52 |
53 | Button("Import Local Snippet...") {
54 | activeAppView = .importSnippet
55 | }
56 |
57 | DisabledCommandGroupButton(
58 | text: "Open Snippets Library...",
59 | shouldBeDisabled: $shouldBeDisabled,
60 | type: .openLibrary
61 | ) {
62 | activeAppView = .snippetsLibrary(nil)
63 | }
64 |
65 | Divider()
66 |
67 | DisabledCommandGroupButton(
68 | text: "Upload All Snippets To Xcode...",
69 | shouldBeDisabled: $shouldBeDisabled,
70 | type: .uploadSnippets
71 | ) {
72 | activeAppSheet = .snippetsUpload
73 | }
74 | .keyboardShortcut(
75 | "u",
76 | modifiers: [.command, .control, .option]
77 | )
78 | }
79 |
80 | CommandGroup(replacing: .help) {
81 | Button("Report issue...") {
82 | composeEmail()
83 | }
84 |
85 | Button("Show User Guides") {
86 | openURL(url: DIContainer.urlFactory.getURL(withType: .userGuides))
87 | }
88 | }
89 |
90 | CommandGroup(replacing: .windowList) {
91 | Button("Developer Documentation") {
92 | openURL(url: DIContainer.urlFactory.getURL(withType: .docs))
93 | }
94 | .keyboardShortcut(
95 | "0",
96 | modifiers: [.command, .shift])
97 | }
98 | }
99 | }
100 |
101 | // MARK: - Methods
102 |
103 | private func composeEmail() {
104 | let service = NSSharingService(named: NSSharingService.Name.composeEmail)
105 | service?.recipients = ["support@swiftdevtools.com"]
106 | service?.subject = "Snippets Library Issue"
107 | service?.perform(withItems: ["Please describe your problem here..."])
108 | }
109 |
110 | private func openURL(url: URL?) {
111 | guard let safeURL = url else { return }
112 | openURL(safeURL)
113 | }
114 |
115 | private func openSnippet(from output: NotificationCenter.Publisher.Output) {
116 | if let userInfo = output.userInfo, let snippetId = userInfo["snippetId"] as? String {
117 | NSApplication.shared.windows.first?.center()
118 | NSApplication.shared.windows.first?.makeKeyAndOrderFront(nil)
119 | NSApp.activate(ignoringOtherApps: true)
120 | activeAppView = .snippetsLibrary(snippetId)
121 | }
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "1.000",
9 | "green" : "0.478",
10 | "red" : "0.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryboxx/SnippetsLibrary/d7a4c31ceb735b6cf92fcff8cfa38fcfa743ee0c/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/1024.png
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryboxx/SnippetsLibrary/d7a4c31ceb735b6cf92fcff8cfa38fcfa743ee0c/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/128.png
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryboxx/SnippetsLibrary/d7a4c31ceb735b6cf92fcff8cfa38fcfa743ee0c/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/16.png
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/256-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryboxx/SnippetsLibrary/d7a4c31ceb735b6cf92fcff8cfa38fcfa743ee0c/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/256-1.png
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryboxx/SnippetsLibrary/d7a4c31ceb735b6cf92fcff8cfa38fcfa743ee0c/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/256.png
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/32-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryboxx/SnippetsLibrary/d7a4c31ceb735b6cf92fcff8cfa38fcfa743ee0c/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/32-1.png
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryboxx/SnippetsLibrary/d7a4c31ceb735b6cf92fcff8cfa38fcfa743ee0c/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/32.png
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/512-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryboxx/SnippetsLibrary/d7a4c31ceb735b6cf92fcff8cfa38fcfa743ee0c/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/512-1.png
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryboxx/SnippetsLibrary/d7a4c31ceb735b6cf92fcff8cfa38fcfa743ee0c/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/512.png
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryboxx/SnippetsLibrary/d7a4c31ceb735b6cf92fcff8cfa38fcfa743ee0c/SnippetsLibrary/Assets.xcassets/AppIcon.appiconset/64.png
--------------------------------------------------------------------------------
/SnippetsLibrary/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 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/Colors/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/Colors/accent.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "1.000",
9 | "green" : "0.478",
10 | "red" : "0.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/Colors/background.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.125",
9 | "green" : "0.125",
10 | "red" : "0.125"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/Colors/darkGrey.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.180",
9 | "green" : "0.176",
10 | "red" : "0.176"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/Colors/defaultBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.971",
9 | "green" : "0.962",
10 | "red" : "0.966"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "display-p3",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.102",
27 | "green" : "0.102",
28 | "red" : "0.102"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/Colors/secondaryBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.188",
9 | "green" : "0.165",
10 | "red" : "0.161"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/Colors/skeletonLight.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.837",
9 | "green" : "0.837",
10 | "red" : "0.837"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.261",
27 | "green" : "0.261",
28 | "red" : "0.261"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/Colors/skeletonRegular.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.754",
9 | "green" : "0.754",
10 | "red" : "0.754"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.371",
27 | "green" : "0.371",
28 | "red" : "0.371"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/Colors/skeletonThin.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.894",
9 | "green" : "0.895",
10 | "red" : "0.894"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.130",
27 | "green" : "0.130",
28 | "red" : "0.130"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/icLogo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icLogo.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/icLogo.imageset/icLogo.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryboxx/SnippetsLibrary/d7a4c31ceb735b6cf92fcff8cfa38fcfa743ee0c/SnippetsLibrary/Assets.xcassets/icLogo.imageset/icLogo.pdf
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/icLogoStatusBar.imageset/Books_front_matte.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryboxx/SnippetsLibrary/d7a4c31ceb735b6cf92fcff8cfa38fcfa743ee0c/SnippetsLibrary/Assets.xcassets/icLogoStatusBar.imageset/Books_front_matte.pdf
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/icLogoStatusBar.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Books_front_matte.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/icSnippetFileBlank.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icSnippetFileBlank.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/icSnippetFileBlank.imageset/icSnippetFileBlank.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryboxx/SnippetsLibrary/d7a4c31ceb735b6cf92fcff8cfa38fcfa743ee0c/SnippetsLibrary/Assets.xcassets/icSnippetFileBlank.imageset/icSnippetFileBlank.png
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/icSnippetFileWhite.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icSnippetFile.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Assets.xcassets/icSnippetFileWhite.imageset/icSnippetFile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryboxx/SnippetsLibrary/d7a4c31ceb735b6cf92fcff8cfa38fcfa743ee0c/SnippetsLibrary/Assets.xcassets/icSnippetFileWhite.imageset/icSnippetFile.png
--------------------------------------------------------------------------------
/SnippetsLibrary/Dependencies/DependencyContainer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DependencyContainer.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 12/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | let DIContainer = DependencyContainer.shared
11 |
12 | final class DependencyContainer {
13 |
14 | // MARK: - Services
15 |
16 | lazy var snippetsParserService: SnippetsParserService = SnippetsParserServiceImpl()
17 | lazy var urlFactory: URLFactory = URLFactoryImpl()
18 | lazy var networkServcie: NetworkService = NetworkServiceImpl()
19 |
20 | lazy var databaseService: DatabaseService = DatabaseServiceImpl(
21 | logsService: logsService,
22 | crashlyticsService: crashlyticsService
23 | )
24 | lazy var logsService: LogsService = LogsServiceImpl()
25 | lazy var crashlyticsService: CrashlyticsService = CrashlyticsServiceImpl()
26 | lazy var userDefaultsService: UserDefaultsService = UserDefaultsServiceImpl()
27 |
28 | // MARK: - Shared
29 |
30 | static let shared = DependencyContainer()
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Extensions/AnyPublisher+SinkWithoutValue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnyPublisher+SinkWithoutValue.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 12/09/2021.
6 | //
7 |
8 | import Combine
9 |
10 | extension AnyPublisher where Self.Output == Void {
11 |
12 | func sink(receiveCompletion: @escaping (Subscribers.Completion) -> Void) -> AnyCancellable {
13 | return sink(receiveCompletion: receiveCompletion) { _ in }
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Extensions/AppDelegate+NSMenuDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate+NSMenuDelegate.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 27/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension AppDelegate: NSMenuDelegate {
11 |
12 | func menuWillOpen(_ menu: NSMenu) {
13 | setupStatusView(for: menu)
14 | }
15 |
16 | func menuDidClose(_ menu: NSMenu) {}
17 |
18 | internal func setupMenuItems() {
19 | menu = AppMenu()
20 |
21 | self.statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
22 | self.statusBarItem?.menu = menu
23 | menu?.delegate = self
24 |
25 | if let item = menu?.items.first(where: { $0.title == "Developer Documentation..." }) {
26 | item.action = #selector(openDocsURL(_:))
27 | }
28 |
29 | if let button = self.statusBarItem?.button {
30 | button.image = NSImage(named: "icLogoStatusBar")
31 | }
32 | }
33 |
34 | private func setupStatusView(for menu: NSMenu) {
35 | if let item = menu.items.first {
36 | statusView = NSHostingView(rootView: StatusView())
37 | setupItemView(
38 | menu: menu,
39 | item: item
40 | )
41 | }
42 | }
43 |
44 | private func setupItemView(
45 | menu: NSMenu,
46 | item: NSMenuItem
47 | ) {
48 | statusView?.frame = CGRect(
49 | x: .zero,
50 | y: .zero,
51 | width: 250.0,
52 | height: 170.0
53 | )
54 | item.view = statusView
55 | }
56 |
57 | @objc private func openDocsURL(_ sender: Any) {
58 | let urlFactory = DIContainer.urlFactory
59 | let docsURL = urlFactory.getURL(withType: .docs)
60 | guard let safeURL = docsURL else { return }
61 | openURL(safeURL)
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Extensions/AppMenu+HideWindow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppMenu+HideWindow.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 27/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension AppMenu: NSWindowDelegate {
11 |
12 | func windowShouldClose(_ sender: NSWindow) -> Bool {
13 | NSApp.hide(nil)
14 | return false
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Extensions/DatabaseReference+Timeout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DatabaseReference+Timeout.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 25/09/2021.
6 | //
7 |
8 | import FirebaseDatabase
9 |
10 | extension DatabaseReference {
11 |
12 | func observe(
13 | _ eventType: DataEventType,
14 | timeout: TimeInterval,
15 | with block: @escaping (DataSnapshot?) -> Void
16 | ) -> UInt {
17 | var handle: UInt!
18 |
19 | let timer = Timer.scheduledTimer(
20 | withTimeInterval: timeout,
21 | repeats: false
22 | ) { _ in
23 | self.removeObserver(withHandle: handle)
24 | block(nil)
25 | }
26 |
27 | handle = observe(eventType) { snapshot in
28 | timer.invalidate()
29 | block(snapshot)
30 | }
31 |
32 | return handle
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Extensions/NSApplication+AppVersion.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSApplication+AppVersion.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 07/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension NSApplication {
11 |
12 | static var appVersion: String {
13 | let versionNumber = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
14 | let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String
15 |
16 | let formattedBuildNumber = buildNumber.map {
17 | return "(\($0))"
18 | }
19 |
20 | return [versionNumber,formattedBuildNumber].compactMap { $0 }.joined(separator: " ")
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Extensions/NSNotification+Name.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSNotification+Name.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 27/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | extension NSNotification {
11 |
12 | static let statusBarSnippetTapped = Notification.Name("statusBarSnippetTapped")
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Extensions/NSTableView+BackgroundColor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSTableView+BackgroundColor.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 08/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension NSTableView {
11 |
12 | open override func viewDidMoveToWindow() {
13 | super.viewDidMoveToWindow()
14 |
15 | backgroundColor = NSColor.clear
16 | enclosingScrollView?.drawsBackground = false
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Extensions/SnippetPlist+Dictonary.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetPlist+Dictonary.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 19/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | extension SnippetPlist {
11 |
12 | func convertedToDictonary(id: String? = nil) -> [String: Any] {
13 | return [
14 | "IDECodeSnippetIdentifier": id ?? self.id,
15 | "IDECodeSnippetTitle": title,
16 | "IDECodeSnippetSummary": summary,
17 | "IDECodeSnippetCompletionPrefix": completion,
18 | "IDECodeSnippetCompletionScopes": availability,
19 | "IDECodeSnippetContents": contents,
20 | "IDECodeSnippetLanguage": language,
21 | "IDECodeSnippetPlatformFamily": platform,
22 | "IDECodeSnippetUserSnippet": userSnippet,
23 | "IDECodeSnippetVersion": version,
24 | "IDECodeSnippetAuthor": author ?? "Christopher Lowiec"
25 | ] as [String: Any]
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Extensions/SnippetsLibraryView+Equatable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetsLibraryView+Equatable.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 25/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | extension SnippetsLibraryView: Equatable {
11 |
12 | static func == (lhs: SnippetsLibraryView, rhs: SnippetsLibraryView) -> Bool {
13 | return lhs.viewModel.snippets != rhs.viewModel.snippets
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Extensions/StartView+Equatable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StartView+Equatable.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 25/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | extension StartView: Equatable {
11 |
12 | static func == (lhs: StartView, rhs: StartView) -> Bool {
13 | return lhs.viewModel.activeAppView == rhs.viewModel.activeAppView
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Extensions/String+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Extensions.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 12/10/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | extension String {
11 |
12 | func numberOfLines() -> Int {
13 | return numberOfOccurrencesOf(string: "\n") + 1
14 | }
15 |
16 | func numberOfOccurrencesOf(string: String) -> Int {
17 | return components(separatedBy: string).count - 1
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Extensions/View+Displayed.swift:
--------------------------------------------------------------------------------
1 | //
2 | // View+Displayed.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 25/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension View {
11 |
12 | func makeDisplayed(
13 | with visible: Binding,
14 | imageName: String? = nil,
15 | title: String,
16 | subtitle: String? = nil,
17 | displayingTime: Double = 2,
18 | state: ToastViewState = .none
19 | ) -> some View {
20 | self.overlay(
21 | ToastView(
22 | imageName: imageName,
23 | title: title,
24 | subtitle: subtitle,
25 | state: state
26 | )
27 | .offset(y: visible.wrappedValue ? -((Layout.defaultWindowSize.height / 2) - Layout.smallPadding) : 0)
28 | .makeVisible(
29 | visible.wrappedValue,
30 | removed: true
31 | )
32 | .animation(.default)
33 | .onChange(of: visible.wrappedValue) { _ in
34 | DispatchQueue.main.asyncAfter(deadline: .now() + displayingTime) {
35 | withAnimation {
36 | visible.wrappedValue = false
37 | }
38 | }
39 | }
40 | .onTapGesture {
41 | withAnimation {
42 | visible.wrappedValue = false
43 | }
44 | }
45 | )
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Extensions/View+Skeletonable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // View+Skeletonable.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 25/09/2021.
6 | //
7 |
8 | import SwiftUI
9 | import SwiftUISkeleton
10 |
11 | extension View {
12 |
13 | func makeSkeletonable(
14 | animating: Bool,
15 | isBottomView: Bool = false
16 | ) -> some View {
17 | self.skeleton(
18 | with: animating,
19 | shape: RoundedRectangle(cornerRadius: 4.0),
20 | animation: .linear(duration: 1)
21 | .repeatForever(autoreverses: true),
22 | gradient:
23 | Gradient(
24 | colors: [
25 | Color(isBottomView ? "skeletonLight" : "skeletonRegular"),
26 | Color(isBottomView ? "skeletonThin": "skeletonLight")
27 | ]
28 | )
29 | )
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Extensions/View+Visibility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // View+Visibility.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 07/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension View {
11 |
12 | @ViewBuilder func makeVisible(
13 | _ visible: Bool,
14 | removed: Bool = false
15 | ) -> some View {
16 | if !visible {
17 | if !removed {
18 | self.hidden()
19 | }
20 | } else {
21 | self
22 | }
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | LSApplicationCategoryType
22 | public.app-category.developer-tools
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | Apache 2.0 License. All trademarks are protected.
27 | NSSystemAdministrationUsageDescription
28 | I want to read all your files
29 |
30 |
31 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Models/Snippet.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Snippet.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 07/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | typealias SnippetId = String
11 |
12 | struct Snippet: Codable, Identifiable, Hashable {
13 |
14 | // MARK: - Stored Properties
15 |
16 | let id: SnippetId
17 | var title: String
18 | var summary: String
19 | var content: String
20 | var author: String
21 | var completion: String
22 | var platform: SnippetPlatform
23 | var availability: SnippetAvailability
24 | var tags: [String]?
25 |
26 | // MARK: - Computed Properties
27 |
28 | var type: SnippetType {
29 | title.contains("+") ? .extending : .snippets
30 | }
31 |
32 | var category: SnippetCategory {
33 | return SnippetCategory.allCases.first(where: { title.lowercased().contains($0.rawValue) }) ?? .helper
34 | }
35 |
36 | // MARK: - CodingKeys
37 |
38 | enum CodingKeys: String, CodingKey {
39 | case id, title, summary, content, author, completion, platform, availability, tags
40 | }
41 |
42 | // MARK: - Initialization
43 |
44 | init() {
45 | id = UUID().uuidString
46 | title = ""
47 | summary = ""
48 | content = ""
49 | author = ""
50 | completion = ""
51 | platform = .all
52 | availability = .allScopes
53 | }
54 |
55 | init(mockedForSkeleton: Bool) {
56 | id = UUID().uuidString
57 | title = "Default snippet title"
58 | summary = "Default snippet summary"
59 | content = ""
60 | author = ""
61 | completion = ""
62 | platform = .all
63 | availability = .allScopes
64 | }
65 |
66 | init(
67 | id: SnippetId,
68 | title: String,
69 | summary: String,
70 | content: String,
71 | author: String,
72 | completion: String,
73 | platform: SnippetPlatform,
74 | availability: SnippetAvailability,
75 | tags: [String] = []
76 | ) {
77 | self.id = id
78 | self.title = title
79 | self.summary = summary
80 | self.content = content
81 | self.author = author
82 | self.completion = completion
83 | self.platform = platform
84 | self.availability = availability
85 | self.tags = tags
86 | }
87 |
88 | init(from snippetPlist: SnippetPlist) {
89 | id = snippetPlist.id
90 | title = snippetPlist.title
91 | summary = snippetPlist.summary
92 | content = snippetPlist.contents
93 | author = snippetPlist.author ?? ""
94 | completion = snippetPlist.completion
95 | platform = SnippetPlatform.allCases.first(where: { $0.rawValue == snippetPlist.platform }) ?? SnippetPlatform.all
96 | availability = SnippetAvailability.allCases.first(where: { $0.string == snippetPlist.availability.first }) ?? SnippetAvailability.allScopes
97 | tags = []
98 | }
99 |
100 | init(from decoder: Decoder) throws {
101 | let values = try decoder.container(keyedBy: PlistCodingKeys.self)
102 | id = try values.decode(String.self, forKey: .id)
103 | title = try values.decode(String.self, forKey: .title)
104 | summary = try values.decode(String.self, forKey: .summary)
105 | content = try values.decode(String.self, forKey: .contents)
106 | author = try values.decode(String.self, forKey: .author)
107 | completion = try values.decode(String.self, forKey: .completion)
108 |
109 | let platformName = try values.decode(String.self, forKey: .platform)
110 | platform = SnippetPlatform.allCases.first { $0.rawValue == platformName } ?? .all
111 |
112 | let availabilityName = try values.decode([String].self, forKey: .availability)
113 | availability = SnippetAvailability.allCases.first { $0.string == availabilityName.first } ?? .allScopes
114 | tags = try? values.decode([String].self, forKey: .tags)
115 | }
116 |
117 | // MARK: - Methods
118 |
119 | func encode(to encoder: Encoder) throws {
120 | var container = encoder.container(keyedBy: CodingKeys.self)
121 | try container.encode(id, forKey: .id)
122 | try container.encode(title, forKey: .title)
123 | try container.encode(summary, forKey: .summary)
124 | try container.encode(content, forKey: .content)
125 | try container.encode(author, forKey: .author)
126 | try container.encode(completion, forKey: .completion)
127 | try container.encode(platform.title, forKey: .platform)
128 | try container.encode(availability.title, forKey: .availability)
129 | try? container.encode(tags, forKey: .tags)
130 | }
131 |
132 | }
133 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Models/SnippetPlist.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetPlist.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 12/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | struct SnippetPlist: Codable {
11 |
12 | var id: String
13 | var title: String
14 | var summary: String
15 | var contents: String
16 | var author: String?
17 | var language: String
18 | var completion: String
19 | var platform: String
20 | var availability: [String]
21 | var userSnippet: Bool
22 | var version: Int
23 |
24 | init(from snippet: Snippet, withId id: String? = nil) {
25 | self.id = id ?? snippet.id
26 | title = snippet.title
27 | summary = snippet.summary
28 | contents = snippet.content
29 | author = snippet.author
30 | completion = snippet.completion
31 | platform = snippet.platform.rawValue
32 | availability = [snippet.availability.string]
33 | language = "Xcode.SourceCodeLanguage.Swift"
34 | userSnippet = true
35 | version = 2
36 | }
37 |
38 | init(from decoder: Decoder) throws {
39 | let values = try decoder.container(keyedBy: PlistCodingKeys.self)
40 | id = try values.decode(String.self, forKey: .id)
41 | title = try values.decode(String.self, forKey: .title)
42 | summary = try values.decode(String.self, forKey: .summary)
43 | contents = try values.decode(String.self, forKey: .contents)
44 | author = try values.decode(String.self, forKey: .author)
45 | completion = try values.decode(String.self, forKey: .completion)
46 | language = try values.decode(String.self, forKey: .language)
47 | userSnippet = try values.decode(Bool.self, forKey: .userSnippet)
48 | version = try values.decode(Int.self, forKey: .version)
49 |
50 | let platformName = try values.decode(String.self, forKey: .platform)
51 | platform = SnippetPlatform.allCases.first { $0.rawValue == platformName }?.rawValue ?? SnippetPlatform.all.rawValue
52 |
53 | let availabilityKey = try values.decode([String].self, forKey: .availability)
54 | availability = [SnippetAvailability.allCases.first { $0.string == availabilityKey.first }?.string ?? SnippetAvailability.allScopes.string]
55 | }
56 |
57 | func encode(to encoder: Encoder) throws {
58 | var container = encoder.container(keyedBy: PlistCodingKeys.self)
59 | try container.encode(id, forKey: .id)
60 | try container.encode(title, forKey: .title)
61 | try container.encode(summary, forKey: .summary)
62 | try container.encode(contents, forKey: .contents)
63 | try container.encode(author, forKey: .author)
64 | try container.encode(completion, forKey: .completion)
65 | try container.encode(platform, forKey: .platform)
66 | try container.encode(availability, forKey: .availability)
67 | try container.encode(language, forKey: .language)
68 | try container.encode(userSnippet, forKey: .userSnippet)
69 | try container.encode(version, forKey: .version)
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Modules/SnippetDetails/SnippetDetailsViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetDetailsViewModel.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 11/09/2021.
6 | //
7 |
8 | import SwiftUI
9 | import Combine
10 |
11 | final class SnippetDetailsViewModel: ObservableObject {
12 |
13 | // MARK: - Stored Properties
14 |
15 | @Published var snippet: Snippet
16 | @Published var text: String = ""
17 | @Binding private(set) var activeAppView: ActiveAppView?
18 |
19 | let type: SnippetDetailsViewType
20 | private var lastSavedSnippet: Snippet? = nil
21 |
22 | @Published var platformSelectionIndex = 0
23 | let platforms = SnippetPlatform.allCases
24 |
25 | @Published var availabilitySelectionIndex = 0
26 | let availabilities = SnippetAvailability.allCases
27 |
28 | @Published private(set) var shouldDismissView = false
29 | @Published var shouldShowErrorAlert = false
30 |
31 | private let snippetsParserService: SnippetsParserService
32 | private let databaseService: DatabaseService
33 |
34 | private var cancellables = Set()
35 |
36 | // MARK: - Computed Properties
37 |
38 | var hasChanges: Bool {
39 | lastSavedSnippet != snippet && !snippet.title.isEmpty &&
40 | !snippet.summary.isEmpty && !snippet.content.isEmpty &&
41 | !snippet.completion.isEmpty
42 | }
43 |
44 | // MARK: - Initialization
45 |
46 | init(
47 | snippet: Snippet,
48 | type: SnippetDetailsViewType,
49 | activeAppView: Binding,
50 | snippetsParserService: SnippetsParserService = DIContainer.snippetsParserService,
51 | databaseService: DatabaseService = DIContainer.databaseService
52 | ) {
53 | self.snippet = snippet
54 | self.type = type
55 | self._activeAppView = activeAppView
56 | self.snippetsParserService = snippetsParserService
57 | self.databaseService = databaseService
58 |
59 | setup()
60 | text = snippet.content
61 | }
62 |
63 | // MARK: - Methods
64 |
65 | func performChanges() {
66 | guard hasChanges else { return }
67 |
68 | if type == .create {
69 | createSnippet()
70 | } else {
71 | updateSnippet()
72 | }
73 | }
74 |
75 | private func createSnippet() {
76 | snippetsParserService.createSnippet(snippet)
77 | .sink { [weak self] completion in
78 | switch completion {
79 | case .finished: return
80 | case .failure:
81 | self?.shouldShowErrorAlert.toggle()
82 | }
83 | } receiveValue: { [weak self] in
84 | self?.saveSnippetsInDatabase($0)
85 | }
86 | .store(in: &cancellables)
87 | }
88 |
89 | private func updateSnippet() {
90 | databaseService.updateSnippet(snippet)
91 | .sink { [weak self] completion in
92 | switch completion {
93 | case .finished:
94 | DispatchQueue.main.async {
95 | self?.shouldDismissView.toggle()
96 | }
97 | case .failure:
98 | self?.shouldShowErrorAlert.toggle()
99 | }
100 | }
101 | .store(in: &cancellables)
102 | }
103 |
104 | private func saveSnippetsInDatabase(_ snippet: SnippetPlist) {
105 | databaseService.saveSnippet(snippet)
106 | .sink { [weak self] completion in
107 | switch completion {
108 | case .finished:
109 | DispatchQueue.main.async {
110 | self?.shouldDismissView.toggle()
111 | }
112 | case .failure:
113 | self?.shouldShowErrorAlert.toggle()
114 | }
115 | }
116 | .store(in: &cancellables)
117 | }
118 |
119 | private func setup() {
120 | lastSavedSnippet = snippet
121 | platformSelectionIndex = SnippetPlatform.allCases.firstIndex(of: snippet.platform) ?? 0
122 | availabilitySelectionIndex = SnippetAvailability.allCases.firstIndex(of: snippet.availability) ?? 0
123 | }
124 |
125 | }
126 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Modules/SnippetImport/SnippetImportView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetImportView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 12/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SnippetImportView: View {
11 |
12 | private enum Constants {
13 | static let dropCellSize = CGSize(width: 250, height: 170)
14 | static let cornerRadius: CGFloat = 12.0
15 | static let lineWidth: CGFloat = 1
16 | static let dashCount: CGFloat = 10
17 | }
18 |
19 | // MARK: - Stored Properties
20 |
21 | @ObservedObject private(set) var viewModel: SnippetImportViewModel
22 |
23 | // MARK: - Views
24 |
25 | var body: some View {
26 | ZStack {
27 | VisualEffectView(
28 | material: .windowBackground,
29 | blendingMode: .behindWindow
30 | )
31 |
32 | ZStack {
33 | VStack {
34 | SnippetDropCellView(snippet: viewModel.snippet)
35 | .frame(
36 | width: Constants.dropCellSize.width,
37 | height: Constants.dropCellSize.height
38 | )
39 | .overlay(
40 | RoundedRectangle(cornerRadius: Constants.cornerRadius)
41 | .stroke(
42 | style: StrokeStyle(
43 | lineWidth: Constants.lineWidth,
44 | dash: [Constants.dashCount]
45 | )
46 | )
47 | .foregroundColor(Color.primary)
48 | )
49 | .padding([.horizontal, .bottom])
50 | }
51 | }
52 | .onDrop(
53 | of: [.fileURL],
54 | delegate: viewModel.self
55 | )
56 | }
57 | .makeDisplayed(
58 | with: $viewModel.shouldShowErrorAlert,
59 | imageName: "network",
60 | title: "Network error",
61 | subtitle: "Unable to import the snippet",
62 | state: .failure
63 | )
64 | }
65 |
66 | }
67 |
68 | struct SnippetImportView_Previews: PreviewProvider {
69 | static var previews: some View {
70 | SnippetImportView(viewModel: SnippetImportViewModel(activeAppView: .constant(nil)))
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Modules/SnippetImport/SnippetImportViewModel+DropDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetImportViewModel+DropDelegate.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 13/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension SnippetImportViewModel: DropDelegate {
11 |
12 | func validateDrop(info: DropInfo) -> Bool {
13 | debugPrint("DropInfo: \(info)")
14 | return true
15 | }
16 |
17 | func performDrop(info: DropInfo) -> Bool {
18 | receiveDrop(
19 | dropIndex: .zero,
20 | itemProviders: info.itemProviders(for: [.fileURL])
21 | )
22 | }
23 |
24 | @discardableResult
25 | func receiveDrop(
26 | dropIndex: Int,
27 | itemProviders: [NSItemProvider],
28 | create: Bool = true
29 | ) -> Bool {
30 | var result = false
31 | for provider in itemProviders {
32 | if provider.canLoadObject(ofClass: String.self) {
33 | result = true
34 | loadSnippet(
35 | from: provider,
36 | at: dropIndex
37 | )
38 | }
39 | }
40 | return result
41 | }
42 |
43 | private func loadSnippet(
44 | from provider: NSItemProvider,
45 | at dropIndex: Int
46 | ) {
47 | provider.loadItem(forTypeIdentifier: kUTTypeFileURL as String) { [weak self] (data, error) in
48 | guard
49 | let safeData = data as? Data,
50 | let url = URL(dataRepresentation: safeData, relativeTo: nil)
51 | else {
52 | // Unable to load
53 | return
54 | }
55 |
56 | let decoder = PropertyListDecoder()
57 |
58 | guard
59 | let dataFromURL = try? Data(contentsOf: url),
60 | let snippet = try? decoder.decode(Snippet.self, from: dataFromURL)
61 | else {
62 | // Unable to load
63 | return
64 | }
65 |
66 | self?.createSnippet(snippet)
67 | }
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Modules/SnippetImport/SnippetImportViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetImportViewModel.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 12/09/2021.
6 | //
7 |
8 | import SwiftUI
9 | import Combine
10 |
11 | final class SnippetImportViewModel: ObservableObject {
12 |
13 | // MARK: - Stored Properties
14 |
15 | @Published internal var snippet: Snippet?
16 | @Binding internal var activeAppView: ActiveAppView?
17 |
18 | @Published internal var shouldShowErrorAlert = false
19 |
20 | private let snippetsParserService: SnippetsParserService
21 | private let databaseService: DatabaseService
22 | private let crashlyticsService: CrashlyticsService
23 |
24 | internal var cancellables = Set()
25 |
26 | // MARK: - Initialization
27 |
28 | init(
29 | snippetsParserService: SnippetsParserService = DIContainer.snippetsParserService,
30 | databaseService: DatabaseService = DIContainer.databaseService,
31 | crashlyticsService: CrashlyticsService = DIContainer.crashlyticsService,
32 | activeAppView: Binding
33 | ) {
34 | self.snippetsParserService = snippetsParserService
35 | self._activeAppView = activeAppView
36 | self.databaseService = databaseService
37 | self.crashlyticsService = crashlyticsService
38 | }
39 |
40 | // MARK: - Methods
41 |
42 | internal func createSnippet(_ snippet: Snippet) {
43 | snippetsParserService.createSnippet(snippet)
44 | .sink { [weak self] completion in
45 | switch completion {
46 | case .finished: return
47 | case .failure:
48 | self?.shouldShowErrorAlert.toggle()
49 | self?.crashlyticsService.logNonFatalError(.unableToCreateSnippetFromDroppedFile)
50 | }
51 | } receiveValue: { [weak self] in
52 | self?.saveSnippetsInDatabase($0)
53 | }
54 | .store(in: &self.cancellables)
55 | }
56 |
57 | internal func saveSnippetsInDatabase(_ snippet: SnippetPlist) {
58 | databaseService.saveSnippet(snippet)
59 | .sink { [weak self] completion in
60 | switch completion {
61 | case .finished:
62 | self?.activeAppView = .snippetsLibrary(snippet.id)
63 | case .failure:
64 | self?.shouldShowErrorAlert.toggle()
65 | }
66 | }
67 | .store(in: &cancellables)
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Modules/SnippetsLibrary/SnippetLibraryList/SnippetsLibraryListFilterView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetsLibraryListFilterView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 01/10/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SnippetsLibraryListFilterView: View {
11 |
12 | private enum Constants {
13 | static let dividerRotationDegree = 90.0
14 | static let viewHeight: CGFloat = 24.0
15 | }
16 |
17 | // MARK: - Stored Properites
18 |
19 | @Binding internal var selectedCategory: SnippetCategory?
20 | private let categories = SnippetCategory.allCases
21 |
22 | // MARK: - Views
23 |
24 | var body: some View {
25 | ScrollView(
26 | .horizontal,
27 | showsIndicators: false
28 | ) {
29 | LazyHStack(spacing: .zero) {
30 | ForEach(categories, id: \.self) { category in
31 | Button {
32 | selectedCategory = category
33 | } label: {
34 | Text(category.rawValue.capitalized)
35 | .font(.system(size: 11.0))
36 | .foregroundColor(selectedCategory == category ? Color.white : Color.primary)
37 | }
38 | .buttonStyle(PlainButtonStyle())
39 | .padding(.horizontal, Layout.smallPadding)
40 | .padding(.vertical, Layout.smallPadding / 4)
41 | .background(
42 | Capsule()
43 | .foregroundColor(Color.accentColor)
44 | .makeVisible(selectedCategory == category)
45 | )
46 | .padding(
47 | .leading,
48 | category == .view ? Layout.standardPadding : .zero
49 | )
50 |
51 | Divider()
52 | .rotationEffect(.degrees(Constants.dividerRotationDegree))
53 | }
54 |
55 | Button {
56 | selectedCategory = nil
57 | } label: {
58 | Text("Remove all filters")
59 | .font(.system(size: 11.0))
60 | .foregroundColor(Color.red)
61 | }
62 | .buttonStyle(PlainButtonStyle())
63 | .padding(.leading, Layout.smallPadding)
64 | }
65 | }
66 | .frame(height: Constants.viewHeight)
67 | }
68 |
69 | }
70 |
71 | struct SnippetsLibraryListFilterView_Previews: PreviewProvider {
72 | static var previews: some View {
73 | SnippetsLibraryListFilterView(selectedCategory: .constant(nil))
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Modules/SnippetsLibrary/SnippetLibraryList/SnippetsLibraryListSectionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetsLibraryListSectionView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 21/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SnippetsLibraryListSectionView: View {
11 |
12 | private enum Constants {
13 | static let defaultSectionName = "Section"
14 | }
15 |
16 | // MARK: - Stored Properties
17 |
18 | private(set) var snippets: [Snippet]
19 | @Binding internal var shouldShowRemoveAlert: Bool
20 |
21 | // MARK: - Views
22 |
23 | var body: some View {
24 | Section(header: Text(snippets.first?.type.title ?? Constants.defaultSectionName)) {
25 | ForEach(snippets, id: \.id) {
26 | SnippetListItemView(snippet: $0)
27 | #if DEBUG
28 | .contextMenu {
29 | Button("Delete") {
30 | shouldShowRemoveAlert.toggle()
31 | }
32 | }
33 | #endif
34 | }
35 | }
36 | .tag(snippets.first?.type.rawValue ?? .zero)
37 | }
38 |
39 | }
40 |
41 | struct SnippetsLibraryListSectionView_Previews: PreviewProvider {
42 | static var previews: some View {
43 | SnippetsLibraryListSectionView(snippets: [], shouldShowRemoveAlert: .constant(false))
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Modules/SnippetsLibrary/SnippetLibraryList/SnippetsLibraryListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetsLibraryListView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 08/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SnippetsLibraryListView: View {
11 |
12 | private enum Constants {
13 | static let minWidth: CGFloat = Layout.defaultWindowSize.width * 0.35
14 | }
15 |
16 | // MARK: - Stored Properties
17 |
18 | @ObservedObject private(set) var viewModel: SnippetsLibraryListViewModel
19 |
20 | // MARK: - Views
21 |
22 | var body: some View {
23 | VStack {
24 | HStack {
25 | SearchBar {
26 | viewModel.searchedText = $0
27 | }
28 | .padding(.leading)
29 |
30 | Button {
31 | withAnimation {
32 | viewModel.shouldShowFilterView.toggle()
33 | }
34 | } label: {
35 | Image(systemName: "line.horizontal.3.decrease.circle")
36 | .font(.system(size: 15, weight: .light))
37 | .foregroundColor(
38 | Color.primary
39 | .opacity(Layout.mediumOpacity)
40 | )
41 | }
42 | .buttonStyle(PlainButtonStyle())
43 | .help("Choose the filters")
44 |
45 | Button {
46 | viewModel.onReload()
47 | } label: {
48 | Image(systemName: "arrow.counterclockwise.circle")
49 | .font(.system(size: 15, weight: .light))
50 | .foregroundColor(
51 | Color.primary
52 | .opacity(Layout.mediumOpacity)
53 | )
54 | }
55 | .buttonStyle(PlainButtonStyle())
56 | .help("Pull all changes from remote")
57 | .padding(.trailing, Layout.smallPadding)
58 | }
59 | .padding(.top, Layout.largePadding)
60 |
61 | SnippetsLibraryListFilterView(selectedCategory: $viewModel.selectedCategory)
62 | .makeVisible(
63 | viewModel.shouldShowFilterView,
64 | removed: true
65 | )
66 | .animation(.default)
67 |
68 | List(selection: $viewModel.selectedSnippetId) {
69 | ForEach(
70 | viewModel.snippetGroups,
71 | id: \.self
72 | ) {
73 | SnippetsLibraryListSectionView(
74 | snippets: $0,
75 | shouldShowRemoveAlert: $viewModel.shouldShowRemoveAlert
76 | )
77 | .makeVisible(
78 | !$0.isEmpty,
79 | removed: true
80 | )
81 | }
82 | }
83 | .overlay(
84 | EmptySnippetsListView(isListEmpty: .constant(viewModel.snippets.isEmpty || viewModel.hasAnyResults ))
85 | )
86 | }
87 | .frame(minWidth: Constants.minWidth)
88 | .background(
89 | VisualEffectView(
90 | material: .menu,
91 | blendingMode: .behindWindow
92 | )
93 | )
94 | .edgesIgnoringSafeArea(.top)
95 | .alert(isPresented: $viewModel.shouldShowRemoveAlert) {
96 | Alert(
97 | title: Text("Confirm removing"),
98 | message: Text("You sure want to remove this snippet?"),
99 | primaryButton:
100 | .destructive(
101 | Text("Yes, remove"),
102 | action: { viewModel.onRemove(viewModel.selectedSnippetId) }
103 | ),
104 | secondaryButton: .cancel()
105 | )
106 | }
107 | }
108 |
109 | }
110 |
111 | struct SnippetsLibraryListView_Previews: PreviewProvider {
112 | static var previews: some View {
113 | SnippetsLibraryListView(viewModel: SnippetsLibraryListViewModel(snippets: .constant([]), selectedSnippetId: .constant(nil), onReload: {}, onRemove: { _ in }))
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Modules/SnippetsLibrary/SnippetLibraryList/SnippetsLibraryListViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetsLibraryListViewModel.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 01/10/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | final class SnippetsLibraryListViewModel: ObservableObject {
11 |
12 | // MARK: - Stored Properties
13 |
14 | @Binding internal var snippets: [Snippet]
15 | @Binding internal var selectedSnippetId: SnippetId?
16 |
17 | @Published internal var searchedText = ""
18 | @Published internal var shouldShowRemoveAlert = false
19 | @Published internal var shouldShowFilterView = false
20 | @Published internal var selectedCategory: SnippetCategory? = nil
21 |
22 | private(set) var onReload: () -> Void
23 | private(set) var onRemove: (_ snippetId: SnippetId?) -> Void
24 |
25 | // MARK: - Computed Properties
26 |
27 | internal var snippetGroups: [[Snippet]] {
28 | return [
29 | snippets.filter({ $0.type == .snippets }).filter({ matchingSnippet($0) }).filter({ $0.category == selectedCategory || selectedCategory == nil }),
30 | snippets.filter({ $0.type == .extending }).filter { matchingSnippet($0) }
31 | ]
32 | }
33 |
34 | internal var hasAnyResults: Bool {
35 | (snippetGroups.first?.isEmpty == true) && (snippetGroups.last?.isEmpty == true)
36 | }
37 |
38 | // MARK: - Initialization
39 |
40 | init(
41 | snippets: Binding<[Snippet]>,
42 | selectedSnippetId: Binding,
43 | onReload: @escaping () -> Void,
44 | onRemove: @escaping (_ snippetId: SnippetId?) -> Void
45 | ) {
46 | self._snippets = snippets
47 | self._selectedSnippetId = selectedSnippetId
48 | self.onReload = onReload
49 | self.onRemove = onRemove
50 | }
51 |
52 | // MARK: - Methods
53 |
54 | private func matchingSnippet(_ snippet: Snippet) -> Bool {
55 | snippet.title.lowercased().contains(searchedText.lowercased()) ||
56 | snippet.summary.lowercased().contains(searchedText.lowercased()) ||
57 | searchedText.isEmpty
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Modules/SnippetsLibrary/SnippetsLibraryPreview/SnippetsLibraryPreviewView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetsLibraryPreviewView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 08/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SnippetsLibraryPreviewView: View {
11 |
12 | // MARK: - Stored Properties
13 |
14 | @Binding internal var snippets: [Snippet]
15 | @Binding internal var selectedSnippetId: SnippetId?
16 | @Binding internal var activeSheet: AppSheet?
17 | @Binding internal var appAlert: AppAlert?
18 |
19 | private(set) var onTap: (_ snippet: Snippet) -> Void
20 |
21 | // MARK: - Views
22 |
23 | var body: some View {
24 | VStack(spacing: .zero) {
25 | Spacer()
26 | .makeVisible(
27 | selectedSnippetId == nil,
28 | removed: true
29 | )
30 |
31 | Text("Tap the snippet on the left to preview")
32 | .font(.system(size: 17.0))
33 | .foregroundColor(
34 | Color.primary
35 | .opacity(Layout.mediumOpacity)
36 | )
37 | .makeVisible(
38 | selectedSnippetId == nil,
39 | removed: true
40 | )
41 |
42 | SnippetFileCardView(
43 | viewModel: SnippetFileCardViewModel(
44 | snippet: snippets.first(where: { $0.id == selectedSnippetId }),
45 | state: .preview,
46 | activeSheet: $activeSheet,
47 | appAlert: $appAlert
48 | )
49 | )
50 | .padding()
51 | .onTapGesture {
52 | guard let snippet = snippets.first(where: { $0.id == selectedSnippetId }) else { return }
53 | activeSheet = .snippetDetails(snippet, .edit)
54 | onTap(snippet)
55 | }
56 | .makeVisible(
57 | selectedSnippetId != nil,
58 | removed: true
59 | )
60 |
61 | Spacer()
62 | }
63 | .frame(minWidth: Layout.defaultWindowSize.width * 0.65)
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Modules/SnippetsLibrary/SnippetsLibraryView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetsLibraryView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 08/09/2021.
6 | //
7 |
8 | import SwiftUI
9 | import PopupView
10 |
11 | struct SnippetsLibraryView: View {
12 |
13 | // MARK: - Stored Properties
14 |
15 | @ObservedObject private(set) var viewModel: SnippetsLibraryViewModel
16 |
17 | @Binding internal var activeSheet: AppSheet?
18 | @State private var appAlert: AppAlert? = nil
19 | @State private var shouldShowSnippetManual = false
20 |
21 | // MARK: - Views
22 |
23 | var body: some View {
24 | ZStack {
25 | HSplitView {
26 | SnippetsLibraryListView(
27 | viewModel: SnippetsLibraryListViewModel(
28 | snippets: $viewModel.snippets,
29 | selectedSnippetId: $viewModel.selectedSnippetId
30 | ) {
31 | viewModel.fetchSnippets()
32 | } onRemove: {
33 | viewModel.onRemove($0)
34 | }
35 | )
36 |
37 | SnippetsLibraryPreviewView(
38 | snippets: $viewModel.snippets,
39 | selectedSnippetId: $viewModel.selectedSnippetId,
40 | activeSheet: $activeSheet,
41 | appAlert: $appAlert
42 | ) {
43 | viewModel.saveSnippetToRecentSnippets($0)
44 | }
45 | }
46 | }
47 | .frame(
48 | minWidth: Layout.defaultWindowSize.width,
49 | maxWidth: .infinity,
50 | minHeight: Layout.defaultWindowSize.height,
51 | maxHeight: .infinity
52 | )
53 | .onChange(of: viewModel.snippets) { _ in
54 | viewModel.showSnippetPreview()
55 | }
56 | .onChange(of: viewModel.hasConnection) {
57 | viewModel.onChangeConnectionState(hasConnection: $0)
58 | }
59 | .sheet(item: $activeSheet) {
60 | switch $0 {
61 | case let .snippetDetails(snippet, type):
62 | SnippetDetailsView(
63 | viewModel: SnippetDetailsViewModel(
64 | snippet: snippet,
65 | type: type,
66 | activeAppView: .constant(nil)
67 | )
68 | )
69 | case .snippetsUpload:
70 | SnippetsUploadView(viewModel: SnippetsUploadViewModel(snippets: viewModel.snippets))
71 | }
72 | }
73 | .alert(item: $appAlert) {
74 | switch $0 {
75 | case .snippetDownload:
76 | return Alert(
77 | title: Text("Unexpected error"),
78 | message: Text("Unable to download the snippet. Please try again later."),
79 | dismissButton: .cancel()
80 | )
81 | }
82 | }
83 | .makeDisplayed(
84 | with: $viewModel.shouldShowErrorAlert,
85 | imageName: "network",
86 | title: "Network error",
87 | subtitle: "Requested operation couldn't be completed",
88 | state: .failure
89 | )
90 | .popup(
91 | isPresented: $shouldShowSnippetManual,
92 | type: .floater(verticalPadding: Layout.defaultWindowSize.height),
93 | position: .bottom,
94 | animation: .easeInOut,
95 | closeOnTapOutside: true,
96 | backgroundColor: Color.black.opacity(Layout.mediumOpacity)
97 | ) {
98 | viewModel.markSnippetManualAsReaded()
99 | } view: {
100 | SnippetCreationManualView()
101 | }
102 | .onAppear {
103 | showSnippetManualIfNeeded()
104 | }
105 | }
106 |
107 | // MARK: - Methods
108 |
109 | private func showSnippetManualIfNeeded() {
110 | let userDefaultsService = DIContainer.userDefaultsService
111 | shouldShowSnippetManual = userDefaultsService.shouldShowSnippetManual()
112 | }
113 |
114 | }
115 |
116 | struct SnippetsLibraryView_Previews: PreviewProvider {
117 | static var previews: some View {
118 | SnippetsLibraryView(viewModel: SnippetsLibraryViewModel(hasConnection: .constant(true)), activeSheet: .constant(nil))
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Modules/SnippetsLibrary/SnippetsLibraryViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetsLibraryViewModel.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 08/09/2021.
6 | //
7 |
8 | import SwiftUI
9 | import Combine
10 |
11 | final class SnippetsLibraryViewModel: ObservableObject {
12 |
13 | // MARK: - Stored Properties
14 |
15 | @Published internal var snippets: [Snippet] = skeletonSnippets
16 | @Published internal var selectedSnippetId: SnippetId?
17 | @Published internal var shouldShowErrorAlert = false
18 | @Published internal var hasConnection: Bool
19 |
20 | private let activeSnippetId: SnippetId?
21 |
22 | private let databaseService: DatabaseService
23 | private let userDefaultsService: UserDefaultsService
24 |
25 | private var cancellables = Set()
26 |
27 | // MARK: - Initialization
28 |
29 | init(
30 | activeSnippetId: SnippetId? = nil,
31 | hasConnection: Binding,
32 | databaseService: DatabaseService = DIContainer.databaseService,
33 | userDefaultsService: UserDefaultsService = DIContainer.userDefaultsService
34 | ) {
35 | self.activeSnippetId = activeSnippetId
36 | self.hasConnection = hasConnection.wrappedValue
37 | self.databaseService = databaseService
38 | self.userDefaultsService = userDefaultsService
39 |
40 | fetchSnippets()
41 | }
42 |
43 | // MARK: - Methods
44 |
45 | internal func onRemove(_ snippetId: SnippetId?) {
46 | guard
47 | let snippetId = snippetId,
48 | let snippet = snippets.first(where: { $0.id == snippetId })
49 | else {
50 | shouldShowErrorAlert.toggle()
51 | return
52 | }
53 |
54 | removeSnippet(snippet)
55 | }
56 |
57 | internal func fetchSnippets() {
58 | databaseService.snippets
59 | .receive(on: DispatchQueue.main)
60 | .sink { [weak self] completion in
61 | switch completion {
62 | case .finished: return
63 | case .failure:
64 | self?.shouldShowErrorAlert.toggle()
65 | self?.snippets = []
66 | self?.onChangeConnectionState(hasConnection: false)
67 | }
68 | } receiveValue: { [weak self] in
69 | self?.snippets = $0
70 | }
71 | .store(in: &cancellables)
72 | }
73 |
74 | internal func saveSnippetToRecentSnippets(_ snippet: Snippet) {
75 | userDefaultsService.saveRecentSnippet(snippet)
76 | }
77 |
78 | internal func showSnippetPreview() {
79 | guard let snippetId = activeSnippetId else { return }
80 | selectedSnippetId = snippetId
81 | }
82 |
83 | internal func onChangeConnectionState(hasConnection: Bool) {
84 | if !hasConnection && !snippets.isEmpty && snippets != skeletonSnippets {
85 | userDefaultsService.saveSnippetsLocally(snippets)
86 | } else if !hasConnection && snippets.isEmpty {
87 | fetchLocalSnippets()
88 | }
89 | }
90 |
91 | internal func markSnippetManualAsReaded() {
92 | userDefaultsService.markSnippetManualAsReaded()
93 | }
94 |
95 | private func fetchLocalSnippets() {
96 | userDefaultsService.fetchSnippets(fetchingType: .local)
97 | .receive(on: DispatchQueue.main)
98 | .sink { [weak self] completion in
99 | switch completion {
100 | case .finished: return
101 | case .failure:
102 | self?.shouldShowErrorAlert.toggle()
103 | self?.snippets = []
104 | }
105 | } receiveValue: { [weak self] in
106 | self?.snippets = $0
107 | }
108 | .store(in: &cancellables)
109 | }
110 |
111 | private func removeSnippet(_ snippet: Snippet) {
112 | databaseService.removeSnippet(snippet)
113 | .sink { [weak self] completion in
114 | switch completion {
115 | case .finished:
116 | self?.snippets.removeAll { $0.id == snippet.id }
117 | case .failure:
118 | self?.shouldShowErrorAlert.toggle()
119 | }
120 | }
121 | .store(in: &cancellables)
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Modules/SnippetsUpload/SnippetsUploadView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetsUploadView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 26/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SnippetsUploadView: View {
11 |
12 | private enum Constants {
13 | static let totalProgressValue: CGFloat = 1.0
14 | }
15 |
16 | // MARK: - Stored Properties
17 |
18 | @Environment (\.presentationMode) var presentationMode
19 |
20 | @StateObject internal var viewModel: SnippetsUploadViewModel
21 |
22 | // MARK: - Views
23 |
24 | var body: some View {
25 | VStack(spacing: Layout.standardPadding) {
26 | Spacer()
27 |
28 | Image(systemName: viewModel.uploadingStatus == .done ? "checkmark.circle.fill" : "xmark.octagon.fill")
29 | .font(.system(size: 56, weight: .regular))
30 | .foregroundColor(viewModel.uploadingStatus == .done ? Color.green : Color.red)
31 | .makeVisible(
32 | viewModel.uploadingStatus != .uploading && viewModel.uploadingStatus != .initializing,
33 | removed: true
34 | )
35 |
36 | Text(viewModel.uploadingStatus.title)
37 | .font(.system(size: 17, weight: .semibold))
38 | .foregroundColor(Color.primary)
39 |
40 | Text(viewModel.uploadingStatus.message)
41 | .font(.system(size: 13, weight: .regular))
42 | .foregroundColor(
43 | Color.primary
44 | .opacity(Layout.largeOpacity)
45 | )
46 | .multilineTextAlignment(.center)
47 | .padding(.top, -Layout.smallPadding)
48 | .makeVisible(
49 | viewModel.uploadingStatus != .uploading && viewModel.uploadingStatus != .initializing,
50 | removed: true
51 | )
52 |
53 | ProgressView(
54 | value: viewModel.progressValue,
55 | total: Constants.totalProgressValue,
56 | label: { EmptyView() },
57 | currentValueLabel: {
58 | HStack {
59 | Spacer()
60 |
61 | Text(viewModel.uploadingStatus.currentDescription)
62 |
63 | Spacer()
64 | }
65 | .padding(.top, Layout.smallPadding)
66 | }
67 | )
68 | .progressViewStyle(LinearProgressViewStyle(tint: Color.accentColor))
69 | .makeVisible(
70 | viewModel.uploadingStatus == .uploading || viewModel.uploadingStatus == .initializing,
71 | removed: true
72 | )
73 |
74 | Spacer()
75 |
76 | HStack {
77 | Spacer()
78 |
79 | Button("Done") { presentationMode.wrappedValue.dismiss() }
80 | }
81 | .makeVisible(
82 | viewModel.uploadingStatus != .uploading && viewModel.uploadingStatus != .initializing,
83 | removed: true
84 | )
85 | .padding(.bottom)
86 | }
87 | .padding(.horizontal, Layout.largePadding)
88 | .frame(
89 | width: Layout.defaultWindowSize.width * 0.6,
90 | height: Layout.defaultWindowSize.height * 0.7
91 | )
92 | .onChange(of: viewModel.shouldDismissView) { _ in
93 | presentationMode.wrappedValue.dismiss()
94 | }
95 | .onAppear {
96 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
97 | viewModel.uploadSnippetsToXcode()
98 | }
99 | }
100 | }
101 |
102 | }
103 |
104 | struct SnippetsUploadView_Previews: PreviewProvider {
105 | static var previews: some View {
106 | SnippetsUploadView(viewModel: SnippetsUploadViewModel(snippets: []))
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Modules/SnippetsUpload/SnippetsUploadViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetsUploadViewModel.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 26/09/2021.
6 | //
7 |
8 | import AppKit
9 | import Combine
10 |
11 | final class SnippetsUploadViewModel: ObservableObject {
12 |
13 | private enum Constants {
14 | static let midValue: CGFloat = 0.5
15 | static let fullValue: CGFloat = 1.0
16 | }
17 |
18 | // MARK: - Stored Properties
19 |
20 | private(set) var snippets: [Snippet]
21 |
22 | @Published internal var progressValue: CGFloat = .zero
23 | @Published private(set) var shouldDismissView = false
24 | @Published internal var uploadingStatus: UploadingStatus = .initializing
25 |
26 | private let snippetsParserService: SnippetsParserService
27 |
28 | private var cancellables = Set()
29 |
30 | // MARK: - Initialization
31 |
32 | init(
33 | snippets: [Snippet],
34 | snippetsParserService: SnippetsParserService = DIContainer.snippetsParserService
35 | ) {
36 | self.snippets = snippets
37 | self.snippetsParserService = snippetsParserService
38 | }
39 |
40 | // MARK: - Methods
41 |
42 | internal func uploadSnippetsToXcode() {
43 | snippetsParserService.writeToPath(
44 | type: .uploadToXcode,
45 | snippets: snippets
46 | ) {
47 | self.uploadingStatus = .uploading
48 | self.progressValue = Constants.midValue
49 | DispatchQueue.main.asyncAfter(deadline: .now() + Constants.midValue) {
50 | self.progressValue = Constants.fullValue
51 | self.finishUpload()
52 | }
53 | } onError: {
54 | self.uploadingStatus = .error
55 | }
56 | }
57 |
58 | private func finishUpload() {
59 | DispatchQueue.main.asyncAfter(deadline: .now() + Constants.fullValue) {
60 | self.uploadingStatus = .done
61 | }
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Modules/Start/StartView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StartView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 07/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct StartView: View {
11 |
12 | private enum Constants {
13 | static let logoImageHeight: CGFloat = 130
14 | static let menuItemsSpacing: CGFloat = 12.0
15 | }
16 |
17 | // MARK: - Stored Properties
18 |
19 | @ObservedObject private(set) var viewModel: StartViewModel
20 |
21 | // MARK: - Views
22 |
23 | var body: some View {
24 | HSplitView {
25 | VStack(spacing: .zero) {
26 | HStack {
27 | Button {
28 | viewModel.closeView()
29 | } label: {
30 | Image(systemName: "xmark")
31 | .font(.system(size: 10.0, weight: .medium))
32 | }
33 |
34 | Spacer()
35 | }
36 | .padding()
37 | .buttonStyle(PlainButtonStyle())
38 |
39 | Image("icLogo")
40 | .resizable()
41 | .aspectRatio(contentMode: .fit)
42 | .frame(height: Constants.logoImageHeight)
43 | .padding(.top, Layout.smallPadding)
44 |
45 | Text("Snippets Library")
46 | .font(.system(size: 40.0, weight: .regular))
47 |
48 | Text("Version \(NSApplication.appVersion)")
49 | .font(.system(size: 13.0, weight: .light))
50 | .foregroundColor(Color.primary.opacity(Layout.mediumOpacity))
51 | .padding(.top, Layout.smallPadding / 2)
52 |
53 | Spacer()
54 |
55 | VStack(
56 | alignment: .leading,
57 | spacing: Constants.menuItemsSpacing
58 | ) {
59 | StartViewMenuItem(type: .create) {
60 | if viewModel.activeAppView != .snippetsLibrary(nil) {
61 | viewModel.activeAppView = .create
62 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
63 | viewModel.activeAppSheet = .snippetDetails(Snippet(), .create)
64 | }
65 | } else {
66 | viewModel.activeAppSheet = .snippetDetails(Snippet(), .create)
67 | }
68 | }
69 |
70 | StartViewMenuItem(type: .importSnippet) {
71 | viewModel.activeAppView = .importSnippet
72 | }
73 |
74 | StartViewMenuItem(type: .open) {
75 | viewModel.activeAppView = .snippetsLibrary(nil)
76 | }
77 | }
78 | .padding(.leading, Layout.largePadding * 2)
79 | .padding(.trailing, Layout.largePadding)
80 |
81 | Spacer()
82 | }
83 | .frame(width: Layout.defaultWindowSize.width * 0.62)
84 | .ignoresSafeArea()
85 |
86 | StartViewRecentSnippetsView(
87 | recentSnippets: $viewModel.recentSnippets,
88 | activeAppView: $viewModel.activeAppView
89 | )
90 | }
91 | .frame(
92 | width: Layout.defaultWindowSize.width,
93 | height: Layout.defaultWindowSize.height
94 | )
95 | }
96 |
97 | }
98 |
99 | struct StartView_Previews: PreviewProvider {
100 | static var previews: some View {
101 | StartView(viewModel: StartViewModel(activeAppView: .constant(nil), activeAppSheet: .constant(nil)))
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Modules/Start/StartViewMenuItem/StartViewMenuItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StartViewMenuItem.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 07/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct StartViewMenuItem: View {
11 |
12 | // MARK: - Stored Properties
13 |
14 | let type: StartViewMenuItemType
15 | private(set) var onTap: () -> Void
16 |
17 | // MARK: - Views
18 |
19 | var body: some View {
20 | Button {
21 | onTap()
22 | } label: {
23 | HStack(spacing: Layout.standardPadding) {
24 | Image(systemName: type.systemImageName)
25 | .font(.system(size: type.imageFontSize, weight: .light))
26 | .foregroundColor(Color("accent"))
27 |
28 | VStack(
29 | alignment: .leading,
30 | spacing: .zero
31 | ) {
32 | Text(type.title)
33 | .font(.system(size: 13.0, weight: .semibold))
34 |
35 | Text(type.subtitle)
36 | .font(.system(size: 13.0))
37 | .foregroundColor(
38 | Color.primary
39 | .opacity(Layout.mediumOpacity)
40 | )
41 | }
42 | .padding(.leading, type.leadingPadding)
43 |
44 | Spacer()
45 | }
46 | }
47 | .buttonStyle(PlainButtonStyle())
48 | }
49 |
50 | }
51 |
52 | struct StartViewMenuItem_Previews: PreviewProvider {
53 | static var previews: some View {
54 | StartViewMenuItem(type: .create) {}
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Modules/Start/StartViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StartViewModel.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 07/09/2021.
6 | //
7 |
8 | import SwiftUI
9 | import Combine
10 |
11 | final class StartViewModel: ObservableObject {
12 |
13 | // MARK: - Stored Properties
14 |
15 | @Published internal var recentSnippets: [Snippet] = []
16 |
17 | @Binding internal var activeAppView: ActiveAppView?
18 | @Binding internal var activeAppSheet: AppSheet?
19 |
20 | private let userDefaultsService: UserDefaultsService
21 |
22 | private var cancellables = Set()
23 |
24 | // MARK: - Initialization
25 |
26 | init(
27 | activeAppView: Binding,
28 | activeAppSheet: Binding,
29 | userDefaultsService: UserDefaultsService = DIContainer.userDefaultsService
30 | ) {
31 | self._activeAppView = activeAppView
32 | self._activeAppSheet = activeAppSheet
33 | self.userDefaultsService = userDefaultsService
34 |
35 | fetchRecentSnippets()
36 | }
37 |
38 | // MARK: - Methods
39 |
40 | internal func closeView() {
41 | NSApplication.shared.keyWindow?.close()
42 | }
43 |
44 | internal func fetchRecentSnippets() {
45 | userDefaultsService.fetchSnippets(fetchingType: .recent)
46 | .receive(on: DispatchQueue.main)
47 | .sink { [weak self] in
48 | self?.recentSnippets = $0
49 | }
50 | .store(in: &cancellables)
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Modules/Start/StartViewRecentSnippets/StartViewRecentSnippetsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StartViewRecentSnippetsView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 07/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct StartViewRecentSnippetsView: View {
11 |
12 | private enum Constants {
13 | static let viewSize = CGSize(width: 850, height: 460)
14 | static let spacerHeight: CGFloat = 16.0
15 | static let scrollViewHorizontalPadding: CGFloat = 10.0
16 | }
17 |
18 | // MARK: - Stored Properties
19 |
20 | @Binding internal var recentSnippets: [Snippet]
21 | @Binding internal var activeAppView: ActiveAppView?
22 |
23 | @State private var selectedSnippetIndex: Int?
24 |
25 | // MARK: - Views
26 |
27 | var body: some View {
28 | VStack(spacing: .zero) {
29 | Text("No Recent Files")
30 | .font(.system(size: 17.0))
31 | .foregroundColor(
32 | Color.primary
33 | .opacity(Layout.mediumOpacity)
34 | )
35 | .frame(height: Constants.viewSize.height)
36 | .makeVisible(
37 | recentSnippets.isEmpty,
38 | removed: true
39 | )
40 |
41 | ScrollView(showsIndicators: false) {
42 | Rectangle()
43 | .foregroundColor(Color.clear)
44 | .frame(height: Constants.spacerHeight)
45 |
46 | ForEach(
47 | Array(recentSnippets.enumerated()),
48 | id: \.offset
49 | ) { (index, snippet) in
50 | RecentSnippetCardView(
51 | snippet: snippet,
52 | tag: index,
53 | selectedFileIndex: $selectedSnippetIndex
54 | ) {
55 | activeAppView = .snippetsLibrary(snippet.id)
56 | }
57 | }
58 | }
59 | .background(Color.clear)
60 | .makeVisible(!recentSnippets.isEmpty)
61 | .padding(.horizontal, Constants.scrollViewHorizontalPadding)
62 | }
63 | .frame(width: Constants.viewSize.width * 0.38)
64 | .background(
65 | VisualEffectView(
66 | material: .menu,
67 | blendingMode: .behindWindow
68 | )
69 | )
70 | .ignoresSafeArea()
71 | }
72 |
73 | }
74 |
75 | struct StartViewRecentSnippetsView_Previews: PreviewProvider {
76 | static var previews: some View {
77 | StartViewRecentSnippetsView(recentSnippets: .constant([]), activeAppView: .constant(nil))
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Modules/Status/StatusView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StatusView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 27/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct StatusView: View {
11 |
12 | private enum Constants {
13 | static let emptySnippetsViewSize = CGSize(width: 230, height: 150)
14 | static let cornerRadius: CGFloat = 12.0
15 | static let strokeLineWidth: CGFloat = 1
16 | static let strokeDashSpacing: CGFloat = 10
17 | static let viewWidth: CGFloat = 230
18 | static let viewMinHeight: CGFloat = 80
19 | static let viewMaxHeight: CGFloat = 150
20 | }
21 |
22 | // MARK: - Stored Properties
23 |
24 | @StateObject var viewModel = StatusViewModel()
25 |
26 | // MARK: - Views
27 |
28 | var body: some View {
29 | VStack {
30 | VStack {
31 | Text("No recent snippets")
32 | .font(.system(size: 17.0))
33 | }
34 | .frame(
35 | width: Constants.emptySnippetsViewSize.width,
36 | height: Constants.emptySnippetsViewSize.height
37 | )
38 | .overlay(
39 | RoundedRectangle(cornerRadius: Constants.cornerRadius)
40 | .stroke(
41 | style: StrokeStyle(
42 | lineWidth: Constants.strokeLineWidth,
43 | dash: [Constants.strokeDashSpacing],
44 | dashPhase: .zero
45 | )
46 | )
47 | .foregroundColor(.primary)
48 | )
49 | .padding()
50 | .makeVisible(
51 | viewModel.snippets.isEmpty,
52 | removed: true
53 | )
54 |
55 | Group {
56 | FileStatusCard(
57 | viewModel: FileStatusCardViewModel(
58 | snippet: viewModel.snippets.first,
59 | type: .large
60 | )
61 | )
62 |
63 | HStack {
64 | HStack {
65 | FileStatusCard(
66 | viewModel: FileStatusCardViewModel(
67 | snippet: viewModel.snippets.last
68 | )
69 | )
70 |
71 | Spacer()
72 | }
73 | .makeVisible(
74 | viewModel.snippets.count > 1,
75 | removed: true
76 | )
77 |
78 | FileStatusCard(
79 | viewModel: FileStatusCardViewModel(
80 | snippet: viewModel.snippets.randomElement()
81 | )
82 | )
83 | .makeVisible(
84 | viewModel.snippets.count > 2,
85 | removed: true
86 | )
87 | }
88 | }
89 | .makeVisible(
90 | !viewModel.snippets.isEmpty,
91 | removed: true
92 | )
93 |
94 | Spacer()
95 | }
96 | .accentColor(Color.accentColor)
97 | .frame(
98 | width: Constants.viewWidth,
99 | height: viewModel.snippets.count == 1 ? Constants.viewMinHeight : Constants.viewMaxHeight
100 | )
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Modules/Status/StatusViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StatusViewModel.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 27/09/2021.
6 | //
7 |
8 | import SwiftUI
9 | import Combine
10 |
11 | final class StatusViewModel: ObservableObject {
12 |
13 | // MARK: - Stored Properties
14 |
15 | @Published internal var snippets: [Snippet] = []
16 | @Published internal var shouldShowRemoveAlert: Bool = false
17 |
18 | private let userDefaultsService: UserDefaultsService
19 |
20 | private var cancellables = Set()
21 |
22 | // MARK: - Initialization
23 |
24 | init(userDefaultsService: UserDefaultsService = DIContainer.userDefaultsService) {
25 | self.userDefaultsService = userDefaultsService
26 |
27 | fetchSnippets()
28 | }
29 |
30 | // MARK: - Methods
31 |
32 | private func fetchSnippets() {
33 | userDefaultsService.fetchSnippets(fetchingType: .recent)
34 | .receive(on: DispatchQueue.main)
35 | .sink { completion in
36 | switch completion {
37 | case let .failure(error):
38 | debugPrint(error.localizedDescription)
39 | case .finished:
40 | return
41 | }
42 | } receiveValue: { [weak self] in
43 | self?.snippets = $0
44 | }
45 | .store(in: &cancellables)
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Services/Firebase/Crashlytics/CrashlyticsService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CrashlyticsService.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 17/09/2021.
6 | //
7 |
8 | import Firebase
9 |
10 | protocol CrashlyticsService {
11 | func logNonFatalError(_ type: CrashlyticsErrorType)
12 | func logError(withMessage message: String)
13 | }
14 |
15 | final class CrashlyticsServiceImpl: CrashlyticsService {
16 |
17 | // MARK: - Initialization
18 |
19 | init() {
20 | Crashlytics.crashlytics().setUserID(System.getHardwareUUID())
21 | }
22 |
23 | // MARK: - Methods
24 |
25 | internal func logNonFatalError(_ type: CrashlyticsErrorType) {
26 | let userInfo = [
27 | NSLocalizedDescriptionKey: NSLocalizedString("Non-fatal error reported.", comment: ""),
28 | NSLocalizedFailureReasonErrorKey: NSLocalizedString("\(type.rawValue)", comment: "")
29 | ]
30 |
31 | let error = NSError(
32 | domain: NSCocoaErrorDomain,
33 | code: -1001,
34 | userInfo: userInfo
35 | )
36 | Crashlytics.crashlytics().record(error: error)
37 | }
38 |
39 | internal func logError(withMessage message: String) {
40 | Crashlytics.crashlytics().log(message)
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Services/Firebase/Logs/LogsService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LogsService.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 17/09/2021.
6 | //
7 |
8 | import Foundation
9 | import FirebaseDatabase
10 |
11 | protocol LogsService {
12 | func logUserActivity(type: UserActivityLogType)
13 | }
14 |
15 | final class LogsServiceImpl: LogsService {
16 |
17 | // MARK: - Stored Properties
18 |
19 | private let ref = Database.database().reference()
20 |
21 | // MARK: - Methods
22 |
23 | internal func logUserActivity(type: UserActivityLogType) {
24 | let values: [String: Any] = [
25 | "userDeviceUUID": System.getHardwareUUID(),
26 | "actionDescription": type.title,
27 | "date": Date().description(with: Locale.current),
28 | "level": type.level.rawValue
29 | ]
30 | #if RELEASE
31 | ref.child("usersActivityLog").childByAutoId().setValue(values)
32 | #else
33 | ref.child("developersActivityLog").childByAutoId().setValue(values)
34 | #endif
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Services/Network/NetworkService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkService.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 25/09/2021.
6 | //
7 |
8 | import Network
9 | import Combine
10 |
11 | protocol NetworkService {
12 | var isConnected: Published.Publisher { get }
13 | }
14 |
15 | final class NetworkServiceImpl: NetworkService {
16 |
17 | // MARK: - Stored Properties
18 |
19 | private let monitor = NWPathMonitor()
20 |
21 | @Published var isConnectedValue: Bool = false
22 | var isConnected: Published.Publisher { $isConnectedValue }
23 |
24 | // MARK: - Initialization
25 |
26 | init() {
27 | setup()
28 | observeState()
29 | }
30 |
31 | // MARK: - Methods
32 |
33 | internal func observeState() {
34 | self.monitor.pathUpdateHandler = {
35 | self.isConnectedValue = $0.status == .satisfied
36 | }
37 | }
38 |
39 | private func setup() {
40 | let queue = DispatchQueue(label: "NWPathMonitor")
41 | monitor.start(queue: queue)
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Services/URLFactory/URLFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLFactory.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 23/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol URLFactory {
11 | func getURL(withType type: URLType) -> URL?
12 | }
13 |
14 | final class URLFactoryImpl: URLFactory {
15 |
16 | private enum Constants {
17 | static let userGuidesUrlString = "https://github.com/tryboxx/SnippetsLibrary/blob/main/README.md"
18 | static let developerDocumentationUrlString = "https://github.com/tryboxx/SnippetsLibrary/wiki"
19 | }
20 |
21 | // MARK: - Methods
22 |
23 | internal func getURL(withType type: URLType) -> URL? {
24 | switch type {
25 | case .userGuides:
26 | return URL(string: Constants.userGuidesUrlString)
27 | case .docs:
28 | return URL(string: Constants.developerDocumentationUrlString)
29 | }
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Services/UserDefaults/UserDefaultsService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDefaultsService.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 19/09/2021.
6 | //
7 |
8 | import Foundation
9 | import Combine
10 |
11 | protocol UserDefaultsService {
12 | func saveRecentSnippet(_ snippet: Snippet)
13 | func fetchSnippets(fetchingType: SnippetsFetchingType) -> AnyPublisher<[Snippet], Never>
14 | func saveSnippetsLocally(_ snippets: [Snippet])
15 |
16 | func fetchRecentSnippetsFromAppGroup() -> [Snippet]
17 |
18 | func shouldShowSnippetManual() -> Bool
19 | func markSnippetManualAsReaded()
20 | }
21 |
22 | final class UserDefaultsServiceImpl: UserDefaultsService {
23 |
24 | private enum Constants {
25 | static let recentSnippetKey = "RecentSnippet"
26 | static let localSnippetKey = "LocalSnippet"
27 | static let appGroupName = "group.com.cphlowiec.SnippetsLibrary"
28 | static let snippetManualKey = "wasSnippetManualReaded"
29 | }
30 |
31 | // MARK: - Stored Properties
32 |
33 | private let userDefaults = UserDefaults.standard
34 |
35 | // MARK: - Methods
36 |
37 | // MARK: - Recent Snippets -
38 |
39 | internal func saveRecentSnippet(_ snippet: Snippet) {
40 | let snippet = SnippetPlist(from: snippet)
41 | let snippetDictonary = snippet.convertedToDictonary()
42 | let snippetKey = Constants.recentSnippetKey + " \(snippet.id)"
43 |
44 | userDefaults.set(
45 | snippetDictonary,
46 | forKey: snippetKey
47 | )
48 |
49 | saveSnippetDictonaryIntoAppGroup(
50 | snippetDictonary,
51 | key: snippetKey
52 | )
53 | }
54 |
55 | internal func fetchSnippets(fetchingType: SnippetsFetchingType) -> AnyPublisher<[Snippet], Never> {
56 | let fetchingKey = fetchingType == .recent ? Constants.recentSnippetKey : Constants.localSnippetKey
57 | let keys = userDefaults.dictionaryRepresentation().keys.filter { $0.contains(fetchingKey) }
58 |
59 | return Future<[Snippet], Never> { [weak self] promise in
60 | var snippets = [Snippet]()
61 |
62 | for key in keys {
63 | guard
64 | let snippetDictonary = self?.userDefaults.object(forKey: key),
65 | let data = try? JSONSerialization.data(withJSONObject: snippetDictonary, options: []),
66 | let snippet = try? JSONDecoder().decode(SnippetPlist.self, from: data)
67 | else { continue }
68 |
69 | snippets.append(Snippet(from: snippet))
70 | }
71 |
72 | promise(.success(snippets))
73 | }
74 | .eraseToAnyPublisher()
75 | }
76 |
77 | // MARK: - Local snippets -
78 |
79 | internal func saveSnippetsLocally(_ snippets: [Snippet]) {
80 | for snippet in snippets {
81 | saveSnippetLocally(snippet)
82 | }
83 | }
84 |
85 | private func saveSnippetLocally(_ snippet: Snippet) {
86 | let snippet = SnippetPlist(from: snippet)
87 | let snippetDictonary = snippet.convertedToDictonary()
88 | let snippetKey = Constants.localSnippetKey + " \(snippet.id)"
89 |
90 | userDefaults.set(
91 | snippetDictonary,
92 | forKey: snippetKey
93 | )
94 | }
95 |
96 | // MARK: - App Group -
97 |
98 | internal func fetchRecentSnippetsFromAppGroup() -> [Snippet] {
99 | guard let userDefaults = UserDefaults(suiteName: Constants.appGroupName) else { return [] }
100 |
101 | let keys = userDefaults.dictionaryRepresentation().keys.filter { $0.contains(Constants.recentSnippetKey) }
102 | var snippets = [Snippet]()
103 |
104 | for key in keys {
105 | guard
106 | let snippetDictonary = userDefaults.object(forKey: key),
107 | let data = try? JSONSerialization.data(withJSONObject: snippetDictonary, options: []),
108 | let snippet = try? JSONDecoder().decode(SnippetPlist.self, from: data)
109 | else { continue }
110 |
111 | snippets.append(Snippet(from: snippet))
112 | }
113 |
114 | return snippets
115 | }
116 |
117 | private func saveSnippetDictonaryIntoAppGroup(
118 | _ snippetDictonary: [String: Any],
119 | key: String
120 | ) {
121 | guard let userDefaults = UserDefaults(suiteName: Constants.appGroupName) else { return }
122 |
123 | userDefaults.set(
124 | snippetDictonary,
125 | forKey: key
126 | )
127 | }
128 |
129 | // MARK: - Snippet Manual
130 |
131 | internal func shouldShowSnippetManual() -> Bool {
132 | let wasSnippetsManualReaded = userDefaults.bool(forKey: Constants.snippetManualKey)
133 | return !wasSnippetsManualReaded
134 | }
135 |
136 | internal func markSnippetManualAsReaded() {
137 | userDefaults.set(
138 | true,
139 | forKey: Constants.snippetManualKey
140 | )
141 | }
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/SnippetsLibrary/SnippetsLibrary.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.application-groups
8 |
9 | group.com.cphlowiec.SnippetsLibrary
10 |
11 | com.apple.security.files.user-selected.read-write
12 |
13 | com.apple.security.network.client
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/SnippetsLibrary/SnippetsLibraryDebug.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.application-groups
8 |
9 | group.com.cphlowiec.SnippetsLibrary
10 |
11 | com.apple.security.files.user-selected.read-write
12 |
13 | com.apple.security.network.client
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/CustomCodeTheme.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomCodeTheme.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 13/09/2021.
6 | //
7 |
8 | import SwiftUI
9 | import Sourceful
10 |
11 | struct CustomCodeTheme: SourceCodeTheme {
12 |
13 | private enum Constants {
14 | static let backgroundDarkColor = NSColor(red: 41/255, green: 42/255, blue: 48/255, alpha: 1.0)
15 | static let plainTypeLightColor = NSColor(red: 2/255, green: 2/255, blue: 2/255, alpha: 1.0)
16 | static let plainTypeDarkColor = NSColor(red: 223/255, green: 223/255, blue: 224/255, alpha: 1.0)
17 | static let numberTypeLightColor = NSColor(red: 40/255, green: 41/255, blue: 208/255, alpha: 1.0)
18 | static let numberTypeDarkColor = NSColor(red: 214/255, green: 202/255, blue: 134/255, alpha: 1.0)
19 | static let stringTypeLightColor = NSColor(red: 192/255, green: 62/255, blue: 41/255, alpha: 1.0)
20 | static let stringTypeDarkColor = NSColor(red: 239/255, green: 136/255, blue: 118/255, alpha: 1.0)
21 | static let identifierTypeLightColor = NSColor(red: 121/255, green: 82/255, blue: 178/255, alpha: 1.0)
22 | static let identifierTypeDarkColor = NSColor(red: 171/255, green: 131/255, blue: 228/255, alpha: 1.0)
23 | static let keywordTypeLightColor = NSColor(red: 160/255, green: 69/255, blue: 160/255, alpha: 1.0)
24 | static let keywordTypeDarkColor = NSColor(red: 238/255, green: 130/255, blue: 176/255, alpha: 1.0)
25 | static let commentColor = NSColor(red: 129/255.0, green: 140/255.0, blue: 150/255.0, alpha: 1.0)
26 |
27 | static let font = NSFont.monospacedSystemFont(ofSize: 15, weight: .regular)
28 | static let gutterStyle = GutterStyle(backgroundColor: NSColor.clear, minimumWidth: 0)
29 | }
30 |
31 | // MARK: - Stored Properties
32 |
33 | private var colorScheme: ColorScheme = .dark
34 |
35 | private static var lineNumbersColor: NSColor = .clear
36 |
37 | internal let lineNumbersStyle: LineNumbersStyle? = nil
38 | internal let gutterStyle: GutterStyle = Constants.gutterStyle
39 | internal let font = Constants.font
40 |
41 | // MARK: - Computed Properties
42 |
43 | internal var backgroundColor: NSColor {
44 | return colorScheme == .dark ? Constants.backgroundDarkColor : NSColor.white
45 | }
46 |
47 | // MARK: - Initialization
48 |
49 | init(colorScheme: ColorScheme) {
50 | self.colorScheme = colorScheme
51 | }
52 |
53 | // MARK: - Methods
54 |
55 | func color(for syntaxColorType: SourceCodeTokenType) -> NSColor {
56 | switch syntaxColorType {
57 | case .plain:
58 | return colorScheme == .dark ? Constants.plainTypeDarkColor : Constants.plainTypeLightColor
59 | case .number:
60 | return colorScheme == .dark ? Constants.numberTypeDarkColor : Constants.numberTypeLightColor
61 | case .string:
62 | return colorScheme == .dark ? Constants.stringTypeDarkColor : Constants.stringTypeLightColor
63 | case .identifier:
64 | return colorScheme == .dark ? Constants.identifierTypeDarkColor : Constants.identifierTypeLightColor
65 | case .keyword:
66 | return colorScheme == .dark ? Constants.keywordTypeDarkColor : Constants.keywordTypeLightColor
67 | case .comment:
68 | return Constants.commentColor
69 | case .editorPlaceholder:
70 | return colorScheme == .dark ? .gray : .lightGray
71 | }
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/DisabledCommandGroupButtonType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DisabledCommandGroupButtonType.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 26/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum DisabledCommandGroupButtonType {
11 | case openLibrary
12 | case uploadSnippets
13 | }
14 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/Errors/CrashlyticsError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CrashlyticsError.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 17/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum CrashlyticsErrorType: String {
11 | case unableToCreateSnippet
12 | case unableToUpdateSnippet
13 | case unableToRemoveSnippet
14 | case unableToCreateSnippetFromDroppedFile
15 | }
16 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/Errors/DatabaseError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DatabaseError.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 17/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum DatabaseError: Error {
11 | case unableToRetrieveKeyForChild
12 | case unableToSaveSnippet
13 | case unableToFetchData
14 | case unableToDecodeSnippet
15 | case unableToRemoveSnippet
16 | case unableToUpdateSnippet
17 | }
18 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/Errors/SnippetsParserServiceError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetsParserServiceError.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 13/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum SnippetsParserServiceError: Error {
11 | case unableToFindSnippetsDirectory
12 | case unableToSaveSnippet
13 | case unableToDecodeSnippet
14 | case unableToCreateDirectory
15 | case unableToRemoveSnippet
16 | }
17 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/Errors/UserDefaultsServiceError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDefaultsServiceError.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 02/10/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum UserDefaultsServiceError: Error {
11 | case unableToGetData
12 | }
13 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/FileStatusCardType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileStatusCardType.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 27/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum FileStatusCardType {
11 | case large
12 | case normal
13 | }
14 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/PlistCodingKeys.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PlistCodingKeys.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 13/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum PlistCodingKeys: String, CodingKey {
11 | case id = "IDECodeSnippetIdentifier"
12 | case title = "IDECodeSnippetTitle"
13 | case summary = "IDECodeSnippetSummary"
14 | case completion = "IDECodeSnippetCompletionPrefix"
15 | case availability = "IDECodeSnippetCompletionScopes"
16 | case contents = "IDECodeSnippetContents"
17 | case language = "IDECodeSnippetLanguage"
18 | case platform = "IDECodeSnippetPlatformFamily"
19 | case userSnippet = "IDECodeSnippetUserSnippet"
20 | case version = "IDECodeSnippetVersion"
21 | case author = "IDECodeSnippetAuthor"
22 | case tags = "IDECodeSnippetTags"
23 | }
24 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/SnippetAvailability.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetAvailability.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 13/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum SnippetAvailability: CaseIterable {
11 | case allScopes
12 | case implementation
13 | case expression
14 | case function
15 | case string
16 | case topLevel
17 |
18 | var title: String {
19 | switch self {
20 | case .allScopes: return "All Scopes"
21 | case .implementation: return "Class Implementation"
22 | case .expression: return "Code Expression"
23 | case .function: return "Function or Method"
24 | case .string: return "String or Comment"
25 | case .topLevel: return "Top Level"
26 | }
27 | }
28 |
29 | var string: String {
30 | switch self {
31 | case .allScopes: return "All"
32 | case .implementation: return "ClassImplementation"
33 | case .expression: return "CodeExpression"
34 | case .function: return "CodeBlock"
35 | case .string: return "StringOrComment"
36 | case .topLevel: return "TopLevel"
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/SnippetCategory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetCategory.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 01/10/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum SnippetCategory: String, CaseIterable {
11 | case view
12 | case helper
13 | case service
14 | }
15 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/SnippetDetailsViewType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetDetailsViewType.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 13/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum SnippetDetailsViewType {
11 | case create
12 | case edit
13 | }
14 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/SnippetFileCardViewState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetFileCardViewState.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 13/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum SnippetFileCardViewState {
11 | case preview
12 | case edit
13 | }
14 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/SnippetPlatform.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 13/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum SnippetPlatform: String, CaseIterable {
11 | case all
12 | case iphoneos
13 | case macos
14 | case tvos
15 | case watchos
16 |
17 | var title: String {
18 | switch self {
19 | case .all: return "All platforms"
20 | case .iphoneos: return "iOS"
21 | case .macos: return "macOS"
22 | case .tvos: return "tvOS"
23 | case .watchos: return "watchOS"
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/SnippetType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetType.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 21/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum SnippetType: Int, CaseIterable {
11 | case snippets
12 | case extending
13 |
14 | var title: String {
15 | switch self {
16 | case .snippets: return "Snippets"
17 | case .extending: return "Extensions"
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/SnippetWriteType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetWriteType.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 14/10/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum SnippetWriteType {
11 | case download
12 | case uploadToXcode
13 | }
14 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/SnippetsFetchingType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetsFetchingType.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 12/10/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum SnippetsFetchingType {
11 | case recent
12 | case local
13 | }
14 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/StartViewMenuItemType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StartViewMenuItemType.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 13/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum StartViewMenuItemType {
11 | case create
12 | case importSnippet
13 | case open
14 |
15 | var systemImageName: String {
16 | switch self {
17 | case .create: return "plus.square"
18 | case .importSnippet: return "square.and.arrow.down"
19 | case .open: return "folder"
20 | }
21 | }
22 |
23 | var imageFontSize: CGFloat {
24 | switch self {
25 | case .create: return 30.0
26 | case .importSnippet: return 31.0
27 | case .open: return 25.0
28 | }
29 | }
30 |
31 | var title: String {
32 | switch self {
33 | case .create: return "Create a new snippet file"
34 | case .importSnippet: return "Import an existing code snippet"
35 | case .open: return "Open snippets library"
36 | }
37 | }
38 |
39 | var subtitle: String {
40 | switch self {
41 | case .create: return "Create and upload the own code snippets."
42 | case .importSnippet: return "Import local code snippets into the Snippets Library."
43 | case .open: return "Open and browse an existing Snippets Library."
44 | }
45 | }
46 |
47 | var leadingPadding: CGFloat {
48 | switch self {
49 | case .importSnippet: return 1
50 | case .open: return -1
51 | default: return 0
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/URLType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLType.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 23/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum URLType {
11 | case userGuides
12 | case docs
13 | }
14 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/UploadingStatus.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UploadingStatus.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 26/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum UploadingStatus {
11 | case initializing
12 | case uploading
13 | case done
14 | case error
15 |
16 | var currentDescription: String {
17 | switch self {
18 | case .initializing:
19 | return "Initializing..."
20 | case .uploading:
21 | return "Uploading code snippets into the Xcode..."
22 | default:
23 | return ""
24 | }
25 | }
26 |
27 | internal var title: String {
28 | switch self {
29 | case .uploading,
30 | .initializing:
31 | return "Uploading"
32 | case .done:
33 | return "Upload Successful"
34 | case .error:
35 | return "Upload Failed"
36 | }
37 | }
38 |
39 | internal var message: String {
40 | switch self {
41 | case .done: return "Please relaunch Xcode to see all uploaded code snippets."
42 | case .error: return "Unable to upload code snippets. Please try again later."
43 | default: return ""
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/UserActivityLevel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserActivityLevel.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 17/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum UserActivityLevel: Int {
11 | case low
12 | case medium
13 | case high
14 | }
15 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Enums/UserActivityLogType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserActivityLogType.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 17/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | enum UserActivityLogType {
11 | case userSavedSnippet(_ snippetId: String)
12 | case userUpdatedSnippet(_ snippetId: String)
13 | case userRemovedSnippet(_ snippetId: String)
14 |
15 | var title: String {
16 | switch self {
17 | case let .userSavedSnippet(snippetId):
18 | return "User saved snippet with id: \(snippetId)"
19 | case let .userUpdatedSnippet(snippetId):
20 | return "User updated snippet with id: \(snippetId)"
21 | case let .userRemovedSnippet(snippetId):
22 | return "User removed snippet with id: \(snippetId)"
23 | }
24 | }
25 |
26 | var level: UserActivityLevel {
27 | switch self {
28 | case .userSavedSnippet: return .low
29 | case .userUpdatedSnippet: return .medium
30 | case .userRemovedSnippet: return .high
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Layout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Layout.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 07/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct Layout {
11 | static let screenWidth = NSScreen.main?.visibleFrame.width ?? 0
12 | static let screenHeight = NSScreen.main?.visibleFrame.height ?? 0
13 |
14 | static let defaultWindowSize = CGSize(width: 850, height: 460)
15 |
16 | static let zeroPadding: CGFloat = 0.0
17 | static let smallPadding: CGFloat = 8.0
18 | static let standardPadding: CGFloat = 16.0
19 | static let mediumPadding: CGFloat = 24.0
20 | static let largePadding: CGFloat = 32.0
21 |
22 | static let fullOpacity: Double = 1.0
23 | static let largeOpacity: Double = 0.75
24 | static let mediumOpacity: Double = 0.5
25 | static let tinyOpacity: Double = 0.25
26 | static let minimalOpacity: Double = 0.1
27 | static let zeroOpacity: Double = 0.0
28 | }
29 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/Mocks.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Mocks.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 08/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | let mockedSnippets = [
11 | Snippet(
12 | id: UUID().uuidString,
13 | title: "AnyPublisher+Method",
14 | summary: "Easly create AnyPublisher method",
15 | content:
16 | """
17 | func <#method name#>() -> AnyPublisher {
18 | return Future { promise in
19 | do {
20 | try <#throwing action#>
21 | promise(.success(()))
22 | } catch {
23 | promise(.failure(<#Error#>))
24 | }
25 | }
26 | .eraseToAnyPublisher()
27 | }
28 | """,
29 | author: "Christopher Lowiec",
30 | completion: "anypubmeth",
31 | platform: .iphoneos,
32 | availability: .topLevel
33 | )
34 | ]
35 |
36 | let skeletonSnippets = [
37 | Snippet(mockedForSkeleton: true),
38 | Snippet(mockedForSkeleton: true),
39 | Snippet(mockedForSkeleton: true),
40 | Snippet(mockedForSkeleton: true),
41 | Snippet(mockedForSkeleton: true),
42 | Snippet(mockedForSkeleton: true)
43 | ]
44 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/NetworkObserver.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkObserver.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 25/09/2021.
6 | //
7 |
8 | import Foundation
9 | import Combine
10 |
11 | final class NetworkObserver: ObservableObject {
12 |
13 | // MARK: - Stored Porperties
14 |
15 | @Published var isConnected = true
16 |
17 | private let networkService: NetworkService
18 |
19 | private var cancellables = Set()
20 |
21 | // MARK: - Initialization
22 |
23 | init(networkService: NetworkService = DIContainer.networkServcie) {
24 | self.networkService = networkService
25 |
26 | observeNetworkState()
27 | }
28 |
29 | // MARK: - Methods
30 |
31 | private func observeNetworkState() {
32 | networkService.isConnected
33 | .receive(on: DispatchQueue.main)
34 | .sink { [weak self] in
35 | self?.isConnected = $0
36 | }
37 | .store(in: &cancellables)
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/System.swift:
--------------------------------------------------------------------------------
1 | //
2 | // System.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 17/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct System {
11 |
12 | static func getHardwareUUID() -> String {
13 | let matchingDict = IOServiceMatching("IOPlatformExpertDevice")
14 | let platformExpert = IOServiceGetMatchingService(
15 | kIOMasterPortDefault,
16 | matchingDict
17 | )
18 |
19 | defer { IOObjectRelease(platformExpert) }
20 |
21 | guard platformExpert != .zero else { return "" }
22 |
23 | return IORegistryEntryCreateCFProperty(
24 | platformExpert, kIOPlatformUUIDKey as CFString,
25 | kCFAllocatorDefault,
26 | .zero
27 | ).takeRetainedValue() as? String ?? ""
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Supporting Files/TextFieldStyleModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextFieldStyleModifier.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 11/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct TextFieldStyleModifier: ViewModifier {
11 |
12 | // MARK: - Stored Properties
13 |
14 | let type: SnippetDetailsViewType
15 |
16 | // MARK: - Views
17 |
18 | @ViewBuilder
19 | func body(content: Content) -> some View {
20 | if type == .create {
21 | content
22 | .textFieldStyle(DefaultTextFieldStyle())
23 | } else {
24 | content
25 | .textFieldStyle(PlainTextFieldStyle())
26 | }
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Views/DisabledCommandGroupButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DisabledCommandGroupButton.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 23/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct DisabledCommandGroupButton: View {
11 |
12 | // MARK: - Stored Properties
13 |
14 | let text: String
15 | @Binding private(set) var shouldBeDisabled: Bool
16 | let type: DisabledCommandGroupButtonType
17 | let onTap: () -> Void
18 |
19 | // MARK: - Views
20 |
21 | var body: some View {
22 | Button(
23 | text,
24 | action: onTap
25 | )
26 | .disabled(type == .openLibrary ? shouldBeDisabled : !shouldBeDisabled)
27 | }
28 |
29 | }
30 |
31 | struct DisabledCommandGroupButton_Previews: PreviewProvider {
32 | static var previews: some View {
33 | DisabledCommandGroupButton(text: "", shouldBeDisabled: .constant(false), type: .openLibrary) {}
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Views/EmptySnippetsListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EmptySnippetsListView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 25/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct EmptySnippetsListView: View {
11 |
12 | // MARK: - Stored Properties
13 |
14 | @Binding private(set) var isListEmpty: Bool
15 |
16 | // MARK: - Views
17 |
18 | var body: some View {
19 | VStack(spacing: Layout.smallPadding) {
20 | Image(systemName: "cloud")
21 | .font(.system(size: 24, weight: .light))
22 | .foregroundColor(
23 | Color.primary
24 | .opacity(Layout.mediumOpacity)
25 | )
26 |
27 | Text("No code snippets found \nin a database")
28 | .foregroundColor(
29 | Color.primary
30 | .opacity(Layout.mediumOpacity)
31 | )
32 | .multilineTextAlignment(.center)
33 | }
34 | .offset(y: -Layout.largePadding)
35 | .makeVisible(
36 | isListEmpty,
37 | removed: true
38 | )
39 | }
40 |
41 | }
42 |
43 | struct EmptySnippetsListView_Previews: PreviewProvider {
44 | static var previews: some View {
45 | EmptySnippetsListView(isListEmpty: .constant(true))
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Views/FileStatusCard/FileStatusCard.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileStatusCard.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 27/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct FileStatusCard: View {
11 |
12 | private enum Constants {
13 | static let cornerRadius: CGFloat = 10.0
14 | static let strokeWidth: CGFloat = 0.2
15 | static let mediumOpacity = 0.5
16 | static let tinyOpacity = 0.2
17 | static let imageLeadingPadding: CGFloat = 10.0
18 | static let imageRegularHeight: CGFloat = 38.0
19 | static let imageSmallHeight: CGFloat = 30.0
20 | static let lineLimit = 1
21 | static let smallPadding: CGFloat = 6.0
22 | static let shadowRadius: CGFloat = 4.0
23 | static let shadowPosition = CGPoint(x: 0, y: 2)
24 | static let windowSize = CGSize(width: 230, height: 65)
25 | static let smallWindowWidth: CGFloat = 110
26 | }
27 |
28 | // MARK: - Stored Properties
29 |
30 | @ObservedObject internal var viewModel: FileStatusCardViewModel
31 |
32 | // MARK: - Views
33 |
34 | var body: some View {
35 | Button {
36 | viewModel.sendOpenSnippetNotification()
37 | } label: {
38 | ZStack {
39 | VisualEffectView(
40 | material: .menu,
41 | blendingMode: .behindWindow
42 | )
43 |
44 | Rectangle()
45 | .fill(Color.clear)
46 | .overlay(
47 | RoundedRectangle(cornerRadius: Constants.cornerRadius)
48 | .stroke(style: StrokeStyle(lineWidth: Constants.strokeWidth))
49 | .foregroundColor(.gray)
50 | .cornerRadius(Constants.cornerRadius)
51 | )
52 |
53 | HStack {
54 | Image("icSnippetFileWhite")
55 | .resizable()
56 | .aspectRatio(contentMode: .fit)
57 | .frame(height: Constants.imageRegularHeight)
58 | .padding(.leading, Constants.imageLeadingPadding)
59 |
60 | VStack(
61 | alignment: .leading,
62 | spacing: .zero
63 | ) {
64 | Text(viewModel.snippet.title)
65 | .font(.system(size: 13))
66 | .lineLimit(Constants.lineLimit)
67 | .padding(.bottom, Constants.smallPadding / 4)
68 |
69 | Text(viewModel.snippet.summary)
70 | .font(.system(size: 11))
71 | .opacity(Layout.mediumOpacity)
72 | .lineLimit(Constants.lineLimit)
73 | .padding(.bottom, Constants.smallPadding)
74 | }
75 | .padding(.leading, Constants.smallPadding)
76 |
77 | Spacer()
78 | }
79 | .makeVisible(
80 | viewModel.type == .large,
81 | removed: true
82 | )
83 |
84 | VStack(
85 | alignment: .center,
86 | spacing: .zero
87 | ) {
88 | Image("icSnippetFileWhite")
89 | .resizable()
90 | .aspectRatio(contentMode: .fit)
91 | .frame(height: Constants.imageSmallHeight)
92 |
93 | Text(viewModel.snippet.title)
94 | .font(.system(size: 11))
95 | .lineLimit(Constants.lineLimit)
96 | .padding(Constants.smallPadding)
97 | }
98 | .makeVisible(
99 | viewModel.type == .normal,
100 | removed: true
101 | )
102 | }
103 | }
104 | .buttonStyle(PlainButtonStyle())
105 | .frame(
106 | width: viewModel.type == .large ? Constants.windowSize.width : Constants.smallWindowWidth,
107 | height: Constants.windowSize.height,
108 | alignment: .leading
109 | )
110 | .cornerRadius(Constants.cornerRadius)
111 | .shadow(
112 | color: Color.black.opacity(Constants.tinyOpacity),
113 | radius: Constants.shadowRadius,
114 | x: Constants.shadowPosition.x,
115 | y: Constants.shadowPosition.y
116 | )
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Views/FileStatusCard/FileStatusCardViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileStatusCardViewModel.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 27/09/2021.
6 | //
7 |
8 | import Foundation
9 |
10 | final class FileStatusCardViewModel: ObservableObject {
11 |
12 | // MARK: - Stored Properties
13 |
14 | private(set) var snippet: Snippet
15 | private(set) var type: FileStatusCardType
16 |
17 | // MARK: - Initialization
18 |
19 | init(
20 | snippet: Snippet?,
21 | type: FileStatusCardType = .normal
22 | ) {
23 | self.snippet = snippet ?? Snippet()
24 | self.type = type
25 | }
26 |
27 | // MARK: - Methods
28 |
29 | internal func sendOpenSnippetNotification() {
30 | NotificationCenter.default.post(
31 | name: NSNotification.statusBarSnippetTapped,
32 | object: nil,
33 | userInfo: ["snippetId": snippet.id]
34 | )
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Views/RecentSnippetCardView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RecentSnippetCardView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 07/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct RecentSnippetCardView: View {
11 |
12 | private enum Constants {
13 | static let imageHeight: CGFloat = 38.0
14 | static let spacerHeight: CGFloat = 16.0
15 | static let cornerRadius: CGFloat = 6.0
16 | static let lineLimit = 1
17 | static let tapGestureCount = 2
18 | }
19 |
20 | // MARK: - Stored Properties
21 |
22 | let snippet: Snippet
23 | let tag: Int
24 |
25 | @Binding internal var selectedFileIndex: Int?
26 |
27 | private(set) var onTap: () -> Void
28 |
29 | // MARK: - Views
30 |
31 | var body: some View {
32 | Button {
33 | selectedFileIndex = tag
34 | } label: {
35 | HStack(spacing: .zero) {
36 | Image("icSnippetFileWhite")
37 | .resizable()
38 | .aspectRatio(contentMode: .fit)
39 | .frame(height: Constants.imageHeight)
40 |
41 | VStack(
42 | alignment: .leading,
43 | spacing: Layout.smallPadding / 4
44 | ) {
45 | Text(snippet.title)
46 | .font(.system(size: 13))
47 | .foregroundColor(Color.primary)
48 | .lineLimit(Constants.lineLimit)
49 |
50 | Text(snippet.summary)
51 | .font(.system(size: 11))
52 | .foregroundColor(
53 | Color.primary
54 | .opacity(Layout.mediumOpacity)
55 | )
56 | .lineLimit(Constants.lineLimit)
57 | }
58 | .padding(.leading, Layout.smallPadding)
59 |
60 | Spacer()
61 | }
62 | }
63 | .buttonStyle(PlainButtonStyle())
64 | .tag(tag)
65 | .padding(.horizontal, Layout.smallPadding)
66 | .padding(.vertical, Layout.smallPadding / 2)
67 | .background(selectedFileIndex == tag ? Color("accent") : nil)
68 | .cornerRadius(Constants.cornerRadius)
69 | .simultaneousGesture(
70 | TapGesture(count: Constants.tapGestureCount)
71 | .onEnded(onTap)
72 | )
73 | }
74 |
75 | }
76 |
77 | struct RecentSnippetCardView_Previews: PreviewProvider {
78 | static var previews: some View {
79 | RecentSnippetCardView(snippet: Snippet(), tag: 0, selectedFileIndex: .constant(nil)) {}
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Views/SearchBar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SearchBar.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 08/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SearchBar: View {
11 |
12 | private enum Constants {
13 | static let animationDuration: Double = 0.2
14 | }
15 |
16 | // MARK: - Stored Properties
17 |
18 | @State private var searchedText = ""
19 | @State private var isEditing = false
20 |
21 | private(set) var onChange: (String) -> Void
22 |
23 | // MARK: - Views
24 |
25 | var body: some View {
26 | HStack {
27 | Image(systemName: "magnifyingglass")
28 | .font(.system(size: 13.0))
29 | .foregroundColor(
30 | Color.primary
31 | .opacity(Layout.mediumOpacity)
32 | )
33 |
34 | TextField(
35 | "Search for snippets...",
36 | text: $searchedText
37 | ) { isEditing in
38 | onChange(searchedText)
39 | withAnimation {
40 | self.isEditing = isEditing
41 | }
42 | } onCommit: {
43 | onChange(searchedText)
44 | isEditing = false
45 | }
46 | .textFieldStyle(PlainTextFieldStyle())
47 | .padding(.vertical, Layout.smallPadding)
48 | .onChange(of: searchedText) { text in
49 | onChange(text)
50 | }
51 |
52 | Button {
53 | searchedText = ""
54 | isEditing = false
55 | } label: {
56 | Image(systemName: "multiply.circle.fill")
57 | .foregroundColor(Color.gray)
58 | .padding(.leading, Layout.smallPadding)
59 | }
60 | .buttonStyle(PlainButtonStyle())
61 | .makeVisible(isEditing)
62 | .animation(.easeIn(duration: Constants.animationDuration))
63 | }
64 | }
65 |
66 | }
67 |
68 | struct SearchBar_Previews: PreviewProvider {
69 | static var previews: some View {
70 | SearchBar() { _ in }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Views/SnippetCreationManualView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetCreationManualView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 03/11/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SnippetCreationManualView: View {
11 |
12 | private enum Constants {
13 | static let imageHeight: CGFloat = 38.0
14 | static let lineSpacing: CGFloat = 5.0
15 | static let viewSize = CGSize(width: 350.0, height: 400.0)
16 | static let cornerRadius: CGFloat = 12.0
17 | }
18 |
19 | // MARK: - Views
20 |
21 | var body: some View {
22 | VStack {
23 | Image("icSnippetFileWhite")
24 | .resizable()
25 | .aspectRatio(contentMode: .fit)
26 | .frame(height: Constants.imageHeight)
27 |
28 | Text("How to create a snippet?")
29 | .font(.system(size: 17, weight: .bold))
30 | .padding(.bottom, Layout.smallPadding)
31 |
32 | Text("To maintain consistency and unify all code snippets in the Snippets Library, follow this two main rules:")
33 | .font(.system(size: 11, weight: .medium))
34 | .padding(.bottom)
35 | .opacity(Layout.mediumOpacity)
36 | .multilineTextAlignment(.center)
37 |
38 | Text(
39 | """
40 | ✅ Make sure, that your snippet is helpful, complete and contains only the necessary lines required for use.
41 |
42 | ✅ Enter the title, summary, supported platforms and scopes and remember, that the completion should be self-explaining.
43 | """
44 | )
45 | .lineSpacing(Constants.lineSpacing)
46 | .font(.system(size: 13))
47 | .multilineTextAlignment(.leading)
48 | }
49 | .padding(Layout.largePadding)
50 | .frame(
51 | width: Constants.viewSize.width,
52 | height: Constants.viewSize.height
53 | )
54 | .background(Color("defaultBackground"))
55 | .cornerRadius(Constants.cornerRadius)
56 | }
57 |
58 | }
59 |
60 | struct SnippetCreationManualView_Previews: PreviewProvider {
61 | static var previews: some View {
62 | SnippetCreationManualView()
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Views/SnippetDropCellView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetDropCellView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 12/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SnippetDropCellView: View {
11 |
12 | // MARK: - Stored Properties
13 |
14 | let snippet: Snippet?
15 |
16 | // MARK: - Views
17 |
18 | var body: some View {
19 | VStack(spacing: .zero) {
20 | Image(systemName: "doc.text.fill")
21 | .font(.system(size: 36.0))
22 | .padding(.bottom, Layout.smallPadding)
23 |
24 | Text("Drop the snippet file here")
25 | .font(.system(size: 15.0))
26 | }
27 | }
28 |
29 | }
30 |
31 | struct SnippetDropCellView_Previews: PreviewProvider {
32 | static var previews: some View {
33 | SnippetDropCellView(snippet: Snippet())
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Views/SnippetFileCard/SnippetFileCardView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetFileCardView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 07/09/2021.
6 | //
7 |
8 | import SwiftUI
9 | import Sourceful
10 |
11 | struct SnippetFileCardView: View {
12 |
13 | private enum Constants {
14 | static let cornerRadius: CGFloat = 4.0
15 | static let codeEditorHorizontalPadding: CGFloat = 1.0
16 | static let codeEditorVerticlaPadding: CGFloat = 0.0
17 | static let codeEditorHeight: CGFloat = Layout.defaultWindowSize.height - (Layout.largePadding * 4)
18 | static let strokeWidth: CGFloat = 1.0
19 | static let minimalOpacity = 0.0001
20 | static let bottomBarHeight: CGFloat = 20.0
21 | static let bottomBarCornerRadius: CGFloat = 2.0
22 | }
23 |
24 | // MARK: - Stored Properties
25 |
26 | @Environment(\.colorScheme) var colorScheme
27 |
28 | @ObservedObject private(set) var viewModel: SnippetFileCardViewModel
29 |
30 | // MARK: - Views
31 |
32 | var body: some View {
33 | VStack(
34 | alignment: .leading,
35 | spacing: .zero
36 | ) {
37 | HStack {
38 | Text(viewModel.titleText.isEmpty ? viewModel.snippet.title : viewModel.titleText)
39 | .font(.headline)
40 |
41 | Spacer()
42 |
43 | Group {
44 | Button {
45 | viewModel.downloadSnippet()
46 | } label: {
47 | Image(systemName: "arrow.down.circle")
48 | .font(.system(size: 15, weight: .light))
49 | .opacity(Layout.largeOpacity)
50 | }
51 | .buttonStyle(PlainButtonStyle())
52 | .help("Download selected snippet")
53 |
54 | Button {
55 | viewModel.openSnippetDetails()
56 | } label: {
57 | Image(systemName: "info.circle")
58 | .font(.system(size: 15, weight: .light))
59 | .opacity(Layout.largeOpacity)
60 | }
61 | .buttonStyle(PlainButtonStyle())
62 | .help("Show snippet details")
63 | }
64 | }
65 |
66 | HStack {
67 | ForEach(
68 | viewModel.snippet.tags ?? [],
69 | id: \.self
70 | ) {
71 | Text($0)
72 | .font(.footnote)
73 | .foregroundColor(Color.white)
74 | .padding(.horizontal, Layout.smallPadding / 2)
75 | .padding(.vertical, Layout.smallPadding / 4)
76 | .background(
77 | RoundedRectangle(cornerRadius: Constants.cornerRadius)
78 | .foregroundColor(
79 | Color.accentColor
80 | .opacity(Layout.mediumOpacity)
81 | )
82 | )
83 | }
84 | }
85 | .padding(.vertical, Layout.smallPadding)
86 |
87 | ZStack {
88 | SourceCodeTextEditor(
89 | text: .constant(viewModel.snippet.content),
90 | customization: SourceCodeTextEditor.Customization(
91 | didChangeText: { _ in },
92 | insertionPointColor: { colorScheme == .dark ? .white : .black },
93 | lexerForSource: { _ in SwiftLexer() },
94 | textViewDidBeginEditing: { _ in },
95 | theme: { CustomCodeTheme(colorScheme: colorScheme) }
96 | ),
97 | shouldBecomeFirstResponder: false
98 | )
99 | .frame(
100 | minHeight: Constants.codeEditorHeight,
101 | alignment: .leading
102 | )
103 | .padding(.horizontal, Constants.codeEditorHorizontalPadding)
104 | .padding(.vertical, Constants.codeEditorVerticlaPadding)
105 | .cornerRadius(Constants.cornerRadius)
106 | .background(
107 | RoundedRectangle(cornerRadius: Constants.cornerRadius)
108 | .stroke(style: StrokeStyle(lineWidth: Constants.strokeWidth))
109 | .foregroundColor(Color.black)
110 | )
111 |
112 | RoundedRectangle(cornerRadius: Constants.cornerRadius)
113 | .foregroundColor(
114 | Color.black
115 | .opacity(Constants.minimalOpacity)
116 | )
117 | .makeVisible(viewModel.state == .preview)
118 | }
119 |
120 | HStack {
121 | Text("SwiftUI")
122 | .font(.footnote)
123 | .opacity(Layout.mediumOpacity)
124 | .padding(.leading, Layout.smallPadding)
125 |
126 | Divider()
127 |
128 | Spacer()
129 |
130 | Divider()
131 |
132 | Text("\(viewModel.linesCount()) lines")
133 | .font(.footnote)
134 | .opacity(Layout.mediumOpacity)
135 | .padding(.trailing, Layout.smallPadding)
136 | }
137 | .frame(height: Constants.bottomBarHeight)
138 | .background(
139 | RoundedRectangle(cornerRadius: Constants.bottomBarCornerRadius)
140 | .foregroundColor(Color("skeletonLight"))
141 | )
142 | .padding(.vertical, Layout.smallPadding)
143 | }
144 | }
145 |
146 | }
147 |
148 | struct SnippetFileCardView_Previews: PreviewProvider {
149 | static var previews: some View {
150 | SnippetFileCardView(viewModel: SnippetFileCardViewModel(snippet: Snippet(), state: .preview, activeSheet: .constant(nil), appAlert: .constant(nil)))
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Views/SnippetFileCard/SnippetFileCardViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetFileCardViewModel.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 07/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | final class SnippetFileCardViewModel: ObservableObject {
11 |
12 | // MARK: - Stored Properties
13 |
14 | private(set) var snippet: Snippet
15 | private(set) var state: SnippetFileCardViewState
16 |
17 | @Published internal var titleText = ""
18 | @Published internal var contentText = ""
19 | @Binding internal var appAlert: AppAlert?
20 | @Binding internal var activeSheet: AppSheet?
21 |
22 | private let snippetParserService: SnippetsParserService
23 |
24 | private(set) var onDelete: (() -> Void)? = nil
25 |
26 | // MARK: - Initialization
27 |
28 | init(
29 | snippet: Snippet?,
30 | state: SnippetFileCardViewState,
31 | activeSheet: Binding,
32 | appAlert: Binding,
33 | snippetParserService: SnippetsParserService = DIContainer.snippetsParserService,
34 | onDelete: (() -> Void)? = nil
35 | ) {
36 | self.snippet = snippet ?? Snippet()
37 | self.state = state
38 | _activeSheet = activeSheet
39 | _appAlert = appAlert
40 | self.snippetParserService = snippetParserService
41 | self.onDelete = onDelete
42 |
43 | setup()
44 | }
45 |
46 | // MARK: - Methods
47 |
48 | internal func downloadSnippet() {
49 | snippetParserService.writeToPath(
50 | type: .download,
51 | snippets: [snippet]
52 | ) {
53 | self.appAlert = .snippetDownload
54 | }
55 | }
56 |
57 | internal func linesCount() -> Int {
58 | return contentText.numberOfLines()
59 | }
60 |
61 | internal func openSnippetDetails() {
62 | activeSheet = .snippetDetails(snippet, .edit)
63 | }
64 |
65 | private func setup() {
66 | titleText = snippet.title
67 | contentText = snippet.content
68 | snippet.tags = snippet.tags?.isEmpty == true ? ["SwiftUI"] : []
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Views/SnippetListItem/SnippetListItemView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetListItemView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 08/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SnippetListItemView: View {
11 |
12 | private enum Constants {
13 | static let imageHeight: CGFloat = 38.0
14 | static let lineLimit = 1
15 | static let spacing = Layout.smallPadding / 2
16 | static let smallSpacing = Layout.smallPadding / 4
17 | static let cornerRadius: CGFloat = 8.0
18 | }
19 |
20 | // MARK: - Stored Properties
21 |
22 | let snippet: Snippet
23 |
24 | // MARK: - Computed Properties
25 |
26 | private var shouldAnimate: Bool {
27 | snippet.content.isEmpty
28 | }
29 |
30 | // MARK: - Views
31 |
32 | var body: some View {
33 | HStack(spacing: .zero) {
34 | Image("icSnippetFileWhite")
35 | .resizable()
36 | .aspectRatio(contentMode: .fit)
37 | .frame(height: Constants.imageHeight)
38 | .makeSkeletonable(animating: shouldAnimate)
39 |
40 | VStack(
41 | alignment: .leading,
42 | spacing: Constants.smallSpacing
43 | ) {
44 | Text("\(snippet.title)")
45 | .font(.system(size: 13))
46 | .foregroundColor(Color.primary)
47 | .lineLimit(Constants.lineLimit)
48 | .makeSkeletonable(animating: shouldAnimate)
49 |
50 | Text(snippet.summary)
51 | .font(.system(size: 11))
52 | .foregroundColor(
53 | Color.primary
54 | .opacity(Layout.mediumOpacity)
55 | )
56 | .lineLimit(Constants.lineLimit)
57 | .makeSkeletonable(animating: shouldAnimate)
58 | }
59 | .padding(.leading, Layout.smallPadding)
60 |
61 | Spacer()
62 | }
63 | .padding(
64 | .horizontal,
65 | shouldAnimate ? Constants.spacing : .zero
66 | )
67 | .padding(
68 | .vertical,
69 | shouldAnimate ? Constants.smallSpacing : .zero
70 | )
71 | .background(
72 | RoundedRectangle(cornerRadius: Constants.cornerRadius)
73 | .makeSkeletonable(
74 | animating: shouldAnimate,
75 | isBottomView: true
76 | )
77 | .makeVisible(
78 | shouldAnimate,
79 | removed: true
80 | )
81 | )
82 | }
83 |
84 | }
85 |
86 | struct SnippetListItemView_Previews: PreviewProvider {
87 | static var previews: some View {
88 | SnippetListItemView(snippet: Snippet())
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Views/ToastView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ToastView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 25/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | enum ToastViewState {
11 | case success
12 | case failure
13 | case none
14 |
15 | var color: Color {
16 | switch self {
17 | case .success: return Color.green
18 | case .failure: return Color.red
19 | case .none: return Color.primary
20 | }
21 | }
22 | }
23 |
24 | struct ToastView: View {
25 |
26 | private enum Constans {
27 | static let spacing: CGFloat = 16.0
28 | static let imageSize: CGFloat = 22.0
29 | static let lineLimit = 1
30 | static let height: CGFloat = 44.0
31 | static let shadowOpacity = 0.08
32 | static let shadowRadius: CGFloat = 8.0
33 | static let shadowOffset: CGFloat = 4.0
34 | }
35 |
36 | // MARK: - Stored Properties
37 |
38 | @Environment(\.colorScheme) var colorScheme
39 |
40 | let imageName: String?
41 | let title: String
42 | let subtitle: String?
43 | let state: ToastViewState?
44 |
45 | // MARK: - Views
46 |
47 | var body: some View {
48 | HStack(spacing: Constans.spacing) {
49 | Image(systemName: imageName ?? "")
50 | .renderingMode(.template)
51 | .resizable()
52 | .scaledToFit()
53 | .frame(
54 | width: Constans.imageSize,
55 | height: Constans.imageSize
56 | )
57 | .foregroundColor(state?.color)
58 | .makeVisible(
59 | imageName != nil,
60 | removed: true
61 | )
62 |
63 | VStack {
64 | Text(title)
65 | .lineLimit(Constans.lineLimit)
66 | .font(.headline)
67 | .foregroundColor(Color.primary)
68 |
69 | Text(subtitle ?? "")
70 | .lineLimit(Constans.lineLimit)
71 | .font(.subheadline)
72 | .foregroundColor(Color.secondary)
73 | .makeVisible(
74 | subtitle != nil,
75 | removed: true
76 | )
77 | }
78 | .padding(imageName == nil ? .horizontal : .trailing)
79 | }
80 | .padding(.horizontal)
81 | .frame(height: Constans.height)
82 | .background(colorScheme == .dark ? Color("darkGrey") : Color.white)
83 | .cornerRadius(Constans.height / 2)
84 | .shadow(
85 | color: Color.black.opacity(Constans.shadowOpacity),
86 | radius: Constans.shadowRadius,
87 | x: .zero,
88 | y: Constans.shadowOffset
89 | )
90 | }
91 |
92 | }
93 |
94 | struct ToastView_Previews: PreviewProvider {
95 | static var previews: some View {
96 | ToastView(imageName: nil, title: "", subtitle: nil, state: ToastViewState.none)
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/SnippetsLibrary/Views/VisualEffectView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VisualEffectView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 07/09/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct VisualEffectView: NSViewRepresentable {
11 |
12 | // MARK: - Stored Properties
13 |
14 | let material: NSVisualEffectView.Material
15 | let blendingMode: NSVisualEffectView.BlendingMode
16 |
17 | // MARK: - Methods
18 |
19 | internal func makeNSView(context: Context) -> NSVisualEffectView {
20 | let visualEffectView = NSVisualEffectView()
21 | visualEffectView.material = material
22 | visualEffectView.blendingMode = blendingMode
23 | visualEffectView.state = NSVisualEffectView.State.active
24 | return visualEffectView
25 | }
26 |
27 | internal func updateNSView(
28 | _ visualEffectView: NSVisualEffectView,
29 | context: Context
30 | ) {
31 | visualEffectView.material = material
32 | visualEffectView.blendingMode = blendingMode
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/SnippetsLibraryUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/SnippetsLibraryUITests/SnippetsLibraryUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetsLibraryUITests.swift
3 | // SnippetsLibraryUITests
4 | //
5 | // Created by Krzysztof Łowiec on 19/09/2021.
6 | //
7 |
8 | import XCTest
9 |
10 | class SnippetsLibraryUITests: XCTestCase {
11 |
12 | override func setUpWithError() throws {
13 | // Put setup code here. This method is called before the invocation of each test method in the class.
14 |
15 | // In UI tests it is usually best to stop immediately when a failure occurs.
16 | continueAfterFailure = false
17 |
18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
19 | }
20 |
21 | override func tearDownWithError() throws {
22 | // Put teardown code here. This method is called after the invocation of each test method in the class.
23 | }
24 |
25 | func testExample() throws {
26 | // UI tests must launch the application that they test.
27 | let app = XCUIApplication()
28 | app.launch()
29 |
30 | // Use recording to get started writing UI tests.
31 | // Use XCTAssert and related functions to verify your tests produce the correct results.
32 | }
33 |
34 | func testLaunchPerformance() throws {
35 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
36 | // This measures how long it takes to launch your application.
37 | measure(metrics: [XCTApplicationLaunchMetric()]) {
38 | XCUIApplication().launch()
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/SnippetsLibraryUnitTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/SnippetsLibraryUnitTests/SnippetsLibraryUnitTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetsLibraryUnitTests.swift
3 | // SnippetsLibraryUnitTests
4 | //
5 | // Created by Krzysztof Łowiec on 19/09/2021.
6 | //
7 |
8 | import XCTest
9 |
10 | class SnippetsLibraryUnitTests: XCTestCase {
11 |
12 | override func setUpWithError() throws {
13 | // Put setup code here. This method is called before the invocation of each test method in the class.
14 | }
15 |
16 | override func tearDownWithError() throws {
17 | // Put teardown code here. This method is called after the invocation of each test method in the class.
18 | }
19 |
20 | func testExample() throws {
21 | // This is an example of a functional test case.
22 | // Use XCTAssert and related functions to verify your tests produce the correct results.
23 | }
24 |
25 | func testPerformanceExample() throws {
26 | // This is an example of a performance test case.
27 | measure {
28 | // Put the code you want to measure the time of here.
29 | }
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/SnippetsLibraryWidget/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/SnippetsLibraryWidget/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "32x32"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "1x",
26 | "size" : "128x128"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "2x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "1x",
36 | "size" : "256x256"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "2x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "1x",
46 | "size" : "512x512"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "2x",
51 | "size" : "512x512"
52 | }
53 | ],
54 | "info" : {
55 | "author" : "xcode",
56 | "version" : 1
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/SnippetsLibraryWidget/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SnippetsLibraryWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/SnippetsLibraryWidget/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSExtension
6 |
7 | NSExtensionPointIdentifier
8 | com.apple.widgetkit-extension
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/SnippetsLibraryWidget/Models/WidgetEntry.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WidgetEntry.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 03/10/2021.
6 | //
7 |
8 | import WidgetKit
9 |
10 | struct WidgetEntry: TimelineEntry {
11 |
12 | var date: Date = Date()
13 | let snippet: Snippet
14 | let snippets: [Snippet]
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/SnippetsLibraryWidget/Provider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Provider.swift
3 | // SnippetsLibraryWidgetExtension
4 | //
5 | // Created by Krzysztof Łowiec on 03/10/2021.
6 | //
7 |
8 | import WidgetKit
9 | import SwiftUI
10 |
11 | struct Provider: TimelineProvider {
12 |
13 | private enum Constants {
14 | static let refreshInterval = 15
15 | }
16 |
17 | // MARK: - Computed Properties
18 |
19 | private var entries: [Entry] {
20 | var widgetEntries: [WidgetEntry] = []
21 | let userDefaultsService: UserDefaultsService = UserDefaultsServiceImpl()
22 | let snippets = userDefaultsService.fetchRecentSnippetsFromAppGroup()
23 |
24 | for snippet in snippets {
25 | widgetEntries.append(
26 | WidgetEntry(
27 | snippet: snippet,
28 | snippets: snippets
29 | )
30 | )
31 | }
32 |
33 | return widgetEntries
34 | }
35 |
36 | private var defaultWidgetEntry: WidgetEntry {
37 | WidgetEntry(
38 | snippet: entries.first?.snippet ?? Snippet(mockedForSkeleton: true),
39 | snippets: entries.first?.snippets ?? [
40 | Snippet(mockedForSkeleton: true),
41 | Snippet(mockedForSkeleton: true),
42 | Snippet(mockedForSkeleton: true)
43 | ]
44 | )
45 | }
46 |
47 | // MARK: - Methods
48 |
49 | func getSnapshot(
50 | in context: Context,
51 | completion: @escaping (WidgetEntry) -> Void
52 | ) {
53 | completion(defaultWidgetEntry)
54 | }
55 |
56 | func getTimeline(
57 | in context: Context,
58 | completion: @escaping (Timeline) -> Void
59 | ) {
60 | var entries = entries
61 |
62 | let currentDate = Date()
63 | for index in .zero ..< entries.count {
64 | entries[index].date = Calendar.current.date(
65 | byAdding: .second,
66 | value: index * Constants.refreshInterval,
67 | to: currentDate
68 | )!
69 | }
70 |
71 | let timeline = Timeline(
72 | entries: entries,
73 | policy: .atEnd
74 | )
75 | completion(timeline)
76 | }
77 |
78 | func placeholder(in context: Context) -> WidgetEntry {
79 | defaultWidgetEntry
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/SnippetsLibraryWidget/SnippetsLibraryWidget.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.application-groups
8 |
9 | group.com.cphlowiec.SnippetsLibrary
10 |
11 | com.apple.security.network.client
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/SnippetsLibraryWidget/SnippetsLibraryWidget.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetsLibraryWidget.swift
3 | // SnippetsLibraryWidget
4 | //
5 | // Created by Krzysztof Łowiec on 02/10/2021.
6 | //
7 |
8 | import WidgetKit
9 | import SwiftUI
10 |
11 | @main
12 | struct SnippetsLibraryWidget: Widget {
13 |
14 | // MARK: - Stored Properties
15 |
16 | private let kind: String = "SnippetsLibraryWidget"
17 |
18 | // MARK: - Views
19 |
20 | var body: some WidgetConfiguration {
21 | StaticConfiguration(
22 | kind: kind,
23 | provider: Provider()
24 | ) {
25 | SnippetsLibraryWidgetView(entry: $0)
26 | }
27 | .configurationDisplayName("Snippets Library Widget")
28 | .description("General statistics for the Snippet Library.")
29 | .supportedFamilies([.systemSmall, .systemMedium])
30 | }
31 |
32 | }
33 |
34 | struct SnippetsLibraryWidget_Previews: PreviewProvider {
35 | static var previews: some View {
36 | SnippetsLibraryWidgetView(entry: WidgetEntry(date: Date(), snippet: Snippet(), snippets: []))
37 | .previewContext(WidgetPreviewContext(family: .systemSmall))
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/SnippetsLibraryWidget/Views/MediumWidget/MediumWidgetListItemView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MediumWidgetListItemView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 03/10/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct MediumWidgetListItemView: View {
11 |
12 | private enum Constants {
13 | static let imageHeight: CGFloat = 28.0
14 | static let lineLimit = 2
15 | }
16 |
17 | // MARK: - Stored Properties
18 |
19 | let snippet: Snippet
20 |
21 | // MARK: - Views
22 |
23 | var body: some View {
24 | HStack(spacing: .zero) {
25 | Image("icSnippetFileWhite")
26 | .resizable()
27 | .aspectRatio(contentMode: .fit)
28 | .frame(height: Constants.imageHeight)
29 |
30 | Text("\(snippet.title)")
31 | .font(.system(size: 13, weight: .medium))
32 | .foregroundColor(Color.primary)
33 | .lineLimit(Constants.lineLimit)
34 | .padding(.leading, Layout.smallPadding)
35 |
36 | Spacer()
37 | }
38 | }
39 |
40 | }
41 |
42 | struct MediumWidgetListItemView_Previews: PreviewProvider {
43 | static var previews: some View {
44 | MediumWidgetListItemView(snippet: Snippet())
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/SnippetsLibraryWidget/Views/MediumWidget/MediumWidgetView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MediumWidgetView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 03/10/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct MediumWidgetView: View {
11 |
12 | private enum Constants {
13 | static let imageHeight: CGFloat = 64.0
14 | static let maxSnippetsCount = 5
15 | }
16 |
17 | // MARK: - Stored Properties
18 |
19 | let snippets: [Snippet]
20 |
21 | // MARK: - Views
22 |
23 | var body: some View {
24 | GeometryReader { geometry in
25 | ZStack {
26 | HStack(spacing: .zero) {
27 | Spacer()
28 |
29 | Rectangle()
30 | .foregroundColor(Color("defaultBackground"))
31 | .frame(width: geometry.size.width / 2)
32 | }
33 |
34 | HStack {
35 | SmallWidgetView(snippet: snippets.first ?? Snippet())
36 | .frame(width: (geometry.size.width / 2) - Layout.smallPadding)
37 |
38 | VStack(alignment: .leading) {
39 | ForEach(snippets.prefix(Constants.maxSnippetsCount).filter({ $0 != snippets.first }), id: \.self) { snippet in
40 | Link(destination: URL(string: "widget://\(snippet.id)")!) {
41 | MediumWidgetListItemView(snippet: snippet)
42 | }
43 | }
44 |
45 | Spacer()
46 | .makeVisible(
47 | snippets.count < Constants.maxSnippetsCount,
48 | removed: true
49 | )
50 | }
51 | .frame(
52 | width: (geometry.size.width / 2) - Layout.smallPadding,
53 | height: geometry.size.height - Layout.largePadding
54 | )
55 | }
56 | }
57 | }
58 | }
59 |
60 | }
61 |
62 | struct MediumWidgetView_Previews: PreviewProvider {
63 | static var previews: some View {
64 | MediumWidgetView(snippets: [])
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/SnippetsLibraryWidget/Views/SmallWidget/SmallWidgetView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SmallWidgetView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 02/10/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SmallWidgetView: View {
11 |
12 | private enum Constants {
13 | static let imageHeight: CGFloat = 64.0
14 | static let shadowOpacity = 0.16
15 | static let shadowRadius: CGFloat = 6.0
16 | static let shadowYOffset: CGFloat = 3.0
17 | }
18 |
19 | // MARK: - Stored Properties
20 |
21 | let snippet: Snippet
22 |
23 | // MARK: - Views
24 |
25 | var body: some View {
26 | VStack(
27 | alignment: .leading,
28 | spacing: .zero
29 | ) {
30 | Image("icSnippetFileBlank")
31 | .resizable()
32 | .aspectRatio(contentMode: .fit)
33 | .frame(height: Constants.imageHeight)
34 | .shadow(
35 | color: Color.black
36 | .opacity(Constants.shadowOpacity),
37 | radius: Constants.shadowRadius,
38 | x: .zero,
39 | y: Constants.shadowYOffset
40 | )
41 |
42 | Spacer()
43 |
44 | Text(snippet.title)
45 | .font(.headline)
46 |
47 | Text(snippet.summary)
48 | .font(.footnote)
49 | .foregroundColor(
50 | Color.primary
51 | .opacity(Layout.mediumOpacity)
52 | )
53 | }
54 | .padding()
55 | .widgetURL(URL(string: "widget://\(snippet.id)")!)
56 | }
57 |
58 | }
59 |
60 | struct SmallWidgetView_Previews: PreviewProvider {
61 | static var previews: some View {
62 | SmallWidgetView(snippet: Snippet())
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/SnippetsLibraryWidget/Views/SnippetsLibraryWidgetView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SnippetsLibraryWidgetView.swift
3 | // SnippetsLibrary
4 | //
5 | // Created by Krzysztof Łowiec on 03/10/2021.
6 | //
7 |
8 | import WidgetKit
9 | import SwiftUI
10 |
11 | struct SnippetsLibraryWidgetView: View {
12 |
13 | // MARK: - Stored Properties
14 |
15 | @Environment(\.widgetFamily) var family: WidgetFamily
16 |
17 | private(set) var entry: Provider.Entry
18 |
19 | // MARK: - Views
20 |
21 | var body: some View {
22 | switch family {
23 | case .systemMedium:
24 | MediumWidgetView(snippets: entry.snippets)
25 | default:
26 | SmallWidgetView(snippet: entry.snippet)
27 | }
28 | }
29 |
30 | }
31 |
32 | struct SnippetsLibraryWidgetView_Previews: PreviewProvider {
33 | static var previews: some View {
34 | SnippetsLibraryWidgetView(entry: .init(snippet: Snippet(), snippets: []))
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/SnippetsLibrary/SnippetsLibrary.swift:
--------------------------------------------------------------------------------
1 | struct SnippetsLibrary {
2 | var text = "Hello, World!"
3 | }
4 |
--------------------------------------------------------------------------------
/Tests/SnippetsLibraryTests/SnippetsLibraryTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import SnippetsLibrary
3 |
4 | final class SnippetsLibraryTests: XCTestCase {
5 | func testExample() {
6 | // This is an example of a functional test case.
7 | // Use XCTAssert and related functions to verify your tests produce the correct
8 | // results.
9 | XCTAssertEqual(SnippetsLibrary().text, "Hello, World!")
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/public/images/app_preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryboxx/SnippetsLibrary/d7a4c31ceb735b6cf92fcff8cfa38fcfa743ee0c/public/images/app_preview.png
--------------------------------------------------------------------------------