1.0.5: Eating out slots, rule violations panel, Crashlytics, search bar fix

- Eating out: tap any empty slot → "Mark as eating out"; shows teal indicator,
  skipped by auto-assign, counts as complete, tap again to unmark
- Rule violations panel: access via wand long-press context menu when conflicts exist;
  shows all isRuleOverridden slots with Fix (clear) or Ignore (acknowledge) actions
- Firebase Crashlytics integrated: CrashlyticsService + dSYM upload build phase,
  isPremium property tracked per session
- PremiumSyncService: extracted premium state machine, StoreKit Transaction.updates
  listener, isPremium no longer synced via iCloud to avoid stale state
- StoreManager: analytics on purchase/restore, bundle ID fallback for product ID lookup
- Search bar contrast bug fixed: TextField now has explicit foreground color for dark mode
- WelcomeStepView: redesigned onboarding welcome screen with week preview
- Version bump: 1.0.5 build 22

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
alexandrev-tibco
2026-04-27 09:48:20 +02:00
parent 6c7e12b41f
commit 110807d239
111 changed files with 3116 additions and 154 deletions
+86 -41
View File
@@ -151,17 +151,29 @@ struct WeekPlanShareView: View {
private var verticalBackground: some View {
ZStack {
Color(hex: "#FFFDF8")
LinearGradient(
colors: [Color(hex: "#FFF7F4"), Color(hex: "#FFF3F7"), Color(hex: "#F6FFF8")],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
Ellipse()
.fill(Color.mealMoodMint.opacity(0.28))
.frame(width: 1100, height: 900)
.offset(x: 800, y: -1200)
Circle()
.fill(Color(hex: "#FFD0DF").opacity(0.6))
.frame(width: 1100, height: 1100)
.blur(radius: 90)
.offset(x: 950, y: -1100)
Ellipse()
.fill(Color.mealMoodCoral.opacity(0.2))
.frame(width: 900, height: 680)
.offset(x: -800, y: 1200)
Circle()
.fill(Color(hex: "#C0EAD8").opacity(0.55))
.frame(width: 1000, height: 1000)
.blur(radius: 80)
.offset(x: -900, y: 1300)
Circle()
.fill(Color(hex: "#FFE0C0").opacity(0.4))
.frame(width: 700, height: 700)
.blur(radius: 100)
.offset(x: 100, y: 200)
}
}
@@ -336,58 +348,91 @@ struct WeekPlanShareView: View {
private var verticalLayout: some View {
ZStack {
RoundedRectangle(cornerRadius: 30)
.fill(Color.white.opacity(0.95))
RoundedRectangle(cornerRadius: 32)
.fill(Color.white.opacity(0.88))
.overlay(
RoundedRectangle(cornerRadius: 30)
.stroke(Color.mealMoodMint.opacity(0.45), lineWidth: 2)
RoundedRectangle(cornerRadius: 32)
.stroke(
LinearGradient(
colors: [Color(hex: "#F0C0CC").opacity(0.7), Color(hex: "#B8E8D4").opacity(0.7)],
startPoint: .topLeading,
endPoint: .bottomTrailing
),
lineWidth: 3
)
)
VStack(spacing: 16) {
VStack(spacing: 20) {
ForEach(Array(dayRange), id: \.self) { day in
VStack(alignment: .leading, spacing: 10) {
Text(dayHeaderTitle(for: day).uppercased(with: locale))
.font(.custom("Didot", size: 50))
.foregroundColor(.mealMoodTextPrimary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 20)
.padding(.vertical, 10)
.background(dayHeaderBackground(day: day))
.clipShape(RoundedRectangle(cornerRadius: 12))
HStack(spacing: 0) {
VStack(spacing: 12) {
Text(dayLongTitle(for: day).uppercased(with: locale))
.font(.custom("Didot", size: 68))
.foregroundColor(Color(hex: "#2C3E35"))
.multilineTextAlignment(.center)
.lineLimit(1)
.minimumScaleFactor(0.38)
.frame(maxWidth: .infinity)
VStack(alignment: .leading, spacing: 8) {
Text(dayNumberOnly(for: day))
.font(.system(size: 96, weight: .bold, design: .rounded))
.foregroundColor(Color(hex: "#2C3E35").opacity(0.6))
}
.frame(width: 420)
.frame(maxHeight: .infinity)
.padding(.vertical, 28)
.background(dayHeaderBackground(day: day))
VStack(alignment: .leading, spacing: 24) {
ForEach(mealTypes, id: \.self) { mealType in
HStack(alignment: .top, spacing: 10) {
Text("\(mealTypeLabel(mealType)):")
.font(.system(size: 34, weight: .bold, design: .serif))
.foregroundColor(Color(hex: "#385549"))
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 14) {
Image(systemName: mealType.icon)
.font(.system(size: 42, weight: .medium))
.foregroundColor(mealType == .lunch ? Color(hex: "#C85E2A") : Color(hex: "#27604A"))
.frame(width: 56)
Text(mealTypeLabel(mealType).uppercased(with: locale))
.font(.system(size: 42, weight: .bold, design: .serif))
.foregroundColor(mealType == .lunch ? Color(hex: "#C85E2A") : Color(hex: "#27604A"))
}
Text(dishName(day: day, mealType: mealType))
.font(.system(size: 35, weight: .regular, design: .serif))
.font(.system(size: 62, weight: .medium, design: .serif))
.foregroundColor(.mealMoodTextPrimary)
.lineLimit(2)
.minimumScaleFactor(0.7)
.minimumScaleFactor(0.5)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.leading, 70)
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 6)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.padding(.horizontal, 40)
.padding(.vertical, 28)
.background(Color(hex: "#FFFCF8"))
}
.padding(.horizontal, 16)
.padding(.vertical, 14)
.background(Color.white)
.frame(maxHeight: .infinity)
.clipShape(RoundedRectangle(cornerRadius: 20))
.overlay(
RoundedRectangle(cornerRadius: 16)
.stroke(Color(hex: "#EEE4DE"), lineWidth: 1)
RoundedRectangle(cornerRadius: 20)
.stroke(Color(hex: "#EAD8D0"), lineWidth: 1.5)
)
.clipShape(RoundedRectangle(cornerRadius: 16))
.shadow(color: Color.black.opacity(0.04), radius: 10, x: 0, y: 4)
}
}
.padding(.horizontal, 22)
.padding(.vertical, 20)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding(.horizontal, 28)
.padding(.vertical, 26)
}
}
private func dayNumberOnly(for day: Int) -> String {
let dayDate = plan.weekStartDate.addingDays(day)
let formatter = DateFormatter()
formatter.locale = locale
formatter.dateFormat = "d"
return formatter.string(from: dayDate)
}
private var footerBlock: some View {
HStack(alignment: .center, spacing: 20) {
VStack(alignment: .leading, spacing: 6) {