diff --git a/APP_STORE_CONNECT.md b/APP_STORE_CONNECT.md new file mode 100644 index 0000000..7304c9e --- /dev/null +++ b/APP_STORE_CONNECT.md @@ -0,0 +1,38 @@ +# Rituals App Store Connect Copy + +## Promotional Text (max 170) +Build better habits with guided ritual arcs, daily check-ins, and clear progress insights, all in a private, offline-first tracker designed for focus. + +Character count: 150 / 170 + +## Keywords (max 100) +habit tracker,rituals,daily routine,streaks,goals,productivity,mindfulness,offline,privacy + +Character count: 90 / 100 + +## Description (max 4000) +Rituals helps you build consistent habits through focused, time-bound ritual arcs instead of endless streak pressure. + +Create rituals that match your life: +- Set a custom duration from 7 to 365 days +- Organize habits by morning, midday, afternoon, evening, night, or anytime +- Personalize rituals with themes, icons, categories, and notes + +Stay on track every day: +- Check off habits with fast tap-to-complete actions +- See progress rings and completion summaries at a glance +- Keep momentum with milestone achievements and streak tracking + +Understand your patterns: +- Review daily history in a calendar view with completion detail +- Track trends with weekly averages, active-day stats, and total check-ins +- See your best-performing ritual and habit-level completion rates + +Designed for privacy and reliability: +- Offline-first by default, with no paid backend dependencies +- Local-first data storage with optional iCloud settings sync +- Clean, calm interface built for daily focus + +Whether you are starting a morning routine, improving productivity, or building mindfulness habits, Rituals gives you a clear structure you can actually sustain. + +Character count: 1145 / 4000 diff --git a/Andromida/Assets.xcassets/AccentSoft.colorset/Contents.json b/Andromida/Assets.xcassets/AccentSoft.colorset/Contents.json index f3ed09c..8020074 100644 --- a/Andromida/Assets.xcassets/AccentSoft.colorset/Contents.json +++ b/Andromida/Assets.xcassets/AccentSoft.colorset/Contents.json @@ -4,10 +4,10 @@ "color" : { "color-space" : "srgb", "components" : { - "alpha" : "1.0", - "blue" : "0.81", - "green" : "0.85", - "red" : "0.97" + "alpha" : "1.000", + "blue" : "0xCE", + "green" : "0xD8", + "red" : "0xF7" } }, "idiom" : "universal" @@ -22,10 +22,10 @@ "color" : { "color-space" : "srgb", "components" : { - "alpha" : "1.0", - "blue" : "0.15", - "green" : "0.17", - "red" : "0.23" + "alpha" : "1.000", + "blue" : "0x26", + "green" : "0x2B", + "red" : "0x3A" } }, "idiom" : "universal" diff --git a/Andromida/Assets.xcassets/Background.colorset/Contents.json b/Andromida/Assets.xcassets/Background.colorset/Contents.json index ab3fcb9..17ac278 100644 --- a/Andromida/Assets.xcassets/Background.colorset/Contents.json +++ b/Andromida/Assets.xcassets/Background.colorset/Contents.json @@ -4,10 +4,10 @@ "color" : { "color-space" : "srgb", "components" : { - "alpha" : "1.0", - "blue" : "0.92", - "green" : "0.95", - "red" : "0.97" + "alpha" : "1.000", + "blue" : "0xEA", + "green" : "0xF2", + "red" : "0xF7" } }, "idiom" : "universal" @@ -22,10 +22,10 @@ "color" : { "color-space" : "srgb", "components" : { - "alpha" : "1.0", - "blue" : "0.08", - "green" : "0.09", - "red" : "0.12" + "alpha" : "1.000", + "blue" : "0.080", + "green" : "0.090", + "red" : "0.120" } }, "idiom" : "universal" diff --git a/Andromida/Assets.xcassets/BackgroundTertiary.colorset/Contents.json b/Andromida/Assets.xcassets/BackgroundTertiary.colorset/Contents.json index 48f01c1..3c45a04 100644 --- a/Andromida/Assets.xcassets/BackgroundTertiary.colorset/Contents.json +++ b/Andromida/Assets.xcassets/BackgroundTertiary.colorset/Contents.json @@ -4,10 +4,10 @@ "color" : { "color-space" : "srgb", "components" : { - "alpha" : "1.0", - "blue" : "0.80", - "green" : "0.85", - "red" : "0.89" + "alpha" : "1.000", + "blue" : "0xCC", + "green" : "0xD8", + "red" : "0xE2" } }, "idiom" : "universal" @@ -22,10 +22,10 @@ "color" : { "color-space" : "srgb", "components" : { - "alpha" : "1.0", - "blue" : "0.12", - "green" : "0.14", - "red" : "0.18" + "alpha" : "1.000", + "blue" : "0.120", + "green" : "0.140", + "red" : "0.180" } }, "idiom" : "universal" diff --git a/AndromidaUITests/AndromidaUITests.swift b/AndromidaUITests/AndromidaUITests.swift index ec187db..2c4912f 100644 --- a/AndromidaUITests/AndromidaUITests.swift +++ b/AndromidaUITests/AndromidaUITests.swift @@ -152,6 +152,39 @@ final class AndromidaUITests: XCTestCase { } } + @MainActor + func testAppStorePortraitScreenshots() throws { + let app = makeApp(resetDefaults: true, hasCompletedSetupWizard: true) + app.launch() + + XCUIDevice.shared.orientation = .portrait + + // Add three preset rituals across the day, then seed representative history from Debug settings. + addThreePresetRitualsAcrossDay(app: app) + preloadSixMonthsDemoDataFromDebug(app: app) + ensureDataRichStateForScreenshots(app: app) + + openTab(app: app, label: "Today", index: 0) + XCTAssertFalse(app.staticTexts["No Active Rituals"].exists, "Today should show active rituals for App Store screenshots.") + XCTAssertFalse(app.staticTexts["All caught up"].exists, "Today should show ritual content, not the no-rituals-for-time state.") + saveScreenshot(app: app, named: "01-today-focus-portrait") + + openTab(app: app, label: "Rituals", index: 1) + XCTAssertFalse(app.staticTexts["No Active Rituals"].exists, "Rituals should show active ritual cards for App Store screenshots.") + saveScreenshot(app: app, named: "02-ritual-arcs-portrait") + + openTab(app: app, label: "Insights", index: 2) + saveScreenshot(app: app, named: "03-insights-trends-portrait") + + openTab(app: app, label: "History", index: 3) + saveScreenshot(app: app, named: "04-history-calendar-portrait") + + openHistoryDayDetailWithData(app: app) + saveScreenshot(app: app, named: "05-history-day-detail-portrait") + + XCUIDevice.shared.orientation = .portrait + } + private func makeApp(resetDefaults: Bool, hasCompletedSetupWizard: Bool) -> XCUIApplication { let app = XCUIApplication() app.launchEnvironment["UITEST_MODE"] = "1" @@ -162,6 +195,225 @@ final class AndromidaUITests: XCTestCase { return app } + private func addThreePresetRitualsAcrossDay(app: XCUIApplication) { + openTab(app: app, label: "Rituals", index: 1) + + let addMenu = app.buttons["rituals.addMenu"] + guard addMenu.waitForExistence(timeout: 8) else { return } + addMenu.tap() + + let browsePresets = app.buttons["rituals.browsePresets"] + guard browsePresets.waitForExistence(timeout: 8) else { return } + browsePresets.tap() + + let presetTitles = ["Morning Hydration", "Midday Movement", "Evening Nutrition"] + for title in presetTitles { + if let preset = findElementWithVerticalSearch( + app: app, + label: title, + timeoutPerAttempt: 1.0, + swipeCount: 6 + ) { + if preset.isHittable { + preset.tap() + } else { + preset.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() + } + + let addButton = app.buttons["Add to My Rituals"] + if addButton.waitForExistence(timeout: 8) { + addButton.tap() + } + tapDoneInNavigationBar(app: app, title: title) + RunLoop.current.run(until: Date().addingTimeInterval(0.3)) + } + } + + tapDoneInNavigationBar(app: app, title: "Preset Library") + } + + private func preloadSixMonthsDemoDataFromDebug(app: XCUIApplication) { + openTab(app: app, label: "Settings", index: 4) + + // Move into the lower Debug section before tapping preload. + _ = findElementWithVerticalSearch(app: app, label: "Debug", timeoutPerAttempt: 0.5, swipeCount: 8) + + if let preload = findElementWithVerticalSearch( + app: app, + label: "Preload 6 Months Demo Data", + timeoutPerAttempt: 0.5, + swipeCount: 10 + ) { + if preload.isHittable { + preload.tap() + } else { + preload.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() + } + RunLoop.current.run(until: Date().addingTimeInterval(1.0)) + waitForHistoryDataSeed(app: app) + } + } + + private func waitForHistoryDataSeed(app: XCUIApplication) { + for _ in 0..<6 { + openTab(app: app, label: "History", index: 3) + let nonZeroDay = app.buttons.matching( + NSPredicate(format: "label CONTAINS[c] %@ AND NOT (label CONTAINS[c] %@)", "percent complete", "0 percent complete") + ).firstMatch + if nonZeroDay.waitForExistence(timeout: 1.5) { + return + } + RunLoop.current.run(until: Date().addingTimeInterval(0.5)) + } + } + + + private func ensureDataRichStateForScreenshots(app: XCUIApplication) { + // Try multiple time buckets until Today has content. + let candidateTimes = ["Anytime", "Morning", "Midday", "Afternoon", "Evening", "Night", "Real"] + + for time in candidateTimes { + openTab(app: app, label: "Settings", index: 4) + setSimulatedTimeIfAvailable(app: app, label: time) + + openTab(app: app, label: "Today", index: 0) + RunLoop.current.run(until: Date().addingTimeInterval(0.4)) + + let hasActiveEmptyState = app.staticTexts["No Active Rituals"].exists + let hasTimeEmptyState = app.staticTexts["All caught up"].exists + if !hasActiveEmptyState && !hasTimeEmptyState { + return + } + } + } + + private func setSimulatedTimeIfAvailable(app: XCUIApplication, label: String) { + if let button = firstMatchIfExists(app: app, label: label, timeout: 2) { + if button.isHittable { + button.tap() + } else { + button.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() + } + RunLoop.current.run(until: Date().addingTimeInterval(0.3)) + return + } + + for _ in 0..<4 { + app.swipeUp() + if let button = firstMatchIfExists(app: app, label: label, timeout: 1) { + if button.isHittable { + button.tap() + } else { + button.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() + } + RunLoop.current.run(until: Date().addingTimeInterval(0.3)) + return + } + } + } + + private func firstMatchIfExists(app: XCUIApplication, label: String, timeout: TimeInterval) -> XCUIElement? { + let candidates: [XCUIElement] = [ + app.buttons[label].firstMatch, + app.staticTexts[label].firstMatch, + app.otherElements[label].firstMatch + ] + + let deadline = Date().addingTimeInterval(timeout) + while Date() < deadline { + if let element = candidates.first(where: { $0.exists }) { + return element + } + RunLoop.current.run(until: Date().addingTimeInterval(0.1)) + } + return nil + } + + private func findElementWithVerticalSearch( + app: XCUIApplication, + label: String, + timeoutPerAttempt: TimeInterval, + swipeCount: Int + ) -> XCUIElement? { + if let element = firstMatchIfExists(app: app, label: label, timeout: timeoutPerAttempt) { + return element + } + + for _ in 0.. index { + tabButtons.element(boundBy: index).tap() + return + } + + let fallback = app.buttons[label].firstMatch + if fallback.exists { + fallback.tap() + } + } + + private func saveScreenshot(app: XCUIApplication, named name: String) { + let attachment = XCTAttachment(screenshot: app.screenshot()) + attachment.name = name + attachment.lifetime = .keepAlways + add(attachment) + } + + private func openHistoryDayDetailWithData(app: XCUIApplication) { + openTab(app: app, label: "History", index: 3) + + let nonZeroDay = app.buttons.matching( + NSPredicate(format: "label CONTAINS[c] %@ AND NOT (label CONTAINS[c] %@)", "percent complete", "0 percent complete") + ).firstMatch + + if nonZeroDay.waitForExistence(timeout: 8) { + nonZeroDay.tap() + } else { + let anyDay = app.buttons.matching(NSPredicate(format: "label CONTAINS[c] %@", "percent complete")).firstMatch + if anyDay.waitForExistence(timeout: 8) { + anyDay.tap() + } + } + + _ = app.buttons["Done"].waitForExistence(timeout: 8) + } + private func tapFirstAvailableElementIfPresent( app: XCUIApplication, identifiers: [String], diff --git a/Screenshots/iPad-13/01-today-focus-portrait.png b/Screenshots/iPad-13/01-today-focus-portrait.png new file mode 100644 index 0000000..2ebc1dd Binary files /dev/null and b/Screenshots/iPad-13/01-today-focus-portrait.png differ diff --git a/Screenshots/iPad-13/02-ritual-arcs-portrait.png b/Screenshots/iPad-13/02-ritual-arcs-portrait.png new file mode 100644 index 0000000..f92124b Binary files /dev/null and b/Screenshots/iPad-13/02-ritual-arcs-portrait.png differ diff --git a/Screenshots/iPad-13/03-insights-trends-portrait.png b/Screenshots/iPad-13/03-insights-trends-portrait.png new file mode 100644 index 0000000..cbdd5c9 Binary files /dev/null and b/Screenshots/iPad-13/03-insights-trends-portrait.png differ diff --git a/Screenshots/iPad-13/04-history-calendar-portrait.png b/Screenshots/iPad-13/04-history-calendar-portrait.png new file mode 100644 index 0000000..f2a8ced Binary files /dev/null and b/Screenshots/iPad-13/04-history-calendar-portrait.png differ diff --git a/Screenshots/iPad-13/05-history-day-detail-portrait.png b/Screenshots/iPad-13/05-history-day-detail-portrait.png new file mode 100644 index 0000000..1f51326 Binary files /dev/null and b/Screenshots/iPad-13/05-history-day-detail-portrait.png differ diff --git a/Screenshots/iPhone-6.3/01-today-focus-portrait.png b/Screenshots/iPhone-6.3/01-today-focus-portrait.png new file mode 100644 index 0000000..23767f9 Binary files /dev/null and b/Screenshots/iPhone-6.3/01-today-focus-portrait.png differ diff --git a/Screenshots/iPhone-6.3/02-ritual-arcs-portrait.png b/Screenshots/iPhone-6.3/02-ritual-arcs-portrait.png new file mode 100644 index 0000000..ad14f72 Binary files /dev/null and b/Screenshots/iPhone-6.3/02-ritual-arcs-portrait.png differ diff --git a/Screenshots/iPhone-6.3/03-insights-trends-portrait.png b/Screenshots/iPhone-6.3/03-insights-trends-portrait.png new file mode 100644 index 0000000..cda767d Binary files /dev/null and b/Screenshots/iPhone-6.3/03-insights-trends-portrait.png differ diff --git a/Screenshots/iPhone-6.3/04-history-calendar-portrait.png b/Screenshots/iPhone-6.3/04-history-calendar-portrait.png new file mode 100644 index 0000000..1b55ee6 Binary files /dev/null and b/Screenshots/iPhone-6.3/04-history-calendar-portrait.png differ diff --git a/Screenshots/iPhone-6.3/05-history-day-detail-portrait.png b/Screenshots/iPhone-6.3/05-history-day-detail-portrait.png new file mode 100644 index 0000000..0104cb8 Binary files /dev/null and b/Screenshots/iPhone-6.3/05-history-day-detail-portrait.png differ diff --git a/Screenshots/iPhone-6.5-fallback/01-today-focus-portrait.png b/Screenshots/iPhone-6.5-fallback/01-today-focus-portrait.png new file mode 100644 index 0000000..9a63172 Binary files /dev/null and b/Screenshots/iPhone-6.5-fallback/01-today-focus-portrait.png differ diff --git a/Screenshots/iPhone-6.5-fallback/02-ritual-arcs-portrait.png b/Screenshots/iPhone-6.5-fallback/02-ritual-arcs-portrait.png new file mode 100644 index 0000000..281aabf Binary files /dev/null and b/Screenshots/iPhone-6.5-fallback/02-ritual-arcs-portrait.png differ diff --git a/Screenshots/iPhone-6.5-fallback/03-insights-trends-portrait.png b/Screenshots/iPhone-6.5-fallback/03-insights-trends-portrait.png new file mode 100644 index 0000000..4dc174d Binary files /dev/null and b/Screenshots/iPhone-6.5-fallback/03-insights-trends-portrait.png differ diff --git a/Screenshots/iPhone-6.5-fallback/04-history-calendar-portrait.png b/Screenshots/iPhone-6.5-fallback/04-history-calendar-portrait.png new file mode 100644 index 0000000..7ab69a5 Binary files /dev/null and b/Screenshots/iPhone-6.5-fallback/04-history-calendar-portrait.png differ diff --git a/Screenshots/iPhone-6.5-fallback/05-history-day-detail-portrait.png b/Screenshots/iPhone-6.5-fallback/05-history-day-detail-portrait.png new file mode 100644 index 0000000..3a199c9 Binary files /dev/null and b/Screenshots/iPhone-6.5-fallback/05-history-day-detail-portrait.png differ diff --git a/Screenshots/iPhone-6.9/01-today-focus-portrait.png b/Screenshots/iPhone-6.9/01-today-focus-portrait.png new file mode 100644 index 0000000..13795f2 Binary files /dev/null and b/Screenshots/iPhone-6.9/01-today-focus-portrait.png differ diff --git a/Screenshots/iPhone-6.9/02-ritual-arcs-portrait.png b/Screenshots/iPhone-6.9/02-ritual-arcs-portrait.png new file mode 100644 index 0000000..03d30f8 Binary files /dev/null and b/Screenshots/iPhone-6.9/02-ritual-arcs-portrait.png differ diff --git a/Screenshots/iPhone-6.9/03-insights-trends-portrait.png b/Screenshots/iPhone-6.9/03-insights-trends-portrait.png new file mode 100644 index 0000000..fd5fd58 Binary files /dev/null and b/Screenshots/iPhone-6.9/03-insights-trends-portrait.png differ diff --git a/Screenshots/iPhone-6.9/04-history-calendar-portrait.png b/Screenshots/iPhone-6.9/04-history-calendar-portrait.png new file mode 100644 index 0000000..43bb142 Binary files /dev/null and b/Screenshots/iPhone-6.9/04-history-calendar-portrait.png differ diff --git a/Screenshots/iPhone-6.9/05-history-day-detail-portrait.png b/Screenshots/iPhone-6.9/05-history-day-detail-portrait.png new file mode 100644 index 0000000..ea5931f Binary files /dev/null and b/Screenshots/iPhone-6.9/05-history-day-detail-portrait.png differ