Skip to content
Open

Ios #15

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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ playwright/.cache/
.env
.agents
.claude

film-photo-tracker/
194 changes: 194 additions & 0 deletions Film Tracker.html

Large diffs are not rendered by default.

56 changes: 56 additions & 0 deletions ios/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# macOS
.DS_Store

# Xcode
#
# gitignore.io results for: Xcode
build/
DerivedData/
*.xcodeproj
*.xcworkspace
!default.xcworkspace
xcuserdata/
*.xccheckout
*.xcscmblueprint
*.xctestplan
*.moved-aside
DerivedData/
*.hmap
*.ipa
*.xcuserstate
*.itunespackage

# Swift Package Manager
#
# Add this line if you want to avoid committing the test-generated folders
.build/
Packages/
Package.resolved
Package.pins
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.swiftpm/xcode/xcuserdata/

# XcodeGen
#
# Project file is generated from project.yml
# *.xcodeproj is already ignored above

# Fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output

# Bundle
vendor/bundle/
.bundle/

# Backup files
*.bak
*.swp
*.swo

# Crash reports
*.crash

film-photo-tracker/
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
6 changes: 6 additions & 0 deletions ios/FilmTracker/App/Assets.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
36 changes: 36 additions & 0 deletions ios/FilmTracker/App/ContentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import SwiftUI

struct ContentView: View {
@State private var selectedTab: Tab = .rolls

enum Tab {
case rolls
case equipment
}

var body: some View {
TabView(selection: $selectedTab) {
NavigationStack {
RollsView()
}
.tabItem {
Label("Rolls", systemImage: "film")
}
.tag(Tab.rolls)

NavigationStack {
EquipmentView()
}
.tabItem {
Label("Equipment", systemImage: "camera")
}
.tag(Tab.equipment)
}
.tint(Color(hex: Constants.Design.accent))
}
}

#Preview {
ContentView()
.modelContainer(for: [FilmRoll.self, Exposure.self, Camera.self, Lens.self], inMemory: true)
}
29 changes: 29 additions & 0 deletions ios/FilmTracker/App/FilmTrackerApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import SwiftUI
import SwiftData

@main
struct FilmTrackerApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Camera.self,
Lens.self,
FilmRoll.self,
Exposure.self,
AppSettings.self,
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)

do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()

var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(sharedModelContainer)
}
}
73 changes: 73 additions & 0 deletions ios/FilmTracker/Components/AppButton.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import SwiftUI

struct AppButton: View {
enum Variant {
case primary
case secondary
case ghost
case destructive
}

var title: String
var variant: Variant = .primary
var isDisabled: Bool = false
var action: () -> Void

var body: some View {
Button(action: action) {
Text(title)
.font(.appLabel(16))
.padding(.vertical, 14)
.frame(maxWidth: .infinity)
.background(backgroundView)
.foregroundColor(foregroundColor)
.cornerRadius(Constants.Design.radiusMD)
.opacity(isDisabled ? 0.5 : 1.0)
}
.disabled(isDisabled)
.buttonStyle(ScaleButtonStyle())
}

@ViewBuilder
private var backgroundView: some View {
switch variant {
case .primary:
Color.accent
case .secondary:
Color.surface2
case .ghost:
Color.clear
case .destructive:
Color.appRed
}
}

private var foregroundColor: Color {
switch variant {
case .primary:
.black
case .secondary, .ghost:
.appText
case .destructive:
.white
}
}
}

struct ScaleButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.scaleEffect(configuration.isPressed ? 0.96 : 1.0)
.animation(.easeOut(duration: 0.1), value: configuration.isPressed)
}
}

#Preview {
VStack {
AppButton(title: "Primary Button") {}
AppButton(title: "Secondary Button", variant: .secondary) {}
AppButton(title: "Ghost Button", variant: .ghost) {}
}
.padding()
.background(Color.appBg)
}
43 changes: 43 additions & 0 deletions ios/FilmTracker/Components/AppCard.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import SwiftUI

struct AppCard<Content: View>: View {
var content: Content

init(@ViewBuilder content: () -> Content) {
self.content = content()
}

var body: some View {
ZStack {
Color.surface1

// Subtle grain placeholder
Color.black.opacity(0.05)
.blendMode(.overlay)

content
}
.cornerRadius(Constants.Design.radiusLG)
.overlay(
RoundedRectangle(cornerRadius: Constants.Design.radiusLG)
.stroke(Color.white.opacity(0.1), lineWidth: 0.5)
)
}
}

#Preview {
AppCard {
VStack(alignment: .leading) {
Text("Kodak Portra 400")
.font(.appHeadline())
.foregroundColor(.appText)
Text("36 exposures remaining")
.font(.appBody(14))
.foregroundColor(.muted)
}
.padding()
}
.frame(height: 100)
.padding()
.background(Color.appBg)
}
Loading
Loading