ThriftScan is a SwiftUI MVP for evaluating thrifted clothing/shoes before purchase.
- Capture or select 2-4 photos of an item.
- Enter the thrift store purchase price.
- Upload photos + price to
POST /analyze-itemusing multipart/form-data. - Show a structured analysis and a prominent Buy / Maybe / Pass recommendation.
- Save successful analyses locally with SwiftData for scan history.
- Support a mock mode for offline testing.
- UI: SwiftUI
- Pattern: MVVM
- Persistence: SwiftData (
ScanRecord) - Networking:
URLSessionasync/await, multipart upload - Settings:
UserDefaultsviaAppSettings(backendBaseURLString,useMockAPI)
ThriftScan/
├── ThriftScanApp.swift
├── Models/
│ ├── AnalyzeItemRequest.swift
│ ├── AnalysisResponse.swift
│ └── ScanRecord.swift
├── ViewModels/
│ ├── NewScanFlowViewModel.swift
│ └── ScanHistoryViewModel.swift
├── Services/
│ ├── AnalysisAPIClient.swift
│ ├── AppSettings.swift
│ ├── ItemAnalysisService.swift
│ ├── MockAnalysisProvider.swift
│ └── PhotoStorageService.swift
├── Persistence/
│ └── PersistenceController.swift
├── Navigation/
│ └── ScanRoute.swift
├── Views/
│ ├── MainTabView.swift
│ ├── NewScanFlowRootView.swift
│ ├── PhotoPickerScreen.swift
│ ├── PriceInputScreen.swift
│ ├── AnalysisResultScreen.swift
│ ├── AnalysisSummaryContent.swift
│ ├── HistoryScreen.swift
│ ├── HistoryDetailScreen.swift
│ ├── SettingsScreen.swift
│ ├── CameraImagePicker.swift
│ └── Components/
│ └── RecommendationBanner.swift
└── Resources/
└── MockResponse.json
- Open
ThriftScan.xcodeprojin Xcode 15+. - Select an iOS 17+ simulator or device.
- Build and run.
- In Settings, configure:
Backend base URL(for examplehttp://localhost:8080in simulator).Use mock responsestoggle for local testing.
Ensure these are present in Info.plist:
NSCameraUsageDescriptionNSPhotoLibraryUsageDescription
POST {baseURL}/analyze-item- Content type:
multipart/form-data
images: repeated JPEG files (photo0.jpg,photo1.jpg, ...)thrift_price: decimal number as text
{
"brand": "Patagonia",
"category": "Outerwear",
"item_type": "Nano Puff jacket",
"gender": "Men",
"color": "Forge Grey",
"material": "Recycled polyester ripstop",
"pattern": "Solid",
"style_keywords": ["outdoor", "lightweight insulation"],
"suggested_search_queries": ["patagonia nano puff men's forge grey medium"],
"condition_checklist": ["Verify zipper pulls and teeth"],
"estimated_resale_low": 85,
"estimated_resale_high": 140,
"suggested_list_price": 119,
"expected_sale_price": 105,
"estimated_net_profit": 62,
"recommendation": "Buy",
"reasoning": "Strong brand velocity and healthy margin."
}recommendation should be one of: Buy, Maybe, Pass (case-insensitive is normalized by the app).
When Use mock responses is enabled:
- The app loads
Resources/MockResponse.json. - If that file fails to load, it falls back to a deterministic synthetic mock response.
This mode avoids any backend dependency for UI/flow testing.