diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml new file mode 100644 index 0000000..09c0a82 --- /dev/null +++ b/.github/workflows/swift.yml @@ -0,0 +1,23 @@ +# This workflow will build a Swift project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift + +name: Swift + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: macos-latest + + steps: + - uses: actions/checkout@v3 + - name: Build + run: swift build -v + - name: Check Package + run: swift package describe + diff --git a/Package.swift b/Package.swift index 92c44d6..208f738 100644 --- a/Package.swift +++ b/Package.swift @@ -1,12 +1,15 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.5 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "WhatsNew", + defaultLocalization: "en", platforms: [ - .iOS(.v14) + .iOS(.v15), + .macOS(.v12), + .tvOS(.v15) ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. @@ -14,15 +17,12 @@ let package = Package( name: "WhatsNew", targets: ["WhatsNew"]), ], - dependencies: [ - // Dependencies declare other packages that this package depends on. - // .package(url: /* package url */, from: "1.0.0"), - ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "WhatsNew", - dependencies: []) + dependencies: [] + ) ] ) diff --git a/README.md b/README.md index 8f78e0e..d8e18a5 100644 --- a/README.md +++ b/README.md @@ -3,18 +3,20 @@ This package is a simple way of adding What's New pages to your app. The pages are dynamically created based on the number of items passed to the WhatsNewView. -How to use: +This framework supports iOS, iPadOS, tv)S (15.0 and above) and macOS (12.0 and above). + +## How to use: 1) In your target's General Settings, make sure your app Display Name is set as you want it. -2) Create the content for each What's New page you want displayed. You can use What's New BulletPoint struct to add bullet points with images, bold titles and explanatory text. The compnent will use whatever accent color you set for the page. +2) Create the content for each What's New page you want displayed. You can use What's New `BulletPointView` struct to add bullet points with images, bold titles and explanatory text. The component will use whatever accent color you set for the page. -``` +```swift struct WhatsNewPageView: View { var body: some View { VStack (alignment: .leading, spacing: 10){ BulletPointView(title: "We now have SEARCH!!!", - imageName: "paintbrush.fill", + systemName: "paintbrush.fill", text: "Search to find books that have been on previous best seller lists.") BulletPointView(title: "More bugs squashed.", imageName: "myTruck", @@ -26,20 +28,20 @@ struct WhatsNewPageView: View { .accentColor(Color.red) } } +``` ... same for each additional page -``` 3) Import WhatsNew into your startup file. -4) In your @main struct, create a state variable to control display of WhatsNew pages. +4) In your `@main` struct, create a state variable to control display of WhatsNew pages. -5) Instantiate a WhatsNew class. You can use the (alwaysShow: true) parameter to always display WhatsNew content for testing. +5) Instantiate a `WhatsNew` class. You can use the `(alwaysShow: true)` parameter to always display WhatsNew content for testing. -6) Add the .onAppear modifier to check for updates in the version number of the app. Add .sheet modifier to your ContentView() call to display the What's new compomnent. +6) Add the `.onAppear` modifier to check for updates in the version number of the app. Add the `.sheet` modifier to your `ContentView()` call to display the What's New compomnent. -``` +```swift import SwiftUI import WhatsNew @@ -51,9 +53,9 @@ struct PackageTesterApp: App { var body: some Scene { WindowGroup { ContentView() - .onAppear(perform: { + .onAppear { whatsNew.checkForUpdate(showWhatsNew: $showWhatsNew) - }) + } .sheet(isPresented: $showWhatsNew) { WhatsNewView { WhatsNewPageView() diff --git a/Sources/WhatsNew/BulletPointView.swift b/Sources/WhatsNew/BulletPointView.swift index 9b050e6..4b2134d 100644 --- a/Sources/WhatsNew/BulletPointView.swift +++ b/Sources/WhatsNew/BulletPointView.swift @@ -13,7 +13,8 @@ let frameWidth = 50.0 public struct BulletPointView: View { let title: String - let imageName: String + let imageName: String? + let systemName: String? let text : String public init(title: String = "New feature", @@ -21,19 +22,40 @@ public struct BulletPointView: View { text: String = "This is a new feature for this app. And this text should wrap.") { self.title = title self.imageName = imageName + self.systemName = nil self.text = text } + public init(title: String = "New feature", + systemName: String = "circle.fill", + text: String = "This is a new feature for this app with a system icon. And this text should wrap.") { + self.title = title + self.imageName = nil + self.systemName = systemName + self.text = text + } + public var body: some View { HStack (alignment: .center){ - if let image = UIImage(named: imageName) { +#if os(iOS) + if let name = imageName, let image = UIImage(named: name) { Image(uiImage: image) .resizable() .renderingMode(.template) .frame(height: frameWidth * (image.size.height/image.size.width)) .bulletStyle() - } else { - Image(systemName: imageName) + } +#elseif os(macOS) + if let name = imageName, let image = NSImage(named: NSImage.Name(name)) { + Image(nsImage: image) + .resizable() + .renderingMode(.template) + .frame(height: frameWidth * (CGFloat(image.size.height)/CGFloat(image.size.width))) + .bulletStyle() + } +#endif + if let system = systemName { + Image(systemName: system) .renderingMode(.template) .bulletStyle() .font(.title) @@ -51,20 +73,6 @@ public struct BulletPointView: View { } } -struct BulletPointView_Previews: PreviewProvider { - static var previews: some View { - VStack (alignment: .leading){ - BulletPointView(imageName: "square.and.pencil") - BulletPointView(imageName: "hare.fill") - BulletPointView(imageName: "circle.fill") - BulletPointView(imageName: "car.2.fill") - BulletPointView(imageName: "switch.2") - BulletPointView(imageName: "ellipsis") - }.padding() - } -} - - struct Bullet: ViewModifier { func body(content: Content) -> some View { content @@ -80,3 +88,17 @@ extension View { } } +#if DEBUG +struct BulletPointView_Previews: PreviewProvider { + static var previews: some View { + VStack (alignment: .leading){ + BulletPointView(systemName: "square.and.pencil") + BulletPointView(systemName: "hare.fill") + BulletPointView(systemName: "circle.fill") + BulletPointView(systemName: "car.2.fill") + BulletPointView(systemName: "switch.2") + BulletPointView(systemName: "ellipsis") + }.padding() + } +} +#endif diff --git a/Sources/WhatsNew/Resources/de.lproj/Localizable.strings b/Sources/WhatsNew/Resources/de.lproj/Localizable.strings new file mode 100644 index 0000000..6900d26 --- /dev/null +++ b/Sources/WhatsNew/Resources/de.lproj/Localizable.strings @@ -0,0 +1,5 @@ +/* Button */ +"Continue" = "Fortfahren"; + +/* Dialog Title */ +"What's New\nin %@" = "Neues\nin %@"; diff --git a/Sources/WhatsNew/Resources/en.lproj/Localizable.strings b/Sources/WhatsNew/Resources/en.lproj/Localizable.strings new file mode 100644 index 0000000..37d9a75 --- /dev/null +++ b/Sources/WhatsNew/Resources/en.lproj/Localizable.strings @@ -0,0 +1,6 @@ +/* Button */ +"Continue" = "Continue"; + +/* Dialog Title */ +"What's New\nin %@" = "What's New\nin %@"; + diff --git a/Sources/WhatsNew/Resources/es.lproj/Localizable.strings b/Sources/WhatsNew/Resources/es.lproj/Localizable.strings new file mode 100644 index 0000000..ef4dfbb --- /dev/null +++ b/Sources/WhatsNew/Resources/es.lproj/Localizable.strings @@ -0,0 +1,5 @@ +/* Button */ +"Continue" = "Continuar"; + +/* Dialog Title */ +"What's New\nin %@" = "Lo nuevo\nen %@"; diff --git a/Sources/WhatsNew/Resources/fr.lproj/Localizable.strings b/Sources/WhatsNew/Resources/fr.lproj/Localizable.strings new file mode 100644 index 0000000..0daae0a --- /dev/null +++ b/Sources/WhatsNew/Resources/fr.lproj/Localizable.strings @@ -0,0 +1,5 @@ +/* Button */ +"Continue" = "Continuer"; + +/* Dialog Title */ +"What's New\nin %@" = "Nouveautés\ndans %@"; diff --git a/Sources/WhatsNew/Resources/it.lproj/Localizable.strings b/Sources/WhatsNew/Resources/it.lproj/Localizable.strings new file mode 100644 index 0000000..aa1167b --- /dev/null +++ b/Sources/WhatsNew/Resources/it.lproj/Localizable.strings @@ -0,0 +1,5 @@ +/* Button */ +"Continue" = "Continua"; + +/* Dialog Title */ +"What's New\nin %@" = "Novità\nin %@"; diff --git a/Sources/WhatsNew/Resources/nl.lproj/Localizable.strings b/Sources/WhatsNew/Resources/nl.lproj/Localizable.strings new file mode 100644 index 0000000..ef040df --- /dev/null +++ b/Sources/WhatsNew/Resources/nl.lproj/Localizable.strings @@ -0,0 +1,5 @@ +/* Button */ +"Continue" = "Doorgaan"; + +/* Dialog Title */ +"What's New\nin %@" = "Wat is er nieuw\nin %@"; diff --git a/Sources/WhatsNew/Resources/pt-BR.lproj/Localizable.strings b/Sources/WhatsNew/Resources/pt-BR.lproj/Localizable.strings new file mode 100644 index 0000000..6b98ded --- /dev/null +++ b/Sources/WhatsNew/Resources/pt-BR.lproj/Localizable.strings @@ -0,0 +1,5 @@ +/* Button */ +"Continue" = "Continuar"; + +/* Dialog Title */ +"What's New\nin %@" = "Novidades\nem %@"; diff --git a/Sources/WhatsNew/WhatsNew.swift b/Sources/WhatsNew/WhatsNew.swift index 1ca670c..ccfdb07 100644 --- a/Sources/WhatsNew/WhatsNew.swift +++ b/Sources/WhatsNew/WhatsNew.swift @@ -10,10 +10,10 @@ import SwiftUI public class WhatsNew: ObservableObject { - @AppStorage("savedVersion") var savedVersion: String = "1.000.000" + @AppStorage("savedVersion") var savedVersion: String? var alwaysShow = false - + public init(alwaysShow: Bool = false) { self.alwaysShow = alwaysShow } @@ -21,20 +21,32 @@ public class WhatsNew: ObservableObject { //check the currentVersion against the saved version public func checkForUpdate( showWhatsNew: Binding ) { let currentVersion = getCurrentAppVersion() - print("Current Version: \(currentVersion)\nSaved Version: \(savedVersion)") +#if DEBUG + print("Current Version: \(currentVersion)\nSaved Version: \(savedVersion ?? "None")") +#endif if alwaysShow { showWhatsNew.wrappedValue = alwaysShow return } - if savedVersion < currentVersion { - savedVersion = currentVersion + // If this is the first launch (no saved version), save the current version and return + guard let savedVersion = savedVersion else { + self.savedVersion = currentVersion + return + } + + if isVersion(savedVersion, lessThan: currentVersion) { + self.savedVersion = currentVersion showWhatsNew.wrappedValue = true - } else if savedVersion > currentVersion { + } else if isVersion(savedVersion, greaterThan: currentVersion) { +#if DEBUG print("App is behind!") - savedVersion = currentVersion +#endif + self.savedVersion = currentVersion } else { +#if DEBUG print("App is up to date!") +#endif } } @@ -42,6 +54,27 @@ public class WhatsNew: ObservableObject { func getCurrentAppVersion() -> String { return "\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String).\(Bundle.main.infoDictionary?["CFBundleVersion"] as! String)" } + + // Compare version strings as version numbers + private func isVersion(_ version1: String, lessThan version2: String) -> Bool { + let v1Components = version1.split(separator: ".").compactMap { Int($0) } + let v2Components = version2.split(separator: ".").compactMap { Int($0) } + + for (v1, v2) in zip(v1Components, v2Components) { + if v1 < v2 { + return true + } else if v1 > v2 { + return false + } + } + + return v1Components.count < v2Components.count + } + + private func isVersion(_ version1: String, greaterThan version2: String) -> Bool { + return !isVersion(version1, lessThan: version2) && version1 != version2 + } + } diff --git a/Sources/WhatsNew/WhatsNewView.swift b/Sources/WhatsNew/WhatsNewView.swift index 6e82009..6bf4de7 100644 --- a/Sources/WhatsNew/WhatsNewView.swift +++ b/Sources/WhatsNew/WhatsNewView.swift @@ -12,46 +12,79 @@ public struct WhatsNewView: View { @Environment(\.presentationMode) var presentationMode - let appName: String = Bundle.main.infoDictionary!["CFBundleDisplayName"] as? String ?? "New App" + let appName: String = Bundle.main.localizedInfoDictionary?["CFBundleDisplayName"] as? String ?? "My App" + let multiPage: Bool + let showVersion: Bool let content: Content - public init(@ViewBuilder contentProvider: () -> Content){ + private let bundle = Bundle.module + + public init(multiPage: Bool = true, showVersion: Bool = true, @ViewBuilder contentProvider: () -> Content) { + self.multiPage = multiPage + self.showVersion = showVersion self.content = contentProvider() } public var body: some View { VStack { VStack (alignment: .center) { - Text("What's New") - .fontWeight(.bold) - Text("in \(appName)") + Text(String(format:NSLocalizedString("What's New\nin %@", bundle: bundle, comment: "Dialog Title"), appName)) .fontWeight(.bold) + if showVersion, let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String { + Text("v\(version)") + .font(.footnote) + .padding(.top) + } } .font(.title) .multilineTextAlignment(.center) .padding(.top, 50) - TabView { + if multiPage { + TabView { + content + } +#if !os(macOS) + .tabViewStyle(.page) + .indexViewStyle(.page(backgroundDisplayMode: .always)) +#endif + } else { content + .padding() } - .tabViewStyle(PageTabViewStyle()) - .indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always)) - - Button(action: { + Button(NSLocalizedString("Continue", bundle: bundle, comment: "Button")) { presentationMode.wrappedValue.dismiss() - }, label: { - Text("Continue") - .foregroundColor(.white) - .background(RoundedRectangle(cornerRadius: 12, style: .continuous) - .fill(Color("AccentColor")) - .frame(width: 300, height: 40)) - }) + } + .foregroundColor(.white) + .padding() + .background(Color("AccentColor")) + .cornerRadius(12) .padding(.bottom, 30) + .buttonStyle(.plain) } .padding(.horizontal, 10) } } +#if DEBUG +struct WhatsNewView_Previews: PreviewProvider { + static var previews: some View { + WhatsNewView(multiPage: false) { + VStack (alignment: .leading) { + BulletPointView(title: "New feature", + systemName: "circle.fill", + text: "This is a new feature for this app. And this text should wrap.") + BulletPointView(title: "New feature", + systemName: "square.fill", + text: "This is a new feature for this app. And this text should wrap.") + BulletPointView(title: "New feature", + systemName: "triangle.fill", + text: "This is a new feature for this app. And this text should wrap.") + } + } + } +} +#endif diff --git a/WhatsNewDemo/WhatsNewDemo.xcodeproj/project.pbxproj b/WhatsNewDemo/WhatsNewDemo.xcodeproj/project.pbxproj new file mode 100644 index 0000000..3ea6524 --- /dev/null +++ b/WhatsNewDemo/WhatsNewDemo.xcodeproj/project.pbxproj @@ -0,0 +1,408 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + EA054FA82A4E52B10046251E /* WhatsNewDemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA054FA72A4E52B10046251E /* WhatsNewDemoApp.swift */; }; + EA054FAA2A4E52B10046251E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA054FA92A4E52B10046251E /* ContentView.swift */; }; + EA054FAC2A4E52B20046251E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EA054FAB2A4E52B20046251E /* Assets.xcassets */; }; + EA054FBE2A4E53D00046251E /* PageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA054FBD2A4E53D00046251E /* PageView.swift */; }; + EA63674D2A4E8AD60075B9B5 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EA054FAF2A4E52B20046251E /* Preview Assets.xcassets */; }; + EAC88E2C2A4E894C0030F8F7 /* WhatsNew in Frameworks */ = {isa = PBXBuildFile; productRef = EAC88E2B2A4E894C0030F8F7 /* WhatsNew */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + EA054FA42A4E52B10046251E /* WhatsNewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WhatsNewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; + EA054FA72A4E52B10046251E /* WhatsNewDemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewDemoApp.swift; sourceTree = ""; }; + EA054FA92A4E52B10046251E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + EA054FAB2A4E52B20046251E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + EA054FAD2A4E52B20046251E /* WhatsNewDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WhatsNewDemo.entitlements; sourceTree = ""; }; + EA054FAF2A4E52B20046251E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + EA054FBD2A4E53D00046251E /* PageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageView.swift; sourceTree = ""; }; + EAC88E292A4E87870030F8F7 /* WhatsNew */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = WhatsNew; path = ..; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + EA054FA12A4E52B10046251E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EAC88E2C2A4E894C0030F8F7 /* WhatsNew in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + EA054F9B2A4E52B10046251E = { + isa = PBXGroup; + children = ( + EAC88E282A4E87870030F8F7 /* Packages */, + EA054FA62A4E52B10046251E /* WhatsNewDemo */, + EA054FA52A4E52B10046251E /* Products */, + EAC88E2A2A4E894C0030F8F7 /* Frameworks */, + ); + sourceTree = ""; + }; + EA054FA52A4E52B10046251E /* Products */ = { + isa = PBXGroup; + children = ( + EA054FA42A4E52B10046251E /* WhatsNewDemo.app */, + ); + name = Products; + sourceTree = ""; + }; + EA054FA62A4E52B10046251E /* WhatsNewDemo */ = { + isa = PBXGroup; + children = ( + EA054FA72A4E52B10046251E /* WhatsNewDemoApp.swift */, + EA054FBD2A4E53D00046251E /* PageView.swift */, + EA054FA92A4E52B10046251E /* ContentView.swift */, + EA054FAB2A4E52B20046251E /* Assets.xcassets */, + EA054FAD2A4E52B20046251E /* WhatsNewDemo.entitlements */, + EA054FAE2A4E52B20046251E /* Preview Content */, + ); + path = WhatsNewDemo; + sourceTree = ""; + }; + EA054FAE2A4E52B20046251E /* Preview Content */ = { + isa = PBXGroup; + children = ( + EA054FAF2A4E52B20046251E /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + EAC88E282A4E87870030F8F7 /* Packages */ = { + isa = PBXGroup; + children = ( + EAC88E292A4E87870030F8F7 /* WhatsNew */, + ); + name = Packages; + sourceTree = ""; + }; + EAC88E2A2A4E894C0030F8F7 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + EA054FA32A4E52B10046251E /* WhatsNewDemo */ = { + isa = PBXNativeTarget; + buildConfigurationList = EA054FB32A4E52B20046251E /* Build configuration list for PBXNativeTarget "WhatsNewDemo" */; + buildPhases = ( + EA054FA02A4E52B10046251E /* Sources */, + EA054FA12A4E52B10046251E /* Frameworks */, + EA054FA22A4E52B10046251E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = WhatsNewDemo; + packageProductDependencies = ( + EAC88E2B2A4E894C0030F8F7 /* WhatsNew */, + ); + productName = WhatsNewDemo; + productReference = EA054FA42A4E52B10046251E /* WhatsNewDemo.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + EA054F9C2A4E52B10046251E /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1500; + LastUpgradeCheck = 1500; + TargetAttributes = { + EA054FA32A4E52B10046251E = { + CreatedOnToolsVersion = 15.0; + }; + }; + }; + buildConfigurationList = EA054F9F2A4E52B10046251E /* Build configuration list for PBXProject "WhatsNewDemo" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + fr, + es, + "pt-BR", + it, + de, + nl, + ); + mainGroup = EA054F9B2A4E52B10046251E; + productRefGroup = EA054FA52A4E52B10046251E /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + EA054FA32A4E52B10046251E /* WhatsNewDemo */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + EA054FA22A4E52B10046251E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EA63674D2A4E8AD60075B9B5 /* Preview Assets.xcassets in Resources */, + EA054FAC2A4E52B20046251E /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + EA054FA02A4E52B10046251E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EA054FAA2A4E52B10046251E /* ContentView.swift in Sources */, + EA054FBE2A4E53D00046251E /* PageView.swift in Sources */, + EA054FA82A4E52B10046251E /* WhatsNewDemoApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + EA054FB12A4E52B20046251E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + EA054FB22A4E52B20046251E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + EA054FB42A4E52B20046251E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = WhatsNewDemo/WhatsNewDemo.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"WhatsNewDemo/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 12.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.catloafsoft.WhatsNewDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3"; + TVOS_DEPLOYMENT_TARGET = 15.0; + }; + name = Debug; + }; + EA054FB52A4E52B20046251E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = WhatsNewDemo/WhatsNewDemo.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"WhatsNewDemo/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 12.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.catloafsoft.WhatsNewDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3"; + TVOS_DEPLOYMENT_TARGET = 15.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + EA054F9F2A4E52B10046251E /* Build configuration list for PBXProject "WhatsNewDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EA054FB12A4E52B20046251E /* Debug */, + EA054FB22A4E52B20046251E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + EA054FB32A4E52B20046251E /* Build configuration list for PBXNativeTarget "WhatsNewDemo" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EA054FB42A4E52B20046251E /* Debug */, + EA054FB52A4E52B20046251E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCSwiftPackageProductDependency section */ + EAC88E2B2A4E894C0030F8F7 /* WhatsNew */ = { + isa = XCSwiftPackageProductDependency; + productName = WhatsNew; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = EA054F9C2A4E52B10046251E /* Project object */; +} diff --git a/WhatsNewDemo/WhatsNewDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/WhatsNewDemo/WhatsNewDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/WhatsNewDemo/WhatsNewDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/WhatsNewDemo/WhatsNewDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/WhatsNewDemo/WhatsNewDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/WhatsNewDemo/WhatsNewDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/WhatsNewDemo/WhatsNewDemo/Assets.xcassets/AccentColor.colorset/Contents.json b/WhatsNewDemo/WhatsNewDemo/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..1fc8b3e --- /dev/null +++ b/WhatsNewDemo/WhatsNewDemo/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,15 @@ +{ + "colors" : [ + { + "color" : { + "platform" : "universal", + "reference" : "secondaryLabelColor" + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WhatsNewDemo/WhatsNewDemo/Assets.xcassets/AppIcon.appiconset/Contents.json b/WhatsNewDemo/WhatsNewDemo/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..532cd72 --- /dev/null +++ b/WhatsNewDemo/WhatsNewDemo/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,63 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WhatsNewDemo/WhatsNewDemo/Assets.xcassets/Contents.json b/WhatsNewDemo/WhatsNewDemo/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/WhatsNewDemo/WhatsNewDemo/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WhatsNewDemo/WhatsNewDemo/ContentView.swift b/WhatsNewDemo/WhatsNewDemo/ContentView.swift new file mode 100644 index 0000000..7894092 --- /dev/null +++ b/WhatsNewDemo/WhatsNewDemo/ContentView.swift @@ -0,0 +1,26 @@ +// +// ContentView.swift +// WhatsNewDemo +// +// Created by Stéphane Peter on 6/29/23. +// + +import SwiftUI + +struct ContentView: View { + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + } + .padding() + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/WhatsNewDemo/WhatsNewDemo/PageView.swift b/WhatsNewDemo/WhatsNewDemo/PageView.swift new file mode 100644 index 0000000..d01a648 --- /dev/null +++ b/WhatsNewDemo/WhatsNewDemo/PageView.swift @@ -0,0 +1,38 @@ +// +// PageView.swift +// WhatsNewDemo +// +// Created by Stéphane Peter on 6/29/23. +// + +import SwiftUI +import WhatsNew + +struct PageView: View { + let page: Int + + var body: some View { + VStack (alignment: .leading, spacing: 10){ + Text("This is page \(page)") + .font(.title) + .multilineTextAlignment(.center) + + BulletPointView(title: "We now have SEARCH!!!", + systemName: "paintbrush.fill", + text: "Search to find books that have been on previous best seller lists.") + BulletPointView(title: "More bugs squashed.", + imageName: "Truck", + text: "And the hits keep coming") + + Spacer() + } + .padding() + .accentColor(Color.red) + } +} + +struct PageView_Previews: PreviewProvider { + static var previews: some View { + PageView(page: 1) + } +} diff --git a/WhatsNewDemo/WhatsNewDemo/Preview Content/Preview Assets.xcassets/Contents.json b/WhatsNewDemo/WhatsNewDemo/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/WhatsNewDemo/WhatsNewDemo/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/WhatsNewDemo/WhatsNewDemo/WhatsNewDemo.entitlements b/WhatsNewDemo/WhatsNewDemo/WhatsNewDemo.entitlements new file mode 100644 index 0000000..f2ef3ae --- /dev/null +++ b/WhatsNewDemo/WhatsNewDemo/WhatsNewDemo.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + diff --git a/WhatsNewDemo/WhatsNewDemo/WhatsNewDemoApp.swift b/WhatsNewDemo/WhatsNewDemo/WhatsNewDemoApp.swift new file mode 100644 index 0000000..2b00767 --- /dev/null +++ b/WhatsNewDemo/WhatsNewDemo/WhatsNewDemoApp.swift @@ -0,0 +1,30 @@ +// +// WhatsNewDemoApp.swift +// WhatsNewDemo +// +// Created by Stéphane Peter on 6/29/23. +// + +import SwiftUI +import WhatsNew + +@main +struct WhatsNewDemoApp: App { + let whatsNew = WhatsNew(alwaysShow: true) + @State private var showWhatsNew = false + + var body: some Scene { + WindowGroup { + ContentView() + .onAppear { + whatsNew.checkForUpdate(showWhatsNew: $showWhatsNew) + } + .sheet(isPresented: $showWhatsNew) { + WhatsNewView { + PageView(page: 1) + PageView(page: 2) + } + } + } + } +}