Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
@ -72,7 +72,17 @@ struct AndromidaApp: App {
|
|||||||
let settings = SettingsStore()
|
let settings = SettingsStore()
|
||||||
_settingsStore = State(initialValue: settings)
|
_settingsStore = State(initialValue: settings)
|
||||||
_categoryStore = State(initialValue: CategoryStore())
|
_categoryStore = State(initialValue: CategoryStore())
|
||||||
_store = State(initialValue: RitualStore(modelContext: container.mainContext, seedService: RitualSeedService(), settingsStore: settings))
|
|
||||||
|
let ritualStore = RitualStore(modelContext: container.mainContext, seedService: RitualSeedService(), settingsStore: settings)
|
||||||
|
if isUITesting, environment["UITEST_SEED_THREE_PRESETS"] == "1" {
|
||||||
|
ritualStore.createRitualFromPreset(RitualPresetLibrary.healthPresets[0]) // morning
|
||||||
|
ritualStore.createRitualFromPreset(RitualPresetLibrary.healthPresets[1]) // midday
|
||||||
|
ritualStore.createRitualFromPreset(RitualPresetLibrary.healthPresets[3]) // evening
|
||||||
|
}
|
||||||
|
if isUITesting, environment["UITEST_PRELOAD_DEMO_DATA"] == "1" {
|
||||||
|
ritualStore.preloadDemoData()
|
||||||
|
}
|
||||||
|
_store = State(initialValue: ritualStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
@ -82,12 +92,13 @@ struct AndromidaApp: App {
|
|||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
|
|
||||||
if hasCompletedSetupWizard {
|
if hasCompletedSetupWizard {
|
||||||
|
let uiTestInitialTab = uiTestRequestedInitialTab()
|
||||||
// Main app - start on Rituals tab if just completed wizard
|
// Main app - start on Rituals tab if just completed wizard
|
||||||
RootView(
|
RootView(
|
||||||
store: store,
|
store: store,
|
||||||
settingsStore: settingsStore,
|
settingsStore: settingsStore,
|
||||||
categoryStore: categoryStore,
|
categoryStore: categoryStore,
|
||||||
initialTab: justCompletedWizard ? .rituals : .today
|
initialTab: uiTestInitialTab ?? (justCompletedWizard ? .rituals : .today)
|
||||||
)
|
)
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
} else {
|
} else {
|
||||||
@ -111,4 +122,16 @@ struct AndromidaApp: App {
|
|||||||
.preferredColorScheme(settingsStore.theme.colorScheme)
|
.preferredColorScheme(settingsStore.theme.colorScheme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func uiTestRequestedInitialTab() -> RootView.RootTab? {
|
||||||
|
guard ProcessInfo.processInfo.environment["UITEST_MODE"] == "1" else { return nil }
|
||||||
|
switch ProcessInfo.processInfo.environment["UITEST_INITIAL_TAB"]?.lowercased() {
|
||||||
|
case "today": return .today
|
||||||
|
case "rituals": return .rituals
|
||||||
|
case "insights": return .insights
|
||||||
|
case "history": return .history
|
||||||
|
case "settings": return .settings
|
||||||
|
default: return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -140,6 +140,7 @@ struct SettingsView: View {
|
|||||||
) {
|
) {
|
||||||
ritualStore.preloadDemoData()
|
ritualStore.preloadDemoData()
|
||||||
}
|
}
|
||||||
|
.accessibilityIdentifier("settings.debug.preloadDemoData")
|
||||||
|
|
||||||
SettingsRow(
|
SettingsRow(
|
||||||
systemImage: "checkmark.circle.badge.xmark",
|
systemImage: "checkmark.circle.badge.xmark",
|
||||||
|
|||||||
@ -8,6 +8,28 @@
|
|||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
final class AndromidaUITests: XCTestCase {
|
final class AndromidaUITests: XCTestCase {
|
||||||
|
private let doneLabels = ["Done", "Terminé", "Listo"]
|
||||||
|
private let settingsTabLabels = ["Settings", "Réglages", "Paramètres", "Ajustes"]
|
||||||
|
private let tabLabelMap: [String: [String]] = [
|
||||||
|
"Today": ["Today", "Aujourd'hui", "Hoy"],
|
||||||
|
"Rituals": ["Rituals", "Rituels", "Rituales"],
|
||||||
|
"Insights": ["Insights", "Aperçus", "Ideas"],
|
||||||
|
"History": ["History", "Historique", "Historial"],
|
||||||
|
"Settings": ["Settings", "Réglages", "Paramètres", "Ajustes"]
|
||||||
|
]
|
||||||
|
private let tabSymbolMap: [String: String] = [
|
||||||
|
"Today": "sun.max.fill",
|
||||||
|
"Rituals": "sparkles",
|
||||||
|
"Insights": "chart.bar.fill",
|
||||||
|
"History": "calendar",
|
||||||
|
"Settings": "gearshape.fill"
|
||||||
|
]
|
||||||
|
private let preloadLabels = [
|
||||||
|
"Preload 6 Months Demo Data",
|
||||||
|
"Précharge 6 mois Données de démonstration",
|
||||||
|
"Precarga 6 Meses Datos de demostración"
|
||||||
|
]
|
||||||
|
private let preloadPrefixLabels = ["Preload 6", "Précharge 6", "Precarga 6"]
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
override func setUpWithError() throws {
|
||||||
continueAfterFailure = false
|
continueAfterFailure = false
|
||||||
@ -155,14 +177,14 @@ final class AndromidaUITests: XCTestCase {
|
|||||||
@MainActor
|
@MainActor
|
||||||
func testAppStorePortraitScreenshots() throws {
|
func testAppStorePortraitScreenshots() throws {
|
||||||
let app = makeApp(resetDefaults: true, hasCompletedSetupWizard: true)
|
let app = makeApp(resetDefaults: true, hasCompletedSetupWizard: true)
|
||||||
|
app.launchEnvironment["UITEST_INITIAL_TAB"] = "settings"
|
||||||
app.launch()
|
app.launch()
|
||||||
|
|
||||||
XCUIDevice.shared.orientation = .portrait
|
XCUIDevice.shared.orientation = .portrait
|
||||||
|
|
||||||
// Add three preset rituals across the day, then seed representative history from Debug settings.
|
// Ensure rituals exist before seeding demo history.
|
||||||
addThreePresetRitualsAcrossDay(app: app)
|
assertRitualsPresentBeforePreload(app: app)
|
||||||
preloadSixMonthsDemoDataFromDebug(app: app)
|
preloadSixMonthsDemoDataFromDebug(app: app)
|
||||||
ensureDataRichStateForScreenshots(app: app)
|
|
||||||
|
|
||||||
openTab(app: app, label: "Today", index: 0)
|
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["No Active Rituals"].exists, "Today should show active rituals for App Store screenshots.")
|
||||||
@ -188,6 +210,8 @@ final class AndromidaUITests: XCTestCase {
|
|||||||
private func makeApp(resetDefaults: Bool, hasCompletedSetupWizard: Bool) -> XCUIApplication {
|
private func makeApp(resetDefaults: Bool, hasCompletedSetupWizard: Bool) -> XCUIApplication {
|
||||||
let app = XCUIApplication()
|
let app = XCUIApplication()
|
||||||
app.launchEnvironment["UITEST_MODE"] = "1"
|
app.launchEnvironment["UITEST_MODE"] = "1"
|
||||||
|
app.launchEnvironment["UITEST_SEED_THREE_PRESETS"] = "1"
|
||||||
|
app.launchEnvironment["UITEST_PRELOAD_DEMO_DATA"] = "1"
|
||||||
if resetDefaults {
|
if resetDefaults {
|
||||||
app.launchEnvironment["UITEST_RESET_USER_DEFAULTS"] = "1"
|
app.launchEnvironment["UITEST_RESET_USER_DEFAULTS"] = "1"
|
||||||
}
|
}
|
||||||
@ -195,54 +219,20 @@ final class AndromidaUITests: XCTestCase {
|
|||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
private func addThreePresetRitualsAcrossDay(app: XCUIApplication) {
|
private func preloadSixMonthsDemoDataFromDebug(app: XCUIApplication) {
|
||||||
openTab(app: app, label: "Rituals", index: 1)
|
// iPad tab/sidebars are highly variable in UI tests on iOS 26;
|
||||||
|
// demo data is already guaranteed via launch environment.
|
||||||
let addMenu = app.buttons["rituals.addMenu"]
|
if app.windows.firstMatch.frame.width >= 1000 {
|
||||||
guard addMenu.waitForExistence(timeout: 8) else { return }
|
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)
|
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(
|
if let preload = findElementWithVerticalSearch(
|
||||||
app: app,
|
app: app,
|
||||||
label: "Preload 6 Months Demo Data",
|
label: "settings.debug.preloadDemoData",
|
||||||
timeoutPerAttempt: 0.5,
|
timeoutPerAttempt: 0.3,
|
||||||
swipeCount: 10
|
swipeCount: 12
|
||||||
) {
|
) {
|
||||||
if preload.isHittable {
|
if preload.isHittable {
|
||||||
preload.tap()
|
preload.tap()
|
||||||
@ -250,21 +240,64 @@ final class AndromidaUITests: XCTestCase {
|
|||||||
preload.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
|
preload.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
|
||||||
}
|
}
|
||||||
RunLoop.current.run(until: Date().addingTimeInterval(1.0))
|
RunLoop.current.run(until: Date().addingTimeInterval(1.0))
|
||||||
waitForHistoryDataSeed(app: app)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tapPreloadByPrefixSearch(app: app) {
|
||||||
|
RunLoop.current.run(until: Date().addingTimeInterval(1.0))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let preload = findAnyElementWithVerticalSearch(
|
||||||
|
app: app,
|
||||||
|
labels: preloadLabels,
|
||||||
|
timeoutPerAttempt: 0.5,
|
||||||
|
swipeCount: 10
|
||||||
|
)
|
||||||
|
|
||||||
|
if let preload {
|
||||||
|
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))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func waitForHistoryDataSeed(app: XCUIApplication) {
|
private func tapPreloadByPrefixSearch(app: XCUIApplication) -> Bool {
|
||||||
for _ in 0..<6 {
|
func tapMatchingElement() -> Bool {
|
||||||
openTab(app: app, label: "History", index: 3)
|
let elements = app.descendants(matching: .any).allElementsBoundByIndex
|
||||||
let nonZeroDay = app.buttons.matching(
|
for element in elements where element.exists {
|
||||||
NSPredicate(format: "label CONTAINS[c] %@ AND NOT (label CONTAINS[c] %@)", "percent complete", "0 percent complete")
|
let label = element.label
|
||||||
).firstMatch
|
if preloadPrefixLabels.contains(where: { label.contains($0) }) {
|
||||||
if nonZeroDay.waitForExistence(timeout: 1.5) {
|
if element.isHittable {
|
||||||
return
|
element.tap()
|
||||||
|
} else {
|
||||||
|
element.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
RunLoop.current.run(until: Date().addingTimeInterval(0.5))
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tapMatchingElement() { return true }
|
||||||
|
for _ in 0..<12 {
|
||||||
|
app.swipeUp()
|
||||||
|
if tapMatchingElement() { return true }
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func assertRitualsPresentBeforePreload(app: XCUIApplication) {
|
||||||
|
openTab(app: app, label: "Rituals", index: 1)
|
||||||
|
XCTAssertFalse(
|
||||||
|
app.staticTexts["No Active Rituals"].exists,
|
||||||
|
"Expected seeded rituals before preload."
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -356,35 +389,128 @@ final class AndromidaUITests: XCTestCase {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
private func tapDoneInNavigationBar(app: XCUIApplication, title: String) {
|
private func findAnyElementWithVerticalSearch(
|
||||||
let navDone = app.navigationBars[title].buttons["Done"].firstMatch
|
app: XCUIApplication,
|
||||||
if navDone.waitForExistence(timeout: 6) {
|
labels: [String],
|
||||||
navDone.tap()
|
timeoutPerAttempt: TimeInterval,
|
||||||
return
|
swipeCount: Int
|
||||||
|
) -> XCUIElement? {
|
||||||
|
for label in labels {
|
||||||
|
if let element = firstMatchIfExists(app: app, label: label, timeout: timeoutPerAttempt) {
|
||||||
|
return element
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let anyDone = app.buttons["Done"].firstMatch
|
for _ in 0..<swipeCount {
|
||||||
if anyDone.waitForExistence(timeout: 4) {
|
app.swipeUp()
|
||||||
|
for label in labels {
|
||||||
|
if let element = firstMatchIfExists(app: app, label: label, timeout: timeoutPerAttempt) {
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0..<swipeCount {
|
||||||
|
app.swipeDown()
|
||||||
|
for label in labels {
|
||||||
|
if let element = firstMatchIfExists(app: app, label: label, timeout: timeoutPerAttempt) {
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func tapDoneInNavigationBar(app: XCUIApplication, title: String) {
|
||||||
|
for navTitle in [title] {
|
||||||
|
for done in doneLabels {
|
||||||
|
let navDone = app.navigationBars[navTitle].buttons[done].firstMatch
|
||||||
|
if navDone.waitForExistence(timeout: 2) {
|
||||||
|
navDone.tap()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let anyDone = firstExistingDoneButton(app: app, timeout: 4) {
|
||||||
anyDone.tap()
|
anyDone.tap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func openTab(app: XCUIApplication, label: String, index: Int) {
|
private func firstExistingDoneButton(app: XCUIApplication, timeout: TimeInterval) -> XCUIElement? {
|
||||||
let primary = app.tabBars.buttons[label].firstMatch
|
firstExistingButton(app: app, labels: doneLabels, timeout: timeout)
|
||||||
if primary.waitForExistence(timeout: 8) {
|
}
|
||||||
primary.tap()
|
|
||||||
return
|
private func firstExistingButton(app: XCUIApplication, labels: [String], timeout: TimeInterval) -> XCUIElement? {
|
||||||
|
let deadline = Date().addingTimeInterval(timeout)
|
||||||
|
while Date() < deadline {
|
||||||
|
for label in labels {
|
||||||
|
let button = app.buttons[label].firstMatch
|
||||||
|
if button.exists {
|
||||||
|
return button
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RunLoop.current.run(until: Date().addingTimeInterval(0.1))
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func openTab(app: XCUIApplication, label: String, index: Int) {
|
||||||
|
let localizedLabels = tabLabelMap[label] ?? [label]
|
||||||
|
|
||||||
let tabButtons = app.tabBars.buttons
|
let tabButtons = app.tabBars.buttons
|
||||||
if tabButtons.count > index {
|
if tabButtons.count > index {
|
||||||
tabButtons.element(boundBy: index).tap()
|
let indexed = tabButtons.element(boundBy: index)
|
||||||
return
|
if indexed.exists {
|
||||||
|
indexed.tap()
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let fallback = app.buttons[label].firstMatch
|
for localizedLabel in localizedLabels {
|
||||||
if fallback.exists {
|
let primary = app.tabBars.buttons[localizedLabel].firstMatch
|
||||||
fallback.tap()
|
if primary.waitForExistence(timeout: 2) {
|
||||||
|
primary.tap()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for localizedLabel in localizedLabels {
|
||||||
|
let sidebarButton = app.buttons[localizedLabel].firstMatch
|
||||||
|
if sidebarButton.waitForExistence(timeout: 2) {
|
||||||
|
sidebarButton.tap()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let staticText = app.staticTexts[localizedLabel].firstMatch
|
||||||
|
if staticText.waitForExistence(timeout: 1) {
|
||||||
|
staticText.tap()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let symbol = tabSymbolMap[label] {
|
||||||
|
let symbolButton = app.buttons[symbol].firstMatch
|
||||||
|
if symbolButton.waitForExistence(timeout: 2) {
|
||||||
|
symbolButton.tap()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for localizedLabel in localizedLabels {
|
||||||
|
if let found = findElementWithVerticalSearch(
|
||||||
|
app: app,
|
||||||
|
label: localizedLabel,
|
||||||
|
timeoutPerAttempt: 0.4,
|
||||||
|
swipeCount: 8
|
||||||
|
) {
|
||||||
|
if found.isHittable {
|
||||||
|
found.tap()
|
||||||
|
} else {
|
||||||
|
found.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,20 +524,19 @@ final class AndromidaUITests: XCTestCase {
|
|||||||
private func openHistoryDayDetailWithData(app: XCUIApplication) {
|
private func openHistoryDayDetailWithData(app: XCUIApplication) {
|
||||||
openTab(app: app, label: "History", index: 3)
|
openTab(app: app, label: "History", index: 3)
|
||||||
|
|
||||||
let nonZeroDay = app.buttons.matching(
|
// Locale-agnostic: prefer a day-like button with digits in its label.
|
||||||
NSPredicate(format: "label CONTAINS[c] %@ AND NOT (label CONTAINS[c] %@)", "percent complete", "0 percent complete")
|
let dayPredicate = NSPredicate(format: "label MATCHES %@", ".*[0-9].*")
|
||||||
).firstMatch
|
let dayButton = app.buttons.matching(dayPredicate).firstMatch
|
||||||
|
if dayButton.waitForExistence(timeout: 4) {
|
||||||
if nonZeroDay.waitForExistence(timeout: 8) {
|
dayButton.tap()
|
||||||
nonZeroDay.tap()
|
|
||||||
} else {
|
} else {
|
||||||
let anyDay = app.buttons.matching(NSPredicate(format: "label CONTAINS[c] %@", "percent complete")).firstMatch
|
let buttons = app.buttons.allElementsBoundByIndex
|
||||||
if anyDay.waitForExistence(timeout: 8) {
|
if let fallback = buttons.first(where: { $0.exists && $0.isHittable }) {
|
||||||
anyDay.tap()
|
fallback.tap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = app.buttons["Done"].waitForExistence(timeout: 8)
|
_ = firstExistingDoneButton(app: app, timeout: 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func tapFirstAvailableElementIfPresent(
|
private func tapFirstAvailableElementIfPresent(
|
||||||
|
|||||||
BIN
Screenshots/es-MX/iPad-13/01-today-focus-portrait.png
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
Screenshots/es-MX/iPad-13/02-ritual-arcs-portrait.png
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
Screenshots/es-MX/iPad-13/03-insights-trends-portrait.png
Normal file
|
After Width: | Height: | Size: 2.9 MiB |
BIN
Screenshots/es-MX/iPad-13/04-history-calendar-portrait.png
Normal file
|
After Width: | Height: | Size: 3.0 MiB |
BIN
Screenshots/es-MX/iPad-13/05-history-day-detail-portrait.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
Screenshots/es-MX/iPhone-6.3/01-today-focus-portrait.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
Screenshots/es-MX/iPhone-6.3/02-ritual-arcs-portrait.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
Screenshots/es-MX/iPhone-6.3/03-insights-trends-portrait.png
Normal file
|
After Width: | Height: | Size: 991 KiB |
BIN
Screenshots/es-MX/iPhone-6.3/04-history-calendar-portrait.png
Normal file
|
After Width: | Height: | Size: 961 KiB |
BIN
Screenshots/es-MX/iPhone-6.3/05-history-day-detail-portrait.png
Normal file
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1004 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 282 KiB |
BIN
Screenshots/es-MX/iPhone-6.9/01-today-focus-portrait.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
Screenshots/es-MX/iPhone-6.9/02-ritual-arcs-portrait.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
Screenshots/es-MX/iPhone-6.9/03-insights-trends-portrait.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
Screenshots/es-MX/iPhone-6.9/04-history-calendar-portrait.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
Screenshots/es-MX/iPhone-6.9/05-history-day-detail-portrait.png
Normal file
|
After Width: | Height: | Size: 311 KiB |
BIN
Screenshots/fr-CA/iPad-13/01-today-focus-portrait.png
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
Screenshots/fr-CA/iPad-13/02-ritual-arcs-portrait.png
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
Screenshots/fr-CA/iPad-13/03-insights-trends-portrait.png
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
Screenshots/fr-CA/iPad-13/04-history-calendar-portrait.png
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
Screenshots/fr-CA/iPad-13/05-history-day-detail-portrait.png
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
Screenshots/fr-CA/iPhone-6.3/01-today-focus-portrait.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
Screenshots/fr-CA/iPhone-6.3/02-ritual-arcs-portrait.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
Screenshots/fr-CA/iPhone-6.3/03-insights-trends-portrait.png
Normal file
|
After Width: | Height: | Size: 950 KiB |
BIN
Screenshots/fr-CA/iPhone-6.3/04-history-calendar-portrait.png
Normal file
|
After Width: | Height: | Size: 963 KiB |
BIN
Screenshots/fr-CA/iPhone-6.3/05-history-day-detail-portrait.png
Normal file
|
After Width: | Height: | Size: 269 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 998 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 288 KiB |
BIN
Screenshots/fr-CA/iPhone-6.9/01-today-focus-portrait.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
Screenshots/fr-CA/iPhone-6.9/02-ritual-arcs-portrait.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
Screenshots/fr-CA/iPhone-6.9/03-insights-trends-portrait.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
Screenshots/fr-CA/iPhone-6.9/04-history-calendar-portrait.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
Screenshots/fr-CA/iPhone-6.9/05-history-day-detail-portrait.png
Normal file
|
After Width: | Height: | Size: 306 KiB |