├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── format.yml │ └── swift.yml ├── .gitignore ├── .spi.yml ├── .swift-format ├── .swiftpm └── xcode │ └── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Docs ├── address-dark.png ├── bitcoin-wallet-ui-kit-themes.png ├── button-dark.png ├── colors-dark.png ├── icon-dark.png ├── text-dark.png └── words-dark.png ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── BitcoinUI │ ├── AddressFormattedView.swift │ ├── BitcoinUI.docc │ ├── BitcoinUI.md │ ├── GettingStarted.md │ ├── Resources │ │ ├── art-files │ │ │ ├── 01-01-colors-preview-500.png │ │ │ ├── 01-01-images-preview-500.png │ │ │ ├── 01-02-colors-preview-500.png │ │ │ ├── 01-02-images-preview-500.png │ │ │ └── coldcard-import-halfsize.png │ │ └── code-files │ │ │ ├── 01-01-colors.swift │ │ │ ├── 01-01-images.swift │ │ │ ├── 01-02-colors.swift │ │ │ └── 01-02-images.swift │ └── Tutorials │ │ ├── Colors.tutorial │ │ ├── Images.tutorial │ │ └── Tutorial Table of Contents.tutorial │ ├── ButtonStyles.swift │ ├── Colors.swift │ ├── Images.swift │ ├── Images.xcassets │ ├── Contents.json │ ├── bitbox.imageset │ │ ├── Contents.json │ │ └── bitbox300.svg │ ├── blockstream-jade.imageset │ │ ├── Contents.json │ │ └── jade300.svg │ ├── cobo-vault.imageset │ │ ├── Contents.json │ │ └── cobo300.svg │ ├── coldcard.imageset │ │ ├── Contents.json │ │ └── coldcard.svg │ ├── foundation-passport.imageset │ │ ├── Contents.json │ │ └── foundation300.svg │ ├── generic-hardware-wallet.imageset │ │ ├── Contents.json │ │ └── generic-hardware-wallet.svg │ ├── keepkey.imageset │ │ ├── Contents.json │ │ └── keepkey300.svg │ ├── ledger-nano.imageset │ │ ├── Contents.json │ │ └── ledgernano300.svg │ ├── opendime.imageset │ │ ├── Contents.json │ │ └── opendime.svg │ ├── satoshi-v2.imageset │ │ ├── Contents.json │ │ └── satoshi-v2.svg │ ├── seedplate.imageset │ │ ├── Contents.json │ │ └── seedplate300.svg │ ├── trezor-model-t.imageset │ │ ├── Contents.json │ │ └── trezort300.svg │ └── trezor-one.imageset │ │ ├── Contents.json │ │ └── trezorone300.svg │ ├── QRCodeView.swift │ ├── SeedPhraseView.swift │ ├── TextStyles.swift │ └── Utilities.swift └── Tests ├── BitcoinUITests ├── BitcoinUITests.swift └── XCTestManifests.swift └── LinuxMain.swift /.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 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.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/workflows/format.yml: -------------------------------------------------------------------------------- 1 | name: Format 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | swift_format: 10 | name: swift-format 11 | runs-on: macos-14 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Install 15 | run: brew install swift-format 16 | - name: Format 17 | run: swift format --in-place --recursive . 18 | - uses: stefanzweifel/git-auto-commit-action@v4 19 | with: 20 | commit_message: 'ci: run swift-format' 21 | branch: 'main' 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | -------------------------------------------------------------------------------- /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | name: Swift 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: macos-14 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Force Xcode 15 17 | run: sudo xcode-select -switch /Applications/Xcode_15.2.app 18 | # - name: Build 19 | # run: xcodebuild -scheme WalletUI -sdk iphoneos 20 | # - name: Run tests 21 | # run: xcodebuild -scheme WalletUI test -sdk iphonesimulator -destination 'platform=iOS Simulator,OS=15.0,name=iPhone 12' 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - scheme: WalletUI 5 | -------------------------------------------------------------------------------- /.swift-format: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "indentation" : { 4 | "spaces" : 4 5 | }, 6 | "indentWidth": 4, 7 | "maximumBlankLines": 1, 8 | "lineBreakBeforeEachArgument": true, 9 | "trimWhitespace": true 10 | } 11 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Docs/address-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reez/BitcoinUI/a92d2426526cf50858122c9c019e9b5ac138214a/Docs/address-dark.png -------------------------------------------------------------------------------- /Docs/bitcoin-wallet-ui-kit-themes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reez/BitcoinUI/a92d2426526cf50858122c9c019e9b5ac138214a/Docs/bitcoin-wallet-ui-kit-themes.png -------------------------------------------------------------------------------- /Docs/button-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reez/BitcoinUI/a92d2426526cf50858122c9c019e9b5ac138214a/Docs/button-dark.png -------------------------------------------------------------------------------- /Docs/colors-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reez/BitcoinUI/a92d2426526cf50858122c9c019e9b5ac138214a/Docs/colors-dark.png -------------------------------------------------------------------------------- /Docs/icon-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reez/BitcoinUI/a92d2426526cf50858122c9c019e9b5ac138214a/Docs/icon-dark.png -------------------------------------------------------------------------------- /Docs/text-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reez/BitcoinUI/a92d2426526cf50858122c9c019e9b5ac138214a/Docs/text-dark.png -------------------------------------------------------------------------------- /Docs/words-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reez/BitcoinUI/a92d2426526cf50858122c9c019e9b5ac138214a/Docs/words-dark.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Matthew Ramsden 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.10 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: "BitcoinUI", 8 | platforms: [ 9 | .iOS(.v16) 10 | ], 11 | products: [ 12 | // Products define the executables and libraries a package produces, and make them visible to other packages. 13 | .library( 14 | name: "BitcoinUI", 15 | targets: ["BitcoinUI"] 16 | ) 17 | ], 18 | dependencies: [ 19 | // Dependencies declare other packages that this package depends on. 20 | // .package(url: /* package url */, from: "1.0.0"), 21 | ], 22 | targets: [ 23 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 24 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 25 | .target( 26 | name: "BitcoinUI", 27 | dependencies: [] 28 | ), 29 | .testTarget( 30 | name: "BitcoinUITests", 31 | dependencies: ["BitcoinUI"] 32 | ), 33 | ] 34 | ) 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![cover image](/Docs/bitcoin-wallet-ui-kit-themes.png) 2 | 3 | # BitcoinUI 4 | 5 | BitcoinUI is a native iOS implementation of [Bitcoin Wallet UI Kit](https://github.com/GBKS/bitcoin-wallet-ui-kit). 6 | 7 | *Both BitcoinUI and its reference Bitcoin Wallet UI Kit are Work In Progress.* 8 | 9 | [What's Included](#whats-included)
10 | [Basic Usage](#basic-usage)
11 | [Requirements](#requirements)
12 | [Installation](#installation)
13 | 14 | ## What's Included 15 | 16 | The design system in BitcoinUI includes: 17 | 18 | - Colors 19 | - Button styles 20 | - Text styles 21 | - Icons 22 | - Views 23 | 24 |

25 | 26 | colors-code-preview 27 | 28 | 29 | buttons-code-preview 30 | 31 | 32 | text-code-preview 33 | 34 | 35 | hardware-illustrations-code-preview 36 | 37 | 38 | address-code-preview 39 | 40 | 41 | words-code-preview 42 | 43 |

44 | 45 | ## Basic Usage 46 | 47 | ### Colors 48 | 49 | *SwiftUI* 50 | 51 | ```swift 52 | Text("Bitcoin Orange") 53 | .font(.caption) 54 | .foregroundColor(.bitcoinOrange) 55 | .multilineTextAlignment(.center) 56 | ``` 57 | 58 | *UIKit* 59 | 60 | ```swift 61 | let label = UILabel() 62 | label.frame = CGRect(x: 200, y: 200, width: 200, height: 20) 63 | label.text = "Bitcoin Orange" 64 | label.textColor = .bitcoinOrange 65 | ``` 66 | 67 | ### Button Styles 68 | 69 | Three button styles (with a number of optional parameters) are implemented in SwiftUI: 70 | 71 | - `BitcoinFilled` 72 | 73 | - `BitcoinOutlined` 74 | 75 | - `BitcoinPlain` 76 | 77 | *SwiftUI* 78 | 79 | ```swift 80 | Button("Filled button") { 81 | print("Button pressed!") 82 | } 83 | .buttonStyle(BitcoinFilled()) 84 | ``` 85 | 86 | ### Text Styles 87 | 88 | Ten text styles are implemented in SwiftUI: 89 | 90 | - `BitcoinTitle1` - `BitcoinTitle5` 91 | 92 | - `BitcoinBody1` - `BitcoinBody5` 93 | 94 | *SwiftUI* 95 | 96 | ```swift 97 | Text("Title") 98 | .textStyle(BitcoinTitle1()) 99 | ``` 100 | 101 | ### Icons 102 | 103 | *SwiftUI* 104 | 105 | ```swift 106 | BitcoinImage(named: "coldcard") 107 | .resizable() 108 | .aspectRatio(contentMode: .fit) 109 | .frame(height: 75.0) 110 | ``` 111 | 112 | *UIKit* 113 | 114 | ```swift 115 | let image = BitcoinUIImage(named: "coldcard") 116 | let imageView = UIImageView(image: image) 117 | imageView.frame = CGRect(x: 0, y: 0, width: 75, height: 75) 118 | view.addSubview(imageView) 119 | ``` 120 | 121 | ## Requirements 122 | 123 | BitcoinUI currently requires minimum deployment targets of iOS 15. 124 | 125 | ## Installation 126 | 127 | You can add BitcoinUI to an Xcode project by adding it as a package dependency. 128 | 129 | 1. From the **File** menu, select **Add Packages…** 130 | 2. Enter "https://github.com/reez/BitcoinUI" into the package repository URL text field 131 | 3. Depending on how your project is structured: 132 | - If you have a single application target that needs access to the library, then add **BitcoinUI** directly to your application. 133 | - If you want to use this library from multiple targets you must create a shared framework that depends on **BitcoinUI** and then depend on that framework in all of your targets. 134 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/AddressFormattedView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddressFormattedView.swift 3 | // 4 | // 5 | // Created by Matthew Ramsden on 6/21/24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct AddressFormattedView: View { 11 | public let address: String 12 | public let columns: Int 13 | public let spacing: CGFloat 14 | public let gridItemSize: CGFloat 15 | 16 | public init( 17 | address: String, 18 | columns: Int = 3, 19 | spacing: CGFloat = 10, 20 | gridItemSize: CGFloat = 80 21 | ) { 22 | self.address = address 23 | self.columns = columns 24 | self.spacing = spacing 25 | self.gridItemSize = gridItemSize 26 | } 27 | 28 | public var body: some View { 29 | LazyVGrid( 30 | columns: Array( 31 | repeating: GridItem( 32 | .fixed(gridItemSize), 33 | spacing: spacing 34 | ), 35 | count: columns 36 | ), 37 | spacing: spacing 38 | ) { 39 | let chunks = address.chunked(into: 4) 40 | ForEach(chunks.indices, id: \.self) { index in 41 | Text(chunks[index]) 42 | .font(.system(size: 20, weight: .medium, design: .monospaced)) 43 | .foregroundColor(index % 2 == 0 ? .primary : .secondary) 44 | } 45 | } 46 | } 47 | } 48 | 49 | struct AddressFormattedView_Previews: PreviewProvider { 50 | static var previews: some View { 51 | AddressFormattedView( 52 | address: 53 | "tb1pw6y0vtmsn46epvz0j8ddc46ketmp28t82p22hcrrkch3a0jhu40qe267dl" 54 | ) 55 | } 56 | } 57 | 58 | #Preview { 59 | AddressFormattedView( 60 | address: "tb1pw6y0vtmsn46epvz0j8ddc46ketmp28t82p22hcrrkch3a0jhu40qe267dl" 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/BitcoinUI.docc/BitcoinUI.md: -------------------------------------------------------------------------------- 1 | # ``BitcoinUI`` 2 | 3 | An iOS implementation of Bitcoin Wallet UI Kit, with example code implementations via Swift Previews. 4 | 5 | ## Overview 6 | 7 | BitcoinUI provides functions and resources for easy access and implementation of the [Bitcoin UI Kit](https://github.com/GBKS/bitcoin-wallet-ui-kit) design system for applications: 8 | 9 | > "Many designers on Bitcoin products find themselves designing the same set of UI components that have already been thought through by others. The goal of this UI kit is to provide a strong foundation for prototypes, concept explorations and real projects to kickstart their design processes. By not having to worry about the basics, designers can then focus on what makes their products unique." 10 | 11 | ![ColdCard import screen](coldcard-import-halfsize.png) 12 | 13 | ## Topics 14 | 15 | ### Essentials 16 | 17 | - 18 | 19 | ### Icons 20 | 21 | - ``BitcoinImage(named:)`` 22 | - ``BitcoinUIImage(named:)`` 23 | 24 | ### Colors 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/BitcoinUI.docc/GettingStarted.md: -------------------------------------------------------------------------------- 1 | # Getting Started with BitcoinUI 2 | 3 | How to install BitcoinUI in your application. 4 | 5 | ## Installation 6 | 7 | You can add BitcoinUI to an Xcode project by adding it as a package dependency. 8 | 9 | 1. From the **File** menu, select **Swift Packages › Add Package Dependency…** 10 | 2. Enter "https://github.com/reez/BitcoinUI" into the package repository URL text field 11 | 3. Depending on how your project is structured: 12 | - If you have a single application target that needs access to the library, then add **BitcoinUI** directly to your application. 13 | - If you want to use this library from multiple targets you must create a shared framework that depends on **BitcoinUI** and then depend on that framework in all of your targets. 14 | 15 | ## Implementation 16 | 17 | ### Icons 18 | 19 | - ``BitcoinImage(named:)`` 20 | - ``BitcoinUIImage(named:)`` 21 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/BitcoinUI.docc/Resources/art-files/01-01-colors-preview-500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reez/BitcoinUI/a92d2426526cf50858122c9c019e9b5ac138214a/Sources/BitcoinUI/BitcoinUI.docc/Resources/art-files/01-01-colors-preview-500.png -------------------------------------------------------------------------------- /Sources/BitcoinUI/BitcoinUI.docc/Resources/art-files/01-01-images-preview-500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reez/BitcoinUI/a92d2426526cf50858122c9c019e9b5ac138214a/Sources/BitcoinUI/BitcoinUI.docc/Resources/art-files/01-01-images-preview-500.png -------------------------------------------------------------------------------- /Sources/BitcoinUI/BitcoinUI.docc/Resources/art-files/01-02-colors-preview-500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reez/BitcoinUI/a92d2426526cf50858122c9c019e9b5ac138214a/Sources/BitcoinUI/BitcoinUI.docc/Resources/art-files/01-02-colors-preview-500.png -------------------------------------------------------------------------------- /Sources/BitcoinUI/BitcoinUI.docc/Resources/art-files/01-02-images-preview-500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reez/BitcoinUI/a92d2426526cf50858122c9c019e9b5ac138214a/Sources/BitcoinUI/BitcoinUI.docc/Resources/art-files/01-02-images-preview-500.png -------------------------------------------------------------------------------- /Sources/BitcoinUI/BitcoinUI.docc/Resources/art-files/coldcard-import-halfsize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reez/BitcoinUI/a92d2426526cf50858122c9c019e9b5ac138214a/Sources/BitcoinUI/BitcoinUI.docc/Resources/art-files/coldcard-import-halfsize.png -------------------------------------------------------------------------------- /Sources/BitcoinUI/BitcoinUI.docc/Resources/code-files/01-01-colors.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ColorsTutorial: View { 4 | var body: some View { 5 | 6 | Text("Orange") 7 | .font(.caption) 8 | .foregroundColor(.bitcoinOrange) 9 | .multilineTextAlignment(.center) 10 | 11 | } 12 | } 13 | 14 | struct ContentView_Previews: PreviewProvider { 15 | static var previews: some View { 16 | ColorsTutorial() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/BitcoinUI.docc/Resources/code-files/01-01-images.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ImagesTutorial: View { 4 | var body: some View { 5 | 6 | Image(systemName: "heart") 7 | .resizable() 8 | .aspectRatio(contentMode: .fit) 9 | .frame(height: 75.0) 10 | 11 | } 12 | } 13 | 14 | struct ContentView_Previews: PreviewProvider { 15 | static var previews: some View { 16 | ImagesTutorial() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/BitcoinUI.docc/Resources/code-files/01-02-colors.swift: -------------------------------------------------------------------------------- 1 | import BitcoinUI 2 | import SwiftUI 3 | 4 | struct ColorsTutorial: View { 5 | var body: some View { 6 | 7 | Text("Bitcoin Orange") 8 | .font(.caption) 9 | .foregroundColor(Color(UIColor.bitcoinOrange)) 10 | .multilineTextAlignment(.center) 11 | 12 | } 13 | } 14 | 15 | struct ContentView_Previews: PreviewProvider { 16 | static var previews: some View { 17 | ColorsTutorial() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/BitcoinUI.docc/Resources/code-files/01-02-images.swift: -------------------------------------------------------------------------------- 1 | import BitcoinUI 2 | import SwiftUI 3 | 4 | struct ImagesTutorial: View { 5 | var body: some View { 6 | 7 | BitcoinImage(named: "coldcard") 8 | .resizable() 9 | .aspectRatio(contentMode: .fit) 10 | .frame(height: 75.0) 11 | 12 | } 13 | } 14 | 15 | struct ContentView_Previews: PreviewProvider { 16 | static var previews: some View { 17 | ImagesTutorial() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/BitcoinUI.docc/Tutorials/Colors.tutorial: -------------------------------------------------------------------------------- 1 | @Tutorial(time: 1) { 2 | @Intro(title: "Using Bitcoin Colors") { 3 | This tutorial is about using Bitcoin Colors. 4 | 5 | 6 | } 7 | 8 | @Section(title: "Use of Bitcoin Colors") { 9 | @ContentAndMedia(layout: horizontal) { 10 | Let's see how to use Bitcoin Colors. 11 | 12 | 13 | } 14 | 15 | @Steps { 16 | @Step { 17 | Here is what using `Color` would look like. 18 | @Code(name: "Colors.swift", file: 01-01-colors.swift) { 19 | @Image(source: 01-01-colors-preview-500.png, alt: "01-01-colors-preview") 20 | } 21 | } 22 | 23 | @Step { 24 | Here is what using a Bitcoin `Color` would look like. Just `import BitcoinUI`. 25 | @Code(name: "Colors.swift", file: 01-02-colors.swift) { 26 | @Image(source: 01-02-colors-preview-500.png, alt: "01-02-colors-preview") 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/BitcoinUI.docc/Tutorials/Images.tutorial: -------------------------------------------------------------------------------- 1 | @Tutorial(time: 1) { 2 | @Intro(title: "Using Bitcoin Images") { 3 | This tutorial is about using Bitcoin Images. 4 | 5 | 6 | } 7 | 8 | @Section(title: "Use of Bitcoin Images") { 9 | @ContentAndMedia(layout: horizontal) { 10 | Let's see how to use Bitcoin Colors. 11 | 12 | 13 | } 14 | 15 | @Steps { 16 | @Step { 17 | Here is what using `Image` would look like. 18 | @Code(name: "Images.swift", file: 01-01-images.swift) { 19 | @Image(source: 01-01-images-preview-500.png, alt: "01-01-images-preview") 20 | } 21 | } 22 | 23 | @Step { 24 | Here is what using a Bitcoin `Image` would look like. 25 | @Code(name: "Images.swift", file: 01-02-images.swift) { 26 | @Image(source: 01-02-images-preview-500.png, alt: "01-02-images-preview") 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/BitcoinUI.docc/Tutorials/Tutorial Table of Contents.tutorial: -------------------------------------------------------------------------------- 1 | @Tutorials(name: "Tutorials") { 2 | @Intro(title: "Intro") { 3 | Intro 4 | 5 | @Image(source: coldcard-import-halfsize.png, alt: "Intro") 6 | 7 | } 8 | 9 | @Chapter(name: "Chapter 1: Colors") { 10 | Chapter 1 11 | 12 | @Image(source: coldcard-import-halfsize.png, alt: "Colors") 13 | 14 | @TutorialReference(tutorial: "doc:Colors") 15 | } 16 | 17 | @Chapter(name: "Chapter 2: Images") { 18 | Chapter 2 19 | 20 | @Image(source: coldcard-import-halfsize.png, alt: "Images") 21 | 22 | @TutorialReference(tutorial: "doc:Images") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/ButtonStyles.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonStyles.swift 3 | // 4 | // 5 | // Created by Daniel Nordh on 9/8/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | public let defaultButtonWidth = 315.0 12 | public let defaultButtonHeight = 48.0 13 | public let defaultCornerRadius = 5.0 14 | public let defaultTintColor = Color.bitcoinOrange 15 | public let defaultTextColor = Color.bitcoinWhite 16 | public let defaultDisabledFillColor = Color.bitcoinNeutral2 17 | public let defaultDisabledTextColor = Color.bitcoinNeutral5 18 | public let defaultDisabledOutlineColor = Color.bitcoinNeutral4 19 | 20 | /// A `ButtonStyle` corresponding to the Filled type in the Bitcoin Wallet UI Kit 21 | /// 22 | /// ```swift 23 | /// Button("Label") { 24 | /// print("Button pressed!") 25 | /// } 26 | ///.buttonStyle(BitcoinFilled()) 27 | /// ``` 28 | /// - Parameter width: The width of the button (optional, default is 315.0) 29 | /// - Parameter height: The width of the button (optional, default is 48.0) 30 | /// - Parameter cornerRadius: The corner radius of the button (optional, default is 5.0) 31 | /// - Parameter tintColor: The background color of the button (optional, default is .bitcoinOrange) 32 | /// - Parameter textColor: The text color of the button (optional, default is .bitcoinWhite) 33 | /// - Parameter disabledFillColor: The disabled background color of the button (optional, default is .bitcoinNeutral2) 34 | /// - Parameter disabledTextColor: The disabled text color of the button (optional, default is .bitcoinNeutral5) 35 | /// 36 | public struct BitcoinFilled: ButtonStyle { 37 | @Environment(\.colorScheme) private var colorScheme 38 | @Environment(\.isEnabled) private var isEnabled 39 | 40 | let width: CGFloat 41 | let height: CGFloat 42 | let cornerRadius: CGFloat 43 | let tintColor: Color 44 | let textColor: Color 45 | let disabledFillColor: Color 46 | let disabledTextColor: Color 47 | let isCapsule: Bool 48 | 49 | public init( 50 | width: CGFloat = defaultButtonWidth, 51 | height: CGFloat = defaultButtonHeight, 52 | cornerRadius: CGFloat = defaultCornerRadius, 53 | tintColor: Color = defaultTintColor, 54 | textColor: Color = defaultTextColor, 55 | disabledFillColor: Color = defaultDisabledFillColor, 56 | disabledTextColor: Color = defaultDisabledTextColor, 57 | isCapsule: Bool = false 58 | ) { 59 | self.width = width 60 | self.height = height 61 | self.cornerRadius = cornerRadius 62 | self.tintColor = tintColor 63 | self.textColor = textColor 64 | self.disabledFillColor = disabledFillColor 65 | self.disabledTextColor = disabledTextColor 66 | self.isCapsule = isCapsule 67 | } 68 | 69 | public func makeBody(configuration: Configuration) -> some View { 70 | let stateBackgroundColor = stateBackgroundColor(configuration: configuration) 71 | configuration.label 72 | .font(Font.body.bold()) 73 | .padding() 74 | .frame(width: width, height: height) 75 | .background( 76 | stateBackgroundColor.opacity(0.8) 77 | .clipShape(CustomShape(isCapsule: isCapsule, cornerRadius: cornerRadius)) 78 | ) 79 | .foregroundColor(stateTextColor()) 80 | .scaleEffect(configuration.isPressed ? 0.95 : 1) 81 | .animation(.easeOut(duration: 0.1), value: configuration.isPressed) 82 | } 83 | 84 | private func stateBackgroundColor(configuration: Configuration) -> Color { 85 | return isEnabled 86 | ? configuration.isPressed ? tintColor.opacity(0.8) : tintColor : disabledFillColor 87 | } 88 | 89 | private func stateTextColor() -> Color { 90 | return isEnabled ? textColor : disabledTextColor 91 | } 92 | } 93 | 94 | /// A `ButtonStyle` corresponding to the Outline type in the Bitcoin Wallet UI Kit 95 | /// 96 | /// ```swift 97 | /// Button("Label") { 98 | /// print("Button pressed!") 99 | /// } 100 | ///.buttonStyle(BitcoinOutlined()) 101 | /// ``` 102 | /// - Parameter width: The width of the button (optional, default is 315.0) 103 | /// - Parameter height: The width of the button (optional, default is 48.0) 104 | /// - Parameter cornerRadius: The corner radius of the button (optional, default is 5.0) 105 | /// - Parameter tintColor: The border and text color of the button (optional, default is .bitcoinOrange) 106 | /// - Parameter disabledColor: The disabled color of the button (optional, default is .bitcoinNeutral4) 107 | /// 108 | public struct BitcoinOutlined: ButtonStyle { 109 | @Environment(\.colorScheme) private var colorScheme 110 | @Environment(\.isEnabled) private var isEnabled 111 | 112 | let width: CGFloat 113 | let height: CGFloat 114 | let cornerRadius: CGFloat 115 | let tintColor: Color 116 | let disabledColor: Color 117 | let isCapsule: Bool 118 | 119 | public init( 120 | width: CGFloat = defaultButtonWidth, 121 | height: CGFloat = defaultButtonHeight, 122 | cornerRadius: CGFloat = defaultCornerRadius, 123 | tintColor: Color = defaultTintColor, 124 | disabledColor: Color = defaultDisabledOutlineColor, 125 | isCapsule: Bool = false 126 | ) { 127 | self.width = width 128 | self.height = height 129 | self.cornerRadius = cornerRadius 130 | self.tintColor = tintColor 131 | self.disabledColor = disabledColor 132 | self.isCapsule = isCapsule 133 | } 134 | 135 | public func makeBody(configuration: Configuration) -> some View { 136 | configuration.label 137 | .font(Font.body.bold()) 138 | .padding() 139 | .frame(width: width, height: height) 140 | .background(stateBackgroundColor()) 141 | .clipShape(CustomShape(isCapsule: isCapsule, cornerRadius: cornerRadius)) // Use CustomShape 142 | .foregroundColor(stateTextColor()) 143 | .overlay( 144 | CustomShape(isCapsule: isCapsule, cornerRadius: cornerRadius) // Use CustomShape 145 | .stroke(stateBorderColor(configuration: configuration), lineWidth: 1.5) 146 | ) 147 | .scaleEffect(configuration.isPressed ? 0.95 : 1) 148 | .animation(.easeOut(duration: 0.1), value: configuration.isPressed) 149 | } 150 | 151 | private func stateBackgroundColor() -> Color { 152 | return colorScheme == .dark ? .bitcoinBlack : .bitcoinWhite 153 | } 154 | private func stateBorderColor(configuration: Configuration) -> Color { 155 | return isEnabled 156 | ? configuration.isPressed ? tintColor.opacity(0.8) : tintColor : disabledColor 157 | } 158 | private func stateTextColor() -> Color { 159 | return isEnabled ? tintColor : disabledColor 160 | } 161 | } 162 | 163 | /// A `ButtonStyle` corresponding to the Plain type in the Bitcoin Wallet UI Kit 164 | /// 165 | /// ```swift 166 | /// Button("Label") { 167 | /// print("Button pressed!") 168 | /// } 169 | ///.buttonStyle(BitcoinPlain()) 170 | /// ``` 171 | /// - Parameter width: The width of the button (optional, default is 315.0) 172 | /// - Parameter height: The width of the button (optional, default is 48.0) 173 | /// - Parameter tintColor: The text color of the button (optional, default is .bitcoinOrange) 174 | /// - Parameter disabledColor: The disabled text color of the button (optional, default is .bitcoinNeutral4) 175 | /// 176 | public struct BitcoinPlain: ButtonStyle { 177 | @Environment(\.colorScheme) private var colorScheme 178 | @Environment(\.isEnabled) private var isEnabled 179 | 180 | let width: CGFloat 181 | let height: CGFloat 182 | let tintColor: Color 183 | let disabledColor: Color 184 | 185 | public init( 186 | width: CGFloat = defaultButtonWidth, 187 | height: CGFloat = defaultButtonHeight, 188 | tintColor: Color = defaultTintColor, 189 | disabledColor: Color = defaultDisabledTextColor 190 | ) { 191 | self.width = width 192 | self.height = height 193 | self.tintColor = tintColor 194 | self.disabledColor = disabledColor 195 | } 196 | 197 | public func makeBody(configuration: Configuration) -> some View { 198 | configuration.label 199 | .font(Font.body.bold()) 200 | .padding() 201 | .frame(width: width, height: height) 202 | .background(Color.clear) 203 | .foregroundColor(stateTextColor()) 204 | .scaleEffect(configuration.isPressed ? 0.95 : 1) 205 | .animation(.easeOut(duration: 0.1), value: configuration.isPressed) 206 | } 207 | private func stateBorderColor(configuration: Configuration) -> Color { 208 | return isEnabled 209 | ? configuration.isPressed ? tintColor.opacity(0.8) : tintColor : disabledColor 210 | } 211 | private func stateTextColor() -> Color { 212 | return isEnabled ? tintColor : disabledColor 213 | } 214 | } 215 | 216 | private struct CustomShape: Shape { 217 | var isCapsule: Bool 218 | var cornerRadius: CGFloat 219 | 220 | func path(in rect: CGRect) -> Path { 221 | if isCapsule { 222 | return Capsule().path(in: rect) 223 | } else { 224 | return RoundedRectangle(cornerRadius: cornerRadius).path(in: rect) 225 | } 226 | } 227 | } 228 | 229 | struct ButtonStylesView: View { 230 | var body: some View { 231 | 232 | ZStack { 233 | Color(UIColor.systemBackground) 234 | 235 | VStack { 236 | 237 | Text("Button Styles") 238 | .underline() 239 | .font(.largeTitle) 240 | .fontWeight(.semibold) 241 | .padding(.horizontal, .wallet_grid_horizontal_10()) 242 | .padding(.vertical, .wallet_grid_vertical_20()) 243 | .padding(.top, .wallet_grid_vertical_20()) 244 | .padding(.top, .wallet_grid_vertical_20()) 245 | 246 | Spacer() 247 | 248 | Button("BitcoinFilled") {} 249 | .buttonStyle(BitcoinFilled()) 250 | .padding() 251 | 252 | Button("BitcoinFilled") {} 253 | .buttonStyle(BitcoinFilled(isCapsule: true)) 254 | .padding() 255 | 256 | Button("BitcoinPlain") {} 257 | .buttonStyle(BitcoinPlain()) 258 | .padding() 259 | 260 | Button("BitcoinOutlined") {} 261 | .buttonStyle(BitcoinOutlined()) 262 | .padding() 263 | 264 | Button("BitcoinOutlined") {} 265 | .buttonStyle(BitcoinOutlined(isCapsule: true)) 266 | .padding() 267 | 268 | Spacer() 269 | 270 | } 271 | .padding(.horizontal, .wallet_grid_horizontal_10()) 272 | .padding(.vertical, .wallet_grid_vertical_20()) 273 | 274 | } 275 | .edgesIgnoringSafeArea(.all) 276 | 277 | } 278 | } 279 | 280 | struct ButtonStylesView_Previews: PreviewProvider { 281 | static var previews: some View { 282 | ButtonStylesView() 283 | } 284 | } 285 | 286 | #Preview { 287 | ButtonStylesView() 288 | } 289 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Colors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Colors.swift 3 | // 4 | // 5 | // Created by Matthew Ramsden on 12/26/20. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension UIColor { 12 | 13 | public static var bitcoinOrange: UIColor { 14 | return UIColor { (traitCollection: UITraitCollection) -> UIColor in 15 | if traitCollection.userInterfaceStyle == .dark { 16 | return UIColor( 17 | red: 248 / 255, 18 | green: 155 / 255, 19 | blue: 42 / 255, 20 | alpha: 1 21 | ) 22 | } else { 23 | return UIColor( 24 | red: 247 / 255, 25 | green: 147 / 255, 26 | blue: 26 / 255, 27 | alpha: 1 28 | ) 29 | } 30 | } 31 | } 32 | 33 | public static var bitcoinRed: UIColor { 34 | return UIColor { (traitCollection: UITraitCollection) -> UIColor in 35 | if traitCollection.userInterfaceStyle == .dark { 36 | return UIColor( 37 | red: 236 / 255, 38 | green: 99 / 255, 39 | blue: 99 / 255, 40 | alpha: 1 41 | ) 42 | } else { 43 | return UIColor( 44 | red: 235 / 255, 45 | green: 87 / 255, 46 | blue: 87 / 255, 47 | alpha: 1 48 | ) 49 | } 50 | } 51 | } 52 | 53 | public static var bitcoinGreen: UIColor { 54 | return UIColor { (traitCollection: UITraitCollection) -> UIColor in 55 | if traitCollection.userInterfaceStyle == .dark { 56 | return UIColor( 57 | red: 54 / 255, 58 | green: 180 / 255, 59 | blue: 107 / 255, 60 | alpha: 1 61 | ) 62 | } else { 63 | return UIColor( 64 | red: 39 / 255, 65 | green: 174 / 255, 66 | blue: 96 / 255, 67 | alpha: 1 68 | ) 69 | } 70 | } 71 | } 72 | 73 | public static var bitcoinBlue: UIColor { 74 | return UIColor { (traitCollection: UITraitCollection) -> UIColor in 75 | if traitCollection.userInterfaceStyle == .dark { 76 | return UIColor( 77 | red: 60 / 255, 78 | green: 163 / 255, 79 | blue: 222 / 255, 80 | alpha: 1 81 | ) 82 | } else { 83 | return UIColor( 84 | red: 45 / 255, 85 | green: 156 / 255, 86 | blue: 219 / 255, 87 | alpha: 1 88 | ) 89 | } 90 | } 91 | } 92 | 93 | public static var bitcoinPurple: UIColor { 94 | return UIColor { (traitCollection: UITraitCollection) -> UIColor in 95 | if traitCollection.userInterfaceStyle == .dark { 96 | return UIColor( 97 | red: 192 / 255, 98 | green: 117 / 255, 99 | blue: 220 / 255, 100 | alpha: 1 101 | ) 102 | } else { 103 | return UIColor( 104 | red: 187 / 255, 105 | green: 107 / 255, 106 | blue: 217 / 255, 107 | alpha: 1 108 | ) 109 | } 110 | } 111 | } 112 | 113 | public static var bitcoinWhite: UIColor { 114 | return UIColor { (traitCollection: UITraitCollection) -> UIColor in 115 | if traitCollection.userInterfaceStyle == .dark { 116 | return UIColor(red: 1, green: 1, blue: 1, alpha: 1) 117 | } else { 118 | return UIColor(red: 1, green: 1, blue: 1, alpha: 1) 119 | } 120 | } 121 | } 122 | 123 | public static var bitcoinNeutral1: UIColor { 124 | return UIColor { (traitCollection: UITraitCollection) -> UIColor in 125 | if traitCollection.userInterfaceStyle == .dark { 126 | return UIColor( 127 | red: 26 / 255, 128 | green: 26 / 255, 129 | blue: 26 / 255, 130 | alpha: 1 131 | ) 132 | } else { 133 | return UIColor( 134 | red: 248 / 255, 135 | green: 248 / 255, 136 | blue: 248 / 255, 137 | alpha: 1 138 | ) 139 | } 140 | } 141 | } 142 | 143 | public static var bitcoinNeutral2: UIColor { 144 | return UIColor { (traitCollection: UITraitCollection) -> UIColor in 145 | if traitCollection.userInterfaceStyle == .dark { 146 | return UIColor( 147 | red: 45 / 255, 148 | green: 45 / 255, 149 | blue: 45 / 255, 150 | alpha: 1 151 | ) 152 | } else { 153 | return UIColor( 154 | red: 244 / 255, 155 | green: 244 / 255, 156 | blue: 244 / 255, 157 | alpha: 1 158 | ) 159 | } 160 | } 161 | } 162 | 163 | public static var bitcoinNeutral3: UIColor { 164 | return UIColor { (traitCollection: UITraitCollection) -> UIColor in 165 | if traitCollection.userInterfaceStyle == .dark { 166 | return UIColor( 167 | red: 68 / 255, 168 | green: 68 / 255, 169 | blue: 68 / 255, 170 | alpha: 1 171 | ) 172 | } else { 173 | return UIColor( 174 | red: 237 / 255, 175 | green: 237 / 255, 176 | blue: 237 / 255, 177 | alpha: 1 178 | ) 179 | } 180 | } 181 | } 182 | 183 | public static var bitcoinNeutral4: UIColor { 184 | return UIColor { (traitCollection: UITraitCollection) -> UIColor in 185 | if traitCollection.userInterfaceStyle == .dark { 186 | return UIColor( 187 | red: 92 / 255, 188 | green: 92 / 255, 189 | blue: 92 / 255, 190 | alpha: 1 191 | ) 192 | } else { 193 | return UIColor( 194 | red: 222 / 255, 195 | green: 222 / 255, 196 | blue: 222 / 255, 197 | alpha: 1 198 | ) 199 | } 200 | } 201 | } 202 | 203 | public static var bitcoinNeutral5: UIColor { 204 | return UIColor { (traitCollection: UITraitCollection) -> UIColor in 205 | if traitCollection.userInterfaceStyle == .dark { 206 | return UIColor( 207 | red: 120 / 255, 208 | green: 120 / 255, 209 | blue: 120 / 255, 210 | alpha: 1 211 | ) 212 | } else { 213 | return UIColor( 214 | red: 187 / 255, 215 | green: 187 / 255, 216 | blue: 187 / 255, 217 | alpha: 1 218 | ) 219 | } 220 | } 221 | } 222 | 223 | public static var bitcoinNeutral6: UIColor { 224 | return UIColor { (traitCollection: UITraitCollection) -> UIColor in 225 | if traitCollection.userInterfaceStyle == .dark { 226 | return UIColor( 227 | red: 148 / 255, 228 | green: 148 / 255, 229 | blue: 148 / 255, 230 | alpha: 1 231 | ) 232 | } else { 233 | return UIColor( 234 | red: 153 / 255, 235 | green: 153 / 255, 236 | blue: 153 / 255, 237 | alpha: 1 238 | ) 239 | } 240 | } 241 | } 242 | 243 | public static var bitcoinNeutral7: UIColor { 244 | return UIColor { (traitCollection: UITraitCollection) -> UIColor in 245 | if traitCollection.userInterfaceStyle == .dark { 246 | return UIColor( 247 | red: 176 / 255, 248 | green: 176 / 255, 249 | blue: 176 / 255, 250 | alpha: 1 251 | ) 252 | } else { 253 | return UIColor( 254 | red: 119 / 255, 255 | green: 119 / 255, 256 | blue: 119 / 255, 257 | alpha: 1 258 | ) 259 | } 260 | } 261 | } 262 | 263 | public static var bitcoinNeutral8: UIColor { 264 | return UIColor { (traitCollection: UITraitCollection) -> UIColor in 265 | if traitCollection.userInterfaceStyle == .dark { 266 | return UIColor( 267 | red: 204 / 255, 268 | green: 204 / 255, 269 | blue: 204 / 255, 270 | alpha: 1 271 | ) 272 | } else { 273 | return UIColor( 274 | red: 64 / 255, 275 | green: 64 / 255, 276 | blue: 64 / 255, 277 | alpha: 1 278 | ) 279 | } 280 | } 281 | } 282 | 283 | public static var bitcoinBlack: UIColor { 284 | return UIColor { (traitCollection: UITraitCollection) -> UIColor in 285 | if traitCollection.userInterfaceStyle == .dark { 286 | return UIColor(red: 0, green: 0, blue: 0, alpha: 1) 287 | } else { 288 | return UIColor(red: 0, green: 0, blue: 0, alpha: 1) 289 | } 290 | } 291 | } 292 | 293 | } 294 | 295 | extension Color { 296 | public static var bitcoinWhite: Color { 297 | return Color(.bitcoinWhite) 298 | } 299 | public static var bitcoinNeutral1: Color { 300 | return Color(.bitcoinNeutral1) 301 | } 302 | public static var bitcoinNeutral2: Color { 303 | return Color(.bitcoinNeutral2) 304 | } 305 | public static var bitcoinNeutral3: Color { 306 | return Color(.bitcoinNeutral3) 307 | } 308 | public static var bitcoinNeutral4: Color { 309 | return Color(.bitcoinNeutral4) 310 | } 311 | public static var bitcoinNeutral5: Color { 312 | return Color(.bitcoinNeutral5) 313 | } 314 | public static var bitcoinNeutral6: Color { 315 | return Color(.bitcoinNeutral6) 316 | } 317 | public static var bitcoinNeutral7: Color { 318 | return Color(.bitcoinNeutral7) 319 | } 320 | public static var bitcoinNeutral8: Color { 321 | return Color(.bitcoinNeutral8) 322 | } 323 | public static var bitcoinBlack: Color { 324 | return Color(.bitcoinBlack) 325 | } 326 | public static var bitcoinOrange: Color { 327 | return Color(.bitcoinOrange) 328 | } 329 | public static var bitcoinRed: Color { 330 | return Color(.bitcoinRed) 331 | } 332 | public static var bitcoinGreen: Color { 333 | return Color(.bitcoinGreen) 334 | } 335 | public static var bitcoinBlue: Color { 336 | return Color(.bitcoinBlue) 337 | } 338 | public static var bitcoinPurple: Color { 339 | return Color(.bitcoinPurple) 340 | } 341 | } 342 | 343 | /// Extend UIColor to add MyMattress specific colors 344 | 345 | extension UIColor { 346 | 347 | public static var bitcoinMyMattress: UIColor { 348 | return UIColor { (traitCollection: UITraitCollection) -> UIColor in 349 | if traitCollection.userInterfaceStyle == .dark { 350 | return UIColor( 351 | red: 28 / 255, 352 | green: 55 / 255, 353 | blue: 107 / 255, 354 | alpha: 1 355 | ) 356 | } else { 357 | return UIColor( 358 | red: 28 / 255, 359 | green: 55 / 255, 360 | blue: 107 / 255, 361 | alpha: 1 362 | ) 363 | } 364 | } 365 | } 366 | } 367 | 368 | extension Color { 369 | public static var bitcoinMyMattress: Color { 370 | return Color(.bitcoinMyMattress) 371 | } 372 | } 373 | 374 | struct ColorView: View { 375 | let color: Color 376 | let colorLabel: String 377 | 378 | var body: some View { 379 | VStack { 380 | Circle() 381 | .foregroundColor(color) 382 | Text(colorLabel) 383 | .font(.caption) 384 | .foregroundColor(.bitcoinNeutral5) 385 | .multilineTextAlignment(.center) 386 | } 387 | } 388 | 389 | } 390 | 391 | struct ColorsView: View { 392 | var body: some View { 393 | 394 | ZStack { 395 | Color(UIColor.systemBackground) 396 | 397 | VStack { 398 | 399 | Text("Colors") 400 | .underline() 401 | .font(.largeTitle) 402 | .fontWeight(.semibold) 403 | .padding(.horizontal, .wallet_grid_horizontal_10()) 404 | .padding(.vertical, .wallet_grid_vertical_20()) 405 | .padding(.top, .wallet_grid_vertical_20()) 406 | .padding(.top, .wallet_grid_vertical_20()) 407 | 408 | Spacer() 409 | 410 | HStack { 411 | ColorView(color: .bitcoinWhite, colorLabel: "White") 412 | ColorView(color: .bitcoinNeutral1, colorLabel: "Neutral1") 413 | ColorView(color: .bitcoinNeutral2, colorLabel: "Neutral2") 414 | ColorView(color: .bitcoinNeutral3, colorLabel: "Neutral3") 415 | ColorView(color: .bitcoinNeutral4, colorLabel: "Neutral4") 416 | } 417 | .padding(.horizontal, .wallet_grid_horizontal_10()) 418 | .padding(.vertical, .wallet_grid_vertical_20()) 419 | 420 | HStack { 421 | ColorView(color: .bitcoinNeutral5, colorLabel: "Neutral5") 422 | ColorView(color: .bitcoinNeutral6, colorLabel: "Neutral6") 423 | ColorView(color: .bitcoinNeutral7, colorLabel: "Neutral7") 424 | ColorView(color: .bitcoinNeutral8, colorLabel: "Neutral8") 425 | ColorView(color: .bitcoinBlack, colorLabel: "Black") 426 | } 427 | .padding(.horizontal, .wallet_grid_horizontal_10()) 428 | .padding(.vertical, .wallet_grid_vertical_20()) 429 | 430 | HStack { 431 | ColorView(color: .bitcoinOrange, colorLabel: "Orange") 432 | ColorView(color: .bitcoinRed, colorLabel: "Red") 433 | ColorView(color: .bitcoinGreen, colorLabel: "Green") 434 | ColorView(color: .bitcoinBlue, colorLabel: "Blue") 435 | ColorView(color: .bitcoinPurple, colorLabel: "Purple") 436 | } 437 | .padding(.horizontal, .wallet_grid_horizontal_10()) 438 | .padding(.vertical, .wallet_grid_vertical_20()) 439 | 440 | Spacer() 441 | 442 | } 443 | .padding(.horizontal, .wallet_grid_horizontal_10()) 444 | .padding(.vertical, .wallet_grid_vertical_20()) 445 | 446 | } 447 | .edgesIgnoringSafeArea(.all) 448 | 449 | } 450 | } 451 | 452 | struct ColorsView_Previews: PreviewProvider { 453 | static var previews: some View { 454 | ColorsView() 455 | } 456 | } 457 | 458 | #Preview { 459 | ColorsView() 460 | } 461 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Images.swift 3 | // 4 | // 5 | // Created by Matthew Ramsden on 12/26/20. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | /// A function that returns a Bitcoin Icon `Image` 12 | /// 13 | /// This is similar to `Image` but returns a Bitcoin specific `BitcoinImage` 14 | /// 15 | /// ```swift 16 | /// BitcoinImage(named: "coldcard") 17 | /// ``` 18 | /// - Parameter named: The name of the Bitcoin Icon you would like to return. 19 | /// - Returns: The specific Bitcoin `Image` 20 | public func BitcoinImage(named: String) -> Image { 21 | return Image(named, bundle: Bundle.module) 22 | } 23 | 24 | /// A function that returns a Bitcoin Icon `UIImage` 25 | /// 26 | /// This is similar to `UIImage` but returns a Bitcoin specific `BitcoinUIImage` 27 | /// 28 | /// ```swift 29 | /// BitcoinUIImage(named: "coldcard") 30 | /// ``` 31 | /// - Parameter named: The name of the Bitcoin Icon you would like to return. 32 | /// - Returns: The specific Bitcoin `UIImage` 33 | public func BitcoinUIImage(named: String) -> UIImage { 34 | guard let image = UIImage(named: named, in: .module, compatibleWith: nil) else { 35 | assertionFailure("*No BitcoinUI Image Found*") 36 | return UIImage() 37 | } 38 | return image 39 | } 40 | 41 | struct IconView: View { 42 | let named: String 43 | 44 | var body: some View { 45 | 46 | VStack { 47 | BitcoinImage(named: named) 48 | .resizable() 49 | .aspectRatio(contentMode: .fit) 50 | .foregroundColor(Color(UIColor.label)) 51 | Text(named) 52 | .font(.caption) 53 | .foregroundColor(.bitcoinNeutral5) 54 | .multilineTextAlignment(.center) 55 | } 56 | .frame(maxWidth: .infinity) 57 | 58 | } 59 | } 60 | 61 | struct IconsView: View { 62 | var body: some View { 63 | 64 | ZStack { 65 | Color(UIColor.systemBackground) 66 | 67 | VStack { 68 | Text("Images") 69 | .underline() 70 | .font(.largeTitle) 71 | .fontWeight(.semibold) 72 | .padding(.horizontal, .wallet_grid_horizontal_10()) 73 | .padding(.vertical, .wallet_grid_vertical_20()) 74 | .padding(.top, .wallet_grid_vertical_20()) 75 | .padding(.top, .wallet_grid_vertical_20()) 76 | 77 | HStack { 78 | IconView(named: "bitbox") 79 | IconView(named: "blockstream-jade") 80 | IconView(named: "cobo-vault") 81 | } 82 | .padding(.horizontal, .wallet_grid_horizontal_10()) 83 | .padding(.vertical, .wallet_grid_vertical_20()) 84 | 85 | HStack { 86 | IconView(named: "coldcard") 87 | IconView(named: "foundation-passport") 88 | IconView(named: "generic-hardware-wallet") 89 | } 90 | .padding(.horizontal, .wallet_grid_horizontal_10()) 91 | .padding(.vertical, .wallet_grid_vertical_20()) 92 | 93 | HStack { 94 | IconView(named: "keepkey") 95 | IconView(named: "ledger-nano") 96 | IconView(named: "opendime") 97 | } 98 | .padding(.horizontal, .wallet_grid_horizontal_10()) 99 | .padding(.vertical, .wallet_grid_vertical_20()) 100 | 101 | HStack { 102 | IconView(named: "seedplate") 103 | IconView(named: "trezor-model-t") 104 | IconView(named: "trezor-one") 105 | } 106 | .padding(.horizontal, .wallet_grid_horizontal_10()) 107 | .padding(.vertical, .wallet_grid_vertical_20()) 108 | 109 | } 110 | .padding(.horizontal, .wallet_grid_horizontal_10()) 111 | .padding(.vertical, .wallet_grid_vertical_20()) 112 | 113 | } 114 | .edgesIgnoringSafeArea(.all) 115 | 116 | } 117 | } 118 | 119 | struct IconsView_Previews: PreviewProvider { 120 | static var previews: some View { 121 | IconsView() 122 | } 123 | } 124 | 125 | #Preview { 126 | IconsView() 127 | } 128 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/bitbox.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "bitbox300.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/bitbox.imageset/bitbox300.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/blockstream-jade.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "jade300.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/blockstream-jade.imageset/jade300.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/cobo-vault.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "cobo300.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/cobo-vault.imageset/cobo300.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/coldcard.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "coldcard.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/coldcard.imageset/coldcard.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/foundation-passport.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "foundation300.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/foundation-passport.imageset/foundation300.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/generic-hardware-wallet.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "generic-hardware-wallet.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/generic-hardware-wallet.imageset/generic-hardware-wallet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/keepkey.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "keepkey300.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/keepkey.imageset/keepkey300.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/ledger-nano.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ledgernano300.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/ledger-nano.imageset/ledgernano300.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/opendime.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "opendime.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/opendime.imageset/opendime.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/satoshi-v2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "satoshi-v2.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/satoshi-v2.imageset/satoshi-v2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/seedplate.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "seedplate300.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/seedplate.imageset/seedplate300.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/trezor-model-t.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "trezort300.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/trezor-model-t.imageset/trezort300.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/trezor-one.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "trezorone300.svg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Images.xcassets/trezor-one.imageset/trezorone300.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/QRCodeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QRCodeView.swift 3 | // 4 | // 5 | // Created by Matthew Ramsden on 9/28/23. 6 | // 7 | 8 | import CoreImage.CIFilterBuiltins 9 | import SwiftUI 10 | 11 | public enum QRCodeType { 12 | case bitcoin(String) 13 | case lightning(String) 14 | case bip21(String) 15 | 16 | var qrString: String { 17 | switch self { 18 | case .bitcoin(let address): 19 | return "bitcoin:\(address)" 20 | case .lightning(let invoice): 21 | return "lightning:\(invoice)" 22 | case .bip21(let uri): 23 | return uri 24 | } 25 | } 26 | } 27 | 28 | public struct QRCodeView: View { 29 | @State private var viewState = CGSize.zero 30 | let screenBounds = UIScreen.main.bounds 31 | public var qrCodeType: QRCodeType 32 | 33 | public init(qrCodeType: QRCodeType) { 34 | self.qrCodeType = qrCodeType 35 | } 36 | 37 | public var body: some View { 38 | Image(uiImage: generateQRCode(from: qrCodeType.qrString)) 39 | .interpolation(.none) 40 | .resizable() 41 | .scaledToFit() 42 | .padding() 43 | .applyFidgetEffect(viewState: $viewState) 44 | .gesture(dragGesture()) 45 | } 46 | 47 | private func generateQRCode(from string: String) -> UIImage { 48 | let context = CIContext() 49 | let filter = CIFilter.qrCodeGenerator() 50 | let data = Data(string.utf8) 51 | filter.setValue(data, forKey: "inputMessage") 52 | 53 | if let outputImage = filter.outputImage { 54 | if let cgimg = context.createCGImage(outputImage, from: outputImage.extent) { 55 | return UIImage(cgImage: cgimg) 56 | } 57 | } 58 | return UIImage(systemName: "xmark.circle") ?? UIImage() 59 | } 60 | 61 | private func dragGesture() -> some Gesture { 62 | DragGesture() 63 | .onChanged(handleDragChanged(_:)) 64 | .onEnded(handleDragEnded(_:)) 65 | } 66 | 67 | private func handleDragChanged(_ value: DragGesture.Value) { 68 | let translation = value.translation 69 | let multiplier: CGFloat = 0.05 70 | viewState.width = -translation.width * multiplier 71 | viewState.height = -translation.height * multiplier 72 | } 73 | 74 | private func handleDragEnded(_ value: DragGesture.Value) { 75 | withAnimation { 76 | self.viewState = .zero 77 | } 78 | } 79 | } 80 | 81 | struct QRCodeView_Previews: PreviewProvider { 82 | static var previews: some View { 83 | QRCodeView(qrCodeType: .lightning("bitcoinqrcode")) 84 | QRCodeView(qrCodeType: .lightning("lightingqrcode")) 85 | QRCodeView( 86 | qrCodeType: 87 | .bip21( 88 | "BITCOIN:TB1Q05G8L05SGQSNZUC2MRMQ0CHLQ7KTXTCE4JS5DJ?amount=1.23456&message=Monday%20Wallet&lightning=LNTBS1234560U1PNF4QFUDQ4F4HKUERP0YS9WCTVD3JHGNP4Q235EGP5U2NEC2DDCZA8EYKPD3GL3G0ES0M7GNCARZEUWUW6XHPQQPP56Y3KWCPGAJUQW2FFE83XZMNECCX9TF0G7JE6PN9QHN2Y75SHG7YSSP5RACFNW06S4AVX77KEUC00G0LZVFEZHZ6L3Y9XK8SVS9N9ZM4KJWQ9QYYSGQCQPCXQRRSSRZJQDCADLTAWH0Z6QMJ6QL2QR5T4NDVK5XZ0582AG98DGRZ9ML37HHJKZK64UQQ99GQQUQQQQQQQQQQLGQQJQVHZP42NU240ZC9FZQZGL7NH08N6DGFJ9WM2WR4E2YYGEC82FMCNJ94M8K43XFU07GWA3LQDDSS23H96L2AH7SKVAG4MPGWPTLCTJA0CQML9APG&lno=LNO1QGS0V8HW8D368Q9YW7SX8TEJK2AUJLYLL8CP7TZZYH5H8XYPPQQQQQQYZPWJAT8PJQ8F5QK7D8DR77SRN4HQSPGUH6X3QQQ2P4XK7MNYV9UJQ4MPD3KX2AQSESPHR4HA046AUTGRWTGRAGQW3WKD4J6SCF7SAT4Q5A4QVGH0786772CZKDRW9SMJV0PPSU9HM2R7TMD96C098DSEJKALTYEWTQUTZQLD9XNQYQ7KR5TNWNX259ZA3YP043YXCDFGTZ88R9V67NLDZT7CJ6CZ2N257GQRXK2H9QJ96W8P49F5CDZVZNQEC4YTK2W97GTEST57SXKH2R7TKDL5PKDQYV4HS55NQMTLNTQMFK2RNZVRTNGRHFPA7DV2533PJ2Z5YA77D7DGTMV7DHZKN4TLTAR6LRL8RC3534ASQYPWZX48S6WXVKA95MP02UWG3LX6ZCSSY5NJGXA2V9MKG58JH88M4EPSTYDC4YTQU8W439S3F8GSA5T73PHU" 89 | ) 90 | ) 91 | } 92 | } 93 | 94 | #Preview { 95 | QRCodeView(qrCodeType: .bitcoin("bitcoinqrcode")) 96 | } 97 | #Preview { 98 | QRCodeView(qrCodeType: .lightning("lightingqrcode")) 99 | } 100 | #Preview { 101 | QRCodeView( 102 | qrCodeType: 103 | .bip21( 104 | "BITCOIN:TB1Q05G8L05SGQSNZUC2MRMQ0CHLQ7KTXTCE4JS5DJ?amount=1.23456&message=Monday%20Wallet&lightning=LNTBS1234560U1PNF4QFUDQ4F4HKUERP0YS9WCTVD3JHGNP4Q235EGP5U2NEC2DDCZA8EYKPD3GL3G0ES0M7GNCARZEUWUW6XHPQQPP56Y3KWCPGAJUQW2FFE83XZMNECCX9TF0G7JE6PN9QHN2Y75SHG7YSSP5RACFNW06S4AVX77KEUC00G0LZVFEZHZ6L3Y9XK8SVS9N9ZM4KJWQ9QYYSGQCQPCXQRRSSRZJQDCADLTAWH0Z6QMJ6QL2QR5T4NDVK5XZ0582AG98DGRZ9ML37HHJKZK64UQQ99GQQUQQQQQQQQQQLGQQJQVHZP42NU240ZC9FZQZGL7NH08N6DGFJ9WM2WR4E2YYGEC82FMCNJ94M8K43XFU07GWA3LQDDSS23H96L2AH7SKVAG4MPGWPTLCTJA0CQML9APG&lno=LNO1QGS0V8HW8D368Q9YW7SX8TEJK2AUJLYLL8CP7TZZYH5H8XYPPQQQQQQYZPWJAT8PJQ8F5QK7D8DR77SRN4HQSPGUH6X3QQQ2P4XK7MNYV9UJQ4MPD3KX2AQSESPHR4HA046AUTGRWTGRAGQW3WKD4J6SCF7SAT4Q5A4QVGH0786772CZKDRW9SMJV0PPSU9HM2R7TMD96C098DSEJKALTYEWTQUTZQLD9XNQYQ7KR5TNWNX259ZA3YP043YXCDFGTZ88R9V67NLDZT7CJ6CZ2N257GQRXK2H9QJ96W8P49F5CDZVZNQEC4YTK2W97GTEST57SXKH2R7TKDL5PKDQYV4HS55NQMTLNTQMFK2RNZVRTNGRHFPA7DV2533PJ2Z5YA77D7DGTMV7DHZKN4TLTAR6LRL8RC3534ASQYPWZX48S6WXVKA95MP02UWG3LX6ZCSSY5NJGXA2V9MKG58JH88M4EPSTYDC4YTQU8W439S3F8GSA5T73PHU" 105 | ) 106 | ) 107 | } 108 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/SeedPhraseView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SeedPhraseView.swift 3 | // 4 | // 5 | // Created by Matthew Ramsden on 6/14/24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public struct SeedPhraseView: View { 11 | public var words: [String] 12 | public var preferredWordsPerRow: Int 13 | public var usePaging: Bool 14 | public var wordsPerPage: Int 15 | @State private var currentPage = 0 16 | 17 | public init( 18 | words: [String], 19 | preferredWordsPerRow: Int, 20 | usePaging: Bool = false, 21 | wordsPerPage: Int = 6 22 | ) { 23 | self.words = words 24 | self.preferredWordsPerRow = preferredWordsPerRow 25 | self.usePaging = usePaging 26 | self.wordsPerPage = wordsPerPage 27 | } 28 | 29 | public var body: some View { 30 | let capsuleWidth: CGFloat = { 31 | switch preferredWordsPerRow { 32 | case 2: 33 | return 120 34 | case 3: 35 | return 100 36 | case 4: 37 | return 80 38 | default: 39 | return 100 40 | } 41 | }() 42 | 43 | if usePaging { 44 | let chunks = words.chunked(into: wordsPerPage) 45 | 46 | TabView(selection: $currentPage) { 47 | ForEach(0..(_ style: Style) -> some View { 13 | ModifiedContent(content: self, modifier: style) 14 | } 15 | } 16 | 17 | public struct BitcoinTitle1: ViewModifier { 18 | @Environment(\.colorScheme) var colorScheme 19 | public init() {} 20 | public func body(content: Content) -> some View { 21 | content 22 | .font(.system(size: 36, weight: .semibold)) 23 | .foregroundColor(colorScheme == .dark ? .bitcoinWhite : .bitcoinBlack) 24 | } 25 | } 26 | 27 | public struct BitcoinTitle2: ViewModifier { 28 | @Environment(\.colorScheme) var colorScheme 29 | public init() {} 30 | public func body(content: Content) -> some View { 31 | content 32 | .font(.system(size: 28, weight: .semibold)) 33 | .foregroundColor(colorScheme == .dark ? .bitcoinWhite : .bitcoinBlack) 34 | } 35 | } 36 | 37 | public struct BitcoinTitle3: ViewModifier { 38 | @Environment(\.colorScheme) var colorScheme 39 | public init() {} 40 | public func body(content: Content) -> some View { 41 | content 42 | .font(.system(size: 24, weight: .semibold)) 43 | .foregroundColor(colorScheme == .dark ? .bitcoinWhite : .bitcoinBlack) 44 | } 45 | } 46 | 47 | public struct BitcoinTitle4: ViewModifier { 48 | @Environment(\.colorScheme) var colorScheme 49 | public init() {} 50 | public func body(content: Content) -> some View { 51 | content 52 | .font(.system(size: 21, weight: .semibold)) 53 | .foregroundColor(colorScheme == .dark ? .bitcoinWhite : .bitcoinBlack) 54 | } 55 | } 56 | 57 | public struct BitcoinTitle5: ViewModifier { 58 | @Environment(\.colorScheme) var colorScheme 59 | public init() {} 60 | public func body(content: Content) -> some View { 61 | content 62 | .font(.system(size: 18, weight: .semibold)) 63 | .foregroundColor(colorScheme == .dark ? .bitcoinWhite : .bitcoinBlack) 64 | } 65 | } 66 | 67 | public struct BitcoinBody1: ViewModifier { 68 | @Environment(\.colorScheme) var colorScheme 69 | public init() {} 70 | public func body(content: Content) -> some View { 71 | content 72 | .font(.system(size: 24, weight: .regular)) 73 | .foregroundColor(colorScheme == .dark ? .bitcoinWhite : .bitcoinBlack) 74 | } 75 | } 76 | 77 | public struct BitcoinBody2: ViewModifier { 78 | @Environment(\.colorScheme) var colorScheme 79 | public init() {} 80 | public func body(content: Content) -> some View { 81 | content 82 | .font(.system(size: 21, weight: .regular)) 83 | .foregroundColor(colorScheme == .dark ? .bitcoinWhite : .bitcoinBlack) 84 | } 85 | } 86 | 87 | public struct BitcoinBody3: ViewModifier { 88 | @Environment(\.colorScheme) var colorScheme 89 | public init() {} 90 | public func body(content: Content) -> some View { 91 | content 92 | .font(.system(size: 18, weight: .regular)) 93 | .foregroundColor(colorScheme == .dark ? .bitcoinWhite : .bitcoinBlack) 94 | } 95 | } 96 | 97 | public struct BitcoinBody4: ViewModifier { 98 | @Environment(\.colorScheme) var colorScheme 99 | public init() {} 100 | public func body(content: Content) -> some View { 101 | content 102 | .font(.system(size: 15, weight: .regular)) 103 | .foregroundColor(colorScheme == .dark ? .bitcoinWhite : .bitcoinBlack) 104 | } 105 | } 106 | 107 | public struct BitcoinBody5: ViewModifier { 108 | @Environment(\.colorScheme) var colorScheme 109 | public init() {} 110 | public func body(content: Content) -> some View { 111 | content 112 | .font(.system(size: 13, weight: .regular)) 113 | .foregroundColor(colorScheme == .dark ? .bitcoinWhite : .bitcoinBlack) 114 | } 115 | } 116 | 117 | struct TextStylesView: View { 118 | var body: some View { 119 | 120 | ZStack { 121 | Color(UIColor.systemBackground) 122 | 123 | VStack { 124 | 125 | Text("Text Styles") 126 | .underline() 127 | .font(.largeTitle) 128 | .fontWeight(.semibold) 129 | .padding(.horizontal, .wallet_grid_horizontal_10()) 130 | .padding(.vertical, .wallet_grid_vertical_20()) 131 | .padding(.top, .wallet_grid_vertical_20()) 132 | .padding(.top, .wallet_grid_vertical_20()) 133 | 134 | Spacer() 135 | 136 | Text("Title") 137 | .textStyle(BitcoinTitle1()) 138 | .padding() 139 | 140 | Text("Body") 141 | .textStyle(BitcoinBody1()) 142 | .padding() 143 | 144 | Text("Body") 145 | .textStyle(BitcoinBody2()) 146 | .padding() 147 | 148 | Text("Body") 149 | .textStyle(BitcoinBody3()) 150 | .padding() 151 | 152 | Text("Body") 153 | .textStyle(BitcoinBody4()) 154 | .padding() 155 | 156 | Text("Body") 157 | .textStyle(BitcoinBody5()) 158 | .padding() 159 | 160 | Spacer() 161 | 162 | } 163 | .padding(.horizontal, .wallet_grid_horizontal_10()) 164 | .padding(.vertical, .wallet_grid_vertical_20()) 165 | 166 | } 167 | .edgesIgnoringSafeArea(.all) 168 | 169 | } 170 | } 171 | 172 | struct TextStylesView_Previews: PreviewProvider { 173 | static var previews: some View { 174 | TextStylesView() 175 | } 176 | } 177 | 178 | #Preview { 179 | TextStylesView() 180 | } 181 | -------------------------------------------------------------------------------- /Sources/BitcoinUI/Utilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utilities.swift 3 | // 4 | // 5 | // Created by Matthew Ramsden on 8/24/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension Array { 12 | func chunked(into size: Int) -> [[Element]] { 13 | stride(from: 0, to: count, by: size).map { 14 | Array(self[$0.. CGFloat { 23 | return CGFloat(1) * 10 24 | } 25 | 26 | // Choose vertical spacing per Figma 27 | static func wallet_grid_vertical_20() -> CGFloat { 28 | return CGFloat(1) * 20 29 | } 30 | 31 | } 32 | 33 | extension String { 34 | func chunked(into size: Int) -> [String] { 35 | return stride(from: 0, to: self.count, by: size).map { 36 | let start = self.index(self.startIndex, offsetBy: $0) 37 | let end = self.index(start, offsetBy: size, limitedBy: self.endIndex) ?? self.endIndex 38 | return String(self[start..) -> some View { 45 | self 46 | .offset(x: -viewState.wrappedValue.width, y: -viewState.wrappedValue.height) 47 | .rotation3DEffect( 48 | .degrees(viewState.wrappedValue.width), 49 | axis: (x: 0, y: 1, z: 0) 50 | ) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Tests/BitcoinUITests/BitcoinUITests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | @testable import BitcoinUI 4 | 5 | final class BitcoinUITests: XCTestCase { 6 | func testExample() { 7 | // This is an example of a functional test case. 8 | // Use XCTAssert and related functions to verify your tests produce the correct 9 | // results. 10 | //XCTAssertEqual(BitcoinUI().text, "Hello, World!") 11 | } 12 | 13 | static var allTests = [ 14 | ("testExample", testExample) 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /Tests/BitcoinUITests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(BitcoinUITests.allTests) 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import BitcoinUITests 2 | import XCTest 3 | 4 | var tests = [XCTestCaseEntry]() 5 | tests += BitcoinUITests.allTests() 6 | XCTMain(tests) 7 | --------------------------------------------------------------------------------