├── .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 | 
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 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
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 | 
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------