Skip to content
Merged
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
492 changes: 492 additions & 0 deletions ios/StillPoint.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

127 changes: 127 additions & 0 deletions ios/StillPointApp/Components/BlockGridView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import SwiftUI
import StillPointShared

struct BlockGridView: View {
let blocks: [BlockDef]
let elapsed: Double
let totalSeconds: Int

private let blockSize: CGFloat = 56
private let blockSpacing: CGFloat = 11
private let blockRadius: CGFloat = 10

private var minuteBlocks: [BlockDef] {
blocks.filter { $0.type == .minute }
}

private var secondBlocks: [BlockDef] {
blocks.filter { $0.type == .second }
}

private var useMinuteBlocks: Bool {
totalSeconds > 120
}

var body: some View {
if useMinuteBlocks {
VStack(spacing: SPSpacing.s3) {
// Minute blocks
LazyVGrid(
columns: [GridItem(.adaptive(minimum: blockSize, maximum: blockSize), spacing: blockSpacing)],
spacing: blockSpacing
) {
ForEach(minuteBlocks) { block in
blockView(block)
}
}

// Divider + "final minute" label
VStack(spacing: SPSpacing.s1) {
Rectangle()
.fill(SPColor.border1)
.frame(height: 1)

Text("FINAL MINUTE")
.font(SPFont.mono(11))
.foregroundStyle(Color(SPColor.fg4))
.tracking(2)

// 10-second blocks
LazyVGrid(
columns: [GridItem(.adaptive(minimum: blockSize, maximum: blockSize), spacing: blockSpacing)],
spacing: blockSpacing
) {
ForEach(secondBlocks) { block in
blockView(block)
}
}
}
}
} else {
LazyVGrid(
columns: [GridItem(.adaptive(minimum: blockSize, maximum: blockSize), spacing: blockSpacing)],
spacing: blockSpacing
) {
ForEach(blocks) { block in
blockView(block)
}
}
}
}

@ViewBuilder
private func blockView(_ block: BlockDef) -> some View {
let blockEnd = block.startTime + block.duration
let isFilled = elapsed >= Double(blockEnd)
let isCurrent = elapsed >= Double(block.startTime)
&& elapsed < Double(blockEnd)
&& elapsed < Double(totalSeconds)
let progress = isCurrent
? (elapsed - Double(block.startTime)) / Double(block.duration)
: isFilled ? 1.0 : 0.0

ZStack {
// Background
RoundedRectangle(cornerRadius: blockRadius)
.fill(SPColor.surface1)

// Fill from bottom
GeometryReader { geo in
VStack {
Spacer()
Rectangle()
.fill(isFilled ? LinearGradient.greenFill : LinearGradient.amberFill)
.frame(height: geo.size.height * progress)
.opacity(isFilled ? 0.85 : 0.7)
}
}
.clipShape(RoundedRectangle(cornerRadius: blockRadius))

// Label
Text(block.label)
.font(SPFont.mono(13, weight: .medium))
.foregroundStyle(isFilled ? SPColor.overlayText : Color(SPColor.fg4))

// Current block pulse border — uses phaseAnimator for continuous pulse
if isCurrent {
RoundedRectangle(cornerRadius: blockRadius)
.stroke(SPColor.amberDim, lineWidth: 1)
.phaseAnimator([false, true]) { content, phase in
content.opacity(phase ? 1.0 : 0.4)
} animation: { _ in
.easeInOut(duration: 1.0)
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
auerbachb marked this conversation as resolved.
}
.frame(width: blockSize, height: blockSize)
.overlay(
RoundedRectangle(cornerRadius: blockRadius)
.stroke(
isFilled ? SPColor.greenBorder :
isCurrent ? SPColor.amberBorder :
SPColor.border1,
lineWidth: 1
)
)
}
}
46 changes: 46 additions & 0 deletions ios/StillPointApp/Components/MindStateBarView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import SwiftUI
import StillPointShared

/// Horizontal segmented bar showing clear (green) and thinking (amber) periods.
struct MindStateBarView: View {
let elapsed: Double
let totalSeconds: Int
let mindStateLog: [MindStateEntry]

var body: some View {
GeometryReader { geo in
let width = geo.size.width
let safeTotalSeconds = max(totalSeconds, 1)

ZStack(alignment: .leading) {
// Background track
RoundedRectangle(cornerRadius: 4)
.fill(SPColor.surface2)

// Segments
ForEach(Array(mindStateLog.enumerated()), id: \.offset) { index, entry in
let startFraction = entry.time / Double(safeTotalSeconds)
let endTime: Double = {
if index + 1 < mindStateLog.count {
return mindStateLog[index + 1].time
}
return min(elapsed, Double(safeTotalSeconds))
}()
let endFraction = endTime / Double(safeTotalSeconds)

let segmentX = width * startFraction
let segmentWidth = max(0, width * (endFraction - startFraction))

Rectangle()
.fill(entry.isClear ? SPColor.green : SPColor.amber)
.opacity(entry.isClear ? 0.5 : 0.6)
.frame(width: segmentWidth)
.offset(x: segmentX)
}
}
}
.frame(height: 8)
.clipShape(RoundedRectangle(cornerRadius: 4))
.padding(.horizontal, SPSpacing.s4)
}
}
64 changes: 64 additions & 0 deletions ios/StillPointApp/Components/ThoughtCaptureView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import SwiftUI

/// Inline thought capture card that appears when the user taps "I'm thinking"
struct ThoughtCaptureView: View {
let onCapture: (String) -> Void
let onDismiss: () -> Void

@State private var text = ""
@FocusState private var isFocused: Bool

private var trimmedText: String { text.trimmingCharacters(in: .whitespacesAndNewlines) }

var body: some View {
VStack(spacing: SPSpacing.s2) {
HStack {
Text("capture this thought")
.font(SPFont.mono(11))
.foregroundStyle(SPColor.amberText)
.tracking(1)
Spacer()
Button {
onDismiss()
} label: {
Image(systemName: "xmark")
.font(.system(size: 12, weight: .medium))
.foregroundStyle(Color(SPColor.fg4))
}
}

TextField("what were you thinking about?", text: $text)
.font(SPFont.serifItalic(15))
.foregroundStyle(Color(SPColor.fg))
.focused($isFocused)
.onSubmit {
if !trimmedText.isEmpty {
onCapture(trimmedText)
}
}

HStack {
Spacer()
Button {
if !trimmedText.isEmpty {
onCapture(trimmedText)
} else {
onDismiss()
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} label: {
Text(trimmedText.isEmpty ? "skip" : "save")
.font(SPFont.mono(12, weight: .medium))
.foregroundStyle(trimmedText.isEmpty ? Color(SPColor.fg4) : SPColor.amber)
}
}
}
.padding(SPSpacing.s3)
.background(SPColor.amberBgFaint)
.clipShape(RoundedRectangle(cornerRadius: 12))
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(SPColor.amberBorderSubtle)
)
.onAppear { isFocused = true }
}
}
56 changes: 56 additions & 0 deletions ios/StillPointApp/Navigation/MainTabView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import SwiftUI

struct MainTabView: View {
let appVM: AppViewModel
@State private var selectedTab = 0

var body: some View {
TabView(selection: $selectedTab) {
HomeView(appVM: appVM)
.tabItem {
Label("HOME", systemImage: "house")
}
.tag(0)

HistoryView(appVM: appVM)
.tabItem {
Label("PROGRESS", systemImage: "chart.bar")
}
.tag(1)

ThoughtJournalView()
.tabItem {
Label("JOURNAL", systemImage: "book")
}
.tag(2)

PublicBoardView(currentUsername: appVM.currentUser?.username)
.tabItem {
Label("BOARD", systemImage: "person.3")
}
.tag(3)

SettingsView(appVM: appVM)
.tabItem {
Label("SETTINGS", systemImage: "gearshape")
}
.tag(4)
}
.tint(SPColor.green)
.onAppear {
Self.configureTabBarAppearance()
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

private static var tabBarConfigured = false

private static func configureTabBarAppearance() {
guard !tabBarConfigured else { return }
tabBarConfigured = true
let appearance = UITabBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = UIColor(SPColor.bg)
UITabBar.appearance().standardAppearance = appearance
UITabBar.appearance().scrollEdgeAppearance = appearance
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.502",
"green" : "0.871",
"red" : "0.290"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"info" : {
"author" : "xcode",
"version" : 1
}
}
6 changes: 6 additions & 0 deletions ios/StillPointApp/Resources/Assets.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
14 changes: 14 additions & 0 deletions ios/StillPointApp/StillPointApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import SwiftUI
import SwiftData
import StillPointShared

@main
struct StillPointApp: App {
var body: some Scene {
WindowGroup {
RootView()
.preferredColorScheme(.dark)
}
.modelContainer(for: [User.self, Session.self, Thought.self])
}
}
Loading