Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Summit Workshop - Part 2 #19

Draft
wants to merge 6 commits into
base: 2024-summit-workshop/part-1
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions final/RocketReserver.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
66F96FE629FC070600713B80 /* NetworkInterceptorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F96FE529FC070600713B80 /* NetworkInterceptorProvider.swift */; };
66F96FE829FC0D7700713B80 /* View+Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F96FE729FC0D7700713B80 /* View+Alert.swift */; };
66F96FEA29FC183200713B80 /* NotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F96FE929FC183200713B80 /* NotificationView.swift */; };
E6072AF32C7FEEEB00ED4E49 /* TripsBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6072AF22C7FEEEB00ED4E49 /* TripsBadgeView.swift */; };
E61F95BD2C8264C300A55230 /* TripsBadgeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E61F95BC2C8264C300A55230 /* TripsBadgeViewModel.swift */; };
E67B2DF02C7D02BC0095602B /* UserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E67B2DEF2C7D02BC0095602B /* UserView.swift */; };
E67B2DF22C7D03180095602B /* UserViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E67B2DF12C7D03180095602B /* UserViewModel.swift */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -56,6 +58,8 @@
66F96FE729FC0D7700713B80 /* View+Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Alert.swift"; sourceTree = "<group>"; };
66F96FE929FC183200713B80 /* NotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationView.swift; sourceTree = "<group>"; };
66F96FEB29FC2BF400713B80 /* RocketReserverAPI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = RocketReserverAPI; sourceTree = "<group>"; };
E6072AF22C7FEEEB00ED4E49 /* TripsBadgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripsBadgeView.swift; sourceTree = "<group>"; };
E61F95BC2C8264C300A55230 /* TripsBadgeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripsBadgeViewModel.swift; sourceTree = "<group>"; };
E67B2DEF2C7D02BC0095602B /* UserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserView.swift; sourceTree = "<group>"; };
E67B2DF12C7D03180095602B /* UserViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserViewModel.swift; sourceTree = "<group>"; };
E67B2DF32C7D27620095602B /* Me.graphql */ = {isa = PBXFileReference; lastKnownFileType = text; path = Me.graphql; sourceTree = "<group>"; };
Expand Down Expand Up @@ -131,6 +135,8 @@
66F96FA029FAC0D700713B80 /* Preview Content */,
E67B2DEF2C7D02BC0095602B /* UserView.swift */,
E67B2DF12C7D03180095602B /* UserViewModel.swift */,
E6072AF22C7FEEEB00ED4E49 /* TripsBadgeView.swift */,
E61F95BC2C8264C300A55230 /* TripsBadgeViewModel.swift */,
);
path = RocketReserver;
sourceTree = "<group>";
Expand Down Expand Up @@ -253,8 +259,10 @@
66F96F9B29FAC0D600713B80 /* RocketReserverApp.swift in Sources */,
66F96FE229FB0A9600713B80 /* LoginViewModel.swift in Sources */,
66F96FE429FC069D00713B80 /* AuthorizationInterceptor.swift in Sources */,
E61F95BD2C8264C300A55230 /* TripsBadgeViewModel.swift in Sources */,
E67B2DF22C7D03180095602B /* UserViewModel.swift in Sources */,
E67B2DF02C7D02BC0095602B /* UserView.swift in Sources */,
E6072AF32C7FEEEB00ED4E49 /* TripsBadgeView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
9 changes: 9 additions & 0 deletions final/RocketReserver/DetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ struct DetailView: View {
.padding(10)
.navigationTitle(viewModel.launch?.mission?.name ?? "")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
Button(action: { }, label: {
Image(systemName: "person.circle")
.overlay(
TripBadgeView()
)
})
.disabled(true)
}
.task {
viewModel.loadLaunchDetails()
}
Expand Down
23 changes: 23 additions & 0 deletions final/RocketReserver/DetailViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,21 @@ class DetailViewModel: ObservableObject {
if bookingResult.success {
self.appAlert = .basic(title: "Success!",
message: bookingResult.message ?? "Trip booked successfully")

if let bookedTrip = bookingResult.launches?.first ?? nil {
Network.shared.apollo.store.withinReadWriteTransaction { transaction in
let cacheMutation = MeTripsLocalCacheMutation()

try transaction.update(cacheMutation) { data in
data.me?.trips.append(.init(
isBooked: bookedTrip.isBooked,
id: bookedTrip.id,
site: bookedTrip.site,
mission: bookedTrip.mission
))
}
}
}
} else {
self.appAlert = .basic(title: "Could not book trip",
message: bookingResult.message ?? "Unknown failure")
Expand Down Expand Up @@ -95,6 +110,14 @@ class DetailViewModel: ObservableObject {
if cancelResult.success {
self.appAlert = .basic(title: "Trip cancelled",
message: cancelResult.message ?? "Your trip has been officially cancelled")

Network.shared.apollo.store.withinReadWriteTransaction { transaction in
let cacheMutation = MeTripsLocalCacheMutation()

try transaction.update(cacheMutation) { data in
data.me?.trips.removeAll(where: { $0?.id == id })
}
}
} else {
self.appAlert = .basic(title: "Could not cancel trip",
message: cancelResult.message ?? "Unknown failure.")
Expand Down
3 changes: 3 additions & 0 deletions final/RocketReserver/LaunchListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ struct LaunchListView: View {
self.isShowingUser.toggle()
}, label: {
Image(systemName: "person.circle")
.overlay(
TripBadgeView()
)
})
.sheet(isPresented: $isShowingUser) {
UserView()
Expand Down
39 changes: 39 additions & 0 deletions final/RocketReserver/TripsBadgeView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import SwiftUI

struct TripBadgeView : View {

private let size = 16.0
private let x = 20.0
private let y = 0.0

@StateObject private var viewModel = TripsBadgeViewModel()

var body: some View {
ZStack {
Capsule()
.fill(.red)
.frame(width: size * widthMultplier(), height: size, alignment: .topTrailing)
.position(x: x, y: y)

Text("\(viewModel.count)")
.foregroundColor(.white)
.font(Font.caption)
.position(x: x, y: y)
}
.task {
viewModel.loadUserTrips()
}
}

func widthMultplier() -> Double {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like there should be a way to just use the label's size here, but I don't know enough SwiftUI. Feels like there should be an equivalent to UILabel.sizeToFit().

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if you set the background of the Text as the capsule, that should automatically adjust the size.

Also guess who's still subscribed to this repo 🙃

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hahaha Hi Ellen! Thanks for the tip!

let value = viewModel.count

if value < 10 {
return 1.0
} else if value < 100 {
return 1.5
} else {
return 2.0
}
}
}
47 changes: 47 additions & 0 deletions final/RocketReserver/TripsBadgeViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import Apollo
import KeychainSwift
import RocketReserverAPI
import SwiftUI

class TripsBadgeViewModel: ObservableObject {

private var watcher: GraphQLQueryWatcher<MeQuery>?

@Published var count: Int = 0
@Published var appAlert: AppAlert?

func loadUserTrips() {
guard isLoggedIn(), watcher == nil else {
return
}

watcher = Network.shared.apollo.watch(
query: MeQuery(), cachePolicy: .returnCacheDataAndFetch
) { [weak self] result in
guard let self = self else {
return
}

switch result {
case.success(let graphQLResult):
count = graphQLResult.data?.me?.trips.count ?? 0

if let errors = graphQLResult.errors {
self.appAlert = .errors(errors: errors)
}
case .failure(let error):
self.appAlert = .errors(errors: [error])
}
}
}

deinit {
watcher?.cancel()
}

private func isLoggedIn() -> Bool {
let keychain = KeychainSwift()
return keychain.get(LoginView.loginKeychainKey) != nil
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// @generated
// This file was automatically generated and should not be edited.

@_exported import ApolloAPI

public class MeTripsLocalCacheMutation: LocalCacheMutation {
public static let operationType: GraphQLOperationType = .query

public init() {}

public struct Data: RocketReserverAPI.MutableSelectionSet {
public var __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }

public static var __parentType: any ApolloAPI.ParentType { RocketReserverAPI.Objects.Query }
public static var __selections: [ApolloAPI.Selection] { [
.field("me", Me?.self),
] }

public var me: Me? {
get { __data["me"] }
set { __data["me"] = newValue }
}

public init(
me: Me? = nil
) {
self.init(_dataDict: DataDict(
data: [
"__typename": RocketReserverAPI.Objects.Query.typename,
"me": me._fieldData,
],
fulfilledFragments: [
ObjectIdentifier(MeTripsLocalCacheMutation.Data.self)
]
))
}

/// Me
///
/// Parent Type: `User`
public struct Me: RocketReserverAPI.MutableSelectionSet {
public var __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }

public static var __parentType: any ApolloAPI.ParentType { RocketReserverAPI.Objects.User }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("trips", [Trip?].self),
] }

public var trips: [Trip?] {
get { __data["trips"] }
set { __data["trips"] = newValue }
}

public init(
trips: [Trip?]
) {
self.init(_dataDict: DataDict(
data: [
"__typename": RocketReserverAPI.Objects.User.typename,
"trips": trips._fieldData,
],
fulfilledFragments: [
ObjectIdentifier(MeTripsLocalCacheMutation.Data.Me.self)
]
))
}

/// Me.Trip
///
/// Parent Type: `Launch`
public struct Trip: RocketReserverAPI.MutableSelectionSet {
public var __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }

public static var __parentType: any ApolloAPI.ParentType { RocketReserverAPI.Objects.Launch }
public static var __selections: [ApolloAPI.Selection] { [
.field("__typename", String.self),
.field("isBooked", Bool.self),
.fragment(LaunchListDetail.self),
] }

public var isBooked: Bool {
get { __data["isBooked"] }
set { __data["isBooked"] = newValue }
}
public var id: RocketReserverAPI.ID {
get { __data["id"] }
set { __data["id"] = newValue }
}
public var site: String? {
get { __data["site"] }
set { __data["site"] = newValue }
}
public var mission: Mission? {
get { __data["mission"] }
set { __data["mission"] = newValue }
}

public struct Fragments: FragmentContainer {
public var __data: DataDict
public init(_dataDict: DataDict) { __data = _dataDict }

public var launchListDetail: LaunchListDetail {
get { _toFragment() }
_modify { var f = launchListDetail; yield &f; __data = f.__data }
}
}

public init(
isBooked: Bool,
id: RocketReserverAPI.ID,
site: String? = nil,
mission: Mission? = nil
) {
self.init(_dataDict: DataDict(
data: [
"__typename": RocketReserverAPI.Objects.Launch.typename,
"isBooked": isBooked,
"id": id,
"site": site,
"mission": mission._fieldData,
],
fulfilledFragments: [
ObjectIdentifier(MeTripsLocalCacheMutation.Data.Me.Trip.self),
ObjectIdentifier(LaunchListDetail.self)
]
))
}

public typealias Mission = LaunchListDetail.Mission
}
}
}
}
9 changes: 9 additions & 0 deletions final/graphql/Me.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,12 @@ query Me {
}
}
}

query MeTrips @apollo_client_ios_localCacheMutation {
me {
trips {
... LaunchListDetail
isBooked
}
}
}