diff --git a/OpenDocumentReader/DocumentViewController.swift b/OpenDocumentReader/DocumentViewController.swift index 9e5b5327..eb1f141a 100644 --- a/OpenDocumentReader/DocumentViewController.swift +++ b/OpenDocumentReader/DocumentViewController.swift @@ -379,7 +379,7 @@ class DocumentViewController: UIViewController, DocumentDelegate, UISearchBarDel return - case .edit: + case .edit, .editLite: // ready once the tools are up, which `beginEditSession` says editDocument() @@ -810,7 +810,7 @@ class DocumentViewController: UIViewController, DocumentDelegate, UISearchBarDel private func editSessionReady() { isEditSessionReady = true - if ScreenshotMode.screen == .edit { + if ScreenshotMode.screen?.isEdit == true { ScreenshotMode.markReady(view) } } diff --git a/OpenDocumentReader/Features.swift b/OpenDocumentReader/Features.swift index 6a52a651..27b20187 100644 --- a/OpenDocumentReader/Features.swift +++ b/OpenDocumentReader/Features.swift @@ -6,6 +6,7 @@ enum Features { /// The ad banner and the consent form in front of it: Lite only. static var withAds: Bool { LINKS_ADS } - /// Formatting, paragraph changes and pdf marks: Pro only. - static var advancedEditing: Bool { ADVANCED_EDITING } + /// Formatting, paragraph changes and pdf marks: Pro only. The Lite edit + /// screenshot turns them off, because it is taken from the Pro build. + static var advancedEditing: Bool { ADVANCED_EDITING && ScreenshotMode.screen != .editLite } } diff --git a/OpenDocumentReader/ScreenshotMode.swift b/OpenDocumentReader/ScreenshotMode.swift index 90153ce9..35f83a69 100644 --- a/OpenDocumentReader/ScreenshotMode.swift +++ b/OpenDocumentReader/ScreenshotMode.swift @@ -29,12 +29,17 @@ enum ScreenshotMode { case pdf /// a document being edited, keyboard up case edit + /// the same edit as Lite shows it: the Pro tools dimmed, behind a badge + case editLite = "edit-lite" + + /// Whether the screen is an edit, in either app. + var isEdit: Bool { self == .edit || self == .editLite } /// The sample this screen opens, `nil` where it opens none. var sample: String? { switch self { case .browser: return nil - case .text, .edit: return "text" + case .text, .edit, .editLite: return "text" case .sheet: return "sheet" case .office: return "word" case .pdf: return "paper" diff --git a/OpenDocumentReaderUITests/ScreenshotTests.swift b/OpenDocumentReaderUITests/ScreenshotTests.swift index c464d63b..d0d636cc 100644 --- a/OpenDocumentReaderUITests/ScreenshotTests.swift +++ b/OpenDocumentReaderUITests/ScreenshotTests.swift @@ -13,6 +13,19 @@ import XCTest /// the order the store shows them in. final class ScreenshotTests: XCTestCase { + /// Each picture's name, and the screen it is of. The number is the place + /// in the store. Lite's edit takes the place of Pro's in Lite's listing, + /// so both are `04`. + private static let pictures = [ + ("01-browser", "browser"), + ("02-text", "text"), + ("03-sheet", "sheet"), + ("04-edit", "edit"), + ("04-edit-lite", "edit-lite"), + ("05-pdf", "pdf"), + ("06-office", "office"), + ] + /// Long, because it covers translating a document on a simulator that is /// also running eleven other languages' worth of tests today. private let readyTimeout: TimeInterval = 180 @@ -40,7 +53,7 @@ final class ScreenshotTests: XCTestCase { let arguments = app.launchArguments - for (index, screen) in ["browser", "text", "sheet", "edit", "pdf", "office"].enumerated() { + for (name, screen) in Self.pictures { app.launchArguments = arguments + ["-ODRScreenshot", screen] app.launch() @@ -53,11 +66,11 @@ final class ScreenshotTests: XCTestCase { waitForTheFolderToFill(in: app) } - if screen == "edit" { + if screen.hasPrefix("edit") { raiseTheKeyboard(in: app) } - Snapshots.take(String(format: "%02d-%@", index + 1, screen)) + Snapshots.take(name) // rather than leaving it running: the next launch has to go through // didFinishLaunching again to be handed the next screen diff --git a/fastlane/Fastfile b/fastlane/Fastfile index a76a63f8..0b8b6d86 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -245,9 +245,10 @@ platform :ios do ) # The Pro scheme, so no ad sdk is linked and no consent form can come up in - # front of a picture. The two apps are the same app, and what differs - the - # banner Lite carries - is not in a screenshot either way, so one set of - # pictures goes to both listings. + # front of a picture. The two apps differ only in the edit, where Lite dims + # the Pro tools: the run takes that screen a second time as Lite shows it, + # and each listing gets its own edit. The banner Lite carries is in no + # picture either way. capture_ios_screenshots( project: "OpenDocumentReader.xcodeproj", scheme: "ODR Screenshots", @@ -503,7 +504,7 @@ platform :ios do # something captured has to have all of it: half a set is worse in the # store than the set already up there. captured = !Dir.glob(File.join(FRAMED_DIR, "*", "*.png")).empty? - sh(STORE_SCREENSHOTS, "--screenshots", FRAMED_DIR, "--stage", shots_dir) if captured + sh(STORE_SCREENSHOTS, "--screenshots", FRAMED_DIR, "--stage", shots_dir, "--app", options[:name]) if captured UI.important("no screenshots under #{FRAMED_DIR} - writing the text only") unless captured key_path = api_key_file diff --git a/fastlane/screenshots/README.md b/fastlane/screenshots/README.md index dbffa5f2..b9f59bf2 100644 --- a/fastlane/screenshots/README.md +++ b/fastlane/screenshots/README.md @@ -24,7 +24,7 @@ The lane does these steps: ## The set -Six screens per device, in store order: +Six screens per device, in store order. The edit is taken twice: | | | | --- | --- | @@ -32,6 +32,7 @@ Six screens per device, in store order: | `02-text` | a text document | | `03-sheet` | a spreadsheet, with its sheet tabs | | `04-edit` | a document in edit mode, keyboard up | +| `04-edit-lite` | the same, as Lite shows it: the Pro tools dimmed | | `05-pdf` | a PDF, with a search under way | | `06-office` | a Word file | @@ -48,5 +49,10 @@ English pictures, because the app has no Hindi or Swedish UI. ## Both apps The set is taken once, with the `ODR Screenshots` scheme. That scheme builds -the Pro target, which links no ad sdk, so no consent form can appear. Both -listings get the same pictures. +the Pro target, which links no ad sdk, so no consent form can appear. + +The two apps differ only in the edit. `-ODRScreenshot edit-lite` turns the Pro +tools off in the Pro build, so Lite's edit is taken from the same build. +`scripts/store_screenshots.py --stage DIR --app lite` puts that picture in +place of `04-edit`, and each listing gets its own set. Both edits share the +headline of `04-edit` in `fastlane/frames/frames.json`. diff --git a/scripts/frame-screenshots.py b/scripts/frame-screenshots.py index 07949aa1..415cf21c 100755 --- a/scripts/frame-screenshots.py +++ b/scripts/frame-screenshots.py @@ -659,9 +659,14 @@ def main(argv=None): shutil.rmtree(out, ignore_errors=True) out.mkdir(parents=True, exist_ok=True) + # Lite's edit is framed as the screen it replaces: the same place, + # and the same headline, which is true of both apps + replaces = {lite: screen for screen, lite in store.LITE.items()} + for path in sorted(folder.glob("*.png")): - name = next((n for n in screens if path.stem.endswith(n)), None) - if name is None: + taken = next((n for n in store.CAPTURED if path.stem.endswith(n)), None) + name = replaces.get(taken, taken) + if name not in screens: continue with Image.open(path) as shot: diff --git a/scripts/store_screenshots.py b/scripts/store_screenshots.py index d0c86059..f125846f 100755 --- a/scripts/store_screenshots.py +++ b/scripts/store_screenshots.py @@ -10,7 +10,7 @@ # # scripts/store_screenshots.py --languages what to capture # scripts/store_screenshots.py check what was captured -# scripts/store_screenshots.py --stage DIR check it and stage it +# scripts/store_screenshots.py --stage DIR --app A check it and stage A's set # # The store has eleven locales and the app is translated into nine of them. # The other two get the English pictures, which is what their storefront would @@ -57,6 +57,18 @@ "06-office", ) +# What Lite shows in place of a screen of Pro's. The edit is the one screen +# where the two apps differ: Lite dims the Pro tools behind a badge. +LITE = { + "04-edit": "04-edit-lite", +} + +APPS = ("pro", "lite") + +# Every picture a run takes, longest name first, so a name that ends another +# one is never matched in its place. +CAPTURED = tuple(sorted(SCREENS + tuple(LITE.values()), key=len, reverse=True)) + # What App Store Connect accepts, upright, in pixels. An app that runs on both # has to hand in both, and the store fits every smaller iPhone and iPad from # these two. More than one size per device because which simulator a runner has @@ -125,9 +137,9 @@ def collect(directory): pictures = {} for path in sorted(folder.glob("*.png")): - screen = next((name for name in SCREENS if path.stem.endswith(name)), None) + screen = next((name for name in CAPTURED if path.stem.endswith(name)), None) if screen is None: - problems.append(f"{locale}: {path.name} is not one of {', '.join(SCREENS)}") + problems.append(f"{locale}: {path.name} is not one of {', '.join(CAPTURED)}") continue try: @@ -148,7 +160,7 @@ def collect(directory): pictures.setdefault(kind, {})[screen] = path for kind in SIZES: - missing = [screen for screen in SCREENS if screen not in pictures.get(kind, {})] + missing = [screen for screen in CAPTURED if screen not in pictures.get(kind, {})] if missing: problems.append(f"{locale}: no {kind} {', '.join(missing)}") @@ -157,12 +169,14 @@ def collect(directory): return found, problems -def stage(found, directory): - """Write the screenshot tree deliver uploads. +def stage(found, directory, app): + """Write the screenshot tree deliver uploads for `app`. A folder per store locale, the borrowed ones copied from the English rather than left out: what deliver does not upload for a locale, App Store Connect - keeps - which would be whatever was there before this release. + keeps - which would be whatever was there before this release. Lite's + pictures go under the name of the screen they replace, so they keep its + place in the store. """ directory = Path(directory) @@ -170,8 +184,9 @@ def stage(found, directory): folder = directory / locale folder.mkdir(parents=True, exist_ok=True) for kind, screens in pictures.items(): - for screen, path in screens.items(): - shutil.copyfile(path, folder / f"{kind}-{screen}.png") + for screen in SCREENS: + picture = LITE.get(screen, screen) if app == "lite" else screen + shutil.copyfile(screens[picture], folder / f"{kind}-{screen}.png") for locale in borrowed(): source = directory / FALLBACK @@ -211,8 +226,16 @@ def main(argv=None): metavar="DIR", help="also write the deliver screenshot tree into DIR", ) + parser.add_argument( + "--app", + choices=APPS, + help="the app whose set --stage writes; the two differ in the edit", + ) args = parser.parse_args(argv) + if args.stage and not args.app: + parser.error("--stage needs --app, because Pro and Lite show a different edit") + if args.languages: print("\n".join(languages())) return 0 @@ -228,11 +251,11 @@ def main(argv=None): if args.stage: try: - stage(found, args.stage) + stage(found, args.stage, args.app) except OSError as reason: return fail(str(reason)) print( - f"staged {len(SCREENS)} screenshots per device for " + f"staged {args.app}'s {len(SCREENS)} screenshots per device for " f"{len(found) + len(borrowed())} locales in {args.stage}" ) else: