Skip to content

Commit f10a474

Browse files
authored
Activity creation flow & invites Part 1 (#222)
2 parents d2bfbff + 0b30357 commit f10a474

35 files changed

+1949
-1038
lines changed

.cursor/rules.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,46 @@
110110
"name": "APIService Usage",
111111
"pattern": "\\.(swift)$",
112112
"rule": "Always use the APIService methods for network calls. Never implement direct URL session calls in ViewModels"
113+
},
114+
{
115+
"name": "Singleton Pattern Usage",
116+
"pattern": "\\.(swift)$",
117+
"rule": "ALWAYS use AppCache.shared directly instead of creating @ObservedObject or @StateObject properties. Never use '@ObservedObject private var appCache = AppCache.shared' or similar patterns. Access AppCache.shared directly in code. For UserAuthViewModel, use UserAuthViewModel.shared directly as well."
118+
},
119+
{
120+
"name": "App Color Usage - Universal Colors",
121+
"pattern": "\\.(swift)$",
122+
"rule": "ALWAYS use app-defined colors from Constants.swift instead of SwiftUI's default colors. Use universalBackgroundColor instead of .white, universalAccentColor instead of .black or .primary, universalSecondaryColor instead of .accentColor. Never use Color.primary, Color.secondary, .white, .black directly."
123+
},
124+
{
125+
"name": "App Color Usage - Figma Colors",
126+
"pattern": "\\.(swift)$",
127+
"rule": "For UI elements that match the Figma design, ALWAYS use the figma-prefixed colors (figmaBlue, figmaSoftBlue, figmaBlack300, figmaBlack400, figmaGreen, figmaBittersweetOrange) instead of creating custom colors or using SwiftUI defaults."
128+
},
129+
{
130+
"name": "App Color Usage - Backgrounds",
131+
"pattern": "\\.(swift)$",
132+
"rule": "For backgrounds, use universalBackgroundColor for main backgrounds, authPageBackgroundColor for auth screens. Never use Color.white, .background, or system background colors directly."
133+
},
134+
{
135+
"name": "App Color Usage - Text Colors",
136+
"pattern": "\\.(swift)$",
137+
"rule": "For text colors, use universalAccentColor for primary text, figmaBlack300 for secondary text, universalPlaceHolderTextColor for placeholder text. Avoid .primary, .secondary, or .foregroundColor without specifying the exact color."
138+
},
139+
{
140+
"name": "App Color Usage - Interactive Elements",
141+
"pattern": "\\.(swift)$",
142+
"rule": "For buttons and interactive elements, use figmaBlue for primary actions, figmaSoftBlue for secondary actions, universalPassiveColor for disabled states. Never use system accent colors or .tint without explicit color definition."
143+
},
144+
{
145+
"name": "App Color Usage - Activity Colors",
146+
"pattern": "\\.(swift)$",
147+
"rule": "For activity-related UI, use the predefined activityColors array or activityColorHexCodes. Each activity should have consistent color usage across all screens."
148+
},
149+
{
150+
"name": "Color Constants Import",
151+
"pattern": "\\.(swift)$",
152+
"rule": "All color constants are defined in Views/Helpers/Constants.swift. Import this file or ensure colors are accessible when creating new views. Never hardcode hex values directly in views."
113153
}
114154
]
115155
}

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,35 @@ More complicated case, to supply initial state and `init()` parameters:
6161

6262
</details>
6363

64+
<details>
65+
66+
<summary> Configuring Share URLs </summary>
67+
68+
</br>
69+
70+
The app uses share URLs to allow users to share activities with others. By default, these URLs point to a GitHub Pages URL, but you can customize them:
71+
72+
1. Open `Spawn-App-iOS-SwiftUI/Services/Constants.swift`
73+
2. Update the `shareBase` URL in the `URLs` struct:
74+
75+
```swift
76+
struct URLs {
77+
// Base URL for sharing activities
78+
// Option 1: Use GitHub Pages (recommended for web app)
79+
static let shareBase = "https://daggerpov.github.io/spawn-app"
80+
81+
// Option 2: Use GitHub repository directly
82+
// static let shareBase = "https://github.com/daggerpov/spawn-app"
83+
84+
// Option 3: Use a custom domain
85+
// static let shareBase = "https://your-domain.com"
86+
}
87+
```
88+
89+
The app will automatically generate share URLs in the format: `{shareBase}/activity/{activityId}`
90+
91+
</details>
92+
6493
</br>
6594

6695
# Code Explanations
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "stars-01.png",
5+
"idiom" : "universal",
6+
"scale" : "1x"
7+
},
8+
{
9+
"filename" : "stars-01 1.png",
10+
"idiom" : "universal",
11+
"scale" : "2x"
12+
},
13+
{
14+
"filename" : "stars-01 2.png",
15+
"idiom" : "universal",
16+
"scale" : "3x"
17+
}
18+
],
19+
"info" : {
20+
"author" : "xcode",
21+
"version" : 1
22+
}
23+
}
Loading
Loading
Loading
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "IMessage_logo logo (1).png",
5+
"idiom" : "universal",
6+
"scale" : "1x"
7+
},
8+
{
9+
"filename" : "IMessage_logo logo (1) 1.png",
10+
"idiom" : "universal",
11+
"scale" : "2x"
12+
},
13+
{
14+
"filename" : "IMessage_logo logo (1) 2.png",
15+
"idiom" : "universal",
16+
"scale" : "3x"
17+
}
18+
],
19+
"info" : {
20+
"author" : "xcode",
21+
"version" : 1
22+
},
23+
"properties" : {
24+
"template-rendering-intent" : "template"
25+
}
26+
}
Loading
Loading
Loading

Spawn-App-iOS-SwiftUI/ContentView.swift

Lines changed: 98 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -8,132 +8,115 @@
88
import SwiftUI
99

1010
struct ContentView: View {
11-
var user: BaseUserDTO
12-
@State private var showActivityCreationDrawer: Bool = false
13-
@State private var selectedTab: Int = 0
11+
var user: BaseUserDTO
12+
@State private var selectedTab: Int = 0
1413

15-
var body: some View {
16-
ZStack {
17-
TabView(selection: $selectedTab) {
18-
FeedView(user: user)
19-
.tag(0)
20-
.tabItem {
21-
Image(
22-
uiImage: resizeImage(
23-
UIImage(systemName: "house")!,
24-
targetSize: CGSize(width: 30, height: 27)
25-
)!
26-
)
27-
}
28-
MapView(user: user)
29-
.tag(1)
30-
.tabItem {
31-
Image(
32-
uiImage: resizeImage(
33-
UIImage(systemName: "location.circle")!,
34-
targetSize: CGSize(width: 30, height: 27)
35-
)!
36-
)
37-
}
38-
FeedView(user: user)
39-
.tag(2)
40-
.tabItem {
41-
Image(
42-
uiImage: resizeImage(
43-
UIImage(systemName: "plus.app")!,
44-
targetSize: CGSize(width: 30, height: 27)
45-
)!
46-
)
47-
}
48-
FriendsView(user: user)
49-
.tag(3)
50-
.tabItem {
51-
Image(
52-
uiImage: resizeImage(
53-
UIImage(systemName: "list.bullet")!,
54-
targetSize: CGSize(width: 30, height: 27)
55-
)!
56-
)
57-
}
58-
ProfileView(user: user)
59-
.tag(4)
60-
.tabItem {
61-
Image(
62-
uiImage: resizeImage(
63-
UIImage(systemName: "person.circle")!,
64-
targetSize: CGSize(width: 30, height: 27)
65-
)!
66-
)
67-
}
68-
}
69-
.onChange(of: selectedTab) { newValue in
70-
if newValue == 2 {
71-
// Reset tab selection to previous tab and show the drawer
72-
DispatchQueue.main.async {
73-
// Store the current tab value before changing it
74-
let previousTab = selectedTab
75-
// Return to previous tab (but avoid returning to the create tab itself)
76-
selectedTab = previousTab == 2 ? 0 : previousTab
77-
showActivityCreationDrawer = true
78-
}
79-
}
80-
}
81-
.onAppear {
82-
// TODO DANIEL A: when implementing dark/light theme, look at Quote Droplet's
83-
// code for how to do that here
84-
UITabBar
85-
.appearance().backgroundColor = UIColor.white
86-
.withAlphaComponent(0.9)
87-
UITabBar.appearance().unselectedItemTintColor = UIColor.black
88-
}
89-
}
90-
.sheet(isPresented: $showActivityCreationDrawer) {
91-
ActivityCreationView(
92-
creatingUser: user,
93-
closeCallback: {
94-
showActivityCreationDrawer = false
95-
}
96-
)
97-
.presentationDragIndicator(.visible)
98-
}
99-
}
14+
var body: some View {
15+
TabView(selection: $selectedTab) {
16+
FeedView(user: user)
17+
.tag(0)
18+
.tabItem {
19+
Image(
20+
uiImage: resizeImage(
21+
UIImage(systemName: "house")!,
22+
targetSize: CGSize(width: 30, height: 27)
23+
)!
24+
)
25+
}
26+
MapView(user: user)
27+
.tag(1)
28+
.tabItem {
29+
Image(
30+
uiImage: resizeImage(
31+
UIImage(systemName: "location.circle")!,
32+
targetSize: CGSize(width: 30, height: 27)
33+
)!
34+
)
35+
}
36+
ActivityCreationView(
37+
creatingUser: user,
38+
closeCallback: {
39+
// Navigate back to home tab when closing
40+
selectedTab = 0
41+
},
42+
selectedTab: $selectedTab
43+
)
44+
.tag(2)
45+
.tabItem {
46+
Image(
47+
uiImage: resizeImage(
48+
UIImage(named: "activities_icon")!,
49+
targetSize: CGSize(width: 30, height: 27)
50+
)!
51+
)
52+
}
53+
FriendsView(user: user)
54+
.tag(3)
55+
.tabItem {
56+
Image(
57+
uiImage: resizeImage(
58+
UIImage(systemName: "list.bullet")!,
59+
targetSize: CGSize(width: 30, height: 27)
60+
)!
61+
)
62+
}
63+
ProfileView(user: user)
64+
.tag(4)
65+
.tabItem {
66+
Image(
67+
uiImage: resizeImage(
68+
UIImage(systemName: "person.circle")!,
69+
targetSize: CGSize(width: 30, height: 27)
70+
)!
71+
)
72+
}
73+
}
74+
.onAppear {
75+
// TODO DANIEL A: when implementing dark/light theme, look at Quote Droplet's
76+
// code for how to do that here
77+
UITabBar
78+
.appearance().backgroundColor = UIColor.white
79+
.withAlphaComponent(0.9)
80+
UITabBar.appearance().unselectedItemTintColor = UIColor.black
81+
}
82+
}
10083
}
10184

10285
@available(iOS 17.0, *)
10386
#Preview {
104-
ContentView(user: BaseUserDTO.danielAgapov)
87+
ContentView(user: BaseUserDTO.danielAgapov)
10588
}
10689

10790
func resizeImage(_ image: UIImage, targetSize: CGSize) -> UIImage? {
108-
let size = image.size
91+
let size = image.size
10992

110-
// Calculate the scaling factor to fit the image to the target dimensions while maintaining the aspect ratio
111-
let widthRatio = targetSize.width / size.width
112-
let heightRatio = targetSize.height / size.height
113-
let ratio = min(widthRatio, heightRatio)
93+
// Calculate the scaling factor to fit the image to the target dimensions while maintaining the aspect ratio
94+
let widthRatio = targetSize.width / size.width
95+
let heightRatio = targetSize.height / size.height
96+
let ratio = min(widthRatio, heightRatio)
11497

115-
let newSize = CGSize(width: size.width * ratio, height: size.height * ratio)
116-
// Add padding by using a percentage of the available space (e.g., 40% from top, 60% from bottom)
117-
let paddingFactor = 0.9
118-
let yOffset = (targetSize.height - newSize.height) * paddingFactor
98+
let newSize = CGSize(width: size.width * ratio, height: size.height * ratio)
99+
// Add padding by using a percentage of the available space (e.g., 40% from top, 60% from bottom)
100+
let paddingFactor = 0.9
101+
let yOffset = (targetSize.height - newSize.height) * paddingFactor
119102

120-
//Create a new image context
121-
let renderer = UIGraphicsImageRenderer(size: targetSize)
122-
let newImage = renderer.image { context in
123-
// Fill the background with a transparent color
124-
context.cgContext.setFillColor(UIColor.clear.cgColor)
125-
context.cgContext.fill(CGRect(origin: .zero, size: targetSize))
103+
//Create a new image context
104+
let renderer = UIGraphicsImageRenderer(size: targetSize)
105+
let newImage = renderer.image { context in
106+
// Fill the background with a transparent color
107+
context.cgContext.setFillColor(UIColor.clear.cgColor)
108+
context.cgContext.fill(CGRect(origin: .zero, size: targetSize))
126109

127-
// draw new image
128-
image.draw(
129-
in: CGRect(
130-
x: 0,
131-
y: yOffset,
132-
width: newSize.width,
133-
height: newSize.height
134-
)
135-
)
136-
}
110+
// draw new image
111+
image.draw(
112+
in: CGRect(
113+
x: 0,
114+
y: yOffset,
115+
width: newSize.width,
116+
height: newSize.height
117+
)
118+
)
119+
}
137120

138-
return newImage
121+
return newImage
139122
}

Spawn-App-iOS-SwiftUI/Services/API/APIService.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ extension Optional: OptionalProtocol {
2020
}
2121

2222
class APIService: IAPIService {
23-
static var baseURL: String =
24-
"https://spawn-app-back-end-production.up.railway.app/api/v1/"
23+
static var baseURL: String = ServiceConstants.URLs.apiBase
2524

2625
var errorMessage: String? // TODO: currently not being accessed; maybe use in alert to user
2726
var errorStatusCode: Int? // if 404 -> just populate empty array, that's fine
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import Foundation
2+
3+
struct ServiceConstants {
4+
// MARK: - App Configuration
5+
struct URLs {
6+
// Base URL for API calls
7+
static let apiBase = "https://spawn-app-back-end-production.up.railway.app/api/v1/"
8+
9+
// Base URL for sharing activities - updated to match deployed web app
10+
static let shareBase = "https://getspawn.com"
11+
}
12+
13+
// MARK: - Share URL Generation
14+
static func generateActivityShareURL(for activityId: UUID) -> URL {
15+
return URL(string: "\(URLs.shareBase)/activity/\(activityId.uuidString)")!
16+
}
17+
}

0 commit comments

Comments
 (0)