Skip to content

Instantly share code, notes, and snippets.

@luthviar
Last active September 16, 2025 04:43
Show Gist options
  • Select an option

  • Save luthviar/66b38d050c9fa6137f3074ec817a261a to your computer and use it in GitHub Desktop.

Select an option

Save luthviar/66b38d050c9fa6137f3074ec817a261a to your computer and use it in GitHub Desktop.
//
// ContentView19.swift
// Coba14July2025
//
// Created by Luthfi Abdurrahim on 21/08/25.
//
import SwiftUI
import ThemeEazy
// MARK: - Models
struct Product: Identifiable {
let id = UUID()
let name: String
let price: Double
let image: String
let category: ProductCategory
let description: String
let rating: Double
}
enum ProductCategory: String, CaseIterable {
case all = "All"
case ipCamera = "IP Camera"
case cloudRecording = "Cloud Recording"
case accessories = "Accessories"
case storage = "Storage"
}
// MARK: - View Model
class ShoppingViewModel: ObservableObject {
@Published var products: [Product] = []
@Published var cartItems: [Product] = []
@Published var selectedCategory: ProductCategory = .all
@Published var isLoading = false
init() {
loadProducts()
/// Simulate 20 items in cart initially
for _ in 0..<20 {
cartItems.append(sampleProducts.randomElement()!)
}
}
var filteredProducts: [Product] {
if selectedCategory == .all {
return products
}
return products.filter { $0.category == selectedCategory }
}
var cartItemCount: Int {
cartItems.count
}
func addToCart(_ product: Product) {
cartItems.append(product)
}
func removeFromCart(_ product: Product) {
if let index = cartItems.firstIndex(where: { $0.id == product.id }) {
cartItems.remove(at: index)
}
}
func loadProducts() {
isLoading = true
// Simulate API call
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.products = sampleProducts
self.isLoading = false
}
}
}
// MARK: - Sample Data
let sampleProducts: [Product] = [
Product(name: "WiFi Security Camera", price: 299.99, image: "camera.fill", category: .ipCamera, description: "High-definition wireless security camera with night vision", rating: 4.5),
Product(name: "Smart Doorbell Camera", price: 199.99, image: "bell.fill", category: .ipCamera, description: "Smart doorbell with two-way audio and motion detection", rating: 4.7),
Product(name: "PTZ Camera", price: 449.99, image: "video.fill", category: .ipCamera, description: "Pan-tilt-zoom camera with remote control", rating: 4.3),
Product(name: "Cloud Storage 1TB", price: 99.99, image: "icloud.fill", category: .cloudRecording, description: "1TB cloud storage for 1 year", rating: 4.6),
Product(name: "Premium Cloud Plan", price: 199.99, image: "icloud.and.arrow.up.fill", category: .cloudRecording, description: "Premium cloud recording with AI features", rating: 4.8),
Product(name: "Camera Mount", price: 29.99, image: "mount.fill", category: .accessories, description: "Universal camera mounting bracket", rating: 4.2),
Product(name: "Power Adapter", price: 19.99, image: "powerplug.fill", category: .accessories, description: "12V power adapter for cameras", rating: 4.4),
Product(name: "MicroSD Card 64GB", price: 39.99, image: "sdcard.fill", category: .storage, description: "High-speed microSD card for local storage", rating: 4.5)
]
// MARK: - Main Content View
struct ContentView19: View {
@StateObject private var viewModel = ShoppingViewModel()
@State private var showingCart = false
var body: some View {
NavigationView {
VStack(spacing: 0) {
// Header
headerView
// Category Filter
categoryFilterView
// Products Grid
if viewModel.isLoading {
Spacer()
ProgressView("Loading products...")
.font(.headline)
Spacer()
} else {
productsGridView
}
}
.navigationBarHidden(true)
}
.sheet(isPresented: $showingCart) {
CartView(viewModel: viewModel)
}
}
// MARK: - Header View
private var headerView: some View {
HStack {
Text("Antares Eazy")
.font(LGNFont.heading5)
.foregroundColor(.black)
Spacer()
Button(action: {
showingCart = true
}) {
ZStack(alignment: .topTrailing) {
Image(systemName: "cart.fill")
.font(.title2)
.foregroundColor(.black)
if viewModel.cartItemCount > 0 {
Text("\(viewModel.cartItemCount)")
.font(.caption2)
.fontWeight(.bold)
.foregroundColor(.white)
.frame(minWidth: 20, minHeight: 20)
.background(Color.red)
.clipShape(Circle())
.offset(x: 8, y: -8)
}
}
}
}
.padding(.horizontal, 20)
.padding(.top, 10)
.padding(.bottom, 20)
}
// MARK: - Category Filter View
private var categoryFilterView: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12) {
ForEach(ProductCategory.allCases, id: \.self) { category in
CategoryButton(
title: category.rawValue,
isSelected: viewModel.selectedCategory == category
) {
withAnimation(.easeInOut(duration: 0.3)) {
viewModel.selectedCategory = category
}
}
}
}
.padding(.horizontal, 20)
}
.padding(.bottom, 0)
}
// MARK: - Products Grid View
private var productsGridView: some View {
ScrollView {
LazyVStack {
/// Use enumerated array to have stable RandomAccessCollection and access index & product
ForEach(Array(viewModel.filteredProducts.enumerated()), id: \.element.id) { index, product in
ProductCardViewLiveStore(product: ProductCardViewLiveStore.ProductCardData(imageUrl: "", name: "IP Camera", price: 0, originalPrice: 0, discountPercentage: "5%"))
.padding(.top, index == 0 ? 11 : 0)
}
}
}
}
}
// MARK: - Category Button
struct CategoryButton: View {
let title: String
let isSelected: Bool
let action: () -> Void
var body: some View {
Button(action: action) {
Text(title)
.font(.system(size: 12, weight: .semibold))
.foregroundColor(isSelected ? .white : .blue)
.padding(.horizontal, 16)
.padding(.vertical, 4)
}
.background(
ZStack {
// Background fill
RoundedRectangle(cornerRadius: 25)
.fill(isSelected ? Color.blue : Color.clear)
// Custom border using inset
if !isSelected {
RoundedRectangle(cornerRadius: 25)
.fill(Color.blue)
.mask(
RoundedRectangle(cornerRadius: 25)
.stroke(lineWidth: 2) // Double the desired width
)
}
}
)
}
}
// MARK: - Product Card
struct ProductCard: View {
let product: Product
let onAddToCart: () -> Void
var body: some View {
VStack(alignment: .leading, spacing: 8) {
// Product Image
ZStack {
RoundedRectangle(cornerRadius: 12)
.fill(Color.gray.opacity(0.1))
.frame(height: 140)
Image(systemName: product.image)
.font(.system(size: 40))
.foregroundColor(.blue)
}
// Product Info
VStack(alignment: .leading, spacing: 4) {
Text(product.name)
.font(.system(size: 14, weight: .medium))
.lineLimit(2)
.multilineTextAlignment(.leading)
// Rating
HStack(spacing: 2) {
ForEach(0..<5) { index in
Image(systemName: index < Int(product.rating) ? "star.fill" : "star")
.font(.caption2)
.foregroundColor(.yellow)
}
Text(String(format: "%.1f", product.rating))
.font(.caption2)
.foregroundColor(.gray)
}
// Price and Add Button
HStack {
Text("$\(String(format: "%.2f", product.price))")
.font(.system(size: 16, weight: .bold))
.foregroundColor(.black)
Spacer()
Button(action: onAddToCart) {
Image(systemName: "plus.circle.fill")
.font(.title3)
.foregroundColor(.blue)
}
}
}
.padding(.horizontal, 4)
}
.padding(8)
.background(Color.white)
.cornerRadius(12)
.shadow(color: .black.opacity(0.1), radius: 3, x: 0, y: 2)
}
}
// MARK: - Cart View
struct CartView: View {
@ObservedObject var viewModel: ShoppingViewModel
@Environment(\.presentationMode) var presentationMode
private var totalPrice: Double {
viewModel.cartItems.reduce(0) { $0 + $1.price }
}
var body: some View {
NavigationView {
VStack {
if viewModel.cartItems.isEmpty {
// Empty Cart State
Spacer()
Image(systemName: "cart")
.font(.system(size: 60))
.foregroundColor(.gray)
Text("Your cart is empty")
.font(.headline)
.foregroundColor(.gray)
.padding(.top, 10)
Spacer()
} else {
// Cart Items List
List {
ForEach(viewModel.cartItems) { product in
CartItemRow(product: product) {
viewModel.removeFromCart(product)
}
}
}
.listStyle(PlainListStyle())
// Cart Summary
VStack(spacing: 15) {
Divider()
HStack {
Text("Total (\(viewModel.cartItemCount) items)")
.font(.headline)
Spacer()
Text("$\(String(format: "%.2f", totalPrice))")
.font(.headline)
.fontWeight(.bold)
}
Button(action: {
// Checkout action
}) {
Text("Checkout")
.font(.headline)
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding(.vertical, 15)
.background(Color.blue)
.cornerRadius(12)
}
}
.padding()
.background(Color.white)
}
}
.navigationTitle("Shopping Cart")
.navigationBarItems(
trailing: Button("Done") {
presentationMode.wrappedValue.dismiss()
}
)
}
}
}
// MARK: - Cart Item Row
struct CartItemRow: View {
let product: Product
let onRemove: () -> Void
var body: some View {
HStack(spacing: 12) {
// Product Image
Image(systemName: product.image)
.font(.title2)
.foregroundColor(.blue)
.frame(width: 40, height: 40)
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
// Product Info
VStack(alignment: .leading, spacing: 4) {
Text(product.name)
.font(.system(size: 16, weight: .medium))
.lineLimit(1)
Text(product.category.rawValue)
.font(.caption)
.foregroundColor(.gray)
}
Spacer()
// Price and Remove Button
VStack(alignment: .trailing, spacing: 4) {
Text("$\(String(format: "%.2f", product.price))")
.font(.system(size: 16, weight: .bold))
Button(action: onRemove) {
Image(systemName: "trash")
.font(.caption)
.foregroundColor(.red)
}
}
}
.padding(.vertical, 8)
}
}
// MARK: - Preview
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
//
// ContentView18.swift
// Coba14July2025
//
// Created by Luthfi Abdurrahim on 19/08/25.
//
import CoreResource
import SwiftUI
import ThemeEazy
public struct ProductCardViewLiveStore: View {
public struct ProductCardData: Identifiable {
public let id: UUID = UUID()
public let imageUrl: String
public let name: String
public let price: Double
public let originalPrice: Double?
public let discountPercentage: String?
public var formattedPrice: String {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = "IDR"
formatter.currencySymbol = "Rp "
formatter.maximumFractionDigits = 0
return formatter.string(from: NSNumber(value: price)) ?? "Rp \(Int(price))"
}
public var formattedOriginalPrice: String {
guard let originalPrice else { return "" }
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = "IDR"
formatter.currencySymbol = "Rp "
formatter.maximumFractionDigits = 0
return formatter.string(from: NSNumber(value: originalPrice)) ?? "Rp \(Int(originalPrice))"
}
}
public let product: ProductCardData
public var body: some View {
HStack(alignment: .top) {
AsyncImage(url: URL(string: "https://iot-ihsmart-images-public-stage.oss-ap-southeast-5.aliyuncs.com/eazy/perangkat/foto%20produk/BARDI%20-%20Smart%20IP%20Camera%20Static%20Indoor_icon.jpg")) { newImage in
newImage
.resizable()
.scaledToFit()
.frame(height: 120)
} placeholder: {
Image(.ipCamExample)
.resizable()
.scaledToFit()
.frame(height: 120)
}
.padding(.trailing, 16)
ZStack(alignment: .topLeading) {
VStack(alignment: .leading, spacing: 0) {
Text(product.name)
.font(LGNFont.bodySmallSemiBold)
.foregroundColor(Color.customApplicationColor(.tertiary80))
.padding(.bottom, 8)
Text("Rp 364.000")
.font(LGNFont.bodyMediumBold)
.foregroundColor(Color.customApplicationColor(.tertiary80))
HStack(spacing: 1) {
Text("Rp 473.000")
.font(LGNFont.bodyCaptionRegular)
.foregroundColor(Color.customApplicationColor(.black70))
.strikethrough()
ZStack(alignment: .leading) {
Image(.discountLabelBackgroundRed)
.resizable()
Text("23%")
.font(LGNFont.bodyCaptionSemiBold)
.foregroundColor(LGNColor.error500)
.padding(.leading, 6)
.padding(.trailing, 2)
.layoutPriority(1)
}
.frame(height: 16)
}
}
VStack {
Spacer()
HStack {
Spacer()
Button(action: {
// Handle buy action
print("Buy button tapped for")
}) {
Text("Beli")
.font(LGNFont.bodySmallSemiBold)
.foregroundColor(.white)
.padding(.vertical, 7)
.padding(.horizontal, 22)
.background(LGNColor.primary500)
.cornerRadius(8)
}
}
}
}
}
.padding(.vertical, 13)
.padding(.horizontal, 16)
.background(Color.white)
.cornerRadius(12)
.shadow(
color: Color.black.opacity(0.12),
radius: 6.24,
x: 0,
y: 0
)
.padding(.horizontal, 26)
.padding(.bottom, 14)
}
}
@luthviar
Copy link
Author

image

@luthviar
Copy link
Author

image

@luthviar
Copy link
Author

WhatsApp.Video.2025-08-21.at.14.22.56.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment