diff --git a/apps/ios/Shared/Views/Badges/BadgesCheckOrderView.swift b/apps/ios/Shared/Views/Badges/BadgesCheckOrderView.swift index 3c3ee66318..368f95f0d4 100644 --- a/apps/ios/Shared/Views/Badges/BadgesCheckOrderView.swift +++ b/apps/ios/Shared/Views/Badges/BadgesCheckOrderView.swift @@ -11,6 +11,18 @@ import StoreKit import SimpleXChat struct BadgesCheckOrderView: View { + let level: BadgeLevel + let period: BadgePeriod + var startDate = Date() + var priceList: BadgePriceList? = nil + + var body: some View { + BadgesCheckOrderLayout(level: level, period: period, startDate: startDate, priceList: priceList) + .task { await BadgeStore.shared.load() } + } +} + +struct BadgesCheckOrderLayout: View { @EnvironmentObject var theme: AppTheme @EnvironmentObject var chatModel: ChatModel @ObservedObject private var store = BadgeStore.shared @@ -104,7 +116,6 @@ struct BadgesCheckOrderView: View { } .frame(maxHeight: .infinity) .navigationBarTitleDisplayMode(.inline) - .task { await store.load() } .alert(item: $alert) { $0.alert } } @@ -217,7 +228,7 @@ struct RestorePurchasesButton: View { struct BadgesCheckOrderView_Previews: PreviewProvider { static var previews: some View { NavigationView { - BadgesCheckOrderView( + BadgesCheckOrderLayout( level: .supporter, period: .oneMonth, startDate: previewDate, diff --git a/apps/ios/Shared/Views/Badges/BadgesHowLongView.swift b/apps/ios/Shared/Views/Badges/BadgesHowLongView.swift index fb3dbb077f..570bd5eae5 100644 --- a/apps/ios/Shared/Views/Badges/BadgesHowLongView.swift +++ b/apps/ios/Shared/Views/Badges/BadgesHowLongView.swift @@ -69,6 +69,17 @@ enum BadgePeriod: String, CaseIterable, Identifiable { } struct BadgesHowLongView: View { + let level: BadgeLevel + var startDate = Date() + var priceList: BadgePriceList? = nil + + var body: some View { + BadgesHowLongLayout(level: level, startDate: startDate, priceList: priceList) + .task { await BadgeStore.shared.load() } + } +} + +struct BadgesHowLongLayout: View { @EnvironmentObject var theme: AppTheme @ObservedObject private var store = BadgeStore.shared // store.forSale reads the active profile's badge @@ -142,7 +153,6 @@ struct BadgesHowLongView: View { } .frame(maxHeight: .infinity) .navigationBarTitleDisplayMode(.inline) - .task { await store.load() } } private func periodCard(_ period: BadgePeriod) -> some View { @@ -239,7 +249,7 @@ struct BadgeBillingFooter: View { struct BadgesHowLongView_Previews: PreviewProvider { static var previews: some View { NavigationView { - BadgesHowLongView( + BadgesHowLongLayout( level: .supporter, startDate: previewDate, priceList: BadgePriceList(oneMonth: .price("$7"), monthly: .price("$7"), annual: .price("$42"), monthlyAmount: 7, annualAmount: 42, subscriptionsForSale: true, subscribedBefore: false) diff --git a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift index 8a9bcaf059..a52c905307 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupLinkView.swift @@ -241,9 +241,9 @@ struct GroupLinkView_Previews: PreviewProvider { static var previews: some View { @State var groupLink: GroupLink? = GroupLink( userContactLinkId: 1, - connLinkContact: CreatedConnLink(connFullLink: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", connShortLink: nil), - shortLinkDataSet: false, - shortLinkLargeDataSet: false, + connLinkContact: CreatedConnLink(connFullLink: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", connShortLink: "https://smp6.simplex.im/g#cL3iklYlt_Tws1B_1aGylbT_3HowPMQhuCFoBiCjZ1M"), + shortLinkDataSet: true, + shortLinkLargeDataSet: true, groupLinkId: "abc", acceptMemberRole: .member ) diff --git a/apps/ios/Shared/Views/NewChat/QRCode.swift b/apps/ios/Shared/Views/NewChat/QRCode.swift index 2b38065bd9..6ab370a11a 100644 --- a/apps/ios/Shared/Views/NewChat/QRCode.swift +++ b/apps/ios/Shared/Views/NewChat/QRCode.swift @@ -46,19 +46,31 @@ struct SimpleXLinkQRCode: View { private let smallQRRatio: CGFloat = 0.63 +extension EnvironmentValues { + struct FixedQRCode: EnvironmentKey { + static let defaultValue: UIImage? = nil + } + + var fixedQRCode: UIImage? { + get { self[FixedQRCode.self] } + set { self[FixedQRCode.self] = newValue } + } +} + struct QRCode: View { let uri: String var small: Bool = false var withLogo: Bool = true var tintColor = UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1) var onShare: (() -> Void)? = nil + @Environment(\.fixedQRCode) private var fixedQRCode @State private var image: UIImage? = nil @State private var makeScreenshotFunc: () -> Void = {} @State private var width: CGFloat = .infinity var body: some View { ZStack { - if let image = image { + if let image = fixedQRCode ?? image { qrCodeImage(image).frame(width: width, height: width) GeometryReader { g in let w = g.size.width * (small ? smallQRRatio : 1) @@ -90,7 +102,7 @@ struct QRCode: View { } } .onTapGesture(perform: makeScreenshotFunc) - .task { image = await generateImage(uri, tintColor: tintColor, errorLevel: small ? "M" : "L") } + .task { if fixedQRCode == nil { image = await generateImage(uri, tintColor: tintColor, errorLevel: small ? "M" : "L") } } .frame(maxWidth: .infinity, maxHeight: .infinity) } } @@ -103,7 +115,7 @@ private func qrCodeImage(_ image: UIImage) -> some View { .textSelection(.enabled) } -private func generateImage(_ uri: String, tintColor: UIColor, errorLevel: String) async -> UIImage? { +func generateImage(_ uri: String, tintColor: UIColor, errorLevel: String) async -> UIImage? { let context = CIContext() let filter = CIFilter.qrCodeGenerator() filter.message = Data(uri.utf8) @@ -131,6 +143,6 @@ extension View { struct QRCode_Previews: PreviewProvider { static var previews: some View { - QRCode(uri: "https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FFe5ICmvrm4wkrr6X1LTMii-lhBqLeB76%23MCowBQYDK2VuAyEAdhZZsHpuaAk3Hh1q0uNb_6hGTpuwBIrsp2z9U2T0oC0%3D&e2e=v%3D1%26x3dh%3DMEIwBQYDK2VvAzkAcz6jJk71InuxA0bOX7OUhddfB8Ov7xwQIlIDeXBRZaOntUU4brU5Y3rBzroZBdQJi0FKdtt_D7I%3D%2CMEIwBQYDK2VvAzkA-hDvk1duBi1hlOr08VWSI-Ou4JNNSQjseY69QyKm7Kgg1zZjbpGfyBqSZ2eqys6xtoV4ZtoQUXQ%3D") + QRCode(uri: "https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw", small: true) } } diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index f25733a002..d6c59711a9 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -1027,7 +1027,7 @@ struct SetSimplexDomainView: View { struct UserAddressView_Previews: PreviewProvider { static var previews: some View { let chatModel = ChatModel() - chatModel.userAddress = UserContactLink(CreatedConnLink(connFullLink: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", connShortLink: nil)) + chatModel.userAddress = UserContactLink(CreatedConnLink(connFullLink: "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", connShortLink: "https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw")) return Group { UserAddressView() diff --git a/apps/ios/Shared/Views/UserSettings/VersionView.swift b/apps/ios/Shared/Views/UserSettings/VersionView.swift index e30c11699e..c894694cf3 100644 --- a/apps/ios/Shared/Views/UserSettings/VersionView.swift +++ b/apps/ios/Shared/Views/UserSettings/VersionView.swift @@ -10,9 +10,26 @@ import SwiftUI import SimpleXChat struct VersionView: View { - @EnvironmentObject var theme: AppTheme @State var versionInfo: CoreVersionInfo? + var body: some View { + VersionLayout(appVersion: appVersion, appBuild: appBuild, versionInfo: versionInfo) + .onAppear { + do { + versionInfo = try apiGetVersion() + } catch let error { + logger.error("apiGetVersion error: \(responseError(error))") + } + } + } +} + +struct VersionLayout: View { + @EnvironmentObject var theme: AppTheme + let appVersion: String? + let appBuild: String? + let versionInfo: CoreVersionInfo? + var body: some View { List { Section { @@ -37,18 +54,11 @@ struct VersionView: View { } } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - .onAppear { - do { - versionInfo = try apiGetVersion() - } catch let error { - logger.error("apiGetVersion error: \(responseError(error))") - } - } } } struct VersionView_Previews: PreviewProvider { static var previews: some View { - VersionView() + VersionLayout(appVersion: "7.1", appBuild: "363", versionInfo: CoreVersionInfo(version: "7.1.0.14", simplexmqVersion: "7.1.0.14", simplexmqCommit: "c0c416daf052eb0215ae432ae665522a7c96c0a6")) } } diff --git a/apps/ios/SimpleX Snapshots/References/GroupLinkView-dark-large.png b/apps/ios/SimpleX Snapshots/References/GroupLinkView-dark-large.png index cf46fb2341..8b2ee08584 100644 Binary files a/apps/ios/SimpleX Snapshots/References/GroupLinkView-dark-large.png and b/apps/ios/SimpleX Snapshots/References/GroupLinkView-dark-large.png differ diff --git a/apps/ios/SimpleX Snapshots/References/GroupLinkView-light-large.png b/apps/ios/SimpleX Snapshots/References/GroupLinkView-light-large.png index 05f6fbfe2d..bb89076fb0 100644 Binary files a/apps/ios/SimpleX Snapshots/References/GroupLinkView-light-large.png and b/apps/ios/SimpleX Snapshots/References/GroupLinkView-light-large.png differ diff --git a/apps/ios/SimpleX Snapshots/References/ProtocolServerView-dark-large.png b/apps/ios/SimpleX Snapshots/References/ProtocolServerView-dark-large.png index 3e53efb772..d345af0b60 100644 Binary files a/apps/ios/SimpleX Snapshots/References/ProtocolServerView-dark-large.png and b/apps/ios/SimpleX Snapshots/References/ProtocolServerView-dark-large.png differ diff --git a/apps/ios/SimpleX Snapshots/References/ProtocolServerView-light-large.png b/apps/ios/SimpleX Snapshots/References/ProtocolServerView-light-large.png index 7cd08c9904..fdf4ee92e4 100644 Binary files a/apps/ios/SimpleX Snapshots/References/ProtocolServerView-light-large.png and b/apps/ios/SimpleX Snapshots/References/ProtocolServerView-light-large.png differ diff --git a/apps/ios/SimpleX Snapshots/References/QRCode-dark-large.png b/apps/ios/SimpleX Snapshots/References/QRCode-dark-large.png index 9e088b017d..eb27982b6a 100644 Binary files a/apps/ios/SimpleX Snapshots/References/QRCode-dark-large.png and b/apps/ios/SimpleX Snapshots/References/QRCode-dark-large.png differ diff --git a/apps/ios/SimpleX Snapshots/References/QRCode-light-large.png b/apps/ios/SimpleX Snapshots/References/QRCode-light-large.png index e01aca146b..a7711f5e25 100644 Binary files a/apps/ios/SimpleX Snapshots/References/QRCode-light-large.png and b/apps/ios/SimpleX Snapshots/References/QRCode-light-large.png differ diff --git a/apps/ios/SimpleX Snapshots/References/UserAddressView-dark-large.png b/apps/ios/SimpleX Snapshots/References/UserAddressView-dark-large.png index e3f1287db2..db8bfee6f6 100644 Binary files a/apps/ios/SimpleX Snapshots/References/UserAddressView-dark-large.png and b/apps/ios/SimpleX Snapshots/References/UserAddressView-dark-large.png differ diff --git a/apps/ios/SimpleX Snapshots/References/UserAddressView-light-large.png b/apps/ios/SimpleX Snapshots/References/UserAddressView-light-large.png index 4ed81fc844..dc0cd300e7 100644 Binary files a/apps/ios/SimpleX Snapshots/References/UserAddressView-light-large.png and b/apps/ios/SimpleX Snapshots/References/UserAddressView-light-large.png differ diff --git a/apps/ios/SimpleX Snapshots/References/VerifyCodeView-dark-large.png b/apps/ios/SimpleX Snapshots/References/VerifyCodeView-dark-large.png index 7a0debd123..ffb70553da 100644 Binary files a/apps/ios/SimpleX Snapshots/References/VerifyCodeView-dark-large.png and b/apps/ios/SimpleX Snapshots/References/VerifyCodeView-dark-large.png differ diff --git a/apps/ios/SimpleX Snapshots/References/VerifyCodeView-light-large.png b/apps/ios/SimpleX Snapshots/References/VerifyCodeView-light-large.png index 56e09bec1e..a6ba059c7e 100644 Binary files a/apps/ios/SimpleX Snapshots/References/VerifyCodeView-light-large.png and b/apps/ios/SimpleX Snapshots/References/VerifyCodeView-light-large.png differ diff --git a/apps/ios/SimpleX Snapshots/References/VersionView-dark-large.png b/apps/ios/SimpleX Snapshots/References/VersionView-dark-large.png index 1d212c8233..8c0d726498 100644 Binary files a/apps/ios/SimpleX Snapshots/References/VersionView-dark-large.png and b/apps/ios/SimpleX Snapshots/References/VersionView-dark-large.png differ diff --git a/apps/ios/SimpleX Snapshots/References/VersionView-light-large.png b/apps/ios/SimpleX Snapshots/References/VersionView-light-large.png index b4900903db..0a83d62d8b 100644 Binary files a/apps/ios/SimpleX Snapshots/References/VersionView-light-large.png and b/apps/ios/SimpleX Snapshots/References/VersionView-light-large.png differ diff --git a/apps/ios/SimpleX Snapshots/SnapshotTests.swift b/apps/ios/SimpleX Snapshots/SnapshotTests.swift index e905f75223..3a32588afe 100644 --- a/apps/ios/SimpleX Snapshots/SnapshotTests.swift +++ b/apps/ios/SimpleX Snapshots/SnapshotTests.swift @@ -5,6 +5,12 @@ import UIKit @testable import SimpleXChat final class SnapshotTests: XCTestCase { + private var fixedQRCode: UIImage? + + override func setUp() async throws { + fixedQRCode = await generateImage(previewQRLink, tintColor: UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1), errorLevel: "M") + } + @MainActor func testPreviews() throws { NSTimeZone.default = try XCTUnwrap(TimeZone(identifier: "UTC")) @@ -21,7 +27,7 @@ final class SnapshotTests: XCTestCase { for scene in previewScenes() where scene.allSizes || size == "large" { let themeName = dark ? "dark" : "light" let file = "\(scene.name)-\(themeName)-\(size).png" - let image = try renderScene(scene, theme.toAppTheme(), dark: dark, in: windowScene) + let image = try renderScene(scene, theme.toAppTheme(), dark: dark, fixedQRCode: fixedQRCode, in: windowScene) try XCTUnwrap(image.pngData()).write(to: dir.appendingPathComponent(file)) index.append([ "scene": scene.name, @@ -305,13 +311,16 @@ private func snapshotTheme(_ dark: Bool) -> ThemeManager.ActiveTheme { ) } +private let previewQRLink = "https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw" + @MainActor -private func renderScene(_ scene: SnapshotScene, _ theme: AppTheme, dark: Bool, in windowScene: UIWindowScene) throws -> UIImage { +private func renderScene(_ scene: SnapshotScene, _ theme: AppTheme, dark: Bool, fixedQRCode: UIImage?, in windowScene: UIWindowScene) throws -> UIImage { let screen = windowScene.screen let width = screen.bounds.width let root = VStack(spacing: 0) { scene.view } .environmentObject(ChatModel.shared) .environmentObject(theme) + .environment(\.fixedQRCode, fixedQRCode) .frame(width: width, height: scene.height) let host = UIHostingController(rootView: AnyView(root)) if #available(iOS 16.4, *) { @@ -548,6 +557,94 @@ private final class SnapshotFixtures { let team: GroupInfo let news: GroupInfo let contact: Contact + private let markdown: [String: ParsedMarkdown] = [ + "https://simplex.chat": ParsedMarkdown(formattedText: [ + FormattedText(text: "https://simplex.chat", format: .uri) + ]), + "---": ParsedMarkdown( + formattedText: [ + FormattedText(text: "---", format: .markup(token: .ruleMark)) + ], + markdownBlocks: [ + .rule(mark: SegmentRange(from: 0, to: 1)) + ] + ), + "Hi! Did you see the **new release**?": ParsedMarkdown(formattedText: [ + FormattedText(text: "Hi! Did you see the "), + FormattedText(text: "new release", format: .bold(singleStar: false)), + FormattedText(text: "?") + ]), + "Yes, *great* changes 🎉": ParsedMarkdown(formattedText: [ + FormattedText(text: "Yes, "), + FormattedText(text: "great", format: .bold(singleStar: true)), + FormattedText(text: " changes 🎉") + ]), + "# Release notes\n- terminal layout\n- `code` blocks\n\n> quoted line": ParsedMarkdown( + formattedText: [ + FormattedText(text: "# ", format: .markup(token: .headingMark(level: 1))), + FormattedText(text: "Release notes"), + FormattedText(text: "\n", format: .markup(token: .lineBreak)), + FormattedText(text: "- ", format: .markup(token: .listMark(number: nil))), + FormattedText(text: "terminal layout"), + FormattedText(text: "\n", format: .markup(token: .lineBreak)), + FormattedText(text: "- ", format: .markup(token: .listMark(number: nil))), + FormattedText(text: "code", format: .snippet), + FormattedText(text: " blocks"), + FormattedText(text: "\n\n", format: .markup(token: .paragraphBreak(lineBreaks: 2))), + FormattedText(text: "> ", format: .markup(token: .quoteMark)), + FormattedText(text: "quoted line") + ], + markdownBlocks: [ + .heading(level: 1, content: SegmentRange(from: 1, to: 2)), + .list(items: [ + ListEntry(number: nil, content: SegmentRange(from: 4, to: 5), items: []), + ListEntry(number: nil, content: SegmentRange(from: 7, to: 9), items: []) + ]), + .quote(content: SegmentRange(from: 11, to: 12)) + ] + ), + "Plan for today:\n1. review\n2. **release**": ParsedMarkdown( + formattedText: [ + FormattedText(text: "Plan for today:"), + FormattedText(text: "\n", format: .markup(token: .lineBreak)), + FormattedText(text: "1. ", format: .markup(token: .listMark(number: "1."))), + FormattedText(text: "review"), + FormattedText(text: "\n", format: .markup(token: .lineBreak)), + FormattedText(text: "2. ", format: .markup(token: .listMark(number: "2."))), + FormattedText(text: "release", format: .bold(singleStar: false)) + ], + markdownBlocks: [ + .paragraph(content: SegmentRange(from: 0, to: 1)), + .list(items: [ + ListEntry(number: "1.", content: SegmentRange(from: 3, to: 4), items: []), + ListEntry(number: "2.", content: SegmentRange(from: 6, to: 7), items: []) + ]) + ] + ), + "**Version 7.1** is out": ParsedMarkdown(formattedText: [ + FormattedText(text: "Version 7.1", format: .bold(singleStar: false)), + FormattedText(text: " is out") + ]), + "## What's new\n- terminal layout\n- faster sync": ParsedMarkdown( + formattedText: [ + FormattedText(text: "## ", format: .markup(token: .headingMark(level: 2))), + FormattedText(text: "What's new"), + FormattedText(text: "\n", format: .markup(token: .lineBreak)), + FormattedText(text: "- ", format: .markup(token: .listMark(number: nil))), + FormattedText(text: "terminal layout"), + FormattedText(text: "\n", format: .markup(token: .lineBreak)), + FormattedText(text: "- ", format: .markup(token: .listMark(number: nil))), + FormattedText(text: "faster sync") + ], + markdownBlocks: [ + .heading(level: 2, content: SegmentRange(from: 1, to: 2)), + .list(items: [ + ListEntry(number: nil, content: SegmentRange(from: 4, to: 5), items: []), + ListEntry(number: nil, content: SegmentRange(from: 7, to: 8), items: []) + ]) + ] + ) + ] init() { alice = Self.member(2, "alice", .admin, image: imageBase64(UIColor(red: 0.85, green: 0.45, blue: 0.3, alpha: 1), CGSize(width: 64, height: 64))) @@ -594,7 +691,7 @@ private final class SnapshotFixtures { } private func msg(_ dir: CIDirection, _ text: String, _ minutes: Double, quote: CIQuote? = nil, reactions: [CIReactionCount] = [], content: MsgContent? = nil, file: CIFile? = nil) -> ChatItem { - let parsed = text.isEmpty ? nil : parseSimpleXMarkdownText(text) + let parsed = markdown[text] let mc = content ?? .text(text) return ChatItem( chatDir: dir, @@ -613,7 +710,7 @@ private final class SnapshotFixtures { } private func quote(_ dir: CIDirection?, _ text: String, image: String? = nil) -> CIQuote { - let parsed = text.isEmpty ? nil : parseSimpleXMarkdownText(text) + let parsed = markdown[text] return CIQuote( chatDir: dir, itemId: 1, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt index d28f4b3ae6..d4b8c9a195 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt @@ -212,7 +212,7 @@ internal fun PreviewContactConnectionInfoView() { SimpleXTheme { ContactConnectionInfoLayout( chatModel = ChatModel, - connLink = CreatedConnLink("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", null), + connLink = CreatedConnLink("https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FFe5ICmvrm4wkrr6X1LTMii-lhBqLeB76%23MCowBQYDK2VuAyEAdhZZsHpuaAk3Hh1q0uNb_6hGTpuwBIrsp2z9U2T0oC0%3D&e2e=v%3D1%26x3dh%3DMEIwBQYDK2VvAzkAcz6jJk71InuxA0bOX7OUhddfB8Ov7xwQIlIDeXBRZaOntUU4brU5Y3rBzroZBdQJi0FKdtt_D7I%3D%2CMEIwBQYDK2VvAzkA-hDvk1duBi1hlOr08VWSI-Ou4JNNSQjseY69QyKm7Kgg1zZjbpGfyBqSZ2eqys6xtoV4ZtoQUXQ%3D", "https://smp4.simplex.im/i#Wu_NH3_F5YphQrIMSCSfWhvqz7tjpZsu/A2dCDGGVkh0-doRPIZVTM-TflHes2uYMVRgA_tK_uCs"), showShortLink = remember { mutableStateOf(true) }, contactConnection = PendingContactConnection.getSampleData(), focusAlias = false, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt index 7171c7475c..07dbb6aaea 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/QRCode.kt @@ -60,6 +60,8 @@ fun SimpleXLinkQRCode( ) } +val LocalFixedQRCode = staticCompositionLocalOf { null } + @Composable fun QRCode( connReq: String, @@ -73,8 +75,9 @@ fun QRCode( val scope = rememberCoroutineScope() val logoSize = if (small) 0.21f else 0.16f val errorLevel = if (small) QrCode.ErrorLevel.M else QrCode.ErrorLevel.L + val fixedQRCode = LocalFixedQRCode.current val qr = remember(connReq, tintColor, withLogo) { - qrCodeBitmap(connReq, 1024, errorLevel).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) + (fixedQRCode ?: qrCodeBitmap(connReq, 1024, errorLevel)).replaceColor(Color.Black.toArgb(), tintColor.toArgb()) .let { if (withLogo) it.addLogo(logoSize) else it } } Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { @@ -127,6 +130,6 @@ expect fun ImageBitmap.replaceColor(from: Int, to: Int): ImageBitmap @Composable fun PreviewQRCode() { SimpleXTheme { - QRCode(connReq = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D") + QRCode(connReq = "https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw", small = true) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt index 674aa3831b..22dade4986 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt @@ -839,9 +839,9 @@ fun PreviewUserAddressLayoutAddressCreated() { UserAddressLayout( user = User.sampleData, userAddress = UserContactLinkRec( - CreatedConnLink("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", null), - shortLinkDataSet = false, - shortLinkLargeDataSet = false, + CreatedConnLink("https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D", "https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw"), + shortLinkDataSet = true, + shortLinkLargeDataSet = true, addressSettings = AddressSettings(businessAddress = false, autoAccept = null, autoReply = null) ), createAddress = {}, diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt index 77dad81aa4..0a197f4da4 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt @@ -1,6 +1,5 @@ package chat.simplex.common -import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -306,9 +305,3 @@ class DialogState { fun onResult(result: T) = onResult!!.complete(result) } - -@Preview -@Composable -fun AppPreview() { - AppScreen() -} diff --git a/apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/SnapshotTest.kt b/apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/SnapshotTest.kt index 1ca2f7bec8..c814aecf0f 100644 --- a/apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/SnapshotTest.kt +++ b/apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/SnapshotTest.kt @@ -8,10 +8,11 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.window.WindowState -import chat.simplex.common.AppPreview +import boofcv.alg.fiducial.qrcode.QrCode import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel import chat.simplex.common.simplexWindowState @@ -43,6 +44,7 @@ import kotlin.test.assertEquals private const val SCENE_WIDTH = 402 private const val SCENE_MAX_HEIGHT = 874 +private const val PREVIEW_QR_LINK = "https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw" private val chatItemFontScales = listOf(1f, 1.4f, 1.5f) @@ -112,7 +114,6 @@ private val previewScenes = listOf( SnapshotScene("UserAddressView.PreviewUserAddressLayoutAddressCreated") { PreviewUserAddressLayoutAddressCreated() }, SnapshotScene("AdvancedNetworkSettings.PreviewAdvancedNetworkSettingsLayout") { PreviewAdvancedNetworkSettingsLayout() }, SnapshotScene("NetworkAndServers.PreviewNetworkAndServersLayout") { PreviewNetworkAndServersLayout() }, - SnapshotScene("DesktopApp.AppPreview") { AppPreview() }, ) private val previewsCallingCore = listOf( @@ -139,12 +140,29 @@ class SnapshotTest { val theme = appPrefs.currentTheme.get()!! val themeOverrides = appPrefs.themeOverrides.get() val fontScale = appPrefs.fontScale.get() + val densityScale = appPrefs.densityScale.get() + val oneHandUI = appPrefs.oneHandUI.get() + val chatBottomBar = appPrefs.chatBottomBar.get() + val chatItemRoundness = appPrefs.chatItemRoundness.get() + val chatItemTail = appPrefs.chatItemTail.get() + val privacyMediaBlurRadius = appPrefs.privacyMediaBlurRadius.get() + val appearanceBarsBlurRadius = appPrefs.appearanceBarsBlurRadius.get() + val inAppBarsAlpha = appPrefs.inAppBarsAlpha.get() + val appBarMaxHeightPx = AppBarHandler.appBarMaxHeightPx val index = mutableListOf() val failures = mutableListOf() try { Locale.setDefault(Locale.US) TimeZone.setDefault(TimeZone.getTimeZone("UTC")) simplexWindowState.windowState = WindowState(size = DpSize(SCENE_WIDTH.dp, SCENE_MAX_HEIGHT.dp)) + appPrefs.densityScale.set(1f) + appPrefs.oneHandUI.set(true) + appPrefs.chatBottomBar.set(true) + appPrefs.chatItemRoundness.set(0.75f) + appPrefs.chatItemTail.set(true) + appPrefs.privacyMediaBlurRadius.set(0) + appPrefs.appearanceBarsBlurRadius.set(if (appPrefs.deviceSupportsBlur) 50 else 0) + appPrefs.inAppBarsAlpha.set(appPrefs.inAppBarsDefaultAlpha) appPrefs.themeOverrides.set(listOf(ThemeOverrides(base = DefaultTheme.LIGHT), ThemeOverrides(base = DefaultTheme.DARK))) for (base in listOf(DefaultTheme.LIGHT, DefaultTheme.DARK)) { ThemeManager.applyTheme(base.themeName) @@ -170,6 +188,15 @@ class SnapshotTest { TimeZone.setDefault(timeZone) appPrefs.themeOverrides.set(themeOverrides) appPrefs.fontScale.set(fontScale) + appPrefs.densityScale.set(densityScale) + appPrefs.oneHandUI.set(oneHandUI) + appPrefs.chatBottomBar.set(chatBottomBar) + appPrefs.chatItemRoundness.set(chatItemRoundness) + appPrefs.chatItemTail.set(chatItemTail) + appPrefs.privacyMediaBlurRadius.set(privacyMediaBlurRadius) + appPrefs.appearanceBarsBlurRadius.set(appearanceBarsBlurRadius) + appPrefs.inAppBarsAlpha.set(inAppBarsAlpha) + AppBarHandler.appBarMaxHeightPx = appBarMaxHeightPx ThemeManager.applyTheme(theme) } assertEquals(emptyList(), failures) @@ -178,9 +205,11 @@ class SnapshotTest { private fun renderSnapshot(content: @Composable () -> Unit): Image { var height = 0 + val fixedQRCode = qrCodeBitmap(PREVIEW_QR_LINK, 1024, QrCode.ErrorLevel.M) val frame = renderScene(width = SCENE_WIDTH, height = SCENE_MAX_HEIGHT, frames = 4) { + AppBarHandler.appBarMaxHeightPx = with(LocalDensity.current) { AppBarHeight.roundToPx() } Box(Modifier.width(SCENE_WIDTH.dp).background(CurrentColors.collectAsState().value.colors.background).onSizeChanged { height = it.height }) { - CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler(), content = content) + CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler(), LocalFixedQRCode provides fixedQRCode, content = content) } } return Surface.makeRasterN32Premul(SCENE_WIDTH, height).run { diff --git a/apps/multiplatform/common/src/desktopTest/snapshots/ContactConnectionInfoView.PreviewContactConnectionInfoView-dark-font-100.png b/apps/multiplatform/common/src/desktopTest/snapshots/ContactConnectionInfoView.PreviewContactConnectionInfoView-dark-font-100.png index babfa14943..1010afd3e8 100644 Binary files a/apps/multiplatform/common/src/desktopTest/snapshots/ContactConnectionInfoView.PreviewContactConnectionInfoView-dark-font-100.png and b/apps/multiplatform/common/src/desktopTest/snapshots/ContactConnectionInfoView.PreviewContactConnectionInfoView-dark-font-100.png differ diff --git a/apps/multiplatform/common/src/desktopTest/snapshots/ContactConnectionInfoView.PreviewContactConnectionInfoView-light-font-100.png b/apps/multiplatform/common/src/desktopTest/snapshots/ContactConnectionInfoView.PreviewContactConnectionInfoView-light-font-100.png index 82e016ee9e..a33e3e4d7f 100644 Binary files a/apps/multiplatform/common/src/desktopTest/snapshots/ContactConnectionInfoView.PreviewContactConnectionInfoView-light-font-100.png and b/apps/multiplatform/common/src/desktopTest/snapshots/ContactConnectionInfoView.PreviewContactConnectionInfoView-light-font-100.png differ diff --git a/apps/multiplatform/common/src/desktopTest/snapshots/DesktopApp.AppPreview-dark-font-100.png b/apps/multiplatform/common/src/desktopTest/snapshots/DesktopApp.AppPreview-dark-font-100.png deleted file mode 100644 index 392292c002..0000000000 Binary files a/apps/multiplatform/common/src/desktopTest/snapshots/DesktopApp.AppPreview-dark-font-100.png and /dev/null differ diff --git a/apps/multiplatform/common/src/desktopTest/snapshots/DesktopApp.AppPreview-light-font-100.png b/apps/multiplatform/common/src/desktopTest/snapshots/DesktopApp.AppPreview-light-font-100.png deleted file mode 100644 index 63964d2a46..0000000000 Binary files a/apps/multiplatform/common/src/desktopTest/snapshots/DesktopApp.AppPreview-light-font-100.png and /dev/null differ diff --git a/apps/multiplatform/common/src/desktopTest/snapshots/QRCode.PreviewQRCode-dark-font-100.png b/apps/multiplatform/common/src/desktopTest/snapshots/QRCode.PreviewQRCode-dark-font-100.png index e84f06e103..7b1f5d8a30 100644 Binary files a/apps/multiplatform/common/src/desktopTest/snapshots/QRCode.PreviewQRCode-dark-font-100.png and b/apps/multiplatform/common/src/desktopTest/snapshots/QRCode.PreviewQRCode-dark-font-100.png differ diff --git a/apps/multiplatform/common/src/desktopTest/snapshots/QRCode.PreviewQRCode-light-font-100.png b/apps/multiplatform/common/src/desktopTest/snapshots/QRCode.PreviewQRCode-light-font-100.png index 053da1d2ec..5c1bdc7ee8 100644 Binary files a/apps/multiplatform/common/src/desktopTest/snapshots/QRCode.PreviewQRCode-light-font-100.png and b/apps/multiplatform/common/src/desktopTest/snapshots/QRCode.PreviewQRCode-light-font-100.png differ diff --git a/apps/multiplatform/common/src/desktopTest/snapshots/UserAddressView.PreviewUserAddressLayoutAddressCreated-dark-font-100.png b/apps/multiplatform/common/src/desktopTest/snapshots/UserAddressView.PreviewUserAddressLayoutAddressCreated-dark-font-100.png index c38addfc85..e918b87318 100644 Binary files a/apps/multiplatform/common/src/desktopTest/snapshots/UserAddressView.PreviewUserAddressLayoutAddressCreated-dark-font-100.png and b/apps/multiplatform/common/src/desktopTest/snapshots/UserAddressView.PreviewUserAddressLayoutAddressCreated-dark-font-100.png differ diff --git a/apps/multiplatform/common/src/desktopTest/snapshots/UserAddressView.PreviewUserAddressLayoutAddressCreated-light-font-100.png b/apps/multiplatform/common/src/desktopTest/snapshots/UserAddressView.PreviewUserAddressLayoutAddressCreated-light-font-100.png index 6b993c3150..2c4fb48774 100644 Binary files a/apps/multiplatform/common/src/desktopTest/snapshots/UserAddressView.PreviewUserAddressLayoutAddressCreated-light-font-100.png and b/apps/multiplatform/common/src/desktopTest/snapshots/UserAddressView.PreviewUserAddressLayoutAddressCreated-light-font-100.png differ diff --git a/apps/multiplatform/common/src/desktopTest/snapshots/WhatsNewView.PreviewWhatsNewView-light-font-100.png b/apps/multiplatform/common/src/desktopTest/snapshots/WhatsNewView.PreviewWhatsNewView-light-font-100.png index 70a76858de..3c4b6cb69b 100644 Binary files a/apps/multiplatform/common/src/desktopTest/snapshots/WhatsNewView.PreviewWhatsNewView-light-font-100.png and b/apps/multiplatform/common/src/desktopTest/snapshots/WhatsNewView.PreviewWhatsNewView-light-font-100.png differ diff --git a/plans/2026-10-29-pure-previews-rework.md b/plans/2026-10-29-pure-previews-rework.md new file mode 100644 index 0000000000..55d2d172b6 --- /dev/null +++ b/plans/2026-10-29-pure-previews-rework.md @@ -0,0 +1,320 @@ +# Pure previews: rework + +Date: 2026-10-29. + +Pull request: https://github.com/simplex-chat/simplex-chat/pull/7938. Branch: ep/pure-previews. + +The branch had 109 preview-only parameters, each marked `// do not pass`, in 65 files. All are removed. Previews run master's code, except three views split into a loader and a layout, and one environment value for the QR picture. + +Rules, from the owner: + +``` +1 previews keep master's core calls that send no network request: database reads, parsing, validation +2 a preview whose rendering in CI can send a network request draws a layout split cleanly from its view, or its scene is excluded +3 VersionView is split, so that its scene does not depend on the release +4 QR codes: one environment value on iOS, one CompositionLocal on Kotlin, set by the snapshot tests +5 camera: master's code +``` + +Paths: + +``` +iOS apps/ios/Shared/ +Kotlin apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ +``` + +Line numbers of app code are those of master at 3bdecd63f. Core lines are those of `src/Simplex/Chat/Library/Commands.hs` and `src/Simplex/Chat/Remote.hs` at the same commit; agent lines are those of simplexmq c0c416daf, the commit `cabal.project` pins. + +## 1. Outcomes + +``` +(a) CI the parameter goes; the preview runs master's code; the scene is rendered on master and compared on every ios-sim or desktop-linux run +(a) CL the parameter goes; the preview draws previewDate (2025-01-15 10:15 UTC); from 2026-01-01 the clock-dependent text is "1/15/25" and "Wed, 15 Jan 2025", as in master's references +(a) X the parameter goes; the scene stays excluded, as on master +(a) S1 the parameter goes; the snapshot test sets the QR picture of seam S1 +(a) SIM the parameter goes; the simulator build of CodeScanner draws a fixed text and requests no camera access +(b) the parameter goes; the preview draws the layout of a loader/layout split (section 3) +``` + +Calls kept in previews, as on master: + +``` +db core API reading or writing the CI database, without network (section 4) +fn pure core function: markdown parse, name validation, JSON length, server address parse +agent agent API reading agent memory +store StoreKit read without network: canMakePayments, storefront country code +keychain keychain read +files file read or delete +defaults UserDefaults write +paste pasteboard read +camera camera authorization status, and the permission request of ScannerInView +global write to a shared singleton +``` + +## 2. Parameters + +| # | File | Parameter | Outcome | Measure | Calls kept | +|---|---|---|---|---|---| +| 1 | Model/ChatModel.swift | `ChatModel.init(migrationState:)` | (a) CI | previews call `ChatModel()`, as master's 12 preview sites | files | +| 2 | Views/Badges/BadgesCheckOrderView.swift | `priceList` | (b) | `BadgesCheckOrderLayout(priceList:)` | — | +| 3 | Views/Badges/BadgesCheckOrderView.swift | `previewCanBuy` | (b) | the layout reads `store.canBuy` | — | +| 4 | Views/Badges/BadgesCheckOrderView.swift | `previewCanMakePayments` | (b) | the layout reads `AppStore.canMakePayments` | store | +| 5 | Views/Badges/BadgesChooseBadgeView.swift | `previewMonthlyPrices` | (a) X | master | — | +| 6 | Views/Badges/BadgesChooseBadgeView.swift | `previewBrowserAllowed` | (a) X | master | — | +| 7 | Views/Badges/BadgesHowLongView.swift | `priceList` | (b) | `BadgesHowLongLayout(priceList:)` | — | +| 8 | Views/Badges/BadgesRedeemCodeView.swift | `previewCameraStatus` | (a) CI | ScannerInView draws "Tap to scan" | camera | +| 9 | Views/Call/IncomingCallView.swift | `previewInvitation` | (a) CI | master's preview sets `CallController.shared.activeCallInvitation` | global | +| 10 | Views/Chat/ChatInfoView.swift | `ChatInfoView.previewContactInfo` | (a) CI | `apiContactInfo`, `apiGetContactCode` for the sample contact | db | +| 11 | Views/Chat/ChatInfoView.swift | `ChatInfoView.previewChatItemTTL` | (a) CI | `ChatModel.shared.chatItemTTL` of the CI database | — | +| 12 | Views/Chat/ChatInfoView.swift | `ChatTTLOption.previewChatItemTTL` | (a) CI | as row 11 | — | +| 13 | Views/Chat/ChatItemForwardingView.swift | `previewChats` | (a) CI | `ChatModel.shared.chats` of the CI profile | — | +| 14 | Views/Chat/ChatItemForwardingView.swift | `previewHasSimplexLink` | (a) CI | `hasSimplexLink("hello")` | fn | +| 15 | Views/Chat/ChatView.swift | `previewAvailableContent` | (a) CI | `initChatView` for the sample contact | db | +| 16 | Views/Chat/Group/AddGroupMembersView.swift | `AddGroupMembersView.previewContacts` | (a) CI | `filterMembersToAdd`: contacts of `ChatModel.shared.chats` | — | +| 17 | Views/Chat/Group/AddGroupMembersView.swift | `AddGroupMembersViewCommon.previewContacts` | (a) CI | as row 16 | — | +| 18 | Views/Chat/Group/ChannelRelaysView.swift | `previewMembers` | (a) CI | `loadGroupMembers`, `apiGetGroupRelays` for the sample group | db | +| 19 | Views/Chat/Group/GroupChatInfoView.swift | `previewGroupLink` | (a) CI | `apiGetGroupLink` for the sample group | db | +| 20 | Views/Chat/Group/GroupChatInfoView.swift | `previewChatItemTTL` | (a) CI | as row 11 | — | +| 21 | Views/Chat/Group/GroupChatInfoView.swift | `sendReceiptsUserDefault` | (a) CI | `onAppear` reads `User.sampleData`, which the test sets as `ChatModel.shared.currentUser` | — | +| 22 | Views/Chat/Group/GroupLinkView.swift | `previewQRImage` | (a) S1 | fixed QR picture | — | +| 23 | Views/Chat/Group/GroupLinkView.swift | `shouldCreate` | (a) CI | `apiCreateGroupLink` for group 1 fails on the CI database | db | +| 24 | Views/Chat/Group/GroupMemberInfoView.swift | `previewMemberInfo` | (a) CI | `apiGroupMemberInfo`, `apiGetGroupMemberCode`, `apiContactInfo` for the sample member | db | +| 25 | Views/Chat/Group/GroupProfileView.swift | `previewShortDescrLength` | (a) CI | `chatJsonLength` of the sample group's short description; GroupWelcomeView_Previews as well | fn | +| 26 | Views/Chat/Group/MemberSupportView.swift | `previewMembers` | (a) CI | `loadGroupMembers` for the sample group | db | +| 27 | Views/Chat/Group/SecondaryChatView.swift | `previewAvailableContent` | (a) CI | `initChatView` of ChatView for the sample group scope | db | +| 28 | Views/Chat/ScanCodeView.swift | `previewCameraStatus` | (a) SIM | `CodeScannerView` | — | +| 29 | Views/Chat/VerifyCodeView.swift | `previewQRImage` | (a) S1 | fixed QR picture | — | +| 30 | Views/ChatList/ChatListView.swift | `badgeModel` | (a) CI | `BadgeModel.shared` of the CI profile | — | +| 31 | Views/ChatList/ChatListView.swift | `previewSubsTotal` | (a) CI | `getAgentSubsTotal` of the CI profile | agent | +| 32 | Views/ChatList/ChatListView.swift | `previewInUS` | (a) CI | `isInUS` of the simulator's storefront | store | +| 33 | Views/ChatList/ChatListView.swift | `previewNoShownBadge` | (a) CI | as row 30 | — | +| 34 | Views/ChatList/ChatListView.swift | `chatTagsModel` | (a) CI | `ChatTagsModel.shared` of the CI profile | — | +| 35 | Views/ChatList/ChatListView.swift | `SubsStatusIndicator.previewSubsTotal` | (a) CI | as row 31 | agent | +| 36 | Views/ChatList/ServersSummaryView.swift | `previewServersSummary` | (a) X | master | — | +| 37 | Views/ChatList/ServersSummaryView.swift | `previewServerAddresses` | (a) X | master | — | +| 38 | Views/ChatList/UserPicker.swift | `previewUsers` | (a) CI | `listUsersAsync` of the CI database | db | +| 39 | Views/Database/DatabaseEncryptionView.swift | `initialRandomDBPassphrase` | (a) CI | `initialRandomDBPassphraseGroupDefault` of the CI app | — | +| 40 | Views/Database/DatabaseEncryptionView.swift | `storedKey` | (a) CI | `kcDatabasePassword` of the CI app | keychain | +| 41 | Views/Database/DatabaseEncryptionView.swift | `previewDatabasePassword` | (a) CI | as row 40 | keychain | +| 42 | Views/Database/DatabaseErrorView.swift | `storedDBKey` | (a) CI | as row 40 | keychain | +| 43 | Views/Database/DatabaseErrorView.swift | `previewShowRestoreDbButton` | (a) CI | `shouldShowRestoreDbButton`: `encryptionStartedDefault` of the CI app is false | — | +| 44 | Views/Database/DatabaseView.swift | `previewUserName` | (a) CI | `User.sampleData`, which the test sets | — | +| 45 | Views/Database/DatabaseView.swift | `previewAppFilesCountAndSize` | (a) CI | the CI app's files folder | files | +| 46 | Views/LocalAuth/SetAppPasscodeView.swift | `previewProhibitedPasscode` | (a) CI | `kcSelfDestructPassword` of the CI app | keychain | +| 47 | Views/Migration/MigrateFromDevice.swift | `previewMigrationState` | (a) X | master | — | +| 48 | Views/Migration/MigrateToDevice.swift | `pasteboardHasStrings` | (a) CI | the CI simulator's pasteboard, empty after the smoke test | paste | +| 49 | Views/Migration/MigrateToDevice.swift | `previewCameraStatus` | (a) CI | ScannerInView draws "Tap to scan" | camera | +| 50 | Views/NewChat/AddChannelView.swift | `previewValidName` | (a) CI | `mkValidName("")` | fn | +| 51 | Views/NewChat/AddChannelView.swift | `previewHasRelays` | (a) CI | `checkHasRelays` of the CI profile | db | +| 52 | Views/NewChat/AddGroupView.swift | `previewValidName` | (a) CI | `mkValidName("")` | fn | +| 53 | Views/NewChat/NewChatView.swift | `NewChatView.previewQRImage` | (a) X | master | — | +| 54 | Views/NewChat/NewChatView.swift | `connLinkInvitation` | (a) X | master | — | +| 55 | Views/NewChat/NewChatView.swift | `InviteView.previewQRImage` | (a) X | master | — | +| 56 | Views/NewChat/NewChatView.swift | `ScannerInView.previewCameraStatus` | (a) CI | rows 8, 49, 67 | camera | +| 57 | Views/NewChat/QRCode.swift | `MutableQRCode.previewImage` | (a) S1 | fixed QR picture | — | +| 58 | Views/NewChat/QRCode.swift | `SimpleXCreatedLinkQRCode.previewImage` | (a) S1 | fixed QR picture | — | +| 59 | Views/NewChat/QRCode.swift | `SimpleXLinkQRCode.previewImage` | (a) S1 | fixed QR picture | — | +| 60 | Views/NewChat/QRCode.swift | `QRCode.previewImage` | (a) S1 | fixed QR picture | — | +| 61 | Views/Onboarding/CreateProfile.swift | `previewValidName` | (a) CI | `mkValidName("")` | fn | +| 62 | Views/Onboarding/CreateSimpleXAddress.swift | `previewQRImage` | (a) S1 | master's preview draws the address QR code only when `ChatModel.shared.userAddress` is set; it is nil for the CI profile | — | +| 63 | Views/Onboarding/OnboardingView.swift | `previewVersionSaved` | (a) CI | `setLastVersionDefault` | defaults | +| 64 | Views/Onboarding/SimpleXInfo.swift | `previewVersionSaved` | (a) CI | as row 63 | defaults | +| 65 | Views/Onboarding/WhatsNewView.swift | `previewInUS` | (a) CI | as row 32 | store | +| 66 | Views/RemoteAccess/ConnectDesktopView.swift | `previewRemoteCtrls` | (a) CI | `setLocalDeviceName`, `listRemoteCtrls` of the CI database | db | +| 67 | Views/RemoteAccess/ConnectDesktopView.swift | `previewCameraStatus` | (a) CI | ScannerInView draws "Tap to scan" | camera | +| 68 | Views/TerminalView.swift | `authorized` | (a) X | master's parameter; master | — | +| 69 | Views/TerminalView.swift | `showing` | (a) X | master | — | +| 70 | Views/UserSettings/CallSettings.swift | `previewInChina` | (a) CI | `CallController.isInChina` of the simulator's storefront | store | +| 71 | Views/UserSettings/NetworkAndServers/NetworkAndServers.swift | `justOpened` | (a) CI | `getUserServers`, `validateServers_` of the CI database | db | +| 72 | Views/UserSettings/NetworkAndServers/NewServerView.swift | `previewServerAddress` | (a) CI | `parseServerAddress("")` | fn | +| 73 | Views/UserSettings/NetworkAndServers/ProtocolServerView.swift | `previewServerAddress` | (a) CI | `parseServerAddress` of the sample server | fn | +| 74 | Views/UserSettings/NetworkAndServers/ProtocolServerView.swift | `previewQRImage` | (a) S1 | fixed QR picture | — | +| 75 | Views/UserSettings/NetworkAndServers/ProtocolServersView.swift | `YourServersView.previewServerAddresses` | (a) CI | `parseServerAddress` of the sample servers | fn | +| 76 | Views/UserSettings/NetworkAndServers/ProtocolServersView.swift | `ProtocolServerViewLink.previewServerAddress` | (a) CI | as row 75 | fn | +| 77 | Views/UserSettings/NetworkAndServers/ScanProtocolServer.swift | `previewCameraStatus` | (a) SIM | `CodeScannerView` | — | +| 78 | Views/UserSettings/NotificationsView.swift | `previewNtfToken` | (a) CI | `apiGetNtfToken` of the CI database | db | +| 79 | Views/UserSettings/PrivacySettings.swift | `previewUserName` | (a) CI | as row 44 | — | +| 80 | Views/UserSettings/SettingsView.swift | `previewInUS` | (a) CI | as row 32 | store | +| 81 | Views/UserSettings/UserAddressView.swift | `previewQRImage` | (a) S1 | fixed QR picture | — | +| 82 | Views/UserSettings/VersionView.swift | `previewVersionInfo` | (b) | `VersionLayout(appVersion:appBuild:versionInfo:)` | — | +| 83 | model/ChatModel.kt | `getTimestampDateText.previewNow` | (a) CL | DateSeparator text | — | +| 84 | model/ChatModel.kt | `getTimestampText.previewNow` | (a) CL | chat list time | — | +| 85 | views/chat/ChatView.kt | `ChatLayout.previewNow` | (a) CL | as row 83 | — | +| 86 | views/chat/ChatView.kt | `ChatItemsList.previewNow` | (a) CL | as row 83 | — | +| 87 | views/chat/ChatView.kt | `DateSeparator.previewNow` | (a) CL | as row 83 | — | +| 88 | views/chat/group/GroupMemberInfoView.kt | `previewContactConnectionStats` | (a) CI | `apiContactInfo` in `withBGApi` throws "Controller is not initialized" in the test JVM | db | +| 89 | views/chat/group/GroupProfileView.kt | `previewShortDescrFits` | (a) X | `previewsCallingCore`, as master | — | +| 90 | views/chatlist/ChatPreviewView.kt | `previewNow` | (a) CL | as row 84 | — | +| 91 | views/chatlist/ContactRequestView.kt | `previewNow` | (a) CL | as row 84 | — | +| 92 | views/database/DatabaseErrorView.kt | `previewStoredDBKey` | (a) CI | `ksDatabasePassword.get()` returns null with the CI test's settings | — | +| 93 | views/database/DatabaseErrorView.kt | `previewShowRestoreDbButton` | (a) CI | `shouldShowRestoreDbButton` returns false: `encryptionStartedAt` is null in the CI test's settings | — | +| 94 | views/database/DatabaseErrorView.kt | `previewUseKeychain` | (a) CI | `storeDBPassphrase` default, true | — | +| 95 | views/newchat/AddChannelView.kt | `AddChannelView.previewNameValid` | (a) X | `previewsCallingCore`, as master | — | +| 96 | views/newchat/AddChannelView.kt | `AddChannelView.previewHasRelays` | (a) X | as row 95 | — | +| 97 | views/newchat/AddChannelView.kt | `ProfileStepView.previewNameValid` | (a) X | as row 95 | — | +| 98 | views/newchat/AddChannelView.kt | `ProfileStepView.previewHasRelays` | (a) X | as row 95 | — | +| 99 | views/newchat/AddGroupView.kt | `previewNameValid` | (a) X | as row 95 | — | +| 100 | views/newchat/ContactConnectionInfoView.kt | `previewQRImage` | (a) S1 | fixed QR picture | — | +| 101 | views/newchat/NewChatSheet.kt | `NewChatSheet.previewConnectTarget` | (a) CI | `chatParseMarkdown` in a `LaunchedEffect` throws in the test JVM, which does not load the core library; the scene renders as in master's reference | fn | +| 102 | views/newchat/NewChatSheet.kt | `NewChatSheetLayout.previewConnectTarget` | (a) CI | as row 101 | fn | +| 103 | views/newchat/NewChatSheet.kt | `ContactsSearchBar.previewConnectTarget` | (a) CI | as row 101 | fn | +| 104 | views/newchat/QRCode.kt | `SimpleXCreatedLinkQRCode.previewImage` | (a) S1 | fixed QR picture | — | +| 105 | views/newchat/QRCode.kt | `SimpleXLinkQRCode.previewImage` | (a) S1 | fixed QR picture | — | +| 106 | views/newchat/QRCode.kt | `QRCode.previewImage` | (a) S1 | fixed QR picture | — | +| 107 | views/onboarding/SimpleXInfo.kt | `previewMigrationState` | (a) CI | `chatModel.migrationState` is null with the CI test's settings | files | +| 108 | views/usersettings/UserAddressView.kt | `previewQRImage` | (a) S1 | fixed QR picture | — | +| 109 | views/usersettings/UserProfileView.kt | `previewShortDescrFits` | (a) X | as row 89; EditOff and EditOn | — | + +Counts: + +``` +(a) 104 + CI 64 + CL 7 + X 17 + S1 14 + SIM 2 +(b) 5 +(c) 0 +``` + +## 3. Splits + +Each loader keeps its view's name and parameters, so its callers are unchanged. The loader runs the call it ran before, at the same moment: the view's `.task` or `.onAppear` moves from the end of the layout's modifiers to the layout in the loader's body. State stays where it was: the loader owns the loaded value, the layout owns its UI state. The preview draws the layout with fixed values. + +``` +BadgesCheckOrderView Views/Badges/BadgesCheckOrderView.swift + loader BadgesCheckOrderView(level, period, startDate, priceList): .task { await BadgeStore.shared.load() } + layout BadgesCheckOrderLayout(level, period, startDate, priceList) + moved .task { await store.load() } (:107) + preview BadgesCheckOrderLayout + full lines 10 added, 1 removed, 1 changed + +BadgesHowLongView Views/Badges/BadgesHowLongView.swift + loader BadgesHowLongView(level, startDate, priceList): .task { await BadgeStore.shared.load() } + layout BadgesHowLongLayout(level, startDate, priceList) + moved .task { await store.load() } (:145) + preview BadgesHowLongLayout + full lines 9 added, 1 removed, 1 changed + +VersionView Views/UserSettings/VersionView.swift + loader VersionView: @State versionInfo, .onAppear { versionInfo = try apiGetVersion() } + layout VersionLayout(appVersion, appBuild, versionInfo) + moved .onAppear (:40-46), @EnvironmentObject theme (:13) into the layout + preview VersionLayout(appVersion: "7.1", appBuild: "363", versionInfo: CoreVersionInfo(version: "7.1.0.14", ...)) + full lines 16 added, 8 removed, 1 changed +``` + +`store` of both badge layouts is `BadgeStore.shared` (`@ObservedObject private var store = BadgeStore.shared`), the object the loaders call. + +## 4. Network evidence + +Previews that kept a core, agent or StoreKit call: + +| Rows | Scene | Code path | Network in CI | +|---|---|---|---| +| 2, 3, 4 | BadgesCheckOrderView | BadgesCheckOrderView.swift:107 `store.load()` → BadgeStore.swift:219 `Product.products(for:)`, :228 `subscriptionOriginalIds()` | StoreKit: yes; split | +| 7 | BadgesHowLongView | BadgesHowLongView.swift:145 `store.load()` → BadgeStore.swift:219 | StoreKit: yes; split | +| 10 | ChatInfoView | ChatInfoView.swift:305-326: :313 `apiContactInfo` → Commands.hs:1956-1964 (`getContact` :1958, agent `getConnectionServers` :1963 → Agent.hs:3128-3131 store read, :3141-3180 agent memory); :314 `apiGetContactCode` → Commands.hs:2040-2053, :3838-3839 → Agent.hs:3133-3138 store read | no | +| 15 | ChatView | ChatView.swift:333-338 → `initChatView` :743-803: :750 `apiContactInfo` (row 10) for the sample contact; :791 → :805-818 `apiGetChatContentTypes` → Commands.hs:738-739 store read; :772-790, with :783 `apiGetUpdatedGroupLinkData`, needs a group with `useRelays`; :797-801 needs an unread chat | no | +| 18 | ChannelRelaysView | ChannelRelaysView.swift:45-55: `loadGroupMembers` → ChatModel.swift:592-602 `apiListMembers` → Commands.hs:3293-3294 store read; :49 `apiGetGroupRelays` → Commands.hs:2790-2795 store read | no | +| 19 | GroupChatInfoView | GroupChatInfoView.swift:298-312: :305 `apiGetGroupLink` → Commands.hs:3392-3395 store read | no | +| 23 | GroupLinkView | GroupLinkView.swift:164-169 → :179-196 `apiCreateGroupLink(1)` → Commands.hs:3364-3377: `getGroupInfo` :3365 fails before `createConnection` :3373; the CI database has no group: smoke-test.sh:93-126 creates only the profile | no | +| 24 | GroupMemberInfoView | GroupMemberInfoView.swift:293-328: :301 `apiGroupMemberInfo` → Commands.hs:1986-1989 store read and agent memory; :303 `apiGetGroupMemberCode` → Commands.hs:2054-2078 store read; :320 `apiContactInfo` (row 10) | no | +| 26 | MemberSupportView | MemberSupportView.swift:22-26 `loadGroupMembers` (row 18) | no | +| 27 | SecondaryChatView | ChatView `initChatView` for a group: `GroupInfo.sampleData.useRelays` is false (SimpleXChat/ChatTypes.swift:3103), so :772-790 is skipped; :791 (row 15) | no | +| 31, 35 | ChatListView | ChatListView.swift:736-760 `startTask` :753 `getAgentSubsTotal` → Commands.hs:3760-3764 → Agent/Client.hs:2627-2645 agent memory | no | +| 38 | UserPicker | UserPicker.swift:100-116: :106 `listUsersAsync` → Commands.hs:517 store read | no | +| 51 | AddChannelView | AddChannelView.swift:112-113 → `checkHasRelays` :272-278: :273 `getUserServers` → Commands.hs:1816-1817 store read | no | +| 66 | ConnectDesktopView | ConnectDesktopView.swift:87-104: :88 `setLocalDeviceName` → Commands.hs:3704 variable write; :89 `listRemoteCtrls` → Commands.hs:3723 → Remote.hs:606-611 store read; :92 `disconnectDesktop` needs a desktop session; :94 `findKnownDesktop` needs `useMulticast` (:29-31), which needs a linked desktop | no | +| 71 | NetworkServersView | NetworkAndServers.swift:136-155: :140 `getUserServers` (row 51); :144 `validateServers_` → Commands.hs:1833-1834, :4322-4326 store read | no | +| 78 | NotificationsView | NotificationsView.swift:83-85 `apiGetNtfToken` → Commands.hs:1681 → Agent.hs:3332-3336 store read | no | +| 88 | GroupMemberInfoView.kt | GroupMemberInfoView.kt:502-509 `withBGApi { apiContactInfo }` → model/SimpleXAPI.kt:989 throws "Controller is not initialized" | no | +| — | ActiveCallOverlay | `WebRTCClient` of the preview: WebRTCClient.swift:25 `getChatCtrl()` → SimpleXChat/API.swift:19-22 reads the controller pointer; :82-89 sets two flags of `RTCAudioSession.sharedInstance()` | no | + +## 5. QR picture (S1) + +iOS, `Views/NewChat/QRCode.swift`: + +```swift +extension EnvironmentValues { + struct FixedQRCode: EnvironmentKey { + static let defaultValue: UIImage? = nil + } + + var fixedQRCode: UIImage? { + get { self[FixedQRCode.self] } + set { self[FixedQRCode.self] = newValue } + } +} +``` + +``` +QRCode added @Environment(\.fixedQRCode) private var fixedQRCode +QRCode changed if let image = fixedQRCode ?? image { +QRCode changed .task { if fixedQRCode == nil { image = await generateImage(...) } } +``` + +Kotlin, `views/newchat/QRCode.kt`: + +``` +added val LocalFixedQRCode = staticCompositionLocalOf { null } +added val fixedQRCode = LocalFixedQRCode.current (QRCode, before remember) +changed (fixedQRCode ?: qrCodeBitmap(connReq, 1024, errorLevel)).replaceColor(...) (QRCode, in remember) +``` + +Snapshot tests: + +``` +link https://smp6.simplex.im/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw, the address of tests/MarkdownTests.hs:324 +iOS setUp: fixedQRCode = await generateImage(link, tintColor: QRCode's default tint, errorLevel: "M") +iOS renderScene root: .environment(\.fixedQRCode, fixedQRCode) +iOS QRCode.swift: generateImage is internal, so the test calls it +Kotlin renderSnapshot root: LocalFixedQRCode provides qrCodeBitmap(link, 1024, QrCode.ErrorLevel.M), in the theme of the render +``` + +The views add the logo, and on desktop the tint, to the picture, as to a generated one. Short links are drawn `small`, with error level M, in both apps. + +Previews with links pass the short form of a preset server, as the core returns it (shortenShortLink): host only, without the key hash. + +``` +iOS GroupLinkView_Previews connShortLink https://smp6.simplex.im/g#cL3iklYlt_Tws1B_1aGylbT_3HowPMQhuCFoBiCjZ1M; shortLinkDataSet, shortLinkLargeDataSet true +iOS UserAddressView_Previews connShortLink the address link +iOS QRCode_Previews QRCode(uri: the address link, small: true) +Kotlin PreviewUserAddressLayoutAddressCreated connShortLink the address link; shortLinkDataSet, shortLinkLargeDataSet true +Kotlin PreviewContactConnectionInfoView connFullLink the invitation of master's QRCode_Previews; connShortLink https://smp4.simplex.im/i#Wu_NH3_F5YphQrIMSCSfWhvqz7tjpZsu/A2dCDGGVkh0-doRPIZVTM-TflHes2uYMVRgA_tK_uCs +Kotlin PreviewQRCode QRCode(connReq = the address link, small = true) +``` + +Each short link decodes as AConnShortLink and encodes back to the same text. + +## 6. Other branch changes + +``` +ActiveCallView.swift, ActiveCallOverlay_Previews master +DesktopApp.kt, AppPreview deleted keep +SnapshotTests.swift, ActiveCallOverlay scene master +SnapshotTests.swift, SnapshotFixtures markdown table keep, values built with initializers +SnapshotTest.kt, setting pins and appBarMaxHeightPx keep +SnapshotTest.kt, AppPreview scene removed keep +check-previews.py, EXCLUDED master +SnapshotTest.kt, previewsCallingCore master +every other file master +``` + +## 7. App code + +Full lines by `git diff -w --word-diff`, blank lines aside: + +``` +iOS Views/Badges/BadgesCheckOrderView.swift 10 added, 1 removed, 1 changed +iOS Views/Badges/BadgesHowLongView.swift 9 added, 1 removed, 1 changed +iOS Views/UserSettings/VersionView.swift 16 added, 8 removed, 1 changed +iOS Views/NewChat/QRCode.swift 10 added, 4 changed: 1 of them the preview +iOS Views/Chat/Group/GroupLinkView.swift 3 changed: preview +iOS Views/UserSettings/UserAddressView.swift 1 changed: preview +Kotlin views/newchat/QRCode.kt 2 added, 2 changed: 1 of them the preview +Kotlin views/newchat/ContactConnectionInfoView.kt 1 changed: preview +Kotlin views/usersettings/UserAddressView.kt 3 changed: preview +Kotlin desktopMain DesktopApp.kt 6 removed: preview and its import +``` diff --git a/scripts/ci/desktop-linux/README.md b/scripts/ci/desktop-linux/README.md index 06211e6396..3b12c342a9 100644 --- a/scripts/ci/desktop-linux/README.md +++ b/scripts/ci/desktop-linux/README.md @@ -31,11 +31,11 @@ Queueing for a runner adds from under a minute to 20 minutes. Results are artifa ## Snapshots -`:common:desktopTest` runs `SnapshotTest` (`apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/SnapshotTest.kt`). It renders each preview listed in `previewScenes` with Compose's `ImageComposeScene`, 402 dp wide at density 1, one pixel per dp, as tall as the preview's content up to 874 dp, so a preview that fills any height is 874 pixels tall. It renders the light and dark themes without a wallpaper, over the theme's background, and keeps the fourth frame, 3 s of frame time after the first. The scene's coroutine context sets the animation duration scale (`MotionDurationScale`) to 0, so each animation ends at its first frame and a spinner is drawn the same in every run. As the app does for its screens, the test provides an `AppBarHandler` (`LocalAppBarHandler`) and the window state (`simplexWindowState`, sized as the scene). Each preview is rendered at the default font size; the chat item previews (package `views.chat.item`) also at 1.4 and 1.5, the two largest values of the desktop app's font size setting. +`:common:desktopTest` runs `SnapshotTest` (`apps/multiplatform/common/src/desktopTest/kotlin/chat/simplex/app/SnapshotTest.kt`). It renders each preview listed in `previewScenes` with Compose's `ImageComposeScene`, 402 dp wide at density 1, one pixel per dp, as tall as the preview's content up to 874 dp, so a preview that fills any height is 874 pixels tall. It renders the light and dark themes without a wallpaper, over the theme's background, and keeps the fourth frame, 3 s of frame time after the first. The scene's coroutine context sets the animation duration scale (`MotionDurationScale`) to 0, so each animation ends at its first frame and a spinner is drawn the same in every run. As the app does for its screens, the test provides an `AppBarHandler` (`LocalAppBarHandler`), the window state (`simplexWindowState`, sized as the scene) and the app bar height in pixels (`AppBarHandler.appBarMaxHeightPx`, which `AppScreen` sets), and restores the height when it finishes. Each preview is rendered at the default font size; the chat item previews (package `views.chat.item`) also at 1.4 and 1.5, the two largest values of the desktop app's font size setting. `everyPreviewIsRendered` reads the Kotlin sources of `commonMain` and `desktopMain` without comments, and fails when a `@Preview` function is missing from both `previewScenes` and `previewsCallingCore`, or when either names a function absent from the sources; a scene is named `.`. `previewsCallingCore` lists the previews whose layouts validate names or descriptions with the core library (`chatValidName`, `chatJsonLength`), which the test JVM does not load, so they are not rendered. The previews of `androidMain` and of the Android app module are left out too, since they use Android APIs. -The test runs in UTC and en_US (the JVM's default `TimeZone` and `Locale`), and replaces the theme overrides with ones without a wallpaper. It sets the app's theme and font size for each render, and restores these settings and defaults when it finishes. Before each render it clears the terminal's items, which previews that send commands to the core add with the time they are sent, so that `PreviewTerminalLayout` shows an empty terminal. Previews show `previewDate` (`ChatItemView.kt`) in place of the current time. +The test runs in UTC and en_US (the JVM's default `TimeZone` and `Locale`), and replaces the theme overrides with ones without a wallpaper. It sets the app's theme and font size for each render, sets the density scale, the one-hand UI, the chat toolbar position, the message shape, the media blur and the bars' blur and opacity to their defaults, and restores these settings and defaults when it finishes. Before each render it clears the terminal's items, which previews that send commands to the core add with the time they are sent, so that `PreviewTerminalLayout` shows an empty terminal. Previews show `previewDate` (`ChatItemView.kt`) in place of the current time. The test provides `LocalFixedQRCode` (`QRCode.kt`) with the bitmap that `qrCodeBitmap` draws for a short address link in the theme of the render, so every QR code draws this bitmap. The images go to `SNAPSHOT_DIR`, otherwise to `apps/multiplatform/common/build/snapshots`, as PNG files named `.--font-<100|140|150>.png`, with `index-desktop.json` listing each file's scene, theme, font scale, and pixel width and height. The app job sets `SNAPSHOT_DIR` to `snapshots/` of the artifact `desktop-snapshots`, then the step "Compare snapshots" runs `scripts/ci/snapshots-compare.py` with the references in `apps/multiplatform/common/src/desktopTest/snapshots`, as the iOS workflow does ([iOS](../ios-sim/README.md), "Snapshots"): a missing, extra or resized render, or a pixel beyond the tolerance, fails the step, and the diff images go to `snapshots-diff/` of the same artifact. diff --git a/scripts/ci/ios-sim/README.md b/scripts/ci/ios-sim/README.md index 67eee816fd..23d2e6c95a 100644 --- a/scripts/ci/ios-sim/README.md +++ b/scripts/ci/ios-sim/README.md @@ -46,7 +46,7 @@ At the end of the app job's smoke test, `smoke-test.sh` runs `snapshots.sh --.png`, with `index-.json` listing each file's scene, theme, size, pixel width and height, scale and the content size category the app saw.