Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"layers" : [
{
"filename" : "Front.solidimagestacklayer"
},
{
"filename" : "Back.solidimagestacklayer"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "vision",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
6 changes: 6 additions & 0 deletions trivit Vision/Images.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
29 changes: 29 additions & 0 deletions trivit Vision/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>Trivit</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>5.0.1</string>
<key>CFBundleVersion</key>
<string>2</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
</dict>
</dict>
</plist>
56 changes: 56 additions & 0 deletions trivit Vision/Models/Trivit.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// Trivit.swift
// trivit Vision
//
// Simplified SwiftData model for visionOS (matches watch app pattern)
//

import Foundation
import SwiftData

@Model
final class Trivit: Equatable {
var id: UUID
var title: String
var count: Int
var colorIndex: Int
var isCollapsed: Bool
var createdAt: Date
var sortOrder: Int

init(
id: UUID = UUID(),
title: String = "New Trivit",
count: Int = 0,
colorIndex: Int = 0,
isCollapsed: Bool = true,
createdAt: Date = Date(),
sortOrder: Int = 0
) {
self.id = id
self.title = title
self.count = count
self.colorIndex = colorIndex
self.isCollapsed = isCollapsed
self.createdAt = createdAt
self.sortOrder = sortOrder
}

func increment() {
count += 1
}

func decrement() {
if count > 0 {
count -= 1
}
}

func reset() {
count = 0
}

static func == (lhs: Trivit, rhs: Trivit) -> Bool {
lhs.id == rhs.id
}
}
62 changes: 62 additions & 0 deletions trivit Vision/Theme/TrivitColors.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// TrivitColors.swift
// trivit Vision
//
// Color themes for the visionOS app - matching iOS app design
//

import SwiftUI

struct TrivitColors {
static let colorCount = 10

// Main color palette (flat design inspired) - same as iOS app
static let palette: [Color] = [
Color(hex: "1ABC9C"), // Turquoise
Color(hex: "2ECC71"), // Emerald
Color(hex: "3498DB"), // Peter River
Color(hex: "9B59B6"), // Amethyst
Color(hex: "E74C3C"), // Alizarin
Color(hex: "F39C12"), // Orange
Color(hex: "E91E63"), // Pink
Color(hex: "00BCD4"), // Cyan
Color(hex: "8BC34A"), // Light Green
Color(hex: "FF5722"), // Deep Orange
]

static func color(at index: Int) -> Color {
let safeIndex = abs(index) % palette.count
return palette[safeIndex]
}

static func randomColorIndex() -> Int {
Int.random(in: 0..<palette.count)
}
}

// MARK: - Color Extension for Hex Support
extension Color {
init(hex: String) {
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int: UInt64 = 0
Scanner(string: hex).scanHexInt64(&int)
let a, r, g, b: UInt64
switch hex.count {
case 3: // RGB (12-bit)
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
case 6: // RGB (24-bit)
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
case 8: // ARGB (32-bit)
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
default:
(a, r, g, b) = (1, 1, 1, 0)
}
self.init(
.sRGB,
red: Double(r) / 255,
green: Double(g) / 255,
blue: Double(b) / 255,
opacity: Double(a) / 255
)
}
}
14 changes: 14 additions & 0 deletions trivit Vision/TrivitVisionApp.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudKit</string>
</array>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.wouterdevriendt.trivit</string>
</array>
</dict>
</plist>
64 changes: 64 additions & 0 deletions trivit Vision/TrivitVisionApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// TrivitVisionApp.swift
// trivit Vision
//
// visionOS app entry point - syncs with iOS via CloudKit
//

import SwiftUI
import SwiftData
import os.log

private let logger = Logger(subsystem: "com.wouterdevriendt.trivit.vision", category: "VisionApp")

@main
struct TrivitVisionApp: App {
// Check for sample data mode (for screenshots)
private static var isSampleDataMode: Bool {
ProcessInfo.processInfo.arguments.contains("-SampleDataMode")
}

var sharedModelContainer: ModelContainer = {
let schema = Schema([Trivit.self])

let modelConfiguration = ModelConfiguration(
schema: schema,
isStoredInMemoryOnly: isSampleDataMode,
cloudKitDatabase: isSampleDataMode ? .none : .automatic
)

do {
logger.info("🥽 Creating ModelContainer for visionOS app")
let container = try ModelContainer(for: schema, configurations: [modelConfiguration])

if isSampleDataMode {
logger.info("🥽 Sample data mode - creating sample trivits")
let context = container.mainContext
let sampleTrivits = [
Trivit(title: "Glasses of water", count: 7, colorIndex: 0, sortOrder: 0),
Trivit(title: "Push-ups done", count: 42, colorIndex: 1, sortOrder: 1),
Trivit(title: "Books read", count: 3, colorIndex: 2, sortOrder: 2),
Trivit(title: "Meditation", count: 15, colorIndex: 4, sortOrder: 3),
Trivit(title: "Coffee cups", count: 5, colorIndex: 5, sortOrder: 4),
Trivit(title: "Steps walked", count: 12, colorIndex: 3, sortOrder: 5),
]
for trivit in sampleTrivits {
context.insert(trivit)
}
try? context.save()
}

return container
} catch {
logger.error("🥽 Failed to create ModelContainer: \(error)")
fatalError("Could not create ModelContainer: \(error)")
}
}()

var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(sharedModelContainer)
}
}
84 changes: 84 additions & 0 deletions trivit Vision/Views/AddTrivitView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//
// AddTrivitView.swift
// trivit Vision
//
// Sheet for creating a new counter
//

import SwiftUI
import SwiftData

struct AddTrivitView: View {
@Environment(\.modelContext) private var modelContext
@Environment(\.dismiss) private var dismiss
@Query(sort: \Trivit.sortOrder) private var trivits: [Trivit]
@State private var title = ""
@State private var selectedColorIndex = 0

var body: some View {
NavigationStack {
Form {
Section("Name") {
TextField("Counter name", text: $title)
}

Section("Color") {
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 5), spacing: 12) {
ForEach(0..<TrivitColors.colorCount, id: \.self) { index in
Circle()
.fill(TrivitColors.color(at: index))
.frame(width: 40, height: 40)
.overlay {
if index == selectedColorIndex {
Circle()
.strokeBorder(.white, lineWidth: 3)
}
}
.hoverEffect(.highlight)
.onTapGesture {
selectedColorIndex = index
}
}
}
.padding(.vertical, 8)
}
}
.navigationTitle("New Counter")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
dismiss()
}
}
ToolbarItem(placement: .confirmationAction) {
Button("Add") {
createTrivit()
dismiss()
}
.disabled(title.trimmingCharacters(in: .whitespaces).isEmpty)
}
}
}
.onAppear {
// Auto-select next color in sequence
let lastColorIndex = trivits.last?.colorIndex ?? -1
selectedColorIndex = (lastColorIndex + 1) % TrivitColors.colorCount
}
}

private func createTrivit() {
let maxSortOrder = trivits.map { $0.sortOrder }.max() ?? -1
let newTrivit = Trivit(
title: title.trimmingCharacters(in: .whitespaces),
count: 0,
colorIndex: selectedColorIndex,
sortOrder: maxSortOrder + 1
)
modelContext.insert(newTrivit)
}
}

#Preview {
AddTrivitView()
.modelContainer(for: [Trivit.self], inMemory: true)
}
Loading