From eaf4dce75eac026a4e69e0b00826876e2d3746ec Mon Sep 17 00:00:00 2001 From: Eric Dahl Date: Mon, 26 Oct 2026 22:46:07 -0400 Subject: [PATCH] A pack owner in a Trial sees the "Trial ending soon" note (#961) The between-Takes note read license(), which counts a pack on the Armed family as Pro, so a pack owner's Trial looked already over. It now reads planLicense(), like the Trial sheets and the days chip (#959). Record press, the limits and the after-Take offers keep license(). Assisted-by: Claude Code:claude-opus-5-5 --- App/RecordScreen.swift | 8 ++++- AppTests/TrialNoteTests.swift | 44 ++++++++++++++++++++++++++ ShowRecorder.xcodeproj/project.pbxproj | 4 +++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 AppTests/TrialNoteTests.swift diff --git a/App/RecordScreen.swift b/App/RecordScreen.swift index aabb9843..8821cf0b 100644 --- a/App/RecordScreen.swift +++ b/App/RecordScreen.swift @@ -1249,10 +1249,16 @@ final class RecordScreenModel { let mixerNamed = linkedSources.contains { $0.hasMixerName } let moment = onDevice ?? show.flatMap { proOffers.afterTakeStopped(show: $0, license: license, isRecording: recorder.isRecording, mixerNamed: mixerNamed) } - ?? proOffers.betweenTakes(show: show, license: license, isRecording: recorder.isRecording) + ?? Self.trialNote(&proOffers, show: show, pro: pro, isRecording: recorder.isRecording) if let moment { offerPro(moment) } } + /// The Trial's last days between Takes (#62), from the plan as owned so a pack on the Armed family doesn't hide them + /// (#961). + static func trialNote(_ offers: inout ProOffers, show: String?, pro: ProStore, isRecording: Bool) -> ProOfferMoment? { + offers.betweenTakes(show: show, license: pro.planLicense(), isRecording: isRecording) + } + /// Opens the paywall for `moment` (nil: from Settings), once whatever sheet is closing has gone. func offerPro(_ moment: ProOfferMoment?) { Task { diff --git a/AppTests/TrialNoteTests.swift b/AppTests/TrialNoteTests.swift new file mode 100644 index 00000000..5aefef6e --- /dev/null +++ b/AppTests/TrialNoteTests.swift @@ -0,0 +1,44 @@ +import Foundation +import Recording +import Testing +@testable import ShowRecorder + +/// The between-Takes "Trial ending soon" note (#961): it reads the plan as owned, so a pack on the Armed family doesn't +/// hide the Trial's last days. +@MainActor +struct TrialNoteTests { + /// A store owning `extra` plus a Trial started 13 days ago, with an XR18 Armed. + func store(owning extra: [String] = [], trial: Bool = true) -> ProStore { + let now = Date() + var owned = extra.map { OwnedPurchase(productID: $0, purchaseDate: now.addingTimeInterval(-20 * 86_400)) } + if trial { owned.append(OwnedPurchase(productID: ProPurchases.trialProductID, purchaseDate: now.addingTimeInterval(-13 * 86_400))) } + let store = ProStore(status: ProStatus(lastKnown: ProPurchases(owned: owned))) + store.armedFamily = { .xAir } + return store + } + + func note(_ pro: ProStore) -> ProOfferMoment? { + var offers = ProOffers(defaults: UserDefaults(suiteName: UUID().uuidString)!) + return RecordScreenModel.trialNote(&offers, show: "Friday", pro: pro, isRecording: false) + } + + @Test func packOwnerInTheTrialsLastDaysSeesTheNote() { + let moment = note(store(owning: [ProPurchases.packProductIDs[.xAir]!])) + guard case .trialEndingSoon = moment else { + Issue.record("no Trial note: \(String(describing: moment))") + return + } + } + + @Test func trialWithoutAPackStillSeesTheNote() { + let moment = note(store()) + guard case .trialEndingSoon = moment else { + Issue.record("no Trial note: \(String(describing: moment))") + return + } + } + + @Test func proOwnerNeverSeesTheNote() { + #expect(note(store(owning: [ProPurchases.proProductID])) == nil) + } +} diff --git a/ShowRecorder.xcodeproj/project.pbxproj b/ShowRecorder.xcodeproj/project.pbxproj index 9797e880..bec6e5d1 100644 --- a/ShowRecorder.xcodeproj/project.pbxproj +++ b/ShowRecorder.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ 76BB34DDF7CEAD4AC34661C9 /* ShowRecorderLiveActivity.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = EF187F967C613A176A6CB202 /* ShowRecorderLiveActivity.appex */; platformFilters = (ios, ); settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 7978ADCB74542A7F2803E11F /* MeterGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB46E32691A6C070E799F33D /* MeterGrid.swift */; }; 7F1555A334A37A41677E5B47 /* TakeActivity in Frameworks */ = {isa = PBXBuildFile; productRef = 4DDAD2337FDDC304B0F1E946 /* TakeActivity */; }; + 86D02F5B48C3A86B008A219F /* TrialNoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9258491DEF2285144B77ED93 /* TrialNoteTests.swift */; }; 876AC19AE21C45AFA7E6C512 /* SupportSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74ACE7E232B13FE02C923373 /* SupportSection.swift */; }; 992430B53737588E241A07B2 /* RecordScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AAAF44EF0038E4AFC2D899 /* RecordScreen.swift */; }; 9FD36E125AA2BC7EE8AAEAD5 /* MonitorExperiment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D942D124B2BCC55DD0044462 /* MonitorExperiment.swift */; }; @@ -162,6 +163,7 @@ 8DFAA5CBF9186C8638F0BDDD /* WindowCloseGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowCloseGuard.swift; sourceTree = ""; }; 8F032F2B06E5F2F3AA51A95B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 90AA2E3938B5B72E1F6B30B8 /* PowerMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PowerMonitor.swift; sourceTree = ""; }; + 9258491DEF2285144B77ED93 /* TrialNoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrialNoteTests.swift; sourceTree = ""; }; 92F1D9C008374B162456C153 /* WatchSessionLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchSessionLink.swift; sourceTree = ""; }; 9383D6894516F217BC4158B8 /* RemoteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteView.swift; sourceTree = ""; }; 93A6FB94028F7291894E26FC /* ProStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProStore.swift; sourceTree = ""; }; @@ -259,6 +261,7 @@ C8309F65F6D2135D9274A367 /* DriveFolderModelTests.swift */, 4AE3649927662D4F08EC25D9 /* MeterGridTests.swift */, F3D98E5C83E528C4AEB5B355 /* SupportMailTests.swift */, + 9258491DEF2285144B77ED93 /* TrialNoteTests.swift */, BC392AB28BE7D826B3A9216D /* TrialSheetTests.swift */, ); path = AppTests; @@ -543,6 +546,7 @@ 01E4851C32FBDE4D3CCC11D7 /* DriveFolderModelTests.swift in Sources */, 201B434DBE445FE432B3A9FB /* MeterGridTests.swift in Sources */, DA46657B1E3BAA150CD1F749 /* SupportMailTests.swift in Sources */, + 86D02F5B48C3A86B008A219F /* TrialNoteTests.swift in Sources */, 4ED0D022389613E78380D23E /* TrialSheetTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0;