├── .gitignore
├── .swn
├── .swo
├── .swp
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── README.md
├── iOSDevUK.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcuserdata
│ └── david.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
├── iOSDevUK
├── AdminSection
│ ├── AdminView.swift
│ ├── Domains
│ │ ├── AdminLocationViewModel.swift
│ │ ├── AdminSessionViewModel.swift
│ │ ├── AdminSpeakerViewModel.swift
│ │ ├── AdminSponsorViewModel.swift
│ │ ├── AdminViewModel.swift
│ │ └── LoginViewModel.swift
│ ├── Locations
│ │ ├── AddLocation.swift
│ │ └── AdminLocations.swift
│ ├── LoginView.swift
│ ├── Sessions
│ │ ├── AddSession.swift
│ │ ├── AdminSessions.swift
│ │ └── SelectSpeakerView.swift
│ ├── Speakers
│ │ ├── AddSpeakerView.swift
│ │ └── AdminSpeakers.swift
│ └── Sponsors
│ │ ├── AddSponsorView.swift
│ │ └── AdminSponsors.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ └── iOSDevUK_Icon_1024.png
│ ├── Colors
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── NewColors
│ │ │ ├── Contents.json
│ │ │ ├── SegmentTitle.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── buttonBackground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── buttonTitle.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── cardBackground.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── cardBottom.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── cardTop.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── iconColor.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── linkButton.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── mainTextColor.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── mapPin.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── outlineColor.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── pickerSelected.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── purple200.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── purple300.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── textBody.colorset
│ │ │ │ └── Contents.json
│ │ │ └── textGrey.colorset
│ │ │ │ └── Contents.json
│ │ ├── backgroundColor.colorset
│ │ │ └── Contents.json
│ │ ├── goldColor.colorset
│ │ │ └── Contents.json
│ │ ├── platinumColor.colorset
│ │ │ └── Contents.json
│ │ ├── primary.colorset
│ │ │ └── Contents.json
│ │ ├── secondary.colorset
│ │ │ └── Contents.json
│ │ └── silverColor.colorset
│ │ │ └── Contents.json
│ ├── Contents.json
│ └── Images
│ │ ├── Back.imageset
│ │ ├── Angle-left.pdf
│ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── EV.imageset
│ │ ├── Contents.json
│ │ └── petrolStations.pdf
│ │ ├── Shops.imageset
│ │ ├── Contents.json
│ │ └── shopping.pdf
│ │ ├── about.imageset
│ │ ├── Contents.json
│ │ └── Group 95-1.pdf
│ │ ├── aboutBackground.imageset
│ │ ├── Contents.json
│ │ ├── NantYrArian.jpg
│ │ └── Sunset.png
│ │ ├── appIcon.imageset
│ │ ├── Contents.json
│ │ └── iOSDevUK_Icon_1024.png
│ │ ├── attendee.imageset
│ │ ├── Contents.json
│ │ └── attendee.pdf
│ │ ├── background.imageset
│ │ ├── Contents.json
│ │ └── background.png
│ │ ├── calendar.imageset
│ │ ├── Contents.json
│ │ └── calendar.pdf
│ │ ├── car.imageset
│ │ ├── Contents.json
│ │ └── parking.pdf
│ │ ├── conferenceImage.imageset
│ │ ├── Contents.json
│ │ └── conferenceImage.jpg
│ │ ├── devices.imageset
│ │ ├── Contents.json
│ │ └── devices.png
│ │ ├── emptySchedule.imageset
│ │ ├── Contents.json
│ │ └── emotySchedule.pdf
│ │ ├── help.imageset
│ │ ├── Contents.json
│ │ └── file.pdf
│ │ ├── home.imageset
│ │ ├── Contents.json
│ │ └── home.pdf
│ │ ├── house.imageset
│ │ ├── Contents.json
│ │ └── key.pdf
│ │ ├── inclusivity.imageset
│ │ ├── Contents.json
│ │ └── Info icons.pdf
│ │ ├── info.imageset
│ │ ├── Contents.json
│ │ └── info.pdf
│ │ ├── link.imageset
│ │ ├── Contents.json
│ │ └── link.pdf
│ │ ├── linkedin.imageset
│ │ ├── Contents.json
│ │ └── linkedin.pdf
│ │ ├── mapImage.imageset
│ │ ├── Contents.json
│ │ └── mapImage.pdf
│ │ ├── mapPin.imageset
│ │ ├── Contents.json
│ │ └── Property 1=Location.pdf
│ │ ├── phone.imageset
│ │ ├── Contents.json
│ │ └── Group 95-2.pdf
│ │ ├── placeholder.imageset
│ │ ├── Contents.json
│ │ └── placeholder.png
│ │ ├── pubs.imageset
│ │ ├── Contents.json
│ │ └── pubs.pdf
│ │ ├── schedule.imageset
│ │ ├── Contents.json
│ │ └── schedule.pdf
│ │ ├── slack.imageset
│ │ ├── Contents.json
│ │ └── slack.pdf
│ │ ├── sponsors.imageset
│ │ ├── Contents.json
│ │ └── Group 95.pdf
│ │ ├── ticket.imageset
│ │ ├── Contents.json
│ │ └── ticket.pdf
│ │ ├── transport.imageset
│ │ ├── Contents.json
│ │ └── transport.pdf
│ │ ├── twitter.imageset
│ │ ├── Contents.json
│ │ └── twitter.pdf
│ │ ├── university.imageset
│ │ ├── Contents.json
│ │ └── university.pdf
│ │ └── you.imageset
│ │ ├── Contents.json
│ │ └── you.jpg
├── DataUploading
│ ├── Networking.swift
│ ├── locations.json
│ ├── sessions.json
│ ├── speakers.json
│ └── sponsors.json
├── Domains
│ ├── AllSessionsViewModel.swift
│ ├── BaseViewModel.swift
│ ├── MapViewModel.swift
│ ├── MyScheduleViewModel.swift
│ ├── SpeakerDetailViewModel.swift
│ └── WeatherViewModel.swift
├── Extensions
│ ├── Array + Extension.swift
│ ├── Bundle + Extensions.swift
│ ├── Date + Extensions.swift
│ ├── Double + Extensions.swift
│ └── String + Extensions.swift
├── FirebaseHelpers
│ ├── FCollectionReference.swift
│ ├── FirebaseAuthenticationService.swift
│ ├── FirebaseFileManager.swift
│ └── FirebaseRepository.swift
├── Info.plist
├── MOCK
│ └── DummyData.swift
├── Model
│ ├── AppErrors.swift
│ ├── CoreData
│ │ └── iOSDevUK.xcdatamodeld
│ │ │ ├── .xccurrentversion
│ │ │ └── iOSDevUK.xcdatamodel
│ │ │ └── contents
│ ├── EventInformation.swift
│ ├── InformationItem.swift
│ ├── Location.swift
│ ├── ScheduleType.swift
│ ├── Session.swift
│ ├── Speaker.swift
│ ├── Sponsor.swift
│ └── WeatherData.swift
├── Modifiers
│ ├── BackgroundModifier.swift
│ ├── ButtonModifier.swift
│ └── FontModifier.swift
├── Navigation
│ └── NavigationRouter.swift
├── Presentation
│ ├── AttendeeView
│ │ ├── AttendeeRowView.swift
│ │ └── AttendeeScreen.swift
│ ├── HomeView
│ │ ├── ContactButtonView.swift
│ │ ├── EventInfoView.swift
│ │ ├── HomeScreen.swift
│ │ ├── SessionsHorizontalScrollView.swift
│ │ └── SpeakersHorizontalScrollView.swift
│ ├── InfoView
│ │ ├── AboutView.swift
│ │ ├── AppInformationView.swift
│ │ ├── DropDownRowView.swift
│ │ ├── InclusivityView.swift
│ │ ├── InfoView.swift
│ │ ├── LocationHeaderView.swift
│ │ └── LocationsListView.swift
│ ├── MyScheduleView
│ │ └── MyScheduleView.swift
│ ├── SecondaryViews
│ │ ├── MapView
│ │ │ └── MapScreen.swift
│ │ ├── Sessions
│ │ │ ├── AllSessionsView.swift
│ │ │ └── SessionDetailView.swift
│ │ ├── SpeakerView
│ │ │ ├── AllSpeakersView.swift
│ │ │ └── SpeakerDetailScreen.swift
│ │ └── SponsorsView
│ │ │ └── SponsorsScreen.swift
│ └── ViewComponents
│ │ ├── AnimatedButtonView.swift
│ │ ├── AppSegmentControl.swift
│ │ ├── DayPickerView.swift
│ │ ├── EmptySessionView.swift
│ │ ├── HourlyWeatherView.swift
│ │ ├── InfoRowView.swift
│ │ ├── LocationInfoCardView.swift
│ │ ├── MapPinShape.swift
│ │ ├── MapPinView.swift
│ │ ├── NavigationRowView.swift
│ │ ├── RemoteImage.swift
│ │ ├── SectionHeaderView.swift
│ │ ├── SessionCardView.swift
│ │ ├── SessionRowView.swift
│ │ ├── SpeakerCardView.swift
│ │ ├── SpeakerRowView.swift
│ │ ├── SponsorCard.swift
│ │ ├── SponsorRow.swift
│ │ ├── TabBarView.swift
│ │ ├── UniversityMapView.swift
│ │ └── WeatherView.swift
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Resources
│ └── Fonts
│ │ ├── SFPRODISPLAYBLACKITALIC.OTF
│ │ ├── SFPRODISPLAYBOLD.OTF
│ │ ├── SFPRODISPLAYHEAVYITALIC.OTF
│ │ ├── SFPRODISPLAYLIGHTITALIC.OTF
│ │ ├── SFPRODISPLAYMEDIUM.OTF
│ │ ├── SFPRODISPLAYREGULAR.OTF
│ │ ├── SFPRODISPLAYSEMIBOLDITALIC.OTF
│ │ ├── SFPRODISPLAYTHINITALIC.OTF
│ │ ├── SFPRODISPLAYULTRALIGHTITALIC.OTF
│ │ ├── SFProDisplay-Light.ttf
│ │ └── SFProDisplay-Semibold.ttf
├── Utils
│ ├── Constants.swift
│ ├── LocalStorageService.swift
│ ├── LocationService.swift
│ ├── MappingUtils.swift
│ └── Reachability.swift
├── iOSDevUK.entitlements
└── iOSDevUKApp.swift
└── iOSDevUKTests
├── AllSessionsViewModelTest.swift
├── FactorySetup.swift
├── SessionDetailViewModelTest.swift
├── SessionRowViewModelTest.swift
└── SpeakerDetailViewModelTests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | #Fastlane
6 | fastlane/
7 |
8 | #Firebase
9 | GoogleService-Info*.plist
10 |
11 | ## User settings
12 | xcuserdata/
13 |
14 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
15 | *.xcscmblueprint
16 | *.xccheckout
17 |
18 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
19 | build/
20 | DerivedData/
21 | *.moved-aside
22 | *.pbxuser
23 | !default.pbxuser
24 | *.mode1v3
25 | !default.mode1v3
26 | *.mode2v3
27 | !default.mode2v3
28 | *.perspectivev3
29 | !default.perspectivev3
30 |
31 | ## Obj-C/Swift specific
32 | *.hmap
33 |
34 | ## App packaging
35 | *.ipa
36 | *.dSYM.zip
37 | *.dSYM
38 |
39 | ## Playgrounds
40 | timeline.xctimeline
41 | playground.xcworkspace
42 |
43 | # Swift Package Manager
44 | #
45 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
46 | # Packages/
47 | # Package.pins
48 | # Package.resolved
49 | # *.xcodeproj
50 | #
51 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
52 | # hence it is not needed unless you have added a package configuration file to your project
53 | # .swiftpm
54 |
55 | .build/
56 |
57 | # CocoaPods
58 | #
59 | # We recommend against adding the Pods directory to your .gitignore. However
60 | # you should judge for yourself, the pros and cons are mentioned at:
61 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
62 | #
63 | Pods/
64 | Podfile.lock
65 | #
66 | # Add this line if you want to avoid checking in source code from the Xcode workspace
67 | *.xcworkspace
68 |
69 | # Carthage
70 | #
71 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
72 | # Carthage/Checkouts
73 |
74 | Carthage/Build/
75 |
76 | # Accio dependency management
77 | Dependencies/
78 | .accio/
79 |
80 | # fastlane
81 | #
82 | # It is recommended to not store the screenshots in the git repo.
83 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
84 | # For more information about the recommended setup visit:
85 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
86 |
87 | fastlane/report.xml
88 | fastlane/Preview.html
89 | fastlane/screenshots/**/*.png
90 | fastlane/test_output
91 |
92 | # Code Injection
93 | #
94 | # After new code Injection tools there's a generated folder /iOSInjectionProject
95 | # https://github.com/johnno1962/injectionforxcode
96 |
97 | iOSInjectionProject/
98 |
99 | ### macOS ###
100 | # General
101 | .DS_Store
102 | .AppleDouble
103 | .LSOverride
104 |
105 | # Icon must end with two \r
106 | Icon
107 |
108 |
109 | # Thumbnails
110 | ._*
111 |
112 | # Files that might appear in the root of a volume
113 | .DocumentRevisions-V100
114 | .fseventsd
115 | .Spotlight-V100
116 | .TemporaryItems
117 | .Trashes
118 | .VolumeIcon.icns
119 | .com.apple.timemachine.donotpresent
120 |
121 | # Directories potentially created on remote AFP share
122 | .AppleDB
123 | .AppleDesktop
124 | Network Trash Folder
125 | Temporary Items
126 | .apdisk
127 |
128 | ### macOS Patch ###
129 | # iCloud generated files
130 | *.icloud
131 |
132 | # Generated
133 | *.generated.swift
134 | Derived
135 | *.xcodeproj
136 | *..xcworkspace
137 | .package.resolved
138 |
139 |
140 | # Caches
141 | **/*.xcodeproj/MockingbirdCache/
142 |
143 | # Mockingbird
144 | MockingbirdMocks
145 | Pods/MockingbirdFramework/mockingbird/bin/
146 |
147 |
--------------------------------------------------------------------------------
/.swn:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/.swn
--------------------------------------------------------------------------------
/.swo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/.swo
--------------------------------------------------------------------------------
/.swp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/.swp
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 |
2 |
3 | source "https://rubygems.org"
4 |
5 |
6 | gem "fastlane"
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 David Kababyan
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 | # iOSDevUK
2 | This is a redevelopment of the conference app for [iOSDevUK](https://www.iosdevuk.com), in its eleventh year (2023).
3 | The app uses [SwiftUI](https://developer.apple.com/xcode/swiftui/) and [Firebase](https://firebase.google.com/).
4 |
5 | ## Setup
6 |
7 | To start using the code, the following steps are required.
8 |
9 | ### Bundle Id
10 |
11 | The Bundle Id is set to one for the conference app for its release on the AppStore.
12 | Is using this code as a basis for a different conference app, change the Bundle ID for something that is associated with your developer account.
13 |
14 | ### Firebase
15 |
16 | The application uses Firebase for the data storage and the location of the image files.
17 |
18 | * **AppInformation** - Information for the application. The collection contains one item, which has the fields for the application.
19 | * **InformationItem** - A collection of links to further information, e.g. joining instructions.
20 | * **Location** - A collection of locations, associated with a location type (e.g. supermarkets, EV charging points).
21 | * **Session** - A collection of sessions, which includes talks, workshops and social events.
22 | * **Speaker** - A collection of infomration about the speakers for the conference.
23 | * **Sponsor** - A collection of information about the sponsors for the conference.
24 |
25 | The file `GoogleService-Info.plist` is required to be inserted into the project.
26 | This is generated by Firebase when a new data store is created. The Xcode project has a placeholder for the file, but no file is included in the repository.
27 |
28 | ## Dependencies
29 |
30 | The application uses the following 3rd party libraries:
31 |
32 | * [Firebase](https://firebase.google.com/) - for access to Firebase cloud facilities, including Firestore, Storage and Auth, used under the [Firebase Apache License](https://github.com/firebase/firebase-ios-sdk/blob/master/LICENSE).
33 | * [Factory](https://github.com/hmlongco/Factory) - used for depedency injection, used [Factory's MIT license](https://github.com/hmlongco/Factory/blob/main/LICENSE).
34 | * [Kingfisher](https://github.com/onevcat/Kingfisher) - used for downloading and caching images, used under [Kingfisher's MIT license](https://github.com/onevcat/Kingfisher/blob/master/LICENSE).
35 |
36 | ## License
37 |
38 | The app uses the MIT License, specified in the [License file](LICENSE).
39 |
40 | ## Developers
41 |
42 | The re-designed app has been created by David Kababyan ([@Dave_iOSDev](https://twitter.com/Dave_iOSDev)), with some contributions from Neil Taylor ([@Digidol](https://twitter.com/digidol)) and other contributors shown at [github.com/DavidFaraday/iOSDevUK](https://github.com/DavidFaraday/iOSDevUK).
43 |
--------------------------------------------------------------------------------
/iOSDevUK.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/iOSDevUK.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/iOSDevUK.xcodeproj/xcuserdata/david.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Promises (Playground) 1.xcscheme
8 |
9 | isShown
10 |
11 | orderHint
12 | 3
13 |
14 | Promises (Playground) 2.xcscheme
15 |
16 | isShown
17 |
18 | orderHint
19 | 4
20 |
21 | Promises (Playground) 3.xcscheme
22 |
23 | isShown
24 |
25 | orderHint
26 | 5
27 |
28 | Promises (Playground) 4.xcscheme
29 |
30 | isShown
31 |
32 | orderHint
33 | 6
34 |
35 | Promises (Playground) 5.xcscheme
36 |
37 | isShown
38 |
39 | orderHint
40 | 7
41 |
42 | Promises (Playground).xcscheme
43 |
44 | isShown
45 |
46 | orderHint
47 | 2
48 |
49 | iOSDevUK.xcscheme_^#shared#^_
50 |
51 | orderHint
52 | 0
53 |
54 |
55 | SuppressBuildableAutocreation
56 |
57 | 63F7E0B528CC942100B2E1B7
58 |
59 | primary
60 |
61 |
62 | EF17827F2941F2D800EFFB2D
63 |
64 | primary
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/iOSDevUK/AdminSection/AdminView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AdminView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 20/10/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AdminView: View {
11 | @EnvironmentObject var router: NavigationRouter
12 |
13 | @StateObject private var viewModel = AdminViewModel()
14 |
15 | @ViewBuilder
16 | private func navigationBarTrailingItem() -> some View {
17 | Button(AppStrings.logOut) {
18 | Task {
19 | await viewModel.logOutUser()
20 | router.infoPath.removeLast()
21 | }
22 | }
23 | }
24 |
25 | var body: some View {
26 | Form {
27 | NavigationLink(AppStrings.speakers, value: InfoDestination.adminSpeakers)
28 | NavigationLink(AppStrings.sessions, value: InfoDestination.adminSessions)
29 | NavigationLink(AppStrings.locations, value: InfoDestination.adminLocations)
30 | NavigationLink(AppStrings.sponsors, value: InfoDestination.adminSponsors)
31 | }
32 | .task {
33 | await viewModel.uploadNewData()
34 | }
35 | .navigationTitle(AppStrings.adminArea)
36 | .toolbar {
37 | ToolbarItem(placement: .navigationBarTrailing, content: navigationBarTrailingItem)
38 | }
39 | .alert(isPresented: $viewModel.showError, content: {
40 | Alert(title: Text(AppStrings.error), message: Text(viewModel.logoutError?.localizedDescription ?? ""), dismissButton: .default(Text(AppStrings.ok)))
41 | })
42 | }
43 | }
44 |
45 | struct AdminView_Previews: PreviewProvider {
46 | static var previews: some View {
47 | AdminView()
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/iOSDevUK/AdminSection/Domains/AdminLocationViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AdminLocationViewModel.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 05/03/2023.
6 | //
7 |
8 | import Foundation
9 |
10 | final class AdminLocationViewModel: ObservableObject {
11 | private var firebaseRepository: FirebaseRepositoryProtocol
12 |
13 | @Published var name = ""
14 | @Published var note = "Notes"
15 | @Published var locationType = LocationType.au
16 | @Published var imageLink = ""
17 | @Published var latitude = ""
18 | @Published var longitude = ""
19 |
20 | var location: Location?
21 |
22 | init(location: Location? = nil, firebaseRepository: FirebaseRepositoryProtocol = FirebaseRepository.shared) {
23 | self.location = location
24 | self.firebaseRepository = firebaseRepository
25 | setupUI()
26 | }
27 |
28 | private func setupUI() {
29 | guard let location = location else { return }
30 |
31 | name = location.name
32 | note = location.note ?? ""
33 | imageLink = location.imageLink ?? ""
34 | latitude = "\(location.latitude)"
35 | longitude = "\(location.longitude)"
36 | locationType = location.locationType
37 | }
38 |
39 | func save() async {
40 | let newLocation = Location(id: location?.id ?? name.removeSpaces, name: name, note: note, imageLink: imageLink, latitude: Double(latitude) ?? 0.0, longitude: Double(longitude) ?? 0.0, webLink: nil, locationType: locationType)
41 |
42 | do {
43 | try firebaseRepository.saveData(data: newLocation, to: .Location)
44 | } catch {
45 | print("Error saving location", error.localizedDescription)
46 | }
47 | }
48 |
49 | func deleteLocation(_ location: Location) {
50 | firebaseRepository.deleteDocument(with: location.id, from: .Location)
51 | }
52 |
53 | func invalidForm() -> Bool {
54 | name == "" || latitude == "" || longitude == "" || note == ""
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/iOSDevUK/AdminSection/Domains/AdminSessionViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AdminSessionViewModel.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 05/03/2023.
6 | //
7 |
8 | import Foundation
9 | import Combine
10 |
11 |
12 | final class AdminSessionViewModel: ObservableObject {
13 | private let firebaseRepository: FirebaseRepositoryProtocol
14 |
15 | @Published var title = ""
16 | @Published var content = "Session content"
17 | @Published var type = SessionType.talk
18 |
19 | @Published var location: Location?
20 | @Published private var locationId = ""
21 |
22 | @Published var selectedSpeakers = Set()
23 |
24 | @Published private var speakerIds: [String] = []
25 |
26 | @Published var startDate = Date()
27 | @Published var endDate = Date()
28 |
29 | var subscriptions = Set()
30 |
31 | var session: Session?
32 |
33 | init(session: Session? = nil, firebaseRepository: FirebaseRepositoryProtocol = FirebaseRepository.shared) {
34 | self.firebaseRepository = firebaseRepository
35 | self.session = session
36 | setupUI()
37 | observerData()
38 | }
39 |
40 |
41 | private func observerData() {
42 | $location
43 | .map({ $0?.id ?? "" })
44 | .assign(to: &$locationId)
45 |
46 | $selectedSpeakers
47 | .map({ $0.map({ $0.id }) })
48 | .assign(to: &$speakerIds)
49 | }
50 |
51 |
52 | private func setupUI() {
53 | guard let session = session else { return }
54 |
55 | title = session.title
56 | content = session.content
57 | type = session.type
58 | locationId = session.locationId ?? ""
59 | speakerIds = session.speakerIds
60 | startDate = session.startDate
61 | endDate = session.endDate
62 | }
63 |
64 | @MainActor
65 | func save(speakers: Set) async {
66 | // selectedSpeakers = speakers
67 | //
68 | //
69 | // guard !speakerIds.isEmpty else { return }
70 |
71 | let newSession = Session(id: session?.id ?? UUID().uuidString, title: title, content: content, startDate: startDate, endDate: endDate, locationId: locationId, speakerIds: speakerIds, type: type)
72 |
73 | // print(newSession)
74 |
75 | do {
76 | try firebaseRepository.saveData(data: newSession, to: .Session)
77 | }
78 | catch {
79 | print("Error saving session", error.localizedDescription)
80 | }
81 | }
82 |
83 |
84 | func deleteSession(_ session: Session) {
85 | firebaseRepository.deleteDocument(with: session.id, from: .Session)
86 | }
87 |
88 | func invalidForm() -> Bool {
89 | title == "" || content == "" || locationId == ""
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/iOSDevUK/AdminSection/Domains/AdminSpeakerViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AdminSpeakerViewModel.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 15/01/2023.
6 | //
7 |
8 | import SwiftUI
9 | import PhotosUI
10 |
11 | final class AdminSpeakerViewModel: ObservableObject {
12 | private var firebaseRepository: FirebaseRepositoryProtocol
13 |
14 | @Published var fullName = ""
15 | @Published var currentPosition = ""
16 | @Published var twitter = ""
17 | @Published var linkedIn = ""
18 | @Published var imageLink = ""
19 | @Published var selectedItem: PhotosPickerItem?
20 | @Published var selectedImageData: Data? = nil
21 | @Published var bio = "Bio"
22 |
23 | @Published var webLinkName = ""
24 | @Published var url = ""
25 |
26 | var speaker: Speaker?
27 |
28 | init(speaker: Speaker? = nil, firebaseRepository: FirebaseRepositoryProtocol = FirebaseRepository.shared) {
29 | self.speaker = speaker
30 | self.firebaseRepository = firebaseRepository
31 | setupUI()
32 | }
33 |
34 | private func setupUI() {
35 | guard let speaker = speaker else { return }
36 |
37 | fullName = speaker.name
38 | currentPosition = speaker.currentPosition ?? ""
39 | twitter = speaker.twitterId ?? ""
40 | linkedIn = speaker.linkedIn ?? ""
41 | imageLink = speaker.imageLink
42 | bio = speaker.biography
43 | }
44 |
45 | func save() async {
46 | let newSpeaker = Speaker(id: speaker?.id ?? fullName.removeSpaces, name: fullName, currentPosition: currentPosition, biography: bio, linkedIn: linkedIn, twitterId: twitter, imageLink: imageLink, webLinks: nil)
47 |
48 | do {
49 | try firebaseRepository.saveData(data: newSpeaker, to: .Speaker)
50 | }
51 | catch {
52 | print("Error saving speaker", error.localizedDescription)
53 | }
54 |
55 | //With image uploading func Will add in new version
56 | // Task {
57 | // let imageLink = await uploadAvatar()
58 | //
59 | // let newSpeaker = Speaker(id: speaker?.id ?? UUID().uuidString, name: fullName, biography: bio, linkedIn: linkedIn, twitterId: twitter, imageLink: imageLink, webLinks: nil)
60 | // do {
61 | // try FirebaseReference(.Speaker).document(newSpeaker.id).setData(from: newSpeaker)
62 | // }
63 | // catch {
64 | // print("Error saving speaker", error.localizedDescription)
65 | // }
66 | // }
67 | }
68 |
69 |
70 | // func uploadAvatar() async -> String {
71 | // guard let imageData = selectedImageData else { return "" }
72 | //
73 | // do {
74 | // return try await FirebaseFileManager.shared.uploadImage(imageData, directory: .Speakers)
75 | // } catch {
76 | // print(error.localizedDescription)
77 | // return ""
78 | // }
79 | // }
80 |
81 | func deleteSpeaker(_ speaker: Speaker) {
82 | firebaseRepository.deleteDocument(with: speaker.id, from: .Speaker)
83 | }
84 |
85 | func invalidForm() -> Bool {
86 | fullName == "" || bio == "" || imageLink == ""
87 | }
88 | }
89 |
90 |
--------------------------------------------------------------------------------
/iOSDevUK/AdminSection/Domains/AdminSponsorViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AdminSponsorViewModel.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 05/03/2023.
6 | //
7 |
8 | import Foundation
9 |
10 | final class AdminSponsorViewModel: ObservableObject {
11 | private var firebaseRepository: FirebaseRepositoryProtocol
12 |
13 | @Published var name = ""
14 | @Published var url = ""
15 | @Published var urlText = ""
16 | @Published var category = SponsorCategory.Silver
17 | @Published var imageLinkDark = ""
18 | @Published var imageLinkLight = ""
19 | @Published var tagline = "About the sponsor"
20 |
21 | var sponsor: Sponsor?
22 |
23 | init(
24 | sponsor: Sponsor? = nil,
25 | firebaseRepository: FirebaseRepositoryProtocol = FirebaseRepository.shared
26 | ) {
27 | self.sponsor = sponsor
28 | self.firebaseRepository = firebaseRepository
29 | setupUI()
30 | }
31 |
32 | private func setupUI() {
33 | guard let sponsor = sponsor else { return }
34 |
35 | name = sponsor.name
36 | url = sponsor.url
37 | urlText = sponsor.urlText
38 | imageLinkDark = sponsor.imageLinkDark ?? ""
39 | imageLinkLight = sponsor.imageLinkLight ?? ""
40 | tagline = sponsor.tagline
41 | category = sponsor.sponsorCategory
42 | }
43 |
44 | func save() async {
45 | let newSponsor = Sponsor(id: sponsor?.id ?? name.removeSpaces, name: name, tagline: tagline, url: url, urlText: urlText, sponsorCategory: category, imageLinkDark: imageLinkDark, imageLinkLight: imageLinkLight)
46 |
47 | do {
48 | try firebaseRepository.saveData(data: newSponsor, to: .Sponsor)
49 | }
50 | catch {
51 | print("Error saving sponsor", error.localizedDescription)
52 | }
53 | }
54 |
55 | func deleteSponsor(_ sponsor: Sponsor) {
56 | firebaseRepository.deleteDocument(with: sponsor.id, from: .Sponsor)
57 | }
58 |
59 | func invalidForm() -> Bool {
60 | name == "" || url == "" || urlText == "" || imageLinkDark == "" || imageLinkLight == "" || tagline == ""
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/iOSDevUK/AdminSection/Domains/AdminViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AdminViewModel.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 26/10/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | final class AdminViewModel: ObservableObject {
11 | private let firebaseAuth: FirebaseAuthenticationServiceProtocol
12 |
13 | @Published var logoutError: Error?
14 | @Published var showError = false
15 |
16 | @MainActor
17 | func logOutUser() async {
18 | do {
19 | try await firebaseAuth.logOutUser()
20 | } catch (let error) {
21 | logoutError = error
22 | }
23 |
24 | showError = logoutError != nil
25 | }
26 |
27 | init(firebaseAuth: FirebaseAuthenticationServiceProtocol = FirebaseAuthenticationService.shared) {
28 | self.firebaseAuth = firebaseAuth
29 | }
30 |
31 | @MainActor
32 | func uploadNewData() async {
33 | // try? await FileUploadService.shared.uploadNewData(from: "sponsors.json", to: .Sponsor, objectType: Sponsor.self)
34 | // sleep(2)
35 | // try? await FileUploadService.shared.uploadNewData(from: "speakers.json", to: .Speaker, objectType: Speaker.self)
36 | // sleep(2)
37 | // try? await FileUploadService.shared.uploadNewData(from: "locations.json", to: .Location, objectType: Location.self)
38 | // sleep(2)
39 | // try? await FileUploadService.shared.uploadNewData(from: "sessions.json", to: .Session, objectType: CustomSession.self)
40 |
41 | print("DEBUG: Uploaded new data to firestore!")
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/iOSDevUK/AdminSection/Domains/LoginViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginViewModel.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 14/01/2023.
6 | //
7 |
8 | import SwiftUI
9 |
10 | final class LoginViewModel: ObservableObject {
11 | private var firebaseAuth: FirebaseAuthenticationServiceProtocol
12 |
13 | @Published var loginSuccessful = false
14 | @Published var loginError: Error?
15 | @Published var showError = false
16 |
17 | init(firebaseAuth: FirebaseAuthenticationServiceProtocol = FirebaseAuthenticationService.shared) {
18 | self.firebaseAuth = firebaseAuth
19 | }
20 |
21 | @MainActor
22 | func loginUserWith(email: String, password: String) async {
23 | do {
24 | loginSuccessful = try await firebaseAuth.loginUserWith(email: email, password: password)
25 | } catch (let error) {
26 | loginError = error
27 | showError = true
28 | }
29 | }
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/iOSDevUK/AdminSection/Locations/AddLocation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AddLocation.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 20/10/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AddLocation: View {
11 | @Environment(\.dismiss) var dismiss
12 | @ObservedObject var viewModel: AdminLocationViewModel
13 |
14 | @ViewBuilder
15 | private func navigationBarTrailingItem() -> some View {
16 | Button {
17 | Task {
18 | await viewModel.save()
19 | }
20 | dismiss()
21 | } label: {
22 | Text(AppStrings.save)
23 | }
24 | .disabled(viewModel.invalidForm())
25 | }
26 |
27 |
28 | @ViewBuilder
29 | private func main() -> some View {
30 | Form {
31 | Section {
32 | TextField("Name", text: $viewModel.name)
33 | TextField("Latitude", text: $viewModel.latitude)
34 | .keyboardType(.decimalPad)
35 | TextField("Latitude", text: $viewModel.longitude)
36 | .keyboardType(.decimalPad)
37 |
38 | Picker("Type", selection: $viewModel.locationType) {
39 | ForEach(LocationType.allCases, id: \.self) { location in
40 | Text(location.name)
41 | .tag(location)
42 | }
43 | }
44 | TextEditor(text: $viewModel.note)
45 | .frame(height: AppConstants.textViewHeight)
46 |
47 | } header: {
48 | Text(AppStrings.locationDetails)
49 | }
50 | }
51 | }
52 |
53 | var body: some View {
54 | main()
55 | .navigationTitle(viewModel.location?.name ?? AppStrings.addLocation)
56 | .toolbar {
57 | ToolbarItem(placement: .navigationBarTrailing, content: navigationBarTrailingItem)
58 | }
59 | }
60 | }
61 |
62 | struct AddLocation_Previews: PreviewProvider {
63 | static var previews: some View {
64 | AddLocation(viewModel: AdminLocationViewModel())
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/iOSDevUK/AdminSection/Locations/AdminLocations.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AdminLocations.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 20/10/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AdminLocations: View {
11 | @EnvironmentObject var viewModel: BaseViewModel
12 | @StateObject private var adminLocationViewModel = AdminLocationViewModel()
13 |
14 | var categories: [String : [Location]] {
15 | .init(
16 | grouping: viewModel.locations,
17 | by: { $0.locationType.rawValue }
18 | )
19 | }
20 |
21 | @ViewBuilder
22 | private func navigationBarTrailingItem() -> some View {
23 | NavigationLink(value: InfoDestination.adminAddLocation(nil)) {
24 | Image(systemName: ImageNames.plus)
25 | .font(.title3)
26 | }
27 | }
28 |
29 |
30 | @ViewBuilder
31 | private func main() -> some View {
32 | Form {
33 | ForEach(categories.keys.sorted(), id: \String.self) { key in
34 |
35 | Section {
36 | ForEach(categories[key] ?? [], id: \.id) { location in
37 |
38 | NavigationLink(value: InfoDestination.adminAddLocation(location)) {
39 | Text(location.name)
40 | .font(.subheadline)
41 | .lineLimit(1)
42 | .minimumScaleFactor(0.6)
43 | }
44 | }
45 | .onDelete { indexSet in
46 | guard let index = indexSet.first, let locations = categories[key] else { return }
47 | adminLocationViewModel.deleteLocation(locations[index])
48 | }
49 | } header: {
50 | let locationType: LocationType = LocationType(rawValue: key) ?? .au
51 | SectionHeaderView(title: locationType.name)
52 | .font(.headline)
53 | }
54 | }
55 | }
56 | }
57 |
58 | var body: some View {
59 | main()
60 | .navigationTitle(AppStrings.locations)
61 | .navigationBarTitleDisplayMode(.inline)
62 | .toolbar {
63 | ToolbarItem(placement: .navigationBarTrailing, content: navigationBarTrailingItem)
64 | }
65 | }
66 | }
67 |
68 | struct AdminLocations_Previews: PreviewProvider {
69 | static var previews: some View {
70 | AdminLocations()
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/iOSDevUK/AdminSection/LoginView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 13/01/2023.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct LoginView: View {
11 | @Environment(\.dismiss) var dismiss
12 |
13 | @StateObject var viewModel = LoginViewModel()
14 |
15 | @State private var email = ""
16 | @State private var password = ""
17 |
18 | @ViewBuilder
19 | private func dismissButton() -> some View {
20 | ZStack(alignment: .topTrailing) {
21 | HStack {
22 | Spacer()
23 | Button("Close") {
24 | dismiss()
25 | }
26 | .padding()
27 | }
28 | }
29 | }
30 |
31 | @ViewBuilder
32 | private func loginButton() -> some View {
33 | Button {
34 | Task {
35 | await viewModel.loginUserWith(email: email, password: password)
36 | }
37 |
38 | } label: {
39 | Text(AppStrings.login)
40 | .foregroundStyle(Color(.darkText))
41 | .fontWeight(.semibold)
42 | .frame(maxWidth: .infinity)
43 | }
44 | .capsuleBackgroundView(color: Color(.buttonBackground))
45 | .padding(.top, 10)
46 | .onChange(of: viewModel.loginSuccessful, perform: { newValue in
47 | if newValue {
48 | dismiss()
49 | }
50 | })
51 | }
52 |
53 | @ViewBuilder
54 | private func loginComponent() -> some View {
55 | VStack(spacing: 10) {
56 | Text(AppStrings.login)
57 | .semiboldAppFont(size: 20)
58 | .foregroundStyle(Color(.mainText))
59 |
60 | HStack {
61 | Image(systemName: ImageNames.envelope)
62 | .foregroundColor(.accentColor)
63 | .frame(width: 30)
64 | TextField(AppStrings.email, text: $email)
65 | .keyboardType(.emailAddress)
66 | .textFieldStyle(.roundedBorder)
67 | }
68 |
69 | HStack {
70 | Image(systemName: ImageNames.lock)
71 | .foregroundColor(.accentColor)
72 | .frame(width: 30)
73 | SecureField(AppStrings.password, text: $password)
74 | .textFieldStyle(.roundedBorder)
75 | }
76 |
77 | loginButton()
78 | }
79 | .padding()
80 | .background(.ultraThinMaterial)
81 | .overlay(
82 | RoundedRectangle(cornerRadius: 16)
83 | .stroke(Color.accentColor, lineWidth: 2)
84 | )
85 | .padding()
86 | }
87 |
88 | var body: some View {
89 | VStack {
90 | dismissButton()
91 | loginComponent()
92 | Spacer()
93 | Spacer()
94 | }
95 | .alert(isPresented: $viewModel.showError, content: {
96 | Alert(title: Text(AppStrings.error), message: Text(viewModel.loginError?.localizedDescription ?? ""), dismissButton: .default(Text(AppStrings.ok)))
97 | })
98 | }
99 | }
100 |
101 | struct LoginView_Previews: PreviewProvider {
102 | static var previews: some View {
103 | LoginView()
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/iOSDevUK/AdminSection/Sessions/AdminSessions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AdminSessions.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 20/10/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AdminSessions: View {
11 | @EnvironmentObject var viewModel: BaseViewModel
12 | @StateObject private var adminSessionViewModel = AdminSessionViewModel()
13 |
14 | @ViewBuilder
15 | private func sessionRowView(session: Session) -> some View {
16 | VStack(alignment: .leading, spacing: 10) {
17 | Text(session.title)
18 | .appFont(size: 18)
19 | Text("\(session.duration)")
20 | .appFont(size: 14)
21 | }
22 | .foregroundStyle(Color(.mainText))
23 | }
24 |
25 | @ViewBuilder
26 | private func navigationBarTrailingItem() -> some View {
27 | NavigationLink(value: InfoDestination.adminAddSession(nil)) {
28 | Image(systemName: ImageNames.plus)
29 | .font(.title3)
30 | }
31 | }
32 |
33 | @ViewBuilder
34 | private func main() -> some View {
35 | Form {
36 | ForEach(viewModel.sessions, id: \.id) { session in
37 | NavigationLink(value: InfoDestination.adminAddSession(session)) {
38 | sessionRowView(session: session)
39 | }
40 | }
41 | .onDelete { indexSet in
42 | guard let index = indexSet.first else { return }
43 | adminSessionViewModel.deleteSession(viewModel.sessions[index])
44 | }
45 | }
46 | .listStyle(.plain)
47 | }
48 |
49 |
50 | var body: some View {
51 | main()
52 | .navigationTitle(AppStrings.sessions)
53 | .navigationBarTitleDisplayMode(.inline)
54 | .toolbar {
55 | ToolbarItem(placement: .navigationBarTrailing, content: navigationBarTrailingItem)
56 | }
57 | }
58 | }
59 |
60 | struct AdminSessions_Previews: PreviewProvider {
61 | static var previews: some View {
62 | AdminSessions()
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/iOSDevUK/AdminSection/Sessions/SelectSpeakerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SelectSpeakerView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 21/04/2023.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SelectSpeakerView: View {
11 |
12 | @EnvironmentObject var viewModel: BaseViewModel
13 | @Environment(\.dismiss) var dismiss
14 |
15 |
16 | @Binding var selectedSpeakers: Set
17 |
18 | @ViewBuilder
19 | private func navigationBarTrailingItem() -> some View {
20 | Button {
21 | dismiss()
22 | } label: {
23 | Text(AppStrings.save)
24 | }
25 | }
26 |
27 |
28 | @ViewBuilder
29 | private func main() -> some View {
30 | Form {
31 | ForEach(viewModel.speakers) { speaker in
32 | Button(action: { toggleSelection(speaker) }) {
33 | HStack {
34 | Text(speaker.name)
35 | .font(.subheadline)
36 | Spacer()
37 | if selectedSpeakers.contains(where: { $0.id == speaker.id }) {
38 | Image(systemName: ImageNames.checkmark)
39 | .font(.system(size: 13, weight: .bold))
40 | .foregroundColor(Color.accentColor)
41 | }
42 | }
43 | }
44 | .tag(speaker.id)
45 | }
46 | }
47 | }
48 |
49 | var body: some View {
50 | NavigationStack {
51 | main()
52 | .navigationTitle(AppStrings.selectSpeakers)
53 | .toolbar {
54 | ToolbarItem(placement: .navigationBarTrailing, content: navigationBarTrailingItem)
55 | }
56 |
57 | }
58 | }
59 |
60 | private func toggleSelection(_ speaker: Speaker) {
61 | if let existingIndex = selectedSpeakers.firstIndex(where: { $0.id == speaker.id }) {
62 | selectedSpeakers.remove(at: existingIndex)
63 | } else {
64 | selectedSpeakers.insert(speaker)
65 | }
66 | }
67 |
68 | }
69 |
70 | struct SelectSpeakerView_Previews: PreviewProvider {
71 | static var previews: some View {
72 | SelectSpeakerView(selectedSpeakers: .constant([]))
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/iOSDevUK/AdminSection/Speakers/AdminSpeakers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AdminSpeakers.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 20/10/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AdminSpeakers: View {
11 | @EnvironmentObject var viewModel: BaseViewModel
12 | @StateObject private var adminSpeakerViewModel = AdminSpeakerViewModel()
13 |
14 | @ViewBuilder
15 | private func navigationBarTrailingItem() -> some View {
16 | NavigationLink(value: InfoDestination.adminAddSpeaker(nil)) {
17 | Image(systemName: ImageNames.plus)
18 | .font(.title3)
19 | }
20 | }
21 |
22 | @ViewBuilder
23 | private func main() -> some View {
24 | Form {
25 | ForEach(viewModel.speakers) { speaker in
26 | NavigationLink(value: InfoDestination.adminAddSpeaker(speaker)) {
27 | Text(speaker.name)
28 | .font(.subheadline)
29 | }
30 | }
31 | .onDelete { indexSet in
32 | guard let index = indexSet.first else { return }
33 | adminSpeakerViewModel.deleteSpeaker(viewModel.speakers[index])
34 | }
35 | }
36 |
37 | }
38 |
39 | var body: some View {
40 | main()
41 | .navigationTitle(AppStrings.speakers)
42 | .navigationBarTitleDisplayMode(.inline)
43 | .toolbar {
44 | ToolbarItem(placement: .navigationBarTrailing, content: navigationBarTrailingItem)
45 | }
46 | }
47 |
48 | }
49 |
50 | struct AdminSpeakers_Previews: PreviewProvider {
51 | static var previews: some View {
52 | AdminSpeakers()
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/iOSDevUK/AdminSection/Sponsors/AddSponsorView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AddSponsorView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 20/10/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AddSponsorView: View {
11 | @Environment(\.dismiss) var dismiss
12 | @ObservedObject var viewModel: AdminSponsorViewModel
13 |
14 | @ViewBuilder
15 | private func navigationBarTrailingItem() -> some View {
16 | Button {
17 | Task {
18 | await viewModel.save()
19 | }
20 | dismiss()
21 | } label: {
22 | Text(AppStrings.save)
23 | }
24 | .disabled(viewModel.invalidForm())
25 | }
26 |
27 | @ViewBuilder
28 | private func main() -> some View {
29 | Form {
30 | Section {
31 | TextField("Name", text: $viewModel.name)
32 | TextField("Url in https:// format", text: $viewModel.url)
33 | TextField("Link Text", text: $viewModel.urlText)
34 |
35 | Picker("Category", selection: $viewModel.category) {
36 | ForEach(SponsorCategory.allCases, id: \.self) { category in
37 | Text(category.rawValue)
38 | .tag(category)
39 | }
40 | }
41 |
42 | HStack {
43 | TextField("Image link dark", text: $viewModel.imageLinkDark)
44 | Spacer()
45 | if !viewModel.imageLinkDark.isEmpty {
46 | RemoteImageView(url: URL(string: viewModel.imageLinkDark))
47 | .scaledToFit()
48 | .clipShape(RoundedRectangle(cornerRadius: 8))
49 | .frame(width: 50)
50 | }
51 | }
52 |
53 | HStack {
54 | TextField("Image link light", text: $viewModel.imageLinkLight)
55 | Spacer()
56 | if !viewModel.imageLinkLight.isEmpty {
57 | RemoteImageView(url: URL(string: viewModel.imageLinkLight))
58 | .scaledToFit()
59 | .clipShape(RoundedRectangle(cornerRadius: 8))
60 | .frame(width: 50)
61 | }
62 | }
63 |
64 | TextEditor(text: $viewModel.tagline)
65 | .frame(height: AppConstants.textViewHeight)
66 | } header: {
67 | Text(AppStrings.sponsorDetails)
68 | }
69 | }
70 | }
71 |
72 |
73 | var body: some View {
74 | main()
75 | .navigationTitle(viewModel.sponsor?.name ?? AppStrings.addSponsor)
76 | .toolbar {
77 | ToolbarItem(placement: .navigationBarTrailing, content: navigationBarTrailingItem)
78 | }
79 | }
80 | }
81 |
82 | struct AddSponsor_Previews: PreviewProvider {
83 | static var previews: some View {
84 | AddSponsorView(viewModel: AdminSponsorViewModel())
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/iOSDevUK/AdminSection/Sponsors/AdminSponsors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AdminSponsors.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 20/10/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AdminSponsors: View {
11 | @EnvironmentObject var viewModel: BaseViewModel
12 | @StateObject private var adminSponsorViewModel = AdminSponsorViewModel()
13 |
14 | @ViewBuilder
15 | private func navigationBarTrailingItem() -> some View {
16 | NavigationLink(value: InfoDestination.adminAddSponsor(nil)) {
17 | Image(systemName: ImageNames.plus)
18 | .font(.title3)
19 | }
20 | }
21 |
22 |
23 | @ViewBuilder
24 | private func main() -> some View {
25 | Form {
26 | ForEach(viewModel.sponsors, id: \.id) { sponsor in
27 | NavigationLink(value: InfoDestination.adminAddSponsor(sponsor)) {
28 |
29 | HStack(spacing: 5) {
30 | ZStack {
31 | Circle()
32 | .frame(width: 20)
33 | .foregroundColor(sponsor.sponsorCategory.color)
34 | Text(sponsor.sponsorCategory.rawValue.prefix(1))
35 | .fontWeight(.bold)
36 | .foregroundColor(.black)
37 | .font(.system(size: 13))
38 | }
39 |
40 | Text(sponsor.name)
41 | .font(.subheadline)
42 | }
43 | }
44 | }
45 | .onDelete { indexSet in
46 | guard let index = indexSet.first else { return }
47 | adminSponsorViewModel.deleteSponsor(viewModel.sponsors[index])
48 | }
49 | }
50 | .listStyle(.plain)
51 | }
52 |
53 | var body: some View {
54 | main()
55 | .navigationTitle(AppStrings.sponsors)
56 | .navigationBarTitleDisplayMode(.inline)
57 | .toolbar {
58 | ToolbarItem(placement: .navigationBarTrailing, content: navigationBarTrailingItem)
59 | }
60 | }
61 | }
62 |
63 | struct AdminSponsors_Previews: PreviewProvider {
64 | static var previews: some View {
65 | AdminSponsors()
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "iOSDevUK_Icon_1024.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | }
9 | ],
10 | "info" : {
11 | "author" : "xcode",
12 | "version" : 1
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/AppIcon.appiconset/iOSDevUK_Icon_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Assets.xcassets/AppIcon.appiconset/iOSDevUK_Icon_1024.png
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xEC",
9 | "green" : "0x85",
10 | "red" : "0x83"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "display-p3",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0xFF",
27 | "green" : "0xFA",
28 | "red" : "0xFA"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/NewColors/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/NewColors/SegmentTitle.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xE9",
9 | "green" : "0xA1",
10 | "red" : "0x9F"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "display-p3",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x9D",
27 | "green" : "0x97",
28 | "red" : "0x98"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/NewColors/buttonBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xEC",
9 | "green" : "0x85",
10 | "red" : "0x83"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "display-p3",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0xE9",
27 | "green" : "0xA1",
28 | "red" : "0x9F"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/NewColors/buttonTitle.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFF",
9 | "green" : "0xFA",
10 | "red" : "0xFA"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "display-p3",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x15",
27 | "green" : "0x15",
28 | "red" : "0x15"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/NewColors/cardBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xF8",
9 | "green" : "0xF8",
10 | "red" : "0xF8"
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" : "0x28",
27 | "green" : "0x22",
28 | "red" : "0x23"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/NewColors/cardBottom.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x3A",
9 | "green" : "0x4C",
10 | "red" : "0xBB"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/NewColors/cardTop.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x3E",
9 | "green" : "0x53",
10 | "red" : "0xD4"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/NewColors/iconColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xEC",
9 | "green" : "0x85",
10 | "red" : "0x83"
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 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/NewColors/linkButton.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xF8",
9 | "green" : "0xEB",
10 | "red" : "0xEB"
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" : "0x3B",
27 | "green" : "0x2F",
28 | "red" : "0x30"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/NewColors/mainTextColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x15",
9 | "green" : "0x15",
10 | "red" : "0x15"
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" : "0xFA",
28 | "red" : "0xFA"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/NewColors/mapPin.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFF",
9 | "green" : "0xFA",
10 | "red" : "0xFA"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/NewColors/outlineColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xEA",
9 | "green" : "0xE9",
10 | "red" : "0xE9"
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" : "0x28",
27 | "green" : "0x22",
28 | "red" : "0x23"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/NewColors/pickerSelected.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x3B",
9 | "green" : "0x2F",
10 | "red" : "0x30"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "display-p3",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0xFF",
27 | "green" : "0xFA",
28 | "red" : "0xFA"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/NewColors/purple200.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xE9",
9 | "green" : "0xA1",
10 | "red" : "0x9F"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/NewColors/purple300.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xEC",
9 | "green" : "0x85",
10 | "red" : "0x83"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/NewColors/textBody.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x3B",
9 | "green" : "0x2F",
10 | "red" : "0x30"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "display-p3",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x9D",
27 | "green" : "0x97",
28 | "red" : "0x98"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/NewColors/textGrey.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x9D",
9 | "green" : "0x97",
10 | "red" : "0x98"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/backgroundColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xF6",
9 | "green" : "0xF1",
10 | "red" : "0xF2"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "display-p3",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x00",
27 | "green" : "0x00",
28 | "red" : "0x00"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/goldColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.000",
9 | "green" : "0.843",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/platinumColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.886",
9 | "green" : "0.894",
10 | "red" : "0.898"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/primary.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.567",
9 | "green" : "0.329",
10 | "red" : "0.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | },
20 | "properties" : {
21 | "localizable" : true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/secondary.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.290",
9 | "green" : "0.631",
10 | "red" : "0.761"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | },
20 | "properties" : {
21 | "localizable" : true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Colors/silverColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.796",
9 | "green" : "0.761",
10 | "red" : "0.745"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/Back.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Angle-left.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/EV.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "petrolStations.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/Shops.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "shopping.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/about.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Group 95-1.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/about.imageset/Group 95-1.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 2.500000 0.000000 cm
14 | 0.979167 0.979730 1.000000 scn
15 | 10.833333 22.000000 m
16 | 4.166667 22.000000 l
17 | 1.869167 22.000000 0.000000 19.943916 0.000000 17.416666 c
18 | 0.000000 4.583332 l
19 | 0.000000 2.056084 1.869167 0.000000 4.166667 0.000000 c
20 | 10.833333 0.000000 l
21 | 13.130833 0.000000 15.000000 2.056084 15.000000 4.583332 c
22 | 15.000000 17.416666 l
23 | 15.000000 19.943916 13.130833 22.000000 10.833333 22.000000 c
24 | h
25 | 13.333333 4.583332 m
26 | 13.333333 3.067165 12.211666 1.833332 10.833333 1.833332 c
27 | 4.166667 1.833332 l
28 | 2.788333 1.833332 1.666667 3.067165 1.666667 4.583332 c
29 | 1.666667 17.416666 l
30 | 1.666667 18.932833 2.788333 20.166666 4.166667 20.166666 c
31 | 10.833333 20.166666 l
32 | 12.211666 20.166666 13.333333 18.932833 13.333333 17.416666 c
33 | 13.333333 4.583332 l
34 | h
35 | 11.666666 16.500000 m
36 | 11.666666 15.993083 11.294167 15.583333 10.833333 15.583333 c
37 | 4.166667 15.583333 l
38 | 3.705833 15.583333 3.333333 15.993083 3.333333 16.500000 c
39 | 3.333333 17.006916 3.705833 17.416666 4.166667 17.416666 c
40 | 10.833333 17.416666 l
41 | 11.294167 17.416666 11.666666 17.006916 11.666666 16.500000 c
42 | h
43 | 11.666666 11.916666 m
44 | 11.666666 11.409750 11.294167 11.000000 10.833333 11.000000 c
45 | 4.166667 11.000000 l
46 | 3.705833 11.000000 3.333333 11.409750 3.333333 11.916666 c
47 | 3.333333 12.423583 3.705833 12.833333 4.166667 12.833333 c
48 | 10.833333 12.833333 l
49 | 11.294167 12.833333 11.666666 12.423583 11.666666 11.916666 c
50 | h
51 | 8.333333 7.333333 m
52 | 8.333333 6.826416 7.960833 6.416666 7.500000 6.416666 c
53 | 4.166667 6.416666 l
54 | 3.705833 6.416666 3.333333 6.826416 3.333333 7.333333 c
55 | 3.333333 7.840250 3.705833 8.250000 4.166667 8.250000 c
56 | 7.500000 8.250000 l
57 | 7.960833 8.250000 8.333333 7.840250 8.333333 7.333333 c
58 | h
59 | f
60 | n
61 | Q
62 |
63 | endstream
64 | endobj
65 |
66 | 3 0 obj
67 | 1716
68 | endobj
69 |
70 | 4 0 obj
71 | << /Annots []
72 | /Type /Page
73 | /MediaBox [ 0.000000 0.000000 20.000000 22.000000 ]
74 | /Resources 1 0 R
75 | /Contents 2 0 R
76 | /Parent 5 0 R
77 | >>
78 | endobj
79 |
80 | 5 0 obj
81 | << /Kids [ 4 0 R ]
82 | /Count 1
83 | /Type /Pages
84 | >>
85 | endobj
86 |
87 | 6 0 obj
88 | << /Pages 5 0 R
89 | /Type /Catalog
90 | >>
91 | endobj
92 |
93 | xref
94 | 0 7
95 | 0000000000 65535 f
96 | 0000000010 00000 n
97 | 0000000034 00000 n
98 | 0000001806 00000 n
99 | 0000001829 00000 n
100 | 0000002002 00000 n
101 | 0000002076 00000 n
102 | trailer
103 | << /ID [ (some) (id) ]
104 | /Root 6 0 R
105 | /Size 7
106 | >>
107 | startxref
108 | 2135
109 | %%EOF
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/aboutBackground.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "NantYrArian.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "appearances" : [
10 | {
11 | "appearance" : "luminosity",
12 | "value" : "dark"
13 | }
14 | ],
15 | "filename" : "Sunset.png",
16 | "idiom" : "universal",
17 | "scale" : "1x"
18 | },
19 | {
20 | "idiom" : "universal",
21 | "scale" : "2x"
22 | },
23 | {
24 | "appearances" : [
25 | {
26 | "appearance" : "luminosity",
27 | "value" : "dark"
28 | }
29 | ],
30 | "idiom" : "universal",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "universal",
35 | "scale" : "3x"
36 | },
37 | {
38 | "appearances" : [
39 | {
40 | "appearance" : "luminosity",
41 | "value" : "dark"
42 | }
43 | ],
44 | "idiom" : "universal",
45 | "scale" : "3x"
46 | }
47 | ],
48 | "info" : {
49 | "author" : "xcode",
50 | "version" : 1
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/aboutBackground.imageset/NantYrArian.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Assets.xcassets/Images/aboutBackground.imageset/NantYrArian.jpg
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/aboutBackground.imageset/Sunset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Assets.xcassets/Images/aboutBackground.imageset/Sunset.png
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/appIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "filename" : "iOSDevUK_Icon_1024.png",
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/appIcon.imageset/iOSDevUK_Icon_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Assets.xcassets/Images/appIcon.imageset/iOSDevUK_Icon_1024.png
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/attendee.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "attendee.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/background.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "background.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/background.imageset/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Assets.xcassets/Images/background.imageset/background.png
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/calendar.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "calendar.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/car.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "parking.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "original"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/conferenceImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "conferenceImage.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/conferenceImage.imageset/conferenceImage.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Assets.xcassets/Images/conferenceImage.imageset/conferenceImage.jpg
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/devices.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "devices.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/devices.imageset/devices.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Assets.xcassets/Images/devices.imageset/devices.png
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/emptySchedule.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "emotySchedule.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/help.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "file.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "original"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/home.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "home.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/house.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "key.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "original"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/inclusivity.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Info icons.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/info.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "info.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/link.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "link.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/linkedin.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "linkedin.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/linkedin.imageset/linkedin.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.000000 -0.013916 cm
14 | 0.623529 0.631373 0.913725 scn
15 | 11.527376 2.015236 m
16 | 9.522814 2.015236 l
17 | 9.522814 5.154486 l
18 | 9.522814 5.903080 9.509439 6.866736 8.480220 6.866736 c
19 | 7.436157 6.866736 7.276438 6.051110 7.276438 5.208985 c
20 | 7.276438 2.015454 l
21 | 5.271875 2.015454 l
22 | 5.271875 8.471018 l
23 | 7.196282 8.471018 l
24 | 7.196282 7.588799 l
25 | 7.223220 7.588799 l
26 | 7.615626 8.259705 8.345220 8.660423 9.121939 8.631611 c
27 | 11.153658 8.631611 11.528282 7.295236 11.528282 5.556673 c
28 | 11.527376 2.015236 l
29 | h
30 | 3.010000 9.353455 m
31 | 2.367532 9.353331 1.846625 9.874081 1.846500 10.516518 c
32 | 1.846375 11.158987 2.367125 11.679893 3.009563 11.680017 c
33 | 3.652032 11.680142 4.172938 11.159392 4.173063 10.516954 c
34 | 4.173188 9.874486 3.652469 9.353549 3.010000 9.353455 c
35 | h
36 | 4.012282 2.015204 m
37 | 2.005625 2.015204 l
38 | 2.005625 8.471018 l
39 | 4.012282 8.471018 l
40 | 4.012282 2.015204 l
41 | h
42 | 12.526752 13.541549 m
43 | 0.998313 13.541549 l
44 | 0.453469 13.547706 0.006656 13.111268 0.000000 12.566424 c
45 | 0.000000 0.990172 l
46 | 0.006406 0.445047 0.453188 0.008172 0.998313 0.013985 c
47 | 12.526752 0.013985 l
48 | 13.072970 0.007141 13.521470 0.443985 13.529033 0.990172 c
49 | 13.529033 12.567237 l
50 | 13.521251 13.113174 13.072689 13.549581 12.526752 13.542393 c
51 | f
52 | n
53 | Q
54 |
55 | endstream
56 | endobj
57 |
58 | 3 0 obj
59 | 1254
60 | endobj
61 |
62 | 4 0 obj
63 | << /Annots []
64 | /Type /Page
65 | /MediaBox [ 0.000000 0.000000 13.528809 13.528564 ]
66 | /Resources 1 0 R
67 | /Contents 2 0 R
68 | /Parent 5 0 R
69 | >>
70 | endobj
71 |
72 | 5 0 obj
73 | << /Kids [ 4 0 R ]
74 | /Count 1
75 | /Type /Pages
76 | >>
77 | endobj
78 |
79 | 6 0 obj
80 | << /Pages 5 0 R
81 | /Type /Catalog
82 | >>
83 | endobj
84 |
85 | xref
86 | 0 7
87 | 0000000000 65535 f
88 | 0000000010 00000 n
89 | 0000000034 00000 n
90 | 0000001344 00000 n
91 | 0000001367 00000 n
92 | 0000001540 00000 n
93 | 0000001614 00000 n
94 | trailer
95 | << /ID [ (some) (id) ]
96 | /Root 6 0 R
97 | /Size 7
98 | >>
99 | startxref
100 | 1673
101 | %%EOF
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/mapImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "mapImage.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/mapImage.imageset/mapImage.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Assets.xcassets/Images/mapImage.imageset/mapImage.pdf
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/mapPin.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Property 1=Location.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/mapPin.imageset/Property 1=Location.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Assets.xcassets/Images/mapPin.imageset/Property 1=Location.pdf
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/phone.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Group 95-2.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/phone.imageset/Group 95-2.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 3.333496 0.000732 cm
14 | 0.979167 0.979730 1.000000 scn
15 | 9.204166 21.999313 m
16 | 9.181666 22.000229 4.151667 22.000229 4.129167 21.999313 c
17 | 1.849167 21.977312 0.000000 19.929480 0.000000 17.416895 c
18 | 0.000000 4.583561 l
19 | 0.000000 2.056311 1.869167 0.000227 4.166667 0.000227 c
20 | 9.166666 0.000227 l
21 | 11.464167 0.000227 13.333333 2.056311 13.333333 4.583561 c
22 | 13.333333 17.415979 l
23 | 13.333333 19.929480 11.484166 21.976397 9.204166 21.999313 c
24 | h
25 | 11.666666 4.583561 m
26 | 11.666666 3.067394 10.545000 1.833561 9.166666 1.833561 c
27 | 4.166667 1.833561 l
28 | 2.788333 1.833561 1.666667 3.067394 1.666667 4.583561 c
29 | 1.666667 17.415979 l
30 | 1.666667 18.747896 2.531667 19.860729 3.675833 20.112812 c
31 | 4.254166 18.839561 l
32 | 4.395000 18.528812 4.684166 18.332645 5.000000 18.332645 c
33 | 8.333333 18.332645 l
34 | 8.649166 18.332645 8.937500 18.528812 9.079166 18.839561 c
35 | 9.657499 20.112812 l
36 | 10.801666 19.861645 11.666666 18.747896 11.666666 17.415979 c
37 | 11.666666 4.583561 l
38 | h
39 | 7.500000 3.666895 m
40 | 5.833333 3.666895 l
41 | 5.373333 3.666895 5.000000 4.077560 5.000000 4.583561 c
42 | 5.000000 5.089561 5.373333 5.500227 5.833333 5.500227 c
43 | 7.500000 5.500227 l
44 | 7.960000 5.500227 8.333333 5.089561 8.333333 4.583561 c
45 | 8.333333 4.077560 7.960000 3.666895 7.500000 3.666895 c
46 | h
47 | f
48 | n
49 | Q
50 |
51 | endstream
52 | endobj
53 |
54 | 3 0 obj
55 | 1291
56 | endobj
57 |
58 | 4 0 obj
59 | << /Annots []
60 | /Type /Page
61 | /MediaBox [ 0.000000 0.000000 20.000000 22.000000 ]
62 | /Resources 1 0 R
63 | /Contents 2 0 R
64 | /Parent 5 0 R
65 | >>
66 | endobj
67 |
68 | 5 0 obj
69 | << /Kids [ 4 0 R ]
70 | /Count 1
71 | /Type /Pages
72 | >>
73 | endobj
74 |
75 | 6 0 obj
76 | << /Pages 5 0 R
77 | /Type /Catalog
78 | >>
79 | endobj
80 |
81 | xref
82 | 0 7
83 | 0000000000 65535 f
84 | 0000000010 00000 n
85 | 0000000034 00000 n
86 | 0000001381 00000 n
87 | 0000001404 00000 n
88 | 0000001577 00000 n
89 | 0000001651 00000 n
90 | trailer
91 | << /ID [ (some) (id) ]
92 | /Root 6 0 R
93 | /Size 7
94 | >>
95 | startxref
96 | 1710
97 | %%EOF
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/placeholder.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "placeholder.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/placeholder.imageset/placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Assets.xcassets/Images/placeholder.imageset/placeholder.png
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/pubs.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "pubs.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/schedule.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "schedule.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/slack.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "slack.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/slack.imageset/slack.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Assets.xcassets/Images/slack.imageset/slack.pdf
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/sponsors.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Group 95.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/ticket.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "ticket.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "original"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/transport.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "transport.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/twitter.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "twitter.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/twitter.imageset/twitter.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.150391 0.000000 cm
14 | 0.623529 0.631373 0.913725 scn
15 | 8.152745 8.071955 m
16 | 13.252491 14.000000 l
17 | 12.044014 14.000000 l
18 | 7.615905 8.852762 l
19 | 4.079186 14.000000 l
20 | 0.000000 14.000000 l
21 | 5.348213 6.216465 l
22 | 0.000000 0.000000 l
23 | 1.208546 0.000000 l
24 | 5.884745 5.435658 l
25 | 9.619779 0.000000 l
26 | 13.698964 0.000000 l
27 | 8.152448 8.071955 l
28 | 8.152745 8.071955 l
29 | h
30 | 6.497476 6.147890 m
31 | 5.955591 6.922954 l
32 | 1.644001 13.090229 l
33 | 3.500257 13.090229 l
34 | 6.979759 8.113041 l
35 | 7.521645 7.337976 l
36 | 12.044586 0.868400 l
37 | 10.188331 0.868400 l
38 | 6.497476 6.147593 l
39 | 6.497476 6.147890 l
40 | h
41 | f
42 | n
43 | Q
44 |
45 | endstream
46 | endobj
47 |
48 | 3 0 obj
49 | 620
50 | endobj
51 |
52 | 4 0 obj
53 | << /Annots []
54 | /Type /Page
55 | /MediaBox [ 0.000000 0.000000 14.000000 14.000000 ]
56 | /Resources 1 0 R
57 | /Contents 2 0 R
58 | /Parent 5 0 R
59 | >>
60 | endobj
61 |
62 | 5 0 obj
63 | << /Kids [ 4 0 R ]
64 | /Count 1
65 | /Type /Pages
66 | >>
67 | endobj
68 |
69 | 6 0 obj
70 | << /Pages 5 0 R
71 | /Type /Catalog
72 | >>
73 | endobj
74 |
75 | xref
76 | 0 7
77 | 0000000000 65535 f
78 | 0000000010 00000 n
79 | 0000000034 00000 n
80 | 0000000710 00000 n
81 | 0000000732 00000 n
82 | 0000000905 00000 n
83 | 0000000979 00000 n
84 | trailer
85 | << /ID [ (some) (id) ]
86 | /Root 6 0 R
87 | /Size 7
88 | >>
89 | startxref
90 | 1038
91 | %%EOF
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/university.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "university.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/you.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "you.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/iOSDevUK/Assets.xcassets/Images/you.imageset/you.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Assets.xcassets/Images/you.imageset/you.jpg
--------------------------------------------------------------------------------
/iOSDevUK/DataUploading/Networking.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Networking.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 11/09/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | //used only for uploading sessions because of the date/time format
11 | struct CustomSession: Codable, Identifiable {
12 | let id: String
13 | let title: String
14 | let content: String
15 | let startDate: Date?
16 | let endDate: Date?
17 | let locationId: String?
18 | let speakerIds: [String]
19 | let type: SessionType
20 |
21 |
22 | init(from decoder: Decoder) throws {
23 | let container = try decoder.container(keyedBy: CodingKeys.self)
24 |
25 | self.id = try container.decode(String.self, forKey: .id)
26 | self.title = try container.decode(String.self, forKey: .title)
27 | self.content = try container.decode(String.self, forKey: .content)
28 | self.locationId = try? container.decode(String.self, forKey: .locationId)
29 | self.speakerIds = try container.decode([String].self, forKey: .speakerIds)
30 | self.type = try container.decode(SessionType.self, forKey: .type)
31 |
32 | let startDateString = try container.decode(String.self, forKey: .startDate)
33 | let endDateString = try container.decode(String.self, forKey: .endDate)
34 |
35 | let dateFormatter = DateFormatter()
36 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
37 |
38 | if let startDate = dateFormatter.date(from: startDateString) {
39 | self.startDate = startDate
40 | } else {
41 | throw DecodingError.dataCorruptedError(forKey: .startDate,
42 | in: container,
43 | debugDescription: "Invalid date format start")
44 | }
45 |
46 | if let endDate = dateFormatter.date(from: endDateString) {
47 | self.endDate = endDate
48 | } else {
49 | throw DecodingError.dataCorruptedError(forKey: .endDate,
50 | in: container,
51 | debugDescription: "Invalid date format end")
52 | }
53 | }
54 | }
55 |
56 |
57 | //MARK: - used only to get files from JSON and save to Firebase
58 | final class FileUploadService {
59 | static let shared = FileUploadService()
60 | let firebaseRepo = FirebaseRepository.shared
61 |
62 | private init() { }
63 |
64 | func uploadNewData(from fileName: String, to collection: FCollectionReference, objectType: T.Type) async throws {
65 |
66 | let objects = Bundle.main.decode([T].self, from: fileName)
67 |
68 | for object in objects {
69 | do {
70 | try firebaseRepo.saveData(data: object, to: collection)
71 | } catch {
72 | print(error.localizedDescription)
73 | throw AppError.unknownError
74 | }
75 | }
76 | }
77 | }
78 |
79 |
--------------------------------------------------------------------------------
/iOSDevUK/DataUploading/sponsors.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "urlText" : "Bumble website",
4 | "id" : "bumble",
5 | "sponsorCategory" : "Platinum",
6 | "tagline" : "Bumble Inc. is the parent company of Bumble, Badoo, Fruitz and Official. Our mission is to create a world where all relationships are healthy and equitable, through Kind Connections.",
7 | "imageLinkDark" : "https://firebasestorage.googleapis.com/v0/b/iosdevuk-data.appspot.com/o/Sponsors%2Fbumble-logo.png?alt=media",
8 | "imageLinkLight" : "https://firebasestorage.googleapis.com/v0/b/iosdevuk-data.appspot.com/o/Sponsors%2Fbumble-logo.png?alt=media",
9 | "name" : "Bumble",
10 | "url" : "https://bumble.com/"
11 | },
12 | {
13 | "urlText" : "Glassfy website",
14 | "id" : "glassfy",
15 | "sponsorCategory" : "Gold",
16 | "tagline" : "Scale your in-app subscriptions: Glassfy SDK provides you with in-app subscription infrastructure, real-time user subscription events and out-of-the-box revenue growth tools on iOS, Android and web.",
17 | "imageLinkDark" : "https://firebasestorage.googleapis.com/v0/b/iosdevuk-data.appspot.com/o/Sponsors%2Fglassfy-logo.png?alt=media",
18 | "imageLinkLight" : "https://firebasestorage.googleapis.com/v0/b/iosdevuk-data.appspot.com/o/Sponsors%2Fglassfy-logo.png?alt=media",
19 | "name" : "Glassfy",
20 | "url" : "https://glassfy.io/"
21 | },
22 | {
23 | "urlText" : "Marathon Labs website",
24 | "id" : "marathonlabs",
25 | "sponsorCategory" : "Gold",
26 | "tagline" : "Revolutionize your app testing experience: run any number of tests in less than 15 minutes with ZERO flakiness.",
27 | "imageLinkDark" : "https://firebasestorage.googleapis.com/v0/b/iosdevuk-data.appspot.com/o/Sponsors%2Fmarathon-logo-dark-mode.png?alt=media",
28 | "imageLinkLight" : "https://firebasestorage.googleapis.com/v0/b/iosdevuk-data.appspot.com/o/Sponsors%2Fmarathon-logo-light-mode.png?alt=media",
29 | "name" : "Marathan Labs",
30 | "url" : "https://marathonlabs.io/"
31 | }
32 | ]
--------------------------------------------------------------------------------
/iOSDevUK/Domains/AllSessionsViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AllSessionsViewModel.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 30/10/2022.
6 | //
7 |
8 | import SwiftUI
9 | import Combine
10 |
11 | final class AllSessionsViewModel: ObservableObject {
12 | private let firebaseRepository: FirebaseRepositoryProtocol
13 |
14 | @Published private(set) var fetchError: Error?
15 | @Published private(set) var eventInformation: EventInformation?
16 | @Published var sessions: [Session] = []
17 | @Published var selectedDate = ""
18 |
19 | private var cancellables: Set = []
20 |
21 |
22 | init(firebaseRepository: FirebaseRepositoryProtocol = FirebaseRepository.shared) {
23 | self.firebaseRepository = firebaseRepository
24 | }
25 |
26 | @MainActor
27 | func setCurrentDate() {
28 | guard !sessions.isEmpty else { return }
29 |
30 | //applies date preselection only during the conference
31 | if let startDate = eventInformation?.startDate, let endDate = eventInformation?.endDate {
32 | if !Date().isInRange(startDate: startDate, endDate: endDate) {
33 | return
34 | }
35 | }
36 |
37 | let currentDayMonth = Calendar.current.dateComponents([.month, .day], from: Date())
38 |
39 | for session in sessions {
40 | let sessionDayMont = Calendar.current.dateComponents([.month, .day], from: session.startDate)
41 |
42 | if currentDayMonth == sessionDayMont {
43 | selectedDate = session.startingDay
44 | return
45 | }
46 | }
47 |
48 | selectedDate = sessions.first?.startingDay ?? ""
49 | }
50 |
51 | @MainActor
52 | func fetchEventNotification() async {
53 | guard eventInformation == nil else { return }
54 |
55 | do {
56 | self.eventInformation = try await firebaseRepository.getDocuments(from: .AppInformation)?.first
57 | } catch (let error) {
58 | fetchError = error
59 | }
60 | }
61 |
62 | func setSessions(sessions: [Session]) {
63 | self.sessions = sessions
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/iOSDevUK/Domains/MapViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MapViewModel.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 25/03/2023.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 | import MapKit
11 |
12 | final class MapViewModel: ObservableObject {
13 | @Published var region: MKCoordinateRegion = MKCoordinateRegion(center: MapDetails.aberystwythCoordinates,
14 | span: MapDetails.defaultSpan)
15 |
16 | @Published var locationCategory: LocationType = .au
17 | @Published var allLocations: [Location] = []
18 |
19 | init(allLocations: [Location]) {
20 | self.allLocations = allLocations
21 | }
22 |
23 |
24 | static func openInGoogleMaps(location: Location) {
25 |
26 | let url = URL(string: "comgooglemaps://?saddr=&daddr=\(location.latitude),\(location.longitude)&directionsmode=driving")
27 |
28 | if UIApplication.shared.canOpenURL(url!) {
29 | UIApplication.shared.open(url!, options: [:], completionHandler: nil)
30 | } else {
31 | let urlBrowser = URL(string: "https://www.google.co.in/maps/dir/??saddr=&daddr=\(location.latitude),\(location.longitude)&directionsmode=driving")
32 |
33 | UIApplication.shared.open(urlBrowser!, options: [:], completionHandler: nil)
34 | }
35 | }
36 |
37 | static func openInAppleMaps(location: Location) {
38 |
39 | let mapItem = MKMapItem(placemark: MKPlacemark(coordinate: location.coordinate, addressDictionary:nil))
40 | mapItem.name = location.name
41 | mapItem.openInMaps(launchOptions: [MKLaunchOptionsDirectionsModeKey : MKLaunchOptionsDirectionsModeWalking])
42 | }
43 |
44 | func updateRegion() {
45 | let regionCenter = filteredAnnotations().last?.coordinate ?? MapDetails.aberystwythCoordinates
46 | region = MKCoordinateRegion(center: regionCenter, span: MapDetails.defaultSpan)
47 | }
48 |
49 | func filteredAnnotations() -> [Location] {
50 | allLocations.count > 1 ? allLocations.filter { $0.locationType == locationCategory } : allLocations
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/iOSDevUK/Domains/MyScheduleViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MyScheduleViewModel.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 10/09/2022.
6 | //
7 | import Combine
8 | import SwiftUI
9 |
10 | final class MyScheduleViewModel: ObservableObject {
11 | private let localStorage: LocalStorageServiceProtocol
12 | private let firebaseRepository: FirebaseRepositoryProtocol
13 |
14 | @Published private(set) var favoriteSessionIds: [String] = []
15 | @Published private(set) var sessions: [Session] = []
16 | @Published private(set) var groupedSessions: [String: [Session]] = [:]
17 | @Published private(set) var fetchError: Error?
18 |
19 | private var cancellables: Set = []
20 |
21 | init(
22 | localStorage: LocalStorageServiceProtocol = LocalStorageService.shared,
23 | firebaseRepository: FirebaseRepositoryProtocol = FirebaseRepository.shared
24 | ) {
25 | self.localStorage = localStorage
26 | self.firebaseRepository = firebaseRepository
27 | self.observerData()
28 | }
29 |
30 | private func observerData() {
31 | $sessions
32 | .sink(receiveValue: { receivedSessions in
33 | self.updateGroupedSessions(sessions: receivedSessions)
34 | })
35 | .store(in: &cancellables)
36 | }
37 |
38 | @MainActor
39 | func listenForSessions() async {
40 | guard self.sessions.isEmpty else { return }
41 |
42 | do {
43 | try await firebaseRepository.listen(from: .Session)
44 | .sink(receiveCompletion: { completion in
45 | switch completion {
46 | case .finished:
47 | return
48 | case .failure(let error):
49 | print("Error: \(error.localizedDescription)")
50 | }
51 | }, receiveValue: { [weak self] allSessions in
52 | self?.sessions = allSessions.sorted()
53 | })
54 | .store(in: &cancellables)
55 | } catch (let error) {
56 | fetchError = error
57 | }
58 | }
59 |
60 |
61 | func setFavSessions(favSessionIds: [String]) {
62 | self.favoriteSessionIds = favSessionIds
63 | updateGroupedSessions(sessions: self.sessions)
64 | }
65 |
66 | private func updateGroupedSessions(sessions: [Session]) {
67 | let filteredSessions = sessions.filter { self.favoriteSessionIds.contains($0.id) }
68 |
69 | self.groupedSessions = .init(
70 | grouping: filteredSessions,
71 | by: { $0.startingDay }
72 | )
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/iOSDevUK/Domains/SpeakerDetailViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpeakerDetailViewModel.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 20/09/2022.
6 | //
7 |
8 | import SwiftUI
9 | import Combine
10 |
11 | final class SpeakerDetailViewModel: ObservableObject {
12 | private let firebaseRepository: FirebaseRepositoryProtocol
13 |
14 | @Published private(set) var fetchError: Error?
15 |
16 | @Published private(set) var sessions:[Session] = []
17 | @Published private(set) var speaker: Speaker
18 | @Published private(set) var webLinks: [Weblink] = []
19 |
20 | @Published var showError = false
21 | private var cancellables: Set = []
22 |
23 | init(speaker: Speaker, firebaseRepository: FirebaseRepositoryProtocol = FirebaseRepository.shared) {
24 | self.speaker = speaker
25 | self.firebaseRepository = firebaseRepository
26 |
27 | $fetchError
28 | .dropFirst()
29 | .sink { [weak self] _ in
30 | self?.showError = true
31 | }
32 | .store(in: &cancellables)
33 |
34 | $speaker
35 | .map( { $0.webLinks ?? [] })
36 | .assign(to: &$webLinks)
37 | }
38 |
39 |
40 |
41 | @MainActor
42 | func getSpeakerSessions() async {
43 | do {
44 | sessions = try await firebaseRepository.getDocuments(from: .Session, where: FirebaseKeys.speakerIds, arrayContains: speaker.id) ?? []
45 | } catch (let error) {
46 | fetchError = error
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/iOSDevUK/Domains/WeatherViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WeatherViewModel.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 26/03/2023.
6 | //
7 |
8 | import Foundation
9 | import WeatherKit
10 | import CoreLocation
11 |
12 | final class WeatherViewModel: ObservableObject {
13 | private var mappingUtils: MappingUtilsProtocol
14 |
15 | @Published private var weather: Weather?
16 | @Published private var hourlyWeatherData: Forecast?
17 | @Published private(set) var currentWeather: WeatherData?
18 | @Published private(set) var hourlyWeather: [WeatherData] = []
19 |
20 | let weatherService: WeatherService
21 | var location: CLLocation?
22 |
23 | init(
24 | weatherService: WeatherService,
25 | mappingUtils: MappingUtilsProtocol = MappingUtils.shared
26 | ) {
27 | self.weatherService = weatherService
28 | self.mappingUtils = mappingUtils
29 | observerData()
30 | }
31 |
32 | private func observerData() {
33 | $hourlyWeatherData
34 | .map( { $0?.map(self.mappingUtils.convert) ?? [] })
35 | .assign(to: &$hourlyWeather)
36 |
37 | $weather
38 | .map( { self.mappingUtils.convert(input: $0?.currentWeather) })
39 | .assign(to: &$currentWeather)
40 | }
41 |
42 | @MainActor
43 | func getWeather() async {
44 | guard let location = location else { return }
45 | guard weather == nil else { return }
46 |
47 | self.weather = try? await weatherService.weather(for: location)
48 |
49 | let query = WeatherQuery.hourly(startDate: Date(), endDate: Date().tomorrow)
50 | self.hourlyWeatherData = try? await weatherService.weather(for: location, including: query)
51 | }
52 |
53 | func setLocation(location: CLLocation?) {
54 | self.location = location
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/iOSDevUK/Extensions/Array + Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array + Extension.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 12/08/2023.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Array {
11 | func at(_ index: Index) -> Element? {
12 | guard indices.contains(index) else {
13 | return nil
14 | }
15 | return self[index]
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/iOSDevUK/Extensions/Bundle + Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Bundle+Extensions.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 13/09/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Bundle {
11 | public var appName: String { getInfo("CFBundleName") }
12 | public var displayName: String { getInfo("CFBundleDisplayName") }
13 | public var language: String { getInfo("CFBundleDevelopmentRegion") }
14 | public var identifier: String { getInfo("CFBundleIdentifier") }
15 | public var copyright: String { getInfo("NSHumanReadableCopyright").replacingOccurrences(of: "\\\\n", with: "\n") }
16 |
17 | public var appBuild: String { getInfo("CFBundleVersion") }
18 | public var appVersionLong: String { getInfo("CFBundleShortVersionString") }
19 |
20 | fileprivate func getInfo(_ str: String) -> String { infoDictionary?[str] as? String ?? "⚠️" }
21 | }
22 |
23 |
24 | extension Bundle {
25 |
26 | func data(for filename: String) -> Data? {
27 | let bundle = Bundle(for: type(of: self))
28 | guard let url = bundle.url(forResource: filename, withExtension: nil) else { return nil }
29 |
30 | return try? Data(contentsOf: url)
31 | }
32 | }
33 |
34 | extension Bundle {
35 | func decode(_ type: T.Type, from file: String, dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) -> T {
36 | guard let url = self.url(forResource: file, withExtension: nil) else {
37 | fatalError("Failed to locate \(file) in bundle.")
38 | }
39 |
40 | guard let data = try? Data(contentsOf: url) else {
41 | fatalError("Failed to load \(file) from bundle.")
42 | }
43 |
44 | let decoder = JSONDecoder()
45 | decoder.dateDecodingStrategy = dateDecodingStrategy
46 | decoder.keyDecodingStrategy = keyDecodingStrategy
47 |
48 | do {
49 | return try decoder.decode(T.self, from: data)
50 | } catch DecodingError.keyNotFound(let key, let context) {
51 | fatalError("Failed to decode \(file) from bundle due to missing key '\(key.stringValue)' not found – \(context.debugDescription)")
52 | } catch DecodingError.typeMismatch(_, let context) {
53 | fatalError("Failed to decode \(file) from bundle due to type mismatch – \(context.debugDescription)")
54 | } catch DecodingError.valueNotFound(let type, let context) {
55 | fatalError("Failed to decode \(file) from bundle due to missing \(type) value – \(context.debugDescription)")
56 | } catch DecodingError.dataCorrupted(_) {
57 | fatalError("Failed to decode \(file) from bundle because it appears to be invalid JSON")
58 | } catch {
59 | fatalError("Failed to decode \(file) from bundle: \(error.localizedDescription)")
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/iOSDevUK/Extensions/Date + Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Date + Extensions.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 29/09/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | //needed to save Date to AppStorage
11 | extension Date: RawRepresentable {
12 | private static let formatter = ISO8601DateFormatter()
13 |
14 | public var rawValue: String {
15 | Date.formatter.string(from: self)
16 | }
17 |
18 | public init?(rawValue: String) {
19 | self = Date.formatter.date(from: rawValue) ?? Date()
20 | }
21 | }
22 |
23 | extension Date {
24 |
25 | func date(with year: Int, month: Int, day: Int = 1) -> Date {
26 | Calendar.current.date(from: DateComponents(year: year, month: month, day: day)) ?? Date()
27 | }
28 |
29 | var tomorrow: Date {
30 | Calendar.current.date(byAdding: .day, value: 1, to: Date())!
31 | }
32 |
33 | var longDate: String {
34 | let dateFormatter = DateFormatter()
35 | dateFormatter.dateFormat = "dd MMM yyyy"
36 | return dateFormatter.string(from: self)
37 | }
38 |
39 | var weekDayTime: String {
40 | let dateFormatter = DateFormatter()
41 | dateFormatter.dateFormat = "EE HH:mm"
42 | return dateFormatter.string(from: self)
43 | }
44 |
45 | var dateAndWeekDay: String {
46 | let dateFormatter = DateFormatter()
47 | dateFormatter.dateFormat = "d EE"
48 | return dateFormatter.string(from: self)
49 | }
50 |
51 | var dayOfTheMonth: String {
52 | let dateFormatter = DateFormatter()
53 | dateFormatter.dateFormat = "d"
54 | return dateFormatter.string(from: self)
55 | }
56 |
57 | var year: String {
58 | let dateFormatter = DateFormatter()
59 | dateFormatter.dateFormat = "yyyy"
60 | return dateFormatter.string(from: self)
61 | }
62 |
63 | var dayAndMonth: String {
64 | let dateFormatter = DateFormatter()
65 | dateFormatter.dateFormat = "d MMMM"
66 | return dateFormatter.string(from: self)
67 | }
68 |
69 | var weekDay: String {
70 | let dateFormatter = DateFormatter()
71 | dateFormatter.dateFormat = "EE"
72 | return dateFormatter.string(from: self)
73 | }
74 |
75 | var time: String {
76 | let dateFormatter = DateFormatter()
77 | dateFormatter.dateFormat = "HH:mm"
78 | return dateFormatter.string(from: self)
79 | }
80 |
81 | func isInRange(startDate: Date, endDate: Date) -> Bool {
82 | self >= startDate && self <= endDate
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/iOSDevUK/Extensions/Double + Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Double + Extensions.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 26/03/2023.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Double {
11 |
12 | var toCelsius: String {
13 | let formatter = MeasurementFormatter()
14 | formatter.unitOptions = .providedUnit
15 | formatter.locale = Locale(identifier: "en_GB")
16 |
17 | return formatter.string(from: Measurement(value: self, unit: UnitTemperature.celsius))
18 | }
19 |
20 | var toFahrenheit: String {
21 | let formatter = MeasurementFormatter()
22 | formatter.unitOptions = .providedUnit
23 | formatter.locale = Locale(identifier: "en_US")
24 | return formatter.string(from: Measurement(value: self, unit: UnitTemperature.fahrenheit))
25 | }
26 |
27 | func roundNearest() -> Double {
28 | (self * 2).rounded() / 2
29 | }
30 |
31 | func roundDown() -> String {
32 | self.truncatingRemainder(dividingBy: 1) == 0 ? String(format: "%.0f", self) : String(self)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/iOSDevUK/Extensions/String + Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String + Extensions.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 30/10/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | extension String {
11 | var removeDigits: String {
12 | components(separatedBy: CharacterSet.decimalDigits).joined()
13 | }
14 | var removeChars: String {
15 | components(separatedBy: CharacterSet.letters).joined()
16 | }
17 |
18 | var removeSpaces: String {
19 | self.replacingOccurrences(of: " ", with: "")
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/iOSDevUK/FirebaseHelpers/FCollectionReference.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FCollectionReference.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 10/09/2022.
6 | //
7 |
8 | import Firebase
9 | import FirebaseFirestoreSwift
10 |
11 |
12 | enum FCollectionReference: String {
13 | case Speaker
14 | case Session
15 | case Sponsor
16 | case Location
17 | case InformationItem
18 | case AppInformation
19 | case TestData //used only for testing purposes
20 | }
21 |
22 |
23 | func FirebaseReference(_ collectionReference: FCollectionReference) -> CollectionReference {
24 | return Firestore.firestore().collection(collectionReference.rawValue)
25 | }
26 |
--------------------------------------------------------------------------------
/iOSDevUK/FirebaseHelpers/FirebaseAuthenticationService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FirebaseAuthentication.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 14/01/2023.
6 | //
7 |
8 | import Foundation
9 | import Firebase
10 |
11 | protocol FirebaseAuthenticationServiceProtocol {
12 | func hasCurrentUser() -> Bool
13 | func loginUserWith(email: String, password: String) async throws -> Bool
14 | func logOutUser() async throws
15 | }
16 |
17 | class FirebaseAuthenticationService: FirebaseAuthenticationServiceProtocol {
18 |
19 | static let shared = FirebaseAuthenticationService()
20 |
21 | private init() {}
22 |
23 | func hasCurrentUser() -> Bool {
24 | Auth.auth().currentUser != nil
25 | }
26 |
27 | func loginUserWith(email: String, password: String) async throws -> Bool {
28 |
29 | try await withCheckedThrowingContinuation { continuation in
30 |
31 | Auth.auth().signIn(withEmail: email, password: password) { authResult, error in
32 | if let error = error {
33 | continuation.resume(throwing: error)
34 | return
35 | }
36 | continuation.resume(returning: true)
37 | }
38 | }
39 | }
40 |
41 | func logOutUser() async throws {
42 | do {
43 | try Auth.auth().signOut()
44 | } catch {
45 | throw error
46 | }
47 | }
48 | }
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/iOSDevUK/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIAppFonts
6 |
7 | SFPRODISPLAYBLACKITALIC.OTF
8 | SFPRODISPLAYBOLD.OTF
9 | SFPRODISPLAYHEAVYITALIC.OTF
10 | SFPRODISPLAYLIGHTITALIC.OTF
11 | SFPRODISPLAYMEDIUM.OTF
12 | SFPRODISPLAYREGULAR.OTF
13 | SFPRODISPLAYSEMIBOLDITALIC.OTF
14 | SFPRODISPLAYTHINITALIC.OTF
15 | SFPRODISPLAYULTRALIGHTITALIC.OTF
16 | SFProDisplay-Semibold.ttf
17 | SFProDisplay-Light.ttf
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/iOSDevUK/Model/AppErrors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppErrors.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 27/11/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | enum AppError: Error {
11 | case badSnapshot
12 | case unknownError
13 | }
14 |
15 | extension AppError: LocalizedError {
16 | public var errorDescription: String? {
17 | switch self {
18 | case .badSnapshot:
19 | return NSLocalizedString("Please check your internet connection and try again.", comment: "App error")
20 | case .unknownError:
21 | return NSLocalizedString("Please try again later.", comment: "App error")
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/iOSDevUK/Model/CoreData/iOSDevUK.xcdatamodeld/.xccurrentversion:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/iOSDevUK/Model/CoreData/iOSDevUK.xcdatamodeld/iOSDevUK.xcdatamodel/contents:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/iOSDevUK/Model/EventInformation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EventInformation.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 25/11/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | struct EventInformation: Codable {
11 | let about: String
12 | let notification: String
13 | let startDate: Date
14 | let endDate: Date
15 | let inclusivityText: String
16 | }
17 |
--------------------------------------------------------------------------------
/iOSDevUK/Model/InformationItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InformationItem.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 06/10/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | struct InformationItem: Codable, Identifiable, Comparable {
11 |
12 | let id: String
13 | let name: String
14 | let subtitle: String
15 | let link: String
16 | let imageName: String?
17 |
18 | var url: URL? {
19 | URL(string: link)
20 | }
21 |
22 | static func < (lhs: InformationItem, rhs: InformationItem) -> Bool {
23 | lhs.name < rhs.name
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/iOSDevUK/Model/Location.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Location.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 10/09/2022.
6 | //
7 |
8 | import SwiftUI
9 | import MapKit
10 |
11 | enum LocationType: String, Codable, CaseIterable {
12 | case au, transport, ev, pubs, sm
13 |
14 | var name: String {
15 | switch self {
16 | case .au:
17 | return "Aberystwyth University"
18 | case .transport:
19 | return "Transport"
20 | case .ev:
21 | return "Electric Vehicle"
22 | case .pubs:
23 | return "Pubs"
24 | case .sm:
25 | return "Supermarket"
26 | }
27 | }
28 |
29 | var shortName: String {
30 | switch self {
31 | case .au:
32 | return "University"
33 | case .transport:
34 | return "Transport"
35 | case .ev:
36 | return "EV"
37 | case .pubs:
38 | return "Pubs"
39 | case .sm:
40 | return "Shops"
41 | }
42 | }
43 | }
44 |
45 | struct Location: Codable, Identifiable, Hashable, Comparable {
46 | let id: String
47 | let name: String
48 | let note: String?
49 | let imageLink: String?
50 | let latitude: Double
51 | let longitude: Double
52 | let webLink: Weblink?
53 | let locationType: LocationType
54 |
55 | var coordinate: CLLocationCoordinate2D {
56 | CLLocation(latitude: latitude, longitude: longitude).coordinate
57 | }
58 |
59 | static func ==(lhs: Location, rhs: Location) -> Bool {
60 | lhs.id == rhs.id
61 | }
62 |
63 | static func <(lhs: Location, rhs: Location) -> Bool {
64 | lhs.name < rhs.name
65 | }
66 |
67 | var imageUrl: URL? {
68 | URL(string: imageLink ?? "")
69 | }
70 | }
71 |
72 |
--------------------------------------------------------------------------------
/iOSDevUK/Model/ScheduleType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScheduleType.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 20/03/2024.
6 | //
7 |
8 | import Foundation
9 |
10 | enum ScheduleType: String, CaseIterable {
11 | case mySchedule = "My Schedule"
12 | case allSessions = "All Sessions"
13 | }
14 |
--------------------------------------------------------------------------------
/iOSDevUK/Model/Session.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Session.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 21/09/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | enum SessionType: String, Codable, CaseIterable {
11 | case talk, workshop, lightningTalk, social, lunch, coffeeBiscuits, dinner, registration
12 |
13 | var name: String {
14 | switch self {
15 | case .talk:
16 | return "Talk"
17 | case .workshop:
18 | return "Workshop"
19 | case .lightningTalk:
20 | return "Lightning Talk"
21 | case .social:
22 | return "Social"
23 | case .lunch:
24 | return "Lunch"
25 | case .coffeeBiscuits:
26 | return "Coffee Biscuits"
27 | case .dinner:
28 | return "Dinner"
29 | case .registration:
30 | return "Registration"
31 | }
32 | }
33 | }
34 |
35 | struct Session: Codable, Identifiable, Hashable, Comparable {
36 |
37 | let id: String
38 | let title: String
39 | let content: String
40 | let startDate: Date
41 | let endDate: Date
42 | let locationId: String?
43 | let speakerIds: [String]
44 | let type: SessionType
45 |
46 | var startingDay: String {
47 | startDate.dateAndWeekDay
48 | }
49 |
50 | var duration: String {
51 | "\(startDate.weekDayTime) - \(endDate.time)"
52 | }
53 |
54 | static func < (lhs: Session, rhs: Session) -> Bool {
55 | lhs.startDate < rhs.startDate
56 | }
57 |
58 | func hash(into hasher: inout Hasher) {
59 | hasher.combine(title)
60 | hasher.combine(content)
61 | hasher.combine(startDate)
62 | hasher.combine(endDate)
63 | hasher.combine(locationId)
64 | hasher.combine(speakerIds)
65 | hasher.combine(type)
66 | }
67 | }
68 |
69 |
--------------------------------------------------------------------------------
/iOSDevUK/Model/Speaker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Speaker.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 10/09/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | struct Weblink: Codable, Hashable {
11 | let name: String
12 | let recordName: String
13 | let url: String
14 |
15 | var webUrl: URL? {
16 | URL(string: url)
17 | }
18 |
19 | }
20 |
21 | struct Speaker: Codable, Identifiable, Hashable, Comparable {
22 |
23 | let id: String
24 | let name: String
25 | let currentPosition: String?
26 | let biography: String
27 | let linkedIn: String?
28 | let twitterId: String?
29 | let imageLink: String
30 | let webLinks: [Weblink]?
31 |
32 | var imageUrl: URL? {
33 | URL(string: imageLink)
34 | }
35 |
36 | static func < (lhs: Speaker, rhs: Speaker) -> Bool {
37 | lhs.name < rhs.name
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/iOSDevUK/Model/Sponsor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Sponsor.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 21/09/2022.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | enum SponsorCategory: String, Codable, Comparable, Hashable, CaseIterable {
12 | case Platinum, Gold, Silver
13 |
14 | var sortOrder: Int {
15 | switch self {
16 | case .Platinum:
17 | return 0
18 | case .Gold:
19 | return 1
20 | case .Silver:
21 | return 2
22 | }
23 | }
24 |
25 | var color: Color {
26 | switch self {
27 | case .Platinum:
28 | return Color(.platinum)
29 | case .Gold:
30 | return Color(.gold)
31 | case .Silver:
32 | return Color(.silver)
33 | }
34 | }
35 |
36 | static func < (lhs: SponsorCategory, rhs: SponsorCategory) -> Bool {
37 | lhs.sortOrder < rhs.sortOrder
38 | }
39 |
40 | }
41 |
42 | struct Sponsor: Codable, Identifiable, Equatable, Hashable, Comparable {
43 |
44 | let id: String
45 | let name: String
46 | let tagline: String
47 | let url: String
48 | let urlText: String
49 | let sponsorCategory: SponsorCategory
50 | let imageLinkDark: String?
51 | let imageLinkLight: String?
52 |
53 | var imageUrlDark: URL? {
54 | URL(string: imageLinkDark ?? "")
55 | }
56 | var imageUrlLight: URL? {
57 | URL(string: imageLinkLight ?? "")
58 | }
59 |
60 | var webUrl: URL? {
61 | URL(string: url)
62 | }
63 |
64 | static func < (lhs: Sponsor, rhs: Sponsor) -> Bool {
65 | lhs.sponsorCategory < rhs.sponsorCategory
66 | }
67 |
68 | static func ==(lhs: Sponsor, rhs: Sponsor) -> Bool {
69 | lhs.id == rhs.id
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/iOSDevUK/Model/WeatherData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WeatherData.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 01/04/2023.
6 | //
7 |
8 | import Foundation
9 |
10 | struct WeatherData: Identifiable {
11 | var id = UUID().uuidString
12 | let lastCheck = Date()
13 | let tempDate: Date
14 | let condition: String
15 | let symbolName: String
16 |
17 | let currentTempC: Double
18 | let feelsLikeC: Double
19 |
20 | var currentTempF: Double {
21 | (currentTempC * 9/5) + 32
22 | }
23 | var feelsLikeF: Double {
24 | (currentTempC * 9/5) + 32
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/iOSDevUK/Modifiers/BackgroundModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BackgroundModifier.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 15/03/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct CapsuleBackgroundModifier: ViewModifier {
11 |
12 | let height: CGFloat
13 | let color: Color
14 |
15 | func body(content: Content) -> some View {
16 | content
17 | .padding(.vertical, 10)
18 | .padding(.horizontal, 16)
19 | .background {
20 | Capsule()
21 | .foregroundStyle(color)
22 | .frame(height: height)
23 | }
24 | }
25 | }
26 |
27 | extension View {
28 | func capsuleBackgroundView(height: CGFloat = 35, color: Color = Color(.linkButton)) -> some View {
29 | self.modifier(CapsuleBackgroundModifier(height: height, color: color))
30 | }
31 | }
32 |
33 |
34 | struct RoundBackgroundModifier: ViewModifier {
35 | let color: Color
36 |
37 | func body(content: Content) -> some View {
38 | content
39 | .padding(16)
40 | .background {
41 | RoundedRectangle(cornerRadius: 16)
42 | .foregroundStyle(color)
43 | }
44 | }
45 | }
46 |
47 | extension View {
48 | func roundBackgroundView(color: Color) -> some View {
49 | self.modifier(RoundBackgroundModifier(color: color))
50 | }
51 | }
52 |
53 |
54 | struct CapsuleOutlineModifier: ViewModifier {
55 |
56 | let height: CGFloat
57 | let color: Color
58 |
59 | func body(content: Content) -> some View {
60 | content
61 | .padding(.vertical, 10)
62 | .padding(.horizontal, 16)
63 | .background {
64 | Capsule()
65 | .stroke(color, lineWidth: 2)
66 | .frame(height: height)
67 | }
68 | }
69 | }
70 |
71 | extension View {
72 | func capsuleOutlineView(height: CGFloat = 40, color: Color = Color(.outline)) -> some View {
73 | self.modifier(CapsuleOutlineModifier(height: height, color: color))
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/iOSDevUK/Modifiers/ButtonModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ButtonModifier.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 16/03/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AppButtonStyle: ButtonStyle {
11 | let color: Color
12 | let fontSize: CGFloat
13 | let height: CGFloat
14 |
15 | init(color: Color, fontSize: CGFloat = 15, height: CGFloat = 16) {
16 | self.color = color
17 | self.fontSize = fontSize
18 | self.height = height
19 | }
20 |
21 |
22 |
23 | func makeBody(configuration: Configuration) -> some View {
24 | configuration.label
25 | .boldAppFont(size: fontSize)
26 | .foregroundStyle(Color(.buttonTitle))
27 | .padding(.horizontal, 30)
28 | .padding(.vertical, height)
29 | .frame(maxWidth: .infinity)
30 | .background(
31 | Capsule()
32 | .fill(color)
33 | )
34 | }
35 | }
36 |
37 | extension ButtonStyle where Self == AppButtonStyle {
38 | static func primary(with color: Color) -> AppButtonStyle {
39 | return AppButtonStyle(color: color)
40 | }
41 |
42 | static var appPrimary: AppButtonStyle {
43 | return primary(with: Color(.buttonBackground))
44 | }
45 |
46 | static func customButton(fontSize: CGFloat = 24, height: CGFloat = 15) -> AppButtonStyle {
47 | return AppButtonStyle(color: Color(.buttonBackground), fontSize: fontSize, height: height)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/iOSDevUK/Modifiers/FontModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppFontModifier.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 15/03/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AppFontModifier: ViewModifier {
11 | enum Weight {
12 | case light
13 | case regular
14 | case semibold
15 | case bold
16 | }
17 |
18 | let size: CGFloat
19 | let weight: Weight
20 |
21 | init(weight: Weight, size: CGFloat) {
22 | self.size = size
23 | self.weight = weight
24 | }
25 |
26 | func body(content: Content) -> some View {
27 | switch weight {
28 | case .regular:
29 | content.font(.custom("SFProDisplay-Regular", size: size))
30 | case .bold:
31 | content.font(.custom("SFProDisplay-Bold", size: size))
32 | case .light:
33 | content.font(.custom("SFProDisplay-Light", size: size))
34 | case .semibold:
35 | content.font(.custom("SFProDisplay-Semibold", size: size))
36 |
37 | }
38 | }
39 | }
40 |
41 | extension View {
42 | func appFont(weight: AppFontModifier.Weight = .regular, size: CGFloat) -> some View {
43 | modifier(AppFontModifier(weight: weight, size: size))
44 | }
45 |
46 | func boldAppFont(size: CGFloat) -> some View {
47 | appFont(weight: .bold, size: size)
48 | }
49 |
50 | func semiboldAppFont(size: CGFloat) -> some View {
51 | appFont(weight: .semibold, size: size)
52 | }
53 |
54 | func lightAppFont(size: CGFloat) -> some View {
55 | appFont(weight: .light, size: size)
56 | }
57 | }
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/iOSDevUK/Navigation/NavigationRouter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavigationRouter.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 20/11/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | final class NavigationRouter: ObservableObject {
11 | @Published var homePath: [Destination] = []
12 | @Published var schedulePath: [Destination] = []
13 | @Published var attendeePath: [Destination] = []
14 | @Published var infoPath: [InfoDestination] = []
15 | }
16 |
17 |
18 | enum Destination: Hashable {
19 | case speaker(Speaker)
20 | case speakers([Speaker])
21 | case session(Session)
22 | case sessions([Session])
23 | case sponsor
24 | case locations(locations: [Location])
25 | }
26 |
27 | enum InfoDestination: Hashable {
28 | case inclusivity
29 | case sponsors
30 | case aboutApp
31 | case appInformation
32 | case locationList
33 | case locations(locations: [Location])
34 | case admin
35 | case adminSpeakers
36 | case adminAddSpeaker(Speaker?)
37 | case adminSessions
38 | case adminAddSession(Session?)
39 | case adminLocations
40 | case adminAddLocation(Location?)
41 | case adminSponsors
42 | case adminAddSponsor(Sponsor?)
43 | }
44 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/AttendeeView/AttendeeRowView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AttendeeRowView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 17/03/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AttendeeRowView: View {
11 | let subtitle: String?
12 | let title: String
13 | let description: String
14 | let image: Image
15 |
16 | init(subtitle: String? = nil, title: String, description: String, image: Image) {
17 | self.subtitle = subtitle
18 | self.title = title
19 | self.description = description
20 | self.image = image
21 | }
22 |
23 | var body: some View {
24 | HStack {
25 | VStack(alignment: .leading, spacing: 10) {
26 | if let subtitle {
27 | Text(subtitle)
28 | .appFont(size: 12)
29 | .foregroundStyle(Color(.purple200))
30 | }
31 |
32 | Text(title)
33 | .semiboldAppFont(size: 22)
34 | .foregroundStyle(Color(.mainText))
35 |
36 | Text(description)
37 | .multilineTextAlignment(.leading)
38 | .appFont(size: 14)
39 | .foregroundStyle(Color(.textBody))
40 | }
41 |
42 | Spacer()
43 |
44 | image
45 | }
46 | .padding(10)
47 | .roundBackgroundView(color: Color(.cardBackground))
48 | }
49 | }
50 |
51 | #Preview {
52 | AttendeeRowView(subtitle: nil, title: "", description: "", image: Image(systemName: "circle"))
53 | }
54 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/AttendeeView/AttendeeScreen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AttendeeView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 10/09/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AttendeeScreen: View {
11 | @EnvironmentObject var viewModel: BaseViewModel
12 | @EnvironmentObject var router: NavigationRouter
13 |
14 | @ViewBuilder
15 | private func main() -> some View {
16 | ScrollView {
17 | LazyVStack(alignment: .leading, spacing: 15) {
18 | ForEach(viewModel.infoItems) { item in
19 | if let url = item.url {
20 | Link(destination: url) {
21 | AttendeeRowView(
22 | subtitle: item.imageName == "ticket" ? "LINK" : "PDF",
23 | title: item.name,
24 | description: item.subtitle,
25 | image: Image(item.imageName ?? "")
26 | )
27 | }
28 | } else {
29 | AttendeeRowView(
30 | title: item.name,
31 | description: item.subtitle,
32 | image: Image(item.imageName ?? "")
33 | )
34 | }
35 | }
36 | }
37 | .padding([.horizontal, .top], 16)
38 | }
39 | }
40 |
41 | var body: some View {
42 | NavigationStack(path: $router.attendeePath) {
43 | main()
44 | .navigationTitle("Attendee Information")
45 | .navigationDestination(for: Destination.self) { destination in
46 | switch destination {
47 | case .session(let session):
48 | SessionDetailView(session: session)
49 | case .sessions(let sessions):
50 | AllSessionsView(sessions: sessions)
51 | case .speaker(let speaker):
52 | SpeakerDetailScreen(speaker: speaker)
53 | case .speakers(let speakers):
54 | AllSpeakersView(speakers: speakers)
55 | case .sponsor:
56 | SponsorsScreen()
57 | case .locations(let locations):
58 | MapScreen(allLocations: locations)
59 | }
60 | }
61 | }
62 | }
63 | }
64 |
65 | struct AttendeeView_Previews: PreviewProvider {
66 | static var previews: some View {
67 | AttendeeScreen()
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/HomeView/ContactButtonView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContactButtonView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 18/03/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ContactButtonView: View {
11 | let imageName: String
12 | let title: String
13 |
14 | var body: some View {
15 | HStack {
16 | Image(imageName)
17 | .resizable()
18 | .aspectRatio(contentMode: .fit)
19 | .frame(width: 22)
20 | .foregroundStyle(Color(.mainText))
21 |
22 | Spacer()
23 |
24 | Text(title)
25 | .semiboldAppFont(size: 14)
26 | .foregroundStyle(Color(.mainText))
27 |
28 | Spacer()
29 | }
30 | .padding(.vertical, 2)
31 | .capsuleOutlineView()
32 | }
33 | }
34 |
35 | #Preview {
36 | ContactButtonView(imageName: "", title: "")
37 | }
38 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/HomeView/EventInfoView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EventInfoView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 18/03/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct EventInfoView: View {
11 |
12 | let eventDate: String?
13 | let notificationBody: String?
14 |
15 | var body: some View {
16 | VStack(alignment: .leading, spacing: 20) {
17 | Text("Details")
18 | .foregroundStyle(Color(.mainText))
19 | .boldAppFont(size: 20)
20 |
21 | UniversityMapView(eventDate: eventDate)
22 |
23 | if let notificationBody {
24 | Text(notificationBody)
25 | .appFont(size: 16)
26 | .foregroundStyle(Color(.mainText))
27 | }
28 | }
29 | }
30 | }
31 |
32 | #Preview {
33 | EventInfoView(eventDate: "", notificationBody: "")
34 | }
35 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/HomeView/SessionsHorizontalScrollView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SessionsHorizontalScrollView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 09/04/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SessionsHorizontalScrollView: View {
11 | let sessions: [Session]
12 | let geometry: GeometryProxy
13 |
14 | var body: some View {
15 |
16 | VStack(alignment: .leading) {
17 | HStack {
18 | Text(AppStrings.sessions)
19 | .foregroundStyle(Color(.mainText))
20 | .boldAppFont(size: 20)
21 |
22 | Spacer()
23 | NavigationLink(AppStrings.viewAll, value: Destination.sessions(sessions))
24 | .foregroundStyle(Color(.textGrey))
25 | }
26 | .padding(.horizontal)
27 |
28 | if #available(iOS 17.0, *) {
29 | ScrollView(.horizontal) {
30 | LazyHStack(spacing: 10) {
31 | ForEach(sessions, id: \.self) { session in
32 |
33 | NavigationLink(value: Destination.session(session)) {
34 | SessionCardView(session: session, geometry: geometry)
35 | .id(session)
36 | }
37 | }
38 | }
39 | .scrollTargetLayout()
40 | }
41 | .scrollTargetBehavior(.viewAligned)
42 | .safeAreaPadding(.horizontal, 20)
43 | .scrollIndicators(.hidden)
44 | } else {
45 | ScrollView(.horizontal) {
46 | LazyHStack(spacing: 10) {
47 | ForEach(sessions, id: \.self) { session in
48 |
49 | NavigationLink(value: Destination.session(session)) {
50 | SessionCardView(session: session, geometry: geometry)
51 | .id(session)
52 | }
53 | }
54 | }
55 | .padding(.leading, 16)
56 | }
57 | .scrollIndicators(.hidden)
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/HomeView/SpeakersHorizontalScrollView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpeakersHorizontalScrollView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 15/03/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SpeakersHorizontalScrollView: View {
11 | let speakers: [Speaker]
12 |
13 | var body: some View {
14 | VStack(alignment: .leading, spacing: 20) {
15 | HStack {
16 | Text(AppStrings.speakers)
17 | .foregroundStyle(Color(.mainText))
18 | .boldAppFont(size: 20)
19 |
20 | Spacer()
21 |
22 | NavigationLink(AppStrings.viewAll, value: Destination.speakers(speakers.sorted()))
23 | .foregroundStyle(Color(.textGrey))
24 | }
25 | .padding(.horizontal)
26 |
27 |
28 | if #available(iOS 17.0, *) {
29 | ScrollView(.horizontal) {
30 | LazyHStack(spacing: 5) {
31 | ForEach(speakers) { speaker in
32 | NavigationLink(value: Destination.speaker(speaker)) {
33 | SpeakerCardView(speaker: speaker)
34 | .frame(width: 110)
35 | }
36 | }
37 | }
38 | .scrollTargetLayout()
39 | }
40 | .scrollTargetBehavior(.viewAligned)
41 | .safeAreaPadding(.horizontal, 10)
42 | .scrollIndicators(.hidden)
43 | } else {
44 | ScrollView(.horizontal) {
45 | LazyHStack(spacing: 10) {
46 | ForEach(speakers) { speaker in
47 | NavigationLink(value: Destination.speaker(speaker)) {
48 | SpeakerCardView(speaker: speaker)
49 | .frame(width: 110)
50 | }
51 | }
52 | }
53 | .padding(.leading, 16)
54 | }
55 | .scrollIndicators(.hidden)
56 | }
57 | }
58 | }
59 | }
60 |
61 | #Preview {
62 | SpeakersHorizontalScrollView(speakers: DummyData.speakers)
63 | }
64 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/InfoView/AboutView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AboutView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 13/09/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AboutView: View {
11 | @EnvironmentObject var viewModel: BaseViewModel
12 | @Environment(\.presentationMode) var presentationMode
13 |
14 | @ViewBuilder
15 | private func navigationBarLeadingItem() -> some View {
16 | Button { presentationMode.wrappedValue.dismiss() }
17 | label: { Image(.back) }
18 | .tint(Color(.mainText))
19 | }
20 |
21 | var body: some View {
22 | ScrollView {
23 | Image(.aboutBackground)
24 | .resizable()
25 | .aspectRatio(contentMode: .fit)
26 |
27 | VStack(alignment: .leading, spacing: 20) {
28 |
29 | Text("iOSDevUK is the UK conference for iOS developers. It takes place in Aberystwyth, on the mid-Wales coast, from \(viewModel.eventInformation?.startDate.dayOfTheMonth ?? "") to the \(viewModel.eventInformation?.endDate.dayAndMonth ?? "").")
30 |
31 | Text("• Great talks\n• Great get-togethers\n• Optional workshops") .multilineTextAlignment(.leading)
32 |
33 | Text("The conference is organised by Aberystwyth University and is now in its eleventh year. iOS, iPhone, iPad, Apple Watch, watchOS, Apple TV and tvOS are trademarks of Apple Inc. For the avoidance of doubt, Apple Inc. has no association with this conference.")
34 | }
35 | .padding(20)
36 | }
37 | .appFont(size: 16)
38 | .foregroundStyle(Color(.textBody))
39 | .edgesIgnoringSafeArea(.top)
40 | .navigationBarBackButtonHidden()
41 | .toolbar {
42 | ToolbarItem(placement: .topBarLeading, content: navigationBarLeadingItem)
43 | }
44 |
45 | }
46 | }
47 |
48 | struct AboutView_Previews: PreviewProvider {
49 | static var previews: some View {
50 | AboutView()
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/InfoView/AppInformationView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppInformationView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 13/09/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AppInformationView: View {
11 | @EnvironmentObject var viewModel: BaseViewModel
12 | @Environment(\.presentationMode) var presentationMode
13 |
14 | @ViewBuilder
15 | private func navigationBarLeadingItem() -> some View {
16 | Button { presentationMode.wrappedValue.dismiss() }
17 | label: { Image(.back) }
18 | .tint(Color(.mainText))
19 | }
20 |
21 | var body: some View {
22 |
23 | ScrollView {
24 | VStack(spacing: 20) {
25 |
26 | Text("The app has been developed by David Kababyan ([@Dave_iOSDev](https://twitter.com/Dave_iOSDev)), with contributions from Neil Taylor ([@digidol](https://twitter.com/digidol)).\n\nApp data wrangling by Chris Price ([@iOSDevUK](https://twitter.com/iOSDevUK)).")
27 |
28 | Text("Designed by Oksana Korotun ([@LinkedIn](https://linkedin.com/in/oksana-korotun/)).")
29 |
30 | Text("Thanks to John Gilbey ([@John_Gilbey](https://twitter.com/John_Gilbey)) for his picture of conference attendees that is used in this app.")
31 |
32 | Text("Other images by Neil & Chris.")
33 |
34 | VStack(spacing: 10) {
35 | if let twitterUrl = URL(string: ContactAccounts.developer) {
36 | Link("Contact the developer", destination: twitterUrl)
37 | }
38 |
39 | if let linkedInUrl = URL(string: ContactAccounts.designer) {
40 | Link("Contact the designer", destination: linkedInUrl)
41 | }
42 |
43 | if let twitterUrl = URL(string: ContactAccounts.digidol) {
44 | Link("Contact @digidol", destination: twitterUrl)
45 | }
46 | }
47 | .tint(Color(.purple300))
48 |
49 | Spacer()
50 | }
51 | .appFont(size: 16)
52 | .multilineTextAlignment(.center)
53 | .padding(.top, 25)
54 | .padding()
55 | .navigationTitle(AppStrings.appInfo)
56 | .navigationBarBackButtonHidden()
57 | .toolbar {
58 | ToolbarItem(placement: .topBarLeading, content: navigationBarLeadingItem)
59 | }
60 |
61 | }
62 | }
63 | }
64 |
65 | struct AppInformationView_Previews: PreviewProvider {
66 | static var previews: some View {
67 | Group {
68 | NavigationView {
69 | AppInformationView()
70 | .preferredColorScheme(.dark)
71 | }
72 | NavigationView {
73 | AppInformationView()
74 | .preferredColorScheme(.light)
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/InfoView/DropDownRowView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DropDownRowView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 01/04/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct DropDownRowView: View {
11 | let title: String
12 | let imageName: String
13 | let locations: [Location]
14 | @State private var isExpanded = false
15 |
16 | var body: some View {
17 | VStack(alignment: .leading) {
18 | LocationHeaderView(
19 | text: title,
20 | imageName: imageName,
21 | isExpanded: isExpanded
22 | ) {
23 | withAnimation {
24 | isExpanded.toggle()
25 | }
26 | }
27 |
28 | if isExpanded {
29 | ForEach(locations) { location in
30 | NavigationLink(value: InfoDestination.locations(locations: [location])) {
31 | Text(location.name)
32 | .semiboldAppFont(size: 16)
33 | .lineLimit(1)
34 | .minimumScaleFactor(0.6)
35 | }
36 | .tint(Color(.buttonBackground))
37 | .frame(height: 44)
38 | .padding(.horizontal, 10)
39 | }
40 | }
41 | }
42 | .roundBackgroundView(color: Color(.cardBackground))
43 | }
44 | }
45 |
46 | #Preview {
47 | DropDownRowView(title: "Aber uni", imageName: "", locations: [DummyData.location, DummyData.location])
48 | }
49 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/InfoView/InclusivityView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InclusivityView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 11/09/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct InclusivityView: View {
11 | @EnvironmentObject var viewModel: BaseViewModel
12 | @Environment(\.presentationMode) var presentationMode
13 |
14 |
15 | @ViewBuilder
16 | private func navigationBarLeadingItem() -> some View {
17 | Button { presentationMode.wrappedValue.dismiss() }
18 | label: { Image(.back) }
19 | .tint(Color(.mainText))
20 | }
21 |
22 | var body: some View {
23 | ScrollView {
24 | Image(.conference)
25 | .resizable()
26 | .aspectRatio(contentMode: .fit)
27 |
28 | Text(AppStrings.inclusivityPolicy)
29 | .semiboldAppFont(size: 24)
30 | .foregroundStyle(Color(.mainText))
31 | .minimumScaleFactor(0.7)
32 |
33 | Spacer()
34 |
35 | if let inclusivityText = viewModel.eventInformation?.inclusivityText {
36 | Text(inclusivityText)
37 | .appFont(size: 16)
38 | .foregroundStyle(Color(.textBody))
39 | .lineLimit(nil)
40 | .padding()
41 | }
42 | }
43 | .edgesIgnoringSafeArea(.top)
44 | .navigationBarBackButtonHidden()
45 | .toolbar {
46 | ToolbarItem(placement: .topBarLeading, content: navigationBarLeadingItem)
47 | }
48 |
49 | }
50 | }
51 |
52 | struct InclusivityViewView_Previews: PreviewProvider {
53 | static var previews: some View {
54 | InclusivityView()
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/InfoView/LocationHeaderView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocationHeaderView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 01/04/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct LocationHeaderView: View {
11 | let text: String
12 | let imageName: String
13 | let isExpanded: Bool
14 | var action: () -> Void
15 |
16 | init(text: String, imageName: String, isExpanded: Bool, action: @escaping () -> Void) {
17 | self.text = text
18 | self.imageName = imageName
19 | self.isExpanded = isExpanded
20 | self.action = action
21 | }
22 |
23 | var body: some View {
24 |
25 | HStack(spacing: 10) {
26 | Image(imageName)
27 | .resizable()
28 | .frame(width: 20, height: 20)
29 | .foregroundStyle(Color(.icon))
30 | .roundBackgroundView(color: Color(.linkButton))
31 |
32 | Text(text)
33 | .boldAppFont(size: 16)
34 | .foregroundStyle(Color(.mainText))
35 |
36 | Spacer()
37 |
38 | Image(systemName: isExpanded ? ImageNames.chevronUp : ImageNames.chevronDown)
39 | }
40 | .contentShape(Rectangle()) //needed to recognise tap on empty space
41 | .onTapGesture {
42 | action()
43 | }
44 | }
45 | }
46 |
47 | #Preview {
48 | LocationHeaderView(text: "", imageName: "", isExpanded: false) {
49 |
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/InfoView/LocationsListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocationsListView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 25/03/2023.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct LocationsListView: View {
11 | @EnvironmentObject var viewModel: BaseViewModel
12 | @Environment(\.presentationMode) var presentationMode
13 |
14 | @State var expanded: Bool = true
15 | var groupedLocations: [String : [Location]] {
16 | .init(
17 | grouping: viewModel.locations,
18 | by: { $0.locationType.rawValue }
19 | )
20 | }
21 |
22 | @ViewBuilder
23 | private func navigationBarLeadingItem() -> some View {
24 | Button { presentationMode.wrappedValue.dismiss() }
25 | label: { Image(.back) }
26 | .tint(Color(.mainText))
27 | }
28 |
29 | @ViewBuilder
30 | private func main() -> some View {
31 | VStack(alignment: .leading, spacing: 10) {
32 | NavigationLink(value: InfoDestination.locations(locations: viewModel.locations)) {
33 | UniversityMapView()
34 | .padding(.horizontal, 16)
35 | }
36 | .buttonStyle(.plain)
37 |
38 | ScrollView {
39 | ForEach(groupedLocations.keys.sorted(), id: \String.self) { key in
40 | let locationType: LocationType = LocationType(rawValue: key) ?? .au
41 |
42 | DropDownRowView(
43 | title: locationType.name,
44 | imageName: locationType.shortName.lowercased(),
45 | locations: groupedLocations[key] ?? []
46 | )
47 | .padding(.horizontal, 16)
48 | }
49 | }
50 | }
51 | }
52 |
53 |
54 | var body: some View {
55 | main()
56 | .navigationBarTitle(AppStrings.locations, displayMode: .inline)
57 | .navigationBarBackButtonHidden()
58 | .toolbar {
59 | ToolbarItem(placement: .topBarLeading, content: navigationBarLeadingItem)
60 | }
61 | }
62 | }
63 |
64 | struct LocationsListView_Previews: PreviewProvider {
65 | static var previews: some View {
66 | LocationsListView()
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/SecondaryViews/Sessions/AllSessionsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AllSessionsView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 21/09/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AllSessionsView: View {
11 |
12 | @StateObject private var viewModel = AllSessionsViewModel()
13 | @EnvironmentObject var baseViewModel: BaseViewModel
14 |
15 | @Environment(\.presentationMode) var presentationMode
16 | var showBackButton: Bool = true
17 | let sessions: [Session]
18 | private var groupedSessions: [String : [Session]] {
19 | .init(
20 | grouping: baseViewModel.sessions,
21 | by: { $0.startingDay }
22 | )
23 | }
24 |
25 |
26 | @ViewBuilder
27 | private func navigationBarLeadingItem() -> some View {
28 | Button { presentationMode.wrappedValue.dismiss() }
29 | label: { Image(.back) }
30 | .tint(Color(.mainText))
31 | }
32 |
33 | var body: some View {
34 | ScrollView {
35 | LazyVStack(alignment: .leading, spacing: 10) {
36 | ForEach(groupedSessions[viewModel.selectedDate]?.sorted() ?? [], id: \.id) { session in
37 |
38 | NavigationLink(value: Destination.session(session)) {
39 | SessionRowView(
40 | session: session,
41 | showSpeakers: true
42 | ) {
43 | baseViewModel.updateFavoriteSession(sessionId: session.id)
44 | }
45 | .id(session)
46 | }
47 | }
48 | }
49 | .padding(.horizontal, 16)
50 | }
51 | .task { await viewModel.fetchEventNotification() }
52 | .task { baseViewModel.loadFavSessions() }
53 | .task { viewModel.setCurrentDate() }
54 | .onAppear { viewModel.setSessions(sessions: sessions) }
55 | .safeAreaInset(edge: .top) {
56 | Picker("", selection: $viewModel.selectedDate.animation()) {
57 | ForEach(groupedSessions.keys.sorted(), id: \String.self) { weekDay in
58 | Text(weekDay.removeDigits)
59 | }
60 | }
61 | .pickerStyle(.segmented)
62 | .padding(10)
63 | .background(Color(.background))
64 | }
65 | .navigationTitle(showBackButton ? "All Sessions" : "")
66 | .navigationBarTitleDisplayMode(.inline)
67 | .navigationBarBackButtonHidden()
68 | .toolbar {
69 | if showBackButton {
70 | ToolbarItem(placement: .topBarLeading, content: navigationBarLeadingItem)
71 | }
72 | }
73 | }
74 | }
75 |
76 | struct SessionsView_Previews: PreviewProvider {
77 | static var previews: some View {
78 | NavigationView {
79 | AllSessionsView(sessions: DummyData.sessions)
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/SecondaryViews/Sessions/SessionDetailView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SessionDetailView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 03/10/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SessionDetailView: View {
11 | @EnvironmentObject var baseViewModel: BaseViewModel
12 | @Environment(\.presentationMode) var presentationMode
13 |
14 | let session: Session
15 |
16 | @ViewBuilder
17 | private func navigationBarLeadingItem() -> some View {
18 | Button { presentationMode.wrappedValue.dismiss() }
19 | label: { Image(.back) }
20 | .tint(Color(.mainText))
21 | }
22 |
23 | @ViewBuilder
24 | private func main() -> some View {
25 |
26 | ScrollView {
27 | VStack(alignment: .leading, spacing: 10) {
28 |
29 | Text(session.title)
30 | .boldAppFont(size: 24)
31 | .foregroundStyle(Color(.mainText))
32 | .padding(.top, 10)
33 |
34 | Text(session.content)
35 | .foregroundStyle(Color(.textBody))
36 | .appFont(size: 16)
37 | .multilineTextAlignment(.leading)
38 | .padding(.vertical, 10)
39 |
40 |
41 | InfoRowView(text: session.duration, imageName: ImageNames.calendar)
42 |
43 |
44 | if let location = baseViewModel.getLocation(with: session.locationId) {
45 | NavigationLink(value: Destination.locations(locations: [location])) {
46 | InfoRowView(text: location.name, imageName: ImageNames.location)
47 | }
48 | }
49 |
50 | if !baseViewModel.getSpeakers(with: session.speakerIds).isEmpty {
51 | ForEach(baseViewModel.getSpeakers(with: session.speakerIds)) { speaker in
52 | NavigationLink(value: Destination.speaker(speaker)) {
53 | SpeakerRowView(speaker: speaker)
54 | }
55 | }
56 | }
57 | }
58 | .padding(.horizontal, 16)
59 | }
60 | .scrollIndicators(.hidden)
61 | .safeAreaInset(edge: .bottom) {
62 | AnimatedButtonView(
63 | title: baseViewModel.isFavorite(session.id) ? "Remove from schedule" : "Add to schedule",
64 | color: Color(.buttonBackground),
65 | action: {
66 | baseViewModel.updateFavoriteSession(sessionId: session.id)
67 | }
68 | )
69 | .frame(height: 50)
70 | .padding([.bottom, .horizontal], 16)
71 | }
72 | }
73 |
74 | var body: some View {
75 | main()
76 | .navigationTitle("Event Info")
77 | .navigationBarTitleDisplayMode(.inline)
78 | .navigationBarBackButtonHidden()
79 | .toolbar {
80 | ToolbarItem(placement: .topBarLeading, content: navigationBarLeadingItem)
81 | }
82 |
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/SecondaryViews/SpeakerView/AllSpeakersView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AllSpeakersView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 10/09/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AllSpeakersView: View {
11 | @Environment(\.presentationMode) var presentationMode
12 |
13 | let speakers: [Speaker]
14 |
15 | @ViewBuilder
16 | private func navigationBarLeadingItem() -> some View {
17 | Button { presentationMode.wrappedValue.dismiss() }
18 | label: { Image(.back) }
19 | .tint(Color(.mainText))
20 | }
21 |
22 |
23 | var body: some View {
24 |
25 | ScrollView {
26 | LazyVStack {
27 | ForEach(speakers) { speaker in
28 | NavigationLink(value: Destination.speaker(speaker)) {
29 | SpeakerRowView(speaker: speaker)
30 | .padding(.bottom, 5)
31 | }
32 | }
33 | }
34 | .padding([.top, .horizontal], 16)
35 | }
36 | .scrollIndicators(.hidden)
37 | .navigationTitle(AppStrings.speakers)
38 | .navigationBarTitleDisplayMode(.large)
39 | .navigationBarBackButtonHidden()
40 | .toolbar {
41 | ToolbarItem(placement: .topBarLeading, content: navigationBarLeadingItem)
42 | }
43 | }
44 | }
45 |
46 | struct SpeakersView_Previews: PreviewProvider {
47 | static var previews: some View {
48 | NavigationView {
49 | AllSpeakersView(speakers: DummyData.speakers)
50 | }
51 | .previewDevice(PreviewDevice(rawValue: "iPhone 14 pro"))
52 | .previewDisplayName("iPhone 14")
53 |
54 | AllSpeakersView(speakers: DummyData.speakers)
55 | .previewDevice(PreviewDevice(rawValue: "iPad mini (6th generation)"))
56 | .previewDisplayName("iPad mini")
57 |
58 | AllSpeakersView(speakers: DummyData.speakers)
59 | .previewDevice(PreviewDevice(rawValue: "iPad Pro (12.9-inch) (6th generation)"))
60 | .previewDisplayName("iPad pro 11")
61 |
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/SecondaryViews/SponsorsView/SponsorsScreen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SponsorsView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 27/10/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SponsorsScreen: View {
11 | @EnvironmentObject var viewModel: BaseViewModel
12 | @Environment(\.presentationMode) var presentationMode
13 |
14 | @ViewBuilder
15 | private func navigationBarLeadingItem() -> some View {
16 | Button { presentationMode.wrappedValue.dismiss() }
17 | label: { Image(.back) }
18 | .tint(Color(.mainText))
19 | }
20 |
21 | var body: some View {
22 | ScrollView {
23 | LazyVStack(alignment: .leading, spacing: 20) {
24 | ForEach(viewModel.sponsors) { sponsor in
25 | SponsorRow(sponsor: sponsor)
26 | }
27 | }
28 | .padding([.top, .horizontal], 16)
29 | }
30 | .navigationTitle(AppStrings.sponsors)
31 | .navigationBarTitleDisplayMode(.inline)
32 | .navigationBarBackButtonHidden()
33 | .toolbar {
34 | ToolbarItem(placement: .topBarLeading, content: navigationBarLeadingItem)
35 | }
36 |
37 | }
38 | }
39 |
40 | struct SponsorsView_Previews: PreviewProvider {
41 | static var previews: some View {
42 | SponsorsScreen()
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/ViewComponents/AnimatedButtonView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnimatedButtonView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 18/04/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AnimatedButtonView: View {
11 | @State var isAnimating = false
12 | @State var doneAnimation = false
13 | @State var submitScale: CGFloat = 1
14 |
15 | let animationDuration: TimeInterval = 0.20
16 |
17 | let title: String
18 | let color: Color
19 | let shouldAnimate: Bool
20 | let buttonAction: () -> Void
21 |
22 | init(title: String, color: Color, shouldAnimate: Bool = true, action: @escaping () -> Void) {
23 | self.title = title
24 | self.color = color
25 | self.shouldAnimate = shouldAnimate
26 | self.buttonAction = action
27 | }
28 |
29 |
30 | var body: some View {
31 | ZStack {
32 | Capsule()
33 | .fill(color)
34 | .frame(maxWidth: self.isAnimating ? (52) : .infinity)
35 | .scaleEffect(submitScale, anchor: .center)
36 |
37 | Tick(scaleFactor: 0.2)
38 | .trim(from: 0, to: self.doneAnimation ? 1 : 0)
39 | .stroke(style: StrokeStyle(lineWidth: 2, lineCap: .round))
40 | .foregroundColor(Color(.buttonTitle))
41 | .frame(width: 16)
42 | .offset(x: -4, y: 4)
43 | .animation(.easeOut(duration: 0.35), value: self.doneAnimation)
44 |
45 | Text(title)
46 | .semiboldAppFont(size: 18)
47 | .foregroundColor(Color(.buttonTitle))
48 | .opacity(self.isAnimating ? 0 : 1)
49 | .animation(.easeOut(duration: animationDuration), value: self.isAnimating)
50 | .scaleEffect(self.isAnimating ? 0.7 : 1)
51 | .animation(.easeOut(duration: animationDuration), value: self.isAnimating)
52 | .padding(10)
53 | }
54 | .frame(maxWidth: .infinity)
55 | .onTapGesture {
56 | if shouldAnimate && !self.isAnimating {
57 | toggleIsAnimating()
58 | resetSubmit()
59 | self.doneAnimation = true
60 | }
61 | buttonAction()
62 | }
63 | }
64 |
65 | func resetSubmit() {
66 | Timer.scheduledTimer(withTimeInterval: animationDuration * 4.5, repeats: false) { _ in
67 | self.doneAnimation.toggle()
68 | }
69 | Timer.scheduledTimer(withTimeInterval: animationDuration * 5.5, repeats: false) { _ in
70 | toggleIsAnimating()
71 | }
72 | }
73 |
74 | func toggleIsAnimating() {
75 | withAnimation(Animation.spring(response: animationDuration * 1.25, dampingFraction: 0.9, blendDuration: 1)) {
76 | self.isAnimating.toggle()
77 | }
78 | }
79 | }
80 |
81 |
82 |
83 |
84 | struct Tick: Shape {
85 | let scaleFactor: CGFloat
86 |
87 | func path(in rect: CGRect) -> Path {
88 | let cX = rect.midX + 4
89 | let cY = rect.midY - 3
90 |
91 | var path = Path()
92 | path.move(to: CGPoint(x: rect.midX, y: rect.minY))
93 | path.move(to: CGPoint(x: cX - (42 * scaleFactor), y: cY - (4 * scaleFactor)))
94 | path.addLine(to: CGPoint(x: cX - (scaleFactor * 18), y: cY + (scaleFactor * 28)))
95 | path.addLine(to: CGPoint(x: cX + (scaleFactor * 40), y: cY - (scaleFactor * 36)))
96 | return path
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/ViewComponents/AppSegmentControl.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppSegmentControl.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 20/03/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | import SwiftUI
11 |
12 | struct AppSegmentedControl: View where T.RawValue == String {
13 |
14 | @Binding var selection: Int
15 |
16 | var options: [T] {
17 | return T.allCases as! [T]
18 | }
19 |
20 |
21 | var body: some View {
22 | HStack(spacing: 2) {
23 | ForEach(options.indices, id:\.self) { index in
24 |
25 | Rectangle()
26 | .foregroundStyle(Color(.buttonBackground))
27 | .cornerRadius(20)
28 | .padding(2)
29 | .opacity(selection == index ? 1 : 0.1)
30 | .onTapGesture {
31 | withAnimation(.interactiveSpring()) {
32 | selection = index
33 | }
34 | }
35 | .overlay(
36 | Text(options[index].rawValue.capitalized)
37 | .semiboldAppFont(size: 18)
38 | .foregroundStyle(selection == index ? Color(.buttonTitle) : Color(.segmentTitle))
39 | )
40 | }
41 | }
42 | .frame(height: 40)
43 | .cornerRadius(20)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/ViewComponents/DayPickerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DayPickerView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 20/03/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | //struct DayPickerView: View {
11 | //
12 | // let days: [String]
13 | // @Binding var selection: String
14 | //
15 | // var body: some View {
16 | // ScrollView(.horizontal) {
17 | // HStack {
18 | // ForEach(days, id: \.self) { day in
19 | // VStack(spacing: 20) {
20 | // Text(day.removeChars)
21 | // .semiboldAppFont(size: 20)
22 | // Text(day.removeDigits.uppercased())
23 | // .appFont(size: 14)
24 | // }
25 | // .frame(width: 54, height: 56)
26 | // .foregroundStyle(selection == day ? Color(.buttonTitle) : Color(.mainText))
27 | // .roundBackgroundView(color: selection == day ? Color(.pickerSelected) : Color(.cardBackground))
28 | // .onTapGesture {
29 | // selection = day
30 | // }
31 | // }
32 | // }
33 | // .padding(.leading)
34 | // }
35 | // }
36 | //}
37 |
38 | //#Preview {
39 | // DayPickerView()
40 | //}
41 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/ViewComponents/EmptySessionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EmptySessionView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 05/03/2023.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct EmptyContentView: View {
11 | let image: Image
12 | let title: String
13 | let description: String?
14 | let buttonTitle: String?
15 | var action: (() -> ())?
16 |
17 | init(
18 | image: Image,
19 | title: String,
20 | description: String? = nil,
21 | buttonTitle: String? = nil,
22 | action: (() -> Void)? = nil
23 | ) {
24 | self.image = image
25 | self.title = title
26 | self.description = description
27 | self.buttonTitle = buttonTitle
28 | self.action = action
29 | }
30 |
31 | var body: some View {
32 |
33 | if #available(iOS 17.0, *) {
34 | ContentUnavailableView {
35 | VStack(spacing: 10) {
36 | image
37 | .resizable()
38 | .frame(width: 80, height: 80)
39 | .aspectRatio(contentMode: .fill)
40 | .foregroundStyle(Color(.linkButton))
41 |
42 | Text(title)
43 | .boldAppFont(size: 20)
44 | .foregroundStyle(Color(.mainText))
45 | }
46 | } description: {
47 | if let description {
48 | Text(description)
49 | .multilineTextAlignment(.center)
50 | .foregroundStyle(Color(.mainText))
51 | .appFont(size: 16)
52 | }
53 | } actions: {
54 | if let action = action, let buttonTitle {
55 | Button(action: action, label: {
56 | Text(buttonTitle)
57 | })
58 | .buttonStyle(.appPrimary)
59 | }
60 | }
61 | } else {
62 | VStack(spacing: 12) {
63 | Spacer()
64 |
65 | image
66 | .resizable()
67 | .frame(width: 80, height: 80)
68 | .foregroundStyle(Color(.linkButton))
69 |
70 | Text(title)
71 | .semiboldAppFont(size: 20)
72 | .foregroundStyle(Color(.mainText))
73 |
74 | if let description {
75 | Text(description)
76 | .multilineTextAlignment(.center)
77 | .foregroundStyle(Color(.mainText))
78 | .appFont(size: 16)
79 | }
80 |
81 | if let action = action, let buttonTitle {
82 | Spacer()
83 |
84 | Button(action: action, label: {
85 | Text(buttonTitle)
86 | })
87 | .buttonStyle(.appPrimary)
88 | .padding([.horizontal, .bottom], 16)
89 | }
90 | }
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/ViewComponents/HourlyWeatherView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HourlyWeatherView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 02/04/2023.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct HourlyWeatherView: View {
11 |
12 | private let weatherData: WeatherData
13 | private let isCelsius: Bool
14 |
15 | init(weatherData: WeatherData, isCelsius: Bool) {
16 | self.weatherData = weatherData
17 | self.isCelsius = isCelsius
18 | }
19 |
20 | var body: some View {
21 | VStack(spacing: 5) {
22 | Text(weatherData.tempDate.formatted(date: .omitted, time: .shortened))
23 | .appFont(size: 16)
24 |
25 | HStack(spacing: 5) {
26 | Image(systemName: weatherData.symbolName)
27 | .symbolRenderingMode(.palette)
28 | .foregroundStyle(Color.primary, Color(.purple200))
29 |
30 | Text(isCelsius ? weatherData.feelsLikeC.roundNearest().toCelsius : weatherData.feelsLikeF.roundNearest().toFahrenheit)
31 | }
32 | }
33 | .appFont(size: 14)
34 | .foregroundStyle(Color(.mainText))
35 | }
36 | }
37 |
38 | struct HourlyWeatherView_Previews: PreviewProvider {
39 | static var previews: some View {
40 | HourlyWeatherView(weatherData: DummyData.weatherData, isCelsius: true)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/ViewComponents/InfoRowView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LinkRowView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 15/03/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct InfoRowView: View {
11 | let text: String
12 | let imageName: String
13 |
14 | var body: some View {
15 |
16 | HStack(spacing: 10) {
17 | Image(imageName)
18 | .resizable()
19 | .frame(width: 30, height: 30)
20 | .foregroundStyle(Color(.icon))
21 | .roundBackgroundView(color: Color(.linkButton))
22 |
23 | Text(text)
24 | .boldAppFont(size: 16)
25 | .foregroundStyle(Color(.mainText))
26 |
27 | Spacer()
28 | }
29 | .roundBackgroundView(color: Color(.cardBackground))
30 |
31 | }
32 | }
33 |
34 | #Preview {
35 | InfoRowView(text: "", imageName: "")
36 | }
37 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/ViewComponents/LocationInfoCardView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocationInfoCardView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 06/04/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct LocationInfoCardView: View {
11 | let location: Location
12 | let action: () -> Void
13 |
14 | init(location: Location, action: @escaping () -> Void) {
15 | self.location = location
16 | self.action = action
17 | }
18 |
19 | var body: some View {
20 | VStack(alignment: .leading, spacing: 10) {
21 | Text(location.name)
22 | .semiboldAppFont(size: 18)
23 | .foregroundStyle(Color(.mainText))
24 |
25 | if let note = location.note {
26 | Text(note)
27 | .appFont(size: 15)
28 | .foregroundStyle(Color(.textGrey))
29 | }
30 |
31 | if let url = location.webLink?.webUrl {
32 | Link("Website", destination: url)
33 | .tint(Color(.purple300))
34 | .appFont(size: 14)
35 | }
36 |
37 | Button("Navigate", action: action)
38 | .buttonStyle(.appPrimary)
39 | .padding(.vertical, 5)
40 | }
41 | .roundBackgroundView(color: Color(.cardBackground))
42 | }
43 | }
44 |
45 | #Preview {
46 | LocationInfoCardView(location: DummyData.location) { }
47 | }
48 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/ViewComponents/MapPinShape.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MapPinShape.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 06/04/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct MapPinShape: Shape {
11 | func path(in rect: CGRect) -> Path {
12 | var path = Path()
13 |
14 | path.move(to: CGPoint(x: rect.midX, y: rect.maxY))
15 |
16 | path.addQuadCurve(to: CGPoint(x: rect.midX, y: rect.minY), control: CGPoint(x: rect.minX, y: rect.minY))
17 |
18 | path.addQuadCurve(to: CGPoint(x: rect.midX, y: rect.maxY), control: CGPoint(x: rect.maxX, y: rect.minY))
19 |
20 | return path
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/ViewComponents/MapPinView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MapPinView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 06/04/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct MapPinView: View {
11 | let imageName: String
12 | let isSelected: Bool
13 |
14 | var body: some View {
15 | ZStack(alignment: .top) {
16 | MapPinShape()
17 | .fill(isSelected ? Color(.purple300) : Color(.mapPin))
18 | .frame(width: 70, height: 50)
19 |
20 | Image(imageName.lowercased())
21 | .resizable()
22 | .frame(width: 20, height: 20)
23 | .foregroundStyle(isSelected ? .white : Color(.purple300))
24 | .padding(.top, 5)
25 | }
26 | .scaleEffect(isSelected ? 1.2 : 1.0)
27 | .padding(.horizontal, -20)
28 | }
29 | }
30 |
31 | #Preview {
32 | MapPinView(imageName: "ev", isSelected: false)
33 | }
34 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/ViewComponents/NavigationRowView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavigationRowView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 05/08/2023.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct NavigationRowView: View {
11 | let imageName: String
12 | let title: String
13 |
14 | var body: some View {
15 | HStack(spacing: 15) {
16 | Image(imageName)
17 | .tint(Color(.mainText))
18 |
19 | Text(title)
20 | .semiboldAppFont(size: 16)
21 | .foregroundStyle(Color(.mainText))
22 |
23 | Spacer()
24 | Image(systemName: ImageNames.chevronRight)
25 | .tint(Color(.mainText))
26 | }
27 | .padding(4)
28 | .roundBackgroundView(color: Color(.cardBackground))
29 | }
30 | }
31 |
32 | struct NavigationRowView_Previews: PreviewProvider {
33 | static var previews: some View {
34 | NavigationRowView(imageName: ImageNames.mapPin, title: AppStrings.sponsors)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/ViewComponents/RemoteImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RemoteImage.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 10/09/2022.
6 | //
7 |
8 | import Kingfisher
9 | import SwiftUI
10 |
11 | struct RemoteImageView: View {
12 | private let url: URL?
13 |
14 | init(url: URL?) {
15 | self.url = url
16 | }
17 |
18 | var body: some View {
19 | KFImage.url(url)
20 | .placeholder {
21 | Image(.placeholder)
22 | .resizable()
23 | }
24 | .resizable()
25 | .loadDiskFileSynchronously()
26 | // .cacheMemoryOnly()
27 | .fade(duration: 0.25)
28 | }
29 | }
30 |
31 |
32 | struct RemoteImageView_Previews: PreviewProvider {
33 | static var previews: some View {
34 | RemoteImageView(url: URL(string: "https://xsgames.co/randomusers/avatar.php?g=male") )
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/ViewComponents/SectionHeaderView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SectionHeaderView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 20/11/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SectionHeaderView: View {
11 | @Environment(\.font) private var font
12 |
13 | private let title: String
14 |
15 | init(title: String) {
16 | self.title = title
17 | }
18 |
19 | var body: some View {
20 | HStack {
21 | Text(title)
22 | .font(font)
23 | }
24 | .padding(.bottom, 8)
25 | .textCase(nil)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/ViewComponents/SessionRowView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NewSessionCard.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 16/03/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SessionRowView: View {
11 | @EnvironmentObject var baseViewModel: BaseViewModel
12 |
13 | let session: Session
14 | let showSpeakers: Bool
15 | var action: (() -> Void)?
16 |
17 | init(
18 | session: Session,
19 | showSpeakers: Bool = false,
20 | action: (() -> Void)? = nil
21 | ) {
22 | self.session = session
23 | self.showSpeakers = showSpeakers
24 | self.action = action
25 | }
26 |
27 | @ViewBuilder
28 | private func speakerRowView(speaker: Speaker) -> some View {
29 | HStack(spacing: 8) {
30 | RemoteImageView(url: speaker.imageUrl)
31 | .scaledToFit()
32 | .frame(width: 24, height: 24)
33 | .clipShape(Circle())
34 |
35 | Text(speaker.name)
36 | .foregroundStyle(Color(.mainText))
37 | .minimumScaleFactor(0.8)
38 | .lineLimit(2)
39 | .appFont(size: 14)
40 | }
41 | }
42 |
43 | var body: some View {
44 | VStack(alignment: .leading, spacing: 14) {
45 |
46 | HStack {
47 | Text("\(session.duration)")
48 | .appFont(size: 14)
49 | .foregroundStyle(Color(.mainText))
50 |
51 | Spacer()
52 |
53 | if let action = action {
54 | Button(action: action) {
55 | Image(systemName: baseViewModel.isFavorite(session.id) ? ImageNames.bookmarkFill : ImageNames.bookmark)
56 | }
57 | }
58 | }
59 |
60 | Text("\(session.title)")
61 | .boldAppFont(size: 18)
62 | .lineLimit(2)
63 | .multilineTextAlignment(.leading)
64 | .foregroundStyle(Color(.mainText))
65 |
66 | if let location = baseViewModel.getLocation(with: session.locationId) {
67 | Label("\(location.name)", image: ImageNames.location)
68 | .tint(Color(.purple300))
69 | .capsuleBackgroundView(height: 30)
70 | }
71 |
72 | if showSpeakers && !baseViewModel.getSpeakers(with: session.speakerIds).isEmpty {
73 | HStack(spacing: 10) {
74 | ForEach(baseViewModel.getSpeakers(with: session.speakerIds)) { speaker in
75 | speakerRowView(speaker: speaker)
76 | }
77 | }
78 | }
79 | }
80 | .roundBackgroundView(color: Color(.cardBackground))
81 | .padding(.bottom, 10)
82 | }
83 | }
84 |
85 | #Preview {
86 | SessionRowView(session: DummyData.sessions.first!)
87 | }
88 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/ViewComponents/SpeakerCardView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpeakerCellView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 10/09/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SpeakerCardView: View {
11 |
12 | let speaker: Speaker
13 |
14 | var body: some View {
15 | VStack(spacing: 8) {
16 | RemoteImageView(url: speaker.imageUrl)
17 | .scaledToFit()
18 | .frame(width: 90, height: 90)
19 | .clipShape(Circle())
20 |
21 | Text(speaker.name)
22 | .foregroundStyle(Color(.mainText))
23 | .minimumScaleFactor(0.6)
24 | .lineLimit(2, reservesSpace: true)
25 | .semiboldAppFont(size: 14)
26 | }
27 | }
28 | }
29 |
30 | struct SpeakerCellView_Previews: PreviewProvider {
31 | static var previews: some View {
32 | SpeakerCardView(speaker: DummyData.speakers[0])
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/ViewComponents/SpeakerRowView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpeakerRowView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 15/03/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SpeakerRowView: View {
11 | let speaker: Speaker
12 |
13 | var body: some View {
14 | HStack(spacing: 12) {
15 | RemoteImageView(url: speaker.imageUrl)
16 | .scaledToFit()
17 | .frame(width: 62, height: 62)
18 | .clipShape(Circle())
19 |
20 | VStack(alignment: .leading, spacing: 10) {
21 | Text(speaker.name)
22 | .foregroundStyle(Color(.mainText))
23 | .minimumScaleFactor(0.8)
24 | .lineLimit(2)
25 | .boldAppFont(size: 18)
26 |
27 | if let position = speaker.currentPosition, !position.isEmpty {
28 | Text(position)
29 | .foregroundStyle(Color(.textGrey))
30 | .minimumScaleFactor(0.8)
31 | .semiboldAppFont(size: 16)
32 | .multilineTextAlignment(.leading)
33 | }
34 | }
35 | Spacer()
36 | }
37 | .roundBackgroundView(color: Color(.cardBackground))
38 | }
39 | }
40 |
41 | #Preview {
42 | SpeakerRowView(speaker: DummyData.speakers.first!)
43 | }
44 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/ViewComponents/SponsorCard.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SponsorCard.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 07/08/2023.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SponsorCard: View {
11 | @Environment(\.colorScheme) var colorScheme
12 | let height: CGFloat = UIDevice.current.userInterfaceIdiom == .pad ? 160 : 100
13 |
14 | let sponsor: Sponsor
15 |
16 | var body: some View {
17 | VStack {
18 | RemoteImageView(url: colorScheme == .dark ? sponsor.imageUrlDark : sponsor.imageUrlLight)
19 | .aspectRatio(contentMode: .fit)
20 | .frame(height: height)
21 | .frame(maxWidth: .infinity)
22 | .padding(.horizontal, 10)
23 | }
24 | .roundBackgroundView(color: Color(.cardBackground))
25 | }
26 | }
27 |
28 | struct SponsorCard_Previews: PreviewProvider {
29 | static var previews: some View {
30 | SponsorCard(sponsor: DummyData.sponsors[0])
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/ViewComponents/SponsorRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SponsorRow.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 27/10/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SponsorRow: View {
11 | @Environment(\.colorScheme) var colorScheme
12 |
13 | let sponsor: Sponsor
14 |
15 | var body: some View {
16 | VStack(alignment: .leading, spacing: 10) {
17 | RemoteImageView(url: colorScheme == .dark ? sponsor.imageUrlDark : sponsor.imageUrlLight)
18 | .aspectRatio(contentMode: .fit)
19 | .frame(height: 150)
20 | .frame(maxWidth: .infinity)
21 | .padding(.vertical, 5)
22 | .padding(.horizontal, 10)
23 |
24 | Divider()
25 | .frame(height: 2)
26 | .overlay(Color(.linkButton))
27 | .padding(.vertical, 10)
28 |
29 | Text(sponsor.tagline)
30 | .multilineTextAlignment(.leading)
31 | .appFont(size: 18)
32 |
33 | if let url = sponsor.webUrl {
34 | Link(destination: url) {
35 | Text(sponsor.urlText)
36 | }
37 | .buttonStyle(.appPrimary)
38 | .padding(.vertical, 10)
39 | }
40 | }
41 | .roundBackgroundView(color: Color(.cardBackground))
42 | }
43 | }
44 |
45 | struct SponsorRow_Previews: PreviewProvider {
46 | static var previews: some View {
47 | SponsorRow(sponsor: DummyData.sponsors[0])
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/ViewComponents/TabBarView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabBarView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 10/09/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct TabBarView: View {
11 |
12 | var body: some View {
13 | TabView {
14 | ForEach(Tab.allCases) { tab in
15 | tab.view
16 | .tabItem {
17 | tab.icon
18 | .resizable()
19 | .aspectRatio(contentMode: .fit)
20 | }
21 | }
22 | }
23 | }
24 | }
25 |
26 | extension TabBarView {
27 | fileprivate enum Tab: CaseIterable, Identifiable {
28 | case home
29 | case mySchedule
30 | case attendee
31 | case info
32 |
33 | var id: Int {
34 | self.hashValue
35 | }
36 |
37 | var icon: Image {
38 | switch self {
39 | case .home:
40 | return Image(.home)
41 | case .mySchedule:
42 | return Image(.schedule)
43 | case .attendee:
44 | return Image(.attendee)
45 | case .info:
46 | return Image(.info)
47 | }
48 | }
49 |
50 | @ViewBuilder
51 | var view: some View {
52 | switch self {
53 | case .home:
54 | HomeScreen()
55 | case .mySchedule:
56 | MyScheduleView()
57 | case .attendee:
58 | AttendeeScreen()
59 | case .info:
60 | InfoView()
61 | }
62 | }
63 | }
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/iOSDevUK/Presentation/ViewComponents/UniversityMapView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UniversityMapView.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 01/04/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct UniversityMapView: View {
11 | let eventDate: String?
12 |
13 | init(eventDate: String? = nil) {
14 | self.eventDate = eventDate
15 | }
16 |
17 | var body: some View {
18 | ZStack {
19 | Image(ImageNames.mapImage)
20 | .resizable()
21 | .aspectRatio(contentMode: .fill)
22 | .frame(height: eventDate != nil ? 170 : 144)
23 | .clipShape(RoundedRectangle(cornerRadius: 16))
24 |
25 | VStack(alignment: .leading, spacing: 10) {
26 |
27 | if let eventDate {
28 | Text(eventDate)
29 | .appFont(size: 16)
30 | .foregroundStyle(Color(.buttonTitle))
31 | .capsuleBackgroundView(
32 | height: 44,
33 | color: Color(.buttonBackground)
34 | )
35 | }
36 |
37 | HStack(spacing: 10) {
38 | Image(ImageResource.mapPin)
39 | .resizable()
40 | .aspectRatio(contentMode: .fit)
41 | .frame(height: 28)
42 | .foregroundStyle(Color(.buttonBackground))
43 |
44 | Text("Aberystwyth University")
45 | .semiboldAppFont(size: 20)
46 | .foregroundStyle(Color(.mainText))
47 |
48 | Spacer()
49 | }
50 | .roundBackgroundView(color: Color(.buttonTitle))
51 | }
52 | .padding(.horizontal, 20)
53 | .padding(.top, 2)
54 | }
55 | }
56 | }
57 |
58 | #Preview {
59 | UniversityMapView(eventDate: nil)
60 | }
61 |
--------------------------------------------------------------------------------
/iOSDevUK/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/iOSDevUK/Resources/Fonts/SFPRODISPLAYBLACKITALIC.OTF:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Resources/Fonts/SFPRODISPLAYBLACKITALIC.OTF
--------------------------------------------------------------------------------
/iOSDevUK/Resources/Fonts/SFPRODISPLAYBOLD.OTF:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Resources/Fonts/SFPRODISPLAYBOLD.OTF
--------------------------------------------------------------------------------
/iOSDevUK/Resources/Fonts/SFPRODISPLAYHEAVYITALIC.OTF:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Resources/Fonts/SFPRODISPLAYHEAVYITALIC.OTF
--------------------------------------------------------------------------------
/iOSDevUK/Resources/Fonts/SFPRODISPLAYLIGHTITALIC.OTF:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Resources/Fonts/SFPRODISPLAYLIGHTITALIC.OTF
--------------------------------------------------------------------------------
/iOSDevUK/Resources/Fonts/SFPRODISPLAYMEDIUM.OTF:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Resources/Fonts/SFPRODISPLAYMEDIUM.OTF
--------------------------------------------------------------------------------
/iOSDevUK/Resources/Fonts/SFPRODISPLAYREGULAR.OTF:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Resources/Fonts/SFPRODISPLAYREGULAR.OTF
--------------------------------------------------------------------------------
/iOSDevUK/Resources/Fonts/SFPRODISPLAYSEMIBOLDITALIC.OTF:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Resources/Fonts/SFPRODISPLAYSEMIBOLDITALIC.OTF
--------------------------------------------------------------------------------
/iOSDevUK/Resources/Fonts/SFPRODISPLAYTHINITALIC.OTF:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Resources/Fonts/SFPRODISPLAYTHINITALIC.OTF
--------------------------------------------------------------------------------
/iOSDevUK/Resources/Fonts/SFPRODISPLAYULTRALIGHTITALIC.OTF:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Resources/Fonts/SFPRODISPLAYULTRALIGHTITALIC.OTF
--------------------------------------------------------------------------------
/iOSDevUK/Resources/Fonts/SFProDisplay-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Resources/Fonts/SFProDisplay-Light.ttf
--------------------------------------------------------------------------------
/iOSDevUK/Resources/Fonts/SFProDisplay-Semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DavidFaraday/iOSDevUK/4333fe30e3c587f5330089527fcaaeecfe55b471/iOSDevUK/Resources/Fonts/SFProDisplay-Semibold.ttf
--------------------------------------------------------------------------------
/iOSDevUK/Utils/LocalStorageService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocalStorageService.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 12/08/2023.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol LocalStorageServiceProtocol {
11 | func save(items: [String], for key: String)
12 | func loadArray(with key: String) -> [String]
13 | }
14 |
15 | final class LocalStorageService: LocalStorageServiceProtocol {
16 |
17 | static let shared = LocalStorageService()
18 |
19 | private init() {}
20 |
21 | func save(items: [String], for key: String) {
22 | UserDefaults.standard.set(items, forKey: key)
23 | }
24 |
25 | func loadArray(with key: String) -> [String] {
26 | UserDefaults.standard.stringArray(forKey: key) ?? []
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/iOSDevUK/Utils/LocationService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocationManager.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 25/03/2023.
6 | //
7 |
8 | import MapKit
9 |
10 | enum MapDetails {
11 | static let startingLatitude: CLLocationDegrees = 52.41483885670968
12 | static let startingLongitude: CLLocationDegrees = -4.076185527558135
13 | static let defaultLocationAberystwyth = CLLocation(latitude: startingLatitude, longitude: startingLongitude)
14 | static let aberystwythCoordinates = defaultLocationAberystwyth.coordinate
15 | static let defaultSpan = MKCoordinateSpan.init(latitudeDelta: 0.025, longitudeDelta: 0.025)
16 | }
17 |
18 | class LocationService: NSObject, CLLocationManagerDelegate, ObservableObject {
19 |
20 | @Published var region = MKCoordinateRegion(center: MapDetails.aberystwythCoordinates,
21 | span: MapDetails.defaultSpan)
22 |
23 | @Published var currentLocation = MapDetails.aberystwythCoordinates
24 |
25 | static let shared = LocationService()
26 |
27 | var locationManager: CLLocationManager?
28 |
29 |
30 | private override init() {
31 | super.init()
32 | requestLocationAccess()
33 | }
34 |
35 | func requestLocationAccess() {
36 |
37 | if locationManager == nil {
38 | locationManager = CLLocationManager()
39 | locationManager!.delegate = self
40 | locationManager!.desiredAccuracy = kCLLocationAccuracyBest
41 | locationManager!.requestWhenInUseAuthorization()
42 |
43 | locationManager!.activityType = .other
44 | locationManager!.pausesLocationUpdatesAutomatically = true
45 |
46 | locationManager!.startMonitoringSignificantLocationChanges()
47 | }
48 | }
49 |
50 | static func region(for coordinates: [CLLocationCoordinate2D]) -> MKCoordinateRegion {
51 |
52 | var rect = MKMapRect.null
53 |
54 | for coordinate in coordinates {
55 |
56 | let point = MKMapPoint(coordinate)
57 | rect = rect.union(MKMapRect(x: point.x, y: point.y, width: 0, height: 0))
58 | }
59 |
60 | var region = MKCoordinateRegion(rect)
61 | region.span.latitudeDelta *= 1.5
62 | region.span.longitudeDelta *= 1.5
63 |
64 | return region
65 | }
66 |
67 |
68 | //MARK: - Delegates
69 | func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
70 | print("DEBUG: Failed to get location")
71 | }
72 |
73 |
74 | func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
75 | guard let manager = locationManager else { return }
76 |
77 | if manager.authorizationStatus == .notDetermined {
78 | manager.requestWhenInUseAuthorization()
79 | }
80 | }
81 |
82 | func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
83 | print("DEBUG: Updated location")
84 | currentLocation = locations.last!.coordinate
85 | region = MKCoordinateRegion(center: currentLocation, span: MapDetails.defaultSpan)
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/iOSDevUK/Utils/MappingUtils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MappingUtils.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 02/04/2023.
6 | //
7 |
8 | import Foundation
9 | import WeatherKit
10 |
11 | protocol MappingUtilsProtocol {
12 | func convert(input: HourWeather) -> WeatherData
13 | func convert(input: CurrentWeather?) -> WeatherData?
14 | }
15 |
16 |
17 | class MappingUtils: MappingUtilsProtocol {
18 |
19 | static let shared = MappingUtils()
20 |
21 | private init() {}
22 |
23 | func convert(input: HourWeather) -> WeatherData {
24 |
25 | WeatherData(
26 | tempDate: input.date,
27 | condition: input.condition.description,
28 | symbolName: input.symbolName,
29 | currentTempC: input.temperature.value,
30 | feelsLikeC: input.apparentTemperature.value
31 | )
32 | }
33 | func convert(input: CurrentWeather?) -> WeatherData? {
34 | guard let input = input else { return nil }
35 |
36 | return WeatherData(
37 | tempDate: input.date,
38 | condition: input.condition.description,
39 | symbolName: input.symbolName,
40 | currentTempC: input.temperature.value,
41 | feelsLikeC: input.apparentTemperature.value
42 | )
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/iOSDevUK/Utils/Reachability.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Reachability.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 06/02/2023.
6 | //
7 |
8 | import Foundation
9 | import SystemConfiguration
10 |
11 |
12 | public class Reachability {
13 |
14 | class func HasConnection() -> Bool {
15 |
16 | var zeroAddress = sockaddr_in(sin_len: 0, sin_family: 0, sin_port: 0, sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
17 |
18 | zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))
19 | zeroAddress.sin_family = sa_family_t(AF_INET)
20 |
21 | let defaultRoutReachability = withUnsafePointer(to: &zeroAddress, {
22 | $0.withMemoryRebound(to: sockaddr.self, capacity: 1, {
23 | zeroSockAddress in
24 |
25 | SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)
26 | })
27 | })
28 |
29 | var flags : SCNetworkReachabilityFlags = SCNetworkReachabilityFlags(rawValue: 0)
30 |
31 | if SCNetworkReachabilityGetFlags(defaultRoutReachability!, &flags) == false {
32 | return false
33 | }
34 |
35 | let isReachable = (flags.rawValue & UInt32(kSCNetworkFlagsReachable)) != 0
36 |
37 | let needConnection = (flags.rawValue & UInt32(kSCNetworkFlagsConnectionRequired)) != 0
38 |
39 | return (isReachable && !needConnection)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/iOSDevUK/iOSDevUK.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.developer.weatherkit
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/iOSDevUK/iOSDevUKApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // iOSDevUKApp.swift
3 | // iOSDevUK
4 | //
5 | // Created by David Kababyan on 10/09/2022.
6 | //
7 |
8 | import SwiftUI
9 | import Firebase
10 |
11 | @main
12 | struct iOSDevUKApp: App {
13 |
14 | init() {
15 | FirebaseApp.configure()
16 | }
17 |
18 | @State private var router = NavigationRouter()
19 | @State private var baseViewModel = BaseViewModel()
20 |
21 | var body: some Scene {
22 | WindowGroup {
23 | TabBarView()
24 | .environmentObject(router)
25 | .environmentObject(baseViewModel)
26 | .environmentObject(LocationService.shared)
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/iOSDevUKTests/AllSessionsViewModelTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AllSessionsViewModelTest.swift
3 | // iOSDevUKTests
4 | //
5 | // Created by David Kababyan on 08/12/2022.
6 | //
7 |
8 | import XCTest
9 | import Combine
10 |
11 | @testable import iOSDevUK
12 |
13 | final class AllSessionsViewModelTest: XCTestCase {
14 |
15 | private var cancellables: Set = []
16 |
17 | func test_InitSetsSessions_ToTwo() {
18 | let sut = AllSessionsViewModel(sessions: DummyData.sessions)
19 |
20 | sut.$sessions
21 | .sink { newValue in
22 | XCTAssertEqual(newValue.count, 2)
23 | }
24 | .store(in: &cancellables)
25 | }
26 |
27 |
28 | @MainActor
29 | func test_SelectedDateGetsValue() {
30 | let sut = AllSessionsViewModel(sessions: DummyData.sessions)
31 |
32 | sut.$selectedDate
33 | .dropFirst()
34 | .sink { newValue in
35 | XCTAssertNotEqual(newValue, "")
36 | }
37 | .store(in: &cancellables)
38 |
39 | sut.setCurrentDate()
40 | }
41 |
42 | @MainActor
43 | func test_SelectedDateSetToToday() {
44 | let sut = AllSessionsViewModel(sessions: [DummyData.sessions[1]])
45 |
46 | sut.$selectedDate
47 | .dropFirst()
48 | .sink { newValue in
49 | XCTAssertEqual(newValue, Date().dateAndWeekDay)
50 | }
51 | .store(in: &cancellables)
52 |
53 | sut.setCurrentDate()
54 | }
55 |
56 | @MainActor
57 | func test_SelectedDateSetTo10Thu() {
58 | let sut = AllSessionsViewModel(sessions: [DummyData.sessions[0]])
59 |
60 | sut.$selectedDate
61 | .dropFirst()
62 | .sink { newValue in
63 | XCTAssertEqual(newValue, "10 Thu")
64 | }
65 | .store(in: &cancellables)
66 |
67 | sut.setCurrentDate()
68 | }
69 |
70 | @MainActor
71 | func test_SetCurrentDateWithEmptySessionReturns() {
72 | let sut = AllSessionsViewModel(sessions: [])
73 |
74 | sut.$selectedDate
75 | .sink { newValue in
76 | XCTAssertEqual(newValue, "")
77 | }
78 | .store(in: &cancellables)
79 |
80 | sut.setCurrentDate()
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/iOSDevUKTests/FactorySetup.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FactorySetup.swift
3 | // iOSDevUKTests
4 | //
5 | // Created by David Kababyan on 08/12/2022.
6 | //
7 |
8 | import Foundation
9 | import Factory
10 | @testable import iOSDevUK
11 |
12 | extension Container {
13 | static func setupMocks(objectsToReturn: [Codable], shouldReturnError: Bool = false) {
14 | Container.shared.firebaseRepository.register { MockFirebaseRepository(objectsToReturn: objectsToReturn, shouldReturnError: shouldReturnError) }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/iOSDevUKTests/SessionRowViewModelTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SessionRowViewModelTest.swift
3 | // iOSDevUKTests
4 | //
5 | // Created by David Kababyan on 10/12/2022.
6 | //
7 |
8 | import XCTest
9 | import Combine
10 | import Factory
11 |
12 | @testable import iOSDevUK
13 |
14 | final class SessionRowViewModelTest: XCTestCase {
15 |
16 | private var cancellables: Set = []
17 |
18 | override func tearDownWithError() throws {
19 | cancellables = []
20 | }
21 |
22 | func test_InitialPropertyValuesAreNil() {
23 | Container.setupMocks(objectsToReturn: [DummyData.location])
24 |
25 | let sut = SessionRowViewModel()
26 |
27 | XCTAssertNil(sut.location)
28 | XCTAssertNil(sut.fetchError)
29 | }
30 |
31 |
32 | func test_fetchLocationWithoutId() {
33 | Container.setupMocks(objectsToReturn: [DummyData.location])
34 |
35 | let sut = SessionRowViewModel()
36 |
37 | Task {
38 | await sut.fetchLocation(with: nil)
39 | }
40 |
41 | XCTAssertNil(sut.location)
42 | }
43 |
44 |
45 | func test_fetchLocationReturnsLocation() async {
46 | Container.setupMocks(objectsToReturn: [DummyData.location])
47 |
48 | let sut = SessionRowViewModel()
49 | await sut.fetchLocation(with: "TestLocation123")
50 |
51 |
52 | sut.$location
53 | .dropFirst()
54 | .sink { newValue in
55 | XCTAssertNotNil(newValue)
56 | XCTAssertEqual(newValue?.id, "TestLocation123")
57 | }
58 | .store(in: &cancellables)
59 | }
60 |
61 |
62 | func test_fetchLocationReturnsError() async {
63 | Container.setupMocks(objectsToReturn: [DummyData.location], shouldReturnError: true)
64 |
65 | let sut = SessionRowViewModel()
66 | await sut.fetchLocation(with: "TestLocation123")
67 |
68 | sut.$fetchError
69 | .dropFirst()
70 | .sink { newValue in
71 | XCTAssertNotNil(newValue)
72 | XCTAssertEqual(newValue as? AppError, AppError.badSnapshot)
73 | }
74 | .store(in: &cancellables)
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/iOSDevUKTests/SpeakerDetailViewModelTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpeakerDetailViewModelTests.swift
3 | // iOSDevUKTests
4 | //
5 | // Created by David Kababyan on 08/12/2022.
6 | //
7 |
8 | import XCTest
9 | import Factory
10 | import Combine
11 |
12 | @testable import iOSDevUK
13 |
14 | final class SpeakerDetailViewModelTests: XCTestCase {
15 |
16 | private var cancellables: Set = []
17 |
18 | override func setUpWithError() throws {
19 | Container.setupMocks(objectsToReturn: DummyData.sessions)
20 | }
21 |
22 | override func tearDownWithError() throws {
23 | cancellables = []
24 | }
25 |
26 | func test_FetchSpeakerSessions_ReturnTwo() {
27 | let sut = SpeakerDetailViewModel(speaker: DummyData.speakers[0])
28 |
29 | let expectation = expectation(description: "waiting for network call")
30 |
31 | sut.$sessions
32 | .dropFirst()
33 | .sink { newValue in
34 | XCTAssertEqual(newValue.count, 2)
35 | expectation.fulfill()
36 | }
37 | .store(in: &cancellables)
38 |
39 | Task {
40 | await sut.getSpeakerSessions()
41 | }
42 |
43 | waitForExpectations(timeout: 0.1)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------