├── .github
├── FUNDING.yml
└── workflows
│ └── build.yml
├── .gitignore
├── .ruby-version
├── Gemfile
├── Gemfile.lock
├── Icon.png
├── LICENSE
├── README.md
├── SECURITY.md
├── Settings.bundle
├── Acknowledgements.plist
├── Root.plist
└── en.lproj
│ └── Root.strings
├── SwiftLeeds.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
└── xcshareddata
│ └── xcschemes
│ ├── SwiftLeeds.xcscheme
│ └── SwiftLeedsAppClip.xcscheme
├── SwiftLeeds
├── App
│ ├── AppState.swift
│ ├── Constants.swift
│ ├── Info.plist
│ └── SwiftLeedsApp.swift
├── Data
│ └── Model
│ │ ├── Activity.swift
│ │ ├── Local.swift
│ │ ├── Presentation.swift
│ │ ├── Schedule.swift
│ │ ├── Speaker.swift
│ │ └── Sponsor.swift
├── Extension
│ ├── Calendar.swift
│ ├── Color.swift
│ ├── Date.swift
│ ├── LinearGradient.swift
│ ├── String.swift
│ └── View+MeasureSize.swift
├── Network
│ ├── Endpoints.swift
│ ├── HttpMethod.swift
│ ├── Request.swift
│ ├── Requests.swift
│ └── URLSession.swift
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Push
│ ├── AppDelegate+Push.swift
│ └── TokenDetails.swift
├── Resources
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── AppIcon.png
│ │ │ └── Contents.json
│ │ ├── CarriageworksTheatre.imageset
│ │ │ ├── CarriageworksTheatre.jpg
│ │ │ └── Contents.json
│ │ ├── Clock.imageset
│ │ │ ├── Clock.pdf
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── Icon.imageset
│ │ │ ├── Contents.json
│ │ │ ├── Icon-Dark.pdf
│ │ │ └── Icon.pdf
│ │ ├── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchScreen-Dark.png
│ │ │ ├── LaunchScreen-Dark@2x.png
│ │ │ ├── LaunchScreen-Dark@3x.png
│ │ │ ├── LaunchScreen.png
│ │ │ ├── LaunchScreen@2x.png
│ │ │ └── LaunchScreen@3x.png
│ │ ├── LeedsPlayhouse.imageset
│ │ │ ├── Contents.json
│ │ │ └── LeedsPlayhouse.jpeg
│ │ ├── SwiftLeedsIcon.imageset
│ │ │ ├── Contents.json
│ │ │ └── SwiftLeedsIcon.pdf
│ │ └── wineglass.fill.symbolset
│ │ │ ├── Contents.json
│ │ │ └── wineglass.fill.svg
│ └── Colors.xcassets
│ │ ├── AccentColor.colorset
│ │ └── Contents.json
│ │ ├── Background.colorset
│ │ └── Contents.json
│ │ ├── CellBackground.colorset
│ │ └── Contents.json
│ │ ├── CellBorder.colorset
│ │ └── Contents.json
│ │ ├── CellForeground.colorset
│ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── Gradients
│ │ ├── BuyTicketGradientEnd.colorset
│ │ │ └── Contents.json
│ │ ├── BuyTicketGradientStart.colorset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ │ ├── LaunchScreenBackground.colorset
│ │ └── Contents.json
│ │ ├── ListBackground.colorset
│ │ └── Contents.json
│ │ └── TabBarBackground.colorset
│ │ └── Contents.json
├── Style
│ └── SquishyButtonStyle.swift
├── SwiftLeeds.entitlements
└── Views
│ ├── About
│ └── AboutView.swift
│ ├── Common
│ ├── Helper.swift
│ ├── SectionHeader.swift
│ └── SwiftLeedsContainer.swift
│ ├── Components
│ ├── CommonTileButton.swift
│ ├── CommonTileView.swift
│ ├── FancyHeaderView.swift
│ ├── HeaderView.swift
│ ├── StackedTileView.swift
│ └── WebView.swift
│ ├── Local
│ ├── BottomSheetView.swift
│ ├── LocalCell.swift
│ ├── LocalView.swift
│ └── LocalViewModel.swift
│ ├── My Conference
│ ├── ActivityView.swift
│ ├── AnnouncementCell.swift
│ ├── MyConferenceView.swift
│ ├── MyConferenceViewModel.swift
│ ├── ScheduleView.swift
│ ├── SpeakerView.swift
│ └── TalkCell.swift
│ ├── Sponsors
│ ├── SponsorTileView.swift
│ ├── SponsorsView.swift
│ └── SponsorsViewModel.swift
│ └── Tab
│ ├── SidebarMainView.swift
│ ├── SidebarView.swift
│ ├── Tabs.swift
│ └── TabsMainView.swift
├── SwiftLeedsAppClip
├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
├── ContentView.swift
├── Info.plist
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── SwiftLeedsAppClip.entitlements
└── SwiftLeedsAppClipApp.swift
├── SwiftLeedsTests
└── SwiftLeedsTests.swift
├── SwiftLeedsUITests
├── SwiftLeedsUITests.swift
└── SwiftLeedsUITestsLaunchTests.swift
├── SwiftLeedsWidget
├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── AppIcon.png
│ │ └── Contents.json
│ ├── Contents.json
│ └── WidgetBackground.colorset
│ │ └── Contents.json
├── Info.plist
├── SwiftLeedsMediumWidgetView.swift
├── SwiftLeedsSmallWidgetView.swift
├── SwiftLeedsWidget.swift
├── SwiftLeedsWidgetEntryView.swift
└── WidgetSetup
│ ├── SwiftLeedsWidgetEntry.swift
│ ├── TimeineProvider.swift
│ └── WidgetConstants.swift
├── SwiftLeedsWidgetExtension.entitlements
├── appClipCode.svg
├── fastlane
├── Appfile
├── Fastfile
└── README.md
└── media
└── swift-leeds-logo.png
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: SwiftLeeds
4 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: iOS CI Build Workflow
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | build:
11 | name: Build SwiftLeeds (DEBUG)
12 | runs-on: macos-13
13 | steps:
14 | - name: Checkout iOS Repository
15 | uses: actions/checkout@v2
16 |
17 | - name: Build iOS Project via Fastlane
18 | uses: maierj/fastlane-action@v2.2.1
19 | with:
20 | lane: build_debug
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## Obj-C/Swift specific
9 | *.hmap
10 |
11 | ## App packaging
12 | *.ipa
13 | *.dSYM.zip
14 | *.dSYM
15 |
16 | ## Playgrounds
17 | timeline.xctimeline
18 | playground.xcworkspace
19 |
20 | # Swift Package Manager
21 | #
22 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
23 | # Packages/
24 | # Package.pins
25 | # Package.resolved
26 | # *.xcodeproj
27 | #
28 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
29 | # hence it is not needed unless you have added a package configuration file to your project
30 | # .swiftpm
31 |
32 | .build/
33 | *.DS_Store
34 |
35 | # CocoaPods
36 | #
37 | # We recommend against adding the Pods directory to your .gitignore. However
38 | # you should judge for yourself, the pros and cons are mentioned at:
39 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
40 | #
41 | # Pods/
42 | #
43 | # Add this line if you want to avoid checking in source code from the Xcode workspace
44 | # *.xcworkspace
45 |
46 | # Carthage
47 | #
48 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
49 | # Carthage/Checkouts
50 |
51 | Carthage/Build/
52 |
53 | # Accio dependency management
54 | Dependencies/
55 | .accio/
56 |
57 | # fastlane
58 | #
59 | # It is recommended to not store the screenshots in the git repo.
60 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
61 | # For more information about the recommended setup visit:
62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
63 |
64 | fastlane/report.xml
65 | fastlane/Preview.html
66 | fastlane/screenshots/**/*.png
67 | fastlane/test_output
68 |
69 | # Code Injection
70 | #
71 | # After new code Injection tools there's a generated folder /iOSInjectionProject
72 | # https://github.com/johnno1962/injectionforxcode
73 |
74 | iOSInjectionProject/
75 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 3.0.4
2 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem 'fastlane', '2.206.2'
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | CFPropertyList (3.0.5)
5 | rexml
6 | addressable (2.8.0)
7 | public_suffix (>= 2.0.2, < 5.0)
8 | artifactory (3.0.15)
9 | atomos (0.1.3)
10 | aws-eventstream (1.2.0)
11 | aws-partitions (1.601.0)
12 | aws-sdk-core (3.131.2)
13 | aws-eventstream (~> 1, >= 1.0.2)
14 | aws-partitions (~> 1, >= 1.525.0)
15 | aws-sigv4 (~> 1.1)
16 | jmespath (~> 1, >= 1.6.1)
17 | aws-sdk-kms (1.57.0)
18 | aws-sdk-core (~> 3, >= 3.127.0)
19 | aws-sigv4 (~> 1.1)
20 | aws-sdk-s3 (1.114.0)
21 | aws-sdk-core (~> 3, >= 3.127.0)
22 | aws-sdk-kms (~> 1)
23 | aws-sigv4 (~> 1.4)
24 | aws-sigv4 (1.5.0)
25 | aws-eventstream (~> 1, >= 1.0.2)
26 | babosa (1.0.4)
27 | claide (1.1.0)
28 | colored (1.2)
29 | colored2 (3.1.2)
30 | commander (4.6.0)
31 | highline (~> 2.0.0)
32 | declarative (0.0.20)
33 | digest-crc (0.6.4)
34 | rake (>= 12.0.0, < 14.0.0)
35 | domain_name (0.5.20190701)
36 | unf (>= 0.0.5, < 1.0.0)
37 | dotenv (2.7.6)
38 | emoji_regex (3.2.3)
39 | excon (0.92.3)
40 | faraday (1.10.0)
41 | faraday-em_http (~> 1.0)
42 | faraday-em_synchrony (~> 1.0)
43 | faraday-excon (~> 1.1)
44 | faraday-httpclient (~> 1.0)
45 | faraday-multipart (~> 1.0)
46 | faraday-net_http (~> 1.0)
47 | faraday-net_http_persistent (~> 1.0)
48 | faraday-patron (~> 1.0)
49 | faraday-rack (~> 1.0)
50 | faraday-retry (~> 1.0)
51 | ruby2_keywords (>= 0.0.4)
52 | faraday-cookie_jar (0.0.7)
53 | faraday (>= 0.8.0)
54 | http-cookie (~> 1.0.0)
55 | faraday-em_http (1.0.0)
56 | faraday-em_synchrony (1.0.0)
57 | faraday-excon (1.1.0)
58 | faraday-httpclient (1.0.1)
59 | faraday-multipart (1.0.4)
60 | multipart-post (~> 2)
61 | faraday-net_http (1.0.1)
62 | faraday-net_http_persistent (1.2.0)
63 | faraday-patron (1.0.0)
64 | faraday-rack (1.0.0)
65 | faraday-retry (1.0.3)
66 | faraday_middleware (1.2.0)
67 | faraday (~> 1.0)
68 | fastimage (2.2.6)
69 | fastlane (2.206.2)
70 | CFPropertyList (>= 2.3, < 4.0.0)
71 | addressable (>= 2.8, < 3.0.0)
72 | artifactory (~> 3.0)
73 | aws-sdk-s3 (~> 1.0)
74 | babosa (>= 1.0.3, < 2.0.0)
75 | bundler (>= 1.12.0, < 3.0.0)
76 | colored
77 | commander (~> 4.6)
78 | dotenv (>= 2.1.1, < 3.0.0)
79 | emoji_regex (>= 0.1, < 4.0)
80 | excon (>= 0.71.0, < 1.0.0)
81 | faraday (~> 1.0)
82 | faraday-cookie_jar (~> 0.0.6)
83 | faraday_middleware (~> 1.0)
84 | fastimage (>= 2.1.0, < 3.0.0)
85 | gh_inspector (>= 1.1.2, < 2.0.0)
86 | google-apis-androidpublisher_v3 (~> 0.3)
87 | google-apis-playcustomapp_v1 (~> 0.1)
88 | google-cloud-storage (~> 1.31)
89 | highline (~> 2.0)
90 | json (< 3.0.0)
91 | jwt (>= 2.1.0, < 3)
92 | mini_magick (>= 4.9.4, < 5.0.0)
93 | multipart-post (~> 2.0.0)
94 | naturally (~> 2.2)
95 | optparse (~> 0.1.1)
96 | plist (>= 3.1.0, < 4.0.0)
97 | rubyzip (>= 2.0.0, < 3.0.0)
98 | security (= 0.1.3)
99 | simctl (~> 1.6.3)
100 | terminal-notifier (>= 2.0.0, < 3.0.0)
101 | terminal-table (>= 1.4.5, < 2.0.0)
102 | tty-screen (>= 0.6.3, < 1.0.0)
103 | tty-spinner (>= 0.8.0, < 1.0.0)
104 | word_wrap (~> 1.0.0)
105 | xcodeproj (>= 1.13.0, < 2.0.0)
106 | xcpretty (~> 0.3.0)
107 | xcpretty-travis-formatter (>= 0.0.3)
108 | gh_inspector (1.1.3)
109 | google-apis-androidpublisher_v3 (0.23.0)
110 | google-apis-core (>= 0.6, < 2.a)
111 | google-apis-core (0.6.0)
112 | addressable (~> 2.5, >= 2.5.1)
113 | googleauth (>= 0.16.2, < 2.a)
114 | httpclient (>= 2.8.1, < 3.a)
115 | mini_mime (~> 1.0)
116 | representable (~> 3.0)
117 | retriable (>= 2.0, < 4.a)
118 | rexml
119 | webrick
120 | google-apis-iamcredentials_v1 (0.12.0)
121 | google-apis-core (>= 0.6, < 2.a)
122 | google-apis-playcustomapp_v1 (0.9.0)
123 | google-apis-core (>= 0.6, < 2.a)
124 | google-apis-storage_v1 (0.16.0)
125 | google-apis-core (>= 0.6, < 2.a)
126 | google-cloud-core (1.6.0)
127 | google-cloud-env (~> 1.0)
128 | google-cloud-errors (~> 1.0)
129 | google-cloud-env (1.6.0)
130 | faraday (>= 0.17.3, < 3.0)
131 | google-cloud-errors (1.2.0)
132 | google-cloud-storage (1.36.2)
133 | addressable (~> 2.8)
134 | digest-crc (~> 0.4)
135 | google-apis-iamcredentials_v1 (~> 0.1)
136 | google-apis-storage_v1 (~> 0.1)
137 | google-cloud-core (~> 1.6)
138 | googleauth (>= 0.16.2, < 2.a)
139 | mini_mime (~> 1.0)
140 | googleauth (1.2.0)
141 | faraday (>= 0.17.3, < 3.a)
142 | jwt (>= 1.4, < 3.0)
143 | memoist (~> 0.16)
144 | multi_json (~> 1.11)
145 | os (>= 0.9, < 2.0)
146 | signet (>= 0.16, < 2.a)
147 | highline (2.0.3)
148 | http-cookie (1.0.5)
149 | domain_name (~> 0.5)
150 | httpclient (2.8.3)
151 | jmespath (1.6.1)
152 | json (2.6.2)
153 | jwt (2.4.1)
154 | memoist (0.16.2)
155 | mini_magick (4.11.0)
156 | mini_mime (1.1.2)
157 | multi_json (1.15.0)
158 | multipart-post (2.0.0)
159 | nanaimo (0.3.0)
160 | naturally (2.2.1)
161 | optparse (0.1.1)
162 | os (1.1.4)
163 | plist (3.6.0)
164 | public_suffix (4.0.7)
165 | rake (13.0.6)
166 | representable (3.2.0)
167 | declarative (< 0.1.0)
168 | trailblazer-option (>= 0.1.1, < 0.2.0)
169 | uber (< 0.2.0)
170 | retriable (3.1.2)
171 | rexml (3.2.5)
172 | rouge (2.0.7)
173 | ruby2_keywords (0.0.5)
174 | rubyzip (2.3.2)
175 | security (0.1.3)
176 | signet (0.17.0)
177 | addressable (~> 2.8)
178 | faraday (>= 0.17.5, < 3.a)
179 | jwt (>= 1.5, < 3.0)
180 | multi_json (~> 1.10)
181 | simctl (1.6.8)
182 | CFPropertyList
183 | naturally
184 | terminal-notifier (2.0.0)
185 | terminal-table (1.8.0)
186 | unicode-display_width (~> 1.1, >= 1.1.1)
187 | trailblazer-option (0.1.2)
188 | tty-cursor (0.7.1)
189 | tty-screen (0.8.1)
190 | tty-spinner (0.9.3)
191 | tty-cursor (~> 0.7)
192 | uber (0.1.0)
193 | unf (0.1.4)
194 | unf_ext
195 | unf_ext (0.0.8.2)
196 | unicode-display_width (1.8.0)
197 | webrick (1.7.0)
198 | word_wrap (1.0.0)
199 | xcodeproj (1.22.0)
200 | CFPropertyList (>= 2.3.3, < 4.0)
201 | atomos (~> 0.1.3)
202 | claide (>= 1.0.2, < 2.0)
203 | colored2 (~> 3.1)
204 | nanaimo (~> 0.3.0)
205 | rexml (~> 3.2.4)
206 | xcpretty (0.3.0)
207 | rouge (~> 2.0.7)
208 | xcpretty-travis-formatter (1.0.1)
209 | xcpretty (~> 0.2, >= 0.0.7)
210 |
211 | PLATFORMS
212 | x86_64-darwin-21
213 |
214 | DEPENDENCIES
215 | fastlane (= 2.206.2)
216 |
217 | BUNDLED WITH
218 | 2.2.33
219 |
--------------------------------------------------------------------------------
/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftLeeds/swiftleeds-ios/3961b7a7ec88cdfdc34d520864ffad08ef1c1336/Icon.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 SwiftLeeds
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 |
3 | 
4 | 
5 | 
6 | [](https://opensource.org/licenses/MIT)
7 |
8 |
9 | ## Introduction 👋🏼
10 |
11 | [SwiftLeeds](https://swiftleeds.co.uk) is a brand new Swift conference that started in 2021. It's a truly unique conference that is built by the community for the community. Hosted in the heart of Leeds City, UK.
12 |
13 | ## Developer Setup 💻
14 |
15 | Follow these steps to get your development environment setup to run SwiftLeeds iOS app locally.
16 |
17 | ### Prerequisites
18 |
19 | You'll need the following installed to get started:
20 |
21 | ```
22 | Xcode 14.3.1
23 | ```
24 |
25 | #### Xcode
26 |
27 | Xcode 14.0 can be downloaded directly from the [Apple Developer Downloads Page](https://download.developer.apple.com/Developer_Tools/Xcode_14.3.1/Xcode_14.3.1.xip).
28 |
29 | _You will need to have access to your developer account in order to download this._
30 |
31 | - Once downloaded and unzipped, move the app to your Applications folder and run it.
32 | - Accept the prompt to install additional tools to allow it to run until you see the Xcode welcome pane with the new project button.
33 |
34 | ## Contributing 🏗
35 |
36 | We welcome all contributions to this repository. Please raise a PR so our Lead Maintainer (Matthew Gallagher) can help get this pushed through, alternatively please raise an Issue or Discussion topic.
37 |
38 | ### Branch Stratergy
39 |
40 | We branch off the *main* branch into a *Feature branch* and then generate a PR to merge the changes back into *main*. We do not merge from any branch other than *main*.
41 |
42 |
43 | ## Contributors
44 |
45 | ### Active Contributors
46 | - [Matthew Gallagher](https://github.com/pdamonkey)
47 | - [Adam Rush](https://github.com/adamrushy)
48 | - [Karim Ebrahem](https://github.com/KarimEbrahemAbdelaziz)
49 | - [Lucky Agarwal](https://github.com/luckyagarwal)
50 | - [Muralidharan Kathiresan](https://github.com/kmuralidharan91)
51 |
52 | ### Previous Contributors
53 | - [Alex Logan](https://github.com/SwiftyAlex)
54 | - [Kannan Prasad](https://github.com/kannanprasad87)
55 |
56 | Thanks to all the effors from our App Contributors:
57 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Use this section to tell people about which versions of your project are
6 | currently being supported with security updates.
7 |
8 | | Version | Supported |
9 | | ------- | ------------------ |
10 | | 5.1.x | :white_check_mark: |
11 | | 5.0.x | :x: |
12 | | 4.0.x | :white_check_mark: |
13 | | < 4.0 | :x: |
14 |
15 | ## Reporting a Vulnerability
16 |
17 | Use this section to tell people how to report a vulnerability.
18 |
19 | Tell them where to go, how often they can expect to get an update on a
20 | reported vulnerability, what to expect if the vulnerability is accepted or
21 | declined, etc.
22 |
--------------------------------------------------------------------------------
/Settings.bundle/Acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | MIT License
10 |
11 | Copyright (c) 2021 Lorenzo Fiamingo
12 |
13 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18 | License
19 | MIT
20 | Title
21 | SwiftUI CachedAsyncImage
22 | Type
23 | PSGroupSpecifier
24 |
25 |
26 | StringsTable
27 | Acknowledgements
28 | Title
29 | Acknowledgements
30 |
31 |
32 |
--------------------------------------------------------------------------------
/Settings.bundle/Root.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | StringsTable
6 | Root
7 | PreferenceSpecifiers
8 |
9 |
10 | Type
11 | PSChildPaneSpecifier
12 | Title
13 | ACKNOWLEDGEMENTS
14 | File
15 | Acknowledgements
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Settings.bundle/en.lproj/Root.strings:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftLeeds/swiftleeds-ios/3961b7a7ec88cdfdc34d520864ffad08ef1c1336/Settings.bundle/en.lproj/Root.strings
--------------------------------------------------------------------------------
/SwiftLeeds.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SwiftLeeds.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SwiftLeeds.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "readabilitymodifier",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/yazio/ReadabilityModifier",
7 | "state" : {
8 | "revision" : "ce162150a090d5ae54a682d1f6be3862a3ad3ad4",
9 | "version" : "1.0.0"
10 | }
11 | },
12 | {
13 | "identity" : "swiftui-cached-async-image",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/lorenzofiamingo/swiftui-cached-async-image",
16 | "state" : {
17 | "revision" : "467a3d17479887943ab917a379e62bbaff60ac8a",
18 | "version" : "2.1.1"
19 | }
20 | }
21 | ],
22 | "version" : 2
23 | }
24 |
--------------------------------------------------------------------------------
/SwiftLeeds.xcodeproj/xcshareddata/xcschemes/SwiftLeeds.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/SwiftLeeds.xcodeproj/xcshareddata/xcschemes/SwiftLeedsAppClip.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/SwiftLeeds/App/AppState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppState.swift
3 | // SwiftLeeds
4 | //
5 | // Created by karim ebrahim on 25/07/2023.
6 | //
7 |
8 | import Foundation
9 | enum TabItems: Int {
10 | case conference, location, about, sponsors
11 | }
12 |
13 | final class AppState: ObservableObject {
14 | var selectedTab: TabItems = .conference
15 | }
16 |
--------------------------------------------------------------------------------
/SwiftLeeds/App/Constants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Constants.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 29/06/2022.
6 | //
7 |
8 | import CoreGraphics
9 |
10 | enum Constants {
11 | static let cellRadius: CGFloat = 12
12 | static let compactCellMinimumHeight: CGFloat = 53
13 | static let cellMinimumHeight: CGFloat = 65
14 | static let bottomSheetRadius: CGFloat = 30
15 | static let minHeightRatio: CGFloat = 0.3
16 | static let maxHeightRatio: CGFloat = 0.5
17 | static let snapRatio: CGFloat = 0.25
18 | }
19 |
20 | enum Padding {
21 | static let screen: CGFloat = 16
22 | static let cell: CGFloat = 12
23 | static let cellGap: CGFloat = 16
24 | static let stackGap: CGFloat = 4
25 | }
26 |
27 | enum Strings {
28 | static let aboutSwiftLeeds = """
29 | Adam Rush founded SwiftLeeds in 2019, born from over ten years of experience attending conferences. The inspiration was bringing a modern, inclusive conference in the North of the UK to be more accessible for all.
30 |
31 | SwiftLeeds is now run with over ten community volunteers building the website, iOS applications and making sure we cover all the bases on the day. SwiftLeeds is entirely non-profit, and the funds make sure we can deliver the best experience possible.
32 |
33 | In-person conferences are the best way to meet like-minded people who enjoy building apps with Swift. You can also learn from the best people in the industry and chat about all things Swift.
34 | """
35 | static let aboutContributor = """
36 | SwiftLeeds is a conference for the community, by the community. Here's the people who helped to bring you the conference this year.
37 | """
38 | }
39 |
40 | enum Assets {
41 | enum Image {
42 | static let carriageworksTheatre = "CarriageworksTheatre"
43 | static let leedsPlayhouse = "LeedsPlayhouse"
44 | static let swiftLeedsIcon = "SwiftLeedsIcon"
45 | static let swiftLeedsIconWithNoBackground = "Icon"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/SwiftLeeds/App/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UILaunchScreen
6 |
7 | UIColorName
8 | LaunchScreenBackground
9 | UIImageName
10 | LaunchImage
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/SwiftLeeds/App/SwiftLeedsApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftLeedsApp.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 14/11/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct SwiftLeedsApp: App {
12 | @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
13 |
14 | @StateObject private var appState = AppState()
15 |
16 | var body: some Scene {
17 | WindowGroup {
18 | Tabs()
19 | .environmentObject(appState)
20 | .onContinueUserActivity(NSUserActivityTypeBrowsingWeb, perform: handleUserActivity)
21 | }
22 | }
23 | }
24 |
25 | // MARK: - AppDelegate
26 | final class AppDelegate: NSObject, UIApplicationDelegate {
27 | static let pushURL: String = "https://www.swiftleeds.co.uk/push"
28 |
29 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
30 | URLCache.shared.diskCapacity = 100_000_000
31 |
32 | UITabBar.appearance().backgroundColor = UIColor(named: "TabBarBackground")
33 |
34 | requestPushAuthorization(application: application)
35 |
36 | return true
37 | }
38 |
39 | func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
40 | guard let url = URL(string: Self.pushURL) else { print("⛔️ Invalid push URL"); return }
41 | sendPushRegistrationDatails(to: url, deviceToken: deviceToken)
42 | }
43 |
44 | func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
45 | print("⛔️ Push registration failed:", error)
46 | handleFailedRegistration(application: application, error: error)
47 | }
48 | }
49 |
50 | private extension SwiftLeedsApp {
51 | func handleUserActivity(_ userActivity: NSUserActivity) {
52 | guard let incomingURL = userActivity.webpageURL, let components = URLComponents(
53 | url: incomingURL, resolvingAgainstBaseURL: true), let queryItems = components.queryItems
54 | else { return }
55 | print(queryItems)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/SwiftLeeds/Data/Model/Activity.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Activity.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 31/08/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | // MARK: - Activity
11 | struct Activity: Codable, Identifiable {
12 | let id: UUID
13 | let title: String
14 | let subtitle: String?
15 | let description: String?
16 | let image: String?
17 | let metadataURL: String?
18 | }
19 |
20 | // MARK: - Static Data
21 | extension Activity {
22 | static let lunch = Activity(id: UUID(),
23 | title: "Lunch 🍕",
24 | subtitle: "It's time for some well deserved food",
25 | description: "We have partnered with the venue to provide us with handmade food. The venue has an incredible chef who will produce food to cater to everyone. They have access to a stone-baked pizza oven to provide fresh pizza slices and handmade buffet food with a vast selection. Don't forget your handmade brownie or Bakewell slice 😋",
26 | image: "IMG_6298.jpg-93D1F0E2-6F47-4149-944B-FB824EFB2549",
27 | metadataURL: "")
28 | }
29 |
--------------------------------------------------------------------------------
/SwiftLeeds/Data/Model/Local.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Local.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Alex Logan on 08/08/2022.
6 | //
7 |
8 | import Foundation
9 | import MapKit
10 |
11 | struct Local: Decodable {
12 | let data: [LocationCategory]
13 |
14 | struct LocationCategory: Decodable, Identifiable, Equatable {
15 | let id: UUID
16 | let name: String
17 | let symbolName: String
18 | let locations: [Location]
19 | }
20 |
21 | struct Location: Decodable, Identifiable, Equatable {
22 | let id: UUID
23 | let name: String
24 | let url: URL
25 | let location: CLLocation
26 | }
27 | }
28 |
29 | extension Local.Location {
30 | init(from decoder: Decoder) throws {
31 | let values = try decoder.container(keyedBy: CodingKeys.self)
32 |
33 | id = try values.decode(UUID.self, forKey: .id)
34 | name = try values.decode(String.self, forKey: .name)
35 | url = try values.decode(URL.self, forKey: .url)
36 |
37 | let latitude = try values.decode(Double.self, forKey: .latitude)
38 | let longitude = try values.decode(Double.self, forKey: .longitude)
39 | location = CLLocation(latitude: latitude, longitude: longitude)
40 | }
41 |
42 | private enum CodingKeys: String, CodingKey {
43 | case id, name, latitude = "lat", longitude = "lon", url
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/SwiftLeeds/Data/Model/Presentation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Presentation.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 31/08/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | // MARK: - Presentation
11 | struct Presentation: Codable, Identifiable {
12 | let id: UUID
13 | let title: String
14 | let synopsis: String
15 | let speakers: [Speaker]
16 | let image: String?
17 | let slidoURL: String?
18 | let videoURL: String?
19 | }
20 |
21 | // MARK: - Static Data
22 | extension Presentation {
23 | static let donnyWalls = Presentation(id: UUID(), title: "Building (and testing) custom property wrappers for SwiftUI", synopsis: "In this talk, you will learn everything you need to know about using DynamicProperty to build custom property wrappers that integrate with SwiftUI’s view lifecycle and environment beautifully. And more importantly, you will learn how you can write unit tests for your custom property wrappers as well.", speakers: [.init(id: UUID(), name: "Donny Wals", biography: "I'm a curious, passionate iOS Developer from The Netherlands who loves learning and sharing knowledge.", profileImage: "https://swiftleeds-speakers.s3.eu-west-2.amazonaws.com/jOaeQ1Og_400x400.jpeg-AEAB9C2A-9572-4E6A-A63E-C3534EE5C321", organisation: "DonnyWals.com", twitter: "donnywals")], image: nil, slidoURL: "https://app.sli.do/event/2x7itwrn", videoURL: nil)
24 |
25 | static let skyBet = Presentation(id: UUID(), title: "UI automation with XCUItest", synopsis: "In this duo talk with Poornima and Sanaa, we'll be exploring how to create the most efficient UI automation using Apple's UI automation frameworks. We'll be covering:\r\n\r\n- What is UI automation testing\r\n- Setting up the framework\r\n- BDD\r\n- Base Class\r\n- Page Object Model\r\n- Data mocking\r\n- Execution: Test Plans/test schemes\r\n- Test Results: Reporting\r\n- Debugging - breakpoints, prints, etc\r\n- CI\r\n- Pros and cons", speakers: [.init(id: UUID(), name: "Poornima Suraj", biography: "16 years of experience in IT with more than 14 years working exclusively on Automation testing.", profileImage: "https://swiftleeds-speakers.s3.eu-west-2.amazonaws.com/4769-0o0o0-YroTPVoSrXCZvVwZXEvMUt.png-0DCB39D9-EA7C-4DCA-B4B2-B4712D2A8FCE", organisation: "Sky Betting & Gaming", twitter: nil), .init(id: UUID(), name: "Sanaa Shahzadi", biography: "Currently, an iOS Engineer at Sky Betting and Gaming previously worked as a Software Engineer in Test (SEiT).", profileImage: "https://swiftleeds-speakers.s3.eu-west-2.amazonaws.com/5b00-0o0o0-nwCWuRFWhXd8QZVQBSB5G2.jpeg-3A19F66F-6A15-44E7-810E-EDF37463B537", organisation: "Sky Betting & Gaming", twitter: "SanaaShahzadi")], image: nil, slidoURL: nil, videoURL: "https://www.youtube.com/watch?v=x4ZAh-iNQO8&t=2s")
26 | }
27 |
--------------------------------------------------------------------------------
/SwiftLeeds/Data/Model/Schedule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Schedule.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 01/08/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | struct Schedule: Decodable {
11 | let data: Data
12 |
13 | struct Data: Decodable {
14 | let event: Event
15 | let events: [Event]
16 | let slots: [Slot]
17 | }
18 |
19 | struct Event: Decodable, Identifiable {
20 | let id: UUID
21 | let name: String
22 | let location: String
23 | let date: Date
24 |
25 | var daysUntil: Int {
26 | Calendar.current.numberOfDays(to: date)
27 | }
28 | }
29 |
30 | struct Slot: Identifiable {
31 | let id: UUID
32 | let date: Date?
33 | let startTime: String
34 | let duration: Int
35 | let activity: Activity?
36 | let presentation: Presentation?
37 |
38 | private enum CodingKeys: CodingKey {
39 | case id, activity, presentation, date, startTime, duration
40 | }
41 |
42 | static var timeFormat: DateFormatter = {
43 | let dateFormatter = DateFormatter()
44 | dateFormatter.dateFormat = "HH:mm"
45 | return dateFormatter
46 | }()
47 | }
48 | }
49 |
50 | // MARK: - Slot Decodable
51 | extension Schedule.Slot: Codable {
52 | init(from decoder: Decoder) throws {
53 | let values = try decoder.container(keyedBy: CodingKeys.self)
54 |
55 | id = try values.decode(UUID.self, forKey: .id)
56 | startTime = try values.decode(String.self, forKey: .startTime)
57 | duration = try values.decode(Int.self, forKey: .duration)
58 |
59 | let date = try values.decodeIfPresent(String.self, forKey: .date) ?? ""
60 | self.date = ISO8601DateFormatter().date(from: date)
61 |
62 | if let activity = try values.decodeIfPresent(Activity.self, forKey: .activity) {
63 | self.activity = activity
64 | self.presentation = nil
65 | } else if let presentation = try values.decodeIfPresent(Presentation.self, forKey: .presentation) {
66 | self.activity = nil
67 | self.presentation = presentation
68 | } else {
69 | throw(SlotError.invalidSlot)
70 | }
71 | }
72 |
73 | enum SlotError: Error {
74 | case invalidSlot
75 | }
76 | }
77 |
78 | // MARK: - Slot Equatable
79 | extension Schedule.Slot: Equatable {
80 | static func == (lhs: Schedule.Slot, rhs: Schedule.Slot) -> Bool {
81 | lhs.id == rhs.id
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/SwiftLeeds/Data/Model/Speaker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Speaker.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 31/08/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | struct Speaker: Codable, Identifiable {
11 | let id: UUID
12 | let name: String
13 | let biography: String
14 | let profileImage: String
15 | let organisation: String
16 | let twitter: String?
17 | }
18 |
19 | // MARK: - Formatting helpers
20 | extension Array where Element == Speaker {
21 | var joinedNames: String {
22 | ListFormatter.localizedString(byJoining: self.map { $0.name })
23 | }
24 |
25 | var joinedOrganisations: String {
26 | let organisations = Set(self.map { $0.organisation })
27 | return ListFormatter.localizedString(byJoining: organisations.map { $0 })
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/SwiftLeeds/Data/Model/Sponsor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Sponsor.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Muralidharan Kathiresan on 26/06/23.
6 | //
7 |
8 | import Foundation
9 |
10 | struct Sponsors: Decodable {
11 | let data: [Sponsor]
12 | }
13 |
14 | struct Sponsor: Decodable, Hashable, Identifiable {
15 | let id: String
16 | let name: String
17 | let subtitle: String
18 | let image: String
19 | let sponsorLevel: SponsorLevel
20 | let url: String
21 | let jobs: [Job]
22 |
23 | static let sample = Sponsor(id: "id", name: "SwiftLeeds", subtitle: "Best Conference", image: "https://swiftleeds-speakers.s3.eu-west-2.amazonaws.com/961E45E2-8667-42F6-895E-4CE5E8B954E2-skybrand.png", sponsorLevel: .platinum, url: "", jobs: [.sample])
24 | }
25 |
26 | enum SponsorLevel: String, Decodable {
27 | case silver
28 | case platinum
29 | case gold
30 | }
31 |
32 | struct Job: Decodable, Hashable, Identifiable {
33 | let id: UUID
34 | let title: String
35 | let details: String
36 | let location: String
37 | let url: String
38 |
39 | static let sample = Job(id: UUID(), title: "Senior iOS Engineer", details: "Bringing all your Swift skills to the fore", location: "Leeds", url: "https://www.swiftleeds.co.uk")
40 | }
41 |
--------------------------------------------------------------------------------
/SwiftLeeds/Extension/Calendar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Calendar.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 04/08/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Calendar {
11 | func numberOfDays(to date: Date) -> Int {
12 | let fromDate = startOfDay(for: Date.now)
13 | let toDate = startOfDay(for: date)
14 | let numberOfDays = dateComponents([.day], from: fromDate, to: toDate)
15 |
16 | return numberOfDays.day ?? 0
17 | }
18 |
19 | static var atConferenceVenue: Calendar {
20 | var calendar = Calendar.current
21 | calendar.timeZone = .init(abbreviation: "BST")!
22 | return calendar
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/SwiftLeeds/Extension/Color.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Color.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 14/11/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension Color {
11 | static let accent = Color("AccentColor")
12 |
13 | static let tabBarBackground = Color("TabBarBackground")
14 | static let background = Color("Background")
15 | static let listBackground = Color("ListBackground")
16 |
17 | static let cellBackground = Color("CellBackground")
18 | static let cellBorder = Color("CellBorder")
19 | static let cellForeground = Color("CellForeground")
20 |
21 | static let buyTicketGradientStart = Color("BuyTicketGradientStart")
22 | static let buyTicketGradientEnd = Color("BuyTicketGradientEnd")
23 |
24 | static let weatherGradientStart = Color("WeatherGradientStart")
25 | static let weatherGradientEnd = Color("WeatherGradientEnd")
26 | }
27 |
--------------------------------------------------------------------------------
/SwiftLeeds/Extension/Date.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Date.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 21/08/2023.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Date {
11 | var withoutTimeAtConferenceVenue: Date {
12 | guard let date = Calendar.atConferenceVenue.date(from: Calendar.atConferenceVenue.dateComponents([.year, .month, .day], from: self)) else {
13 | fatalError("Failed to strip time from Date")
14 | }
15 |
16 | return date
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/SwiftLeeds/Extension/LinearGradient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LinearGradient.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Alex Logan on 05/07/2022.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | extension LinearGradient {
12 | static let weather = LinearGradient(colors: [
13 | .weatherGradientStart, .weatherGradientEnd
14 | ], startPoint: .bottomLeading, endPoint: .topLeading)
15 |
16 | static let announcement = LinearGradient(colors: [
17 | .buyTicketGradientStart, .buyTicketGradientEnd
18 | ], startPoint: .bottomLeading, endPoint: .topLeading)
19 | }
20 |
--------------------------------------------------------------------------------
/SwiftLeeds/Extension/String.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 06/08/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | extension String {
11 | var noEmojis: String {
12 | return self.unicodeScalars
13 | .filter { $0.properties.isEmojiPresentation == false }
14 | .reduce("") { $0 + String($1) }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/SwiftLeeds/Extension/View+MeasureSize.swift:
--------------------------------------------------------------------------------
1 | //
2 | // View+MeasureSize.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Alex Logan on 17/07/2022.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct SizePreferenceKey: PreferenceKey {
12 | static var defaultValue: CGSize = .zero
13 |
14 | static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
15 | if nextValue() == .zero { return }
16 | value = nextValue()
17 | }
18 | }
19 |
20 | struct SizeMeasuringModifier: ViewModifier {
21 | func body(content: Content) -> some View {
22 | content.background(GeometryReader { geometry in
23 | Color.clear.preference(key: SizePreferenceKey.self, value: geometry.size)
24 | })
25 | }
26 | }
27 |
28 | extension View {
29 | func measureSize(perform action: @escaping (CGSize) -> Void) -> some View {
30 | self.modifier(SizeMeasuringModifier())
31 | .onPreferenceChange(SizePreferenceKey.self, perform: action)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/SwiftLeeds/Network/Endpoints.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Endpoints.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Alex Logan on 11/08/2022.
6 | //
7 |
8 | import Foundation
9 | import NetworkKit
10 |
11 | /// All endpoints should stay in this file to avoid creating lots of little files
12 |
13 | // MARK: - Schedule Endpoint
14 | struct ScheduleEndpoint: Endpoint {
15 | typealias DataType = Schedule
16 | let path: String = "schedule"
17 | var eventID: String?
18 |
19 | var queryParameters: [URLQueryItem] {
20 | if let eventID {
21 | return [.init(name: "event", value: eventID)]
22 | } else {
23 | return []
24 | }
25 | }
26 | }
27 |
28 | // MARK: - Local Endpoint
29 | struct LocalEndpoint: Endpoint {
30 | typealias DataType = Local
31 | let path: String = "local"
32 | }
33 |
34 | // MARK: - Sponsors Endpoint
35 | struct SponsorsEndpoint: Endpoint {
36 | typealias DataType = Sponsors
37 | let path: String = "sponsors"
38 | }
39 |
--------------------------------------------------------------------------------
/SwiftLeeds/Network/HttpMethod.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HttpMethod.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 24/09/2023.
6 | //
7 |
8 | import Foundation
9 |
10 | public enum HttpMethod: Equatable {
11 | case get([URLQueryItem])
12 | case post(Data?)
13 | case head
14 |
15 | var name: String {
16 | switch self {
17 | case .get: return "GET"
18 | case .post: return "POST"
19 | case .head: return "HEAD"
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/SwiftLeeds/Network/Request.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Request.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 24/09/2023.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct Request {
11 | let scheme: String
12 | let host: String
13 | let path: String?
14 | let method: HttpMethod
15 | public var headers: [String: String] = [:]
16 | let eTagKey: String?
17 | let url: URL
18 |
19 | public init(scheme: String = "https", host: String, path: String, method: HttpMethod = .get([]), headers: [String: String] = [:], eTagKey: String? = nil) {
20 | self.scheme = scheme
21 | self.host = host
22 | self.path = path
23 | self.method = method
24 | self.headers = headers
25 | self.eTagKey = eTagKey
26 |
27 | var components = URLComponents()
28 | components.scheme = scheme
29 | components.host = host
30 | components.path = path
31 |
32 | guard let url = components.url else { preconditionFailure("Couldn't create a url from components") }
33 | self.url = url
34 | }
35 |
36 | var urlRequest: URLRequest {
37 | var request = URLRequest(url: url)
38 |
39 | switch method {
40 | case let .get(queryItems):
41 | var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
42 | components?.queryItems = queryItems
43 |
44 | guard let url = components?.url else { preconditionFailure("Couldn't create a url from components...") }
45 |
46 | request = URLRequest(url: url)
47 | case .post(let data):
48 | request.httpBody = data
49 | case .head:
50 | break
51 | }
52 |
53 | request.httpMethod = method.name
54 | request.allHTTPHeaderFields = headers
55 | request.setValue("application/json", forHTTPHeaderField: "Content-Type")
56 | request.setValue("application/json", forHTTPHeaderField: "Accept")
57 | request.setValue("*", forHTTPHeaderField: "Accept-Encoding")
58 |
59 | if let eTagKey, let eTagValue = UserDefaults.standard.value(forKey: eTagKey) as? String {
60 | request.setValue(eTagValue, forHTTPHeaderField: "If-None-Match")
61 | }
62 |
63 | return request
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/SwiftLeeds/Network/Requests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Requests.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 24/09/2023.
6 | //
7 |
8 | import Foundation
9 |
10 | enum Requests {
11 | private static let host = "swiftleeds.co.uk"
12 | private static let apiPath = "/api/v1"
13 |
14 | static let schedule = Request(host: host, path: "\(apiPath)/schedule", eTagKey: "etag-schedule")
15 | static let local = Request(host: host, path: "\(apiPath)/local", eTagKey: "etag-local")
16 | static let sponsors = Request(host: host, path: "\(apiPath)/sponsors", eTagKey: "etag-sponsors")
17 |
18 | static func schedule(for eventID: UUID) -> Request {
19 | Request(host: host, path: "\(apiPath)/schedule", method: .get([.init(name: "event", value: eventID.uuidString)]), eTagKey: "etag-schedule-\(eventID.uuidString)")
20 | }
21 |
22 | static var defaultDateDecodingStratergy: JSONDecoder.DateDecodingStrategy = {
23 | let dateFormatter = DateFormatter()
24 | dateFormatter.dateFormat = "dd-MM-yyyy"
25 | return .formatted(dateFormatter)
26 | }()
27 | }
28 |
--------------------------------------------------------------------------------
/SwiftLeeds/Network/URLSession.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLSession.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 24/09/2023.
6 | //
7 |
8 | import Foundation
9 |
10 | public extension URLSession {
11 | static var awaitConnectivity: URLSession = {
12 | let configuration = URLSessionConfiguration.default
13 | configuration.waitsForConnectivity = true
14 | configuration.timeoutIntervalForRequest = 30
15 | configuration.timeoutIntervalForResource = 30
16 | configuration.urlCache = nil
17 | configuration.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
18 | return URLSession(configuration: configuration)
19 | }()
20 |
21 | func cached(_ request: Request, using decoder: JSONDecoder = .init(),
22 | dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate,
23 | fileManager: FileManager = .default, filename: String? = nil) async throws -> Response {
24 | let filename = filename ?? request.url.lastPathComponent
25 | let path = fileManager.temporaryDirectory.appendingPathComponent(filename)
26 |
27 | guard let data = fileManager.contents(atPath: path.path.appending(".json")) else { throw NetworkError.cacheNotFound }
28 |
29 | let decoded = Task.detached(priority: .userInitiated) {
30 | try Task.checkCancellation()
31 | decoder.dateDecodingStrategy = dateDecodingStrategy
32 | return try decoder.decode(Response.self, from: data)
33 | }
34 |
35 | return try await decoded.value
36 | }
37 |
38 | func decode(_ request: Request, using decoder: JSONDecoder = .init(),
39 | dateDecodingStrategy: JSONDecoder.DateDecodingStrategy?,
40 | fileManager: FileManager = .default, filename: String? = nil) async throws -> Response {
41 | let filename = filename ?? request.url.lastPathComponent
42 | let path = fileManager.temporaryDirectory.appendingPathComponent("\(filename).json")
43 |
44 | let decoded = Task.detached(priority: .userInitiated) {
45 | do {
46 | let (data, response) = try await self.data(for: request.urlRequest)
47 |
48 | try Task.checkCancellation()
49 |
50 | guard let response = response as? HTTPURLResponse else { throw URLError(.badServerResponse) }
51 |
52 | switch response.statusCode {
53 | case 200...299: break
54 | case 304: throw NetworkError.notModified
55 | default: throw NetworkError.unexpectedStatusCode(response.statusCode)
56 | }
57 |
58 | try data.write(to: path, options: .atomicWrite)
59 |
60 | if let eTagKey = request.eTagKey, let eTagValue = response.value(forHTTPHeaderField: "Etag") {
61 | UserDefaults.standard.set(eTagValue, forKey: eTagKey)
62 | }
63 |
64 | if let dateDecodingStrategy {
65 | decoder.dateDecodingStrategy = dateDecodingStrategy
66 | }
67 |
68 | return try decoder.decode(Response.self, from: data)
69 | } catch {
70 | let nsError = error as NSError
71 |
72 | if let networkIssue = NetworkError.NetworkIssue(rawValue: nsError.code) {
73 | throw NetworkError.networkIssue(networkIssue)
74 | }
75 |
76 | throw NetworkError.unexpectedError(error)
77 | }
78 | }
79 |
80 | return try await decoded.value
81 | }
82 | }
83 |
84 | // MARK: - NetworkError
85 | public enum NetworkError: Error {
86 | case cacheNotFound
87 | case notModified
88 | case unexpectedStatusCode(Int)
89 | case unexpectedError(Error)
90 | case networkIssue(NetworkIssue)
91 |
92 | public enum NetworkIssue: Int, CaseIterable {
93 | case backgroundSessionInUseByAnotherProcess = -996
94 | case timedOut = -1001
95 | case cannotFindHost = -1003
96 | case cannotConnectToHost = -1004
97 | case networkConnectionLost = -1005
98 | case notConnectedToInternet = -1009
99 | case secureConnectionFailed = -1200
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/SwiftLeeds/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftLeeds/Push/AppDelegate+Push.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate+Push.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 04/09/2022.
6 | //
7 |
8 | import UIKit
9 | import UserNotifications
10 |
11 | extension AppDelegate {
12 | func requestPushAuthorization(application: UIApplication) {
13 | let notificatioNCenter = UNUserNotificationCenter.current()
14 | notificatioNCenter.requestAuthorization(options: [.badge, .sound, .alert]) { [weak self] isGranted, error in
15 | guard isGranted else { print("⛔️ not granted"); return }
16 | if let error = error { print("⛔️", error); return }
17 |
18 | notificatioNCenter.delegate = self
19 |
20 | DispatchQueue.main.async {
21 | application.registerForRemoteNotifications()
22 | }
23 | }
24 | }
25 |
26 | func sendPushRegistrationDatails(to url: URL, deviceToken: Data) {
27 | var details = TokenDetails(token: deviceToken)
28 |
29 | #if DEBUG
30 | details.debug = true
31 | print("🚀", details)
32 | #endif
33 |
34 | var request = URLRequest(url: url)
35 | request.addValue("application/json", forHTTPHeaderField: "Content-Type")
36 | request.httpMethod = "POST"
37 | request.httpBody = try? TokenDetails.encoder.encode(details)
38 |
39 | Task {
40 | do {
41 | let (_, response) = try await URLSession.shared.data(for: request)
42 |
43 | guard let statusCode = (response as? HTTPURLResponse)?.statusCode, 200..<399 ~= statusCode else {
44 | print("⛔️ Push registration failed, invalid response")
45 | return
46 | }
47 | } catch {
48 | print("⛔️ Push registration failed due to unexpected network issue:", error)
49 | }
50 | }
51 | }
52 |
53 | func handleFailedRegistration(application: UIApplication, error: Error) {
54 | print("⛔️ Push registration failed:", error)
55 | }
56 | }
57 |
58 | // MARK: - UNUserNotificationCenterDelegate
59 | extension AppDelegate: UNUserNotificationCenterDelegate {
60 | func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
61 | [.banner, .sound, .badge]
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/SwiftLeeds/Push/TokenDetails.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TokenDetails.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 04/09/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | struct TokenDetails: Encodable {
11 | let token: String
12 | var debug: Bool = false
13 |
14 | static var encoder: JSONEncoder {
15 | let encoder = JSONEncoder()
16 | encoder.outputFormatting = .prettyPrinted
17 | return encoder
18 | }
19 | }
20 |
21 | extension TokenDetails {
22 | init(token: Data) {
23 | self.token = token.reduce("") { $0 + String(format: "%02x", $1) }
24 | }
25 | }
26 |
27 | extension TokenDetails: CustomStringConvertible {
28 | var description: String {
29 | do {
30 | let data = try Self.encoder.encode(self)
31 | return String(data: data, encoding: .utf8) ?? "Invalid token"
32 | } catch {
33 | return "Invalid token"
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftLeeds/swiftleeds-ios/3961b7a7ec88cdfdc34d520864ffad08ef1c1336/SwiftLeeds/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon.png
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "AppIcon.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | }
9 | ],
10 | "info" : {
11 | "author" : "xcode",
12 | "version" : 1
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/CarriageworksTheatre.imageset/CarriageworksTheatre.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftLeeds/swiftleeds-ios/3961b7a7ec88cdfdc34d520864ffad08ef1c1336/SwiftLeeds/Resources/Assets.xcassets/CarriageworksTheatre.imageset/CarriageworksTheatre.jpg
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/CarriageworksTheatre.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "CarriageworksTheatre.jpg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/Clock.imageset/Clock.pdf:
--------------------------------------------------------------------------------
1 | %PDF-1.7
2 |
3 | 1 0 obj
4 | << >>
5 | endobj
6 |
7 | 2 0 obj
8 | << /Length 3 0 R >>
9 | stream
10 | /DeviceRGB CS
11 | /DeviceRGB cs
12 | q
13 | 1.000000 0.000000 -0.000000 1.000000 0.750000 -0.750000 cm
14 | 0.583333 0.583333 0.583333 scn
15 | 10.250000 7.000000 m
16 | 10.250000 4.376647 8.123353 2.250000 5.500000 2.250000 c
17 | 5.500000 0.750000 l
18 | 8.951780 0.750000 11.750000 3.548220 11.750000 7.000000 c
19 | 10.250000 7.000000 l
20 | h
21 | 5.500000 2.250000 m
22 | 2.876647 2.250000 0.750000 4.376647 0.750000 7.000000 c
23 | -0.750000 7.000000 l
24 | -0.750000 3.548220 2.048220 0.750000 5.500000 0.750000 c
25 | 5.500000 2.250000 l
26 | h
27 | 0.750000 7.000000 m
28 | 0.750000 9.623352 2.876647 11.750000 5.500000 11.750000 c
29 | 5.500000 13.250000 l
30 | 2.048220 13.250000 -0.750000 10.451779 -0.750000 7.000000 c
31 | 0.750000 7.000000 l
32 | h
33 | 5.500000 11.750000 m
34 | 8.123353 11.750000 10.250000 9.623352 10.250000 7.000000 c
35 | 11.750000 7.000000 l
36 | 11.750000 10.451779 8.951780 13.250000 5.500000 13.250000 c
37 | 5.500000 11.750000 l
38 | h
39 | f
40 | n
41 | Q
42 | q
43 | 1.000000 0.000000 -0.000000 1.000000 6.250000 3.543915 cm
44 | 0.583333 0.583333 0.583333 scn
45 | 0.750000 6.006073 m
46 | 0.750000 6.420287 0.414214 6.756073 0.000000 6.756073 c
47 | -0.414214 6.756073 -0.750000 6.420287 -0.750000 6.006073 c
48 | 0.750000 6.006073 l
49 | h
50 | 0.000000 2.706073 m
51 | -0.750000 2.706073 l
52 | -0.750000 2.421994 -0.589498 2.162297 -0.335410 2.035253 c
53 | 0.000000 2.706073 l
54 | h
55 | 1.864590 0.935253 m
56 | 2.235074 0.750011 2.685578 0.900179 2.870820 1.270663 c
57 | 3.056062 1.641147 2.905894 2.091652 2.535410 2.276894 c
58 | 1.864590 0.935253 l
59 | h
60 | -0.750000 6.006073 m
61 | -0.750000 2.706073 l
62 | 0.750000 2.706073 l
63 | 0.750000 6.006073 l
64 | -0.750000 6.006073 l
65 | h
66 | -0.335410 2.035253 m
67 | 1.864590 0.935253 l
68 | 2.535410 2.276894 l
69 | 0.335410 3.376894 l
70 | -0.335410 2.035253 l
71 | h
72 | f
73 | n
74 | Q
75 |
76 | endstream
77 | endobj
78 |
79 | 3 0 obj
80 | 1585
81 | endobj
82 |
83 | 4 0 obj
84 | << /Annots []
85 | /Type /Page
86 | /MediaBox [ 0.000000 0.000000 12.500000 12.500000 ]
87 | /Resources 1 0 R
88 | /Contents 2 0 R
89 | /Parent 5 0 R
90 | >>
91 | endobj
92 |
93 | 5 0 obj
94 | << /Kids [ 4 0 R ]
95 | /Count 1
96 | /Type /Pages
97 | >>
98 | endobj
99 |
100 | 6 0 obj
101 | << /Pages 5 0 R
102 | /Type /Catalog
103 | >>
104 | endobj
105 |
106 | xref
107 | 0 7
108 | 0000000000 65535 f
109 | 0000000010 00000 n
110 | 0000000034 00000 n
111 | 0000001675 00000 n
112 | 0000001698 00000 n
113 | 0000001871 00000 n
114 | 0000001945 00000 n
115 | trailer
116 | << /ID [ (some) (id) ]
117 | /Root 6 0 R
118 | /Size 7
119 | >>
120 | startxref
121 | 2004
122 | %%EOF
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/Clock.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Clock.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/Icon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Icon.pdf",
5 | "idiom" : "universal"
6 | },
7 | {
8 | "appearances" : [
9 | {
10 | "appearance" : "luminosity",
11 | "value" : "dark"
12 | }
13 | ],
14 | "filename" : "Icon-Dark.pdf",
15 | "idiom" : "universal"
16 | }
17 | ],
18 | "info" : {
19 | "author" : "xcode",
20 | "version" : 1
21 | },
22 | "properties" : {
23 | "preserves-vector-representation" : true
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/Icon.imageset/Icon-Dark.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftLeeds/swiftleeds-ios/3961b7a7ec88cdfdc34d520864ffad08ef1c1336/SwiftLeeds/Resources/Assets.xcassets/Icon.imageset/Icon-Dark.pdf
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/Icon.imageset/Icon.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftLeeds/swiftleeds-ios/3961b7a7ec88cdfdc34d520864ffad08ef1c1336/SwiftLeeds/Resources/Assets.xcassets/Icon.imageset/Icon.pdf
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "LaunchScreen.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "appearances" : [
10 | {
11 | "appearance" : "luminosity",
12 | "value" : "dark"
13 | }
14 | ],
15 | "filename" : "LaunchScreen-Dark.png",
16 | "idiom" : "universal",
17 | "scale" : "1x"
18 | },
19 | {
20 | "filename" : "LaunchScreen@2x.png",
21 | "idiom" : "universal",
22 | "scale" : "2x"
23 | },
24 | {
25 | "appearances" : [
26 | {
27 | "appearance" : "luminosity",
28 | "value" : "dark"
29 | }
30 | ],
31 | "filename" : "LaunchScreen-Dark@2x.png",
32 | "idiom" : "universal",
33 | "scale" : "2x"
34 | },
35 | {
36 | "filename" : "LaunchScreen@3x.png",
37 | "idiom" : "universal",
38 | "scale" : "3x"
39 | },
40 | {
41 | "appearances" : [
42 | {
43 | "appearance" : "luminosity",
44 | "value" : "dark"
45 | }
46 | ],
47 | "filename" : "LaunchScreen-Dark@3x.png",
48 | "idiom" : "universal",
49 | "scale" : "3x"
50 | }
51 | ],
52 | "info" : {
53 | "author" : "xcode",
54 | "version" : 1
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/LaunchImage.imageset/LaunchScreen-Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftLeeds/swiftleeds-ios/3961b7a7ec88cdfdc34d520864ffad08ef1c1336/SwiftLeeds/Resources/Assets.xcassets/LaunchImage.imageset/LaunchScreen-Dark.png
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/LaunchImage.imageset/LaunchScreen-Dark@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftLeeds/swiftleeds-ios/3961b7a7ec88cdfdc34d520864ffad08ef1c1336/SwiftLeeds/Resources/Assets.xcassets/LaunchImage.imageset/LaunchScreen-Dark@2x.png
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/LaunchImage.imageset/LaunchScreen-Dark@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftLeeds/swiftleeds-ios/3961b7a7ec88cdfdc34d520864ffad08ef1c1336/SwiftLeeds/Resources/Assets.xcassets/LaunchImage.imageset/LaunchScreen-Dark@3x.png
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/LaunchImage.imageset/LaunchScreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftLeeds/swiftleeds-ios/3961b7a7ec88cdfdc34d520864ffad08ef1c1336/SwiftLeeds/Resources/Assets.xcassets/LaunchImage.imageset/LaunchScreen.png
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/LaunchImage.imageset/LaunchScreen@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftLeeds/swiftleeds-ios/3961b7a7ec88cdfdc34d520864ffad08ef1c1336/SwiftLeeds/Resources/Assets.xcassets/LaunchImage.imageset/LaunchScreen@2x.png
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/LaunchImage.imageset/LaunchScreen@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftLeeds/swiftleeds-ios/3961b7a7ec88cdfdc34d520864ffad08ef1c1336/SwiftLeeds/Resources/Assets.xcassets/LaunchImage.imageset/LaunchScreen@3x.png
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/LeedsPlayhouse.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "LeedsPlayhouse.jpeg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "original"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/LeedsPlayhouse.imageset/LeedsPlayhouse.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftLeeds/swiftleeds-ios/3961b7a7ec88cdfdc34d520864ffad08ef1c1336/SwiftLeeds/Resources/Assets.xcassets/LeedsPlayhouse.imageset/LeedsPlayhouse.jpeg
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/SwiftLeedsIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "SwiftLeedsIcon.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "preserves-vector-representation" : true,
14 | "template-rendering-intent" : "original"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/SwiftLeedsIcon.imageset/SwiftLeedsIcon.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftLeeds/swiftleeds-ios/3961b7a7ec88cdfdc34d520864ffad08ef1c1336/SwiftLeeds/Resources/Assets.xcassets/SwiftLeedsIcon.imageset/SwiftLeedsIcon.pdf
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/wineglass.fill.symbolset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "symbols" : [
7 | {
8 | "filename" : "wineglass.fill.svg",
9 | "idiom" : "universal"
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Assets.xcassets/wineglass.fill.symbolset/wineglass.fill.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
94 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Colors.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x30",
9 | "green" : "0x3B",
10 | "red" : "0xFF"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "platform" : "universal",
24 | "reference" : "systemRedColor"
25 | },
26 | "idiom" : "universal"
27 | }
28 | ],
29 | "info" : {
30 | "author" : "xcode",
31 | "version" : 1
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Colors.xcassets/Background.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xED",
9 | "green" : "0xED",
10 | "red" : "0xED"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x1E",
27 | "green" : "0x1D",
28 | "red" : "0x1C"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Colors.xcassets/CellBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFF",
9 | "green" : "0xFF",
10 | "red" : "0xFF"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x3A",
27 | "green" : "0x3A",
28 | "red" : "0x3A"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Colors.xcassets/CellBorder.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xD4",
9 | "green" : "0xD4",
10 | "red" : "0xD4"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0xFF",
27 | "green" : "0xFF",
28 | "red" : "0xFF"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Colors.xcassets/CellForeground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x4A",
9 | "green" : "0x4A",
10 | "red" : "0x4A"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0xFF",
27 | "green" : "0xFF",
28 | "red" : "0xFF"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Colors.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Colors.xcassets/Gradients/BuyTicketGradientEnd.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x9A",
9 | "green" : "0x9A",
10 | "red" : "0x9A"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x7F",
27 | "green" : "0x7F",
28 | "red" : "0x7F"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Colors.xcassets/Gradients/BuyTicketGradientStart.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x5E",
9 | "green" : "0x5E",
10 | "red" : "0x5E"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x5E",
27 | "green" : "0x5E",
28 | "red" : "0x5E"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Colors.xcassets/Gradients/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Colors.xcassets/LaunchScreenBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "1.000",
9 | "green" : "1.000",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x3A",
27 | "green" : "0x3A",
28 | "red" : "0x3A"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Colors.xcassets/ListBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFF",
9 | "green" : "0xFF",
10 | "red" : "0xFF"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x1E",
27 | "green" : "0x1D",
28 | "red" : "0x1C"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SwiftLeeds/Resources/Colors.xcassets/TabBarBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xF9",
9 | "green" : "0xF9",
10 | "red" : "0xF9"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x39",
27 | "green" : "0x39",
28 | "red" : "0x39"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SwiftLeeds/Style/SquishyButtonStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SquishyButtonStyle.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Alex Logan on 01/07/2022.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct SquishyButtonStyle: ButtonStyle {
12 | func makeBody(configuration: Self.Configuration) -> some View {
13 | configuration.label
14 | .scaleEffect(configuration.isPressed ? 0.98 : 1.0)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/SwiftLeeds/SwiftLeeds.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | aps-environment
6 | development
7 | com.apple.developer.associated-domains
8 |
9 | appclips:swiftleeds.co.uk
10 | applinks:swiftleeds.co.uk
11 |
12 | com.apple.security.application-groups
13 |
14 | group.uk.co.swiftleeds
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/About/AboutView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AboutView.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 25/06/2022.
6 | //
7 |
8 | import SwiftUI
9 | import ReadabilityModifier
10 |
11 | struct AboutView: View {
12 | @State private var isReportAProblemShown = false
13 |
14 | private let venueURL = URL(string: "https://swiftleeds.co.uk/#venue")
15 | private let codeOfConductURL = URL(string: "https://swiftleeds.co.uk/conduct")
16 | private let reportAProblemLink = "https://forms.gle/PJie9aRNAtzQUdUu9"
17 |
18 | var body: some View {
19 | SwiftLeedsContainer {
20 | ScrollView {
21 | content
22 | }
23 | }
24 | .edgesIgnoringSafeArea(.top)
25 | }
26 |
27 | private var content: some View {
28 | VStack(spacing: Padding.cellGap) {
29 | FancyHeaderView(
30 | title: "About",
31 | foregroundImageName: Assets.Image.swiftLeedsIcon
32 | )
33 |
34 | VStack(spacing: Padding.cellGap) {
35 | StackedTileView(primaryText: "About", secondaryText: Strings.aboutSwiftLeeds)
36 |
37 | CommonTileButton(primaryText: "Report a problem", accessibilityHint: "Opens a web view to allow a problem to be reported", backgroundStyle: Color.cellBackground) {
38 | isReportAProblemShown = true
39 | }
40 |
41 | CommonTileButton(primaryText: "Code of conduct", accessibilityHint: "Opens a web page showing our code of conduct", backgroundStyle: Color.cellBackground) {
42 | openURL(url: codeOfConductURL)
43 | }
44 |
45 | CommonTileButton(primaryText: "Venue", accessibilityHint: "Opens a web page showing our venue information", backgroundStyle: Color.cellBackground) {
46 | openURL(url: venueURL)
47 | }
48 | }
49 | .fitToReadableContentGuide(type: .width)
50 | }
51 | .padding(.bottom, Padding.cellGap)
52 | .sheet(isPresented: $isReportAProblemShown) {
53 | WebView(url: reportAProblemLink)
54 | .ignoresSafeArea(edges: .bottom)
55 | }
56 | .navigationBarHidden(true)
57 | }
58 |
59 | private func openURL(url: URL?) {
60 | guard let url = url else { return }
61 | UIApplication.shared.open(url)
62 | }
63 | }
64 |
65 | struct AboutView_Previews: PreviewProvider {
66 | static var previews: some View {
67 | AboutView()
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Common/Helper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Helper.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 21/08/2023.
6 | //
7 |
8 | import Foundation
9 |
10 | enum Helper {
11 | static var shortDateFormatter: DateFormatter = {
12 | let dateformatter = DateFormatter()
13 | dateformatter.dateFormat = "dd-MM-yyyy"
14 | return dateformatter
15 | }()
16 | }
17 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Common/SectionHeader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SectionHeader.swift
3 | // SwiftLeeds
4 | //
5 | // Created by LUCKY AGARWAL on 23/07/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SectionHeader: View {
11 | let title: String
12 | let fontStyle: Font
13 | let foregroundColor: Color
14 | let maxWidth: CGFloat
15 | let alignment: Alignment
16 | let accesibilityAddTraits: AccessibilityTraits
17 |
18 | internal init(title: String,
19 | fontStyle: Font = .callout.weight(.semibold),
20 | foregroundColor : Color = .secondary,
21 | maxWidth: CGFloat = .infinity,
22 | alignment: Alignment = .leading,
23 | accessbilityAddTraits: AccessibilityTraits = .isHeader
24 | ) {
25 | self.title = title
26 | self.fontStyle = fontStyle
27 | self.foregroundColor = foregroundColor
28 | self.maxWidth = maxWidth
29 | self.alignment = alignment
30 | self.accesibilityAddTraits = accessbilityAddTraits
31 | }
32 |
33 | var body: some View {
34 | Text(title)
35 | .font(fontStyle)
36 | .foregroundColor(foregroundColor)
37 | .frame(maxWidth: maxWidth, alignment: alignment)
38 | .accessibilityAddTraits(accesibilityAddTraits)
39 | }
40 | }
41 |
42 | struct SectionHeader_Previews: PreviewProvider {
43 | static var previews: some View {
44 | SectionHeader(title: "SwiftLeeds")
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Common/SwiftLeedsContainer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftLeedsContainer.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Alex Logan on 01/07/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SwiftLeedsContainer: View {
11 | private var content: () -> (Content)
12 |
13 | init(@ViewBuilder content: @escaping () -> (Content)) {
14 | self.content = content
15 | }
16 |
17 | var body: some View {
18 | ZStack {
19 | Color.background.edgesIgnoringSafeArea(.all)
20 | content()
21 | }
22 | }
23 | }
24 |
25 | struct SwiftLeedsContainer_Previews: PreviewProvider {
26 | static var previews: some View {
27 | SwiftLeedsContainer {
28 | Text(verbatim: "SwiftLeeds 22")
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Components/CommonTileButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommonTileButton.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Alex Logan on 05/07/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct CommonTileButton: View {
11 | let icon: String?
12 | let primaryText: String
13 | let secondaryText: String?
14 | let subtitleText: String?
15 | let accessibilityHint: String?
16 | let showChevron: Bool
17 | let primaryColor: Color
18 | let secondaryColor: Color
19 | var backgroundStyle: BackgroundType
20 |
21 | let onTap: () -> ()
22 |
23 | init(
24 | icon: String? = nil,
25 | primaryText: String,
26 | secondaryText: String? = nil,
27 | subtitleText: String? = nil,
28 | accessibilityHint: String? = nil,
29 | showChevron: Bool = false,
30 | primaryColor: Color = Color.primary,
31 | secondaryColor: Color = Color.secondary,
32 | backgroundStyle: Color = Color.cellBackground,
33 | onTap: @escaping () -> ()
34 | ) where BackgroundType == Color {
35 | self.icon = icon
36 | self.primaryText = primaryText
37 | self.secondaryText = secondaryText
38 | self.subtitleText = subtitleText
39 | self.accessibilityHint = accessibilityHint
40 | self.showChevron = showChevron
41 | self.primaryColor = primaryColor
42 | self.secondaryColor = secondaryColor
43 | self.backgroundStyle = backgroundStyle
44 | self.onTap = onTap
45 | }
46 |
47 | init(
48 | icon: String? = nil,
49 | primaryText: String,
50 | secondaryText: String? = nil,
51 | subtitleText: String? = nil,
52 | accessibilityHint: String? = nil,
53 | showChevron: Bool = false,
54 | primaryColor: Color = Color.primary,
55 | secondaryColor: Color = Color.secondary,
56 | backgroundStyle: BackgroundType,
57 | onTap: @escaping () -> ()
58 | ) {
59 | self.icon = icon
60 | self.primaryText = primaryText
61 | self.secondaryText = secondaryText
62 | self.subtitleText = subtitleText
63 | self.accessibilityHint = accessibilityHint
64 | self.showChevron = showChevron
65 | self.primaryColor = primaryColor
66 | self.secondaryColor = secondaryColor
67 | self.backgroundStyle = backgroundStyle
68 | self.onTap = onTap
69 | }
70 |
71 | var body: some View {
72 | Button(action: onTap) {
73 | CommonTileView(
74 | icon: icon,
75 | primaryText: primaryText,
76 | secondaryText: secondaryText,
77 | subtitleText: subtitleText,
78 | showChevron: showChevron,
79 | primaryColor: primaryColor,
80 | secondaryColor: secondaryColor,
81 | backgroundStyle: backgroundStyle
82 | )
83 | }
84 | .buttonStyle(SquishyButtonStyle())
85 | .accessibilityHint(accessibilityHint ?? "")
86 | .accessibilityAddTraits(.isButton)
87 | }
88 | }
89 |
90 | struct CommonTileButtton_Previews: PreviewProvider {
91 | static var previews: some View {
92 | ZStack {
93 | Color(uiColor: .systemGroupedBackground).edgesIgnoringSafeArea(.all)
94 | VStack(spacing: Padding.cellGap) {
95 | CommonTileButton(
96 | primaryText: "Primary",
97 | secondaryText: "Secondary",
98 | onTap: {}
99 | )
100 | CommonTileButton(
101 | primaryText: "Primary",
102 | showChevron: true,
103 | onTap: {}
104 | )
105 | CommonTileButton(
106 | primaryText: "Primary",
107 | secondaryText: "Secondary",
108 | backgroundStyle: .red,
109 | onTap: {}
110 | )
111 | CommonTileButton(
112 | primaryText: "Primary",
113 | secondaryText: "Secondary",
114 | primaryColor: .white,
115 | secondaryColor: .white.opacity(0.8),
116 | backgroundStyle: LinearGradient.weather,
117 | onTap: {}
118 | )
119 | }
120 | .padding()
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Components/CommonTileView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommonTileView.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Alex Logan on 05/07/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | /// Generic primary secondary view
11 | struct CommonTileView: View {
12 | @Environment(\.sizeCategory) var sizeCategory
13 |
14 | let icon: String?
15 | let primaryText: String
16 | let secondaryText: String?
17 | let subtitleText: String?
18 | let showChevron: Bool
19 | let primaryColor: Color
20 | let secondaryColor: Color
21 | var backgroundStyle: BackgroundType
22 |
23 | var accessibilityTextEnabled: Bool {
24 | sizeCategory >= .accessibilityMedium
25 | }
26 |
27 | init(
28 | icon: String? = nil,
29 | primaryText: String,
30 | secondaryText: String? = nil,
31 | subtitleText: String? = nil,
32 | showChevron: Bool = false,
33 | primaryColor: Color = Color.primary,
34 | secondaryColor: Color = Color.secondary,
35 | backgroundStyle: BackgroundType
36 | ) {
37 | self.icon = icon
38 | self.primaryText = primaryText
39 | self.secondaryText = secondaryText
40 | self.subtitleText = subtitleText
41 | self.showChevron = showChevron
42 | self.primaryColor = primaryColor
43 | self.secondaryColor = secondaryColor
44 | self.backgroundStyle = backgroundStyle
45 | }
46 |
47 | init(
48 | icon: String? = nil,
49 | primaryText: String,
50 | secondaryText: String? = nil,
51 | subtitleText: String? = nil,
52 | showChevron: Bool = false,
53 | primaryColor: Color = Color.primary,
54 | secondaryColor: Color = Color.secondary,
55 | backgroundStyle: Color = Color.cellBackground
56 | ) where BackgroundType == Color {
57 | self.icon = icon
58 | self.primaryText = primaryText
59 | self.secondaryText = secondaryText
60 | self.subtitleText = subtitleText
61 | self.showChevron = showChevron
62 | self.primaryColor = primaryColor
63 | self.secondaryColor = secondaryColor
64 | self.backgroundStyle = backgroundStyle
65 | }
66 |
67 | var body: some View {
68 | sizeAwareStack(content: {
69 | VStack(alignment: .leading, spacing: 2) {
70 | if let icon {
71 | Text("\(Image(systemName: icon)) \(primaryText)")
72 | .font(.subheadline.weight(.semibold))
73 | } else {
74 | Text(primaryText)
75 | .font(.subheadline.weight(.semibold))
76 | }
77 |
78 |
79 | if let subtitleText {
80 | Text(subtitleText)
81 | .font(.subheadline.weight(.light))
82 |
83 | }
84 | }
85 | .foregroundColor(primaryColor)
86 |
87 | if !accessibilityTextEnabled {
88 | Spacer()
89 | }
90 |
91 | if let secondaryText = secondaryText {
92 | Text(secondaryText)
93 | .font(.subheadline.weight(.medium))
94 | .foregroundColor(secondaryColor)
95 | } else if showChevron {
96 | Image(systemName: "chevron.right")
97 | }
98 | })
99 | .padding(Padding.cell)
100 | .frame(minHeight: Constants.compactCellMinimumHeight)
101 | .background(
102 | backgroundStyle,
103 | in: RoundedRectangle(cornerRadius: Constants.cellRadius)
104 | )
105 | .accessibilityElement(children: .ignore)
106 | .accessibilityLabel(
107 | "\(primaryText), \(secondaryText ?? "")"
108 | )
109 | }
110 |
111 | // When the text is huge, stack vertically instead to avoid compressing the leading text
112 | @ViewBuilder
113 | func sizeAwareStack(@ViewBuilder content: () -> (Content)) -> some View {
114 | if accessibilityTextEnabled {
115 | VStack {
116 | content()
117 | .frame(maxWidth: .infinity, alignment: .leading)
118 | }
119 | } else {
120 | HStack {
121 | content()
122 | }
123 | }
124 | }
125 | }
126 |
127 | struct CommonTileView_Previews: PreviewProvider {
128 | static var previews: some View {
129 | ZStack {
130 | Color(uiColor: .systemGroupedBackground).edgesIgnoringSafeArea(.all)
131 | VStack(spacing: Padding.cellGap) {
132 | CommonTileView(
133 | primaryText: "Primary", secondaryText: "Secondary", subtitleText: "More details", showChevron: true
134 | )
135 | CommonTileView(
136 | primaryText: "Primary"
137 | )
138 | CommonTileView(
139 | primaryText: "Primary",
140 | secondaryText: "Secondary",
141 | backgroundStyle: .red
142 | )
143 | CommonTileView(
144 | primaryText: "Primary",
145 | secondaryText: "Secondary",
146 | primaryColor: .white,
147 | secondaryColor: .white.opacity(0.8),
148 | backgroundStyle: LinearGradient.weather
149 | )
150 | }
151 | .padding()
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Components/FancyHeaderView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HeaderView.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Kannan Prasad on 05/07/2022.
6 | //
7 |
8 | import SwiftUI
9 | import CachedAsyncImage
10 |
11 | struct FancyHeaderView: View {
12 | private let title: String
13 | private let foregroundImageURLs: [URL]
14 | private let foregroundImageName: String?
15 |
16 | private let foregroundImageWidth: Double = 160
17 | private let aspectRatio = 1.66
18 |
19 | @State private var foregroundGroupViewHeight: CGFloat = .zero
20 |
21 | // MARK: - Initialisers
22 | init(title: String, foregroundImageURLs: [URL] = [], foregroundImageName: String? = nil) {
23 | self.title = title
24 | self.foregroundImageURLs = foregroundImageURLs
25 | self.foregroundImageName = foregroundImageName
26 | }
27 |
28 | var body: some View {
29 | Rectangle()
30 | .foregroundColor(.clear)
31 | .edgesIgnoringSafeArea(.top)
32 | .aspectRatio(aspectRatio, contentMode: .fill)
33 | .background(
34 | createRectangleImage(for: Image(Assets.Image.leedsPlayhouse), aspectRatio: aspectRatio)
35 | .aspectRatio(contentMode: .fill)
36 | .accessibilityHidden(true)
37 | )
38 | .overlay(foregroundGroup,alignment: .center)
39 | .padding(.bottom,foregroundGroupViewHeight/2)
40 | }
41 |
42 | private var foregroundGroup: some View {
43 | GeometryReader { geometry in
44 | VStack(spacing: Padding.cellGap) {
45 | foregroundImages
46 | .frame(width: foregroundImageWidth * Double(foregroundImageCount))
47 | .cornerRadius(Constants.cellRadius)
48 | .shadow(color: shadowColor, radius: 8, x: 0, y: 0)
49 | Text(title)
50 | .foregroundColor(.primary)
51 | .font(.title3.weight(.bold))
52 | .accessibilityAddTraits(.isHeader)
53 | }
54 | .frame(width: geometry.frame(in: .global).width,
55 | height: geometry.frame(in: .global).height)
56 | .offset(y: geometry.size.height/2)
57 | .onAppear {
58 | foregroundGroupViewHeight = geometry.size.height
59 | }
60 | .onChange(of: geometry.size) { newValue in
61 | foregroundGroupViewHeight = newValue.height
62 | }
63 | }
64 | }
65 |
66 | @ViewBuilder
67 | private var foregroundImages: some View {
68 | if foregroundImageURLs.isEmpty == false {
69 | HStack {
70 | ForEach(foregroundImageURLs, id: \.self) { foregroundImageURL in
71 | AsyncImage(url: foregroundImageURL) { phase in
72 | switch phase {
73 | case .empty:
74 | loadingView()
75 | case .success(let image):
76 | createRectangleImage(for: image)
77 | .accessibilityHidden(true)
78 | case .failure(_):
79 | createRectangleImage(for: Image(Assets.Image.swiftLeedsIcon))
80 | @unknown default:
81 | loadingView()
82 | }
83 | }
84 | }
85 | }
86 | } else if let foregroundImageName = foregroundImageName {
87 | createRectangleImage(for: Image(foregroundImageName))
88 | } else {
89 | createRectangleImage(for: Image(Assets.Image.swiftLeedsIcon))
90 | }
91 | }
92 |
93 | private func createRectangleImage(for image: Image, aspectRatio: Double = 1.0) -> some View {
94 | return Rectangle()
95 | .foregroundColor(.clear)
96 | .aspectRatio(aspectRatio, contentMode: .fit)
97 | .background(
98 | image
99 | .resizable()
100 | .aspectRatio(contentMode: .fill)
101 | .transition(.opacity)
102 | )
103 | }
104 |
105 | private func loadingView(aspectRatio: Double = 1.0) -> some View {
106 | return Rectangle()
107 | .foregroundColor(.secondary)
108 | .aspectRatio(aspectRatio, contentMode: .fit)
109 | .overlay(
110 | ProgressView()
111 | )
112 | .progressViewStyle(CircularProgressViewStyle())
113 | }
114 |
115 | private var foregroundImageCount: Int {
116 | foregroundImageURLs.count + (foregroundImageName == nil ? 0 : 1)
117 | }
118 |
119 | private var shadowColor: Color {
120 | Color.black.opacity(1/3)
121 | }
122 | }
123 |
124 | struct FancyHeaderView_Previews: PreviewProvider {
125 | static var previews: some View {
126 | Group {
127 | VStack {
128 | Text(verbatim: "Local Asset")
129 | FancyHeaderView(title: "Some Long Text here",
130 | foregroundImageName: Assets.Image.swiftLeedsIcon)
131 | Text(verbatim: "Remote Data")
132 | FancyHeaderView(title: "Swift Taylor",
133 | foregroundImageURLs: [URL(string: "https://cdn-az.allevents.in/events5/banners/458482c4fc7489448aa3d77f6e2cd5d0553fa5edd7178dbf18cf986d2172eaf2-rimg-w1200-h675-gmir.jpg?v=1655230338")!])
134 |
135 | }
136 | ScrollView {
137 | Text(verbatim: "Local Asset")
138 | VStack {
139 | FancyHeaderView(title: "Kannan Prasad",
140 | foregroundImageName: Assets.Image.swiftLeedsIcon)
141 | }
142 | Text(verbatim: "Remote Data")
143 | FancyHeaderView(title: "Swift Taylor",
144 | foregroundImageURLs: [URL(string: "https://cdn-az.allevents.in/events5/banners/458482c4fc7489448aa3d77f6e2cd5d0553fa5edd7178dbf18cf986d2172eaf2-rimg-w1200-h675-gmir.jpg?v=1655230338")!])
145 | }
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Components/HeaderView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HeaderView.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Alex Logan on 11/07/2022.
6 | //
7 |
8 | import SwiftUI
9 | import CachedAsyncImage
10 |
11 | struct HeaderView: View {
12 | private let title: String
13 | private let imageURL: URL?
14 | private let backgroundURL: URL?
15 | private let imageAssetName: String?
16 | private let backgroundImageAssetName: String?
17 | private let placeholderColor: Color
18 | private let imageBackgroundColor: Color
19 |
20 | @State private var textImageStackSize = CGSize.zero
21 |
22 | private let backgroundImageWidthToHeightRatio: CGFloat = 1.66
23 | private let frontImageHeight: CGFloat = 160
24 | private var textOffset: CGFloat { textImageStackSize.height/2 }
25 |
26 | init(
27 | title: String,
28 | imageURL: URL? = nil,
29 | backgroundURL: URL? = nil,
30 | imageAssetName: String? = nil,
31 | backgroundImageAssetName: String? = nil,
32 | placeholderColor: Color = .white,
33 | imageBackgroundColor: Color = .white
34 | ) {
35 | self.title = title
36 | self.imageURL = imageURL
37 | self.backgroundURL = backgroundURL
38 | self.imageAssetName = imageAssetName
39 | self.backgroundImageAssetName = backgroundImageAssetName
40 | self.placeholderColor = placeholderColor
41 | self.imageBackgroundColor = imageBackgroundColor
42 | }
43 |
44 | var body: some View {
45 | VStack(alignment: .center) {
46 | backgroundImage
47 | .overlay(
48 | imageAndTextStack.measureSize { size in
49 | textImageStackSize = size
50 | },
51 | alignment: .center
52 | )
53 | }
54 | .padding(.bottom, textOffset)
55 | .allowsHitTesting(false)
56 | }
57 |
58 | var text: some View {
59 | VStack(spacing: Padding.cellGap) {
60 | Text(title)
61 | .font(.title3.weight(.semibold))
62 | .foregroundColor(.primary)
63 | .multilineTextAlignment(.center)
64 | .accessibilityLabel(title.noEmojis)
65 | .accessibilityHeading(.h1)
66 | }
67 | }
68 |
69 | var imageAndTextStack: some View {
70 | VStack(spacing: Padding.cellGap) {
71 | frontImage
72 | text
73 | }
74 | .offset(x: 0, y: textOffset)
75 | }
76 |
77 | private var frontImage: some View {
78 | Group {
79 | if let imageAssetName = imageAssetName {
80 | Rectangle()
81 | .foregroundColor(.clear)
82 | .aspectRatio(1.0, contentMode: .fit)
83 | .background(
84 | Image(imageAssetName)
85 | .resizable()
86 | .aspectRatio(1.0, contentMode: .fill)
87 | )
88 | } else {
89 | remoteFrontImage
90 | }
91 | }
92 | .frame(width: frontImageHeight, height: frontImageHeight, alignment: .center)
93 | .accessibilityHidden(true)
94 | .cornerRadius(Constants.cellRadius)
95 | .shadow(color: Color.black.opacity(1/3), radius: 8, x: 0, y: 0)
96 | }
97 |
98 | private var remoteFrontImage: some View {
99 | CachedAsyncImage(
100 | url: imageURL,
101 | content: { image in
102 | Rectangle()
103 | .aspectRatio(backgroundImageWidthToHeightRatio, contentMode: .fill)
104 | .foregroundColor(.clear)
105 | .background(
106 | image
107 | .resizable()
108 | .aspectRatio(contentMode: .fill)
109 | .transition(.opacity)
110 | )
111 | .background(imageBackgroundColor)
112 | .clipped()
113 | .transition(contentTransition)
114 | },
115 | placeholder: {
116 | Rectangle()
117 | .foregroundColor(placeholderColor)
118 | .transition(contentTransition)
119 | .overlay(content: {
120 | ProgressView()
121 | .tint(.white)
122 | .opacity(0.5)
123 | })
124 | }
125 | )
126 | }
127 |
128 | private var backgroundImage: some View {
129 | Group {
130 | if let backgroundImageAssetName = backgroundImageAssetName {
131 | Rectangle()
132 | .foregroundColor(.clear)
133 | .background(
134 | Image(backgroundImageAssetName)
135 | .resizable()
136 | .aspectRatio(contentMode: .fill)
137 | )
138 | .clipped()
139 | } else {
140 | remoteBackgroundImage
141 | }
142 | }
143 | .aspectRatio(backgroundImageWidthToHeightRatio, contentMode: .fit)
144 | .edgesIgnoringSafeArea(.top)
145 | .accessibilityHidden(true)
146 | }
147 |
148 | private var remoteBackgroundImage: some View {
149 | CachedAsyncImage(
150 | url: backgroundURL,
151 | content: { image in
152 | Rectangle()
153 | .aspectRatio(backgroundImageWidthToHeightRatio, contentMode: .fit)
154 | .foregroundColor(.clear)
155 | .background(
156 | image
157 | .resizable()
158 | .aspectRatio(contentMode: .fill)
159 | .transition(.opacity)
160 | )
161 | .background(imageBackgroundColor)
162 | .clipped()
163 | .transition(contentTransition)
164 | },
165 | placeholder: {
166 | Rectangle()
167 | .foregroundColor(placeholderColor)
168 | .transition(contentTransition)
169 | .overlay(content: {
170 | ProgressView()
171 | .tint(.white)
172 | .opacity(0.5)
173 | })
174 | }
175 | )
176 | }
177 |
178 | private var contentTransition: AnyTransition {
179 | .opacity.animation(.spring())
180 | }
181 | }
182 |
183 | struct HeaderView_Previews: PreviewProvider {
184 | static var previews: some View {
185 | VStack {
186 | HeaderView(
187 | title: "Taylor Swift",
188 | imageURL: URL(string: "https://cdn-az.allevents.in/events5/banners/458482c4fc7489448aa3d77f6e2cd5d0553fa5edd7178dbf18cf986d2172eaf2-rimg-w1200-h675-gmir.jpg?v=1655230338"),
189 | backgroundURL: URL(string:"https://www.nycgo.com/images/itineraries/42961/soc_fb_dumbo_spots__facebook.jpg")
190 | )
191 | Text(verbatim: "hey! :)")
192 | }
193 | }
194 |
195 | private var contentTransition: AnyTransition {
196 | .opacity.animation(.spring())
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Components/StackedTileView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StackedTileView.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Alex Logan on 05/07/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | /// Used when there's lots of content to display.
11 | struct StackedTileView: View {
12 | let primaryText: String?
13 | let secondaryText: String?
14 | let primaryColor: Color
15 | let secondaryColor: Color
16 | var backgroundStyle: BackgroundType
17 |
18 | init(
19 | primaryText: String?,
20 | secondaryText: String?,
21 | primaryColor: Color = Color.primary,
22 | secondaryColor: Color = Color.secondary,
23 | backgroundStyle: BackgroundType
24 | ) {
25 | self.primaryText = primaryText
26 | self.secondaryText = secondaryText
27 | self.primaryColor = primaryColor
28 | self.secondaryColor = secondaryColor
29 | self.backgroundStyle = backgroundStyle
30 | }
31 |
32 | init(
33 | primaryText: String?,
34 | secondaryText: String?,
35 | primaryColor: Color = Color.primary,
36 | secondaryColor: Color = Color.secondary,
37 | backgroundStyle: Color = Color.cellBackground
38 | ) where BackgroundType == Color {
39 | self.primaryText = primaryText
40 | self.secondaryText = secondaryText
41 | self.primaryColor = primaryColor
42 | self.secondaryColor = secondaryColor
43 | self.backgroundStyle = backgroundStyle
44 | }
45 |
46 | var body: some View {
47 | VStack(alignment: .leading, spacing: Padding.stackGap) {
48 | if let primaryText = primaryText {
49 | Text(primaryText)
50 | .font(.headline.weight(.semibold))
51 | .foregroundColor(primaryColor)
52 | }
53 |
54 | if let secondaryText = secondaryText {
55 | Text(.init(secondaryText))
56 | .font(.subheadline.weight(.regular))
57 | .foregroundColor(secondaryColor)
58 | }
59 | }
60 | .frame(maxWidth: .infinity, minHeight: Constants.compactCellMinimumHeight, alignment: .leading)
61 | .multilineTextAlignment(.leading)
62 | .padding(Padding.cell)
63 | .background(
64 | backgroundStyle,
65 | in: RoundedRectangle(cornerRadius: Constants.cellRadius)
66 | )
67 | .accessibilityElement(children: .ignore)
68 | .accessibilityLabel(accessibilityLabel)
69 | }
70 |
71 | private var accessibilityLabel: String {
72 | [primaryText?.noEmojis, secondaryText?.noEmojis]
73 | .compactMap { $0 }
74 | .joined(separator: ", ")
75 | }
76 | }
77 |
78 |
79 | struct StackedTileView_Previews: PreviewProvider {
80 | static var previews: some View {
81 | ZStack {
82 | Color(uiColor: .systemGroupedBackground).edgesIgnoringSafeArea(.all)
83 | VStack(spacing: Padding.cellGap) {
84 | StackedTileView(
85 | primaryText: "Primary", secondaryText: "Walkin' through a crowd, the village is aglow\nKaleidoscope of loud heartbeats under coats\nEverybody here wanted somethin' more\nSearchin' for a sound we hadn't heard before"
86 | )
87 | StackedTileView(
88 | primaryText: "Primary",
89 | secondaryText: "And it said\nWelcome to New York, it's been waitin' for you\nWelcome to New York, welcome to New York",
90 | primaryColor: .white,
91 | secondaryColor: .white,
92 | backgroundStyle: .red
93 | )
94 | StackedTileView(
95 | primaryText: "Primary",
96 | secondaryText: "Like any great love, it keeps you guessing\nLike any real love, it's ever-changing\nLike any true love, it drives you crazy\nBut you know you wouldn't change anything, anything, anything",
97 | primaryColor: .white,
98 | secondaryColor: .white.opacity(0.8),
99 | backgroundStyle: LinearGradient(colors: [.blue, .teal], startPoint: .leading, endPoint: .trailing)
100 | )
101 | }
102 | .padding()
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Components/WebView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebView.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 12/09/2022.
6 | //
7 |
8 | import SwiftUI
9 | import WebKit
10 |
11 | struct WebView: UIViewRepresentable {
12 | let urlString: String
13 |
14 | init(url: String) {
15 | urlString = url
16 | }
17 |
18 | func makeUIView(context: Context) -> WKWebView {
19 | let webView = WKWebView()
20 | webView.allowsBackForwardNavigationGestures = true
21 | webView.scrollView.isScrollEnabled = true
22 | return webView
23 | }
24 |
25 | func updateUIView(_ uiView: WKWebView, context: Context) {
26 | guard let url = URL(string: urlString) else { return }
27 | let request = URLRequest(url: url)
28 | uiView.load(request)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Local/BottomSheetView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BottomSheetView.swift
3 | // SwiftLeeds
4 | //
5 | // Created by LUCKY AGARWAL on 25/07/22.
6 | //
7 |
8 | import SwiftUI
9 | import ReadabilityModifier
10 |
11 | struct BottomSheetView: View {
12 | @Binding var isOpen: Bool
13 | @Binding var selectedCategory: Local.LocationCategory?
14 |
15 | @GestureState private var translation: CGFloat = 0
16 |
17 | private let categories: [Local.LocationCategory]
18 | private let error: Error?
19 |
20 | private let maxHeight: CGFloat
21 | private let minHeight: CGFloat
22 |
23 | private var offsetY: CGFloat {
24 | isOpen ? 0 : maxHeight - minHeight
25 | }
26 |
27 | internal init (
28 | isOpen: Binding,
29 | selectedCategory: Binding,
30 | categories: [Local.LocationCategory],
31 | error: Error?,
32 | maxHeight: CGFloat
33 | ){
34 | self.minHeight = maxHeight * Constants.minHeightRatio
35 | self.maxHeight = maxHeight
36 | self.categories = categories.filter { $0.locations.isEmpty == false }
37 | self.error = error
38 | self._isOpen = isOpen
39 | self._selectedCategory = selectedCategory
40 | }
41 |
42 | var body: some View {
43 | GeometryReader { geometry in
44 | VStack {
45 | VStack(spacing: Padding.cellGap) {
46 | Spacer()
47 | SectionHeader(title: "Local",
48 | fontStyle: .title2.weight(.semibold),
49 | foregroundColor: .primary)
50 | .fitToReadableContentGuide(type: .width)
51 | ScrollView {
52 | ForEach(categories) { category in
53 | LocalCell(
54 | label: category.name,
55 | imageName: category.symbolName,
56 | foregroundColor: (category == selectedCategory ? .accent : .cellForeground),
57 | labelFontStyle: .body) {
58 | selectedCategory = category
59 | }
60 | }
61 | .padding(.bottom, Padding.screen)
62 | }
63 | .fitToReadableContentGuide(type: .width)
64 | .transition(.opacity)
65 | }
66 | .padding(Padding.screen)
67 | }
68 | .frame(width: geometry.size.width, height: self.maxHeight, alignment: .top)
69 | .background(Color.background)
70 | .cornerRadius(Constants.bottomSheetRadius)
71 | .frame(height: geometry.size.height + Padding.screen, alignment: .bottom)
72 | .offset(y: max(self.offsetY + self.translation, 0))
73 | .animation(.interactiveSpring(), value: isOpen)
74 | .animation(.interactiveSpring(), value: translation)
75 | .gesture(
76 | DragGesture().updating(self.$translation) { value, state, _ in
77 | state = value.translation.height
78 | }.onEnded { value in
79 | let snapDistance = self.maxHeight * Constants.snapRatio
80 | guard abs(value.translation.height) > snapDistance else {
81 | return
82 | }
83 | self.isOpen = value.translation.height < 0
84 | }
85 | )
86 | .ignoresSafeArea(edges: .bottom)
87 | }
88 | }
89 | }
90 |
91 | struct BottomSheet_Previews: PreviewProvider {
92 | static let items: [Local.LocationCategory] = [
93 | Local.LocationCategory(id: UUID(), name: "Food", symbolName: "takeoutbag.and.cup.and.straw.fill", locations: [.init(id: UUID(), name: "Trinity Kitchen", url: URL(string: "https://trinityleeds.com/shops/trinity-kitchen")!, location: .init(latitude: 53.797378, longitude: -1.545209))]),
94 | Local.LocationCategory(id: UUID(), name: "Drinks", symbolName: "wineglass.fill", locations: [.init(id: UUID(), name: "Brew Society", url: URL(string: "https://www.brewsociety.co.uk/")!, location: .init(latitude: 53.79584058588689, longitude: -1.550339186509128))])
95 | ]
96 |
97 | static var previews: some View {
98 | GeometryReader{ proxy in
99 | BottomSheetView(
100 | isOpen: .constant(true),
101 | selectedCategory: .constant(Self.items.first),
102 | categories: Self.items,
103 | error: nil,
104 | maxHeight: proxy.size.height * Constants.maxHeightRatio
105 | )
106 | .background(.blue)
107 | .previewDevice(PreviewDevice(rawValue: "iPhone 13"))
108 | }
109 | .edgesIgnoringSafeArea(.all)
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Local/LocalCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocalCell.swift
3 | // SwiftLeeds
4 | //
5 | // Created by LUCKY AGARWAL on 25/07/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct LocalCell: View {
11 | let label: String
12 | let imageName: String
13 | let foregroundColor: Color
14 | let labelFontStyle: Font
15 | let onTap: () -> ()
16 |
17 | internal init(label : String,
18 | imageName: String,
19 | foregroundColor: Color = .cellForeground,
20 | labelFontStyle: Font = .headline.weight(.medium),
21 | onTap: @escaping () -> () = {}
22 | ){
23 | self.label = label
24 | self.imageName = imageName
25 | self.foregroundColor = foregroundColor
26 | self.labelFontStyle = labelFontStyle
27 | self.onTap = onTap
28 | }
29 |
30 | var body: some View {
31 | Button(action: onTap) {
32 | HStack {
33 | Text(label)
34 | .font(labelFontStyle)
35 | Spacer()
36 |
37 | Image(uiImage: UIImage(systemName: imageName) ?? UIImage(imageLiteralResourceName: imageName))
38 | .renderingMode(.template)
39 | .frame(width: 30)
40 | }
41 | .padding(Padding.cell)
42 | .frame(minHeight: Constants.cellMinimumHeight)
43 | .foregroundColor(foregroundColor)
44 | .background {
45 | RoundedRectangle(cornerRadius: Constants.cellRadius).fill(Color.cellBackground)
46 | }
47 | }
48 | }
49 | }
50 |
51 | struct LocalCell_Previews: PreviewProvider {
52 | static var previews: some View {
53 | VStack {
54 | LocalCell(label: "Food", imageName: "takeoutbag.and.cup.and.straw.fill")
55 | LocalCell(label: "Coffee", imageName: "cup.and.saucer.fill")
56 | LocalCell(label: "Drink", imageName: "wineglass.fill")
57 | LocalCell(label: "Best of Leeds", imageName: "mappin")
58 | }
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Local/LocalView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocalView.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 25/06/2022.
6 | //
7 |
8 | import SwiftUI
9 | import MapKit
10 |
11 | struct LocalView: View {
12 | @StateObject private var model = LocalViewModel()
13 |
14 | @State private var bottomSheetShown = true
15 | @State private var mapRegion: MKCoordinateRegion = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 53.78613099154973, longitude: -1.5461652186147719), span: MKCoordinateSpan(latitudeDelta: 0.04, longitudeDelta: 0.04))
16 | @State private var selectedLocation: Local.Location?
17 |
18 | var body: some View {
19 | ZStack {
20 | GeometryReader{ geometry in
21 | if let category = model.selectedCategory {
22 | Map(coordinateRegion: $mapRegion, showsUserLocation: true, annotationItems: model.selectedLocations) { location in
23 | MapAnnotation(coordinate: location.location.coordinate) {
24 | Image(uiImage: UIImage(systemName: category.symbolName) ?? UIImage(imageLiteralResourceName: category.symbolName))
25 | .frame(width: 44, height: 44)
26 | .background(
27 | RoundedRectangle(cornerRadius: 6)
28 | .fill(.white)
29 | )
30 | .onTapGesture {
31 | selectedLocation = location
32 |
33 | }
34 | }
35 | }
36 | .ignoresSafeArea()
37 | }
38 |
39 | if let location = selectedLocation {
40 | ZStack {
41 | Color.black.opacity(0.3)
42 | .ignoresSafeArea(.all)
43 | .onTapGesture {
44 | selectedLocation = nil
45 | }
46 |
47 | locationInfoView(category: model.selectedCategory!, location: location)
48 | .padding(.bottom, bottomSheetShown ? geometry.size.height * Constants.maxHeightRatio: 0)
49 | .animation(.easeInOut, value: bottomSheetShown)
50 | }
51 | }
52 |
53 | BottomSheetView(
54 | isOpen: $bottomSheetShown,
55 | selectedCategory: $model.selectedCategory,
56 | categories: model.categories,
57 | error: model.error,
58 | maxHeight: geometry.size.height * Constants.maxHeightRatio
59 | )
60 |
61 | if model.error != nil {
62 | errorView
63 | }
64 | }
65 | }
66 | }
67 |
68 | var errorView: some View {
69 | Rectangle()
70 | .foregroundStyle(.ultraThinMaterial)
71 | .edgesIgnoringSafeArea(.all)
72 | .overlay(
73 | VStack(alignment: .center, spacing: Padding.stackGap) {
74 | Text(verbatim: "Something has gone wrong. Please try again later.")
75 | .font(.subheadline.weight(.medium))
76 | .multilineTextAlignment(.center)
77 | Button(action: { reload() }) {
78 | Text(verbatim: "Reload")
79 | }
80 | }
81 | .padding()
82 | )
83 | }
84 |
85 | private func reload() {
86 | Task(priority: .userInitiated) {
87 | await model.loadData()
88 | }
89 | }
90 |
91 | private func locationInfoView(category: Local.LocationCategory, location: Local.Location) -> some View {
92 | VStack(spacing: 10) {
93 | Image(uiImage: UIImage(systemName: category.symbolName) ?? UIImage(imageLiteralResourceName: category.symbolName))
94 | .renderingMode(.template)
95 | .frame(width: 44, height: 44)
96 |
97 | Text(location.name)
98 |
99 | Button {
100 | UIApplication.shared.open(location.url)
101 | } label: {
102 | Text(verbatim: "View More")
103 | .bold()
104 | .padding(12)
105 | .frame(maxWidth: .infinity)
106 | .foregroundColor(Color.accent)
107 | .background(Color.accent.opacity(0.5))
108 | .clipShape(RoundedRectangle(cornerRadius: 6))
109 | }
110 | }
111 | .padding(10)
112 | .padding(.bottom, 5)
113 | .frame(width: 200)
114 | .foregroundColor(Color.cellForeground)
115 | .background(Color.cellBackground)
116 | .clipShape(RoundedRectangle(cornerRadius: 8))
117 | }
118 | }
119 |
120 | struct LocalView_Previews: PreviewProvider {
121 | static var previews: some View {
122 | LocalView()
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Local/LocalViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocalViewModel.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Alex Logan on 08/08/2022.
6 | //
7 |
8 | import SwiftUI
9 | import MapKit
10 |
11 | class LocalViewModel: ObservableObject {
12 | @Published private(set) var categories: [Local.LocationCategory] = []
13 | @Published private(set) var selectedLocations: [Local.Location] = []
14 |
15 | @Published var selectedCategory: Local.LocationCategory? {
16 | didSet { selectedLocations = selectedCategory?.locations ?? [] }
17 | }
18 |
19 | private(set) var error: Error?
20 |
21 | init() {
22 | Task {
23 | await loadData()
24 | }
25 | }
26 |
27 | func loadData() async {
28 | do {
29 | let localResults = try await URLSession.awaitConnectivity.decode(Requests.local, dateDecodingStrategy: Requests.defaultDateDecodingStratergy)
30 | await updateLocal(localResults)
31 | } catch {
32 | if let cachedResponse = try? await URLSession.shared.cached(Requests.local, dateDecodingStrategy: Requests.defaultDateDecodingStratergy) {
33 | await updateLocal(cachedResponse)
34 | } else {
35 | self.error = error
36 | }
37 | }
38 | }
39 |
40 | @MainActor
41 | private func updateLocal(_ localResults: Local) async {
42 | self.categories = localResults.data
43 | self.selectedCategory = self.categories.first
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/My Conference/ActivityView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ActivityView.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 06/08/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ActivityView: View {
11 | let activity: Activity
12 |
13 | @Environment(\.openURL) var openURL
14 |
15 | var body: some View {
16 | SwiftLeedsContainer {
17 | ScrollView {
18 | content
19 | }
20 | }
21 | .edgesIgnoringSafeArea(.top)
22 | }
23 |
24 | private var content: some View {
25 | VStack(spacing: Padding.stackGap) {
26 | FancyHeaderView(
27 | title: activity.title,
28 | foregroundImageURLs: foregroundImageURLs
29 | )
30 |
31 | StackedTileView(
32 | primaryText: activity.subtitle,
33 | secondaryText: activity.description,
34 | secondaryColor: Color.primary
35 | )
36 | .padding(Padding.screen)
37 | }
38 | }
39 |
40 | private var foregroundImageURLs: [URL] {
41 | if let image = activity.image?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed), let url = URL(string: image) {
42 | return [url]
43 | } else {
44 | return []
45 | }
46 |
47 | }
48 | }
49 |
50 | struct ActivityView_Previews: PreviewProvider {
51 | static var previews: some View {
52 | ActivityView(activity: .lunch)
53 | }
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/My Conference/AnnouncementCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnnouncementCell.swift.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 25/06/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AnnouncementCell: View {
11 | @Environment(\.sizeCategory) var sizeCategory
12 |
13 | let label: String
14 | let value: String
15 | let valueIcon: String
16 | let gradientColors: [Color]
17 |
18 | var body: some View {
19 | HStack {
20 | Text(label)
21 | .font(.headline.weight(.bold))
22 |
23 | Spacer()
24 |
25 | sizeAwareStack {
26 | Image(systemName: valueIcon)
27 | .font(.title.weight(.semibold))
28 | Text(value)
29 | .font(.subheadline.weight(.semibold))
30 | }
31 | }
32 | .foregroundColor(.white)
33 | .padding(Padding.cell)
34 | .frame(minHeight: Constants.cellMinimumHeight)
35 | .background {
36 | RoundedRectangle(cornerRadius: Constants.cellRadius)
37 | .fill(LinearGradient(colors: gradientColors, startPoint: .topLeading, endPoint: .topTrailing))
38 | }
39 | .accessibilityElement(children: .ignore)
40 | .accessibilityLabel("\(label). \(value)")
41 | }
42 |
43 |
44 | // When the text is huge, stack vertically instead to avoid compressing the leading text
45 | @ViewBuilder
46 | func sizeAwareStack(@ViewBuilder content: () -> (Content)) -> some View {
47 | if sizeCategory > .accessibilityLarge {
48 | VStack {
49 | content()
50 | }
51 | } else {
52 | HStack {
53 | content()
54 | }
55 | }
56 | }
57 | }
58 |
59 | struct AnnouncementCell_Previews: PreviewProvider {
60 | static var previews: some View {
61 | VStack(spacing: Padding.cellGap) {
62 | AnnouncementCell(label: "Leeds", value: "26℃", valueIcon: "cloud.sun.fill", gradientColors: [.weatherGradientStart, .weatherGradientEnd])
63 | .previewDisplayName("Weather")
64 |
65 | AnnouncementCell(label: "Get your ticket now", value: "69 days", valueIcon: "calendar.circle", gradientColors: [.buyTicketGradientStart, .buyTicketGradientEnd])
66 | .previewDisplayName("Buy Ticket")
67 | }
68 | .padding(20)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/My Conference/MyConferenceView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MyConferenceView.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 14/11/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct MyConferenceView: View {
11 | @StateObject private var viewModel = MyConferenceViewModel()
12 |
13 | @State private var currentIndex: Int = 0
14 | @Namespace private var namespace
15 |
16 | var body: some View {
17 | NavigationView {
18 | VStack(spacing: 0) {
19 | Divider()
20 |
21 | if viewModel.hasLoaded == false {
22 | ZStack {
23 | Color.clear
24 |
25 | ProgressView()
26 | .progressViewStyle(.circular)
27 | .scaleEffect(2)
28 | }
29 | } else if viewModel.slots.isEmpty {
30 | empty
31 | } else {
32 | schedule
33 | }
34 |
35 | Divider()
36 | }
37 | .background(Color.listBackground)
38 | .navigationTitle("Schedule")
39 | .toolbar {
40 | if let currentEvent = viewModel.currentEvent {
41 | ToolbarItem(placement: .navigationBarTrailing) {
42 | Menu {
43 | ForEach(viewModel.events) { event in
44 | Button(action: { viewModel.updateCurrentEvent(event) }) {
45 | Text(event.name)
46 | }
47 | }
48 | } label: {
49 | HStack {
50 | Text(currentEvent.name)
51 | Image(systemName: "chevron.up.chevron.down")
52 | .font(.caption)
53 |
54 | }
55 | }
56 | .accentColor(Color("AccentColor"))
57 | }
58 | }
59 | }
60 | }
61 | .navigationViewStyle(.stack)
62 | .accentColor(.white)
63 | .task {
64 | try? await viewModel.loadSchedule()
65 | }
66 | }
67 |
68 | private var schedule: some View {
69 | VStack(spacing: 0) {
70 | ViewThatFits {
71 | scheduleHeaders
72 |
73 | ScrollView(.horizontal) {
74 | scheduleHeaders
75 | }
76 | }
77 |
78 | TabView(selection: $currentIndex) {
79 | ForEach(Array(zip(viewModel.days.indices, viewModel.days)),
80 | id: \.0) { index, key in
81 | ScheduleView(slots: viewModel.slots[key] ?? [], showSlido: viewModel.showSlido)
82 | .tag(index)
83 | }
84 | }
85 | .tabViewStyle(.page(indexDisplayMode: .never))
86 | .edgesIgnoringSafeArea(.all)
87 | }
88 | }
89 |
90 | @ViewBuilder
91 | private var scheduleHeaders: some View {
92 | if viewModel.days.count == 3 {
93 | // Temporary solution until new API is ready to support days correctly
94 | HStack(spacing: 20) {
95 | tabBarHeader(title: "Talkshow", index: 0)
96 | tabBarHeader(title: "Day 1", index: 1)
97 | tabBarHeader(title: "Day 2", index: 2)
98 | }
99 | .padding(.horizontal)
100 | .padding(.top)
101 | } else if viewModel.days.count > 1 {
102 | HStack(spacing: 20) {
103 | ForEach(Array(zip(viewModel.days.indices, viewModel.days)), id: \.0) { index, key in
104 | tabBarHeader(title: "Day \(index + 1)", index: index)
105 | }
106 | }
107 | .padding(.horizontal)
108 | .padding(.top)
109 | }
110 | }
111 |
112 | private func tabBarHeader(title: String, index: Int) -> some View {
113 | Button {
114 | currentIndex = index
115 | } label: {
116 | VStack(spacing: 4) {
117 | Text(title)
118 | .font(.system(size: 13, weight: .light, design: .default))
119 |
120 | Text("")
121 | .frame(height: 2)
122 | }
123 | .foregroundColor(.cellForeground)
124 | .overlay(alignment: .bottom) {
125 | if currentIndex == index {
126 | Color.cellForeground
127 | .frame(height: 2)
128 | .matchedGeometryEffect(id: "tabSelectionLine", in: namespace, properties: .frame)
129 | } else {
130 | Color.clear.frame(height: 2)
131 | }
132 |
133 | }
134 | .animation(.spring(), value: currentIndex)
135 | }
136 | .buttonStyle(.plain)
137 | }
138 |
139 | @ViewBuilder
140 | private var tickets: some View {
141 | if let numberOfDaysToConference = viewModel.numberOfDaysToConference {
142 | AnnouncementCell(label: "Get your ticket now!",
143 | value: "\(numberOfDaysToConference) days",
144 | valueIcon: "calendar.circle.fill",
145 | gradientColors: [.accent, .accent])
146 | .previewDisplayName("Buy Ticket")
147 | }
148 | }
149 |
150 | private var empty: some View {
151 | VStack(spacing: 10) {
152 | Spacer()
153 |
154 | Image(systemName: "signpost.right.and.left")
155 | .font(.system(size: 60))
156 |
157 | Text("Come back soon")
158 | .font(.title)
159 |
160 | Text("We're working on filling this schedule")
161 | .font(.subheadline)
162 |
163 | Spacer()
164 | }
165 | .foregroundColor(.cellForeground)
166 | .accessibilityElement(children: .ignore)
167 | .accessibilityLabel("Come back soon. We're working on filling this schedule")
168 | }
169 | }
170 |
171 | struct MyConferenceView_Previews: PreviewProvider {
172 | static var previews: some View {
173 | MyConferenceView()
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/My Conference/MyConferenceViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MyConferenceViewModel.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 01/08/2022.
6 | //
7 |
8 | import Foundation
9 | import Combine
10 | import SwiftUI
11 |
12 | class MyConferenceViewModel: ObservableObject {
13 | @Published private(set) var hasLoaded = false
14 | @Published private(set) var event: Schedule.Event?
15 | @Published private(set) var events: [Schedule.Event] = []
16 | @Published private(set) var days: [String] = []
17 | @Published private(set) var slots: [String: [Schedule.Slot]] = [:]
18 | @Published private(set) var currentEvent: Schedule.Event?
19 |
20 | func loadSchedule() async throws {
21 | do {
22 | let schedule = try await URLSession.awaitConnectivity.decode(Requests.schedule, dateDecodingStrategy: Requests.defaultDateDecodingStratergy)
23 | await updateSchedule(schedule)
24 |
25 | do {
26 | let data = try PropertyListEncoder().encode(slots)
27 | UserDefaults(suiteName: "group.uk.co.swiftleeds")?.setValue(data, forKey: "Slots")
28 | } catch {
29 | throw(error)
30 | }
31 | } catch {
32 | if let cachedResponse = try? await URLSession.shared.cached(Requests.schedule, dateDecodingStrategy: Requests.defaultDateDecodingStratergy) {
33 | await updateSchedule(cachedResponse)
34 | } else {
35 | throw(error)
36 | }
37 | }
38 | }
39 |
40 | @MainActor
41 | private func updateSchedule(_ schedule: Schedule) async {
42 | event = schedule.data.event
43 | events = schedule.data.events.sorted(by: { $0.name < $1.name })
44 |
45 | // Set the event to the current one on first launch
46 | if currentEvent == nil {
47 | currentEvent = event
48 | }
49 |
50 | let individualDates = Set(schedule.data.slots.compactMap { $0.date?.withoutTimeAtConferenceVenue }).sorted(by: (<))
51 | days = individualDates.map { Helper.shortDateFormatter.string(from: $0) }
52 |
53 | for date in individualDates {
54 | let key = Helper.shortDateFormatter.string(from: date)
55 | slots[key] = schedule.data.slots.filter { Calendar.current.compare(date, to: $0.date ?? Date(), toGranularity: .day) == .orderedSame }
56 | }
57 |
58 | hasLoaded = true
59 | }
60 |
61 | private func reloadSchedule() async throws {
62 | guard let currentEvent else { return }
63 |
64 | let schedule = try await URLSession.awaitConnectivity.decode(Requests.schedule(for: currentEvent.id), dateDecodingStrategy: Requests.defaultDateDecodingStratergy, filename: "schedule-\(currentEvent.id.uuidString)")
65 | await updateSchedule(schedule)
66 | }
67 |
68 | var numberOfDaysToConference: Int? {
69 | guard let days = event?.daysUntil else { return nil }
70 |
71 | // Stop showing ticket sales a week before the event
72 | if days > 7 {
73 | return days
74 | } else {
75 | return nil
76 | }
77 | }
78 |
79 | // Only show slido links on the day of the event
80 | var showSlido: Bool {
81 | guard let days = event?.daysUntil else { return false }
82 | return days <= 0 && days >= -1
83 | }
84 |
85 | static let stringDateFormatter: DateFormatter = {
86 | let dateFormatter = DateFormatter()
87 | dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"
88 | return dateFormatter
89 | }()
90 |
91 | func updateCurrentEvent(_ event: Schedule.Event) {
92 | currentEvent = event
93 |
94 | Task {
95 | try? await reloadSchedule()
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/My Conference/ScheduleView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScheduleView.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 21/08/2023.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ScheduleView: View {
11 | let slots: [Schedule.Slot]
12 | let showSlido: Bool
13 |
14 | var body: some View {
15 | ScrollView {
16 | VStack(spacing: Padding.cellGap) {
17 | ForEach(slots) { slot in
18 | if let activity = slot.activity {
19 | NavigationLink {
20 | ActivityView(activity: activity)
21 | } label: {
22 | TalkCell(time: slot.startTime, details: activity.title)
23 | .transition(.opacity)
24 | }
25 | }
26 |
27 | if let presentation = slot.presentation {
28 | NavigationLink {
29 | SpeakerView(presentation: presentation,
30 | showSlido: showSlido)
31 | } label: {
32 | TalkCell(time: slot.startTime,
33 | details: presentation.title,
34 | speakers: presentation.speakers)
35 | .transition(.opacity)
36 | }
37 | }
38 | }
39 | }
40 | .animation(.easeInOut, value: slots)
41 | .padding(Padding.screen)
42 | }
43 | }
44 | }
45 |
46 | struct ScheduleView_Previews: PreviewProvider {
47 | static var previews: some View {
48 | ScheduleView(slots: [], showSlido: true)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/My Conference/SpeakerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpeakerView.swift
3 | // SwiftLeeds
4 | //
5 | // Created by LUCKY AGARWAL on 23/07/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SpeakerView: View {
11 | let presentation: Presentation
12 | let showSlido: Bool
13 |
14 | @State private var showWebSheet = false
15 |
16 | @Environment(\.openURL) var openURL
17 |
18 | var body: some View {
19 | SwiftLeedsContainer {
20 | ScrollView {
21 | content
22 | }
23 | }
24 | .edgesIgnoringSafeArea(.top)
25 | }
26 |
27 | private var content: some View {
28 | VStack(spacing: Padding.stackGap) {
29 | if presentation.speakers.isEmpty == false {
30 | FancyHeaderView(
31 | title: presentation.speakers.joinedNames,
32 | foregroundImageURLs: presentation.speakers.map { URL(string: $0.profileImage)! }
33 | )
34 | }
35 |
36 | VStack(spacing: Padding.screen){
37 | StackedTileView(
38 | primaryText: presentation.title,
39 | secondaryText: presentation.synopsis,
40 | secondaryColor: Color.primary
41 | )
42 |
43 | if let videoURL = presentation.videoURL, videoURL.isEmpty == false {
44 | CommonTileView(
45 | icon: "video.fill",
46 | primaryText: "Watch video",
47 | showChevron: true,
48 | secondaryColor: Color.primary
49 | )
50 | .accessibilityHint("Opens the video")
51 | .accessibilityAddTraits(.isButton)
52 | .onTapGesture {
53 | openURL(URL(string: videoURL)!)
54 | }
55 | }
56 |
57 | if showSlido, presentation.slidoURL?.isEmpty == false {
58 | CommonTileButton(
59 | icon: "questionmark.bubble.fill",
60 | primaryText: "Ask Questions Now",
61 | accessibilityHint: "Opens Slido to allow questions to be asked",
62 | primaryColor: .white,
63 | secondaryColor: .white.opacity(0.8),
64 | backgroundStyle: LinearGradient(gradient: Gradient(colors:[.buyTicketGradientStart, .buyTicketGradientEnd]) ,startPoint: .leading, endPoint: .trailing),
65 | onTap: {
66 | showWebSheet.toggle()
67 | }
68 | )
69 | }
70 |
71 | ForEach(presentation.speakers) { speaker in
72 | StackedTileView(
73 | primaryText: "About\(presentation.speakers.count == 1 ? "" : ": \(speaker.name)")",
74 | secondaryText: speaker.biography,
75 | secondaryColor: Color.primary
76 | )
77 |
78 | if let twitter = speaker.twitter, twitter.isEmpty == false {
79 | CommonTileView(
80 | primaryText: "Twitter",
81 | secondaryText: "@\(twitter)",
82 | secondaryColor: Color.primary
83 | )
84 | .accessibilityHint("Opens twitter for this speaker")
85 | .accessibilityAddTraits(.isButton)
86 | .onTapGesture {
87 | openURL(URL(string: "https://twitter.com/\(twitter)")!)
88 | }
89 | }
90 | }
91 | }
92 | .padding(Padding.screen)
93 | }
94 | .sheet(isPresented: $showWebSheet) {
95 | WebView(url: presentation.slidoURL ?? "")
96 | .edgesIgnoringSafeArea(.bottom)
97 | }
98 | }
99 | }
100 |
101 | struct SpeakerView_Previews: PreviewProvider {
102 | static var previews: some View {
103 | SpeakerView(presentation: .donnyWalls, showSlido: true)
104 | .previewDisplayName("Donny Wals")
105 |
106 | SpeakerView(presentation: .skyBet, showSlido: true)
107 | .previewDisplayName("Sky Bet")
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/My Conference/TalkCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TalkCell.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 25/06/2022.
6 | //
7 |
8 | import SwiftUI
9 | import CachedAsyncImage
10 |
11 | struct TalkCell: View {
12 | private let time: String
13 | private let details: String
14 | private let isNext: Bool
15 | private let speakers: [Speaker]
16 | private let gradientColors: [Color]?
17 |
18 | @Environment(\.colorScheme) var colorScheme
19 |
20 | init(time: String, details: String, isNext: Bool = false, speakers: [Speaker] = [], gradientColors: [Color]? = nil) {
21 | self.time = time
22 | self.details = details
23 | self.isNext = isNext
24 | self.speakers = speakers
25 | self.gradientColors = gradientColors
26 | }
27 |
28 | var body: some View {
29 | VStack(alignment: .leading, spacing: 6) {
30 | timeLabel(time)
31 |
32 | HStack {
33 | VStack(alignment: .leading, spacing: 8) {
34 | if speakers.isEmpty == false {
35 | HStack {
36 | VStack(alignment: .leading, spacing: 4) {
37 | Text(speakers.joinedNames)
38 | .font(.headline.weight(.medium))
39 | .multilineTextAlignment(.leading)
40 |
41 | Text(speakers.joinedOrganisations)
42 | .font(.subheadline.weight(.medium))
43 | .opacity(0.6)
44 | }
45 |
46 | Spacer()
47 |
48 | HStack(spacing: -5) {
49 | ForEach(speakers) { speaker in
50 | CachedAsyncImage(
51 | url: URL(string: speaker.profileImage),
52 | content: { image in
53 | image
54 | .resizable()
55 | .aspectRatio(contentMode: .fill)
56 | .frame(maxWidth: 40, maxHeight: 40)
57 | .clipShape(Circle())
58 | },
59 | placeholder: {
60 | Circle()
61 | .fill(.white)
62 | .opacity(0.3)
63 | .frame(maxWidth: 40, maxHeight: 40)
64 | .clipShape(Circle())
65 | }
66 | )
67 | }
68 | }
69 | }
70 | }
71 |
72 | HStack {
73 | Text(details)
74 | .font(.body.weight(.regular))
75 | .multilineTextAlignment(.leading)
76 | Spacer()
77 | }
78 | }
79 | .padding(.trailing, 2)
80 |
81 | Image(systemName: "chevron.right")
82 | }
83 | .padding(Padding.cell)
84 | .frame(maxWidth: .infinity)
85 | .foregroundColor(isNext ? .white : .cellForeground)
86 | .background {
87 | if let gradientColors = gradientColors {
88 | RoundedRectangle(cornerRadius: Constants.cellRadius)
89 | .fill(LinearGradient(colors: gradientColors, startPoint: .topLeading, endPoint: .topTrailing))
90 | } else {
91 | RoundedRectangle(cornerRadius: Constants.cellRadius)
92 | .strokeBorder(Color.cellBorder)
93 | }
94 | }
95 | }
96 | .accessibilityElement(children: .ignore)
97 | .accessibilityLabel(accessibilityLabel)
98 | }
99 |
100 | private func timeLabel(_ value: String) -> some View {
101 | HStack(spacing: 7) {
102 | Image("Clock")
103 |
104 | Text(value)
105 | .foregroundColor(.cellForeground)
106 |
107 | Spacer()
108 | }
109 | .padding(.leading, 4)
110 | }
111 |
112 | private var accessibilityLabel: String {
113 | [time, speakers.joinedNames, speakers.joinedOrganisations, details.noEmojis]
114 | .filter { $0.isEmpty == false }
115 | .joined(separator: ", ")
116 | }
117 | }
118 |
119 | struct TalkCell_Previews: PreviewProvider {
120 | static var previews: some View {
121 | VStack(spacing: Padding.cellGap) {
122 | TalkCell(time: "11:00", details: Presentation.donnyWalls.title, speakers: Presentation.donnyWalls.speakers)
123 |
124 | TalkCell(time: "12:00", details: "Lunch")
125 |
126 | TalkCell(time: "1:00", details: Presentation.skyBet.title, speakers: Presentation.skyBet.speakers)
127 | }
128 | .padding(Padding.screen)
129 | .background(Color.listBackground)
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Sponsors/SponsorTileView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SponsorTileView.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Alex Logan on 01/07/2022.
6 | //
7 |
8 | import SwiftUI
9 | import CachedAsyncImage
10 |
11 | struct SponsorTileView: View {
12 | let sponsor: Sponsor
13 |
14 | @Environment(\.openURL) private var openURL
15 |
16 | var body: some View {
17 | Button(action: { openURL(sponsor.url) }) {
18 | VStack(alignment: .leading, spacing: 0) {
19 | image
20 | .padding(16)
21 |
22 | text
23 |
24 | if sponsor.jobs.isEmpty == false {
25 | Text("JOBS")
26 | .font(.caption)
27 | .fontWeight(.thin)
28 | .padding(Padding.cell)
29 | }
30 |
31 | ForEach(sponsor.jobs) { job in
32 | VStack(spacing: 0) {
33 | Divider()
34 |
35 | CommonTileButton(primaryText: job.title,
36 | subtitleText: job.location,
37 | accessibilityHint: "Opens a web site showing more details about the job",
38 | showChevron: true,
39 | backgroundStyle: Color.cellBackground)
40 | {
41 | openURL(job.url)
42 | }
43 | .foregroundColor(.secondary)
44 | }
45 | }
46 | }
47 | }
48 | .background(Color.cellBackground, in: contentShape)
49 | .buttonStyle(SquishyButtonStyle())
50 | }
51 |
52 | private var image: some View {
53 | CachedAsyncImage(
54 | url: URL(string: sponsor.image),
55 | content: { image in
56 | Rectangle()
57 | .aspectRatio(1.66, contentMode: .fill)
58 | .foregroundColor(.clear)
59 | .background(
60 | image
61 | .resizable()
62 | .aspectRatio(contentMode: .fit)
63 | .transition(.opacity)
64 | )
65 | .background(Color.cellBackground)
66 | .clipped()
67 | .transition(contentTransition)
68 | },
69 | placeholder: {
70 | Rectangle()
71 | .foregroundColor(.cellBackground)
72 | .transition(contentTransition)
73 | .overlay(content: {
74 | ProgressView()
75 | .tint(.white)
76 | .opacity(0.5)
77 | })
78 | }
79 | )
80 | .aspectRatio(1.66, contentMode: .fit)
81 | .accessibilityHidden(true)
82 | }
83 |
84 | private var text: some View {
85 | VStack(alignment: .leading, spacing: 4) {
86 | Text(sponsor.name)
87 | .foregroundColor(.primary)
88 | .font(.subheadline.weight(.medium))
89 |
90 | if sponsor.subtitle.isEmpty == false {
91 | Text(sponsor.subtitle)
92 | .foregroundColor(.secondary)
93 | .font(.subheadline.weight(.regular))
94 | }
95 | }
96 | .accessibilityElement(children: .ignore)
97 | .accessibilityLabel(
98 | "Sponsor, \(sponsor.name), \(sponsor.subtitle)"
99 | )
100 | .padding()
101 | .frame(minHeight: 55)
102 | }
103 |
104 | private var contentShape: some Shape {
105 | RoundedRectangle(cornerRadius: Constants.cellRadius, style: .continuous)
106 | }
107 |
108 | private var contentTransition: AnyTransition {
109 | .opacity.animation(.spring())
110 | }
111 |
112 | private func openURL(_ urlString: String) {
113 | guard let link = URL(string: urlString) else { return }
114 | openURL(link)
115 | }
116 | }
117 |
118 | struct SponsorTileView_Previews: PreviewProvider {
119 | static var previews: some View {
120 | ZStack {
121 | Color.background.edgesIgnoringSafeArea(.all)
122 |
123 | VStack {
124 | SponsorTileView(
125 | sponsor: .sample
126 | )
127 | }
128 | .padding()
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Sponsors/SponsorsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SponsorsView.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Muralidharan Kathiresan on 25/06/23.
6 | //
7 |
8 | import SwiftUI
9 | import ReadabilityModifier
10 |
11 | struct SponsorsView: View {
12 | @StateObject private var viewModel = SponsorsViewModel()
13 |
14 | var body: some View {
15 | SwiftLeedsContainer {
16 | content
17 | }
18 | .edgesIgnoringSafeArea(.top)
19 | }
20 |
21 | private var content: some View {
22 | List {
23 | ForEach(viewModel.sections) { section in
24 | switch section.type {
25 | case .platinum:
26 | Section(header: sectionHeader(for: section.type)) {
27 | ForEach(section.sponsors) { sponsor in
28 | sponsorTile(for: sponsor)
29 | .listRowBackground(Color.clear)
30 | .listRowInsets(EdgeInsets())
31 | .padding(.bottom, Padding.cellGap )
32 | }
33 | }
34 | case .gold, .silver:
35 | Section(header: sectionHeader(for: section.type)) {
36 | grid(for: section.sponsors)
37 | .listRowBackground(Color.clear)
38 | .listRowInsets(EdgeInsets())
39 | }
40 | }
41 | }
42 | }
43 | .padding(.top, 50)
44 | .scrollIndicators(.hidden)
45 | .scrollContentBackground(.hidden)
46 | .fitToReadableContentGuide(type: .width)
47 | .task {
48 | try? await viewModel.loadSponsors()
49 | }
50 | }
51 | }
52 |
53 | private extension SponsorsView {
54 | func sectionHeader(for sponsorLevel: SponsorLevel) -> some View {
55 | Text("\(sponsorLevel.rawValue.capitalized) Sponsors")
56 | .font(.callout.weight(.semibold))
57 | .foregroundColor(.secondary)
58 | .frame(maxWidth:.infinity, alignment: .leading)
59 | .accessibilityAddTraits(.isHeader)
60 | .textCase(nil)
61 | .padding(.bottom, 8)
62 | }
63 |
64 | func sponsorTile(for sponsor: Sponsor) -> some View {
65 | SponsorTileView(sponsor: sponsor)
66 | }
67 |
68 | func grid(for sponsors: [Sponsor]) -> some View {
69 | let columns = [
70 | GridItem(.flexible()), GridItem(.flexible())
71 | ]
72 |
73 | return LazyVGrid(
74 | columns: columns,
75 | alignment: .leading,
76 | spacing: Padding.cellGap,
77 | pinnedViews: []) {
78 | ForEach(sponsors, id: \.self) { sponsor in
79 | sponsorTile(for: sponsor)
80 | }
81 | }.foregroundColor(.clear)
82 | }
83 | }
84 |
85 | struct SponsorsView_Previews: PreviewProvider {
86 | static var previews: some View {
87 | SwiftLeedsContainer {
88 | ScrollView {
89 | SponsorsView()
90 | }
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Sponsors/SponsorsViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SponsorsViewModel.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Muralidharan Kathiresan on 25/06/23.
6 | //
7 |
8 | import Foundation
9 | import Combine
10 | import SwiftUI
11 |
12 | final class SponsorsViewModel: ObservableObject {
13 | @Published private(set) var sections: [Section] = [Section]()
14 |
15 | struct Section: Identifiable {
16 | let type: SponsorLevel
17 | let sponsors: [Sponsor]
18 | var id : String { type.rawValue }
19 | }
20 |
21 | func loadSponsors() async throws {
22 | do {
23 | let sponsors = try await URLSession.awaitConnectivity.decode(Requests.sponsors, dateDecodingStrategy: Requests.defaultDateDecodingStratergy)
24 | await updateSponsors(sponsors)
25 | } catch {
26 | if let cachedResponse = try? await URLSession.shared.cached(Requests.sponsors, dateDecodingStrategy: Requests.defaultDateDecodingStratergy) {
27 | await updateSponsors(cachedResponse)
28 | } else {
29 | throw(error)
30 | }
31 | }
32 | }
33 |
34 | @MainActor
35 | private func updateSponsors(_ sponsors: Sponsors) async {
36 | var sections: [Section] = [Section]()
37 | let sponsors = sponsors.data
38 |
39 | let platinumSponsors = sponsors
40 | .filter {$0.sponsorLevel == .platinum}
41 | .compactMap { $0 }
42 | if !platinumSponsors.isEmpty {
43 | sections.append(Section(type: .platinum, sponsors: platinumSponsors))
44 | }
45 |
46 | let goldSponsors = sponsors
47 | .filter {$0.sponsorLevel == .gold}
48 | .compactMap { $0 }
49 | if !goldSponsors.isEmpty {
50 | sections.append(Section(type: .gold, sponsors: goldSponsors))
51 | }
52 |
53 | let silverSponsors = sponsors
54 | .filter {$0.sponsorLevel == .silver}
55 | .compactMap { $0 }
56 | if !silverSponsors.isEmpty {
57 | sections.append(Section(type: .silver, sponsors: silverSponsors))
58 | }
59 |
60 | self.sections = sections
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Tab/SidebarMainView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SidebarMainView.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Karim Ebrahem on 26/06/2023.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SidebarMainView: View {
11 | @EnvironmentObject var appState: AppState
12 |
13 | var body: some View {
14 | NavigationSplitView {
15 | SidebarView()
16 | } detail: {
17 | switch appState.selectedTab {
18 | case .conference:
19 | MyConferenceView()
20 | case .about:
21 | AboutView()
22 | case .location:
23 | LocalView()
24 | case .sponsors:
25 | SponsorsView()
26 | }
27 | }
28 |
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Tab/SidebarView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SidebarView.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Karim Ebrahem on 26/06/2023.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SidebarView: View {
11 | @EnvironmentObject var appState: AppState
12 |
13 | var body: some View {
14 | List {
15 | NavigationLink(destination: MyConferenceView().onAppear {
16 | appState.selectedTab = .conference
17 | }) {
18 | Label("My Conference", systemImage: "person.fill")
19 | }
20 | NavigationLink(destination: LocalView().onAppear {
21 | appState.selectedTab = .location
22 | }) {
23 | Label("Local", systemImage: "map.fill")
24 | }
25 | NavigationLink(destination: AboutView().onAppear {
26 | appState.selectedTab = .about
27 | }) {
28 | Label("About", systemImage: "info.circle")
29 | }
30 | NavigationLink(destination: SponsorsView().onAppear {
31 | appState.selectedTab = .sponsors
32 | }) {
33 | Label("Sponsors", systemImage: "sparkles")
34 | }
35 | }
36 | .listStyle(.sidebar)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Tab/Tabs.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Tabs.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Matthew Gallagher on 25/06/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct Tabs: View {
11 | @Environment(\.horizontalSizeClass) private var horizontalSizeClass
12 |
13 | var body: some View {
14 | GeometryReader { ruler in
15 | if ruler.size.width < ruler.size.height || horizontalSizeClass == .compact {
16 | TabsMainView()
17 | } else {
18 | SidebarMainView()
19 | }
20 | }
21 | }
22 | }
23 |
24 | struct Tabs_Previews: PreviewProvider {
25 | static var previews: some View {
26 | Tabs()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/SwiftLeeds/Views/Tab/TabsMainView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabsMainView.swift
3 | // SwiftLeeds
4 | //
5 | // Created by Karim Ebrahem on 26/06/2023.
6 | //
7 |
8 | import SwiftUI
9 | import ReadabilityModifier
10 |
11 | struct TabsMainView: View {
12 | @EnvironmentObject var appState: AppState
13 |
14 | var body: some View {
15 | TabView(selection: $appState.selectedTab) {
16 | MyConferenceView()
17 | .tabItem {
18 | Label("My Conference", systemImage: "person.fill")
19 | }
20 | .tag(TabItems.conference)
21 |
22 | LocalView()
23 | .tabItem {
24 | Label("Local", systemImage: "map.fill")
25 | }
26 | .tag(TabItems.location)
27 |
28 | AboutView()
29 | .tabItem {
30 | Label("About", systemImage: "info.circle")
31 | }
32 | .tag(TabItems.about)
33 | SponsorsView()
34 | .tabItem {
35 | Label("Sponsors", systemImage: "sparkles")
36 | }
37 | .tag(TabItems.sponsors)
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/SwiftLeedsAppClip/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/SwiftLeedsAppClip/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/SwiftLeedsAppClip/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftLeedsAppClip/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // SwiftLeedsAppClip
4 | //
5 | // Created by Muralidharan Kathiresan on 03/10/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ContentView: View {
11 | var body: some View {
12 | VStack {
13 | Image(systemName: "globe")
14 | .imageScale(.large)
15 | .foregroundColor(.accentColor)
16 | Text("Hello, world App Clip!")
17 | }
18 | .padding()
19 | }
20 | }
21 |
22 | struct ContentView_Previews: PreviewProvider {
23 | static var previews: some View {
24 | ContentView()
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/SwiftLeedsAppClip/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSAppClip
6 |
7 | NSAppClipRequestEphemeralUserNotification
8 |
9 | NSAppClipRequestLocationConfirmation
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/SwiftLeedsAppClip/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftLeedsAppClip/SwiftLeedsAppClip.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.developer.associated-domains
6 |
7 | appclips:swiftleeds.co.uk
8 |
9 | com.apple.developer.parent-application-identifiers
10 |
11 | $(AppIdentifierPrefix)uk.co.swiftleeds.SwiftLeeds
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/SwiftLeedsAppClip/SwiftLeedsAppClipApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftLeedsAppClipApp.swift
3 | // SwiftLeedsAppClip
4 | //
5 | // Created by Muralidharan Kathiresan on 03/10/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct SwiftLeedsAppClipApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | MyConferenceView()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/SwiftLeedsTests/SwiftLeedsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftLeedsTests.swift
3 | // SwiftLeedsTests
4 | //
5 | // Created by Matthew Gallagher on 14/11/2021.
6 | //
7 |
8 | import XCTest
9 | @testable import SwiftLeeds
10 |
11 | class SwiftLeedsTests: XCTestCase {
12 | override func setUpWithError() throws {}
13 | }
14 |
--------------------------------------------------------------------------------
/SwiftLeedsUITests/SwiftLeedsUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftLeedsUITests.swift
3 | // SwiftLeedsUITests
4 | //
5 | // Created by Matthew Gallagher on 14/11/2021.
6 | //
7 |
8 | import XCTest
9 |
10 | class SwiftLeedsUITests: XCTestCase {
11 | override func setUpWithError() throws {
12 | // Put setup code here. This method is called before the invocation of each test method in the class.
13 |
14 | // In UI tests it is usually best to stop immediately when a failure occurs.
15 | continueAfterFailure = false
16 |
17 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
18 | }
19 |
20 | override func tearDownWithError() throws {
21 | // Put teardown code here. This method is called after the invocation of each test method in the class.
22 | }
23 |
24 | /*func testExample() throws {
25 | let app = XCUIApplication()
26 | app.launch()
27 | }*/
28 | }
29 |
--------------------------------------------------------------------------------
/SwiftLeedsUITests/SwiftLeedsUITestsLaunchTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftLeedsUITestsLaunchTests.swift
3 | // SwiftLeedsUITests
4 | //
5 | // Created by Matthew Gallagher on 14/11/2021.
6 | //
7 |
8 | import XCTest
9 |
10 | class SwiftLeedsUITestsLaunchTests: XCTestCase {
11 | override class var runsForEachTargetApplicationUIConfiguration: Bool {
12 | true
13 | }
14 |
15 | override func setUpWithError() throws {
16 | continueAfterFailure = false
17 | }
18 |
19 | /*func testLaunch() throws {
20 | let app = XCUIApplication()
21 | app.launch()
22 |
23 | // Insert steps here to perform after app launch but before taking a screenshot,
24 | // such as logging into a test account or navigating somewhere in the app
25 |
26 | let attachment = XCTAttachment(screenshot: app.screenshot())
27 | attachment.name = "Launch Screen"
28 | attachment.lifetime = .keepAlways
29 | add(attachment)
30 | }*/
31 | }
32 |
--------------------------------------------------------------------------------
/SwiftLeedsWidget/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/SwiftLeedsWidget/Assets.xcassets/AppIcon.appiconset/AppIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftLeeds/swiftleeds-ios/3961b7a7ec88cdfdc34d520864ffad08ef1c1336/SwiftLeedsWidget/Assets.xcassets/AppIcon.appiconset/AppIcon.png
--------------------------------------------------------------------------------
/SwiftLeedsWidget/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "AppIcon.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | }
9 | ],
10 | "info" : {
11 | "author" : "xcode",
12 | "version" : 1
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/SwiftLeedsWidget/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftLeedsWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/SwiftLeedsWidget/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSExtension
6 |
7 | NSExtensionPointIdentifier
8 | com.apple.widgetkit-extension
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/SwiftLeedsWidget/SwiftLeedsMediumWidgetView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftLeedsMediumWidgetView.swift
3 | // SwiftLeedsWidgetExtension
4 | //
5 | // Created by karim ebrahim on 09/09/2022.
6 | //
7 |
8 | import SwiftUI
9 | import WidgetKit
10 |
11 | struct SwiftLeedsMediumWidgetView: View {
12 |
13 | // MARK: - Pivate Properties
14 |
15 | private let slot: Schedule.Slot
16 |
17 | // MARK: - Init
18 |
19 | init(slot: Schedule.Slot) {
20 | self.slot = slot
21 | }
22 |
23 | // MARK: - Body View
24 |
25 | var body: some View {
26 | buildSlotView(for: slot)
27 | }
28 | }
29 |
30 | // MARK: - View Builders
31 |
32 | extension SwiftLeedsMediumWidgetView {
33 | @ViewBuilder
34 | private func buildSlotView(for slot: Schedule.Slot) -> some View {
35 | if let activity = slot.activity {
36 | slotView(time: slot.startTime, speaker: activity.title, details: activity.description!)
37 | }
38 |
39 | if let presentation = slot.presentation {
40 | let speakers = presentation.speakers.joinedNames
41 | slotView(time: slot.startTime, speaker: speakers, details: presentation.title)
42 | }
43 | }
44 |
45 | private func slotView(time: String, speaker: String = "", details: String) -> some View {
46 | ZStack {
47 | Color.background
48 | contentView(time: time, speaker: speaker, details: details)
49 | }
50 | }
51 |
52 | private func contentView(time: String, speaker: String = "", details: String) -> some View {
53 | VStack(alignment: .leading) {
54 | HStack {
55 | logoView
56 | Spacer()
57 | ZStack {
58 | RoundedRectangle(cornerRadius: 14)
59 | .fill(Color.cellForeground.opacity(0.1))
60 | .frame(width: 100, height: 30, alignment: .center)
61 |
62 | Text(verbatim: "Up Next")
63 | .font(.system(size: 14, weight: .bold, design: .monospaced))
64 | }
65 | }
66 | Spacer()
67 | VStack(alignment: .leading, spacing: 4) {
68 | Text(time)
69 | .font(.system(size: WidgetConstants.timeFontSize, weight: .medium))
70 | VStack(alignment: .leading) {
71 | Text(speaker)
72 | .font(.system(size: WidgetConstants.titleFontSize, weight: .bold, design: .default))
73 | .foregroundColor(Color.accent)
74 | Text(details)
75 | .font(.system(size: WidgetConstants.detailsFontSize, weight: .regular, design: .default))
76 | .lineLimit(2)
77 | }
78 | }
79 | }
80 | .padding(16)
81 | .frame(maxWidth: .infinity, alignment: .leading)
82 | }
83 |
84 | private var logoView: some View {
85 | Image(Assets.Image.swiftLeedsIconWithNoBackground)
86 | .resizable()
87 | .aspectRatio(contentMode: .fill)
88 | .transition(.opacity)
89 | .frame(width: WidgetConstants.logoImageWidth, height: WidgetConstants.logoImageHeight, alignment: .center)
90 | }
91 | }
92 |
93 | // MARK: - Widget Previews
94 |
95 | struct SwiftLeedsMediumWidgetView_Previews: PreviewProvider {
96 | static var previews: some View {
97 | SwiftLeedsMediumWidgetView(slot: Schedule.Slot(id: UUID(), date: Date(), startTime: "11:00 AM", duration: 1, activity: nil, presentation: Presentation.donnyWalls))
98 | .previewContext(WidgetPreviewContext(family: .systemMedium))
99 |
100 | SwiftLeedsMediumWidgetView(slot: Schedule.Slot(id: UUID(), date: Date(), startTime: "11:00 AM", duration: 1, activity: nil, presentation: Presentation.donnyWalls))
101 | .environment(\.colorScheme, .dark)
102 | .previewContext(WidgetPreviewContext(family: .systemMedium))
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/SwiftLeedsWidget/SwiftLeedsSmallWidgetView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftLeedsSmallWidgetView.swift
3 | // SwiftLeedsWidgetExtension
4 | //
5 | // Created by karim ebrahim on 08/09/2022.
6 | //
7 |
8 | import SwiftUI
9 | import WidgetKit
10 |
11 | struct SwiftLeedsSmallWidgetView: View {
12 |
13 | // MARK: - Pivate Properties
14 |
15 | private let slot: Schedule.Slot
16 |
17 | // MARK: - Init
18 |
19 | init(slot: Schedule.Slot) {
20 | self.slot = slot
21 | }
22 |
23 | // MARK: - Body View
24 |
25 | var body: some View {
26 | buildSlotView(for: slot)
27 | }
28 | }
29 |
30 | // MARK: - Widget Previews
31 |
32 | extension SwiftLeedsSmallWidgetView {
33 | @ViewBuilder
34 | private func buildSlotView(for slot: Schedule.Slot) -> some View {
35 | if let activity = slot.activity {
36 | slotView(time: slot.startTime, speaker: activity.title, details: activity.description!)
37 | }
38 |
39 | if let presentation = slot.presentation {
40 | let speakers = presentation.speakers.joinedNames
41 | slotView(time: slot.startTime, speaker: speakers, details: presentation.title)
42 | }
43 | }
44 |
45 | private func slotView(time: String, speaker: String = "", details: String) -> some View {
46 | ZStack {
47 | Color.background
48 | contentView(time: time, speaker: speaker, details: details)
49 | }
50 | }
51 |
52 | private func contentView(time: String, speaker: String = "", details: String) -> some View {
53 | VStack(alignment: .leading) {
54 | logoView
55 | Spacer()
56 | VStack(alignment: .leading, spacing: 4) {
57 | Text(time)
58 | .font(.system(size: WidgetConstants.timeFontSize, weight: .medium))
59 | VStack(alignment: .leading) {
60 | Text(speaker)
61 | .font(.system(size: WidgetConstants.titleFontSize, weight: .bold, design: .default))
62 | .foregroundColor(Color.accent)
63 | .lineLimit(2)
64 | Text(details)
65 | .font(.system(size: WidgetConstants.detailsFontSize, weight: .regular, design: .default))
66 | .lineLimit(2)
67 | }
68 | }
69 | }
70 | .padding(16)
71 | .frame(maxWidth: .infinity, alignment: .leading)
72 | }
73 |
74 | private var logoView: some View {
75 | Image(Assets.Image.swiftLeedsIconWithNoBackground)
76 | .resizable()
77 | .aspectRatio(contentMode: .fill)
78 | .transition(.opacity)
79 | .frame(width: WidgetConstants.logoImageWidth, height: WidgetConstants.logoImageHeight, alignment: .center)
80 | }
81 | }
82 |
83 | struct SwiftLeedsSmallWidgetView_Previews: PreviewProvider {
84 | static var previews: some View {
85 | SwiftLeedsSmallWidgetView(slot: Schedule.Slot(id: UUID(), date: Date(), startTime: "11:00 AM", duration: 1, activity: nil, presentation: Presentation.skyBet))
86 | .previewContext(WidgetPreviewContext(family: .systemSmall))
87 |
88 | SwiftLeedsSmallWidgetView(slot: Schedule.Slot(id: UUID(), date: Date(), startTime: "11:00 AM", duration: 1, activity: nil, presentation: Presentation.skyBet))
89 | .environment(\.colorScheme, .dark)
90 | .previewContext(WidgetPreviewContext(family: .systemSmall))
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/SwiftLeedsWidget/SwiftLeedsWidget.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftLeedsWidget.swift
3 | // SwiftLeedsWidget
4 | //
5 | // Created by karim ebrahim on 05/09/2022.
6 | //
7 |
8 | import WidgetKit
9 | import SwiftUI
10 |
11 | @main
12 | struct SwiftLeedsWidget: Widget {
13 | let kind: String = "SwiftLeedsWidget"
14 |
15 | var body: some WidgetConfiguration {
16 | StaticConfiguration(kind: kind, provider: Provider()) { entry in
17 | SwiftLeedsWidgetEntryView(entry: entry)
18 | }
19 | .configurationDisplayName("SwiftLeeds What's up next?")
20 | .description("This widget to know what is the next talk on our stage.")
21 | .supportedFamilies([.systemSmall, .systemMedium])
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/SwiftLeedsWidget/SwiftLeedsWidgetEntryView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftLeedsWidgetEntryView.swift
3 | // SwiftLeedsWidgetExtension
4 | //
5 | // Created by Karim Ebrahem on 11/09/2022.
6 | //
7 |
8 | import WidgetKit
9 | import SwiftUI
10 |
11 | struct SwiftLeedsWidgetEntryView : View {
12 | var entry: Provider.Entry
13 | @Environment(\.widgetFamily) var family
14 |
15 | var body: some View {
16 | if family == .systemSmall {
17 | SwiftLeedsSmallWidgetView(slot: entry.slot)
18 | } else if family == .systemMedium {
19 | SwiftLeedsMediumWidgetView(slot: entry.slot)
20 | }
21 | }
22 | }
23 |
24 | struct SwiftLeedsWidget_Previews: PreviewProvider {
25 | static var previews: some View {
26 | SwiftLeedsWidgetEntryView(entry: SwiftLeedsWidgetEntry(date: Date(), slot: Schedule.Slot(id: UUID(), date: Date(), startTime: "11:00 AM", duration: 1, activity: Activity.lunch, presentation: Presentation.donnyWalls)))
27 | .previewContext(WidgetPreviewContext(family: .systemSmall))
28 |
29 | SwiftLeedsWidgetEntryView(entry: SwiftLeedsWidgetEntry(date: Date(), slot: Schedule.Slot(id: UUID(), date: Date(), startTime: "11:00 AM", duration: 1, activity: Activity.lunch, presentation: Presentation.donnyWalls)))
30 | .previewContext(WidgetPreviewContext(family: .systemMedium))
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/SwiftLeedsWidget/WidgetSetup/SwiftLeedsWidgetEntry.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftLeedsWidgetEntry.swift
3 | // SwiftLeedsWidgetExtension
4 | //
5 | // Created by Karim Ebrahem on 11/09/2022.
6 | //
7 |
8 | import WidgetKit
9 | import SwiftUI
10 |
11 | struct SwiftLeedsWidgetEntry: TimelineEntry {
12 | var date: Date
13 | let slot: Schedule.Slot
14 | }
15 |
--------------------------------------------------------------------------------
/SwiftLeedsWidget/WidgetSetup/TimeineProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TimelineProvider.swift
3 | // SwiftLeedsWidgetExtension
4 | //
5 | // Created by Karim Ebrahem on 11/09/2022.
6 | //
7 |
8 | import WidgetKit
9 | import SwiftUI
10 |
11 | struct Provider: TimelineProvider {
12 | func placeholder(in context: Context) -> SwiftLeedsWidgetEntry {
13 | SwiftLeedsWidgetEntry(date: Date(), slot: Schedule.Slot(id: UUID(), date: Date(), startTime: "11:00 AM", duration: 1, activity: nil, presentation: Presentation.donnyWalls))
14 | }
15 |
16 | func getSnapshot(in context: Context, completion: @escaping (SwiftLeedsWidgetEntry) -> ()) {
17 | let entry = SwiftLeedsWidgetEntry(date: Date(), slot: Schedule.Slot(id: UUID(), date: Date(), startTime: "11:00 AM", duration: 1, activity: nil, presentation: Presentation.donnyWalls))
18 | completion(entry)
19 | }
20 |
21 | func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) {
22 | var entries: [SwiftLeedsWidgetEntry] = []
23 | var slots: [Schedule.Slot] = []
24 |
25 | do {
26 | if let data = UserDefaults(suiteName: "group.uk.co.swiftleeds")?.data(forKey: "Slots") {
27 | slots = try PropertyListDecoder().decode([Schedule.Slot].self, from: data)
28 | }
29 |
30 | for slot in slots {
31 | let date = buildDate(for: slot)
32 | if date > Date() {
33 | let entry = SwiftLeedsWidgetEntry(date: date, slot: slot)
34 | entries.append(entry)
35 | }
36 | }
37 |
38 | let nextUpdateTime = Calendar.autoupdatingCurrent.date(byAdding: .hour, value: 1, to: Calendar.autoupdatingCurrent.startOfDay(for: Date()))!
39 | let timeline = Timeline(entries: entries, policy: .after(nextUpdateTime))
40 | completion(timeline)
41 | } catch {
42 | let nextUpdateTime = Calendar.autoupdatingCurrent.date(byAdding: .minute, value: 5, to: Calendar.autoupdatingCurrent.startOfDay(for: Date()))!
43 | let timeline = Timeline(entries: entries, policy: .after(nextUpdateTime))
44 | completion(timeline)
45 | }
46 | }
47 |
48 | private func buildDate(for slot: Schedule.Slot) -> Date {
49 | let slotTime = slot.startTime
50 | let slotTimeComponents = slotTime.components(separatedBy: ":")
51 | let slotHour = Int(slotTimeComponents.first ?? "0")
52 | let slotMinute = Int(slotTimeComponents.last ?? "0")
53 |
54 | var dateComponents = DateComponents()
55 | dateComponents.year = 2022
56 | dateComponents.month = 10
57 | dateComponents.day = 20
58 | dateComponents.timeZone = TimeZone.current
59 | dateComponents.hour = slotHour
60 | dateComponents.minute = slotMinute
61 | let userCalendar = Calendar(identifier: .gregorian)
62 | let dateTime = userCalendar.date(from: dateComponents) ?? Date()
63 |
64 | return dateTime
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/SwiftLeedsWidget/WidgetSetup/WidgetConstants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WidgetConstants.swift
3 | // SwiftLeedsWidgetExtension
4 | //
5 | // Created by Karim Ebrahem on 26/09/2022.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | enum WidgetConstants {
12 | static let timeFontSize: CGFloat = 10
13 | static let titleFontSize: CGFloat = 14
14 | static let detailsFontSize: CGFloat = 14
15 | static let logoImageWidth: CGFloat = 45
16 | static let logoImageHeight: CGFloat = 45
17 | }
18 |
--------------------------------------------------------------------------------
/SwiftLeedsWidgetExtension.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.application-groups
6 |
7 | group.uk.co.swiftleeds
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/fastlane/Appfile:
--------------------------------------------------------------------------------
1 | app_identifier("uk.co.swiftleeds.SwiftLeeds")
2 |
--------------------------------------------------------------------------------
/fastlane/Fastfile:
--------------------------------------------------------------------------------
1 | default_platform(:ios)
2 |
3 | platform :ios do
4 | desc "Build SwiftLeeds scheme"
5 | lane :build do |options|
6 | build_app(
7 | project: "SwiftLeeds.xcodeproj",
8 | configuration: options[:configuration],
9 | scheme: "SwiftLeeds",
10 | clean: true,
11 | skip_archive: true,
12 | skip_codesigning: true
13 | )
14 | end
15 |
16 | desc "Build SwiftLeeds scheme with Debug Configuration"
17 | lane :build_debug do
18 | build(configuration: 'Debug')
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/fastlane/README.md:
--------------------------------------------------------------------------------
1 | fastlane documentation
2 | ----
3 |
4 | # Installation
5 |
6 | Make sure you have the latest version of the Xcode command line tools installed:
7 |
8 | ```sh
9 | xcode-select --install
10 | ```
11 |
12 | For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
13 |
14 | # Available Actions
15 |
16 | ## iOS
17 |
18 | ### ios build
19 |
20 | ```sh
21 | [bundle exec] fastlane ios build
22 | ```
23 |
24 | Build SwiftLeeds scheme
25 |
26 | ### ios build_debug
27 |
28 | ```sh
29 | [bundle exec] fastlane ios build_debug
30 | ```
31 |
32 | Build SwiftLeeds scheme with Debug Configuration
33 |
34 | ----
35 |
36 | This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
37 |
38 | More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
39 |
40 | The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
41 |
--------------------------------------------------------------------------------
/media/swift-leeds-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwiftLeeds/swiftleeds-ios/3961b7a7ec88cdfdc34d520864ffad08ef1c1336/media/swift-leeds-logo.png
--------------------------------------------------------------------------------