Repository: appcraftstudio/buymeacoffee
Branch: master
Commit: 20c574ac7dc0
Files: 32
Total size: 35.2 KB
Directory structure:
gitextract_qlxitl2w/
├── .github/
│ └── workflows/
│ └── swift.yml
├── .gitignore
├── .swiftpm/
│ └── xcode/
│ ├── package.xcworkspace/
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata/
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata/
│ └── xcschemes/
│ └── BuyMeACoffee.xcscheme
├── Bundle.swift
├── BuyMeACoffee.podspec
├── CODE_OF_CONDUCT.md
├── LICENSE
├── Package.swift
├── README.md
├── Sources/
│ └── BuyMeACoffee/
│ ├── BMCButton.swift
│ ├── BMCColor.swift
│ ├── BMCFont.swift
│ ├── BMCManager.swift
│ └── Resources/
│ └── Assets.xcassets/
│ ├── Colors/
│ │ ├── Contents.json
│ │ ├── black.colorset/
│ │ │ └── Contents.json
│ │ ├── blue.colorset/
│ │ │ └── Contents.json
│ │ ├── green.colorset/
│ │ │ └── Contents.json
│ │ ├── orange.colorset/
│ │ │ └── Contents.json
│ │ ├── pink.colorset/
│ │ │ └── Contents.json
│ │ ├── purple.colorset/
│ │ │ └── Contents.json
│ │ ├── red.colorset/
│ │ │ └── Contents.json
│ │ ├── white.colorset/
│ │ │ └── Contents.json
│ │ └── yellow.colorset/
│ │ └── Contents.json
│ ├── Contents.json
│ ├── cup-white.imageset/
│ │ └── Contents.json
│ └── cup-yellow.imageset/
│ └── Contents.json
└── Tests/
├── BuyMeACoffeeTests/
│ └── XCTestManifests.swift
├── LinuxMain.swift
└── buymeacoffeeTests/
└── BuyMeACoffeeTests.swift
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/swift.yml
================================================
name: Swift
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Select Xcode 12
run: sudo xcode-select -s /Applications/Xcode_12.app && xcodebuild -version
- name: Linting podspec
run: pod lib lint
- name: Build
run: swift build -v
- name: Run tests
run: swift test -v
================================================
FILE: .gitignore
================================================
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
================================================
FILE: .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
================================================
FILE: .swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
================================================
FILE: .swiftpm/xcode/package.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
<true/>
</dict>
</plist>
================================================
FILE: .swiftpm/xcode/xcshareddata/xcschemes/BuyMeACoffee.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BuyMeACoffee_BuyMeACoffee"
BuildableName = "BuyMeACoffee_BuyMeACoffee"
BlueprintName = "BuyMeACoffee_BuyMeACoffee"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BuyMeACoffee"
BuildableName = "BuyMeACoffee"
BlueprintName = "BuyMeACoffee"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BuyMeACoffeeTests"
BuildableName = "BuyMeACoffeeTests"
BlueprintName = "BuyMeACoffeeTests"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<PostActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Script"
scriptText = "cp ~/Library/Developer/CoreSimulator/Devices/$TARGET_DEVICE_IDENTIFIER/data/Library/Caches/snapshot-bmc-button.png $WORKSPACE_PATH/../../../Images ">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BuyMeACoffeeTests"
BuildableName = "BuyMeACoffeeTests"
BlueprintName = "BuyMeACoffeeTests"
ReferencedContainer = "container:">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PostActions>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BuyMeACoffeeTests"
BuildableName = "BuyMeACoffeeTests"
BlueprintName = "BuyMeACoffeeTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BuyMeACoffee_BuyMeACoffee"
BuildableName = "BuyMeACoffee_BuyMeACoffee"
BlueprintName = "BuyMeACoffee_BuyMeACoffee"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
================================================
FILE: Bundle.swift
================================================
//
// Bundle.swift
// BuyMeACoffee
//
// Created by François Boulais on 01/07/2020.
// Copyright © 2020 App Craft Studio. All rights reserved.
//
import Foundation
internal extension Bundle {
static let module = Bundle(for: BMCManager.self)
}
================================================
FILE: BuyMeACoffee.podspec
================================================
Pod::Spec.new do |spec|
spec.name = 'BuyMeACoffee'
spec.version = '1.0.6'
spec.license = { :type => "MIT", :file => "LICENSE" }
spec.homepage = 'https://www.buymeacoffee.com'
spec.author = { 'François Boulais' => 'francois@appcraftstudio.com' }
spec.social_media_url = 'https://twitter.com/frboulais'
spec.summary = 'Buy Me a Coffee framework for iOS'
spec.source = { :git => 'https://github.com/appcraftstudio/buymeacoffee.git', :tag => "#{spec.version}" }
spec.screenshots = [ 'https://github.com/appcraftstudio/buymeacoffee/raw/master/Images/screenshot-buymeacoffee-home.png',
'https://github.com/appcraftstudio/buymeacoffee/raw/master/Images/screenshot-buymeacoffee-apple-pay.png' ]
spec.ios.deployment_target = '11.0'
spec.platform = :ios, '11.0'
spec.swift_version = '5.0'
spec.source_files = 'Sources/**/*.swift', 'Bundle.swift'
spec.resources = 'Sources/**/Resources/*'
spec.ios.framework = 'UIKit', 'WebKit'
spec.test_spec 'BuyMeACoffeeTests' do |test_spec|
test_spec.source_files = 'Tests/BuyMeACoffeeTests/*.{swift}'
end
end
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at francois@appcraftstudio.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 App Craft Studio
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Package.swift
================================================
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "BuyMeACoffee",
platforms: [
.iOS(.v11)
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(name: "BuyMeACoffee", targets: ["BuyMeACoffee"]),
.library(name: "BuyMeACoffeeDynamic", type: .dynamic, targets: ["BuyMeACoffee"])
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "BuyMeACoffee",
dependencies: [],
resources: [
.process("Resources/Assets.xcassets"),
.process("Resources/Cookie-Regular.ttf"),
.process("Resources/Lato-Regular.ttf")
]),
.testTarget(
name: "BuyMeACoffeeTests",
dependencies: ["BuyMeACoffee"]),
]
)
================================================
FILE: README.md
================================================

# A supporter is worth a thousand followers.
### [Buy Me a Coffee](https://www.buymeacoffee.com) is a modern creator platform. It takes two minutes to start your page and has all the features that you need to build your creative business.
**Donations :moneybag:**
Give your audience a friendly way to thank you.
**Memberships :spiral_calendar:**
Earn recurring revenue by offering a monthly or yearly membership.
**Sell Extras :sparkles:**
A new, creative way to offer Zoom calls, art commissions, anything.
<p align="center">
<br>
<img src="https://github.com/appcraftstudio/buymeacoffee/raw/master/Images/screenshot-buymeacoffee-home.png" width="320">
<img src="https://github.com/appcraftstudio/buymeacoffee/raw/master/Images/screenshot-buymeacoffee-apple-pay.png" width="320">
</p>
## Features
- [X] Apple Pay
- [X] iCloud Keychain
## Requirements
CocoaPods | Swift Package Manager
:---: | :---:
Swift 5.0 | Swift 5.3
Xcode 11.x | Xcode 12.x
## App Store Review
These are the two App Store Review Guidelines articles you have to know before using this framework:
[3.1.1 In-App Purchase](https://developer.apple.com/app-store/review/guidelines/#in-app-purchase)
> - Apps may use in-app purchase currencies to enable customers to “tip” digital content providers in the app.
[3.2.1 Acceptable](https://developer.apple.com/app-store/review/guidelines/#acceptable):
> **(vii)** Apps may enable individual users to give a monetary gift to another individual without using in-app purchase, provided that (a) the gift is a completely optional choice by the giver, and (b) 100% of the funds go to the receiver of the gift. However, a gift that is connected to or associated at any point in time with receiving digital content or services must use in-app purchase.
## Implement Buy Me a Coffee
1. Import the BuyMeACoffee framework in your `UIApplicationDelegate`:
```swift
import BuyMeACoffee
```
2. Configure the `BMCManager` shared instance with the username you've chosen on www.buymeacoffee.com, typically in your app's `application:didFinishLaunchingWithOptions:` method:
```swift
BMCManager.shared.configure(username: "appcraftstudio")
```
3. In the view controller, override the `viewDidLoad` method to set the presenting view controller of the `BMCManager` object.
```swift
BMCManager.shared.presentingViewController = self
// You can also set a custom thank you message
BMCManager.shared.thankYouMessage = "Thank you for supporting 🎉 App Craft Studio !"
```
4. Add a `BMCButton` to your storyboard, XIB file, or instantiate it programmatically. To add the button to your storyboard or XIB file, add a View and set its custom class to `BMCButton`.
5. **Optional**: If you want to customize the button, do the following:
```swift
let configuration = BMCButton.Configuration(color: .orange, font: .cookie)
let button = BMCButton(configuration: configuration)
// or set the burtton configuration later
button.configure(with: configuration)
```
<p align="center">
<br>
<img src="https://github.com/appcraftstudio/buymeacoffee/raw/master/Images/snapshot-bmc-button.png" width="300">
</p>
## (Optional) Configure In-App Purchase
Depending the legal receiver of the gift configured on Buy Me a Coffee, App Store reviewers can ask for In-App Purchase implementation.
If the following In-App Purchase if configured for your application, it will be displayed as primary flow when user tap on the `BMCButton`.
**If the framework can't retrieve In-App Purchase informations, the web flow will be used as fallback.**
### App Store Connect
Go to [App Store Connect](https://appstoreconnect.apple.com), search for the *In-App Purchases* section of your app, and then, create a new one with the following informations:
|||
| --- | --- |
| **Type** | Consumable |
| **Reference Name** | *Buy Me a Coffee* |
| **Product ID** | `your.app.bundle.identifier`*.buymeacoffee* |
| **Cleared for Sale** | :white_check_mark: |
| **Price** | Tier 4 |
| **Display Name** | *Buy Me a Coffee* |
| **Description** | *Hey there! You can now buy me a coffee!*
| **Promotional Image** | [download here](https://github.com/appcraftstudio/buymeacoffee/raw/master/Images/%20in-app-purchase-promotional-image.jpg) |
| **Review Screenshot** | [download here](https://github.com/appcraftstudio/buymeacoffee/raw/master/Images/%20in-app-purchase-review-screenshot.png) |
| **Review Notes** | *Buy Me a Coffee enable customers to “tip” digital content providers in the app.* |
### Capabilities
1. Select the current workspace in the project navigator.
2. Then, select the app target in the left panel.
3. Go to the *Signing & Capabilities* tab.
4. Add the *In-App Purchase* capability.
<p align="center">
<img src="https://github.com/appcraftstudio/buymeacoffee/raw/master/Images/screenshot-xcode-capabilities.png" width="600">
</p>
## Installation
### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html)
You want to add pod `'BuyMeACoffee', '~> 1.0'` similar to the following to your Podfile:
```rb
target 'MyApp' do
pod 'BuyMeACoffee', '~> 1.0'
end
```
Then run a `pod install` inside your terminal, or from CocoaPods.app.
### [Swift Package Manager](https://swift.org/package-manager/)
1. Using Xcode 11 or above go to *File* > *Swift Packages* > *Add Package Dependency*
2. Paste the project URL: https://github.com/appcraftstudio/buymeacoffee.git
3. Click on next and select the project target

---
<a href="https://www.producthunt.com/posts/buy-me-a-coffee-framework-for-ios?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-buy-me-a-coffee-framework-for-ios" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=233953&theme=dark" alt="Buy Me a Coffee framework for iOS - Embed Buy Me a Coffee framework in your applications | Product Hunt Embed" style="width: 250px; height: 54px;" width="250px" height="54px" /></a>
<a href="https://www.buymeacoffee.com/appcraftstudio" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
Copyright © 2020 App Craft Studio. All rights reserved.
================================================
FILE: Sources/BuyMeACoffee/BMCButton.swift
================================================
//
// BMCButton.swift
//
// Created by François Boulais on 26/06/2020.
// Copyright © 2020 App Craft Studio. All rights reserved.
//
#if canImport(UIKit)
import UIKit
@IBDesignable
public class BMCButton: UIButton {
public struct Configuration {
let color: BMCColor
let font: BMCFont
let title: String
public init(color: BMCColor, font: BMCFont, title: String = "Buy me a coffee") {
self.color = color
self.font = font
self.title = title
}
public static let `default`: Self = .init(color: .default, font: .default)
}
private lazy var numberFormatter: NumberFormatter = {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .currency
return numberFormatter
}()
private var _configuration: Configuration = .default
public override var isHighlighted: Bool {
didSet {
alpha = isHighlighted ? 0.85 : 1
}
}
public convenience init(configuration: Configuration) {
self.init(type: .custom)
self._configuration = configuration
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
// MARK: - Interface Builder
public override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setup()
}
// MARK: - Public functions
public func configure(with configuration: Configuration) {
titleLabel?.font = configuration.font.value
setTitleColor(configuration.color.title, for: .normal)
setImage(configuration.color.cup, for: .normal)
backgroundColor = configuration.color.background
var title = configuration.title
if let product = BMCManager.shared.product {
numberFormatter.locale = product.priceLocale
if let price = numberFormatter.string(from: product.price) {
title.append(" (\(price))")
}
}
setTitle(title, for: .normal)
}
// MARK: - Private functions
private func setup() {
layer.shadowOffset = .init(width: 4, height: 4)
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.1
layer.shadowRadius = 2
layer.cornerRadius = 5
contentEdgeInsets = .init(top: 8, left: 12, bottom: 8, right: 12)
titleEdgeInsets = .init(top: 0, left: 6, bottom: 0, right: -6)
imageEdgeInsets = .init(top: 0, left: -6, bottom: 0, right: 6)
imageView?.contentMode = .scaleAspectFit
registerFonts()
adjustsImageWhenHighlighted = false
addTarget(BMCManager.shared, action: #selector(BMCManager.shared.start), for: .touchUpInside)
configure(with: _configuration)
}
private func registerFonts() {
if let url = Bundle.module.url(forResource: "Cookie-Regular", withExtension: "ttf") {
CTFontManagerRegisterFontsForURL(url as CFURL, .process, nil)
}
if let url = Bundle.module.url(forResource: "Lato-Regular", withExtension: "ttf") {
CTFontManagerRegisterFontsForURL(url as CFURL, .process, nil)
}
}
}
#endif
================================================
FILE: Sources/BuyMeACoffee/BMCColor.swift
================================================
//
// BMCColor.swift
//
// Created by François Boulais on 30/06/2020.
// Copyright © 2020 App Craft Studio. All rights reserved.
//
#if canImport(UIKit)
import UIKit
public enum BMCColor: String, CaseIterable {
case orange
case yellow
case purple
case black
case white
case blue
case green
case red
case pink
public static let `default`: Self = .yellow
internal var background: UIColor? {
UIColor(named: rawValue, in: .module, compatibleWith: nil)
}
internal var title: UIColor {
switch self {
case .orange, .purple, .black, .blue, .green, .red, .pink:
return .white
case .yellow, .white:
return .black
}
}
internal var cup: UIImage? {
switch self {
case .yellow:
return UIImage(named: "cup-white", in: .module, compatibleWith: nil)
default:
return UIImage(named: "cup-yellow", in: .module, compatibleWith: nil)
}
}
}
#endif
================================================
FILE: Sources/BuyMeACoffee/BMCFont.swift
================================================
//
// BMCFont.swift
//
// Created by François Boulais on 30/06/2020.
// Copyright © 2020 App Craft Studio. All rights reserved.
//
#if canImport(UIKit)
import UIKit
public enum BMCFont: String, CaseIterable {
case cookie
case lato
case arial
public static let `default`: Self = .cookie
internal var value: UIFont? {
switch self {
case .cookie:
return UIFont(name: rawValue.capitalized, size: 26)
case .lato:
return UIFont(name: rawValue.capitalized, size: 20)
case .arial:
return UIFont(name: rawValue.capitalized, size: 20)
}
}
}
#endif
================================================
FILE: Sources/BuyMeACoffee/BMCManager.swift
================================================
//
// BMCManager.swift
//
// Created by François Boulais on 30/06/2020.
// Copyright © 2020 App Craft Studio. All rights reserved.
//
#if canImport(UIKit)
import UIKit
import SafariServices
import StoreKit
public final class BMCManager: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {
public static let shared = BMCManager()
// MARK: - Public properties
/// The view controller used to present donation flow.
public var presentingViewController: UIViewController?
/// This text is displayed to supporters immediately after they make a payment.
public var thankYouMessage: String?
// MARK: - Internal properties
internal var product: SKProduct?
// MARK: - Private properties
private var username = "appcraftstudio"
private var productsRequest: SKProductsRequest?
private lazy var loadingViewController: UIViewController = {
let activityIndicatorView: UIActivityIndicatorView
if #available(iOS 13.0, *) {
activityIndicatorView = UIActivityIndicatorView(style: .large)
} else {
activityIndicatorView = UIActivityIndicatorView(style: .whiteLarge)
}
activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
activityIndicatorView.color = BMCColor.default.background
activityIndicatorView.startAnimating()
let viewController = UIViewController()
viewController.view.backgroundColor = .white
viewController.view.addSubview(activityIndicatorView)
viewController.modalPresentationStyle = .formSheet
if #available(iOS 13.0, *) {
viewController.isModalInPresentation = true
}
NSLayoutConstraint.activate([
activityIndicatorView.centerXAnchor.constraint(equalTo: viewController.view.centerXAnchor),
activityIndicatorView.centerYAnchor.constraint(equalTo: viewController.view.centerYAnchor)
])
return viewController
}()
private var url: URL? {
URL(string: "https://www.buymeacoffee.com/".appending(username))
}
// MARK: - Public functions
/**
Configure the manager for future donation requests.
- parameters:
- username: The username you've chosen on www.buymeacoffee.com.
*/
public func configure(username: String) {
self.username = username
guard SKPaymentQueue.canMakePayments() else {
return
}
if let productIdentifier = Bundle.main.bundleIdentifier?.appending(".buymeacoffee") {
productsRequest?.cancel()
productsRequest = SKProductsRequest(productIdentifiers: [productIdentifier])
productsRequest?.delegate = self
productsRequest?.start()
}
}
/**
Start the donation flow on presenting view controller.
*/
@objc public func start() {
guard let presentingViewController = presentingViewController else {
fatalError("presentingViewController must be set.")
}
guard SKPaymentQueue.canMakePayments(), let product = product else {
return fallback()
}
presentingViewController.present(loadingViewController, animated: true) {
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(payment)
}
}
// MARK: - Private functions
private override init() { }
private func fallback() {
guard let url = url else {
return
}
let viewController = SFSafariViewController(url: url)
viewController.preferredControlTintColor = BMCColor.default.background
viewController.modalPresentationStyle = .formSheet
presentingViewController?.present(viewController, animated: true)
}
private func showPurchasedMessage() {
let message = thankYouMessage ?? "Thank you for supporting 🎉"
let alertController = UIAlertController(title: "Buy Me a Coffee", message: message, preferredStyle: .alert)
alertController.addAction(.init(title: "Close", style: .default, handler: { [weak self] _ in
self?.loadingViewController.dismiss(animated: true)
}))
alertController.view.tintColor = BMCColor.default.background
loadingViewController.present(alertController, animated: true)
}
// MARK: - SKProductsRequestDelegate
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
product = response.products.first
productsRequest = nil
}
// MARK: - SKPaymentTransactionObserver
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchased:
showPurchasedMessage()
SKPaymentQueue.default().finishTransaction(transaction)
case .failed:
loadingViewController.dismiss(animated: true) { [weak self] in
if (transaction.error as? SKError)?.code != .paymentCancelled {
self?.fallback()
}
}
case .restored:
break
case .deferred:
break
case .purchasing:
break
@unknown default:
break
}
}
}
public func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool {
true
}
}
#endif
================================================
FILE: Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/Contents.json
================================================
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/black.colorset/Contents.json
================================================
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.000",
"red" : "0.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/blue.colorset/Contents.json
================================================
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "0.498",
"red" : "0.373"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/green.colorset/Contents.json
================================================
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.710",
"green" : "0.839",
"red" : "0.475"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/orange.colorset/Contents.json
================================================
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.247",
"green" : "0.506",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/pink.colorset/Contents.json
================================================
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "0.443",
"red" : "0.957"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/purple.colorset/Contents.json
================================================
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "0.373",
"red" : "0.741"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/red.colorset/Contents.json
================================================
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.373",
"green" : "0.373",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/white.colorset/Contents.json
================================================
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/yellow.colorset/Contents.json
================================================
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.000",
"green" : "0.867",
"red" : "1.000"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Sources/BuyMeACoffee/Resources/Assets.xcassets/Contents.json
================================================
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Sources/BuyMeACoffee/Resources/Assets.xcassets/cup-white.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "cup-white.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Sources/BuyMeACoffee/Resources/Assets.xcassets/cup-yellow.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "cup-yellow.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "original"
}
}
================================================
FILE: Tests/BuyMeACoffeeTests/XCTestManifests.swift
================================================
import XCTest
#if !canImport(ObjectiveC)
public func allTests() -> [XCTestCaseEntry] {
return [
testCase(buymeacoffeeTests.allTests),
]
}
#endif
================================================
FILE: Tests/LinuxMain.swift
================================================
import XCTest
import BuyMeACoffeeTests
var tests = [XCTestCaseEntry]()
#if canImport(UIKit)
tests += BuyMeACoffeeTests.allTests()
#endif
XCTMain(tests)
================================================
FILE: Tests/buymeacoffeeTests/BuyMeACoffeeTests.swift
================================================
import Foundation
import XCTest
@testable import BuyMeACoffee
#if canImport(UIKit)
final class BuyMeACoffeeTests: XCTestCase {
private let fileManager: FileManager = .default
func testSnapshotButton() {
guard let url = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first else {
XCTFail()
return
}
var isDirectory: ObjCBool = false
if !fileManager.fileExists(atPath: url.path, isDirectory: &isDirectory) || !isDirectory.boolValue {
XCTAssertNoThrow(try fileManager.createDirectory(at: url, withIntermediateDirectories: false))
}
let button = BMCButton(frame: .init(x: 0, y: 0, width: 200, height: 50))
button.configure(with: .default)
if let data = button.snapshot().pngData() {
let url = url.appendingPathComponent("snapshot-bmc-button").appendingPathExtension("png")
print(url.path)
XCTAssertNoThrow(try data.write(to: url))
} else {
XCTFail()
}
}
static var allTests = [
("snapshot button", testSnapshotButton),
]
}
fileprivate extension BMCButton {
var size: CGSize {
bounds.insetBy(dx: -layer.shadowOffset.width, dy: -layer.shadowOffset.height).size
}
func snapshot() -> UIImage {
UIGraphicsImageRenderer(size: size).image { context in
layer.render(in: context.cgContext)
}
}
}
#endif
gitextract_qlxitl2w/
├── .github/
│ └── workflows/
│ └── swift.yml
├── .gitignore
├── .swiftpm/
│ └── xcode/
│ ├── package.xcworkspace/
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata/
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata/
│ └── xcschemes/
│ └── BuyMeACoffee.xcscheme
├── Bundle.swift
├── BuyMeACoffee.podspec
├── CODE_OF_CONDUCT.md
├── LICENSE
├── Package.swift
├── README.md
├── Sources/
│ └── BuyMeACoffee/
│ ├── BMCButton.swift
│ ├── BMCColor.swift
│ ├── BMCFont.swift
│ ├── BMCManager.swift
│ └── Resources/
│ └── Assets.xcassets/
│ ├── Colors/
│ │ ├── Contents.json
│ │ ├── black.colorset/
│ │ │ └── Contents.json
│ │ ├── blue.colorset/
│ │ │ └── Contents.json
│ │ ├── green.colorset/
│ │ │ └── Contents.json
│ │ ├── orange.colorset/
│ │ │ └── Contents.json
│ │ ├── pink.colorset/
│ │ │ └── Contents.json
│ │ ├── purple.colorset/
│ │ │ └── Contents.json
│ │ ├── red.colorset/
│ │ │ └── Contents.json
│ │ ├── white.colorset/
│ │ │ └── Contents.json
│ │ └── yellow.colorset/
│ │ └── Contents.json
│ ├── Contents.json
│ ├── cup-white.imageset/
│ │ └── Contents.json
│ └── cup-yellow.imageset/
│ └── Contents.json
└── Tests/
├── BuyMeACoffeeTests/
│ └── XCTestManifests.swift
├── LinuxMain.swift
└── buymeacoffeeTests/
└── BuyMeACoffeeTests.swift
Condensed preview — 32 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (40K chars).
[
{
"path": ".github/workflows/swift.yml",
"chars": 450,
"preview": "name: Swift\n\non:\n push:\n branches: [ master ]\n pull_request:\n branches: [ master ]\n\njobs:\n build:\n runs-on: "
},
{
"path": ".gitignore",
"chars": 53,
"preview": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\nxcuserdata/\n"
},
{
"path": ".swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": ".swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": ".swiftpm/xcode/package.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
"chars": 263,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": ".swiftpm/xcode/xcshareddata/xcschemes/BuyMeACoffee.xcscheme",
"chars": 4841,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1200\"\n version = \"1.7\">\n <BuildAction\n "
},
{
"path": "Bundle.swift",
"chars": 253,
"preview": "//\n// Bundle.swift\n// BuyMeACoffee\n//\n// Created by François Boulais on 01/07/2020.\n// Copyright © 2020 App Craft St"
},
{
"path": "BuyMeACoffee.podspec",
"chars": 1351,
"preview": "Pod::Spec.new do |spec|\n spec.name = 'BuyMeACoffee'\n spec.version = '1.0.6'\n spec.lice"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 3359,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "LICENSE",
"chars": 1073,
"preview": "MIT License\n\nCopyright (c) 2020 App Craft Studio\n\nPermission is hereby granted, free of charge, to any person obtaining "
},
{
"path": "Package.swift",
"chars": 1349,
"preview": "// swift-tools-version:5.3\n// The swift-tools-version declares the minimum version of Swift required to build this packa"
},
{
"path": "README.md",
"chars": 6386,
"preview": "\n\n# A supporter is worth a thousand "
},
{
"path": "Sources/BuyMeACoffee/BMCButton.swift",
"chars": 3456,
"preview": "//\n// BMCButton.swift\n//\n// Created by François Boulais on 26/06/2020.\n// Copyright © 2020 App Craft Studio. All righ"
},
{
"path": "Sources/BuyMeACoffee/BMCColor.swift",
"chars": 1035,
"preview": "//\n// BMCColor.swift\n//\n// Created by François Boulais on 30/06/2020.\n// Copyright © 2020 App Craft Studio. All right"
},
{
"path": "Sources/BuyMeACoffee/BMCFont.swift",
"chars": 655,
"preview": "//\n// BMCFont.swift\n// \n// Created by François Boulais on 30/06/2020.\n// Copyright © 2020 App Craft Studio. All righ"
},
{
"path": "Sources/BuyMeACoffee/BMCManager.swift",
"chars": 5854,
"preview": "//\n// BMCManager.swift\n//\n// Created by François Boulais on 30/06/2020.\n// Copyright © 2020 App Craft Studio. All rig"
},
{
"path": "Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/black.colorset/Contents.json",
"chars": 329,
"preview": "{\n \"colors\" : [\n {\n \"color\" : {\n \"color-space\" : \"srgb\",\n \"components\" : {\n \"alpha\" : \"1"
},
{
"path": "Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/blue.colorset/Contents.json",
"chars": 329,
"preview": "{\n \"colors\" : [\n {\n \"color\" : {\n \"color-space\" : \"srgb\",\n \"components\" : {\n \"alpha\" : \"1"
},
{
"path": "Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/green.colorset/Contents.json",
"chars": 329,
"preview": "{\n \"colors\" : [\n {\n \"color\" : {\n \"color-space\" : \"srgb\",\n \"components\" : {\n \"alpha\" : \"1"
},
{
"path": "Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/orange.colorset/Contents.json",
"chars": 329,
"preview": "{\n \"colors\" : [\n {\n \"color\" : {\n \"color-space\" : \"srgb\",\n \"components\" : {\n \"alpha\" : \"1"
},
{
"path": "Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/pink.colorset/Contents.json",
"chars": 329,
"preview": "{\n \"colors\" : [\n {\n \"color\" : {\n \"color-space\" : \"srgb\",\n \"components\" : {\n \"alpha\" : \"1"
},
{
"path": "Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/purple.colorset/Contents.json",
"chars": 329,
"preview": "{\n \"colors\" : [\n {\n \"color\" : {\n \"color-space\" : \"srgb\",\n \"components\" : {\n \"alpha\" : \"1"
},
{
"path": "Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/red.colorset/Contents.json",
"chars": 329,
"preview": "{\n \"colors\" : [\n {\n \"color\" : {\n \"color-space\" : \"srgb\",\n \"components\" : {\n \"alpha\" : \"1"
},
{
"path": "Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/white.colorset/Contents.json",
"chars": 329,
"preview": "{\n \"colors\" : [\n {\n \"color\" : {\n \"color-space\" : \"srgb\",\n \"components\" : {\n \"alpha\" : \"1"
},
{
"path": "Sources/BuyMeACoffee/Resources/Assets.xcassets/Colors/yellow.colorset/Contents.json",
"chars": 329,
"preview": "{\n \"colors\" : [\n {\n \"color\" : {\n \"color-space\" : \"srgb\",\n \"components\" : {\n \"alpha\" : \"1"
},
{
"path": "Sources/BuyMeACoffee/Resources/Assets.xcassets/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "Sources/BuyMeACoffee/Resources/Assets.xcassets/cup-white.imageset/Contents.json",
"chars": 159,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"cup-white.pdf\",\n \"idiom\" : \"universal\"\n }\n ],\n \"info\" : {\n \"auth"
},
{
"path": "Sources/BuyMeACoffee/Resources/Assets.xcassets/cup-yellow.imageset/Contents.json",
"chars": 229,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"cup-yellow.pdf\",\n \"idiom\" : \"universal\"\n }\n ],\n \"info\" : {\n \"aut"
},
{
"path": "Tests/BuyMeACoffeeTests/XCTestManifests.swift",
"chars": 162,
"preview": "import XCTest\n\n#if !canImport(ObjectiveC)\npublic func allTests() -> [XCTestCaseEntry] {\n return [\n testCase(bu"
},
{
"path": "Tests/LinuxMain.swift",
"chars": 154,
"preview": "import XCTest\n\nimport BuyMeACoffeeTests\n\nvar tests = [XCTestCaseEntry]()\n#if canImport(UIKit)\ntests += BuyMeACoffeeTests"
},
{
"path": "Tests/buymeacoffeeTests/BuyMeACoffeeTests.swift",
"chars": 1496,
"preview": "import Foundation\nimport XCTest\n@testable import BuyMeACoffee\n\n#if canImport(UIKit)\nfinal class BuyMeACoffeeTests: XCTes"
}
]
About this extraction
This page contains the full source code of the appcraftstudio/buymeacoffee GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 32 files (35.2 KB), approximately 9.9k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.