Update signing configuration to use 88oakapps.feels identifiers

- Update App Group IDs from group.com.tt.feels to group.com.88oakapps.feels
- Update iCloud container IDs from iCloud.com.tt.feels to iCloud.com.88oakapps.feels
- Sync code constants with entitlements across all targets (iOS, Watch, Widget)
- Update documentation in CLAUDE.md and PROJECT_OVERVIEW.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-29 10:01:49 -06:00
parent b6290403b0
commit 810ac2d649
28 changed files with 12289 additions and 13332 deletions

View File

@@ -77,8 +77,6 @@ struct AddMoodHeaderView: View {
HorizontalVotingView(moodTint: moodTint, onMoodSelected: addItem)
case .cards:
CardVotingView(moodTint: moodTint, onMoodSelected: addItem)
case .radial:
RadialVotingView(moodTint: moodTint, onMoodSelected: addItem)
case .stacked:
StackedVotingView(moodTint: moodTint, onMoodSelected: addItem)
case .aura:
@@ -137,99 +135,62 @@ struct CardVotingView: View {
let moodTint: MoodTints
let onMoodSelected: (Mood) -> Void
private let columns = [
GridItem(.flexible(), spacing: 12),
GridItem(.flexible(), spacing: 12),
GridItem(.flexible(), spacing: 12)
]
var body: some View {
LazyVGrid(columns: columns, spacing: 12) {
ForEach(Mood.allValues) { mood in
Button(action: { onMoodSelected(mood) }) {
mood.icon
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 40, height: 40)
.foregroundColor(moodTint.color(forMood: mood))
.frame(maxWidth: .infinity)
.padding(.vertical, 20)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(moodTint.color(forMood: mood).opacity(0.15))
)
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(moodTint.color(forMood: mood).opacity(0.3), lineWidth: 1)
)
}
.buttonStyle(CardButtonStyle())
.accessibilityLabel(mood.strValue)
.accessibilityHint(String(localized: "Select this mood"))
}
}
.accessibilityElement(children: .contain)
.accessibilityLabel(String(localized: "Mood selection"))
}
}
GeometryReader { geo in
let spacing: CGFloat = 12
let cardWidth = (geo.size.width - spacing * 2) / 3
// Offset to center bottom row cards between top row cards
// Each bottom card should be centered between two top cards
let bottomOffset = (cardWidth + spacing) / 2
// MARK: - Layout 3: Radial/Semi-circle
struct RadialVotingView: View {
let moodTint: MoodTints
let onMoodSelected: (Mood) -> Void
var body: some View {
GeometryReader { geometry in
let center = CGPoint(x: geometry.size.width / 2, y: geometry.size.height * 0.9)
let radius = min(geometry.size.width, geometry.size.height) * 0.65
let moods = Mood.allValues
ZStack {
ForEach(Array(moods.enumerated()), id: \.element.id) { index, mood in
let angle = angleForIndex(index, total: moods.count)
let position = positionForAngle(angle, radius: radius, center: center)
Button(action: { onMoodSelected(mood) }) {
mood.icon
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 44, height: 44)
.foregroundColor(moodTint.color(forMood: mood))
.padding(12)
.background(
Circle()
.fill(moodTint.color(forMood: mood).opacity(0.1))
)
VStack(spacing: spacing) {
// Top row: Great, Good, Average
HStack(spacing: spacing) {
ForEach(Array(Mood.allValues.prefix(3))) { mood in
cardButton(for: mood, width: cardWidth)
}
.buttonStyle(MoodButtonStyle())
.position(position)
.accessibilityLabel(mood.strValue)
.accessibilityHint(String(localized: "Select this mood"))
}
// Bottom row: Bad, Horrible - centered between top row items
HStack(spacing: spacing) {
ForEach(Array(Mood.allValues.suffix(2))) { mood in
cardButton(for: mood, width: cardWidth)
}
}
.padding(.leading, bottomOffset)
.padding(.trailing, bottomOffset)
}
}
.frame(height: 180)
.frame(height: 190)
.accessibilityElement(children: .contain)
.accessibilityLabel(String(localized: "Mood selection"))
}
private func angleForIndex(_ index: Int, total: Int) -> Double {
// Spread moods across a semi-circle (180 degrees), from left to right
let startAngle = Double.pi // 180 degrees (left)
let endAngle = 0.0 // 0 degrees (right)
let step = (startAngle - endAngle) / Double(total - 1)
return startAngle - (step * Double(index))
}
private func positionForAngle(_ angle: Double, radius: CGFloat, center: CGPoint) -> CGPoint {
CGPoint(
x: center.x + radius * CGFloat(cos(angle)),
y: center.y - radius * CGFloat(sin(angle))
)
private func cardButton(for mood: Mood, width: CGFloat) -> some View {
Button(action: { onMoodSelected(mood) }) {
mood.icon
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 40, height: 40)
.foregroundColor(moodTint.color(forMood: mood))
.frame(width: width)
.padding(.vertical, 20)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(moodTint.color(forMood: mood).opacity(0.15))
)
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(moodTint.color(forMood: mood).opacity(0.3), lineWidth: 1)
)
}
.buttonStyle(CardButtonStyle())
.accessibilityLabel(mood.strValue)
.accessibilityHint(String(localized: "Select this mood"))
}
}
// MARK: - Layout 4: Stacked Full-width
// MARK: - Layout 3: Stacked Full-width
struct StackedVotingView: View {
let moodTint: MoodTints
let onMoodSelected: (Mood) -> Void

View File

@@ -101,11 +101,6 @@ struct CustomizeContentView: View {
SettingsSection(title: "Notifications") {
PersonalityPackPickerCompact()
}
// FILTERS
SettingsSection(title: "Day Filter") {
DayFilterPickerCompact()
}
}
.padding(.horizontal, 16)
.padding(.bottom, 32)
@@ -175,11 +170,6 @@ struct CustomizeView: View {
SettingsSection(title: "Notifications") {
PersonalityPackPickerCompact()
}
// FILTERS
SettingsSection(title: "Day Filter") {
DayFilterPickerCompact()
}
}
.padding(.horizontal, 16)
.padding(.bottom, 32)
@@ -406,14 +396,6 @@ struct VotingLayoutPickerCompact: View {
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())], spacing: 4) {
ForEach(0..<6, id: \.self) { _ in RoundedRectangle(cornerRadius: 3).frame(width: 10, height: 12) }
}
case .radial:
ZStack {
ForEach(0..<5, id: \.self) { index in
Circle()
.frame(width: 7, height: 7)
.offset(radialOffset(index: index, total: 5, radius: 15))
}
}
case .stacked:
VStack(spacing: 4) {
ForEach(0..<4, id: \.self) { _ in RoundedRectangle(cornerRadius: 2).frame(width: 32, height: 7) }
@@ -486,11 +468,6 @@ struct VotingLayoutPickerCompact: View {
}
}
private func radialOffset(index: Int, total: Int, radius: CGFloat) -> CGSize {
let angle = Double.pi - (Double.pi * Double(index) / Double(total - 1))
return CGSize(width: radius * CGFloat(cos(angle)), height: -radius * CGFloat(sin(angle)) + 4)
}
private func orbitOffset(index: Int, total: Int, radius: CGFloat) -> CGSize {
let startAngle = -Double.pi / 2
let angleStep = (2 * Double.pi) / Double(total)
@@ -627,59 +604,6 @@ struct PersonalityPackPickerCompact: View {
}
}
// MARK: - Day Filter Picker
struct DayFilterPickerCompact: View {
@StateObject private var filteredDays = DaysFilterClass.shared
@AppStorage(UserDefaultsStore.Keys.theme.rawValue, store: GroupUserDefaults.groupDefaults) private var theme: Theme = .system
@Environment(\.colorScheme) private var colorScheme
let weekdays = [(Calendar.current.shortWeekdaySymbols[0], 1),
(Calendar.current.shortWeekdaySymbols[1], 2),
(Calendar.current.shortWeekdaySymbols[2], 3),
(Calendar.current.shortWeekdaySymbols[3], 4),
(Calendar.current.shortWeekdaySymbols[4], 5),
(Calendar.current.shortWeekdaySymbols[5], 6),
(Calendar.current.shortWeekdaySymbols[6], 7)]
var body: some View {
VStack(spacing: 14) {
HStack(spacing: 8) {
ForEach(weekdays.indices, id: \.self) { dayIdx in
let day = String(weekdays[dayIdx].0)
let value = weekdays[dayIdx].1
let isActive = filteredDays.currentFilters.contains(value)
Button(action: {
if isActive {
filteredDays.removeFilter(filter: value)
} else {
filteredDays.addFilter(newFilter: value)
}
let impactMed = UIImpactFeedbackGenerator(style: .medium)
impactMed.impactOccurred()
}) {
Text(day.prefix(2).uppercased())
.font(.caption.weight(.semibold))
.foregroundColor(isActive ? .white : theme.currentTheme.labelColor.opacity(0.5))
.frame(maxWidth: .infinity)
.frame(height: 40)
.background(
RoundedRectangle(cornerRadius: 10)
.fill(isActive ? Color.accentColor : (colorScheme == .dark ? Color(.systemGray5) : .white))
)
}
.buttonStyle(.plain)
}
}
Text(String(localized: "day_picker_view_text"))
.font(.caption)
.foregroundColor(theme.currentTheme.labelColor.opacity(0.5))
.multilineTextAlignment(.center)
}
}
}
// MARK: - Subscription Banner
struct SubscriptionBannerView: View {
@Binding var showSubscriptionStore: Bool
@@ -787,7 +711,7 @@ struct DayViewStylePickerCompact: View {
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 10) {
ForEach(DayViewStyle.allCases, id: \.rawValue) { style in
ForEach(DayViewStyle.availableCases, id: \.rawValue) { style in
Button(action: {
if UIAccessibility.isReduceMotionEnabled {
dayViewStyle = style

View File

@@ -90,14 +90,6 @@ struct VotingLayoutPickerView: View {
.frame(width: 10, height: 12)
}
}
case .radial:
ZStack {
ForEach(0..<5) { index in
Circle()
.frame(width: 6, height: 6)
.offset(radialOffset(index: index, total: 5, radius: 16))
}
}
case .stacked:
VStack(spacing: 3) {
ForEach(0..<4) { _ in
@@ -179,14 +171,6 @@ struct VotingLayoutPickerView: View {
}
}
private func radialOffset(index: Int, total: Int, radius: CGFloat) -> CGSize {
let angle = Double.pi - (Double.pi * Double(index) / Double(total - 1))
return CGSize(
width: radius * CGFloat(cos(angle)),
height: -radius * CGFloat(sin(angle)) + 4
)
}
private func orbitOffset(index: Int, total: Int, radius: CGFloat) -> CGSize {
// Start from top (-π/2) and go clockwise
let startAngle = -Double.pi / 2

View File

@@ -21,6 +21,8 @@ struct SettingsContentView: View {
@State private var showTrialDatePicker = false
@State private var isExportingWidgets = false
@State private var widgetExportPath: URL?
@State private var isDeletingHealthKitData = false
@State private var healthKitDeleteResult: String?
@StateObject private var healthService = HealthService.shared
@AppStorage(UserDefaultsStore.Keys.firstLaunchDate.rawValue, store: GroupUserDefaults.groupDefaults)
@@ -62,6 +64,7 @@ struct SettingsContentView: View {
tipsPreviewButton
testNotificationsButton
exportWidgetsButton
deleteHealthKitDataButton
clearDataButton
#endif
@@ -475,6 +478,58 @@ struct SettingsContentView: View {
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
}
private var deleteHealthKitDataButton: some View {
ZStack {
theme.currentTheme.secondaryBGColor
Button {
isDeletingHealthKitData = true
healthKitDeleteResult = nil
Task {
do {
let count = try await HealthKitManager.shared.deleteAllMoods()
healthKitDeleteResult = "✓ Deleted \(count) records"
} catch {
healthKitDeleteResult = "✗ Error: \(error.localizedDescription)"
}
isDeletingHealthKitData = false
}
} label: {
HStack(spacing: 12) {
if isDeletingHealthKitData {
ProgressView()
.frame(width: 32)
} else {
Image(systemName: "heart.slash.fill")
.font(.title2)
.foregroundColor(.red)
.frame(width: 32)
}
VStack(alignment: .leading, spacing: 2) {
Text("Delete HealthKit Data")
.foregroundColor(textColor)
if let result = healthKitDeleteResult {
Text(result)
.font(.caption)
.foregroundColor(result.contains("") ? .green : .red)
} else {
Text("Remove all State of Mind records")
.font(.caption)
.foregroundStyle(.secondary)
}
}
Spacer()
}
.padding()
}
.disabled(isDeletingHealthKitData)
}
.fixedSize(horizontal: false, vertical: true)
.cornerRadius(Constants.viewsCornerRaidus, corners: [.topLeft, .topRight, .bottomLeft, .bottomRight])
}
private var clearDataButton: some View {
ZStack {
theme.currentTheme.secondaryBGColor